Skip to content

Bound cumulative SpecConstr inlining - #10622

Merged
lukewilliamboswell merged 2 commits into
mainfrom
fix-10618
Aug 5, 2026
Merged

Bound cumulative SpecConstr inlining#10622
lukewilliamboswell merged 2 commits into
mainfrom
fix-10618

Conversation

@lukewilliamboswell

Copy link
Copy Markdown
Collaborator

Summary

  • bound the cumulative source-body work performed by each SpecConstr clone
  • retain the ordinary exact call when the code-growth budget is exhausted
  • add a regression test built from individually small acyclic wrappers that would otherwise expand exponentially

Root cause

SpecConstr already rejected any single source body larger than its per-function threshold, but it did not bound cumulative transitive inlining. A graph of small acyclic wrappers can call the preceding wrapper multiple times at every level, so every individual inline is admitted while the complete expansion grows exponentially.

In the basic-cli reproduction, the optimized test plan spent minutes in SpecConstr and grew past five million Monotype Lifted expressions and fifteen million inline scopes. This was compiler IR expansion rather than a process deadlock or platform download issue.

The new admission budget charges every accepted inline by the exact source-body size already computed by SpecConstr. Once the budget is spent, the pass preserves the existing ordinary call boundary. This keeps rewrite legality separate from code-growth admission and gives each clone a hard bound on transitive inline work.

Verification

  • exact issue reproduction: all 194 tests passed with roc test --opt=speed --no-cache
  • postcheck Zig module: 259/259 tests passed
  • postcheck architecture, semantic audit, Zig formatting, and Zig lint checks passed
  • full MiniCI: 75/75 phases passed

Closes #10618

@lukewilliamboswell
lukewilliamboswell marked this pull request as ready for review August 5, 2026 01:42
@greptile-apps

greptile-apps Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a cumulative inlining budget to Cloner in spec_constr.zig to bound transitive code growth across acyclic wrapper graphs. The existing per-function body-size gate only rejected single large bodies; small wrappers that each call the prior wrapper twice could still produce exponential IR expansion across levels. The fix charges each admitted inline against a shared per-clone budget (inline_body_work_budget = 4096) so that once the budget is spent the ordinary call boundary is preserved.

  • Refactors inlineBodyAdmission into inlineBodySize (returns BodySize) plus an inline admits() check, allowing the already-computed size to be reused by the new admitInlineBodyGrowth gate.
  • Adds inline_body_growth: CodeGrowthBudget to Cloner, initialised in both init and initForRewrite, and charged in both inlineCallableValue and inlineDirectCallValue after all other guards pass.
  • Includes a regression test building 14-deep binary wrapper chains and asserting that total IR growth stays under 2 × inline_body_work_budget.

Confidence Score: 5/5

Safe to merge. The change adds a cumulative admission budget that fires only after all existing correctness guards pass, so ordinary call boundaries are preserved whenever the budget is spent — the program's observable semantics are unaffected.

The budget is charged as the very last step before body substitution in both inlining paths, is scoped per-Cloner instance, and is consistently initialised in both init and initForRewrite. The refactored inlineBodySize helper cleanly avoids re-traversing already-computed body sizes. The regression test exercises the exponential-growth scenario end-to-end and asserts both the budget-exhaustion fallback (a retained call_proc) and the total growth bound.

Files Needing Attention: No files require special attention.

Important Files Changed

Filename Overview
src/postcheck/monotype_lifted/spec_constr.zig Adds inline_body_growth: CodeGrowthBudget to Cloner (budget 4096), refactors inlineBodyAdmission into inlineBodySize+admits(), and inserts admitInlineBodyGrowth checks as the final gate in both inlineCallableValue and inlineDirectCallValue. Logic is sound: budget is only charged after all other guards pass, is per-clone-instance, and the defensive orelse return false in admitInlineBodyGrowth correctly handles the (unreachable-in-practice) over_limit case.

Reviews (2): Last reviewed commit: "Address SpecConstr review feedback" | Re-trigger Greptile

Comment thread src/postcheck/monotype_lifted/spec_constr.zig Outdated
Comment thread src/postcheck/monotype_lifted/spec_constr.zig Outdated
@lukewilliamboswell
lukewilliamboswell merged commit 24f0b47 into main Aug 5, 2026
21 checks passed
@lukewilliamboswell
lukewilliamboswell deleted the fix-10618 branch August 5, 2026 06:32
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.

roc test --opt=speed freezes when using the basic-cli platform

1 participant