Skip to content

Commit 06fc7ff

Browse files
authored
format: make invoke.target optional for internal calls (#242)
Internal calls via JUMP normally carry a code pointer to the callee's entry point, but there isn't always a meaningful target to record — e.g. at the first instruction of an inlined body, where the JUMP has been elided. The callee identity (identifier, declaration, type) stays meaningful; the target does not. Same pattern as return.data being optional. - Schema: drop `target` from InternalCall.required; expand the description; add a worked example for the no-target case. - TS types: mark `target` optional; relax the guard. - bugc: guard target access in patchInvokeInContext (+ tests assert target is defined before dereferencing). Wave 1 of the transform-context → main split (#31): transform-schema- independent. Forward-references to `transform:["inline"]` in the prose/ example are deferred to the wave that lands the transform schema.
1 parent 01b32f2 commit 06fc7ff

6 files changed

Lines changed: 44 additions & 7 deletions

File tree

‎packages/bugc/src/evmgen/call-contexts.test.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ code {
9797
expect(typeof invoke.declaration!.range!.length).toBe("number");
9898

9999
// Target should be a code pointer (not stack)
100-
expect(Pointer.Region.isCode(call.target.pointer)).toBe(true);
100+
expect(call.target).toBeDefined();
101+
expect(Pointer.Region.isCode(call.target!.pointer)).toBe(true);
101102

102103
// Caller JUMP should NOT have argument pointers
103104
// (args live on the callee JUMPDEST invoke context)
@@ -156,7 +157,8 @@ code {
156157
expect(call.identifier).toBe("add");
157158

158159
// Target should be a code pointer
159-
expect(Pointer.Region.isCode(call.target.pointer)).toBe(true);
160+
expect(call.target).toBeDefined();
161+
expect(Pointer.Region.isCode(call.target!.pointer)).toBe(true);
160162

161163
// Should have argument pointers matching
162164
// function parameters

‎packages/bugc/src/evmgen/generation/function.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,8 @@ function patchInvokeInContext(
537537
const offset = functionRegistry[invoke.identifier];
538538
if (offset === undefined) return;
539539

540+
if (!invoke.target) return;
541+
540542
const ptr = invoke.target.pointer;
541543
if (Format.Pointer.Region.isCode(ptr)) {
542544
ptr.offset = `0x${offset.toString(16)}`;

‎packages/bugc/src/evmgen/optimizer-contexts.test.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,8 @@ code { r = count(0, 5); }`;
502502
expect(Invocation.isInternalCall(invocation)).toBe(true);
503503
const internalCall =
504504
invocation as Format.Program.Context.Invoke.Invocation.InternalCall;
505-
const invokeTarget = internalCall.target.pointer;
505+
expect(internalCall.target).toBeDefined();
506+
const invokeTarget = internalCall.target!.pointer;
506507
expect(invokeTarget).toBeDefined();
507508
expect(
508509
"offset" in invokeTarget ? invokeTarget.offset : undefined,

‎packages/format/src/types/program/context.ts‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ export namespace Context {
171171
export namespace Invocation {
172172
export interface InternalCall extends Function.Identity {
173173
jump: true;
174-
target: Function.PointerRef;
174+
target?: Function.PointerRef;
175175
arguments?: Function.PointerRef;
176176
}
177177

@@ -180,8 +180,7 @@ export namespace Context {
180180
!!value &&
181181
"jump" in value &&
182182
value.jump === true &&
183-
"target" in value &&
184-
Function.isPointerRef(value.target) &&
183+
(!("target" in value) || Function.isPointerRef(value.target)) &&
185184
(!("arguments" in value) || Function.isPointerRef(value.arguments));
186185

187186
export interface ExternalCall extends Function.Identity {

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ caller's JUMP has already consumed the destination from the stack, so
5151
pointer slot values reflect the post-JUMP layout. The target points
5252
to a code location and arguments are passed on the stack.
5353

54+
The `target` field is optional. It may be omitted when there is no
55+
meaningful code pointer to record — most notably at the first
56+
instruction of an inlined function body, where the inlining pass
57+
has elided the JUMP that would normally carry the target. The
58+
callee identity (`identifier`, `declaration`, `type`) remains
59+
meaningful in this case.
60+
5461
<SchemaViewer
5562
schema={{ id: "schema:ethdebug/format/program/context/function/invoke" }}
5663
pointer="#/$defs/InternalCall"

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,13 @@ $defs:
8888
description: |
8989
Pointer to the target of the invocation. For internal
9090
calls, this typically points to a code location.
91+
Optional: may be omitted when there is no meaningful
92+
target pointer to record, e.g., at the first
93+
instruction of an inlined function body where the
94+
inlining pass has elided the JUMP that would normally
95+
carry this pointer. The callee identity
96+
(`identifier`, `declaration`, `type`) is still
97+
meaningful in this case.
9198
properties:
9299
pointer:
93100
$ref: "schema:ethdebug/format/pointer"
@@ -107,7 +114,7 @@ $defs:
107114
- pointer
108115
additionalProperties: false
109116

110-
required: [jump, target]
117+
required: [jump]
111118

112119
ExternalCall:
113120
title: External call
@@ -284,6 +291,25 @@ examples:
284291
location: stack
285292
slot: 2
286293

294+
# -----------------------------------------------------------
295+
# Inlined internal call: no target pointer
296+
# -----------------------------------------------------------
297+
# When the compiler inlines a function, the JUMP that would
298+
# normally carry the invoke context has been elided — there
299+
# is no physical call instruction and no code target to
300+
# point at. The invoke context still records the callee's
301+
# identity so the debugger can maintain a source-level call
302+
# stack.
303+
- invoke:
304+
identifier: "transfer"
305+
declaration:
306+
source:
307+
id: 0
308+
range:
309+
offset: 128
310+
length: 95
311+
jump: true
312+
287313
# -----------------------------------------------------------
288314
# External CALL: token.balanceOf(account)
289315
# -----------------------------------------------------------

0 commit comments

Comments
 (0)