From 6afb78618088d6fc4d6e327fe724a69b10dca2d8 Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Wed, 11 Mar 2026 04:00:23 -0400 Subject: [PATCH 1/4] Add function call tracing documentation Add invoke/return/revert context documentation across concept, reference, and spec pages: - concepts/programs.mdx: new "Function call contexts" section explaining the three context types with a SchemaExample - tracing.mdx: walkthrough of tracing through an internal function call (Adder contract), plus external call and revert examples - Spec pages: added intro prose to function.mdx, return.mdx, revert.mdx --- packages/web/docs/concepts/programs.mdx | 49 ++++++ .../docs/core-schemas/programs/tracing.mdx | 166 ++++++++++++++++++ .../program/context/function/function.mdx | 6 +- .../spec/program/context/function/return.mdx | 5 + .../spec/program/context/function/revert.mdx | 5 + 5 files changed, 230 insertions(+), 1 deletion(-) diff --git a/packages/web/docs/concepts/programs.mdx b/packages/web/docs/concepts/programs.mdx index ec4b3abe4..93c844c27 100644 --- a/packages/web/docs/concepts/programs.mdx +++ b/packages/web/docs/concepts/programs.mdx @@ -157,6 +157,55 @@ Contexts can be composed using: This composition enables describing complex scenarios like conditional variable assignments or function inlining. +## Function call contexts + +Programs answer "what function are we in?" through three context types +that track function boundaries during execution: + +- **invoke** — marks an instruction that enters a function. Indicates + the invocation kind (internal jump, external message call, or + contract creation) and provides pointers to call arguments, target + address, gas, and value as appropriate. +- **return** — marks an instruction associated with a successful + return from a function. Provides a pointer to the return data. +- **revert** — marks an instruction associated with a failed call. + May include a pointer to revert reason data or a numeric panic + code. + +All three extend a common **function identity** schema with optional +fields for the function's name, declaration source range, and type. +This lets compilers provide as much or as little attribution as +available — from a fully identified `transfer` call down to an +anonymous indirect invocation through a function pointer. + + + {`{ + "invoke": { + "identifier": "transfer", + "jump": true, + "target": { + "pointer": { "location": "stack", "slot": 0 } + }, + "arguments": { + "pointer": { + "group": [ + { "name": "to", "location": "stack", "slot": 2 }, + { "name": "amount", "location": "stack", "slot": 3 } + ] + } + } + } +}`} + + +A debugger uses these contexts to reconstruct call stacks, show +function names in stepping UI, and display argument/return values +alongside source code. + ## What tracing enables By following contexts through execution, debuggers can provide: diff --git a/packages/web/docs/core-schemas/programs/tracing.mdx b/packages/web/docs/core-schemas/programs/tracing.mdx index efbae0cfc..246ccb3d9 100644 --- a/packages/web/docs/core-schemas/programs/tracing.mdx +++ b/packages/web/docs/core-schemas/programs/tracing.mdx @@ -121,6 +121,172 @@ b = b + 1; }`} /> +## Tracing through a function call + +The examples above trace simple straight-line code. Real programs +make function calls. **invoke** and **return** contexts let a +debugger follow execution across function boundaries. + +Consider a contract with an internal `add` function: + +``` +name Adder; + +storage { + [0] result: uint256; +} + +create { + result = 0; +} + +func add(a: uint256, b: uint256) -> uint256 { + return a + b; +} + +code { + result = add(3, 4); +} +``` + +The compiler produces a sequence of instructions. The relevant +instructions look like this (offsets are illustrative): + +### Before the call — setting up arguments + +At the call site, the compiler pushes arguments onto the stack and +prepares the jump. The instruction at the JUMP has an **invoke** +context: + + + {`{ + "invoke": { + "identifier": "add", + "jump": true, + "target": { + "pointer": { "location": "stack", "slot": 0 } + }, + "arguments": { + "pointer": { + "group": [ + { "name": "a", "location": "stack", "slot": 2 }, + { "name": "b", "location": "stack", "slot": 3 } + ] + } + } + } +}`} + + +The debugger now knows it's entering `add` with arguments at stack +slots 2 and 3. A trace viewer can show `add(3, 4)` in the call +stack. + +### Inside the function — normal tracing + +Inside `add`, instructions carry their own `code` and `variables` +contexts as usual. The debugger shows the source range within the +function body, and parameters `a` and `b` appear as in-scope +variables. + +### Returning — the result + +When `add` finishes, the JUMP back to the caller has a **return** +context: + + + {`{ + "return": { + "identifier": "add", + "data": { + "pointer": { "location": "stack", "slot": 0 } + } + } +}`} + + +The debugger pops `add` from the call stack and can display the +return value (7). + +### External calls and reverts + +The same pattern applies to external message calls, but with +additional fields. An external CALL instruction carries gas, value, +and input data pointers: + + + {`{ + "invoke": { + "identifier": "balanceOf", + "message": true, + "target": { + "pointer": { "location": "stack", "slot": 1 } + }, + "gas": { + "pointer": { "location": "stack", "slot": 0 } + }, + "input": { + "pointer": { + "group": [ + { "name": "selector", "location": "memory", + "offset": "0x80", "length": 4 }, + { "name": "arguments", "location": "memory", + "offset": "0x84", "length": "0x20" } + ] + } + } + } +}`} + + +If the call reverts, a **revert** context captures the reason: + + + {`{ + "revert": { + "identifier": "transfer", + "reason": { + "pointer": { + "location": "memory", + "offset": "0x80", + "length": "0x64" + } + } + } +}`} + + +For built-in assertion failures, the compiler can provide a panic +code instead of (or alongside) a reason pointer: + + + {`{ + "revert": { + "panic": 17 + } +}`} + + ## Trace data structure A trace step captures the EVM state at a single point: diff --git a/packages/web/spec/program/context/function/function.mdx b/packages/web/spec/program/context/function/function.mdx index 0b00b3a6d..69dcad9d9 100644 --- a/packages/web/spec/program/context/function/function.mdx +++ b/packages/web/spec/program/context/function/function.mdx @@ -7,7 +7,11 @@ import SchemaViewer from "@site/src/components/SchemaViewer"; # Function identity Function contexts (invoke, return, revert) share a common set of -identity fields for the function being called. +identity fields for the function being called. All fields are +optional, allowing compilers to provide as much or as little +detail as available — from a fully named function with source +location and type, down to an empty object for an anonymous +indirect call. diff --git a/packages/web/spec/program/context/function/revert.mdx b/packages/web/spec/program/context/function/revert.mdx index 4b18b518b..98d980988 100644 --- a/packages/web/spec/program/context/function/revert.mdx +++ b/packages/web/spec/program/context/function/revert.mdx @@ -6,6 +6,11 @@ import SchemaViewer from "@site/src/components/SchemaViewer"; # Revert contexts +A revert context marks an instruction associated with a function +revert. It extends the function identity schema with an optional +pointer to revert reason data and/or a numeric panic code for +built-in assertion failures. + From 6dff3795d92878d063c864274a18bf1b4ce23d1e Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Wed, 11 Mar 2026 04:09:30 -0400 Subject: [PATCH 2/4] Make function call tracing example interactive Replace the static BUG code block with an interactive TraceExample component that lets readers compile and step through the Adder contract, seeing invoke/return contexts at function boundaries. Static SchemaExample blocks are kept for the narrative walkthrough and for external call/revert examples (which BUG can't demonstrate). --- .../docs/core-schemas/programs/tracing.mdx | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/packages/web/docs/core-schemas/programs/tracing.mdx b/packages/web/docs/core-schemas/programs/tracing.mdx index 246ccb3d9..4c04c155f 100644 --- a/packages/web/docs/core-schemas/programs/tracing.mdx +++ b/packages/web/docs/core-schemas/programs/tracing.mdx @@ -127,10 +127,20 @@ The examples above trace simple straight-line code. Real programs make function calls. **invoke** and **return** contexts let a debugger follow execution across function boundaries. -Consider a contract with an internal `add` function: +Click **"Try it"** on the example below, then step through the +trace. Watch for **invoke** contexts on the JUMP into `add` and +**return** contexts on the JUMP back to the caller: -``` -name Adder; + uint256 { + return a + b; + }; +} storage { [0] result: uint256; @@ -140,28 +150,24 @@ create { result = 0; } -func add(a: uint256, b: uint256) -> uint256 { - return a + b; -} - code { result = add(3, 4); -} -``` +}`} +/> -The compiler produces a sequence of instructions. The relevant -instructions look like this (offsets are illustrative): +As you step through, three phases are visible: ### Before the call — setting up arguments At the call site, the compiler pushes arguments onto the stack and -prepares the jump. The instruction at the JUMP has an **invoke** -context: +prepares the jump. The JUMP instruction carries an **invoke** +context identifying the function, its target, and the argument +locations: {`{ "invoke": { @@ -195,13 +201,13 @@ variables. ### Returning — the result -When `add` finishes, the JUMP back to the caller has a **return** -context: +When `add` finishes, the JUMP back to the caller carries a +**return** context with a pointer to the result: {`{ "return": { From a52a1f6b74b2b9720d292b6072c93f5c76781f25 Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Wed, 11 Mar 2026 04:10:36 -0400 Subject: [PATCH 3/4] Fix BUG source indentation in tracing examples Add 2-space indentation inside block bodies (storage, create, code, if) to match the canonical style used in .bug example files. --- .../docs/core-schemas/programs/tracing.mdx | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/web/docs/core-schemas/programs/tracing.mdx b/packages/web/docs/core-schemas/programs/tracing.mdx index 4c04c155f..13bc8e8cf 100644 --- a/packages/web/docs/core-schemas/programs/tracing.mdx +++ b/packages/web/docs/core-schemas/programs/tracing.mdx @@ -53,15 +53,15 @@ This example shows a basic counter that increments a storage variable: source={`name Counter; storage { -[0] count: uint256; + [0] count: uint256; } create { -count = 0; + count = 0; } code { -count = count + 1; + count = count + 1; }`} /> @@ -75,21 +75,21 @@ This example demonstrates conditional logic with storage variables: source={`name ThresholdCounter; storage { -[0] count: uint256; -[1] threshold: uint256; + [0] count: uint256; + [1] threshold: uint256; } create { -count = 0; -threshold = 5; + count = 0; + threshold = 5; } code { -count = count + 1; + count = count + 1; -if (count >= threshold) { -count = 0; -} + if (count >= threshold) { + count = 0; + } }`} /> @@ -103,21 +103,21 @@ This example shows working with multiple storage locations: source={`name MultiSlot; storage { -[0] a: uint256; -[1] b: uint256; -[2] sum: uint256; + [0] a: uint256; + [1] b: uint256; + [2] sum: uint256; } create { -a = 10; -b = 20; -sum = 0; + a = 10; + b = 20; + sum = 0; } code { -sum = a + b; -a = a + 1; -b = b + 1; + sum = a + b; + a = a + 1; + b = b + 1; }`} /> From d9240b40754b1f0ca6af7c676c80ea80038c89cc Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Wed, 11 Mar 2026 04:15:06 -0400 Subject: [PATCH 4/4] Revert BUG indentation to match prettier formatting Prettier strips indentation inside template literal strings in MDX JSX props. Revert to the unindented style that prettier enforces. --- .../docs/core-schemas/programs/tracing.mdx | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/web/docs/core-schemas/programs/tracing.mdx b/packages/web/docs/core-schemas/programs/tracing.mdx index 13bc8e8cf..55ec6ca8b 100644 --- a/packages/web/docs/core-schemas/programs/tracing.mdx +++ b/packages/web/docs/core-schemas/programs/tracing.mdx @@ -53,15 +53,15 @@ This example shows a basic counter that increments a storage variable: source={`name Counter; storage { - [0] count: uint256; +[0] count: uint256; } create { - count = 0; +count = 0; } code { - count = count + 1; +count = count + 1; }`} /> @@ -75,21 +75,21 @@ This example demonstrates conditional logic with storage variables: source={`name ThresholdCounter; storage { - [0] count: uint256; - [1] threshold: uint256; +[0] count: uint256; +[1] threshold: uint256; } create { - count = 0; - threshold = 5; +count = 0; +threshold = 5; } code { - count = count + 1; +count = count + 1; - if (count >= threshold) { - count = 0; - } +if (count >= threshold) { +count = 0; +} }`} /> @@ -103,21 +103,21 @@ This example shows working with multiple storage locations: source={`name MultiSlot; storage { - [0] a: uint256; - [1] b: uint256; - [2] sum: uint256; +[0] a: uint256; +[1] b: uint256; +[2] sum: uint256; } create { - a = 10; - b = 20; - sum = 0; +a = 10; +b = 20; +sum = 0; } code { - sum = a + b; - a = a + 1; - b = b + 1; +sum = a + b; +a = a + 1; +b = b + 1; }`} /> @@ -137,21 +137,21 @@ trace. Watch for **invoke** contexts on the JUMP into `add` and source={`name Adder; define { - function add(a: uint256, b: uint256) -> uint256 { - return a + b; - }; +function add(a: uint256, b: uint256) -> uint256 { +return a + b; +}; } storage { - [0] result: uint256; +[0] result: uint256; } create { - result = 0; +result = 0; } code { - result = add(3, 4); +result = add(3, 4); }`} />