// f64cgen_test — f64/f32 deref-load + NaN relop, migrated from // test/wcc/951_f64cgen_run.c (#5-C4, #96 + #97). Two GATE-BLIND f64 codegen // bugs (both stages byte-identical before+after the fix, so byte-id never // catches a reintroduction — the T1 cstage run is the live net): // #96 — f64/f32 deref-load must MOVSD/MOVSS into X0, not MOVQ into AX. // #97 — f64/f32 compare must consult PF (parity) for IEEE-754 NaN: with a NaN // operand `!=` is true, the other five relops false. // The C driver was cstage-ONLY (no byte-id leg — ww_ww run was broken #95); // this migration ADDS the T2 cs==ww byte-id assertion for the first time. package f64cgen_test; fn deref(p: *f64) f64 = { return *p; }; fn zero() f64 = { return 0.0; }; fn one() f64 = { return 1.0; }; fn z() f64 = { return 0.0; }; fn z32() f32 = { return 0.0; }; fn one32() f32 = { return 1.0; }; fn tobits(f: f64) u64 = { return *((&f): *u64); }; fn frombits(b: u64) f64 = { return *((&b): *f64); }; // #96 deref-load: dirty X0 with junk before the call so X0-retention can't mask // a broken return load; `*px` must reach X0 for the MULSD. @test fn deref_basic() void = { let x: f64 = 7.5; let y: f64 = 1.25; let px: *f64 = &x; let junk: f64 = y * 2.0; assert(junk == 2.5); let r: f64 = deref(px); assert(r == 7.5); let q: f64 = *px * 2.0; assert(q == 15.0); }; // #96 value propagation through an f64-returning fn then truncated to i32. @test fn deref_valueprop() void = { let x: f64 = 42.0; let r: f64 = deref(&x); assert(r: i32 == 42); }; @test fn arith_deref() void = { let x: f64 = 7.5; let p: *f64 = &x; let q: f64 = *p * 2.0; assert(q: i32 == 15); }; // #96 f64frombits round-trip: reinterpret a u64 bit pattern as f64. @test fn frombits_roundtrip() void = { let bits: u64 = 0x4045000000000000u64; let f: f64 = *((&bits): *f64); assert(f: i32 == 42); }; // #96 copysign(5.0, -1.0) == -5.0 built from tobits/frombits reinterprets. @test fn copysign() void = { let x: f64 = 5.0; let y: f64 = -1.0; let mag: u64 = tobits(x) & 0x7fffffffffffffffu64; let sgn: u64 = tobits(y) & 0x8000000000000000u64; let r: f64 = frombits(mag | sgn); assert(!(r > 0.0)); assert(r < -4.5); assert(r > -5.5); }; // #97 full NaN relop sweep (f64, UCOMISD). Runtime NaN via 0.0/0.0 through // opaque fns so the checker can't const-fold it. @test fn nan_sweep() void = { let z: f64 = zero(); let nan: f64 = z / z; let x: f64 = one(); assert(nan != nan); assert(!(nan == nan)); assert(!(nan == x)); assert(!(nan < x)); assert(!(nan <= x)); assert(!(nan > x)); assert(!(nan >= x)); assert(nan != x); assert(!(x != x)); assert(x == x); assert(x < 2.0); assert(x <= 1.0); assert(2.0 > x); assert(1.0 >= x); assert(!(x > 2.0)); }; // #97 value propagation: of the 6 relops against NaN, exactly one (`!=`) is true. @test fn nan_count() void = { let nan: f64 = z() / z(); let x: f64 = 1.0; let n: i32 = 0; if (nan == x) { n += 1; }; if (nan != x) { n += 1; }; if (nan < x) { n += 1; }; if (nan <= x) { n += 1; }; if (nan > x) { n += 1; }; if (nan >= x) { n += 1; }; assert(n == 1); }; // #97 f32 path (UCOMISS): NaN unordered rules, plus ordered f64 relops correct. @test fn f32_nan_ordered() void = { let z: f32 = z32(); let nan: f32 = z / z; let x: f32 = one32(); assert(nan != nan); assert(!(nan == nan)); assert(!(nan < x)); assert(!(nan >= x)); let a: f64 = 2.0; let b: f64 = 3.0; assert(a < b); assert(!(a > b)); assert(a <= a); assert(b >= a); assert(a == 2.0); assert(!(a != 2.0)); assert(!(b < a)); assert(b > a); }; // #97 `>`/`>=` left-bare arm with runtime-built operands (JA/JAE template // unchanged by the fix — guards the untouched arm). @test fn gt_only() void = { let a: f64 = z() + 2.0; let b: f64 = z() + 3.0; assert(b > a); assert(!(a > b)); assert(b >= a); assert(a >= a); };