Skip to content

bugc: add function inlining pass (inline transform, L2)#230

Merged
gnidan merged 1 commit into
transform-contextfrom
compiler-inline-pass
Jul 2, 2026
Merged

bugc: add function inlining pass (inline transform, L2)#230
gnidan merged 1 commit into
transform-contextfrom
compiler-inline-pass

Conversation

@gnidan

@gnidan gnidan commented Jul 2, 2026

Copy link
Copy Markdown
Member

Adds the function inlining pass and lights up inline in the tracer — completing the transform set (fold/tailcall/coalesce/inline). Emits per #229's frozen contract via the shared addTransform helper.

What it does

InliningStep (level 2, first — after L1 fold, before CSE/TCO/JumpOpt) replaces calls to eligible internal functions with a copy of the callee body spliced into the caller (α-rename, param→arg substitution, return→continuation via dest-substitution, callee deleted once fully inlined). Emission:

  • virtual invoke (jump:true, identity, no target — the format: make invoke.target optional for internal calls #213 optional-target signal) on the first inlined instruction;
  • virtual return at the return point;
  • transform:["inline"] on every body instruction (per-instruction membership, reorder-robust);
  • arguments/data omitted in v1 (per contract).

v1 scope (correctness over coverage — follow-ups noted)

Eligible callee = internal, non-recursive, single-return, leaf; applied at all call sites.

  • Does NOT inline into self-recursive / TCO'd callers. Inlining a helper into a tail-recursive call's args rewrites count(succ(n))count(n+1), which the tail-call optimizer mishandles (a pre-existing bugc bug — reproduces with inlining disabled; tracked separately). This guard keeps the tailcall demos pristine.
  • Deferred: non-leaf/nested callees (dest-substitution ordering), multi-return (continuation-phi × block-merging hazard), size-threshold (non-leaf) inlining.

Correctness notes

  • Return value via dest-substitution, not a continuation phi — robust to L3 block-merging (a phi there became an invalid self-referential phi).
  • Deep clone via structuredClone (preserves bigint const values).

Test changes

  • New inlining.test.ts: leaf single-return + multiple-sites correct at all levels with inline markers; TCO-protection (succ not inlined into count; count stays correct + tailcall).
  • optimizer-contexts.test.ts: leaf helpers (add/dbl/addThree) now inline at L2+ (no real caller JUMP → inline activation); recursive/multi-return functions (fact, isEven/isOdd, count/succ) untouched.

Verification

  • yarn build + full bugc suite green (420 passed, 22 pre-existing skips).
  • Demos verified O2/O3: functionCallAndReturn (add) → inlined + correct; mutualRecursion → untouched; count(succ) → not inlined, tailcall intact, correct.

Adds InliningStep (level 2, first — after L1 fold, before CSE/TCO/
JumpOpt) that replaces calls to eligible internal functions with a
copy of the callee body spliced into the caller. Every inlined
instruction is annotated transform:["inline"] via addTransform, and
the body is bracketed by a virtual invoke (jump:true, identity, no
target — the #213 optional-target signal) / virtual return, so a
debugger can reconstruct a virtual activation. This lights up
`inline` in the tracer, completing the transform set.

v1 eligibility (correctness over coverage; follow-ups noted):
- internal, non-recursive, single-return, LEAF callee;
- applied at all call sites; callee deleted once fully inlined;
- NOT inlined into self-recursive / TCO'd callers: inlining a
  helper into a tail-recursive call's arguments rewrites
  count(succ(n)) -> count(n+1), which the tail-call optimizer
  mishandles (pre-existing bug, tracked separately). Guarding this
  keeps the tailcall demos pristine.

Return values use dest-substitution (not a continuation phi), which
is robust to L3 block-merging; deep clones use structuredClone to
preserve bigint const values.

Updates optimizer-contexts tests: leaf helpers (add/dbl/addThree)
now inline at L2+ (no real caller JUMP; inline activation instead),
while recursive/multi-return functions (fact, isEven/isOdd, count)
are untouched. Deferred to follow-ups: non-leaf/nested callees,
multi-return, size-threshold (non-leaf) inlining.
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-07-02 23:58 UTC

@gnidan gnidan merged commit 22bf0c8 into transform-context Jul 2, 2026
4 checks passed
@gnidan gnidan deleted the compiler-inline-pass branch July 2, 2026 23:54
gnidan added a commit that referenced this pull request Jul 3, 2026
…hip) (#233)

Amend the invoke spec's "Reconstructing activations" section to
resolve an ambiguity #230's inline pass exposed: an inlined body's
invoke/return markers must bracket the body (invoke on the first
instruction, return on the last), never duplicate across interior
instructions. Duplicated boundary markers push/pop spurious
activations.

- State the push/pop display semantics: invoke opens inclusive of
  its instruction, return closes after its instruction.
- Require bracketed emission; permit the single-instruction body
  (entry==exit) to carry both invoke and return, processed
  push-then-pop.
- Separate the two concerns explicitly: push/pop determines an
  activation's lifetime; membership determines which open
  activation an instruction belongs to. An activation stays open
  across non-member (interleaved caller) instructions.

No schema change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant