Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion packages/programs-react/src/components/CallStackDisplay.css
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@
color: var(--programs-text-muted, #888);
}

.call-stack-tailcall {
.call-stack-tailcall,
.call-stack-inline {
margin-left: 4px;
padding: 0 5px;
border-radius: 8px;
Expand All @@ -60,3 +61,10 @@
color: var(--programs-transform-text, #8250df);
border: 1px solid var(--programs-transform-accent, #a475f9);
}

/* Virtual (inline) activations use a dashed border to read as
"not a real frame" while sharing the transform palette. */
.call-stack-inline {
border-style: dashed;
font-style: italic;
}
8 changes: 8 additions & 0 deletions packages/programs-react/src/components/CallStackDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,14 @@ export function CallStackDisplay({
⮌ tail call
</span>
)}
{frame.isInline && (
<span
className="call-stack-inline"
title="Inlined: virtual activation — the body was spliced into the caller, no call occurred"
>
⧉ inline
</span>
)}
</button>
</React.Fragment>
))}
Expand Down
163 changes: 163 additions & 0 deletions packages/programs-react/src/utils/mockTrace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,166 @@ describe("flat (production) TCO back-edge shape", () => {
expect(stack.some((f) => f.isTailCall)).toBe(false);
});
});

// Inlined internal calls (level-2 `inline` transform) produce
// VIRTUAL activations, not real ones. The compiler brackets an
// inlined body with a virtual invoke on the entry-first
// instruction and a virtual return on the exit-last instruction;
// every inlined instruction carries transform:["inline"]. The
// call stack must reconstruct the virtual frame, tag it, and
// tear it down when execution leaves the inlined body — so it
// reads distinctly from a real call and never leaks a phantom
// frame into caller code.
describe("inline virtual activations", () => {
const entryInvoke = {
code: { source: { id: "0" }, range: { offset: 0, length: 1 } },
transform: ["inline"],
invoke: { jump: true, identifier: "dbl" },
};
const bodyMark = {
code: { source: { id: "0" }, range: { offset: 1, length: 1 } },
transform: ["inline"],
};
const exitReturn = {
code: { source: { id: "0" }, range: { offset: 2, length: 1 } },
transform: ["inline"],
return: { identifier: "dbl" },
};
const callerMark = {
code: { source: { id: "0" }, range: { offset: 3, length: 1 } },
};

describe("extractCallInfoFromInstruction inline flag", () => {
it("marks isInline on a virtual (inline) invoke", () => {
const info = extractCallInfoFromInstruction(instr(0, entryInvoke));
expect(info?.kind).toBe("invoke");
expect(info?.isInline).toBe(true);
});

it("marks isInline on a virtual (inline) return", () => {
const info = extractCallInfoFromInstruction(instr(0, exitReturn));
expect(info?.kind).toBe("return");
expect(info?.isInline).toBe(true);
});

it("leaves isInline falsy for a plain (real) invoke", () => {
const info = extractCallInfoFromInstruction(
instr(0, { invoke: { jump: true, identifier: "dbl" } }),
);
expect(info?.isInline).toBeFalsy();
});
});

describe("buildCallStack virtual frame lifetime", () => {
// A single inlined body: entry / body / exit / caller.
const trace: TraceStep[] = [
{ pc: 0, opcode: "PUSH1" }, // entry invoke → push virtual dbl
{ pc: 1, opcode: "ADD" }, // inlined body instruction
{ pc: 2, opcode: "MSTORE" }, // exit return → pop
{ pc: 3, opcode: "JUMPDEST" }, // caller code (no inline marker)
];
const program = {
instructions: [
instr(0, entryInvoke),
instr(1, bodyMark),
instr(2, exitReturn),
instr(3, callerMark),
],
} as unknown as Program;
const pcToInstruction = buildPcToInstructionMap(program);

it("pushes a virtual frame tagged isInline at the entry", () => {
const stack = buildCallStack(trace, pcToInstruction, 0);
expect(stack).toHaveLength(1);
expect(stack[0].identifier).toBe("dbl");
expect(stack[0].isInline).toBe(true);
});

it("keeps the virtual frame open across the inlined body", () => {
const stack = buildCallStack(trace, pcToInstruction, 1);
expect(stack).toHaveLength(1);
expect(stack[0].isInline).toBe(true);
});

it("pops the virtual frame at the exit return", () => {
const stack = buildCallStack(trace, pcToInstruction, 2);
expect(stack).toHaveLength(0);
});

it("does not leak a phantom frame into caller code", () => {
const stack = buildCallStack(trace, pcToInstruction, 3);
expect(stack).toHaveLength(0);
});
});

describe("two gap-separated inline sites of the same helper", () => {
const trace: TraceStep[] = [
{ pc: 0, opcode: "PUSH1" }, // site 1 entry
{ pc: 2, opcode: "MSTORE" }, // site 1 exit
{ pc: 3, opcode: "JUMPDEST" }, // caller gap (no inline)
{ pc: 10, opcode: "PUSH1" }, // site 2 entry
{ pc: 12, opcode: "MSTORE" }, // site 2 exit
{ pc: 13, opcode: "JUMPDEST" }, // caller
];
const program = {
instructions: [
instr(0, entryInvoke),
instr(2, exitReturn),
instr(3, callerMark),
instr(10, entryInvoke),
instr(12, exitReturn),
instr(13, callerMark),
],
} as unknown as Program;
const pcToInstruction = buildPcToInstructionMap(program);

it("shows depth 1 while inside the second body", () => {
const stack = buildCallStack(trace, pcToInstruction, 3);
expect(stack).toHaveLength(1);
expect(stack[0].isInline).toBe(true);
});

it("is empty after both sites — no accumulation", () => {
const stack = buildCallStack(trace, pcToInstruction, 5);
expect(stack).toHaveLength(0);
});
});

describe("defensive membership guard", () => {
// A virtual invoke whose exit return never arrives (residual
// smear / dropped marker): the frame must still be torn down
// when execution reaches a non-inline caller instruction,
// rather than leaking to the end of the trace.
const trace: TraceStep[] = [
{ pc: 0, opcode: "PUSH1" }, // virtual invoke → push
{ pc: 1, opcode: "ADD" }, // still inside the body
{ pc: 3, opcode: "JUMPDEST" }, // caller code, no inline marker
];
const program = {
instructions: [
instr(0, entryInvoke),
instr(1, bodyMark),
instr(3, callerMark),
],
} as unknown as Program;
const pcToInstruction = buildPcToInstructionMap(program);

it("keeps the frame while inline membership holds", () => {
expect(buildCallStack(trace, pcToInstruction, 1)).toHaveLength(1);
});

it("force-pops a stale virtual frame at a non-inline instr", () => {
expect(buildCallStack(trace, pcToInstruction, 2)).toHaveLength(0);
});
});

it("leaves a real call frame's isInline falsy", () => {
const trace: TraceStep[] = [{ pc: 0, opcode: "JUMPDEST" }];
const program = {
instructions: [instr(0, { invoke: { jump: true, identifier: "f" } })],
} as unknown as Program;
const pcToInstruction = buildPcToInstructionMap(program);
const stack = buildCallStack(trace, pcToInstruction, 0);
expect(stack[0].isInline).toBeFalsy();
});
});
56 changes: 46 additions & 10 deletions packages/programs-react/src/utils/mockTrace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ export interface CallInfo {
* (TCO), reusing the current frame rather than nesting.
*/
isTailCall?: boolean;
/**
* True when an `inline` transform is present on the same
* instruction — this invoke/return belongs to an inlined
* (virtual) activation, not a real call.
*/
isInline?: boolean;
}

/**
Expand Down Expand Up @@ -178,10 +184,15 @@ export function extractCallInfoFromInstruction(
if (!info) {
return undefined;
}
const isTailCall = extractTransformFromContext(instruction.context).includes(
"tailcall",
);
return isTailCall ? { ...info, isTailCall: true } : info;
const transforms = extractTransformFromContext(instruction.context);
const decorated: CallInfo = { ...info };
if (transforms.includes("tailcall")) {
decorated.isTailCall = true;
}
if (transforms.includes("inline")) {
decorated.isInline = true;
}
return decorated;
}

function extractCallInfoFromContext(
Expand Down Expand Up @@ -329,6 +340,13 @@ export interface CallFrame {
* (TCO). The frame was reused in place rather than nested.
*/
isTailCall?: boolean;
/**
* True when this frame is a VIRTUAL activation reconstructed
* from an inlined body (transform:["inline"]) rather than a
* real call. Its instructions were spliced into the caller;
* no JUMP occurred.
*/
isInline?: boolean;
}

/**
Expand Down Expand Up @@ -360,12 +378,15 @@ export function buildCallStack(
continue;
}

// Per-instruction inline membership drives the defensive
// guard below: an inlined body's instructions all carry
// transform:["inline"], so a virtual frame is only valid
// while that marker holds.
const isInlineInstr =
extractTransformFromInstruction(instruction).includes("inline");
const callInfo = extractCallInfoFromInstruction(instruction);
if (!callInfo) {
continue;
}

if (callInfo.isTailCall) {
if (callInfo?.isTailCall) {
// A TCO back-edge carries both return and invoke on a
// single instruction: the previous iteration returns
// and the next iteration is invoked, reusing the same
Expand Down Expand Up @@ -393,7 +414,7 @@ export function buildCallStack(
continue;
}

if (callInfo.kind === "invoke") {
if (callInfo?.kind === "invoke") {
// The compiler emits invoke on both the caller JUMP
// and callee entry JUMPDEST for the same call. These
// occur on consecutive trace steps. Only skip if the
Expand Down Expand Up @@ -423,14 +444,29 @@ export function buildCallStack(
callType: callInfo.callType,
argumentNames: argResult?.names,
argumentPointers: argResult?.pointers,
// Tag virtual activations so the widget can render
// them distinctly from real calls.
...(callInfo.isInline ? { isInline: true } : {}),
});
}
} else if (callInfo.kind === "return" || callInfo.kind === "revert") {
} else if (callInfo?.kind === "return" || callInfo?.kind === "revert") {
// Pop the matching frame
if (stack.length > 0) {
stack.pop();
}
}

// Defensive membership guard: a virtual (inline) frame must
// not stay open once execution leaves the inlined body. If
// the current instruction carries no inline marker, tear down
// any trailing virtual frames — belt-and-suspenders against a
// dropped or incomplete virtual return so a phantom activation
// can never leak into caller code.
if (!isInlineInstr) {
while (stack.length > 0 && stack[stack.length - 1].isInline) {
stack.pop();
}
}
}

return stack;
Expand Down
10 changes: 9 additions & 1 deletion packages/web/src/theme/ProgramExample/TraceDrawer.css
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,8 @@
border-left: 3px solid #8250df;
}

.call-stack-tailcall {
.call-stack-tailcall,
.call-stack-inline {
margin-left: 4px;
padding: 0 5px;
border-radius: 8px;
Expand All @@ -279,6 +280,13 @@
border: 1px solid rgba(130, 80, 223, 0.45);
}

/* Virtual (inline) activations read as "not a real frame": dashed
border + italic, sharing the transform purple tint. */
.call-stack-inline {
border-style: dashed;
font-style: italic;
}

/* Trace panels */
.trace-panels {
display: grid;
Expand Down
8 changes: 8 additions & 0 deletions packages/web/src/theme/ProgramExample/TraceDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,14 @@ function TraceDrawerContent(): JSX.Element {
⮌ tail call
</span>
)}
{frame.isInline && (
<span
className="call-stack-inline"
title="Inlined: virtual activation — body spliced into the caller, no call occurred"
>
⧉ inline
</span>
)}
</button>
</React.Fragment>
))
Expand Down
Loading