Lesson 6 — The EVM is a stack machine
Question
You've been talking to Ethereum "from outside" via Alloy. Now peek inside the EVM — what the Ethereum Virtual Machine is and how it works. The EVM is a stack machine — no registers, no memory addresses — just "push and pop" computation.
Principle (minimum model)
- Stack machine vs register machine. Stack = one place to put values (the top); register = named places (R0, R1, ...). The EVM has a 1024-deep stack only.
- Five memory regions. Stack (1024 deep, current computation) / Memory (volatile, within a tx) / Calldata (read-only, tx input) / Storage (persisted, blockchain state) / Code (read-only, contract bytecode).
- Opcode = a 1-byte instruction.
0x01 ADD/0x60 PUSH1/0x52 MSTORE/0x55 SSTORE. ADD pops two stack values and pushes the sum. - Gas. Every opcode has a cost. A tx has a gas limit; if exhausted, execution halts. Storage writes are the most expensive (they're persisted).
- Why a stack machine? Smaller instruction set + simpler operand encoding → fewer consensus bugs, easier to verify and to compile to ZK circuits. Trade-off: runtime efficiency vs native register code.
Worked example + steps
The EVM is a stack machine
The Ethereum Virtual Machine is a stack machine. It has no general registers and no calling conventions in the C sense — almost everything happens on a stack.
The three "places"
Every EVM instruction reads or writes one of these:
| Place | Property | Purpose |
|---|---|---|
| Stack | LIFO, max 1024 deep | Operands and results |
| Memory | Volatile within a tx | Temporary scratch space |
| Storage | Persistent (expensive) | Contract state |
How ADD works
The ADD opcode takes two values from the top of the stack, adds them, and pushes the result back:
Before: stack [..., 7, 5]
ADD
After: stack [..., 12]
That's it: pop, pop, add, push.
The real Revm Stack
This isn't theory — it's a struct in crates/interpreter/src/interpreter/stack.rs:
pub const STACK_LIMIT: usize = 1024;
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct Stack {
/// The underlying data of the stack.
data: Vec<U256>,
}
That's the entire structure: a vector of U256 values, with a hard limit of 1024. The methods you'll see called all over the interpreter:
pub fn new() -> Self
pub fn push(&mut self, value: U256) -> bool
pub fn pop(&mut self) -> Result<U256, InstructionResult>
pub fn peek(&self, no_from_top: usize) -> Result<U256, InstructionResult>
pub fn popn<const N: usize>(&mut self) -> Option<[U256; N]>
pub fn dup(&mut self, n: usize) -> bool
pub fn swap(&mut self, n: usize) -> bool
Read it carefully:
push(...) -> bool—trueif pushed,falseon overflow (more than 1024). The interpreter's macros check this and bail toStackOverflow.pop(...) -> Result<...>— explicit underflow detection, returned asInstructionResult::StackUnderflow.popn<const N: usize>()— pop N values at once, returning a fixed-size array. The const generic means the compiler unrolls the pop loop. This is what makespopn_top!fast.
How ADD actually works
Pop two, sum, push back. Pseudocode:
Before: stack [..., 7, 5]
ADD
After: stack [..., 12]
The real add source in Revm — which the Intermediate tier dissects line by line — doesn't even pop both then push: it pops one, writes through a mutable reference to the other. Revm's interpreter is built so the EVM mental model maps directly to Rust, but with cycle-level optimizations layered on.
Why a stack machine?
- Simplicity — small instruction set means fewer consensus bugs
- Reproducibility — easy to re-execute and verify
- ZK-friendliness — stack semantics map cleanly to constraint systems (you'll see this in zkEVM later)
Drill
Open crates/interpreter/src/interpreter/stack.rs in the repo. Find:
- The
STACK_LIMITcheck insidepush— what does it do on overflow? - The
popnimpl — notice how the constNlets the compiler skip the loop entirely - The
dupandswapmethods — they don't allocate; they just shuffle indices
Now build a tiny stack machine yourself in the next lesson.
Summary (3 lines)
- EVM = stack machine. 1024-deep stack + 5 memory regions (Stack / Memory / Calldata / Storage / Code). Only Storage is persisted.
- 1-byte opcodes; ADD = pop 2 + push 1; wrapping arithmetic (mod 2²⁵⁶); every opcode has gas; Storage writes most expensive.
- Stack machine choice = smaller instruction set, easier ZK circuits, harder to consensus-bug. Trade-off is raw runtime speed. Next: mini EVM stack quiz.