SIMD-0166

SBPF Dynamic stack frames

Author: Alexander Meißner, Alessandro Decina, Lucas Steuernagel · Category: Core Protocol GitHub →

Feature Gate Status

Mainnet Active E809
Testnet Active E784
Devnet Active E902

JE86WkYvTrzW8HgNmrHY7dFYpCmSptUpKupbo2AdQ9cG

TL;DR

The SVM currently allocates a fixed amount of stack space to each function frame. We propose allowing programs to dynamically manage their stack space through the introduction of an explicit stack pointer.

Summary

The SVM currently allocates a fixed amount of stack space to each function frame. We propose allowing programs to dynamically manage their stack space through the introduction of an explicit stack pointer.

Motivation

The SVM allocates a fixed amount of memory to hold a program’s stack. Within the stack region, the virtual machine reserves 4096 bytes of stack space for each function frame. This is simultaneously limiting for functions that require more space, and wasteful for functions that require less space. For well optimized programs that don’t allocate large amounts of stack, the virtual machine currently still reserves 4096 bytes of stack for each function call, leading to suboptimal memory usage, which may cause unnecessary page faults. On the other hand, some programs are known to create large function frames - this seems common with programs that serialize a lot of data - and they have to jump through hoops to avoid overflowing the stack. The virtual machine detects when a stack overflow occurs, and it does so by implementing a stack frame gaps system whereby it inserts a virtual sentinel frame following a valid function frame. If the sentinel frame is accessed, the executing program is aborted. This system is fragile and is incompatible with direct mapping - a feature we expect to enable soon. The changes proposed in this document would allow us to optimize stack memory usage and remove the fragile stack frame gaps system. Note that we do not propose to remove the existing maximum stack space limit: stack space stays unchanged, what changes is how it is partitioned internally.

Impact

We foresee a positive impact in smart contract development. Developers won’t need to worry about exceeding the maximum frame space allowed for a function and won’t face any case of stack access violation if their code follows conventional Rust safety rules. Likewise, when we update the Rust version of our platform tools, developers will not have the burden of modifying their contract just because the newer version is using more stack than the previous one, often reaching the 4096 bytes limit. Refer to issues [#1186](https://github.com/anza-xyz/agave/issues/1186) and [#1158](https://github.com/anza-xyz/agave/issues/1158). We also expect some improvements in program execution. For function calls that handle more than five arguments, there will be one less store and one less load operation due to the new call convention.

Security Considerations

Stack gaps will be disabled for dynamic stack frames to work. Stack gaps could detect invalid accesses between two function frames, if the accessed address would fall between them. With dynamic stack frames, all stack access will be valid, provided that their address is within the allowed range. We already allow functions to read and modify the memory inside the frame of other functions, so removing the stack gaps should not bring any security implications. Although one can change R10 to almost any value that fits in a 64-bit integer with `add64 reg, imm`, every memory access is verified, so there is no risk of invalid accesses from a corrupt register.