Browse the docs

silk.mem.stack.

The native stack of the running thread: the address of the current frame, and scratch storage that lives on the stack instead of in an allocator.

When to use#

Use Stack.withStorage for a small scratch buffer whose size is known only at run time and that must not allocate. Use Stack.frameAddress to find where the running code is on the stack, for example to measure how deep a recursion went or to bound a conservative scan of the stack.

Details#

The stack of every supported target grows down: a function that calls another has its frame at the same or higher addresses than the callee. Stack storage is not initialized and has no cleanup. The compiler does not check how much stack is left, so a large request can run past the end of the stack. That is why Stack.withStorage is unsafe.

Examples#

Sum values in stack scratch storage#

silk
import silk.mem.pointer { Pointer }
import silk.mem.stack { Stack }

fn fill(storage: [*]mut i32) -> i32 {
  let mut total = 0
  let mut at: usize = 0
  while at < 4 {
    unsafe {
      let slot = Pointer.atMut<i32>(storage, at)
      Pointer.write<i32>(slot, 10)
      total = total + Pointer.read<i32>(slot)
    }
    at = at + 1
  }
  return total
}

pub fn main() -> i32 {
  let total = unsafe Stack.withStorage<i32, i32>(4, fill)
  if total != 40 {
    return 1
  }
  return 0
}

Import as Stack with import silk.mem.stack { Stack }.

Public declarations: 1.

Stack#

silk
pub struct Stack

Operations on the native stack of the running thread.

Implementation Stack#

silk
impl Stack

Associated function frameAddress#

silk
pub fn frameAddress() -> [*]const u8

Returns the address of the stack frame of the function that runs this call.

Details#

The address is inside the running thread's stack. The stack grows down on every supported target, so every frame of a caller that is still running is at the same or a higher address. The compiler can inline frameAddress into its caller, so the frame is either the caller's frame or a frame below it. On wasm32 the address is in the stack in linear memory, and a function that keeps nothing there has the same address as its caller. Reading through the address is unsafe and needs the usual pointer proofs.

Gotchas#

The address stops being part of a frame when the function that took it returns. Compare addresses taken on the same thread only.

Associated function withStorage#

silk
pub unsafe fn withStorage<T, A>(count: usize, use: once fn([*]mut T) -> A) -> A

Calls use one time with stack storage for count values of T and returns its result.

Details#

The storage is naturally aligned for T and not initialized. It is released when withStorage returns, so use must not keep the pointer. Values written to the storage are not dropped. A count whose byte size does not fit in usize traps.

Safety#

The caller proves that count values of T fit in the stack that the running thread has left. The compiler does not check the size, and storage past the end of the stack is undefined behavior.

Category: Memory.

Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2.

View the source of this page