From fa73e371894dfe5afff9c93b0be9aa18f0d049db Mon Sep 17 00:00:00 2001 From: "g. nicholas d'andrea" Date: Tue, 6 Oct 2026 19:30:52 -0400 Subject: [PATCH] bugc: leave the jump past an inlined return out of the inlined body At optimization levels 2 and 3, the jump from an inlined body to the caller's continuation runs after the inlined function's return, so it is the caller's code. It kept a transform: ["inline"] context, so a debugger that reads the transform put that jump in an inlined body that had already returned, with no function or call site to name. --- packages/bugc/CHANGELOG.md | 7 +++++++ packages/bugc/src/evmgen/inline-bracket.test.ts | 16 ++++++++++++++++ packages/bugc/src/optimizer/steps/inlining.ts | 15 +++------------ 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/packages/bugc/CHANGELOG.md b/packages/bugc/CHANGELOG.md index 5eee6e0cc1..7c3ec72bdf 100644 --- a/packages/bugc/CHANGELOG.md +++ b/packages/bugc/CHANGELOG.md @@ -69,6 +69,12 @@ support. Changes to the specification itself are tracked in the root inlined call, or returns one's result, now has its own `invoke` and `return` contexts, around the inner call's. Before, its `invoke` (or its `return`) was lost, so the call stack did not balance ([#341]). +- At optimization levels 2 and 3, the jump that leaves an inlined body + for the caller's code no longer has a `transform: ["inline"]` context. + It runs after the inlined function's `return`, so it is not part of the + inlined body. Before, a debugger that reads the transform put that jump + in an inlined body that had already returned, with no function or call + site to name ([#347]). - A cast from dynamic `bytes` to a fixed-size type now reads the bytes. `msg.data[0:4] as bytes4` is the first four bytes of calldata; bytes past the slice's length are zero. Before, the cast gave the slice's @@ -262,3 +268,4 @@ First publication. [#343]: https://github.com/ethdebug/format/pull/343 [#344]: https://github.com/ethdebug/format/pull/344 [#345]: https://github.com/ethdebug/format/pull/345 +[#347]: https://github.com/ethdebug/format/pull/347 diff --git a/packages/bugc/src/evmgen/inline-bracket.test.ts b/packages/bugc/src/evmgen/inline-bracket.test.ts index 6583bcfed8..4e399659da 100644 --- a/packages/bugc/src/evmgen/inline-bracket.test.ts +++ b/packages/bugc/src/evmgen/inline-bracket.test.ts @@ -129,6 +129,22 @@ describe("inlined invoke/return are bracketed on emitted bytecode", () => { expect(t.both).toBe(0); }); + it("marks only the body inline, from its invoke to its return", async () => { + for (const source of [dblTwoSites, multiInstrBody]) { + const instrs = await runtimeInstructions(source, 2); + // Ops past a return (the jump back to the caller's continuation) + // are the caller's code, not part of the inlined body. + let open = 0; + const outside: number[] = []; + instrs.map(flags).forEach((f, i) => { + if (f.invoke) open += 1; + if (f.inline && open === 0) outside.push(i); + if (f.return) open -= 1; + }); + expect(outside).toEqual([]); + } + }); + it("preserves runtime behavior at every level", async () => { for (const level of [0, 1, 2, 3] as const) { const res = await executeProgram(dblTwoSites, { diff --git a/packages/bugc/src/optimizer/steps/inlining.ts b/packages/bugc/src/optimizer/steps/inlining.ts index daca9f2b62..6335959f30 100644 --- a/packages/bugc/src/optimizer/steps/inlining.ts +++ b/packages/bugc/src/optimizer/steps/inlining.ts @@ -220,10 +220,9 @@ export class InliningStep extends BaseOptimizationStep { } const invokes = [...pendingInvokes, inlineInvoke]; const returns = [inlineReturn, ...pendingReturns]; - const returnedJump: Ir.Instruction.Debug = { - ...Ir.Utils.addTransform(after, "inline"), - ...placement(after), - }; + // The jump back to the continuation runs past the callee's return: + // it is the caller's code, with no `inline` transform of its own. + const returnedJump: Ir.Instruction.Debug = { ...after }; const entryBlockId = blockRename.get(callee.entry)!; const returnBlockIds: string[] = []; @@ -717,14 +716,6 @@ function withoutActivationsDebug( return { ...rest, ...(context ? { context } : {}) }; } -/** A debug's inline sites and origin, which say where it is */ -function placement(debug: Ir.Instruction.Debug): Ir.Instruction.Debug { - return { - ...(debug.inlineSites ? { inlineSites: debug.inlineSites } : {}), - ...(debug.origin ? { origin: debug.origin } : {}), - }; -} - /** * Preserve the call site's `code` range on the entry instruction. * The instruction already maps to the callee body, so the two source