liveBook · Manning

Execution context stack

As explained in Rust in Action: Systems programming concepts and techniques

A call stack is the runtime structure that records active function calls, their local data, and the points to which control returns.

An execution context stack keeps track of where control should return as functions call other functions. Each call adds a return point; each ordinary return removes the most recent one. Nested calls therefore return in reverse order. section 5.7.4 section 12.9

A small CPU emulator makes this behavior visible. Its stack is an array of addresses, with a stack pointer marking the next available slot. To call a function, it saves the current instruction position and moves to the function’s address. To return, it retrieves the saved position. The example checks for stack underflow and attempts to guard against overflow. section 5.7.4

Listing 5.28 Adding the call() and ret() methods
41 fn call(&mut self, addr: u16) {
42     let sp = self.stack_pointer;
43     let stack = &mut self.stack;
44  
45     if sp > stack.len() {
46         panic!("Stack overflow!")
47     }
48  
49     stack[sp] = self.position_in_memory as u16;       #1
50     self.stack_pointer += 1;                          #2
51     self.position_in_memory = addr as usize;          #3
52 }
53  
54 fn ret(&mut self) {
55     if self.stack_pointer == 0 {
56         panic!("Stack underflow");
57     }
58  
59     self.stack_pointer -= 1;
60     let call_addr = self.stack[self.stack_pointer];   #4
61     self.position_in_memory = call_addr as usize;     #4
62 }

Compare call() and ret(): one saves the instruction position and advances the stack pointer; the other reverses those steps. section 5.7.4

Watch nested calls grow and shrink

A recursive function prints a row of # characters before calling itself and again after returning. The rows grow longer with each nested call, then shorten one step at a time as calls return. section 12.9

Normal return is not the only way out. Stack unwinding traverses accumulated frames in reverse during exception handling. On UNIX-based systems, setjmp and longjmp can instead transfer control past several nested calls to a previously marked location. This call-tracking role is related to, but distinct from, choosing whether program data lives on the stack or heap. section 11.4.1 section 12.9 section 6.3

How Rust in Action: Systems programming concepts and techniques covers it

How deep it goes
Works through a fixed-size stack, stack pointer, and call-and-return methods; does not develop a general stack-frame layout.
Examples
Uses a Rust CPU emulator with call() and ret(), plus a recursive program that prints its changing call depth.
What it assumes
Begins with basic Rust code and introduces the stack through CPU behavior without assuming prior knowledge of memory terminology.
What you can do after
Build it from scratch in a small CPU emulator and use call depth to understand nested returns.
How it connects
Links function calls to instruction addresses, stack pointers, recursion, exception unwinding, and setjmp/longjmp.

Also explained in

Compare how 8 books explain execution context stack →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Rust in Action: Systems programming concepts and techniques. The section links point to the parts of the book it draws on.