Reputation: 141
For RISC-V, does the stack pointer point at the last data that was pushed on the stack, or the next free address location for the stack?
When the stack pointer is being initialized at the very beginning of the program (e.g. crt.S) (i.e. stack is empty), should the stack pointer be initialized to point at the memory location where the first word will get pushed or the address before? (For example, let's say the very first element of the stack will be pushed at 4092. Then, does the stack pointer start at 4096 or 4092?)
A pointer to where this is defined will be greatly appreciated.
Upvotes: 7
Views: 12675
Reputation: 26706
This is not specified by the instruction set architecture and as far as the ISA is concerned, the stack can be full or empty, descending or ascending — the RISC V instruction set favors no particular arrangement. These terms (full/empty, ascending/descending) are commonly used in ARM instruction set: full means the stack pointer points to an occupied/non-free location whereas empty means the the stack pointer points to the first available word.
However, the stack direction & whether the stack pointer refers to the first word in use or first free word is specified by the RISC V calling convention, which is part of the ABI (application binary interface).
RISC V's standard calling convention uses the stack like MIPS, and that is a full descending approach, which means that the stack pointer points to the last word in use, and free memory is at addresses lower than the current value in the stack pointer register.
If you want to use stack space, then decrement the stack pointer and that new memory between the new stack pointer value and the old one is yours to use; just release it before returning to the caller so that the caller has the same stack pointer value as it gave you. (Also don't write to the memory pointed by the initial undecremented pointer as from where it points and above are all considered in use.)
So, the initial stack pointer can point just past the end of the stack, to a word that is not available to use, not expected to be used, and software should decrement first, to find stack space to use.
From: https://github.com/riscv/riscv-elf-psabi-doc/blob/master/riscv-elf.md
The stack grows downwards (towards lower addresses) and the stack pointer shall be aligned to a 128-bit boundary upon procedure entry. The first argument passed on the stack is located at offset zero of the stack pointer on function entry; following arguments are stored at correspondingly higher addresses.
and further:
Procedures must not rely upon the persistence of stack-allocated data whose addresses lie below the stack pointer.
Suffice it to say that offset 0 relative to the address held in the stack pointer register belongs to the caller in terms of who owns the memory (and is therefore responsible for deallocation of that memory) whether or not any arguments are actually passed on the stack. (If there are one or more parameters passed on the stack, the callee is allowed to modify these memory values at will, but the memory and its deallocation still belongs to the caller; obviously, callees must not modify non-parameter memory at or above the stack pointer.)
To allocate a single word on the stack, first decrement the stack pointer register, sp
, then use location 0 relative to sp
. Other callers will preserve this memory. Upon return the sp
should be returned to its original value held at function entry.
Typically on MIPS & RISC V, when stack space is needed, the assembly language programmer or the compiler will compute the maximum stack space needed for all local storage and for performing function calls (i.e. for excess parameters), and allocate that amount of space in a single instruction in prologue and deallocate with a single instruction in epilogue.
the caller will allocate stack space just once in prologue and release it just once in epilogue rather than repeatedly decrementing the stack pointer register.
(RISC V also provides for no red-zone, which, for other ABIs, is some space below the stack pointer guaranteed to be useable without having signal handlers or anything else overwrite it asynchronously.)
Some simulators manage stack space in strange ways (such as on change to the stack pointer register value — rather than on actual writes to the stack space, which is more how the hardware would work), so these choose to waste a word rather than having the initial stack pointer refer to a memory address on a different page/section or potentially even referring to non-existent memory, even though software running on the simulator should decrement the sp
before using stack space, which would bring the stack pointer to valid memory area, so that only valid memory is actually being accessed.
Upvotes: 11