Stack Size

A private (members-only) forum for discussing all issues related to the Beta test of Native mode devices.
mikep
Posts: 796
Joined: 24 September 2005, 15:54 PM

Stack Size

Post by mikep »

I'm getting stack size errors for some of my tasks when they are compiled with the native mode compiler.

I usually use a stack margin of 5. For example one task has a stack declared as 45 bytes. This is the bare minimum in ZVM mode. In native mode the compiler is asking for a stack size of 55.

I suppose the easiest way is to add a condition around the declaration of the stack size constant. There should be a comment on this in section 4.6 of the documentation.

It also presents a potential problem because a program compiled by ZVM may not have enough RAM in native mode.
Mike Perks
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Re: Stack Size

Post by dkinzer »

mikep wrote:In native mode the compiler is asking for a stack size of 55.
It is true that in native mode the stack sizes need to be significantly larger. As described in the Native Mode Many Tasks post, this is due to two factors: the task context is much larger and each task needs stack additional space to allow interrupts to be serviced. (In VM mode, the interrupt service stack use is from the system stack.)

I would suggest a minimum task stack size of 100 to 200 bytes. You can then use System.TaskHeadRoom to determine how much stack space is used and pare it back to something closer to the minimum.
- Don Kinzer
mikep
Posts: 796
Joined: 24 September 2005, 15:54 PM

Re: Stack Size

Post by mikep »

dkinzer wrote:I would suggest a minimum task stack size of 100 to 200 bytes. You can then use System.TaskHeadRoom to determine how much stack space is used and pare it back to something closer to the minimum.
That's all fine and easy to cope with. We just need some additional documentation to explain the "incompatibility" and how to deal with it e.g. with conditional compile.
Mike Perks
stevech
Posts: 715
Joined: 22 February 2006, 20:56 PM

Post by stevech »

what about the provision for stack space for interrupts? Seems to me that the interrupted task's state has to be at least partially pushed, before changing to a kernel mode stack.
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

stevech wrote:what about the provision for stack space for interrupts? Seems to me that the interrupted task's state has to be at least partially pushed, before changing to a kernel mode stack.
There is no kernel stack. Each task stack must have enough headroom to accommodate the stack needs of any ISR (system or user-provided) that might run during its execution.

The current implementations of system-provided ISRs use stack space as shown in the table below. User-written ISRs are likely to need more stack space unless they're written in assembly language.

[table][mrow]ISR Type[col]Stack Use
[row]RTC[col]9
[row]UART[col]10
[row]HW Interrupts[col]10
[row]OutputCapture[col]11
[row]InputCapture[col]12
[row]SW UART[col]13
[row]X-10[col]14[/table]
- Don Kinzer
mikep
Posts: 796
Joined: 24 September 2005, 15:54 PM

Post by mikep »

stevech wrote:what about the provision for stack space for interrupts? Seems to me that the interrupted task's state has to be at least partially pushed, before changing to a kernel mode stack.
The stack size for a ZVM task is always 15 bytes larger than it needs to be. Six of these additional 15 bytes are used to hold the task's virtual machine context (IP, SP, BP). See section 3.27.1 for details on the TCB.

I think ISRs are always handled using the stack of the currently active task rather than trying to do a (more expensive) context switch first. This means that for native mode devices the stack space for a task is always 15 bytes larger than the ZVM stack. These 15 bytes are used to store registers in an ISR and of course only one ISR can be active at a time. This is the default code provided by GCC and consists of pushes of the registers r0, r1, SREG, r18-r27, r30, and r31. Don has already noted the stack sizes needed by the native system provided ISRs.

Edit: Clarified sizes of TCBs and where to find them in the documentation.
Last edited by mikep on 10 February 2008, 19:20 PM, edited 3 times in total.
Mike Perks
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

In VM mode devices, there is a "system stack" that is completely separate from the task stack. The system stack is actually the AVR hardware stack and it is used for the call/return and register storage needed by the system routines. The task stack is a virtual stack that is not related in any way to the AVR hardware stack. In fact, it moves in the opposite direction as compared to the hardware stack.

In native mode devices, there is only the hardware stack - no virtual stacks. When a task is started, its task stack is set up with the necessary information to begin running the task when its time comes up. When a task's time slice is done (or is otherwise caused to suspend), the entire AVR context is pushed on that task's stack. The context consists of 32 general purpose registers, the status register and the address at which to resume exectuion - 35 bytes total. After all of the context information is pushed, the stack pointer (SP register) is stored in the task's Task Control Block. For the task being activated, this process is reversed.

In contrast, the context for a VM device is 6 bytes and it is stored entirely in the Task Control Block - no virtual stack space is used.

It would have been possible to store the native mode context in the Task Control Block, too, but it was cleaner (and faster) to just use the hardware stack since context switches often begin within an ISR where part of the context has already been saved on the stack.
- Don Kinzer
mikep
Posts: 796
Joined: 24 September 2005, 15:54 PM

Post by mikep »

From the manytasks thread:
dkinzer wrote:One of the differences with native mode is that the minimum task size is much larger due to two factors. Firstly, when a task switch is done, the context of the current task is saved to its task stack. This requires 35 bytes (32 GP registers, IP, and status register). Secondly, whenever an interrupt occurs, the ISR saves whatever registers it needs to save on the stack of the currently executing task. I'd have to confirm the actual maximum but I suspect that this may require at most 10-15 bytes. Interrupts are not nested so the maximum stack use due to interrupts is the largest stack use of any ISR.
I have read these posts several times and still don't quite understand it.
dkinzer wrote:In native mode devices, there is only the hardware stack - no virtual stacks. When a task is started, its task stack is set up with the necessary information to begin running the task when its time comes up. When a task's time slice is done (or is otherwise caused to suspend), the entire AVR context is pushed on that task's stack.
So there is only one hardware stack in native mode. Each task context is stored in the task "stack" probably with a link to the next task and some status flags - 38 bytes. You also indicated in the manytasks thread that during an ISR, the registers are saved onto the stack as well. This doesn't seem to be case for user written ISRs. The manythreads comment in bold above doesn't seem to match the library code and is inconsistent with user written ISRs so I am confused.

So back to my original question. Why is the native mode compiler asking for a minimum stack size of 60 bytes? I have only accounted for 38 bytes. What is stored in the remainder?

It also seems that the StackMargin option has no affect on the native mode compiler which also requires a minimum stack size of 60.

I notice that in the manytasks.bas file the stack is defined as 70 bytes long. Based on current information I have reduced the stack size to 60 and it still works. Actually 59 works as well but not 58.
Mike Perks
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

mikep wrote:Why is the native mode compiler asking for a minimum stack size of 60 bytes? I have only accounted for 38 bytes. What is stored in the remainder?
The AVR hardware stack is used for each task's stack but each task has its own separate stack space. When a task switch is performed, the current task's context is pushed on its stack and the stack pointer is stored in the task's TCB. Then, the SP for task being activated is retrieved from its TCB and stored in the AVR's SP register and then the task's context is popped from its stack after which execution continues from where it was suspended.

During execution for a particular task, if an interrupt occurs the ISR saves registers on that task's stack, does its work, restores the registers and returns back to the task. As the task calls system routines or user routines, return addresses and register saves are performed on the current task's stack. Note that it is possible for a task switch to occur when the current task is deeply nested into system or user routines so the task's context is stacked on top of that normal call/return context.

The task stack requires space for the Task Control Block (12 bytes), space for the task context when the task is suspended (35 bytes), space for invoking system and user-written functions (an unknown amount), and space for registers saved by an ISR (currently 14 bytes maximum for system ISRs, an unknown amount for user-supplied ISRs. The parts that are of known size aggregate to 61 bytes so that's why the stack shouldn't be less than 60 bytes and should probably be larger to allow for the normal nested call/return sequence.

As a side issue, the Task Control Block is located at the end of the task's stack space and the AVR's stack pointer for the task is initialized to the address just before the TCB. The stack grows toward lower addresses as data is pushed onto it. If the stack overflows, it will overwrite other statically allocated data (if the task stack is statically allocated) or other dynamically allocated blocks (if the task stack is dynamically allocated).
mikep wrote:It also seems that the StackMargin option has no affect on the native mode compiler which also requires a minimum stack size of 60.
For VM devices, the StackMargin is added to the estimated task stack size that is calculated by the compiler. Since there is currently no estimate of required stack size produced for native mode devices, the StackMargin has no useful purpose.
mikep wrote:I have reduced the stack size to 60 and it still works.
It would be interesting to check the task stack headroom. See the description of System.TaskHeadRoom() it will give you a snapshot of the amount of never-been-used space in the stack.
- Don Kinzer
mikep
Posts: 796
Joined: 24 September 2005, 15:54 PM

Post by mikep »

dkinzer wrote:The AVR hardware stack is used for each task's stack but each task has its own separate stack space.

The task stack requires space for the Task Control Block (12 bytes), space for the task context when the task is suspended (35 bytes), space for invoking system and user-written functions (an unknown amount), and space for registers saved by an ISR (currently 14 bytes maximum for system ISRs, an unknown amount for user-supplied ISRs. The parts that are of known size aggregate to 61 bytes so that's why the stack shouldn't be less than 60 bytes and should probably be larger to allow for the normal nested call/return sequence.
Ok I understand now. So the TCB for the main task is just before the heap and the stack pointer works down while the heap works upwards to the top of memory. If you have extended RAM then potentially your main task stack is in extended memory unless you define a really large heap.
dkinzer wrote:It would be interesting to check the task stack headroom. See the description of System.TaskHeadRoom() it will give you a snapshot of the amount of never-been-used space in the stack.
I added a call to TaskHeadroom on every iteration. Here is a table of stack sizes and headroom:
[table]
[mrow]Stack Size[mcol]Reported TaskHeadroom[mcol]Minimum Size
[row]53[col]0, then 65535 then hang[col]N/A
[row]54[col]Hang[col]N/A
[row]55[col]0[col]55
[row]60[col]7 then goes down to 5[col]55
[row]70[col]15[col]55
[row]80[col]27[col]53
[row]90[col]35[col]55
[row]100[col]Hang[col]unknown
[row]110[col]55[col]55[/table]
The tasks seem to run just fine with a stack of 55 which is the calculated minimum value using the System.TaskHeadroom call. The results for 53 and 54 are problematic as expected.

I cannot yet explain why stack sizes of 98 through 100 fail.

The result for 60 corrects itself after 2 iterations but the result for 80 is off by 2 bytes.

The fixed size of the task stack is 12 + 35 = 47 bytes. The variable size which includes ISRs and calling functions is only 9 bytes. I calculated this as the maximum of the ISRs used which is the RTC. The UART is used but only in the main task so it has no affect on the task stacks. This can be double-checked by waiting in the main task until the transmit queue is empty.

Hence the maximum stack for each task is 47+9 = 56 bytes. I think that 55 is working because the stack is overwriting at least two bytes which is the length of the allocated heap block.
Mike Perks
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

mikep wrote:[...] the heap works upwards to the top of memory.
Actually, the heap grows from the end of RAM (the end of external RAM if you have it) toward zero. The heap manager is a modified version of that supplied with avr-libc which has the heap growing in the opposite direction.
mikep wrote:If you have extended RAM then potentially your main task stack is in extended memory unless you define a really large heap.
That's true. There is a way to specify that all external RAM be dedicated to the heap. That would place the Main() TCB right at the end of internal RAM.
mikep wrote:I cannot yet explain why stack sizes of 98 through 100 fail.
That is odd.
- Don Kinzer
stevech
Posts: 715
Joined: 22 February 2006, 20:56 PM

Post by stevech »

none of the ISRs enable interrupts?
Doing so would use more stack for nesting.
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

stevech wrote:none of the ISRs enable interrupts?
That is correct. Nested interrupts would complicate things greatly.
- Don Kinzer
mikep
Posts: 796
Joined: 24 September 2005, 15:54 PM

Post by mikep »

dkinzer wrote:There is a way to specify that all external RAM be dedicated to the heap. That would place the Main() TCB right at the end of internal RAM.
How? What about an explicit placement of the heap i.e defining the size of RAM that can be used?
Mike Perks
mikep
Posts: 796
Joined: 24 September 2005, 15:54 PM

Post by mikep »

stevech wrote:none of the ISRs enable interrupts?
Doing so would use more stack for nesting.
It is generally good AVR programming practice to avoid nested ISRs.
Mike Perks
Locked