Skip to content

Commit c00252e

Browse files
committed
format: correlate activations with an activation identifier
Correlate activations with an `activation` identifier on invoke, return, and revert: the invoke that opens an activation and the return or revert that closes it carry the same value, so a debugger pairs a call with its return independent of trace order. It lives inside the invoke/return/revert object, keeping a tail call's two facts flat without a `gather`.
1 parent db92840 commit c00252e

2 files changed

Lines changed: 39 additions & 0 deletions

File tree

‎packages/web/spec/program/context/function/invoke.mdx‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,41 @@ non-member instruction (no `inline` marker for that depth) is
188188
attributed to the enclosing activation, not the inlined one, even
189189
while the virtual activation remains on the stack.
190190

191+
### Correlating with `activation`
192+
193+
The push/pop rule above reconstructs the call stack without any
194+
correlation identifier: it relies on `invoke`/`return` appearing
195+
in a well-nested order. That is sufficient for typical compiler
196+
output, but it has a blind spot — push/pop alone can't tell one
197+
activation from another of the same function, so two inlined
198+
copies of a callee placed back-to-back, or an optimizer that moves
199+
a `return` ahead of its `invoke`, can defeat strict pairing.
200+
201+
An **`activation`** identifier closes it. The `invoke` object that
202+
opens an activation and the `return` (or `revert`) that closes it
203+
carry the same `activation` value; distinct activations carry
204+
distinct values. Pairing is then explicit and order-independent —
205+
matching values pair an invoke with its return regardless of trace
206+
order, and adjacent same-function activations stay distinct because
207+
their identifiers differ.
208+
209+
Unlike function [identity](#identity-and-values) (which function
210+
this is), `activation` identifies _which activation instance_ this
211+
is, so it lives **inside** the `invoke`/`return`/`revert` object
212+
rather than as a sibling context. Two activation facts on one instruction
213+
— a tail call's back-edge, which both returns from one activation
214+
and invokes the next — therefore stay flat, each fact carrying its
215+
own `activation` value, with no `gather` required.
216+
217+
When `activation` is present it is **authoritative** for pairing —
218+
which `invoke` matches which `return`. Push/pop remains the fallback
219+
an identifier-less debugger uses; in well-nested output the two
220+
agree. Membership — which instructions belong to a virtual
221+
activation — is unaffected: it stays the per-instruction
222+
[`inline` marker](#activation-membership). Attributing a body that
223+
an optimizer relocated to a _specific_ activation would need a
224+
membership-level identifier; that is out of scope here.
225+
191226
### Identity and values
192227

193228
Every function-identity field (`identifier`, `declaration`,

‎schemas/program/context/function/invoke.schema.yaml‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,10 @@ examples:
344344
offset: 128
345345
length: 95
346346
jump: true
347+
# Correlation id: the matching inlined `return` carries the
348+
# same value, so the two pair even if `transfer` is inlined
349+
# at several sites.
350+
activation: "transfer#0"
347351

348352
# -----------------------------------------------------------
349353
# External CALL: token.balanceOf(account)

0 commit comments

Comments
 (0)