Skip to content

Avoid boxing captured variables - #1068

Merged
maleadt merged 2 commits into
mainfrom
tb/closure-boxes
Oct 28, 2026
Merged

maleadt merged 2 commits into
mainfrom
tb/closure-boxes

Conversation

@maleadt

@maleadt maleadt commented Oct 27, 2026

Copy link
Copy Markdown
Member

Julia 1.14's Test.detect_closure_boxes (JuliaLang/julia#60478) reports methods whose lowered code puts a captured variable in a Core.Box, which is untyped. It flagged 18 methods here; this fixes all of them and adds a test (guarded on the detector being available, so it currently runs on the nightly CI job).

  • Self-recursive local helpers in the IR passes (check_user/scan_uses, both rewrite_uses!, repoint!, walk, normalize_metadata!, remapType, has_load) are now top-level functions taking their state as arguments. The two identical use scanners in AddKernelState and add_input_arguments! share one helper; box_used_only_by_instructions recurses on itself and returns early; the Metal global-user scan is a worklist loop.
  • Variables assigned both outside and inside a do block (native_code in compile_method_instance, the code_native results, ft/f in call!/call_spirv_builtin!) are assigned once, or declared local in the block.
  • Other captured, reassigned variables: explain_error's mi and mcgen's spirv (passed to dump_output instead) are computed once, and lower_is_fpclass! caches the sign test in the loop instead of a closure.
  • Callback state stays mutable, but typed: the lazily computed emitted table in check_ir and the @device_code file counter are Refs.

Compile times of a few PTX, Metal and SPIR-V kernels are unchanged (within noise, if anything slightly faster). Tested on Julia 1.10, 1.13 and 1.14.

Captured locals that are reassigned, or local closures that call themselves, are
stored in a `Core.Box`, which is untyped. Hoist the recursive helpers of the IR
passes to top-level functions (or use a worklist), return values from blocks
instead of assigning outer variables, and use a typed `Ref` where a callback
genuinely keeps state.
Uses `Test.detect_closure_boxes`, available from Julia 1.14.
@maleadt
maleadt merged commit 2c6251a into main Oct 28, 2026
35 of 36 checks passed
@maleadt
maleadt deleted the tb/closure-boxes branch October 28, 2026 07:22
@codecov

codecov Bot commented Oct 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.13158% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.47%. Comparing base (272a9bd) to head (b111505).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
src/irgen.jl 84.05% 11 Missing ⚠️
src/metal.jl 95.45% 2 Missing ⚠️
src/determinism.jl 92.85% 1 Missing ⚠️
src/spirv.jl 75.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1068      +/-   ##
==========================================
+ Coverage   92.42%   92.47%   +0.05%     
==========================================
  Files          31       31              
  Lines        7323     7309      -14     
==========================================
- Hits         6768     6759       -9     
+ Misses        555      550       -5     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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