Skip to main content

Invocation contexts

An invoke context marks an instruction that enters a function. The schema distinguishes three kinds of invocation — internal calls (via JUMP), external message calls (CALL/DELEGATECALL/ STATICCALL), and contract creations (CREATE/CREATE2) — each with fields appropriate to the call mechanism.

All variants extend the function identity schema and may include pointers to the call target, arguments, gas, value, and input data as applicable. See Tracing execution for worked examples showing how debuggers use invoke and return contexts to reconstruct call stacks.

Pointer evaluation and instruction placement

An instruction's context describes what is known following that instruction's execution: the fact that a function was invoked holds from that point forward. Pointers within the context reference the machine state at the instruction's trace step — the state a debugger observes when it encounters the instruction.

For internal calls, this context is typically placed on the callee's entry JUMPDEST rather than the caller's JUMP. JUMP consumes its destination operand from the stack; at the entry JUMPDEST, the remaining stack (return address followed by arguments) is stable and directly addressable.

For external calls and contract creations, this context marks the CALL/DELEGATECALL/STATICCALL/CREATE/CREATE2 instruction itself, where the call parameters are visible on the stack.

Loading ....

Internal call

An internal call represents a function call within the same contract. This context is typically placed on the callee's entry JUMPDEST; the caller's JUMP has already consumed the destination from the stack, so pointer slot values reflect the post-JUMP layout. The target points to a code location and arguments are passed on the stack.

The target field is optional. It may be omitted when there is no meaningful code pointer to record — most notably at the first instruction of an inlined function body, where the inlining pass has elided the JUMP that would normally carry the target. The callee identity (identifier, declaration, type) remains meaningful in this case; a sibling transform: ["inline"] key on the same context indicates that the call was inlined rather than physically invoked.

Loading ....

Inlined internal calls

When the compiler inlines a callee, there is no JUMP and no runtime activation record: the callee's instructions are spliced directly into the caller. The call still happened at the source level, so it is still marked with an invoke context — one that describes the kind of call without a physical target:

  • jump: true marks the invocation as an internal call kind (as opposed to a message call or contract creation). It does not assert that a JUMP instruction executes here — an inlined call has none.
  • target is omitted: there is no code location to point at, because the JUMP that would carry it was elided.
  • a sibling transform: ["inline"] key marks the instruction as belonging to an inlined body.

The callee identity (identifier, declaration, type, all optional) is preserved, so the inlined function still appears on the debugger's call stack: the debugger reconstructs a virtual activation for it (see Reconstructing activations).

Compilers typically inline small or leaf non-recursive callees at every call site, so the same callee can produce several independent virtual activations across a trace — one per inlined site. (The precise eligibility rule is a compiler choice; the format is the same however inlining decisions are made.)

External call

An external call represents a call to another contract via CALL, DELEGATECALL, or STATICCALL. The type of call may be indicated by setting delegate or static to true. If neither flag is present, the invocation represents a regular CALL.

Loading ....

Contract creation

A contract creation represents a CREATE or CREATE2 operation. The presence of salt implies CREATE2.

Loading ....

Reconstructing activations

A debugger reconstructs the logical call stack from invoke and return contexts. Each entry on that stack is an activation (the DWARF term for a call-stack entry). Activation handling is uniform whether or not the call was inlined:

  • Push an activation when an invoke context is encountered and pop it when the matching return context is encountered, in trace order.

An inlined callee therefore appears on the call stack exactly as a non-inlined one does. Two kinds of activation differ only in how they are backed, distinguished by the presence of an inline transform marker — not by whether target is present:

  • A real activation comes from an invoke without an inline transform marker. It corresponds to an actual call at runtime, corroborated by machine state — a return address on the EVM stack — and occupies a real stack region.
  • A virtual activation comes from an invoke whose context carries transform: ["inline"] (an inline identifier in its transform list). It has no runtime corroboration and occupies no EVM stack region; it exists only in the debug annotations. Its target is typically omitted (the JUMP was elided), but target-absence is not itself the signal — a real internal call may also omit target (see Internal call). The reliable discriminator is the inline marker.

Activation membership

An instruction belongs to the innermost open virtual activation if and only if its context carries an inline identifier in its transform list — so composed markers such as ["inline", "fold"] still confer membership. The nesting depth is the number of "inline" occurrences in the list (doubly-inlined code carries ["inline", "inline"]). Membership is determined per-instruction from this marker, not from instruction ranges: optimization passes may relocate or interleave an inlined body, so a positional "everything between the invoke and the return" rule would be unsound.

Identity and values

Every function-identity field (identifier, declaration, type) is optional, so a virtual activation degrades gracefully — from full identity down to an anonymous inlined frame — with no fabricated data. A debugger renders whatever is present.

An inlining compiler typically preserves the callee's declaration and per-instruction source ranges for a virtual activation, and can resolve inlined locals that it homed in addressable memory, via variables contexts. Identity fields remain optional and degrade gracefully as described above. Such a compiler does not emit invoke.arguments or return.data pointers in this first version; individual parameter values may still be inspectable as locals inside the body where they are memory-homed. A virtual activation with no resolvable values is still a valid, displayable frame.

A debugger that ignores transform contexts still sees a coherent invoke/return pair and a sound source-level call stack. One that understands them can present virtual activations distinctly — for example, collapsible and tied to the callee's source location.