Repository navigation
feat(framework): unified-closure overloads of with_handle_scope and parallelize_safe - #129
Merged
Merged
Conversation
…arallelize_safe Mojo 1.2.0 kept moving stdlib and MAX APIs from compile-time closure parameters to runtime closure arguments (Tuple.consume_elements, SIMD.reduce, foreach, warp reductions). Both public closure APIs here were still legacy-only, which is the form that read a dead stack slot on 1.1.0. `with_handle_scope(b, env, body)` and `parallelize_safe(func, n)` now take the closure as `Some[def(...)]`, an ordinary argument with an explicit capture list, and hold the implementation. The new parallelize_safe forwards `func` straight to MAX's parallelize. The legacy `[body]`/`[func]` overloads are unchanged in signature and delegate, so downstream code compiles as before and can migrate ahead of the legacy form's removal. Each form has a runtime witness: scopedCallUnified and parallelSquaresUnified run the same work as scopedCall and parallelSquares through the new forms, and the host and runtime suites run every case against both. Mutation-checked: breaking either new overload fails every case for both exports of its pair, so the tests reach the shared implementation from both forms. Cover calls are added per overload. examples/vectors-addon.mojo moves to the unified form, and its results on 100,003-element vectors are bit-identical to the legacy build's. Also uses `match` (new in 1.2.0) in napi_status_name, js_type_name and createTypedArrayView's type dispatch. Output is unchanged; equivalence was checked over every input code before the rewrite. CLAUDE.md gains the rule that makes it safe: a named constant is `case == NAME:`, because a bare name binds and matches everything. scopedCall's generated .d.ts type goes from (a: any, b: any): any to a real signature, because its body now delegates and inference cannot see it. Verified on darwin-arm64 / Node 24.15.0: every CI compile (addon, coverage target, examples, FFI probe, kitchen sink) is warning-clean under --Werror; 862 tests, 12 GC tests; the full suite under Guard Malloc with MALLOC_STRICT_SIZE=1; all static gates; scopedCall benchmark unchanged (72.3 vs 71 ns). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adopts the two Mojo 1.2.0 patterns from the changelog review that earn their keep here.
1. Unified-closure overloads (public API, additive)
1.2.0 kept moving stdlib and MAX APIs from compile-time
capturingclosure parameters to runtime closure arguments (Tuple.consume_elements,SIMD.reduce,foreach, warp reductions). Our two public closure APIs were legacy-only, which is the form that silently read a dead stack slot on 1.1.0.with_handle_scope[body](b, env)andparallelize_safe[func](n)keep their signatures and delegate, so downstream (@qkstat/retrieve,@qkstat/embed) compiles unchanged.scopedCallUnifiedandparallelSquaresUnifiedrun the same work asscopedCallandparallelSquaresthrough the new forms.tests/host.test.jsandtests/runtime.test.jsrun every case against both.framework_coverage.mojo.examples/vectors-addon.mojonow uses the unified form. Its results on 100,003-element vectors are bit-identical to the legacy build's.2.
matchin three lookup functions (internal, no behaviour change)napi_status_name(24 literal arms),js_type_name(case ==on named constants, with null and object merged into one arm) andcreateTypedArrayView's string dispatch. Each was checked equivalent over every input code before the rewrite. CLAUDE.md gains the rule that makesmatchsafe here: a named constant iscase == NAME:, because a bare name binds a new variable and matches everything.--Werrorcatches most shapes, but not all.Also
scopedCall's generated.d.tstype goes from(a: any, b: any): anyto(n: number, fn: (i: number) => unknown): number. Its body now delegates, so inference cannot see it, and an explicit entry fixes that.docs/EXPORTS.md), and the API reference anddocs/MOJO-RULES.mdare regenerated.Considered and not adopted
not Copyable: the handle types are already non-copyable.EnumLike/case .NAME: it needs the unstable__enum.if case .Some(x) =: theOptionalsites are N-API output slots.comptime matchtype switches: there is no type dispatch to replace.Verified locally (darwin-arm64, Node 24.15.0)
--Werror: addon, coverage target, examples, FFI probe, kitchen sink.MALLOC_STRICT_SIZE=1.scopedCallbenchmark is unchanged (72.3 vs 71 ns).m0-interop(non-required) will still fail its gate step until m0 is re-gated on Mojo 1.2.0.🤖 Generated with Claude Code