Files
ef79aafea9 codegen: emit negative-zero float constants via math.Float*frombits (#27)
Go has no negative-zero literal: constant arithmetic is exact rational,
so `float64(-0)` is +0. Every emission path that rendered a finite float
constant as a decimal literal therefore collapsed f32.const/f64.const
-0.0 to +0, and the global-initializer path skipped -0 entirely behind
its `fv == 0` zero-value check. Found by go-spidermonkey's test262 run:
SpiderMonkey's CharsToNumber, fdlibm atan2 and Math.sumPrecise all
return the constant -0.0, so Number("-0") lost the sign of zero.

Route -0 through the same math.Float{32,64}frombits path NaN/Inf
already use (shared predicate floatNeedsBitsEmission), in goConstF32 /
goConstF64, floatInitExpr, and the global-initializer zero-skip. The
gcasm backend inherits the fix, since it compiles the generated Go.

Verified: new fixture cg_negzero.wat (constants direct, through calls,
global initializers, arithmetic identities, NaN payload) failed against
the wazero reference before the fix and passes after; `go test ./...`
exit 0.

Co-authored-by: Claude Fable 5 <[email protected]>
2026-07-12 09:08:50 +09:00

41 lines
1.8 KiB
WebAssembly Text Format

;; cg_negzero.wat — negative-zero float constants. Go has no -0.0 literal
;; (constant arithmetic is exact, so `float64(-0)` is +0): every constant
;; and global-initializer path must preserve the sign bit some other way.
;; Results are observed as raw bits via reinterpret so the fixture harness
;; compares them exactly.
(module
;; Constant emitted inside a function body (SSA OpConstF64/OpConstF32).
(func (export "f64_negzero_bits") (result i64)
(i64.reinterpret_f64 (f64.const -0)))
(func (export "f32_negzero_bits") (result i32)
(i32.reinterpret_f32 (f32.const -0)))
;; The same constants observed through a call, so a folded reinterpret
;; could not mask a broken literal.
(func $nz64 (result f64) (f64.const -0))
(func $nz32 (result f32) (f32.const -0))
(func (export "f64_negzero_call_bits") (result i64)
(i64.reinterpret_f64 (call $nz64)))
(func (export "f32_negzero_call_bits") (result i32)
(i32.reinterpret_f32 (call $nz32)))
;; Global initializers: -0 must not be treated as "zero value already
;; correct", and non-zero negatives must keep their sign.
(global $gnz64 f64 (f64.const -0))
(global $gnz32 f32 (f32.const -0))
(func (export "global_f64_negzero_bits") (result i64)
(i64.reinterpret_f64 (global.get $gnz64)))
(func (export "global_f32_negzero_bits") (result i32)
(i32.reinterpret_f32 (global.get $gnz32)))
;; -0 must survive arithmetic identities: x*1, x+(-0), min(-0,+0).
(func (export "f64_negzero_mul1_bits") (result i64)
(i64.reinterpret_f64 (f64.mul (f64.const -0) (f64.const 1))))
(func (export "f64_min_negzero_bits") (result i64)
(i64.reinterpret_f64 (f64.min (f64.const -0) (f64.const 0))))
;; NaN payload constants keep going through the frombits path.
(func (export "f64_nan_payload_bits") (result i64)
(i64.reinterpret_f64 (f64.const nan:0x123456)))
)