Configurable Stack Size

General discussions about using the Astrobe IDE to program the FPGA RISC5 cpu used in Project Oberon 2013
Post Reply
gray
Posts: 71
Joined: Tue Feb 12, 2019 2:59 am
Location: Mauritius

Configurable Stack Size

Post by gray » 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:

Code: Select all

PROCEDURE Init*;
BEGIN
  (* ... *)
  stackSize := 8000H;
  (* ... *)
END Init;
Modules.Init:

Code: Select all

PROCEDURE Init*;
BEGIN
  (* ... *) DEC(limit, 8000H)
END 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:

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.
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.

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.
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:

Code: Select all

BEGIN Init; Load("Oberon", M);
  LED(res); REPEAT UNTIL FALSE  (*only if load fails*)
END Modules.
The body of Oberon:

Code: Select all

BEGIN
  (* ... *)
  Modules.Load("System", Mod); Mod := NIL;
  Loop
END 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:

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;
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:

Code: Select all

BEGIN
  (* ... *)
  Modules.Load("System", Mod); Mod := NIL;
  SYSTEM.LDREG(14, Kernel.stackOrg);
  Loop
END Oberon.
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.

Post Reply