Trace playground
See ethdebug/format in motion. Each example below compiles a small BUG program and lets you step through its execution trace — watching the source line, in-scope variables, call stack, and instruction context light up at every step.
Click "Try it" on an example to open the trace drawer, then use the step controls to walk through execution. The examples build on each other: start at the top and work down, picking up one idea at a time.
Start here: a storage write
Counter increments a single storage slot. Step through it to get a feel
for the drawer — the highlighted source line, the value in storage, and
the context attached to each instruction.
name Counter;
storage {
[0] count: uint256;
}
create {
count = 0;
}
code {
count = count + 1;
}
Following a function call
Real programs call functions, and the debugger follows them. Watch the
call stack push a frame on the JUMP into add (an invoke context)
and pop it on the JUMP back (a return context).
name Adder;
define {
function add(a: uint256, b: uint256) -> uint256 {
return a + b;
};
}
storage {
[0] result: uint256;
}
create {
result = 0;
}
code {
result = add(3, 4);
}
For the exact shape of invoke, return, and revert contexts, see the function call spec and the tracing reference.
Watching the optimizer
Compilers rewrite code as they optimize, and transform contexts
record what they did. Set the Opt selector to O2 and step through
these tail-recursive programs: the recursive call folds into a loop, and
the back-edge JUMP carries transform: ["tailcall"] next to its invoke
and return. The call stack stays flat instead of growing one frame per
iteration.
name TailSum;
define {
function sum(n: uint256, acc: uint256) -> uint256 {
if (n == 0) { return acc; }
else { return sum(n - 1, acc + n); }
};
}
storage {
[0] result: uint256;
}
create {
result = 0;
}
code {
result = sum(5, 0);
}
name TailFactorial;
define {
function fact(n: uint256, acc: uint256) -> uint256 {
if (n == 0) { return acc; }
else { return fact(n - 1, acc * n); }
};
}
storage {
[0] result: uint256;
}
create {
result = 0;
}
code {
result = fact(5, 1);
}
Flip the Opt selector between O0 and O2 to see the call stack
change. For how the tailcall transform composes with invoke/return, see
the transform context spec and the
tail-call optimization walkthrough.