Configurable Stack Size
Posted: Sat Apr 17, 2021 6:48 am
As mentioned in here, my first real use of creating my own boot file was to make the stack size configurable. To tie up that loose end, here's how. It should basically apply to Project Oberon as well, but I'll focus on Embedded Oberon.
The stack size is hard-coded to 32 kB (8000H) in two places.
Kernel.Init:
Modules.Init:
My motivation is that on a device with limited RAM per CPU, I'd like to reduce the stack size if I am sure that my commands, tasks, and the related dynamic loading of modules don't need 32k stack. Embedded Oberon requires about 10k stack space to even start up, as measured using my stack overflow monitor, a hardware device in the FPGA.
Interestingly, the (final) stack size could be set lower than the 10k -- if we're sure all modules loaded upon system start don't use the addresses the range Kernel.stackOrg - 10k. Nothing stops the stack pointer of "reaching" into module memory space (unless we have some stack overflow protection in place and active). As long as that space is not used by modules, nothing bad will happen. Hence, the stack size could be set to, say, 8k, and upon startup, the stack would temporarily "borrow" some 2k of module space.
The required changes should be obvious and straight-forward:
A new boot file file needs to be created of course.
Alternatively, the stack size can be passed from the boot loader, together with the values for MemLim and stackOrg. Address 4 can be used for that. It works well, too. This has the advantage that the memory configuration parameters for different boards and RAM layouts are in one place, but it requires to recreate the FPGA implementation for a stack size change, as we do now for, say, a heap size change. The boot file is the same for all memory layout variants. I am not sure yet which of the two solutions I prefer.
For module Modules the required change is as above. Address 4, later reserved for the interrupt handler, is not used while initialising the inner core until Kernel.Init can grab the value (Embedded Oberon, Astrobe compiler).
A concluding note about really, really squeezing out the last byte of available RAM for the stack.
The body of Modules:
The body of Oberon:
This call chain leaves the stack pointer value just before calling 'Loop' at the value it had when the body of Modules loaded Oberon, and the stack space above is "lost", unless Oberon.Reset is executed at some point later from the trap or abort handlers, respectively:
So if, or as soon as, we're sure that 'Load("Oberon")' will work correctly, and not return to its call in the body of Modules, we could do in the body of Oberon what Oberon.Reset does:
This will reclaim the stack space "above", a whopping 276 bytes on an unmodified EO system.
Of course, executing the command 'Oberon.Reset' from the Astrobe terminal has the same effect.
The stack size is hard-coded to 32 kB (8000H) in two places.
Kernel.Init:
Code: Select all
PROCEDURE Init*;
BEGIN
(* ... *)
stackSize := 8000H;
(* ... *)
END Init;
Code: Select all
PROCEDURE Init*;
BEGIN
(* ... *) DEC(limit, 8000H)
END Init;
Interestingly, the (final) stack size could be set lower than the 10k -- if we're sure all modules loaded upon system start don't use the addresses the range Kernel.stackOrg - 10k. Nothing stops the stack pointer of "reaching" into module memory space (unless we have some stack overflow protection in place and active). As long as that space is not used by modules, nothing bad will happen. Hence, the stack size could be set to, say, 8k, and upon startup, the stack would temporarily "borrow" some 2k of module space.
The required changes should be obvious and straight-forward:
Code: Select all
MODULE Kernel;
(* ... *)
CONST StackSize = 4000H; (* 16k *)
(* ... *)
PROCEDURE Init*;
BEGIN
(* ... *)
stackSize := StackSize;
(* ... *)
END Init;
END Kernel.
Code: Select all
MODULE Modules;
IMPORT SYSTEM, Files, Kernel;
(* ... *)
PROCEDURE Init*;
BEGIN
(* .. *) DEC(limit, Kernel.stackSize)
END Init;
(* ... *)
END Modules.
Alternatively, the stack size can be passed from the boot loader, together with the values for MemLim and stackOrg. Address 4 can be used for that. It works well, too. This has the advantage that the memory configuration parameters for different boards and RAM layouts are in one place, but it requires to recreate the FPGA implementation for a stack size change, as we do now for, say, a heap size change. The boot file is the same for all memory layout variants. I am not sure yet which of the two solutions I prefer.
Code: Select all
MODULE* BootLoad;
(* ...*)
CONST StackSize = 4000H;
(* ... *)
BEGIN
(* ... *)
SYSTEM.PUT(12, MemLim); SYSTEM.PUT(24, stackOrg); SYSTEM.PUT(4, StackSize); LED(84H)
END BootLoad.
Code: Select all
MODULE Kernel;
(* ... *)
PROCEDURE Init*;
BEGIN
(* ... *)
SYSTEM.GET(4, stackSize)
(* ... *)
END Init;
END Kernel.
A concluding note about really, really squeezing out the last byte of available RAM for the stack.
The body of Modules:
Code: Select all
BEGIN Init; Load("Oberon", M);
LED(res); REPEAT UNTIL FALSE (*only if load fails*)
END Modules.
Code: Select all
BEGIN
(* ... *)
Modules.Load("System", Mod); Mod := NIL;
Loop
END Oberon.
Code: Select all
PROCEDURE Reset*;
BEGIN
IF CurTask.state = active THEN Remove(CurTask) END ;
SYSTEM.LDREG(14, Kernel.stackOrg); (*reset stack pointer*) Loop
END Reset;
Code: Select all
BEGIN
(* ... *)
Modules.Load("System", Mod); Mod := NIL;
SYSTEM.LDREG(14, Kernel.stackOrg);
Loop
END Oberon.
Of course, executing the command 'Oberon.Reset' from the Astrobe terminal has the same effect.