Add unboxed operand lane for arithmetic binary operators#2664
Merged
Conversation
Comparison operators (PR sebastienros#2578) and bitwise operators read identifier operands as raw doubles through slot/global caches, but Plus/Minus/Times/ Divide still materialize JsNumber instances via the full identifier read path for every evaluation, including re-materializing unboxed slot values. Add NumericOperandLane covering id-op-id, id-op-const and const-op-id leaf shapes: both operands read via TryGetNumberSlotForRead (declines strings, objects, BigInt, TDZ - the generic path then owns coercion and errors), compute on raw doubles, box the result once. A nullable class rather than an embedded struct so the many non-numeric Plus nodes pay one reference field instead of two slot caches. Raw-double multiply preserves -0 for zero products of opposite signs, matching the boxed integer arm fixed in sebastienros#2620. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The exclusion predated sebastienros#2620: the boxed integer-multiply arm used to produce +0 for 0 * -5, so a raw-double lane would have changed observable results. sebastienros#2620 routes zero products through double arithmetic, and an unboxed slot stores -0 exactly, so x = a * b can now use the same slot-to-slot lane as + - / %. Extends IntegerMultiplicationPreservesNegativeZero with the discard-mode assignment shapes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The lane object costs an extra dereference per evaluation on hit-heavy loops (MixedArithmetic +8.7% vs the embedded-struct comparison lanes). Minus/Times/Divide are almost always numeric and follow the comparison classes precedent of an embedded lane struct; Plus keeps a heap-allocated wrapper so string-concat nodes pay one null reference instead of an embedded slot-cache pair. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lahma
enabled auto-merge (squash)
July 13, 2026 12:26
This was referenced Jul 15, 2026
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.
Summary
Comparison operators got an unboxed slot-operand lane in #2578 and bitwise operators have
BitwiseIdentifierLane, but the arithmetic operators (+ - * /) still evaluate identifier operands through the full boxed read path —JintIdentifierExpression.GetValue→MaterializeUnboxedOrNull, re-materializing aJsNumberfor every unboxed slot value written by the compound/assignment fast paths. ETW profiling shows Times/Plus as top drivers into the identifier-read lane (~11% inclusive on a SunSpider mix).This adds
NumericOperandLaneforid op id,id op numericConstandnumericConst op idleaf shapes: both operands are read as raw doubles through the establishedSlotLocationCache+TryGetNumberSlotForRead+ validated-global-descriptor machinery, the op computes on raw doubles, and the result is boxed once byJsNumber.Create.Three commits:
valueOf/ BigInt / TDZ ordering are untouched (the generic path still owns them). Suspendable frames decline (generator resume replays a saved left operand). Integer-typed results are preserved (Create(double)normalizes integral values to the same Integer-typed instances the boxed arms produce), so downstream integer fast paths stay armed.*in the numeric assignment fast path — the exclusion rationale predated Preserve -0 in integer multiplication fast paths #2620: the boxed integer-multiply arm used to produce+0for0 * -5; since Preserve -0 in integer multiplication fast paths #2620 routes zero products through double arithmetic, and an unboxed slot stores-0exactly,x = a * bcan use the same slot lane as+ - / %.IntegerMultiplicationPreservesNegativeZeroextended with the discard-mode shapes.BoxedNumericOperandLanebuilt only when the shape matches, so the many string-concat Plus nodes carry a single null reference instead of an embedded slot-cache pair.Depends on the
JsNumber.Create(double)fmod fix (#2662) — the lane boxes results throughCreate(double), which made the fmod-based integral detection a first-order cost.Benchmarks (default jobs, adjacent A/B vs #2662 branch)
s + tdecline guard)Gates
Object.is),valueOfcalled exactly once through the generic path, string concat, TDZ left-then-right ReferenceError ordering, integer-typing chains ((a*b) % 5stays on the integer remainder path) — verified cold and cache-armed🤖 Generated with Claude Code