Thursday 14 June 2018

Secondary Stack in Cortex GNAT RTS

In GNAT, the secondary stack is a construct used with indeterminate types. For example, if a function returns a String, it isn't possible for the caller to determine how much space to reserve for the result: instead, the called function allocates the amount of space required on the secondary stack, and on return the caller determines how much space to allocate on the normal (primary) stack, and pops the function's result from the secondary stack to there.

This note discusses how the secondary stack is managed in Cortex GNAT RTS for FSF GCC and GNAT Community Edition (was GNAT GPL).

User interface

There are three levels to date:

  • GCC6 (also covers GNAT GPL 2016)
  • GCC7 (from this point of view, also covers GNAT GPL 2017)
  • GCC8 (also covers GNAT CE 2018)

One control available at all levels is the binder switch -Dnn[k|m], which specifies the default secondary stack size to be nn [kilo|mega] bytes.

I strongly suggest not using -D; it's aimed at the full runtime, and allocates a lot of space that will be wasted in this runtime (21 times the specified size).

Additionally, GCC7 and GCC8 both offer the task aspect Secondary_Stack_Size.

In Cortex GNAT RTS, up to and including Release 2018-06-07, Secondary_Stack_Size is ignored. Instead, the default action is taken.

Default behaviour (absent Secondary_Stack_Size)

A task's secondary stack is carved out of its primary stack (it's placed above the stack as seen by the task on entry, and grows upward (towards higher addresses), so if you get a primary stack overflow it'll smash another task's secondary stack. Secondary stack overflow is detected, and results in Storage_Error).

The proportion allocated is controlled by the function System.Parameters.Secondary_Stack_Size, and is currently 10% (unless you've specified -D to the binder).

Secondary_Stack_Size handling

Available in Release 2018-06-14.

GCC7

The secondary stack is still carved out of the primary stack, but is of the size specified (rather than 10%, as above).

GCC8

The secondary stack is allocated by the compiler statically in the package in which the task is declared. This is OK in Ravenscar (tasks have to be created at library level, and mustn't terminate), but clearly can't be how secondary stacks are handled in a full Ada RTS.

The details

System.Parameters

This discussion omits items referenced by the binder but not used.

Unspecified_Size
A constant used to specify "use the default value".
Secondary_Stack_Size
A function which determines how much of a task's stack is to be used for the secondary stack (unless the aspect Secondary_Stack_Size has been specified).
Runtime_Default_Sec_Stack_Size
The default size for secondary stacks; the default (0) means to use 10% of the task's stack.
Default_Secondary_Stack_Size
Written by the binder, to Runtime_Default_Sec_Stack_Size unless -D is given. This is the value used by Secondary_Stack_Size to determine how much stack to allocate.

System.Tasking.Restricted.Stages

The important subprograms are Create_Restricted_Task_Sequential and Create_Restricted_Task. From the secondary stack point of view, these are the same (the _Sequential version delays task activation until elaboration is complete).

GCC7

procedure Create_Restricted_Task_Sequential
  (Priority             : Integer;
   Stack_Address        : System.Address;
   Size                 : System.Parameters.Size_Type;
   Secondary_Stack_Size : System.Parameters.Size_Type;
   Task_Info            : System.Task_Info.Task_Info_Type;
   CPU                  : Integer;
   State                : Task_Procedure_Access;
   Discriminants        : System.Address;
   Elaborated           : Access_Boolean;
   Task_Image           : String;
   Created_Task         : Task_Id);

The parameter Secondary_Stack_Size, if not Unspecified_Size, specifies the actual size of the secondary stack required. In either case, the wrapper code which is given to FreeRTOS as the FreeRTOS task body creates a secondary stack of the appropriate size on the task stack before calling the Ada task body.

GCC8

procedure Create_Restricted_Task_Sequential
  (Priority             : Integer;
   Stack_Address        : System.Address;
   Size                 : System.Parameters.Size_Type;
   Sec_Stack_Address    : System.Secondary_Stack.SS_Stack_Ptr;
   Secondary_Stack_Size : System.Parameters.Size_Type;
   Task_Info            : System.Task_Info.Task_Info_Type;
   CPU                  : Integer;
   State                : Task_Procedure_Access;
   Discriminants        : System.Address;
   Elaborated           : Access_Boolean;
   Task_Image           : String;
   Created_Task         : Task_Id);

In this, Sec_Stack_Address is set null if aspect Secondary_Stack_Size has not been set, in which case Secondary_Stack_Size is set to Unspecified_Size. The wrapper code which is given to FreeRTOS as the FreeRTOS task body creates a secondary stack of the appropriate size on the task stack before calling the Ada task body.

If aspect Secondary_Stack_Size has been set, then Sec_Stack_Address is not null, and points to a secondary stack of the correct size which has already been allocated by the compiler. In this case, the parameter Secondary_Stack_Size isn't relevant.

No comments:

Post a Comment