Skip to content

Commit 6666e80

Browse files
author
Ralph Küpper
committed
docs: changelog fragment for #7831
1 parent 554986b commit 6666e80

1 file changed

Lines changed: 48 additions & 0 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
### Fixed
2+
3+
- **A declared numeric type is no longer treated as proof that the value is a
4+
number** (#7773, #7776). Perry does not enforce annotations at runtime, but
5+
codegen answered `is_numeric_expr` = `true` on the strength of one and then
6+
emitted bare f64 arithmetic on whatever the slot actually held.
7+
8+
That is worse than producing a `NaN`, because arithmetic on a NaN-BOXED value
9+
is not a no-op: `fadd`/`fmul` propagate the input NaN's payload, so a
10+
NaN-boxed string comes back out of the instruction still tagged as that
11+
string and flows on as if nothing happened — `typeof (v * 2)` answered
12+
`"string"`. Four divergences from Node, all silent: `o.x + 1` gave `NaN`
13+
where Node concatenates; `const v = o.x; v + 1` looked as though the `+ 1`
14+
had evaporated; `v * 2` returned the string; and summing a `P[]` with one
15+
`as any`-stored `Q` element gave `NaN`.
16+
17+
A new `numeric_proof_is_declared_only` separates "an annotation said so" from
18+
a real proof. It is deliberately narrower than
19+
`expr_may_return_boxed_value_from_raw_f64_fallback` (which answers "is there
20+
a raw-f64 tier worth trying" and stays true for reads with no boxed fallback
21+
at all): element-shape and class-field loop facts, `Ptr<Shape>` numeric
22+
fields, scalar replacement, POD records and typed arrays all answer `false`
23+
and keep their bare loads. `+` then lowers through an inline NaN-box tag test
24+
`fadd` on the fast arm, `js_dynamic_string_or_number_add` on the cold one —
25+
because the spec's `+` dispatches on the runtime value; every other
26+
arithmetic operator is a plain `ToNumber` and only needed the existing
27+
residual-coerce rule taught to see a refined LOCAL.
28+
29+
`expr/mod.rs::lower_numeric_binary_value` turned out to be a second
30+
arithmetic tier that bypasses `binary::lower` entirely and emits bare
31+
`fadd`/`fmul` with no residual coerce at all; it was the path both
32+
refined-local shapes took, and it now hands declared-only operands down the
33+
same way its two existing `Mod` cases do.
34+
35+
Two details are load-bearing and are pinned by the test. The diamond covers
36+
the whole `+` **tree**, not one node each: per-node diamonds make the outer
37+
add of `s += o.x + 1` consume a phi that LLVM cannot prove is a canonical
38+
double, which killed the `fadd` in the loop (+38% before fusing, +8.6%
39+
after). And every leaf is tested except those `expr_produces_canonical_raw_f64`
40+
vouches for — testing only the declared-only leaves skips the ACCUMULATOR,
41+
which holds a string the moment this lowering's own cold arm concatenates,
42+
and summed `16zw1113151719` down to `16zw`.
43+
44+
Measured on the quiet M1 mini, same runtime in both arms: element-shape clone
45+
218 → 217 ms (−0.5%, untouched), `this.v + 1` in a method 70 → 76 ms (+8.6%),
46+
`s += p.x + p.y` with an escaped receiver 196 → 263 ms (+34.2%). The cost
47+
falls only on reads nothing could prove, which already pay an inline header
48+
precheck or a `js_typed_feedback_class_field_get_guard` call.

0 commit comments

Comments
 (0)