Skip to content

The walk widening a boxed store's array sources skips a value it already found unchanged - #7377

Merged
matz merged 1 commit into
matz:masterfrom
makenowjust:MakeNowJust/boxed-source-walk-memo
Oct 5, 2026
Merged

matz merged 1 commit into
matz:masterfrom
makenowjust:MakeNowJust/boxed-source-walk-memo

Conversation

@makenowjust

@makenowjust makenowjust commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #7376

What changes

widen_boxed_array_sources (with widen_boxed_elem_sources, src/analyze_pass.c) follows a value back to where its arrays are built, to depth 6, and remembered nothing of what it had walked. Unlike a predicate, it writes as it goes: it widens typed arrays, pins locals and method values to the general Array, and joins the store's element kind into a boxed parameter. So it cannot simply remember its answers; it remembers only the visits that changed nothing.

  • A visit that returned 0 is recorded with the depth it ran at, under the current generation. A later visit of the same node, at that depth or deeper and in the same generation, is skipped: with nothing changed in between, it would make a subset of the same calls in the same state (the depth only cuts the walk shorter), and those changed nothing.
  • A visit still on the stack (a cycle back to the same node) is never skipped; only completed visits are recorded.
  • A new generation starts at every call in the walk that may write the analysis state, whether or not it reports a change: widen_arg_array (its fresh-array pin writes the local's type back even when it answers 0), the pin blocks (which also write lv->type without a change), and a boxed parameter's element kinds when they move. A walk from outside opens and closes a generation of its own, so nothing carries from one walk to the next.

Why the results do not change

spinel -c was run on master and on the change from the same worktree, with the same input paths, in both overflow modes:

The comparison of every test/*.rb with this change alone is running; its result will be added to this pull request as a comment.

Before / after

The generator from the issue, spinel -c, counting the bodies of widen_boxed_array_sources that run (a local counter, not part of this change):

K walk steps before after
8 5,920 1,328
16 37,568 4,448
32 264,064 16,064
64 1,971,968 60,800
128 15,224,320 236,288

The steps went from about 7.5 times per doubling of K to about 4 times: within one walk each node is now expanded a bounded number of times, but every call site still starts a walk of its own over the caller's locals. Sharing what one walk found between the walks of one binding pass makes it linear; that is #7378, stacked on this one. K = 128 goes from 79 s to 27 s with this change alone; the rest is mostly the walk fixed in #7375.

A 6.9k-line machine-generated program: C generation 55 s → 4.3 s with this change and #7375.

(Timings are from a shared machine under load.)

Testing

make gate:

scale-test: instance_eval forwarding work at 2x the wrappers is 1.74x (limit 2.50)
scale-test: work at 4x the program is 4.74x (linear 4.00, limit 5.20)
scale-test: work at 4x the program, compiled to C, is 6.09x (limit 6.90)
scale-test: call-shape work at 4x the units, compiled to C, is 4.22x (linear 4.00, limit 4.50)
Tests: 5881 pass, 0 fail, 0 error
gate: stamp for tree f48c1d8c05dd on master 725905f64ce5; git commit --amend --no-edit adds the Gate: trailer
gate: ALL GREEN

The stamp line above names the origin/master the clone had fetched when the gate finished (725905f); the tree the gate ran was based on e5e8f79, as described below.

SPINEL_INT_OVERFLOW=promote make test: 5861 pass, 15 fail, 14 error. The 29 failures are exactly the set master e5e8f79 fails with (0 new, 0 fixed).

The gate ran on a branch based on e5e8f79 that combines this change, and #7378 stacked on it, with the other compile-time fixes from the same investigation (#7375, #7365, #7367, #7369, since merged). This branch was then rebased onto 92510d6 without conflicts; after the rebase, the generated C for 72 programs (63 tests and 9 machine-generated programs) in both modes was checked again against master and is unchanged.

Summary by CodeRabbit

  • Improvements
    • Array-source analysis now avoids repeating certain unchanged work, which may improve analysis efficiency while preserving its existing depth limit.

…ady found unchanged

widen_boxed_array_sources (with widen_boxed_elem_sources) follows every
write of a local to depth 6 with nothing to remember what it already
walked, so a function writing one local W times walked W^6 paths, from
every boxed push or index write and every call binding a boxed argument
whose parameter takes stores. A large machine-generated program
(6.9k lines) that writes the same few locals thousands of times, and
shifts them as `l0 << 1`, which counts as a push, spent 45 of its 48
seconds of C generation here.

The walk writes the analysis state as it goes, so only a visit that
changed nothing is remembered: under the current generation, with the
depth it ran at. A later visit of the same node, at that depth or
deeper and in the same generation, makes a subset of the same calls in
the same state, which change nothing either, and is skipped. Each walk
from outside opens and closes a generation, and so does every call in
it that may write the state, whether or not it reports a change (the
pin a local re-derived from its writes takes again writes without one).
The widenings, and the generated C, are unchanged.
@coderabbitai

coderabbitai Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 5da99a53-5661-46a8-b8b4-2253369b90da
📥 Commits

Reviewing files that changed from the base of the PR and between 92510d6 and 658649c.

📒 Files selected for processing (1)
  • src/analyze_pass.c

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 5 remain after this review.


📝 Walkthrough

Walkthrough

widen_boxed_array_sources now memoizes unchanged traversal visits by node, depth, and generation. It skips eligible repeated visits and advances the generation when analysis state changes.

Changes

Boxed-array source traversal

Layer / File(s) Summary
Memoized source walks
src/analyze_pass.c
The array-source and element-source walks record unchanged visits and skip visits memoized in the current generation at the same or greater depth. Both walks retain the depth limit of 6.
Generation invalidation
src/analyze_pass.c
Array argument widening, element type changes, and boxed-array pinning advance the memo generation.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: matz, francescok

Merge Risk: ⚪ Minimal · up to 65864

The memoization change is ready to merge after normal checks; no actionable risk remains from the reviewed traversal paths.

Architecture Summary

Architecture risk: 🟡 Medium · up to 65864

The change affects 1 system.

Changed systems: src

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — src (service) was modified; 1 changed file maps to changed impact.

Before / after behavior

  • observed — Modified behavior in src/analyze_pass.c: Added generation-based memoization for array-source traversal. No-change visits are recorded by node and depth; visits are skipped when recorded in the current generation at the same or greater depth. Outer walks advance the generation before and after traversal. Memo arrays grow to cover visited nodes, with allocation failure terminating the process.
  • observed — Modified behavior in src/analyze_pass.c: After widening an array argument, the code now advances the memo generation before returning, including when widening reports no change.
  • observed — Modified behavior in src/analyze_pass.c: When an element type changes, the code now advances the memo generation in addition to recording the change.
  • observed — Modified behavior in src/analyze_pass.c: The boxed-array pinning path now advances the memo generation after setting the local type to TY_POLY_ARRAY.

Reliability and maintainability

  • inferred — Risk-relevant change factors for src: blast_radius_2; blast_radius_3; direct_dependents_1; direct_dependents_2
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Issue #7376 asks to reduce repeated work in widen_boxed_array_sources and widen_boxed_elem_sources. The change memoizes completed no-change visits by node, depth, and generation, while avoiding sk…
Out of Scope Changes check ✅ Passed The change summary identifies memoization in src/analyze_pass.c. That work directly addresses issue #7376. The supplied evidence does not identify unrelated changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the array-source walk change: it skips a value after finding that the visit made no changes.
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@makenowjust

Copy link
Copy Markdown
Contributor Author

A correction to the description: the comparison of every test/*.rb with this change alone was stopped before it finished, for lack of machine time, and I do not plan to run it again. What supports this change is what the description lists (the 938-test subset and the machine-generated programs, compared together with #7375, and the 72 programs after the rebase). The same full comparison did finish for #7375 and found no difference beyond the embedded revision string.

@makenowjust

Copy link
Copy Markdown
Contributor Author

My previous comment was wrong about this one: the comparison had in fact finished before the job was stopped, and only the last step (comparing the outputs) was missing. It is done now.

spinel -c on every test/*.rb (5,789 files, both --int-overflow modes, 11,578 compilations), master 1be8484 against this change on top of it, built in turn in the same worktree and given the same input paths: the generated C differs in 8 compilations, the 4 tests that embed the build revision in RUBY_DESCRIPTION in both modes, and only in that revision string. Everything else is byte-identical, and the compilations that are refused are refused with the same exit status and message on both.

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.

Compile time grows polynomially in a local's writes in widen_boxed_array_sources

2 participants