diff --git a/Makefile b/Makefile index f504f448..f5d5cfbe 100644 --- a/Makefile +++ b/Makefile @@ -295,7 +295,6 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_globtagreassign_run \ $(BIN)/test_globstructwiden_run \ $(BIN)/test_globtagwiden_run \ - $(BIN)/test_globfloatstructarg_run \ $(BIN)/test_arr_ptr_global \ $(BIN)/test_def_arr_infer_len \ $(BIN)/test_def_arr_len \ @@ -381,7 +380,6 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_peellint_gate \ $(BIN)/test_const_slice_aggregate_run \ $(BIN)/test_append_structlit_evalorder_run \ - $(BIN)/test_f9_float_run \ $(BIN)/test_xmod_fnptr_const_run \ $(BIN)/test_tagged_structlit_payload_run \ $(BIN)/test_forrange_fieldbase_run \ @@ -531,9 +529,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_septest_run \ $(BIN)/test_coloimport_sep \ $(BIN)/test_depmain_sep \ - $(BIN)/test_floatlit_run \ $(BIN)/test_checked_run \ - $(BIN)/test_floatarr_run \ $(BIN)/test_inferred_scalar_global_run \ $(BIN)/test_globalslice_arg_run \ $(BIN)/test_structlit_arrfield_run \ @@ -545,7 +541,6 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_def_mangle_run \ $(BIN)/test_arr_u16_store_run \ $(BIN)/test_arr_module_index_run \ - $(BIN)/test_arr_float_call_index_run \ $(BIN)/test_def_float_lit_run \ $(BIN)/test_struct_composite_init_run \ $(BIN)/test_array_static_init_run \ @@ -553,13 +548,8 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_array_init_acceptiffits_run \ $(BIN)/test_arrlit_elem_narrow_run \ $(BIN)/test_amp_def_global_run \ - $(BIN)/test_f64cgen_run \ $(BIN)/test_f64crossmod_run \ $(BIN)/test_tuprecv_run \ - $(BIN)/test_f64xmm_run \ - $(BIN)/test_f32lit_run \ - $(BIN)/test_f32stamp_run \ - $(BIN)/test_f32arg_run \ $(BIN)/test_tuprecv_f64_run \ $(BIN)/test_assert_builtin_run \ $(BIN)/test_modqualdestr_run \ @@ -1073,16 +1063,6 @@ $(BIN)/test_globtagwiden_run: test/wcc/989_globtagwiden_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< -# 989_globfloatstructarg_run (F8-c9, #31, the EXCEPTION): a module-global -# float-bearing struct passed by value must drain its float eightbytes into -# the SSE arg regs. align-UP — structfloatclass keyed off the global tnode. -$(BIN)/test_globfloatstructarg_run: test/wcc/989_globfloatstructarg_run.c \ - $(BIN)/ww $(BIN)/ww_ww \ - $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - $(BIN)/test_let_global: test/wcc/630_let_global.c $(BIN)/ww $(BIN)/w6c \ $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< @@ -2247,12 +2227,6 @@ $(BIN)/test_append_structlit_evalorder_run: test/wcc/946_append_structlit_evalor $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< -$(BIN)/test_f9_float_run: test/wcc/949_f9_float_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ - $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - $(BIN)/test_xmod_fnptr_const_run: test/wcc/949_xmod_fnptr_const_run.c \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ @@ -2899,10 +2873,6 @@ $(BIN)/test_depmain_sep: test/wcc/989_depmain_sep.c $(BIN)/ww $(BIN)/ww_ww \ $(BIN)/w6l $(BIN)/w6l_ww $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< -$(BIN)/test_floatlit_run: test/wcc/989_floatlit_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - $(BIN)/test_bufio_run: test/wcc/998_bufio_run.c $(BIN)/ww $(BIN)/w6c \ $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< @@ -2915,11 +2885,6 @@ $(BIN)/test_checked_run: test/wcc/969_checked_run.c $(BIN)/ww $(BIN)/w6c \ $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< -$(BIN)/test_floatarr_run: test/wcc/946_floatarr_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - $(BIN)/test_inferred_scalar_global_run: test/wcc/947_inferred_scalar_global_run.c $(BIN)/ww \ $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) @@ -2976,11 +2941,6 @@ $(BIN)/test_arr_module_index_run: test/wcc/915_arr_module_index_run.c $(BIN)/ww $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< -$(BIN)/test_arr_float_call_index_run: test/wcc/916_arr_float_call_index_run.c \ - $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - $(BIN)/test_def_float_lit_run: test/wcc/917_def_float_lit_run.c \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) @@ -3016,10 +2976,6 @@ $(BIN)/test_amp_def_global_run: test/wcc/921_amp_def_global_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< -$(BIN)/test_f64cgen_run: test/wcc/951_f64cgen_run.c $(BIN)/ww $(BIN)/w6c \ - $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - $(BIN)/test_floats_run: test/wcc/952_floats_run.c $(BIN)/ww $(BIN)/w6c \ $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< @@ -3051,29 +3007,9 @@ $(BIN)/test_tuprecv_run: test/wcc/954_tuprecv_run.c $(BIN)/ww \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< -$(BIN)/test_f64xmm_run: test/wcc/955_f64xmm_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_f32lit_run: test/wcc/964_f32lit_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_f32stamp_run: test/wcc/965_f32stamp_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_f32arg_run: test/wcc/907_f32arg_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) - $(CC) $(CFLAGS) -o $@ $< - -$(BIN)/test_tuprecv_f64_run: test/wcc/956_tuprecv_f64_run.c $(BIN)/ww \ - $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ - $(LIB)/libwwrt.a | $(BIN) +# 956 SLIM PIN (#5-C4): asserttyped stamp dimension only — runs w6c_ww and greps +# its stderr (no run/link). Value rows + byte-id live in test/lang/tuprecv_f64_test.ww. +$(BIN)/test_tuprecv_f64_run: test/wcc/956_tuprecv_f64_run.c $(BIN)/w6c_ww | $(BIN) $(CC) $(CFLAGS) -o $@ $< $(BIN)/test_assert_builtin_run: test/wcc/957_assert_builtin_run.c $(BIN)/ww \ @@ -3162,7 +3098,7 @@ test-lang: all LANGBYTEID_DIR = $(OUT)/langbyteid LANGBYTEID_FILES = $(filter-out %_runonly_test.ww,$(wildcard test/lang/*_test.ww)) LANGBYTEID_VERB = test -c -LANGBYTEID_EXPECTED_MIN = 82 +LANGBYTEID_EXPECTED_MIN = 93 $(if $(LANGBYTEID_FILES),,$(error test-lang-byteid: empty corpus)) test-lang-byteid: all @set -e; \ diff --git a/test/lang/arr_float_call_index_test.ww b/test/lang/arr_float_call_index_test.ww new file mode 100644 index 00000000..cbb456c9 --- /dev/null +++ b/test/lang/arr_float_call_index_test.ww @@ -0,0 +1,56 @@ +// arr_float_call_index_test — float arr[i]= with X0-clobbering index, migrated +// from test/wcc/916_arr_float_call_index_run.c (#5-C4, #125). In the `arr[i] = v` +// ASSIGN path, when the element type is float and the INDEX sub-expression +// clobbers X0 (e.g. a fn-call index), the value was LOST pre-fix: both stages +// PUSHed AX (junk for floats), never spilled X0 across the idx/base eval, then +// re-emitted MOVSS/MOVSD X0,(BX) using the already-clobbered X0. The fix spills +// X0 around the idx/base eval for float elements (SUBQ/MOVSD ...(SP)). Broken +// byte-identically between stages (pre-existing, exposed by #122) — T1 run + T2 +// byte-id. The non-call-index rows are regression guards (lit/localvar/arith +// indices don't clobber X0; worked pre-fix). + +package arr_float_call_index_test; + +fn geti(x: f64) i32 = { + let y: f64 = x + 1.0; + return (y: i32); +}; + +// int-arg call: avoids a pre-call f32-arg-push MOVSD-vs-MOVSS sibling divergence; +// the body's f64 arith still clobbers X0. +fn getj(seed: i32) i32 = { + let y: f64 = (seed: f64) + 1.0; + return (y: i32); +}; + +@test fn f64_call_index() void = { + let a: [4]f64 = [99.0, 99.0, 99.0, 99.0]; + a[geti(1.0)] = 1.5f64; + assert((a[2]: i32) == 1); +}; + +@test fn f32_call_index() void = { + let a: [4]f32 = [99.0f32, 99.0f32, 99.0f32, 99.0f32]; + a[getj(1)] = 1.5f32; + assert((a[2]: i32) == 1); +}; + +@test fn f64_lit_index() void = { + let a: [4]f64 = [99.0, 99.0, 99.0, 99.0]; + a[2] = 1.5f64; + assert((a[2]: i32) == 1); +}; + +@test fn f64_localvar_index() void = { + let a: [4]f64 = [99.0, 99.0, 99.0, 99.0]; + let k: i32 = 2; + a[k] = 1.5f64; + assert((a[2]: i32) == 1); +}; + +@test fn f64_arith_index() void = { + let a: [4]f64 = [99.0, 99.0, 99.0, 99.0]; + let k: i32 = 1; + a[k + 1] = 1.5f64; + assert((a[2]: i32) == 1); +}; diff --git a/test/lang/f32arg_test.ww b/test/lang/f32arg_test.ww new file mode 100644 index 00000000..6e09a01b --- /dev/null +++ b/test/lang/f32arg_test.ww @@ -0,0 +1,56 @@ +// f32arg_test — f32 function-argument spill width, migrated from +// test/wcc/907_f32arg_run.c (#5-C4, #143). Passing an f32 as a function argument +// must spill through the stack at f32 width (MOVSS, 4-byte), not MOVSD (8-byte). +// cstage lowered the arg-push/pop with a hardcoded MOVSD; wwstage already split +// f32/f64 via exprfloatkind. The bug was a pure cs!=ww byte-id break (the callee +// reads its f32 param via MOVSS regardless), so T2 byte-id is the primary net; +// the T1 cstage run also asserts the end-to-end XMM round-trip value. + +package f32arg_test; + +fn takef32(x: f32) f32 = { return x; }; + +fn id(x: f32) f32 = { return x; }; + +fn add3(a: f32, b: f32, c: f32) f32 = { return a + b + c; }; + +fn mix(a: f32, b: f64, c: f32) f64 = { return a: f64 + b + c: f64; }; + +fn ten(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32, g: f32, h: f32, i: f32, j: f32) f32 = { + return a + b + c + d + e + f + g + h + i + j; +}; + +@test fn single_var() void = { + let a: f32 = 2.5f32; + let r: f32 = takef32(a); + assert(r: i32 == 2); +}; + +@test fn literal_arg() void = { + let r: f32 = id(3.0f32); + assert(r: i32 == 3); +}; + +@test fn multi_f32() void = { + let x: f32 = 1.5f32; + let y: f32 = 2.5f32; + let z: f32 = 4.0f32; + let r: f32 = add3(x, y, z); + assert(r: i32 == 8); +}; + +// f32 + f64 MIX: the f64 arg stays MOVSD, the f32 args MOVSS, per-arg agree. +@test fn mixed_f32_f64() void = { + let p: f32 = 1.5f32; + let q: f64 = 10.0; + let s: f32 = 0.5f32; + let r: f64 = mix(p, q, s); + assert(r: i32 == 12); +}; + +// STACK-passed f32: 8 ride X0..X7, args 9+10 stay on the stack (fpidx >= 8). +@test fn stack_passed() void = { + let v: f32 = 1.0f32; + let r: f32 = ten(v, v, v, v, v, v, v, v, v, v); + assert(r: i32 == 10); +}; diff --git a/test/lang/f32lit_test.ww b/test/lang/f32lit_test.ww new file mode 100644 index 00000000..5738218e --- /dev/null +++ b/test/lang/f32lit_test.ww @@ -0,0 +1,43 @@ +// f32lit_test — f32-suffixed literal narrowing, migrated from +// test/wcc/964_f32lit_run.c (#5-C4, #104 fold-1). An f32-typed float literal +// must narrow to single precision (CVTSD2SS at the materialise site) before the +// f32 consumer reads it; pre-fix both stages materialised it as a 64-bit double +// in X0 and the downstream MOVSS read the low 4 bytes (garbage; 0.0f for clean +// values, which is why 0.0 coincidentally survived). Covers the f32 suffix +// (1.0f32) and the no-decimal N_INTLIT-float arm (8f32). Both stages emit +// byte-identical asm, so byte-id alone never catches a reintroduction — the T1 +// cstage run is the live net; T2 byte-id rides along. + +package f32lit_test; + +fn g() f32 = { return 2.5f32; }; + +@test fn bare_value() void = { + let x: f32 = 1.0f32; + assert(x: f64 == 1.0); +}; + +@test fn arith() void = { + let a: f32 = 1.5f32; + let b: f32 = 2.5f32; + let s: f32 = a + b; + assert(s: f64 == 4.0); +}; + +@test fn return_arith() void = { + let r: f32 = g() + 1.5f32; + assert(r: i32 == 4); +}; + +@test fn intlit_f32_arm() void = { + let y: f32 = 8f32; + assert(y: i32 == 8); +}; + +// genuine single-rounding: 2^24 + 1 is NOT representable in f32 and rounds back +// to 2^24 (round-to-even); if the add ran in double it would be 16777217.0. +@test fn single_round() void = { + let big: f32 = 16777216.0f32; + let r: f32 = big + 1.0f32; + assert(r: f64 == 16777216.0); +}; diff --git a/test/lang/f32stamp_test.ww b/test/lang/f32stamp_test.ww new file mode 100644 index 00000000..3732e5f1 --- /dev/null +++ b/test/lang/f32stamp_test.ww @@ -0,0 +1,40 @@ +// f32stamp_test — un-suffixed f32-context literal checker-stamp, migrated from +// test/wcc/965_f32stamp_run.c (#5-C4, #104 fold-2). An UN-suffixed float literal +// in an f32 context (`let x: f32 = 1.0`, `return 1.0` from an f32 fn) must be +// stamped f32 by the checker so fold-1's cgen narrow fires; without the stamp it +// stays ty_untyped_float, materialises as a 64-bit double, and the f32 consumer +// reads the low 4 bytes (0.0f for clean values). SCOPE (#104 fold-2): the stamp +// fires at let-init and return ONLY — binop/unary/assign/call-arg/struct-field +// are deferred to #120, so this pins bare let-init / return literals exclusively. +// Both stages byte-identical; T1 cstage run is the live net, T2 byte-id rides. + +package f32stamp_test; + +fn g() f32 = { return 2.0; }; + +fn h() f32 = { return 4.0; }; + +@test fn let_one() void = { + let x: f32 = 1.0; + assert(x: f64 == 1.0); +}; + +@test fn let_decimal() void = { + let y: f32 = 8.0; + assert(y: i32 == 8); +}; + +@test fn let_frac() void = { + let p: f32 = 0.5; + assert(p: f64 == 0.5); +}; + +@test fn return_bare() void = { + assert(g(): i32 == 2); +}; + +@test fn return_then_let() void = { + let r: f32 = h(); + assert(r: f64 == 4.0); + assert(r: i32 == 4); +}; diff --git a/test/lang/f64cgen_test.ww b/test/lang/f64cgen_test.ww new file mode 100644 index 00000000..21adf810 --- /dev/null +++ b/test/lang/f64cgen_test.ww @@ -0,0 +1,143 @@ +// 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); +}; diff --git a/test/lang/f64xmm_test.ww b/test/lang/f64xmm_test.ww new file mode 100644 index 00000000..f7e64e85 --- /dev/null +++ b/test/lang/f64xmm_test.ww @@ -0,0 +1,55 @@ +// f64xmm_test — f64 XMM materialise + tuple .0 compare, migrated from +// test/wcc/955_f64xmm_run.c (#5-C4, #103). Two GATE-BLIND faces (both stages +// emitted byte-identical-but-wrong asm, so byte-id never catches a reintroduction +// — the T1 cstage run is the live net): +// FACE X — a no-decimal float-typed integer literal (`0f64`, `8f64`) is an +// N_INTLIT carrying float TYPE; the integer-immediate path stranded it in +// AX, so `n == 0f64` compared a stale X0 and `(8f64 * 10.0): i32` read +// garbage. Fixed by routing the float-typed N_INTLIT through the float- +// constant-in-X0 emit (+ the wwstage exprfloatkind N_INTLIT arm). +// FACE Z — a tuple positional f64 field read (`r.0`, r:(f64,i64)) loaded via +// the integer op into AX, so `r.0 == 0.0` was wrongly true. Fixed by a +// fld_isfloat branch -> MOVSD/MOVSS into X0. + +package f64xmm_test; + +fn g0(n: f64) i32 = { if (n == 0f64) { return 1; }; return 0; }; + +fn gneg(n: f64) i32 = { if (n == -8f64) { return 1; }; return 0; }; + +fn norm(n: f64) (f64, i64) = { return (n, 0); }; + +@test fn x_cmp_false() void = { + assert(g0(8.0) == 0); +}; + +@test fn x_cmp_true() void = { + assert(g0(0.0) == 1); +}; + +@test fn x_arith() void = { + assert((8f64 * 10.0): i32 == 80); +}; + +// `-8f64` is N_UN(TK_MINUS) over a float-typed N_INTLIT — the wwstage +// exprfloatkind must recurse through the unary into the N_INTLIT-float arm. +@test fn x_neg_cmp_true() void = { + assert(gneg(-8.0) == 1); +}; + +@test fn x_neg_cmp_false() void = { + assert(gneg(8.0) == 0); +}; + +// FACE Z: r.0 == 0.0 with r = (8.0, 0) must be false (bug: wrongly true). +@test fn z_tuple_field() void = { + const r = norm(8.0); + assert(r.0 != 0.0); +}; + +// CONTROL: the let-bound spelling was already correct and must stay correct. +@test fn z_letbound_control() void = { + const r = norm(8.0); + const m = r.0; + assert(m != 0.0); +}; diff --git a/test/lang/f9_float_test.ww b/test/lang/f9_float_test.ww new file mode 100644 index 00000000..749a77be --- /dev/null +++ b/test/lang/f9_float_test.ww @@ -0,0 +1,56 @@ +// f9_float_test — F9 argument widen/drain ABI (float), migrated from +// test/wcc/949_f9_float_run.c (#5-C4, #30/#48/#49). The cgcall push/drain of a +// CONCRETE arg widened into a tagged-union param slot, where a float is involved: +// #30 — a float arg AFTER a widened (i64|void) arg under-drained by one GP word +// (the f64 read the box's leftover payload). +// #48 — the widened arg IS an f64 source: the float arm ate the TAG word into +// X0 and the payload landed in DI as the tag. +// #49 (#263) — a runtime f64 widened into (f64|void) whose arm reads the +// payload: the widen-PUSH did PUSHQ AX while the f64 sat in X0. +// All align wwstage UP to cstage's precomputed widen handling; T1 run + T2 +// byte-id. Register-cursor invariant (rob): the values survive only if tag/ +// payload land in the right GP regs AND the float in the right XMM. + +package f9_float_test; + +fn f(a: (i64 | void), b: f64) f64 = { return b; }; + +fn g(v: (i64 | f64)) i32 = { + match (v) { + case let n: i64 => + return 1; + case let d: f64 => + return 2; + }; + return 9; +}; + +type fv = (f64 | void); + +fn mk(x: f64) f64 = { return x + 1.5; }; + +fn take(v: fv) i32 = { + match (v) { + case let d: f64 => { + if (d == 2.5) { return 0; }; + return 1; + }; + case void => { return 2; }; + }; + return 3; +}; + +@test fn widen_then_float() void = { + let r: f64 = f(7, 1.5); + assert(r == 1.5); +}; + +@test fn float_source_widen() void = { + let d: f64 = 3.5; + assert(g(d) == 2); +}; + +@test fn runtime_float_widen_payload() void = { + let d: f64 = mk(1.0); + assert(take(d) == 0); +}; diff --git a/test/lang/floatarr_test.ww b/test/lang/floatarr_test.ww new file mode 100644 index 00000000..2c00bcef --- /dev/null +++ b/test/lang/floatarr_test.ww @@ -0,0 +1,44 @@ +// floatarr_test — float array-element load/store, migrated from +// test/wcc/946_floatarr_run.c (#5-C4, #119 + #122). A float ARRAY ELEMENT load +// must land in X0 (MOVSS/MOVSD), not the integer register file (MOVQ -> AX); the +// element STORE (array-lit init, arr[i]=, and [v...] repeat-fill) must route FROM +// X0 via MOVSS/MOVSD, not write the raw double low-bits via MOVL AX. Pre-fix the +// f64 elements loaded into AX while the consumer's ADDSD read a stale X0, and f32 +// slots read back garbage. The N_INDEX result type is checker-stamped (dodging +// the #121 unstamped trap). Both stages byte-identical; T1 run + T2 byte-id. + +package floatarr_test; + +@test fn f64_arith() void = { + let a: [3]f64 = [1.5, 2.5, 9.0]; + assert(a[0] + a[1] == 4.0); +}; + +@test fn f64_trunc() void = { + let a: [3]f64 = [1.5, 2.5, 9.0]; + assert(a[0]: i32 == 1); +}; + +@test fn f64_elem2() void = { + let a: [3]f64 = [1.5, 2.5, 9.0]; + assert(a[2] == 9.0); +}; + +@test fn f32_arith() void = { + let b: [2]f32 = [1.5f32, 2.5f32]; + assert((b[0] + b[1]): f64 == 4.0); +}; + +// arr[i]= index store (#122) into a [0.0f32,0.0f32]-init array. +@test fn f32_index_store() void = { + let b: [2]f32 = [0.0f32, 0.0f32]; + b[0] = 1.5f32; + b[1] = 2.5f32; + assert((b[0] + b[1]): f64 == 4.0); +}; + +// [v...] repeat-fill init store (#122): 1.5 * 3 == 4.5 (exact in IEEE). +@test fn f32_repeat_fill() void = { + let c: [3]f32 = [1.5f32...]; + assert((c[0] + c[1] + c[2]): f64 == 4.5); +}; diff --git a/test/lang/floatlit_test.ww b/test/lang/floatlit_test.ww new file mode 100644 index 00000000..4e8b73b2 --- /dev/null +++ b/test/lang/floatlit_test.ww @@ -0,0 +1,45 @@ +// floatlit_test — float LITERAL fold correctly-rounded, migrated from +// test/wcc/989_floatlit_run.c (#5-C4). The two stages once folded float +// literals differently: cstage via strtod (correctly rounded), wwstage via a +// pow-10 accumulation that was 1-2 ULP off on decimal fractions, overflowed its +// i64 accumulator past 19 mantissa digits, and missed DBL_MIN/DBL_MAX by up to +// 2 ULP (989 ratchet #59.10). The fix routes lexnum through strconv.stof64 (the +// Hare-ported correctly-rounded decimal engine). +// +// Each row reads back a literal's IEEE bits via a *u64 reinterpret and asserts +// the C-strtod-oracle bit pattern. The C driver ran (a) cstage build+run + (b) +// w6c-vs-w6c_ww .s byte-id; here the cstage run is test-lang (T1) and byte-id is +// test-lang-byteid (T2), so both dimensions survive. The overflow-reject leg +// (1.7976931348623159e308, both stages must reject) is the fold-3 runww carrier +// test/wcc/data/floatlit_overflow/case.ww. + +package floatlit_test; + +fn bits(v: f64) u64 = { + let x: f64 = v; + let p: *u64 = (&x): *u64; + return *p; +}; + +@test fn vectors() void = { + assert(bits(1.0000000000000002) == 0x3FF0000000000001u64); + assert(bits(9007199254740993.0) == 0x4340000000000000u64); + assert(bits(1.2345e67) == 0x4DDD4E421712C0B7u64); + assert(bits(0.1) == 0x3FB999999999999Au64); + assert(bits(1.1) == 0x3FF199999999999Au64); + assert(bits(123456789012345678901234567890.0) == 0x45F8EE90FF6C373Eu64); + assert(bits(2.2250738585072014e-308) == 0x0010000000000000u64); + assert(bits(0.3) == 0x3FD3333333333333u64); + assert(bits(3.141592653589793) == 0x400921FB54442D18u64); + assert(bits(1.7976931348623157e308) == 0x7FEFFFFFFFFFFFFFu64); + assert(bits(1.7976931348623158e308) == 0x7FEFFFFFFFFFFFFFu64); + assert(bits(7.2057594037927933e16) == 0x4370000000000000u64); + assert(bits(1000000000000000000000.0) == 0x444B1AE4D6E2EF50u64); + assert(bits(1_000.5) == 0x408F440000000000u64); + assert(bits(1.00000000000000011102230246251565404236316680908203125) == 0x3FF0000000000000u64); + assert(bits(1.00000000000000011102230246251565404236316680908203126) == 0x3FF0000000000001u64); + assert(bits(4503599627370497.5) == 0x4330000000000002u64); + assert(bits(0.5) == 0x3FE0000000000000u64); + assert(bits(1.0e308) == 0x7FE1CCF385EBC8A0u64); + assert(bits(2.225073858507202e-308) == 0x0010000000000001u64); +}; diff --git a/test/lang/globfloatstructarg_test.ww b/test/lang/globfloatstructarg_test.ww new file mode 100644 index 00000000..a63811a0 --- /dev/null +++ b/test/lang/globfloatstructarg_test.ww @@ -0,0 +1,40 @@ +// globfloatstructarg_test — module-global float-bearing struct passed by value, +// migrated from test/wcc/989_globfloatstructarg_run.c (#5-C4, #31, F8 EXCEPTION). +// cgcall's per-arg drain decides the SysV eightbyte class via structfloatclass +// read from the local slot's tnode ONLY; a module-global struct arg (lc==nil) +// kept stfc=0, so the float eightbytes drained as integers (POPQ into DI/SI) and +// X0 was never loaded — the callee read its f64 fields from the wrong registers. +// cstage classifies off the operand TYPE and ran correct — the cat-A divergence. +// The fix keys structfloatclass off the global's declared tnode (align ww UP); +// .s is byte-identical. The C driver ran both stages (rule-10 agree+hit); here +// T1 cstage run + T2 byte-id. Each row had its own `pair`/`gp`; renamed per-row +// for single-file coexistence (the global-vs-local drain class is unchanged). + +package globfloatstructarg_test; + +type pair_ff = struct { f: f64, i: i64 }; +let gp_ff: pair_ff = pair_ff { f = 1.5, i = 9 }; +fn take_ff(p: pair_ff) int = { if (p.f == 1.5 && p.i == 9) { return 0; }; return 2; }; + +type pair_if = struct { i: i64, f: f64 }; +let gp_if: pair_if = pair_if { i = 9, f = 2.5 }; +fn take_if(p: pair_if) int = { if (p.i == 9 && p.f == 2.5) { return 0; }; return 2; }; + +type pair_bb = struct { a: f64, b: f64 }; +let gp_bb: pair_bb = pair_bb { a = 1.5, b = 2.5 }; +fn take_bb(p: pair_bb) int = { if (p.a == 1.5 && p.b == 2.5) { return 0; }; return 2; }; + +// float_first: f->X0 (#31). +@test fn float_first() void = { + assert(take_ff(gp_ff) == 0); +}; + +// int_first: int eb->GP, f->X0. +@test fn int_first() void = { + assert(take_if(gp_if) == 0); +}; + +// both_float: two SSE eightbytes. +@test fn both_float() void = { + assert(take_bb(gp_bb) == 0); +}; diff --git a/test/lang/tuprecv_f64_test.ww b/test/lang/tuprecv_f64_test.ww new file mode 100644 index 00000000..6649bd98 --- /dev/null +++ b/test/lang/tuprecv_f64_test.ww @@ -0,0 +1,230 @@ +// tuprecv_f64_test — f64 SysV SSE-cursor tuple receive, migrated from +// test/wcc/956_tuprecv_f64_run.c (#5-C4, #105 + #164/#107 + #10). A tuple-from- +// call receive (single-var 16B/32B, destructure, reassign) must spill each +// element CLASS-AWARE: an f64/f32 word `MOVSD/MOVSS X0, slot`, an integer word +// `MOVQ , slot`. #105 (the headline) is RUNTIME-only and byte-id-BLIND — +// both stages were symmetric-WRONG on master (both MOVQ AX,slot), diverging only +// at runtime — so the T1 cstage run (which executes the asserts) is the live net +// here; T2 byte-id guards rule-10. #164/#107 gave the multi-float tuple return a +// parallel SSE cursor [X0,X1]; #10 sret's the over-cap (f64,f64,f64). The bug +// rows use BRANCHED callees with an f64-param word (an inner issub() CALL +// clobbers AX) — single-return callees mask the bug via register coincidence. +// +// SPLIT DIMENSION: the C driver's chk_stamped sub-dimension (w6c_ww emits NO +// `asserttyped:` diagnostic on the float destructure binding — the #121 A-narrow +// stamp net) is a wwstage-stderr grep test-lang has no channel for (T1 cstage run +// + T2 .s byte-id). It is RETAINED in the slim C pin test/wcc/956_tuprecv_f64_run.c +// (drew C4 ruling: slim-in-place, the 954 ctl_destr precedent), NOT dropped. + +package tuprecv_f64_test; + +fn issub_f64(n: f64) bool = { return false; }; + +fn issub_i64(n: i64) bool = { return false; }; + +fn norm_fi(n: f64) (f64, i64) = { + if (issub_f64(n)) { return (n * 2.0, -52); }; + return (n, 0); +}; + +fn norm_if(n: f64) (i64, f64) = { + if (issub_f64(n)) { return (-52, n * 2.0); }; + return (0, n); +}; + +fn clob(x: f64) f64 = { return x + 1.0; }; + +fn pair(a: f64, b: f64) (f64, f64) = { + if (issub_f64(a)) { return (a * 2.0, b * 2.0); }; + return (a, b); +}; + +fn tri(a: i64, b: f64, c: i64) (i64, f64, i64) = { + if (issub_i64(a)) { return (a * 2, b * 2.0, c * 2); }; + return (a, b, c); +}; + +fn fs(n: f64) (f64, str) = { + if (issub_f64(n)) { return (n * 2.0, "x"); }; + return (n, "hello"); +}; + +fn sf(n: f64) (str, f64) = { + if (issub_f64(n)) { return ("x", n * 2.0); }; + return ("hello", n); +}; + +fn si(n: i64) (str, i64) = { + if (issub_i64(n)) { return ("x", n * 2); }; + return ("hello", n); +}; + +fn tri3(a: f64, b: f64, c: f64) (f64, f64, f64) = { + return (a, b, c); +}; + +fn mk_int(n: i64) (i64, i64) = { + if (issub_i64(n)) { return (n * 2, -1); }; + return (n, 7); +}; + +fn mk_fz() (f64, i64) = { return (2.5, 7); }; + +// BUG — minimal repro: branched norm, f64 word is param n not a literal. +@test fn f64_i64_br() void = { + const r = norm_fi(16.0); + const m: f64 = r.0; + assert(m == 16.0); +}; + +// BUG — deferred read: an intervening f64 CALL clobbers X0 AFTER the receive; +// r.0 must come from the spilled slot, not a stale X0. +@test fn f64_i64_deferred() void = { + const r = norm_fi(16.0); + const junk: f64 = clob(3.0); + const m: f64 = r.0; + assert(m == 16.0); + assert(junk == 4.0); +}; + +// BUG — order-swap (i64, f64): f64 is word1. +@test fn i64_f64_br() void = { + const r = norm_if(16.0); + const i: i64 = r.0; + const m: f64 = r.1; + assert(m == 16.0); + assert(i == 0); +}; + +// BUG — DESTRUCTURE `let (m,i)=norm()` (N_MLET / cgmlet+tupstore). +@test fn destr_f64_i64_br() void = { + let (m, i) = norm_fi(16.0); + assert(m == 16.0); + assert(i == 0); +}; + +// BUG — DESTRUCTURE order-swap (i64,f64): f64 binding is element 1 (cursor DX). +@test fn destr_i64_f64_br() void = { + let (i, m) = norm_if(16.0); + assert(m == 16.0); + assert(i == 0); +}; + +// BUG — REASSIGN `m,i = norm()` (N_MASSIGN / cgmassign+tupstore). +@test fn massign_f64_i64_br() void = { + let m: f64 = 0.0; + let i: i64 = 0; + m, i = norm_fi(16.0); + assert(m == 16.0); + assert(i == 0); +}; + +// BUG — REASSIGN order-swap (i64,f64): f64 target is element 1 (cursor DX). +@test fn massign_i64_f64_br() void = { + let i: i64 = 0; + let m: f64 = 0.0; + i, m = norm_if(16.0); + assert(m == 16.0); + assert(i == 0); +}; + +// #164 — multi-float (f64,f64) destructure: on master both elements collide on +// X0; post-fix a rides X0, b rides X1. +@test fn f64f64_destr_br() void = { + let (x, y) = pair(3.0, 5.0); + assert(x == 3.0); + assert(y == 5.0); +}; + +// #164 — multi-float (f64,f64) SINGLE-VAR whole-tuple receive (16B rt16 branch). +@test fn f64f64_single_br() void = { + const r = pair(3.0, 5.0); + const x: f64 = r.0; + const y: f64 = r.1; + assert(x == 3.0); + assert(y == 5.0); +}; + +// #164 — multi-float (f64,f64) REASSIGN. +@test fn f64f64_massign_br() void = { + let x: f64 = 0.0; + let y: f64 = 0.0; + x, y = pair(3.0, 5.0); + assert(x == 3.0); + assert(y == 5.0); +}; + +// #164 — INTERLEAVED (i64,f64,i64): kills naive position->reg; i64s ride AX,DX, +// f64 rides X0 on an independent counter. +@test fn i64_f64_i64_destr() void = { + let (x, y, z) = tri(3, 2.0, 7); + assert(x == 3); + assert(y == 2.0); + assert(z == 7); +}; + +// #164 — (f64,str): SSE + wide 24B header coexist; f64->X0, str->AX,DX,CX. +@test fn f64_str_destr() void = { + let (f, s) = fs(4.0); + assert(f == 4.0); + assert(s.len == 5); +}; + +// #164 STR-FIRST (str,f64): wide header in slot 0, f64 consumes no GP slot. +@test fn str_f64_destr() void = { + let (s, f) = sf(4.0); + assert(s.len == 5); + assert(f == 4.0); +}; + +// #164 STR-FIRST (str,i64): pure-integer str-first, str@AX,DX,CX then i64@R8. +@test fn str_i64_destr() void = { + let (s, k) = si(7); + assert(s.len == 5); + assert(k == 7); +}; + +// #164 STR-FIRST SINGLE-VAR annotated `let t: (str,i64)` — the shape the old 32B +// single-var branch got wrong (read .ptr from DX while send placed it in AX). +@test fn str_i64_single() void = { + let t: (str, i64) = si(7); + assert(t.1 == 7); + assert(t.0.len == 5); +}; + +// #10 OVER-CAP RECV — three f64 exceeds the SSE return cap (X0,X1); #10 sret's +// it (callee stores X0/X1 -> @sretarg) and destructures it out of @sretscr. +@test fn f64x3_recv() void = { + let (x, y, z) = tri3(1.0, 2.0, 3.0); + assert(x == 1.0); + assert(y == 2.0); + assert(z == 3.0); +}; + +// CONTROL — all-integer branched 2-tuple (MOVQ path untouched). +@test fn ctl_int_br() void = { + const r = mk_int(5); + const a: i64 = r.0; + const b: i64 = r.1; + assert((a: i32) + (b: i32) == 12); +}; + +// CONTROL — destructure with NO f64 (integer element takes the unchanged path). +@test fn ctl_destr() void = { + let (a, b) = mk_int(5); + assert((a: i32) + (b: i32) == 12); +}; + +// CONTROL — #103 FACE-Z single-return (f64,i64) field read (literal f64 word). +@test fn ctl_facez_single() void = { + const t = mk_fz(); + const f: f64 = t.0; + const i: i64 = t.1; + assert((f: i32) + (i: i32) == 9); +}; + +// CONTROL — #103 FACE-X bare 0f64 compare (no tuple). +@test fn ctl_facex_0f64() void = { + const z: f64 = 0.0; + assert(z == 0.0); +}; diff --git a/test/wcc/907_f32arg_run.c b/test/wcc/907_f32arg_run.c deleted file mode 100644 index 2c6545a7..00000000 --- a/test/wcc/907_f32arg_run.c +++ /dev/null @@ -1,225 +0,0 @@ -/* - * 907_f32arg_run — runtime + byte-id regression net for #143: passing an - * f32 as a function argument must spill through the stack at the f32 class - * width (MOVSS, 4-byte), not MOVSD (8-byte). cstage lowered the arg-push - * (cgen.c N_CALL push) and arg-pop (the pop-into-XMM loop) with a hardcoded - * MOVSD; wwstage already split f32/f64 via exprfloatkind (MOVSS for f32). - * The result was a cs!=ww byte-id break on EVERY f32 argument — a pure - * divergence, not a runtime miscompile (the callee reads its f32 param as - * the low 32 bits via MOVSS regardless of how the caller spilled it), but - * it blocked the cs==ww gate and therefore fold-5b (strconv f32tos, whose - * core calls math.f32bits(n) — an f32 arg). - * - * ABI-correct form is MOVSS for single precision per SysV: ref/qbe - * amd64/emit.c:524 — the slot->slot copy-through-XMM path (exactly ww's - * X0 -> 8B stack slot -> XMMn arg-spill shape) narrows the class to Ks for - * non-wide floats and emits movss (clstoa[Ks] == "ss"); movsd is the - * double (Kd) form. The fix aligns cstage UP to MOVSS via the existing - * op_for(node_isf32) helper at both the push and pop sites. wwstage is - * unchanged. This is the f32-arg-push half of the float-register family - * (#119 f32-arr-load, #122 f32-arr-store, #125 float-arr-index, #157 - * tagged-float-return) — "f32 must route through the right XMM width." - * - * Each row carries BOTH dimensions (like 955 / 964 / 965): - * (a) cstage `ww build` + run, asserting the exit code (end-to-end ABI: - * the callee must read the correct f32 value out of the XMM reg). - * (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge (rule-10). - * - * The 990-997 byte-id gates are BLIND to a reintroduction here only if the - * gated selfhost tools never pass an f32 arg (they don't — bootstrap- - * NEUTRAL), so this executed-and-byte-id-checked probe is the live net. - */ -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -struct row { const char *label; const char *src; int want_exit; }; - -static const struct row rows[] = { - /* single f32 var arg — the minimal repro. takef32(2.5) -> 2.5, - * truncated to i32 == 2. On the bug the byte-id breaks (MOVSD push) - * but the value still rounds (MOVSD carries the low 4 bytes too). */ - { "single_var", - "package main;\n" - "fn takef32(x: f32) f32 = { return x; };\n" - "export fn main() i32 = {\n" - " let a: f32 = 2.5f32;\n" - " let r: f32 = takef32(a);\n" - " return r: i32;\n" - "};\n", 2 }, - /* f32 LITERAL directly as the arg (ken's original getj(1.0f32) - * shape that surfaced #143). id(3.0f32) -> 3.0 -> 3. */ - { "literal_arg", - "package main;\n" - "fn id(x: f32) f32 = { return x; };\n" - "export fn main() i32 = {\n" - " let r: f32 = id(3.0f32);\n" - " return r: i32;\n" - "};\n", 3 }, - /* multiple f32 args — each pushed/popped independently; all must - * use MOVSS on both stages. 1.5 + 2.5 + 4.0 == 8.0 (exact in f32). */ - { "multi_f32", - "package main;\n" - "fn add3(a: f32, b: f32, c: f32) f32 = { return a + b + c; };\n" - "export fn main() i32 = {\n" - " let x: f32 = 1.5f32;\n" - " let y: f32 = 2.5f32;\n" - " let z: f32 = 4.0f32;\n" - " let r: f32 = add3(x, y, z);\n" - " return r: i32;\n" - "};\n", 8 }, - /* f32 + f64 MIX — the f64 arg must stay MOVSD (8-byte), the f32 args - * MOVSS (4-byte), and BOTH stages must agree per-arg. 1.5 + 10.0 + - * 0.5 == 12.0 (exact). Guards against the fix over-reaching to f64. */ - { "mixed_f32_f64", - "package main;\n" - "fn mix(a: f32, b: f64, c: f32) f64 = " - "{ return a: f64 + b + c: f64; };\n" - "export fn main() i32 = {\n" - " let p: f32 = 1.5f32;\n" - " let q: f64 = 10.0;\n" - " let s: f32 = 0.5f32;\n" - " let r: f64 = mix(p, q, s);\n" - " return r: i32;\n" - "};\n", 12 }, - /* STACK-passed f32: 10 float args — 8 ride X0..X7, args 9+10 stay on - * the stack (fpidx >= 8). The push-spill still diverged on the bug; - * this exercises the fpidx>=8 leave-on-stack arm. 1.0 * 10 == 10.0. */ - { "stack_passed", - "package main;\n" - "fn ten(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32, " - "g: f32, h: f32, i: f32, j: f32) f32 = {\n" - " return a + b + c + d + e + f + g + h + i + j;\n" - "};\n" - "export fn main() i32 = {\n" - " let v: f32 = 1.0f32;\n" - " let r: f32 = ten(v, v, v, v, v, v, v, v, v, v);\n" - " return r: i32;\n" - "};\n", 10 }, - { NULL, NULL, 0 } -}; - -static int -slurp_eq(const char *a, const char *b) -{ - FILE *fa = fopen(a, "rb"); - FILE *fb = fopen(b, "rb"); - if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } - int rc = 0; - for (;;) { - int ca = fgetc(fa); - int cb = fgetc(fb); - if (ca != cb) { rc = -1; break; } - if (ca == EOF) break; - } - fclose(fa); fclose(fb); - return rc; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[1024]; - if (bin[0] != '/') { - char cwd[1024]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char w6c[1100], w6c_ww[1100]; - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - if (access(w6c_ww, X_OK) != 0) { - fprintf(stderr, "f32arg: w6c_ww missing — cannot run the " - "cs==ww byte-id gate (the whole point of this test)\n"); - return 1; - } - - int n = 0, fail = 0; - for (int i = 0; rows[i].src; i++, n++) { - char tmpdir[64]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/wwf32a_%d_d_%d", - getpid(), i); - mkdir(tmpdir, 0755); - - char src[128], outbin[128], rmcmd[160]; - snprintf(src, sizeof src, "%s/wwf32a_%d_%d.ww", - tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wwf32a_%d_%d", - tmpdir, getpid(), i); - snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); - - FILE *f = fopen(src, "wb"); - if (f == NULL) { fail++; runwait(rmcmd); continue; } - fputs(rows[i].src, f); - fclose(f); - - /* (a) cstage build + run. */ - char cmd[2048]; - snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s", - bin, outbin, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: cstage build failed\n", - rows[i].label); - fail++; - runwait(rmcmd); - continue; - } - - int got = runwait(outbin); - if (got != rows[i].want_exit) { - fprintf(stderr, "row[%s]: cstage exit %d, want %d\n", - rows[i].label, got, rows[i].want_exit); - fail++; - } - - /* (b) cs==ww byte-id gate: emit .s from both stages, cmp. */ - char cs_s[128], ws_s[128]; - snprintf(cs_s, sizeof cs_s, "%s/wwf32a_%d_%d_cs.s", - tmpdir, getpid(), i); - snprintf(ws_s, sizeof ws_s, "%s/wwf32a_%d_%d_ww.s", - tmpdir, getpid(), i); - - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c, cs_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label); - fail++; runwait(rmcmd); continue; - } - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c_ww, ws_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c_ww failed\n", - rows[i].label); - fail++; runwait(rmcmd); continue; - } - if (slurp_eq(cs_s, ws_s) != 0) { - fprintf(stderr, - "row[%s]: cstage/wwstage .s DIFFER (rule-10 " - "byte-id violation)\n", rows[i].label); - fail++; - } - runwait(rmcmd); - } - - if (fail) { - fprintf(stderr, "%d/%d f32-arg-push tests failed\n", fail, n); - return 1; - } - printf("f32arg: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n); - return 0; -} diff --git a/test/wcc/916_arr_float_call_index_run.c b/test/wcc/916_arr_float_call_index_run.c deleted file mode 100644 index 83b8c0b3..00000000 --- a/test/wcc/916_arr_float_call_index_run.c +++ /dev/null @@ -1,235 +0,0 @@ -/* - * 916_arr_float_call_index_run — runtime + byte-id net for #125: in - * the `arr[i] = v` ASSIGN path, when the element type is float and the - * INDEX sub-expression clobbers X0 (e.g. a fn-call index), the value - * was LOST pre-fix because both stages PUSHed AX (junk for floats) + - * never spilled X0 across the idx/base eval, then re-emitted MOVSS/ - * MOVSD X0, (BX) using the already-clobbered X0. - * - * The bug was load-bearing only after #122 routed the value through - * X0 (pre-#122 it sat in AX and the existing PUSH AX accidentally - * protected it). Broken byte-identically between cstage and wwstage, - * so pre-existing + non-regression but exposed by #122. - * - * Fix mirrors the *p = v float deref-store at cmd/w6c/cgen.c:4187 - * (the only other arr[i]= /-style float store): for float elements, - * substitute the PUSHQ AX / POPQ AX pair around the idx/base eval - * with SUBQ $8,SP + MOVSS/MOVSD X0,(SP) ... MOVSS/MOVSD (SP),X0 + - * ADDQ $8,SP. Non-float keeps PUSHQ AX (the original) so the str/ - * slice 3-word pop order at the end of the branch is undisturbed. - * - * The fix changes asm shape for ALL float arr[i]= stores (its own - * scoped fold, byte-identical between stages); covered by the 990- - * 997 byte-id gates + this test's standalone w6c vs w6c_ww cmp. - * - * Rows cover: - * - f64_call_index: a[geti(1.0)] = 1.5f64 — the canonical repro, - * geti is a fn that adds 1 and returns i32 (its body clobbers X0). - * Pre-fix exit=2 (a[2] held geti's last X0=2.0 → cast i32=2); - * post-fix exit=1 (a[2] = 1.5 → cast i32 = 1). - * - f32_call_index: same shape, f32 element — pre-fix even worse - * because CVTSD2SS at #104 only touched X0; AX store would be - * garbage. Post-fix exit=1 same as f64. - * - f64_lit_index: a[2] = 1.5f64 with a LITERAL index — pre-fix - * this already worked (cgexpr of an int literal doesn't clobber - * X0). Regression guard. - * - f64_localvar_index: a[k] = 1.5f64 with k = 2 (local var) — - * pre-fix also worked (MOVQ off(BP), AX doesn't clobber X0). - * Regression guard. - * - f64_arith_index: a[k + 1] = 1.5f64 — integer arith on locals - * doesn't clobber X0 either; pre-fix worked. Regression guard. - * - * Each row carries (a) cstage `ww build` + run asserting exit code - * and (b) w6c vs w6c_ww `.s` cmp (rule-10 byte-id). - */ -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -struct row { const char *label; const char *src; int want_exit; }; - -static const struct row rows[] = { - { "f64_call_index", - "package main;\n" - "export fn geti(x: f64) i32 = {\n" - " let y: f64 = x + 1.0;\n" - " return (y: i32);\n" - "};\n" - "export fn main() i32 = {\n" - " let a: [4]f64 = [99.0, 99.0, 99.0, 99.0];\n" - " a[geti(1.0)] = 1.5f64;\n" - " let v: i32 = (a[2]: i32);\n" - " return v;\n" - "};\n", 1 }, - /* Body of getj uses f64 arith → clobbers X0 via the materialise- - * literal-in-X0 sequence and the ADDSD. The call's argument is - * int (not float), so the call-arg-push goes through the integer - * convention and avoids a pre-existing cs/ww f32-arg-push MOVSD- - * vs-MOVSS divergence in pre-call arg materialisation (sibling - * bug, filed separately). Pre-fix the post-call X0 (set to the - * residue of 1.0+1.0 = 2.0) would overwrite the 1.5f32 in X0, - * yielding garbage. */ - { "f32_call_index", - "package main;\n" - "export fn getj(seed: i32) i32 = {\n" - " let y: f64 = (seed: f64) + 1.0;\n" - " return (y: i32);\n" - "};\n" - "export fn main() i32 = {\n" - " let a: [4]f32 = [99.0f32, 99.0f32, 99.0f32, 99.0f32];\n" - " a[getj(1)] = 1.5f32;\n" - " let v: i32 = (a[2]: i32);\n" - " return v;\n" - "};\n", 1 }, - { "f64_lit_index", - "package main;\n" - "export fn main() i32 = {\n" - " let a: [4]f64 = [99.0, 99.0, 99.0, 99.0];\n" - " a[2] = 1.5f64;\n" - " let v: i32 = (a[2]: i32);\n" - " return v;\n" - "};\n", 1 }, - { "f64_localvar_index", - "package main;\n" - "export fn main() i32 = {\n" - " let a: [4]f64 = [99.0, 99.0, 99.0, 99.0];\n" - " let k: i32 = 2;\n" - " a[k] = 1.5f64;\n" - " let v: i32 = (a[2]: i32);\n" - " return v;\n" - "};\n", 1 }, - { "f64_arith_index", - "package main;\n" - "export fn main() i32 = {\n" - " let a: [4]f64 = [99.0, 99.0, 99.0, 99.0];\n" - " let k: i32 = 1;\n" - " a[k + 1] = 1.5f64;\n" - " let v: i32 = (a[2]: i32);\n" - " return v;\n" - "};\n", 1 }, - { NULL, NULL, 0 } -}; - -static int -slurp_eq(const char *a, const char *b) -{ - FILE *fa = fopen(a, "rb"); - FILE *fb = fopen(b, "rb"); - if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } - int rc = 0; - for (;;) { - int ca = fgetc(fa); - int cb = fgetc(fb); - if (ca != cb) { rc = -1; break; } - if (ca == EOF) break; - } - fclose(fa); fclose(fb); - return rc; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[1024]; - if (bin[0] != '/') { - char cwd[1024]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char w6c[1100], w6c_ww[1100]; - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - if (access(w6c_ww, X_OK) != 0) { - fprintf(stderr, "arrfcidx: w6c_ww missing — cannot run " - "the cs==ww byte-id gate (the whole point of this test)\n"); - return 1; - } - - int n = 0, fail = 0; - for (int i = 0; rows[i].src; i++, n++) { - char tmpdir[64]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/wwafc_%d_d_%d", - getpid(), i); - mkdir(tmpdir, 0755); - - char rmcmd[160]; - snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); - - char src[128], outbin[128], cs_s[128], ws_s[128]; - snprintf(src, sizeof src, "%s/wwafc_%d_%d.ww", - tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wwafc_%d_%d", - tmpdir, getpid(), i); - snprintf(cs_s, sizeof cs_s, "%s/wwafc_%d_%d_cs.s", - tmpdir, getpid(), i); - snprintf(ws_s, sizeof ws_s, "%s/wwafc_%d_%d_ww.s", - tmpdir, getpid(), i); - - FILE *f = fopen(src, "wb"); - if (f == NULL) { runwait(rmcmd); fail++; continue; } - fputs(rows[i].src, f); - fclose(f); - - char cmd[2048]; - snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s", - bin, outbin, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: cstage build failed\n", - rows[i].label); - fail++; - runwait(rmcmd); - continue; - } - - int got = runwait(outbin); - if (got != rows[i].want_exit) { - fprintf(stderr, "row[%s]: cstage exit %d, want %d\n", - rows[i].label, got, rows[i].want_exit); - fail++; - } - - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c, cs_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label); - fail++; runwait(rmcmd); continue; - } - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c_ww, ws_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c_ww failed\n", - rows[i].label); - fail++; runwait(rmcmd); continue; - } - if (slurp_eq(cs_s, ws_s) != 0) { - fprintf(stderr, - "row[%s]: cstage/wwstage .s DIFFER (rule-10 " - "byte-id violation)\n", rows[i].label); - fail++; - } - runwait(rmcmd); - } - - if (fail) { - fprintf(stderr, "%d/%d arr-float-call-idx tests failed\n", fail, n); - return 1; - } - printf("arrfcidx: %d/%d ok (cstage run + cs==ww byte-id)\n", - n, n); - return 0; -} diff --git a/test/wcc/946_floatarr_run.c b/test/wcc/946_floatarr_run.c deleted file mode 100644 index dafefe2f..00000000 --- a/test/wcc/946_floatarr_run.c +++ /dev/null @@ -1,233 +0,0 @@ -/* - * 946_floatarr_run — runtime + byte-id net for #119: a float ARRAY - * ELEMENT load must land in X0 (MOVSS/MOVSD), not the integer register - * file (MOVQ → AX). cgindex's element-load sites ended in the integer - * loadopsz, so `let a: [3]f64 = [...]; a[0] + a[1]` loaded the element - * into AX while the consumer's ADDSD read a stale X0 → wrong sum. #119 - * adds a float-element branch (MOVSS f32 / MOVSD f64 into X0) at all - * three wwstage cgindex sites (global, baselocal, fallback) and both - * cstage N_INDEX element-load sites, deriving float-ness from the same - * stamped element tinfo the esz already reads (elemisfloatc/elemisf32c - * for ident bases, typeisfloat/typeisf32(n.type_) for N_DOT/N_INDEX - * bases — never an unstamped node-stamp, dodging the #121 trap). - * - * #119 also required a wwstage exprfloatkind N_INDEX arm: the consumer - * (cgbin / cgcast) classified an indexed float operand as INTEGER and - * fell to PUSHQ/ADDQ/MOVSXD, while the cstage read the stamped operand - * type and used ADDSD/CVTTSD2SI — a rule-10 divergence the load fix - * exposed. The N_INDEX result type_ is checker-stamped (cgindex reads - * it for esz), so this is not the unstamped-N_MLET case deferred under - * #121. - * - * Each row carries (a) a cstage `ww build` + run asserting the exit - * code, and (b) a w6c vs w6c_ww `.s` cmp (rule-10 byte-id). A row whose - * want_exit is RUN_SKIP runs only the byte-id leg. - * - * #122 fixes the store-side twin the #119 commit deferred: the f32 - * array-element STORE wrote AX (the raw double low-bits) instead of the - * CVTSD2SS-narrowed X0 single, so every f32 array slot read back garbage. - * Both the array-literal-init store (cgen.c:6889 / cgenstmt:949) and the - * arr[i]= index store (cgen.c:3818 / cgenexpr:4209) now route FROM X0 via - * MOVSS/MOVSD, mirroring the scalar float store. The f32 rows below now - * assert the runtime VALUE (not byte-id only) and add an arr[i]= store - * plus a [v...] repeat-fill init (a distinct cgen arm #122 also fixed). - */ -#include -#include -#include -#include -#include - -#define RUN_SKIP (-1) - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -struct row { const char *label; const char *src; int want_exit; }; - -static const struct row rows[] = { - /* f64 element load + float add: 1.5 + 2.5 == 4.0. On the bug the - * elements loaded into AX and the ADDSD read a stale X0 -> != 4.0. */ - { "f64_arith", - "package main;\n" - "export fn main() i32 = {\n" - " let a: [3]f64 = [1.5, 2.5, 9.0];\n" - " if (a[0] + a[1] != 4.0) { return 1; };\n" - " return 0;\n" - "};\n", 0 }, - /* f64 element -> i32 cast: 1.5 truncates to 1. On the bug the cast - * operand classified integer (MOVSXD on AX) not CVTTSD2SI on X0. */ - { "f64_trunc", - "package main;\n" - "export fn main() i32 = {\n" - " let a: [3]f64 = [1.5, 2.5, 9.0];\n" - " return a[0]: i32;\n" - "};\n", 1 }, - /* third element, non-adjacent index: a[2] == 9.0. */ - { "f64_elem2", - "package main;\n" - "export fn main() i32 = {\n" - " let a: [3]f64 = [1.5, 2.5, 9.0];\n" - " if (a[2] != 9.0) { return 1; };\n" - " return 0;\n" - "};\n", 0 }, - /* f32 array-literal-init store + element load + add: suffixed - * literals so fold-1 narrows them. Pre-#122 the init store wrote - * MOVL AX (raw double low-bits) so the slots read garbage; #122 - * routes the store from X0 via MOVSS, so 1.5 + 2.5 == 4.0. */ - { "f32_arith", - "package main;\n" - "export fn main() i32 = {\n" - " let b: [2]f32 = [1.5f32, 2.5f32];\n" - " if ((b[0] + b[1]): f64 != 4.0) { return 1; };\n" - " return 0;\n" - "};\n", 0 }, - /* f32 arr[i]= index store (#122): assign each slot, read back. - * The index-store path popped the value to AX and wrote MOVL (raw - * double low-bits, garbage for f32); #122 stores from X0 via MOVSS. - * The [0.0f32,0.0f32] init also exercises the array-lit store. */ - { "f32_index_store", - "package main;\n" - "export fn main() i32 = {\n" - " let b: [2]f32 = [0.0f32, 0.0f32];\n" - " b[0] = 1.5f32;\n" - " b[1] = 2.5f32;\n" - " if ((b[0] + b[1]): f64 != 4.0) { return 1; };\n" - " return 0;\n" - "};\n", 0 }, - /* f32 [v...] repeat-fill init store (#122): the repeat marker - * fills every slot from the last element's X0 single; pre-#122 the - * fill wrote MOVL AX (raw double low-bits) per slot so each read - * back garbage. Distinct cgen arm from the per-element list store. - * 1.5 * 3 == 4.5 (exact in IEEE). */ - { "f32_repeat_fill", - "package main;\n" - "export fn main() i32 = {\n" - " let c: [3]f32 = [1.5f32...];\n" - " if ((c[0] + c[1] + c[2]): f64 != 4.5) { return 1; };\n" - " return 0;\n" - "};\n", 0 }, - { NULL, NULL, 0 } -}; - -static int -slurp_eq(const char *a, const char *b) -{ - FILE *fa = fopen(a, "rb"); - FILE *fb = fopen(b, "rb"); - if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } - int rc = 0; - for (;;) { - int ca = fgetc(fa); - int cb = fgetc(fb); - if (ca != cb) { rc = -1; break; } - if (ca == EOF) break; - } - fclose(fa); fclose(fb); - return rc; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[1024]; - if (bin[0] != '/') { - char cwd[1024]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char w6c[1100], w6c_ww[1100]; - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - if (access(w6c_ww, X_OK) != 0) { - fprintf(stderr, "floatarr: w6c_ww missing — cannot run the " - "cs==ww byte-id gate (the whole point of this test)\n"); - return 1; - } - - int n = 0, fail = 0; - for (int i = 0; rows[i].src; i++, n++) { - char tmpdir[64]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/wwfarr_%d_d_%d", - getpid(), i); - mkdir(tmpdir, 0755); - - char src[128], outbin[128], cs_s[128], ws_s[128], rmcmd[160]; - snprintf(src, sizeof src, "%s/wwfarr_%d_%d.ww", - tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wwfarr_%d_%d", - tmpdir, getpid(), i); - snprintf(cs_s, sizeof cs_s, "%s/wwfarr_%d_%d_cs.s", - tmpdir, getpid(), i); - snprintf(ws_s, sizeof ws_s, "%s/wwfarr_%d_%d_ww.s", - tmpdir, getpid(), i); - snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); - - FILE *f = fopen(src, "wb"); - if (f == NULL) { fail++; runwait(rmcmd); continue; } - fputs(rows[i].src, f); - fclose(f); - - /* (a) cstage build + run (skipped for byte-id-only rows). */ - if (rows[i].want_exit != RUN_SKIP) { - char cmd[2048]; - snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s", - bin, outbin, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: cstage build failed\n", - rows[i].label); - fail++; - runwait(rmcmd); - continue; - } - - int got = runwait(outbin); - if (got != rows[i].want_exit) { - fprintf(stderr, "row[%s]: cstage exit %d, want %d\n", - rows[i].label, got, rows[i].want_exit); - fail++; - } - } - - /* (b) cs==ww byte-id gate: emit .s from both stages, cmp. */ - char cmd[2048]; - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c, cs_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label); - fail++; runwait(rmcmd); continue; - } - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c_ww, ws_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c_ww failed\n", - rows[i].label); - fail++; runwait(rmcmd); continue; - } - if (slurp_eq(cs_s, ws_s) != 0) { - fprintf(stderr, - "row[%s]: cstage/wwstage .s DIFFER (rule-10 " - "byte-id violation)\n", rows[i].label); - fail++; - } - runwait(rmcmd); - } - - if (fail) { - fprintf(stderr, "%d/%d float-array element tests failed\n", - fail, n); - return 1; - } - printf("floatarr: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n); - return 0; -} diff --git a/test/wcc/949_f9_float_run.c b/test/wcc/949_f9_float_run.c deleted file mode 100644 index 3d15d2c1..00000000 --- a/test/wcc/949_f9_float_run.c +++ /dev/null @@ -1,242 +0,0 @@ -/* - * 949_f9_float_run — F9 argument widen/drain ABI (float) cluster. - * - * The cgcall push/drain handling of a CONCRETE arg widened into a - * tagged-union parameter slot, where a float is involved: - * - * #30 + #48 (align-UP, wwstage-only): the cgcall DRAIN loop had no - * widen-first pop branch. A concrete arg widened into a tagged param - * was pushed as slotsize/8 GP words (tag + payload), but the drain - * classified per the ARG's source type: - * #30 — a float arg AFTER a widened (i64|void) arg: the widened - * box under-drained by one GP word, so `MOVSD (SP),X0` for - * the following f64 read the box's leftover payload word. - * #48 — the widened arg IS an f64 source: source-type=f64 hit the - * float arm, so `MOVSD (SP),X0` ate the TAG word into X0 and - * the payload landed in DI as the tag → callee fell through. - * Both are one missing branch — a widen-first GP pop placed BEFORE - * the float check, draining slotsize/8 words into the integer arg - * cursor (DI=tag, SI=payload). cstage already does this (precomputed - * widen[i]); this aligns wwstage UP and the rows pin cs==ww byte-id. - * - * REGISTER-CURSOR INVARIANT (rob): a lucky-but-wrong float shuffle can pass - * exit codes, so the run rows use values that only survive if the tag/ - * payload land in the right GP regs AND the float in the right XMM: - * #30 returns 0 iff f's b (the f64 arg following the widen) == 1.5; - * #48 returns 2 iff the (i64|f64) box's tag selects the f64 arm (DI=tag). - * Both also assert cstage/wwstage .s byte-identical. - * - * NNN<950, self-contained (/tmp, no imports; #30 returns i32 rather than - * importing os, so the asm byte-id row can run w6c/w6c_ww directly). - */ -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -static int -slurp_eq(const char *a, const char *b) -{ - FILE *fa = fopen(a, "rb"); - FILE *fb = fopen(b, "rb"); - if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } - int rc = 0; - for (;;) { - int ca = fgetc(fa), cb = fgetc(fb); - if (ca != cb) { rc = -1; break; } - if (ca == EOF) break; - } - fclose(fa); fclose(fb); - return rc; -} - -#define K_RUN 0 -#define K_BUILDERR 1 - -struct row { const char *label; const char *src; int want; - int kind; const char *experr; }; - -static const struct row rows[] = { - /* #30: a widened (i64|void) arg FOLLOWED by an f64 arg. Pre-fix the - * f64 drain (MOVSD (SP),X0) read the widened box's leftover payload - * word (int 7) instead of 1.5, so r != 1.5 → 1. */ - { "widen_then_float", - "package main;\n" - "fn f(a: (i64 | void), b: f64) f64 = { return b; };\n" - "export fn main() i32 = {\n" - " let r: f64 = f(7, 1.5);\n" - " if (r == 1.5) { return 0; };\n" - " return 1;\n" - "};\n", 0, K_RUN, NULL }, - /* #48: the widened arg IS an f64 source (i64|f64). Pre-fix the float - * arm ate the tag word into X0 and the payload landed in DI as the - * tag, so the match fell through both arms → 9. Correct = 2 (f64). */ - { "float_source_widen", - "package main;\n" - "fn g(v: (i64 | f64)) i32 = {\n" - " match (v) {\n" - " case let n: i64 =>\n" - " return 1;\n" - " case let d: f64 =>\n" - " return 2;\n" - " };\n" - " return 9;\n" - "};\n" - "export fn main() i32 = {\n" - " let d: f64 = 3.5;\n" - " return g(d);\n" - "};\n", 2, K_RUN, NULL }, - /* #49 (#263 both-stages): a RUNTIME f64 producer (mk) feeds a value - * widened into (f64|void), and the f64 arm READS the payload - * (d == 2.5). Pre-fix the widen-PUSH did `PUSHQ AX` for the payload - * while the f64 sat in X0, so d read stale bits → 1 (cs) / 3 (ww, - * the #48 pop divergence compounding). #48's arm dodged this by not - * reading the payload; this one catches the stale-AX push. Both - * stages now spill X0 → 0. (After the c4 drain fix the two stages - * already agree on the pop; this push fix closes the shared bug.) */ - { "runtime_float_widen_payload", - "package main;\n" - "type fv = (f64 | void);\n" - "fn mk(x: f64) f64 = { return x + 1.5; };\n" - "fn take(v: fv) i32 = {\n" - " match (v) {\n" - " case let d: f64 => {\n" - " if (d == 2.5) { return 0; };\n" - " return 1;\n" - " };\n" - " case void => { return 2; };\n" - " };\n" - " return 3;\n" - "};\n" - "export fn main() i32 = {\n" - " let d: f64 = mk(1.0);\n" - " return take(d);\n" - "};\n", 0, K_RUN, NULL }, -}; - -static int -run_driver(const char *driver, const struct row *r, int i) -{ - char tmpdir[96], src[128], outbin[128], errf[128], rmcmd[160], cmd[1024]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/f9f_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/f9f_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/f9f_%d_%d", tmpdir, getpid(), i); - snprintf(errf, sizeof errf, "%s/err", tmpdir); - snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); - - FILE *f = fopen(src, "wb"); - if (!f) { runwait(rmcmd); return -1; } - fputs(r->src, f); - fclose(f); - - snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s", - driver, outbin, src, errf); - int brc = runwait(cmd); - if (brc != 0) { - fprintf(stderr, "row[%s]: build via %s failed\n", - r->label, driver); - runwait(rmcmd); - return -1; - } - - int got = runwait(outbin); - - runwait(rmcmd); - if (got != r->want) { - fprintf(stderr, "row[%s]: %s exit %d, want %d\n", - r->label, driver, got, r->want); - return 1; - } - return 0; -} - -static int -asm_byte_identical(const char *bin, const struct row *r, int i) -{ - char src[96], cs[96], ws[96], cmd[1024]; - snprintf(src, sizeof src, "/tmp/f9f_asm_%d_%d.ww", getpid(), i); - snprintf(cs, sizeof cs, "/tmp/f9f_asm_%d_%d_c.s", getpid(), i); - snprintf(ws, sizeof ws, "/tmp/f9f_asm_%d_%d_w.s", getpid(), i); - - FILE *f = fopen(src, "wb"); - if (!f) return -1; - fputs(r->src, f); - fclose(f); - - snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c errored\n", r->label); - unlink(src); - return -1; - } - snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null", - bin, ws, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label); - unlink(src); unlink(cs); - return -1; - } - int rc = slurp_eq(cs, ws); - if (rc != 0) - fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n", - r->label); - unlink(src); unlink(cs); unlink(ws); - return rc; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[2080]; - if (bin[0] != '/') { - char cwd[1024]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char cdrv[2120], wdrv[2120]; - snprintf(cdrv, sizeof cdrv, "%s/ww", bin); - snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); - - int n = (int)(sizeof rows / sizeof rows[0]); - int total = 0, fail = 0; - - for (int i = 0; i < n; i++) { - total++; - if (run_driver(cdrv, &rows[i], i) != 0) fail++; - } - if (access(wdrv, X_OK) == 0) { - for (int i = 0; i < n; i++) { - total++; - if (run_driver(wdrv, &rows[i], i) != 0) fail++; - } - for (int i = 0; i < n; i++) { - if (rows[i].kind == K_BUILDERR) - continue; - total++; - if (asm_byte_identical(bin, &rows[i], i) != 0) fail++; - } - } - - if (fail) { - fprintf(stderr, "f9_float: %d/%d checks failed\n", - fail, total); - return 1; - } - printf("f9_float: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/951_f64cgen_run.c b/test/wcc/951_f64cgen_run.c deleted file mode 100644 index 71fd2e53..00000000 --- a/test/wcc/951_f64cgen_run.c +++ /dev/null @@ -1,246 +0,0 @@ -/* - * 951_f64cgen_run — runtime regression net for the two GATE-BLIND f64 - * codegen bugs #96 and #97. Both stages (cstage cgen.c, wwstage - * cgenexpr.ww) emit byte-identical asm before and after the fix, so the - * 990-997 byte-id gates can NEVER catch a reintroduction — only an - * executed-and-checked runtime probe can. This file is that probe. - * - * Table-driven like 700_e2e: each row is a self-contained ww program; - * the C-side cstage `ww build` compiles it, we run the binary and assert - * the exit code. Rows that self-check return 0 on pass / a locator code - * on the first failing assertion; rows that assert value propagation - * return a computed result the harness compares to `want_exit`. - * - * cstage-only by design (mirrors 700_e2e + 969_checked_run): `ww_ww run` - * is broken (#95) and per-program wwstage byte-id is the 990-997 gates' - * job, not this file's. The fix's cs==ww symmetry is verified there. - * - * #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. - */ -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return 1; -} - -struct row { const char *src; int want_exit; }; - -static const struct row rows[] = { - /* #96 deref-load: dirty X0 with a non-x value just before the - * call so an accidental X0-retention can't mask a broken return - * load; f64 return through a bare deref must ride X0, and `*px` - * must reach X0 for the MULSD. (ken's f64gate p96_deref.) */ - { "package main;\n" - "fn deref(p: *f64) f64 = { return *p; };\n" - "export fn main() i32 = {\n" - " let x: f64 = 7.5;\n" - " let y: f64 = 1.25;\n" - " let px: *f64 = &x;\n" - " let junk: f64 = y * 2.0;\n" - " if (junk != 2.5) { return 3; };\n" - " let r: f64 = deref(px);\n" - " if (r != 7.5) { return 1; };\n" - " let q: f64 = *px * 2.0;\n" - " if (q != 15.0) { return 2; };\n" - " return 0;\n" - "};\n", 0 }, - /* #96 value propagation: f64 returned through an f64-returning fn - * then truncated to i32. On the bug the value strands in AX and - * the i32 cast reads stale X0 -> wrong exit. */ - { "package main;\n" - "fn deref(p: *f64) f64 = { return *p; };\n" - "export fn main() i32 = {\n" - " let x: f64 = 42.0;\n" - " let r: f64 = deref(&x);\n" - " return r: i32;\n" - "};\n", 42 }, - /* #96 arith-through-deref: `*p * 2.0` -> i32. */ - { "package main;\n" - "export fn main() i32 = {\n" - " let x: f64 = 7.5;\n" - " let p: *f64 = &x;\n" - " let q: f64 = *p * 2.0;\n" - " return q: i32;\n" - "};\n", 15 }, - /* #96 f64frombits round-trip: reinterpret a u64 bit pattern as - * f64 via `*((&bits): *f64)`. The deref node is f64-typed so it - * must MOVSD into X0; on the bug it MOVQs into AX and the i32 cast - * reads stale X0. 0x4045000000000000 == 42.0. */ - { "package main;\n" - "export fn main() i32 = {\n" - " let bits: u64 = 0x4045000000000000u64;\n" - " let f: f64 = *((&bits): *f64);\n" - " return f: i32;\n" - "};\n", 42 }, - /* #96 copysign-style sign transfer built from primitives: tobits/ - * frombits are f64<->u64 reinterpret derefs (both float and int - * deref-loads exercised). copysign(5.0, -1.0) == -5.0. */ - { "package main;\n" - "fn tobits(f: f64) u64 = { return *((&f): *u64); };\n" - "fn frombits(b: u64) f64 = { return *((&b): *f64); };\n" - "export fn main() i32 = {\n" - " let x: f64 = 5.0;\n" - " let y: f64 = -1.0;\n" - " let mag: u64 = tobits(x) & 0x7fffffffffffffffu64;\n" - " let sgn: u64 = tobits(y) & 0x8000000000000000u64;\n" - " let r: f64 = frombits(mag | sgn);\n" - " if (r > 0.0) { return 1; };\n" - " if (r < -4.5) { if (r > -5.5) { return 0; }; };\n" - " return 2;\n" - "};\n", 0 }, - /* #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. - * isnan via self-inequality; NaN against each of the 6 relops; - * isnan(1.0)==false; ordered non-NaN rows guard for regression. - * (ken's f64gate p97_nan.) */ - { "package main;\n" - "fn zero() f64 = { return 0.0; };\n" - "fn one() f64 = { return 1.0; };\n" - "export fn main() i32 = {\n" - " let z: f64 = zero();\n" - " let nan: f64 = z / z;\n" - " let x: f64 = one();\n" - " if (!(nan != nan)) { return 1; };\n" - " if (nan == nan) { return 2; };\n" - " if (nan == x) { return 3; };\n" - " if (nan < x) { return 4; };\n" - " if (nan <= x) { return 5; };\n" - " if (nan > x) { return 6; };\n" - " if (nan >= x) { return 7; };\n" - " if (!(nan != x)) { return 8; };\n" - " if (x != x) { return 9; };\n" - " if (!(x == x)) { return 10; };\n" - " if (!(x < 2.0)) { return 11; };\n" - " if (!(x <= 1.0)) { return 12; };\n" - " if (!(2.0 > x)) { return 13; };\n" - " if (!(1.0 >= x)) { return 14; };\n" - " if (x > 2.0) { return 15; };\n" - " return 0;\n" - "};\n", 0 }, - /* #97 value propagation: of the 6 relops against NaN, exactly one - * (`!=`) is true. Returns the true-count; the bug returns 3 (==, - * <, <= all wrongly fire on the unordered ZF/CF). */ - { "package main;\n" - "fn z() f64 = { return 0.0; };\n" - "export fn main() i32 = {\n" - " let nan: f64 = z() / z();\n" - " let x: f64 = 1.0;\n" - " let n: i32 = 0;\n" - " if (nan == x) { n += 1; };\n" - " if (nan != x) { n += 1; };\n" - " if (nan < x) { n += 1; };\n" - " if (nan <= x) { n += 1; };\n" - " if (nan > x) { n += 1; };\n" - " if (nan >= x) { n += 1; };\n" - " return n;\n" - "};\n", 1 }, - /* #97 f32 path (UCOMISS): NaN must follow the same unordered - * rules, and ordered f64 relops with no NaN must stay correct. - * (ken's f64gate p97_f32_ordered.) */ - { "package main;\n" - "fn z32() f32 = { return 0.0; };\n" - "fn one32() f32 = { return 1.0; };\n" - "export fn main() i32 = {\n" - " let z: f32 = z32();\n" - " let nan: f32 = z / z;\n" - " let x: f32 = one32();\n" - " if (!(nan != nan)) { return 1; };\n" - " if (nan == nan) { return 2; };\n" - " if (nan < x) { return 3; };\n" - " if (nan >= x) { return 4; };\n" - " let a: f64 = 2.0;\n" - " let b: f64 = 3.0;\n" - " if (!(a < b)) { return 5; };\n" - " if (a > b) { return 6; };\n" - " if (!(a <= a)) { return 7; };\n" - " if (!(b >= a)) { return 8; };\n" - " if (!(a == 2.0)){ return 9; };\n" - " if (a != 2.0) { return 10; };\n" - " if (b < a) { return 11; };\n" - " if (!(b > a)) { return 12; };\n" - " return 0;\n" - "};\n", 0 }, - /* #97 `>`/`>=` LEFT-BARE arm with runtime-built operands: the JA/ - * JAE template stays unchanged by the fix, so this guards that the - * untouched arm still computes ordered greater-than correctly. - * (ken's f64gate p97_gtonly.) */ - { "package main;\n" - "fn z() f64 = { return 0.0; };\n" - "export fn main() i32 = {\n" - " let a: f64 = z() + 2.0;\n" - " let b: f64 = z() + 3.0;\n" - " if (!(b > a)) { return 1; };\n" - " if (a > b) { return 2; };\n" - " if (!(b >= a)) { return 3; };\n" - " if (!(a >= a)) { return 4; };\n" - " return 0;\n" - "};\n", 0 }, - { NULL, 0 } -}; - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[1024]; - if (bin[0] != '/') { - char cwd[1024]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - int n = 0, fail = 0; - for (int i = 0; rows[i].src; i++, n++) { - char tmpdir[64]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/wwf64_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - - char src[128], outbin[128], rmcmd[160]; - snprintf(src, sizeof src, "%s/wwf64_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wwf64_%d_%d", tmpdir, getpid(), i); - snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); - - FILE *f = fopen(src, "wb"); - if (f == NULL) { runwait(rmcmd); fail++; continue; } - fputs(rows[i].src, f); - fclose(f); - - char cmd[1024]; - snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s", - bin, outbin, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row %d: build failed\n src: %s\n", - i, rows[i].src); - fail++; - runwait(rmcmd); - continue; - } - - int got = runwait(outbin); - if (got != rows[i].want_exit) { - fprintf(stderr, "row %d: exit %d, want %d\n src: %s\n", - i, got, rows[i].want_exit, rows[i].src); - fail++; - } - runwait(rmcmd); - } - if (fail) { - fprintf(stderr, "%d/%d f64cgen tests failed\n", fail, n); - return 1; - } - printf("f64cgen: %d/%d ok\n", n, n); - return 0; -} diff --git a/test/wcc/955_f64xmm_run.c b/test/wcc/955_f64xmm_run.c deleted file mode 100644 index e5c0358a..00000000 --- a/test/wcc/955_f64xmm_run.c +++ /dev/null @@ -1,219 +0,0 @@ -/* - * 955_f64xmm_run — runtime + byte-id regression net for #103: an f64 - * value failing to materialise in XMM (X0) before an SSE op. Two faces, - * same class, both GATE-BLIND (cstage cgen.c and wwstage cgenexpr.ww - * emitted byte-identical-but-wrong asm, so the 990-997 byte-id gates - * could never catch a reintroduction — only an executed-and-checked - * runtime probe can). - * - * FACE X — a no-decimal float-typed integer literal (`0f64`, `8f64`) - * is an N_INTLIT carrying float TYPE; the integer-immediate path - * stranded it in AX, so `n == 0f64` compared a stale X0 (true for - * all n) and `(8f64 * 10.0): i32` read garbage. Fixed by routing - * the float-typed N_INTLIT through the float-constant-in-X0 emit - * (cgen.c cgexpr_float / cgenexpr.ww cgfloatbits), plus the wwstage - * exprfloatkind N_INTLIT arm so the downstream f64->i32 cast emits - * CVTTSD2SI not MOVSXD (the #101 structural-vs-stamped asymmetry). - * FACE Z — a tuple positional f64 field read (`r.0`, r:(f64,i64)) - * loaded via the integer op into AX, so `r.0 == 0.0` was wrongly - * true. Fixed by a fld_isfloat branch -> MOVSD/MOVSS into X0 - * (cgen.c:5910 / cgenexpr.ww tuple arm), mirroring the struct-field - * float load at cgen.c:1462,1838 (the #96 pattern). - * - * Each row carries BOTH dimensions (like 953_f64crossmod_run): - * (a) cstage `ww build` + run, asserting the exit code. - * (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge. Both - * stages are fixed identically, so this stays byte-identical - * before and after; it catches one stage being fixed without the - * other. - */ -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -struct row { const char *label; const char *src; int want_exit; }; - -static const struct row rows[] = { - /* FACE X false-case: g(8.0) must be 0. On the bug `n == 0f64` - * compares against a stale X0 (true for all n) -> 1. */ - { "x_cmp_false", - "package main;\n" - "fn g(n: f64) i32 = { if (n == 0f64) { return 1; }; return 0; };\n" - "export fn main() i32 = { return g(8.0); };\n", 0 }, - /* FACE X true-case: g(0.0) must still be 1 — the genuine equal - * case must survive the fix. */ - { "x_cmp_true", - "package main;\n" - "fn g(n: f64) i32 = { if (n == 0f64) { return 1; }; return 0; };\n" - "export fn main() i32 = { return g(0.0); };\n", 1 }, - /* FACE X arith: (8f64 * 10.0): i32 == 80. On the bug 8f64 never - * reaches X0, the MULSD reads stale X0 -> garbage. */ - { "x_arith", - "package main;\n" - "export fn main() i32 = { return (8f64 * 10.0): i32; };\n", 80 }, - /* FACE X negative-lit: `-8f64` is N_UN(TK_MINUS) wrapping a float- - * typed N_INTLIT, so the wwstage exprfloatkind must recurse through - * the unary into the new N_INTLIT-float arm. g(-8.0) == -8f64 must be - * true -> 1; on the bug the literal never reaches X0, the compare is - * always false, and g(-8.0) wrongly returns 0. */ - { "x_neg_cmp_true", - "package main;\n" - "fn g(n: f64) i32 = { if (n == -8f64) { return 1; }; return 0; };\n" - "export fn main() i32 = { return g(-8.0); };\n", 1 }, - /* FACE X negative-lit control: g(8.0) == -8f64 must stay false -> 0. - * Guards the negative-literal path against over-reach. */ - { "x_neg_cmp_false", - "package main;\n" - "fn g(n: f64) i32 = { if (n == -8f64) { return 1; }; return 0; };\n" - "export fn main() i32 = { return g(8.0); };\n", 0 }, - /* FACE Z: tuple positional f64 field compare. r.0 == 0.0 with - * r = (8.0, 0) must be false -> 9. On the bug r.0 loads into AX - * (integer op), `== 0.0` reads stale X0 -> wrongly true -> 5. */ - { "z_tuple_field", - "package main;\n" - "fn norm(n: f64) (f64, i64) = { return (n, 0); };\n" - "export fn main() i32 = {\n" - " const r = norm(8.0);\n" - " if (r.0 == 0.0) { return 5; };\n" - " return 9;\n" - "};\n", 9 }, - /* CONTROL: the let-bound spelling (`const m = r.0; m == 0.0`) was - * already correct (the let-init is float-aware) and must STAY - * correct -> 9. Guards against the FACE-Z fix over- or - * under-reaching. */ - { "z_letbound_control", - "package main;\n" - "fn norm(n: f64) (f64, i64) = { return (n, 0); };\n" - "export fn main() i32 = {\n" - " const r = norm(8.0);\n" - " const m = r.0;\n" - " if (m == 0.0) { return 5; };\n" - " return 9;\n" - "};\n", 9 }, - { NULL, NULL, 0 } -}; - -static int -slurp_eq(const char *a, const char *b) -{ - FILE *fa = fopen(a, "rb"); - FILE *fb = fopen(b, "rb"); - if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } - int rc = 0; - for (;;) { - int ca = fgetc(fa); - int cb = fgetc(fb); - if (ca != cb) { rc = -1; break; } - if (ca == EOF) break; - } - fclose(fa); fclose(fb); - return rc; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[1024]; - if (bin[0] != '/') { - char cwd[1024]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char w6c[1100], w6c_ww[1100]; - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - if (access(w6c_ww, X_OK) != 0) { - fprintf(stderr, "f64xmm: w6c_ww missing — cannot run the " - "cs==ww byte-id gate (the whole point of this test)\n"); - return 1; - } - - int n = 0, fail = 0; - for (int i = 0; rows[i].src; i++, n++) { - char tmpdir[64]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/wwf64m_%d_d_%d", - getpid(), i); - mkdir(tmpdir, 0755); - char rmcmd[160]; - snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); - - char src[128], outbin[128], cs_s[128], ws_s[128]; - snprintf(src, sizeof src, "%s/wwf64m_%d_%d.ww", - tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wwf64m_%d_%d", - tmpdir, getpid(), i); - snprintf(cs_s, sizeof cs_s, "%s/wwf64m_%d_%d_cs.s", - tmpdir, getpid(), i); - snprintf(ws_s, sizeof ws_s, "%s/wwf64m_%d_%d_ww.s", - tmpdir, getpid(), i); - - FILE *f = fopen(src, "wb"); - if (f == NULL) { fail++; runwait(rmcmd); continue; } - fputs(rows[i].src, f); - fclose(f); - - /* (a) cstage build + run. */ - char cmd[2048]; - snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s", - bin, outbin, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: cstage build failed\n", - rows[i].label); - fail++; - runwait(rmcmd); - continue; - } - - int got = runwait(outbin); - if (got != rows[i].want_exit) { - fprintf(stderr, "row[%s]: cstage exit %d, want %d\n", - rows[i].label, got, rows[i].want_exit); - fail++; - } - - /* (b) cs==ww byte-id gate: emit .s from both stages, cmp. */ - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c, cs_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label); - fail++; runwait(rmcmd); continue; - } - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c_ww, ws_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c_ww failed\n", - rows[i].label); - fail++; runwait(rmcmd); continue; - } - if (slurp_eq(cs_s, ws_s) != 0) { - fprintf(stderr, - "row[%s]: cstage/wwstage .s DIFFER (rule-10 " - "byte-id violation)\n", rows[i].label); - fail++; - } - runwait(rmcmd); - } - - if (fail) { - fprintf(stderr, "%d/%d f64 xmm-materialise tests failed\n", - fail, n); - return 1; - } - printf("f64xmm: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n); - return 0; -} diff --git a/test/wcc/956_tuprecv_f64_run.c b/test/wcc/956_tuprecv_f64_run.c index 549e64d6..8b9dc4c3 100644 --- a/test/wcc/956_tuprecv_f64_run.c +++ b/test/wcc/956_tuprecv_f64_run.c @@ -1,63 +1,22 @@ /* - * 956_tuprecv_f64_run — runtime + byte-id regression net for #105 and - * the #164 (#107) multi-float extension. + * 956_tuprecv_f64_run (SLIM PIN, #5-C4) — the #121 A-narrow asserttyped stamp + * dimension for the f64 tuple-receive DESTRUCTURE bindings. The runtime VALUE + * rows + cs==ww byte-id migrated to test/lang/tuprecv_f64_test.ww; what stays + * here is the one dimension test-lang has no channel for: a wwstage-STDERR grep + * that w6c_ww emits NO `asserttyped:` diagnostic on the un-annotated float + * destructure binding (the binding must carry a checker type stamp so cgen's + * class-aware spill fires). cstage has no ww asserttyped pass, so this is + * w6c_ww-only by construction (project memory: audit the *_ww binary). * - * #164 (#107): a multi-float tuple return (e.g. (f64,f64)) mis-routes — - * the SEND emitted every float through X0 (cgexpr clobbers X0 per - * element), so two floats collided on X0 and the receive read both from - * X0. The fix gives the tuple return a SysV SSE cursor [X0,X1] parallel - * to the integer cursor [AX,DX,CX,R8]: a float rides the next XMM on an - * INDEPENDENT counter (ref/qbe/amd64/sysv.c retr). The SEND spills each - * float to @tupfscr as it walks (X0 is clobbered by later elements) and - * reloads X0/X1 by SSE index after the integer pops; every receive site - * (single-var 16B/32B, destructure, reassign) reads the float from its - * SSE-cursor reg. SSE caps at 2 (X0,X1) — (f64,f64,f64) is over-cap, so - * it returns via sret (#10 Fold A SEND + Fold B destructure RECEIVE); the - * f64x3_recv row asserts that round-trip works in both stages. + * NON-VACUITY (drew C4 mutation gate): the grep is LIVE — pre-stamp HEAD w6c_ww + * fires `asserttyped` on the f64 binding ident (nil n.type_), and the grep + * mechanically fires on a synthetic `asserttyped:` line (self-checked below at + * startup; a broken grep FAILS the test before any row runs). * - * #105 (original): a tuple-from-call receive corrupts the f64 word when - * the callee is BRANCHED. Covers ALL THREE receive forms, which share the - * #83 tuple_rseq cursor and all carried the same defect: - * 1. SINGLE-VAR `let r = norm(); ...r.0` (cglet 16B-tuple branch) - * 2. DESTRUCTURE `let (m,i) = norm()` (N_MLET / cgmlet+tupstore) - * 3. REASSIGN `m,i = norm()` (N_MASSIGN / cgmassign+tupstore) - * - * Root (#105, a #103-FACE-Z regression): a (f64,i64)/(i64,f64) tuple - * returns its f64 word in X0 (the SSE return reg) and its integer word - * in an integer reg (tuple_rseq AX/DX). All three receives spilled the - * f64 word via MOVQ from the integer cursor — but that reg holds GARBAGE - * (the f64 is in X0). #103-FACE-Z's field read (MOVSD slot,X0) then read - * that garbage. The fix makes every receive spill CLASS-AWARE: an f64/f32 - * word spills `MOVSD/MOVSS X0, slot`, an integer word spills `MOVQ - * , slot` (as before). cstage cgen.c (3 sites) + wwstage cgenstmt.ww - * (cglet branch + the shared tupstore helper, which covers cgmlet and - * cgmassign), identically. - * - * WHY BRANCHED CALLEES: a SINGLE-return callee masks the bug via - * register coincidence — a float-literal return leaves the f64 bit - * pattern in AX (literal materialise goes through AX), so MOVQ AX,slot - * happens to store the right bits; and X0 stays live to the receive. A - * BRANCHED / multi-statement callee whose f64 word is a non-literal - * (e.g. an f64 param) has an inner CALL clobber AX, so the integer-reg - * spill stores garbage. The bug rows below therefore all use branched - * callees with an f64-param word — single-return rows are gate-blind to - * #105 (this is the gate lesson the task pins). - * - * GATE-BLIND TO BYTE-ID ALONE: both stages are symmetric-WRONG on master - * (both emit MOVQ AX,slot), so the cs==ww .s gate HOLDS on master for - * the bug rows — they diverge only at RUNTIME. Each row carries BOTH - * dimensions (modelled on 954_tuprecv_run): - * (a) cstage `ww build` + run, asserting the exit code — this is what - * catches #105 (master returns the wrong exit). - * (b) w6c vs w6c_ww `.s` cmp — guards rule-10 (both stages fixed - * identically). - * - * CONTROL rows pin that the fix touches nothing else: an all-integer - * branched 2-tuple (correct pre- and post-fix; MOVQ path untouched), the - * destructure form `let (a,b)=mk()` (cgmlet, a separate path the fix - * does not touch), a single-return (f64,i64) field read (#103 FACE-Z - * shape, correct pre- and post-fix), and a bare 0f64 compare (#103 - * FACE-X shape, no tuple involved). + * Slim sibling of the C2 precedent (954 ctl_destr's asserttyped sub-dimension, + * test/lang/tuprecv_test.ww). The 6 rows are the chk_stamped subset of the + * original 956 driver; non-float / control rows carry no stamp dimension and + * live only in the @test file. */ #include #include @@ -74,73 +33,9 @@ runwait(const char *cmd) return -1; } -struct row { - const char *label; - const char *src; - int want_exit; - int chk_stamped; - int want_compile_fail; /* #164: loud-stop rows must NOT compile */ -}; +struct row { const char *label; const char *src; }; static const struct row rows[] = { - /* BUG — minimal repro. norm is BRANCHED (inner issub() CALL clobbers - * AX) and its f64 word is the param n, not a literal. Pre-fix the - * receive spills MOVQ AX,slot (garbage); r.0 != 16.0 -> return 1. - * Post-fix MOVSD X0,slot -> 16.0 -> return 0. */ - { "f64_i64_br", - "package main;\n" - "fn issub(n: f64) bool = { return false; };\n" - "fn norm(n: f64) (f64, i64) = {\n" - "\tif (issub(n)) { return (n*2.0, -52); };\n" - "\treturn (n, 0);\n" - "};\n" - "export fn main() i32 = {\n" - "\tconst r = norm(16.0);\n" - "\tconst m: f64 = r.0;\n" - "\tif (m != 16.0) { return 1; };\n" - "\treturn 0;\n" - "};\n", 0 }, - /* BUG — deferred read. An intervening f64 CALL clobbers X0 AFTER the - * receive; the read of r.0 must come from the SPILLED slot, not a - * stale X0. Strongest catch: proves the spill happened at receive - * time and survives X0 clobber. */ - { "f64_i64_deferred", - "package main;\n" - "fn issub(n: f64) bool = { return false; };\n" - "fn norm(n: f64) (f64, i64) = {\n" - "\tif (issub(n)) { return (n*2.0, -52); };\n" - "\treturn (n, 0);\n" - "};\n" - "fn clob(x: f64) f64 = { return x + 1.0; };\n" - "export fn main() i32 = {\n" - "\tconst r = norm(16.0);\n" - "\tconst junk: f64 = clob(3.0);\n" - "\tconst m: f64 = r.0;\n" - "\tif (m != 16.0) { return 1; };\n" - "\tif (junk != 4.0) { return 2; };\n" - "\treturn 0;\n" - "};\n", 0 }, - /* BUG — order-swap (i64, f64): f64 is word1, spilled from DX pre-fix - * (garbage; the f64 is in X0). Confirms the fix is position-aware: - * word1's f64 -> MOVSD X0, slot+8; word0's i64 -> MOVQ AX, slot+0. */ - { "i64_f64_br", - "package main;\n" - "fn issub(n: f64) bool = { return false; };\n" - "fn norm(n: f64) (i64, f64) = {\n" - "\tif (issub(n)) { return (-52, n*2.0); };\n" - "\treturn (0, n);\n" - "};\n" - "export fn main() i32 = {\n" - "\tconst r = norm(16.0);\n" - "\tconst i: i64 = r.0;\n" - "\tconst m: f64 = r.1;\n" - "\tif (m != 16.0) { return 1; };\n" - "\tif (i != 0) { return 2; };\n" - "\treturn 0;\n" - "};\n", 0 }, - /* BUG — DESTRUCTURE form `let (m,i)=norm()` (N_MLET / cgmlet+tupstore). - * f64 binding m is element 0 (cursor AX); pre-fix MOVQ AX,slot stores - * garbage (issub clobbered AX). Post-fix MOVSD X0,slot. m=16.0, i=0. */ { "destr_f64_i64_br", "package main;\n" "fn issub(n: f64) bool = { return false; };\n" @@ -153,10 +48,7 @@ static const struct row rows[] = { "\tif (m != 16.0) { return 1; };\n" "\tif (i != 0) { return 2; };\n" "\treturn 0;\n" - "};\n", 0, 1 }, - /* BUG — DESTRUCTURE order-swap `let (i,m)=norm()`, (i64,f64). f64 - * binding m is element 1 (cursor DX); pre-fix MOVQ DX,slot garbage, - * post-fix MOVSD X0,slot. i=0, m=16.0. */ + "};\n" }, { "destr_i64_f64_br", "package main;\n" "fn issub(n: f64) bool = { return false; };\n" @@ -169,47 +61,7 @@ static const struct row rows[] = { "\tif (m != 16.0) { return 1; };\n" "\tif (i != 0) { return 2; };\n" "\treturn 0;\n" - "};\n", 0, 1 }, - /* BUG — REASSIGN form `m,i = norm()` (N_MASSIGN / cgmassign+tupstore) - * into pre-declared slots. Same f64-element-from-X0 defect. m=16.0. */ - { "massign_f64_i64_br", - "package main;\n" - "fn issub(n: f64) bool = { return false; };\n" - "fn norm(n: f64) (f64, i64) = {\n" - "\tif (issub(n)) { return (n*2.0, -52); };\n" - "\treturn (n, 0);\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet m: f64 = 0.0;\n" - "\tlet i: i64 = 0;\n" - "\tm, i = norm(16.0);\n" - "\tif (m != 16.0) { return 1; };\n" - "\tif (i != 0) { return 2; };\n" - "\treturn 0;\n" - "};\n", 0 }, - /* BUG — REASSIGN order-swap `i,m = norm()`, (i64,f64). f64 reassign - * target is element 1 (cursor DX). i=0, m=16.0. */ - { "massign_i64_f64_br", - "package main;\n" - "fn issub(n: f64) bool = { return false; };\n" - "fn norm(n: f64) (i64, f64) = {\n" - "\tif (issub(n)) { return (-52, n*2.0); };\n" - "\treturn (0, n);\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet i: i64 = 0;\n" - "\tlet m: f64 = 0.0;\n" - "\ti, m = norm(16.0);\n" - "\tif (m != 16.0) { return 1; };\n" - "\tif (i != 0) { return 2; };\n" - "\treturn 0;\n" - "};\n", 0 }, - /* #164 (#107) HEADLINE — multi-float (f64, f64), destructure. The - * callee is BRANCHED (issub CALL clobbers X0), so on master BOTH - * elements collide on X0: cgexpr(a) leaves a in X0, cgexpr(b) - * overwrites it, and every receive read spills from X0 -> x==y==b - * (5.0). Post-fix a rides the SSE cursor X0, b rides X1; the receive - * splits them. x=3.0, y=5.0. Pre-fix: x==5.0 -> return 1. */ + "};\n" }, { "f64f64_destr_br", "package main;\n" "fn issub(n: f64) bool = { return false; };\n" @@ -222,46 +74,7 @@ static const struct row rows[] = { "\tif (x != 3.0) { return 1; };\n" "\tif (y != 5.0) { return 2; };\n" "\treturn 0;\n" - "};\n", 0, 1 }, - /* #164 HEADLINE — multi-float (f64, f64), SINGLE-VAR whole-tuple - * receive (`let r = pair(); r.0 / r.1`, the 16B rt16 branch). Same - * X0-collision on master; post-fix r.0 from X0, r.1 from X1. */ - { "f64f64_single_br", - "package main;\n" - "fn issub(n: f64) bool = { return false; };\n" - "fn pair(a: f64, b: f64) (f64, f64) = {\n" - "\tif (issub(a)) { return (a*2.0, b*2.0); };\n" - "\treturn (a, b);\n" - "};\n" - "export fn main() i32 = {\n" - "\tconst r = pair(3.0, 5.0);\n" - "\tconst x: f64 = r.0;\n" - "\tconst y: f64 = r.1;\n" - "\tif (x != 3.0) { return 1; };\n" - "\tif (y != 5.0) { return 2; };\n" - "\treturn 0;\n" - "};\n", 0 }, - /* #164 HEADLINE — multi-float (f64, f64), REASSIGN into pre-declared - * slots (N_MASSIGN / cgmassign+tupstore). x=3.0, y=5.0. */ - { "f64f64_massign_br", - "package main;\n" - "fn issub(n: f64) bool = { return false; };\n" - "fn pair(a: f64, b: f64) (f64, f64) = {\n" - "\tif (issub(a)) { return (a*2.0, b*2.0); };\n" - "\treturn (a, b);\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet x: f64 = 0.0;\n" - "\tlet y: f64 = 0.0;\n" - "\tx, y = pair(3.0, 5.0);\n" - "\tif (x != 3.0) { return 1; };\n" - "\tif (y != 5.0) { return 2; };\n" - "\treturn 0;\n" - "};\n", 0 }, - /* #164 — INTERLEAVED (i64, f64, i64): kills naive position->reg. The - * two i64s ride the INTEGER cursor (AX,DX), the f64 the SSE cursor - * (X0) on an independent counter — so x=AX, z=DX, y=X0. 3-element - * destructure. x=3, y=2.0, z=7. */ + "};\n" }, { "i64_f64_i64_destr", "package main;\n" "fn issub(n: i64) bool = { return false; };\n" @@ -275,11 +88,7 @@ static const struct row rows[] = { "\tif (y != 2.0) { return 2; };\n" "\tif (z != 7) { return 3; };\n" "\treturn 0;\n" - "};\n", 0, 1 }, - /* #164 — (f64, str): SSE + wide (24B {ptr,len,cap} header) coexist. - * The f64 rides the SSE cursor (X0); the str rides the INTEGER - * cursor (AX,DX,CX) since the float consumes no GP slot. Destructure - * form. f=4.0, s.len=5 ("hello"). */ + "};\n" }, { "f64_str_destr", "package main;\n" "fn issub(n: f64) bool = { return false; };\n" @@ -292,14 +101,7 @@ static const struct row rows[] = { "\tif (f != 4.0) { return 1; };\n" "\tif (s.len != 5) { return 2; };\n" "\treturn 0;\n" - "};\n", 0, 1 }, - /* #164 STR-FIRST destructure (str, f64): the unification's new- - * coverage shape with the wide header in slot 0. The str rides the - * INTEGER cursor (AX,DX,CX = ptr,len,cap), the f64 the SSE cursor (X0) - * — independent counters, the float consuming no GP slot. The - * destructure path's cursor handled str-first on master too, so this - * pins the dual-cursor restructure PRESERVED it (byte-id both ways) - * AND that the f64 coexists. s.len=5 ("hello"), f=4.0. */ + "};\n" }, { "str_f64_destr", "package main;\n" "fn issub(n: f64) bool = { return false; };\n" @@ -312,140 +114,12 @@ static const struct row rows[] = { "\tif (s.len != 5) { return 1; };\n" "\tif (f != 4.0) { return 2; };\n" "\treturn 0;\n" - "};\n", 0, 1 }, - /* #164 STR-FIRST destructure (str, i64): pure-integer str-first. - * str@AX,DX,CX then i64@R8. Correct on master too (single-cursor - * destructure already routed ptr=AX) — byte-id regression guard that - * the dual cursor left the integer str-first mapping intact. s.len=5, - * k=7. */ - { "str_i64_destr", - "package main;\n" - "fn issub(n: i64) bool = { return false; };\n" - "fn si(n: i64) (str, i64) = {\n" - "\tif (issub(n)) { return (\"x\", n*2); };\n" - "\treturn (\"hello\", n);\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet (s, k) = si(7);\n" - "\tif (s.len != 5) { return 1; };\n" - "\tif (k != 7) { return 2; };\n" - "\treturn 0;\n" - "};\n", 0 }, - /* #164 STR-FIRST SINGLE-VAR (str, i64), annotated `let t: (str,i64) = - * si(); t.0/t.1`: the shape the OLD 32B single-var branch got WRONG — - * it read .ptr from DX while the send placed .ptr in AX (self- - * inconsistent), scrambling the slot so t.1 read the str.ptr word. - * DISCRIMINATES: pre-fix t.1 = a large address != 7; post-fix the - * unified dual cursor lands str@AX,DX,CX + i64@R8 so t.1=7. Both - * stages were wrong IDENTICALLY pre-fix (byte-id held, runtime broke), - * right identically post-fix. Annotated (not bare `const r=`) because - * the wwstage 32B single-var branch keys on the N_TTUPLE type node — - * a bare 32B single-var is a pre-existing stage asymmetry out of #164 - * scope (16B bare single-var rows above cover the inferred path). */ - { "str_i64_single", - "package main;\n" - "fn issub(n: i64) bool = { return false; };\n" - "fn si(n: i64) (str, i64) = {\n" - "\tif (issub(n)) { return (\"x\", n*2); };\n" - "\treturn (\"hello\", n);\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet t: (str, i64) = si(7);\n" - "\tif (t.1 != 7) { return 1; };\n" - "\tif (t.0.len != 5) { return 2; };\n" - "\treturn 0;\n" - "};\n", 0 }, - /* #10 OVER-CAP RECV — three f64 = 0 GP / 3 SSE exceeds the SSE return - * cap (X0,X1 only). #164 loud-stopped this at the SEND; #10 Fold A - * sret's it (callee stores X0/X1 → @sretarg at foff 0/8/16) and Fold B - * destructures it (each element MOVSD'd out of @sretscr into its - * binding), so it is now a working round-trip in BOTH stages. */ - { "f64x3_recv", - "package main;\n" - "fn tri(a: f64, b: f64, c: f64) (f64, f64, f64) = {\n" - "\treturn (a, b, c);\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet (x, y, z) = tri(1.0, 2.0, 3.0);\n" - "\tif (x != 1.0) { return 1; };\n" - "\tif (y != 2.0) { return 2; };\n" - "\tif (z != 3.0) { return 3; };\n" - "\treturn 0;\n" - "};\n", 0, 0, 0 }, - /* CONTROL — all-integer branched 2-tuple. The integer-cursor MOVQ - * path is untouched by the fix (e0/e1 not float), so this is correct - * pre- and post-fix and byte-id both ways. a=5, b=7 -> 12. */ - { "ctl_int_br", - "package main;\n" - "fn issub(n: i64) bool = { return false; };\n" - "fn mk(n: i64) (i64, i64) = {\n" - "\tif (issub(n)) { return (n*2, -1); };\n" - "\treturn (n, 7);\n" - "};\n" - "export fn main() i32 = {\n" - "\tconst r = mk(5);\n" - "\tconst a: i64 = r.0;\n" - "\tconst b: i64 = r.1;\n" - "\treturn (a: i32) + (b: i32);\n" - "};\n", 12 }, - /* CONTROL — destructure-with-NO-f64 `let (a,b)=mk()`. The fix now - * touches cgmlet/tupstore, but an all-integer element takes the - * unchanged MOVQ path, so this stays byte-id and correct pre- and - * post-fix — guards that the class check doesn't perturb integers. - * a=5, b=7 -> 12. */ - { "ctl_destr", - "package main;\n" - "fn issub(n: i64) bool = { return false; };\n" - "fn mk(n: i64) (i64, i64) = {\n" - "\tif (issub(n)) { return (n*2, -1); };\n" - "\treturn (n, 7);\n" - "};\n" - "export fn main() i32 = {\n" - "\tlet (a, b) = mk(5);\n" - "\treturn (a: i32) + (b: i32);\n" - "};\n", 12 }, - /* CONTROL — #103 FACE-Z single-return (f64,i64) field read. The f64 - * word is a literal, so AX coincidentally holds its bits on master; - * correct pre- and post-fix (the fix changes MOVQ AX,slot -> MOVSD - * X0,slot but the run result is unchanged). f=2.5->2, i=7 -> 9. */ - { "ctl_facez_single", - "package main;\n" - "fn mk() (f64, i64) = { return (2.5, 7); };\n" - "export fn main() i32 = {\n" - "\tconst t = mk();\n" - "\tconst f: f64 = t.0;\n" - "\tconst i: i64 = t.1;\n" - "\treturn (f: i32) + (i: i32);\n" - "};\n", 9 }, - /* CONTROL — #103 FACE-X bare 0f64 compare (no tuple). The fix does - * not touch the compare path; pure no-op guard. -> 0. */ - { "ctl_facex_0f64", - "package main;\n" - "export fn main() i32 = {\n" - "\tconst z: f64 = 0.0;\n" - "\tif (z != 0.0) { return 1; };\n" - "\treturn 0;\n" - "};\n", 0 }, - { NULL, NULL, 0 } + "};\n" }, + { NULL, NULL } }; -static int -slurp_eq(const char *a, const char *b) -{ - FILE *fa = fopen(a, "rb"); - FILE *fb = fopen(b, "rb"); - if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } - int rc = 0; - for (;;) { - int ca = fgetc(fa); - int cb = fgetc(fb); - if (ca != cb) { rc = -1; break; } - if (ca == EOF) break; - } - fclose(fa); fclose(fb); - return rc; -} - +/* The asserttyped diagnostic only appears in w6c_ww (the wwstage checker audit); + * an absent w6c_ww means the gate cannot run — fail loud. */ int main(void) { @@ -459,141 +133,68 @@ main(void) bin = absbin; } - char w6c[1100], w6c_ww[1100]; - snprintf(w6c, sizeof w6c, "%s/w6c", bin); + char w6c_ww[1100]; snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); if (access(w6c_ww, X_OK) != 0) { fprintf(stderr, "tuprecv_f64: w6c_ww missing — cannot run the " - "cs==ww byte-id gate\n"); + "asserttyped stamp gate (the whole point of this pin)\n"); return 1; } int n = 0, fail = 0; + + /* NON-VACUITY self-check: the grep must FIRE on a literal asserttyped + * line. A grep that never matches would pass every row vacuously. */ + { + char probe[80], cmd[160]; + snprintf(probe, sizeof probe, "/tmp/wwtupf_probe_%d.txt", getpid()); + FILE *p = fopen(probe, "wb"); + if (!p) { fprintf(stderr, "tuprecv_f64: probe open failed\n"); return 1; } + fputs("error: asserttyped: e.type_ nil\n", p); + fclose(p); + snprintf(cmd, sizeof cmd, "grep -q asserttyped %s", probe); + if (runwait(cmd) != 0) { + fprintf(stderr, "tuprecv_f64: grep self-check FAILED — the " + "asserttyped gate is vacuous\n"); + unlink(probe); + return 1; + } + unlink(probe); + } + for (int i = 0; rows[i].src; i++, n++) { - char tmpdir[64], src[128], outbin[128], rmcmd[160]; + char tmpdir[64], src[128], errf[128], rmcmd[160], cmd[2048]; snprintf(tmpdir, sizeof tmpdir, "/tmp/wwtupf_%d_d_%d", getpid(), i); mkdir(tmpdir, 0755); snprintf(src, sizeof src, "%s/wwtupf_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wwtupf_%d_%d", - tmpdir, getpid(), i); + snprintf(errf, sizeof errf, "%s/err", tmpdir); snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); + FILE *f = fopen(src, "wb"); if (f == NULL) { runwait(rmcmd); fail++; continue; } fputs(rows[i].src, f); fclose(f); - char cmd[2048]; - - /* #164 LOUD-STOP rows: the SSE-cap overflow must FAIL TO COMPILE - * in BOTH stages (rule-7). Assert (a) cstage `ww build` errors, - * and (b) w6c AND w6c_ww each return non-zero — proving the - * loud-stop fires symmetrically. No .s is produced, so the - * byte-id cmp is skipped. Discriminates against master, which - * has no SSE cap and builds the (mis)compile. */ - if (rows[i].want_compile_fail) { - snprintf(cmd, sizeof cmd, - "%s/ww build -o %s %s >/dev/null 2>&1", - bin, outbin, src); - if (runwait(cmd) == 0) { - fprintf(stderr, "row[%s]: cstage build SUCCEEDED, " - "want loud-stop (SSE cap)\n", rows[i].label); - fail++; - } - snprintf(cmd, sizeof cmd, "%s -o /dev/null %s 2>/dev/null", - w6c, src); - if (runwait(cmd) == 0) { - fprintf(stderr, "row[%s]: w6c emitted .s, want " - "loud-stop\n", rows[i].label); - fail++; - } - snprintf(cmd, sizeof cmd, "%s -o /dev/null %s 2>/dev/null", - w6c_ww, src); - if (runwait(cmd) == 0) { - fprintf(stderr, "row[%s]: w6c_ww emitted .s, want " - "loud-stop\n", rows[i].label); - fail++; - } - runwait(rmcmd); - continue; - } - - /* (a) cstage build + run in a scratch dir. */ - snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s", - bin, outbin, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: cstage build failed\n", + snprintf(cmd, sizeof cmd, "%s -o /dev/null %s 2>%s", + w6c_ww, src, errf); + runwait(cmd); + snprintf(cmd, sizeof cmd, "grep -q asserttyped %s", errf); + if (runwait(cmd) == 0) { + fprintf(stderr, "row[%s]: w6c_ww emitted asserttyped " + "(destructure float binding unstamped)\n", rows[i].label); fail++; - runwait(rmcmd); - continue; } - - int got = runwait(outbin); - if (got != rows[i].want_exit) { - fprintf(stderr, "row[%s]: cstage exit %d, want %d\n", - rows[i].label, got, rows[i].want_exit); - fail++; - } - - /* (b) cs==ww byte-id gate: emit .s from both stages, cmp. */ - char cs_s[64], ws_s[64]; - snprintf(cs_s, sizeof cs_s, "/tmp/wwtupf_%d_%d_cs.s", - getpid(), i); - snprintf(ws_s, sizeof ws_s, "/tmp/wwtupf_%d_%d_ww.s", - getpid(), i); - - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c, cs_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label); - fail++; runwait(rmcmd); continue; - } - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c_ww, ws_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c_ww failed\n", - rows[i].label); - fail++; runwait(rmcmd); unlink(cs_s); continue; - } - if (slurp_eq(cs_s, ws_s) != 0) { - fprintf(stderr, - "row[%s]: cstage/wwstage .s DIFFER (rule-10 " - "byte-id violation)\n", rows[i].label); - fail++; - } - - /* (c) #121 A-narrow stamp gate: the un-annotated float- - * destructure binding must now carry a checker type stamp, so - * w6c_ww emits no `asserttyped:` diagnostic. Non-vacuous — - * pre-stamp (HEAD) w6c_ww fires asserttyped on the f64 binding - * ident (nil n.type_). */ - if (rows[i].chk_stamped) { - char errf[80]; - snprintf(errf, sizeof errf, - "/tmp/wwtupf_%d_%d_err.txt", getpid(), i); - snprintf(cmd, sizeof cmd, - "%s -o /dev/null %s 2>%s", w6c_ww, src, errf); - runwait(cmd); - snprintf(cmd, sizeof cmd, - "grep -q asserttyped %s", errf); - if (runwait(cmd) == 0) { - fprintf(stderr, "row[%s]: w6c_ww emitted " - "asserttyped (destructure binding " - "unstamped)\n", rows[i].label); - fail++; - } - unlink(errf); - } - runwait(rmcmd); unlink(cs_s); unlink(ws_s); + runwait(rmcmd); } if (fail) { - fprintf(stderr, "%d/%d tuple-receive-f64 tests failed\n", + fprintf(stderr, "%d/%d tuprecv_f64 asserttyped-stamp rows failed\n", fail, n); return 1; } - printf("tuprecv_f64: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n); + printf("tuprecv_f64: %d/%d ok (asserttyped stamp pin, w6c_ww)\n", n, n); return 0; } diff --git a/test/wcc/964_f32lit_run.c b/test/wcc/964_f32lit_run.c deleted file mode 100644 index a8db3bc4..00000000 --- a/test/wcc/964_f32lit_run.c +++ /dev/null @@ -1,213 +0,0 @@ -/* - * 964_f32lit_run — runtime + byte-id regression net for #104 fold-1: an - * f32-typed float literal must NARROW to single precision in X0 before - * the f32 consumer reads it. Both stages (cstage cgen.c cgexpr_float, - * wwstage cgenexpr.ww cgfloatbits) materialise a float literal as a - * 64-bit DOUBLE in X0 (MOVQ bits -> MOVSD). For an f32-typed literal the - * downstream MOVSS store/return then reads the LOW 4 BYTES of that - * double — garbage (0x00000000 == 0.0f for most clean values, which is - * why 0.0 coincidentally survived the bug). fold-1 appends CVTSD2SS - * X0,X0 at both literal sites (N_FLOATLIT + the float-typed N_INTLIT - * arm) when the node is f32-typed, so the value reaches X0 as a true - * single. Both stages emit byte-identical asm, so the 990-997 byte-id - * gates can NEVER catch a reintroduction — only an executed-and-checked - * runtime probe can. (951_f64cgen is GATE-BLIND here: its f32 rows only - * assert NaN ordering, never a concrete f32 value.) - * - * SCOPE: fold-1 covers literals that carry an explicit f32 TYPE — the - * `f32` suffix (`1.0f32`, `1.5f32`) and the no-decimal N_INTLIT-float - * arm (`8f32`). An UN-suffixed literal in an f32 context (`let x: f32 = - * 1.0`) stays ty_untyped_float through the checker, so the literal node - * is never f32-typed and fold-1's branch can't fire — materialised as a - * double, stored low-4-bytes -> 0.0f. Fixing that needs fold-2: the - * checker lowering untyped-float literals to their f32 context type - * (#104, both checkers). This probe therefore uses suffixed literals - * exclusively; the un-suffixed gap is tracked under #104 fold-2. - * - * Each row carries BOTH dimensions (like 955_f64xmm_run): - * (a) cstage `ww build` + run, asserting the exit code. - * (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge (rule-10). - */ -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -struct row { const char *label; const char *src; int want_exit; }; - -static const struct row rows[] = { - /* The hole 951 misses: a CONCRETE non-trivial f32 value. On the - * bug `let x: f32 = 1.0f32` stores the low 4 bytes of double 1.0 - * (== 0x00000000 == 0.0f), so x:f64 == 0.0 != 1.0 -> 1. */ - { "bare_value", - "package main;\n" - "export fn main() i32 = {\n" - " let x: f32 = 1.0f32;\n" - " if (x: f64 != 1.0) { return 1; };\n" - " return 0;\n" - "};\n", 0 }, - /* arith on f32 literals: 1.5 + 2.5 == 4.0. On the bug both operands - * land as 0.0f -> sum 0.0 != 4.0. */ - { "arith", - "package main;\n" - "export fn main() i32 = {\n" - " let a: f32 = 1.5f32;\n" - " let b: f32 = 2.5f32;\n" - " let s: f32 = a + b;\n" - " if (s: f64 != 4.0) { return 1; };\n" - " return 0;\n" - "};\n", 0 }, - /* value-propagation: f32 returned through an f32 fn + arith on an - * f32 literal, truncated to i32. 2.5 + 1.5 == 4.0 -> 4. */ - { "return_arith", - "package main;\n" - "fn g() f32 = { return 2.5f32; };\n" - "export fn main() i32 = {\n" - " let r: f32 = g() + 1.5f32;\n" - " return r: i32;\n" - "};\n", 4 }, - /* the float-typed N_INTLIT arm (`8f32` — no decimal, f32 suffix). - * Same materialiser, same fold-1 branch. -> 8. */ - { "intlit_f32_arm", - "package main;\n" - "export fn main() i32 = {\n" - " let y: f32 = 8f32;\n" - " return y: i32;\n" - "};\n", 8 }, - /* genuine single-rounding: 2^24 + 1 is NOT representable in f32 and - * rounds back to 2^24 (round-to-even). If the add ran in double it - * would be 16777217.0 != 16777216.0 -> 1. Proves the value is a - * true single, not the low half of a double. */ - { "single_round", - "package main;\n" - "export fn main() i32 = {\n" - " let big: f32 = 16777216.0f32;\n" - " let r: f32 = big + 1.0f32;\n" - " if (r: f64 != 16777216.0) { return 1; };\n" - " return 0;\n" - "};\n", 0 }, - { NULL, NULL, 0 } -}; - -static int -slurp_eq(const char *a, const char *b) -{ - FILE *fa = fopen(a, "rb"); - FILE *fb = fopen(b, "rb"); - if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } - int rc = 0; - for (;;) { - int ca = fgetc(fa); - int cb = fgetc(fb); - if (ca != cb) { rc = -1; break; } - if (ca == EOF) break; - } - fclose(fa); fclose(fb); - return rc; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[1024]; - if (bin[0] != '/') { - char cwd[1024]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char w6c[1100], w6c_ww[1100]; - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - if (access(w6c_ww, X_OK) != 0) { - fprintf(stderr, "f32lit: w6c_ww missing — cannot run the " - "cs==ww byte-id gate (the whole point of this test)\n"); - return 1; - } - - int n = 0, fail = 0; - for (int i = 0; rows[i].src; i++, n++) { - char tmpdir[64]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/wwf32l_%d_d_%d", - getpid(), i); - mkdir(tmpdir, 0755); - - char src[128], outbin[128], cs_s[128], ws_s[128], rmcmd[160]; - snprintf(src, sizeof src, "%s/wwf32l_%d_%d.ww", - tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/wwf32l_%d_%d", - tmpdir, getpid(), i); - snprintf(cs_s, sizeof cs_s, "%s/wwf32l_%d_%d_cs.s", - tmpdir, getpid(), i); - snprintf(ws_s, sizeof ws_s, "%s/wwf32l_%d_%d_ww.s", - tmpdir, getpid(), i); - snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); - - FILE *f = fopen(src, "wb"); - if (f == NULL) { runwait(rmcmd); fail++; continue; } - fputs(rows[i].src, f); - fclose(f); - - /* (a) cstage build + run. */ - char cmd[2048]; - snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s", - bin, outbin, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: cstage build failed\n", - rows[i].label); - fail++; - runwait(rmcmd); - continue; - } - - int got = runwait(outbin); - if (got != rows[i].want_exit) { - fprintf(stderr, "row[%s]: cstage exit %d, want %d\n", - rows[i].label, got, rows[i].want_exit); - fail++; - } - - /* (b) cs==ww byte-id gate: emit .s from both stages, cmp. */ - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c, cs_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label); - fail++; runwait(rmcmd); continue; - } - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c_ww, ws_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c_ww failed\n", - rows[i].label); - fail++; runwait(rmcmd); continue; - } - if (slurp_eq(cs_s, ws_s) != 0) { - fprintf(stderr, - "row[%s]: cstage/wwstage .s DIFFER (rule-10 " - "byte-id violation)\n", rows[i].label); - fail++; - } - runwait(rmcmd); - } - - if (fail) { - fprintf(stderr, "%d/%d f32 literal-materialise tests failed\n", - fail, n); - return 1; - } - printf("f32lit: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n); - return 0; -} diff --git a/test/wcc/965_f32stamp_run.c b/test/wcc/965_f32stamp_run.c deleted file mode 100644 index ef3d1819..00000000 --- a/test/wcc/965_f32stamp_run.c +++ /dev/null @@ -1,215 +0,0 @@ -/* - * 965_f32stamp_run — runtime + byte-id regression net for #104 fold-2: an - * UN-suffixed float literal in an f32 context (`let x: f32 = 1.0`, `return - * 1.0` from an f32 fn) must be stamped f32 by the checker so fold-1's cgen - * narrow (CVTSD2SS at the literal materialise site) fires. Without the stamp - * the literal stays ty_untyped_float, materialises as a 64-bit double, and - * the f32 consumer reads the LOW 4 BYTES of that double — 0x00000000 == 0.0f - * for clean values (`1.0` -> 0.0f, so `x: f64 != 1.0` trips). fold-1 (964) - * only covered SUFFIXED literals (`1.0f32`); the un-suffixed common case was - * its documented hole, closed here. - * - * Both checkers stamp the literal: cstage cmd/wcc/check.c coerce_floatlit (at - * clet + cstmt N_RETURN), wwstage selfhost/cmd/wcc/check.ww coercefloatlit (in - * resolvewalk's post-order N_LET / N_RETURN handler — placed AFTER the child - * re-walk so the post-order exprtype re-stamp doesn't undo it). Both stages - * emit byte-identical asm, so the 990-997 byte-id gates can NEVER catch a - * reintroduction — only an executed-and-checked runtime probe can. - * - * SCOPE (#104 fold-2): the stamp fires at let-init and return ONLY. binop - * (`1.0 + x_f32`), unary minus (`-1.0`), assign, call-arg, and struct-field - * are DEFERRED to #120 — the wwstage cgen's exprfloatkind (cgenutil.ww) - * hardcodes a float literal to f64 and picks f32 off the operands, not the - * node stamp, so a stamped literal in those positions does not narrow in - * wwstage (cs would emit ADDSS, ww ADDSD — a byte-id break). This probe - * therefore uses bare let-init / return literals exclusively. - * - * Each row carries BOTH dimensions (like 955 / 964): - * (a) cstage `ww build` + run, asserting the exit code. - * (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge (rule-10). - */ -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -struct row { const char *label; const char *src; int want_exit; }; - -static const struct row rows[] = { - /* The hole 964 leaves: a bare (UN-suffixed) f32-context literal. On - * the bug `let x: f32 = 1.0` stores the low 4 bytes of double 1.0 - * (== 0x00000000 == 0.0f), so x:f64 == 0.0 != 1.0 -> 1. */ - { "let_one", - "package main;\n" - "export fn main() i32 = {\n" - " let x: f32 = 1.0;\n" - " if (x: f64 != 1.0) { return 1; };\n" - " return 0;\n" - "};\n", 0 }, - /* a bare decimal literal truncated through i32: 8.0 -> 8. On the bug - * the f32 slot holds 0.0f -> 0. */ - { "let_decimal", - "package main;\n" - "export fn main() i32 = {\n" - " let y: f32 = 8.0;\n" - " return y: i32;\n" - "};\n", 8 }, - /* a fractional value (exactly representable): 0.5. Bug -> 0.0f, so - * p:f64 == 0.0 != 0.5 -> 1. */ - { "let_frac", - "package main;\n" - "export fn main() i32 = {\n" - " let p: f32 = 0.5;\n" - " if (p: f64 != 0.5) { return 1; };\n" - " return 0;\n" - "};\n", 0 }, - /* return context: an f32 fn returning a bare literal, truncated to - * i32 at the call site. 2.0 -> 2. On the bug the X0 single is the - * low half of double 2.0 (== 0.0f) -> 0. */ - { "return_bare", - "package main;\n" - "fn g() f32 = { return 2.0; };\n" - "export fn main() i32 = {\n" - " return g(): i32;\n" - "};\n", 2 }, - /* return feeding a let, both un-suffixed: the literal narrows in the - * fn return, the let-init binds the (already-f32) call value. 4.0 -> - * 4. */ - { "return_then_let", - "package main;\n" - "fn h() f32 = { return 4.0; };\n" - "export fn main() i32 = {\n" - " let r: f32 = h();\n" - " if (r: f64 != 4.0) { return 1; };\n" - " return r: i32;\n" - "};\n", 4 }, - { NULL, NULL, 0 } -}; - -static int -slurp_eq(const char *a, const char *b) -{ - FILE *fa = fopen(a, "rb"); - FILE *fb = fopen(b, "rb"); - if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } - int rc = 0; - for (;;) { - int ca = fgetc(fa); - int cb = fgetc(fb); - if (ca != cb) { rc = -1; break; } - if (ca == EOF) break; - } - fclose(fa); fclose(fb); - return rc; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[1024]; - if (bin[0] != '/') { - char cwd[1024]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char w6c[1100], w6c_ww[1100]; - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - if (access(w6c_ww, X_OK) != 0) { - fprintf(stderr, "f32stamp: w6c_ww missing — cannot run the " - "cs==ww byte-id gate (the whole point of this test)\n"); - return 1; - } - - int n = 0, fail = 0; - for (int i = 0; rows[i].src; i++, n++) { - char tmpdir[64]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/wwf32s_%d_d_%d", - getpid(), i); - mkdir(tmpdir, 0755); - - char rmcmd[160]; - snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); - - char src[128]; - snprintf(src, sizeof src, "%s/wwf32s_%d_%d.ww", - tmpdir, getpid(), i); - FILE *f = fopen(src, "wb"); - if (f == NULL) { fail++; runwait(rmcmd); continue; } - fputs(rows[i].src, f); - fclose(f); - - /* (a) cstage build + run. */ - char outbin[128]; - snprintf(outbin, sizeof outbin, "%s/wwf32s_%d_%d", - tmpdir, getpid(), i); - - char cmd[2048]; - snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s", - bin, outbin, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: cstage build failed\n", - rows[i].label); - fail++; - runwait(rmcmd); - continue; - } - - int got = runwait(outbin); - if (got != rows[i].want_exit) { - fprintf(stderr, "row[%s]: cstage exit %d, want %d\n", - rows[i].label, got, rows[i].want_exit); - fail++; - } - - /* (b) cs==ww byte-id gate: emit .s from both stages, cmp. */ - char cs_s[128], ws_s[128]; - snprintf(cs_s, sizeof cs_s, "%s/wwf32s_%d_%d_cs.s", - tmpdir, getpid(), i); - snprintf(ws_s, sizeof ws_s, "%s/wwf32s_%d_%d_ww.s", - tmpdir, getpid(), i); - - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c, cs_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label); - fail++; runwait(rmcmd); continue; - } - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c_ww, ws_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "row[%s]: w6c_ww failed\n", - rows[i].label); - fail++; runwait(rmcmd); continue; - } - if (slurp_eq(cs_s, ws_s) != 0) { - fprintf(stderr, - "row[%s]: cstage/wwstage .s DIFFER (rule-10 " - "byte-id violation)\n", rows[i].label); - fail++; - } - runwait(rmcmd); - } - - if (fail) { - fprintf(stderr, "%d/%d f32 un-suffixed stamp tests failed\n", - fail, n); - return 1; - } - printf("f32stamp: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n); - return 0; -} diff --git a/test/wcc/989_floatlit_run.c b/test/wcc/989_floatlit_run.c deleted file mode 100644 index 40aa3490..00000000 --- a/test/wcc/989_floatlit_run.c +++ /dev/null @@ -1,200 +0,0 @@ -/* - * 989_floatlit_run — runtime + byte-id net for #62: the two stages - * folded float LITERALS differently. cstage folds via strtod - * (correctly rounded); the wwstage lexer used a pow-10 accumulation - * fold that was 1-2 ULP off on decimal fractions, overflowed its i64 - * accumulator past 19 mantissa digits, and missed DBL_MIN/DBL_MAX by - * up to 2 ULP — a pinned cs≠ww DATA divergence (989 ratchet #59.10). - * Fix: lexnum folds through strconv.stof64 (the Hare-ported - * correctly-rounded decimal engine). - * - * Fixture 1 (vectors): each row reads back a literal's IEEE bits via - * a *u64 reinterpret and compares against the C-strtod-oracle bit - * pattern; the exit code pinpoints the failing row. Carries (a) - * cstage `ww build` + run asserting exit 0 (rule-10: convergence - * targets the runtime-CORRECT side) and (b) w6c vs w6c_ww `.s` cmp - * (byte-id — the wwstage fold itself). - * - * Fixture 2 (overflow): 1.7976931348623159e308 rounds above DBL_MAX — - * strtod sets ERANGE so cstage rejects; wwstage must reject too - * (stof64 overflow → errat). Both compilers must exit non-zero. - * - * Retained divergence (task #21): a SUBNORMAL literal (e.g. - * 2.2250738585072011e-308) is rejected by cstage (glibc strtod flags - * partial underflow with ERANGE) but accepted correctly-rounded by - * wwstage (Hare stof semantics) — accept-set asymmetry only, not a - * bits divergence; pre-existing on master (old parsef64 accepted it - * with garbage bits). - * - * 9xx is full; shares the 989 prefix per the 989_sha256 precedent. - */ -#include -#include -#include -#include -#include - -static const char *vectors_src = - "package main;\n" - "fn bits(v: f64) u64 = {\n" - " let x: f64 = v;\n" - " let p: *u64 = (&x): *u64;\n" - " return *p;\n" - "};\n" - "export fn main() i32 = {\n" - " if (bits(1.0000000000000002) != 0x3FF0000000000001u64) { return 1; };\n" - " if (bits(9007199254740993.0) != 0x4340000000000000u64) { return 2; };\n" - " if (bits(1.2345e67) != 0x4DDD4E421712C0B7u64) { return 3; };\n" - " if (bits(0.1) != 0x3FB999999999999Au64) { return 4; };\n" - " if (bits(1.1) != 0x3FF199999999999Au64) { return 5; };\n" - " if (bits(123456789012345678901234567890.0) != 0x45F8EE90FF6C373Eu64) { return 6; };\n" - " if (bits(2.2250738585072014e-308) != 0x0010000000000000u64) { return 7; };\n" - " if (bits(0.3) != 0x3FD3333333333333u64) { return 8; };\n" - " if (bits(3.141592653589793) != 0x400921FB54442D18u64) { return 9; };\n" - " if (bits(1.7976931348623157e308) != 0x7FEFFFFFFFFFFFFFu64) { return 10; };\n" - " if (bits(1.7976931348623158e308) != 0x7FEFFFFFFFFFFFFFu64) { return 11; };\n" - " if (bits(7.2057594037927933e16) != 0x4370000000000000u64) { return 12; };\n" - " if (bits(1000000000000000000000.0) != 0x444B1AE4D6E2EF50u64) { return 13; };\n" - " if (bits(1_000.5) != 0x408F440000000000u64) { return 14; };\n" - " if (bits(1.00000000000000011102230246251565404236316680908203125) != 0x3FF0000000000000u64) { return 15; };\n" - " if (bits(1.00000000000000011102230246251565404236316680908203126) != 0x3FF0000000000001u64) { return 16; };\n" - " if (bits(4503599627370497.5) != 0x4330000000000002u64) { return 17; };\n" - " if (bits(0.5) != 0x3FE0000000000000u64) { return 18; };\n" - " if (bits(1.0e308) != 0x7FE1CCF385EBC8A0u64) { return 19; };\n" - " if (bits(2.225073858507202e-308) != 0x0010000000000001u64) { return 20; };\n" - " return 0;\n" - "};\n"; - -static const char *overflow_src = - "package main;\n" - "export fn main() i32 = {\n" - " let a: f64 = 1.7976931348623159e308;\n" - " return 0;\n" - "};\n"; - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -static int -slurp_eq(const char *a, const char *b) -{ - FILE *fa = fopen(a, "rb"); - FILE *fb = fopen(b, "rb"); - if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } - int rc = 0; - for (;;) { - int ca = fgetc(fa); - int cb = fgetc(fb); - if (ca != cb) { rc = -1; break; } - if (ca == EOF) break; - } - fclose(fa); fclose(fb); - return rc; -} - -static int -writesrc(const char *path, const char *src) -{ - FILE *f = fopen(path, "wb"); - if (f == NULL) return -1; - fputs(src, f); - fclose(f); - return 0; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[1024]; - if (bin[0] != '/') { - char cwd[1024]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char w6c[1100], w6c_ww[1100]; - snprintf(w6c, sizeof w6c, "%s/w6c", bin); - snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); - if (access(w6c_ww, X_OK) != 0) { - fprintf(stderr, "floatlit: w6c_ww missing — cannot run " - "the cs==ww byte-id gate (the whole point of this test)\n"); - return 1; - } - - int fail = 0; - char src[128], outbin[128], cs_s[128], ws_s[128], cmd[2048]; - - char tmpdir[64], rmcmd[160]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/wwflit_%d_d", getpid()); - mkdir(tmpdir, 0755); - snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); - - /* fixture 1: vectors — cstage build+run, then byte-id */ - snprintf(src, sizeof src, "%s/wwflit_%d.ww", tmpdir, getpid()); - if (writesrc(src, vectors_src) != 0) { runwait(rmcmd); return 1; } - - snprintf(outbin, sizeof outbin, "%s/wwflit_%d", tmpdir, getpid()); - snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s", bin, outbin, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "floatlit: cstage build failed\n"); - fail++; - } else { - int got = runwait(outbin); - if (got != 0) { - fprintf(stderr, "floatlit: vector row %d has wrong " - "bits at runtime (cstage)\n", got); - fail++; - } - } - - snprintf(cs_s, sizeof cs_s, "%s/wwflit_%d_cs.s", tmpdir, getpid()); - snprintf(ws_s, sizeof ws_s, "%s/wwflit_%d_ww.s", tmpdir, getpid()); - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, cs_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "floatlit: w6c failed on vectors\n"); - fail++; - } else { - snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", - w6c_ww, ws_s, src); - if (runwait(cmd) != 0) { - fprintf(stderr, "floatlit: w6c_ww failed on vectors\n"); - fail++; - } else if (slurp_eq(cs_s, ws_s) != 0) { - fprintf(stderr, "floatlit: cstage/wwstage .s DIFFER " - "(rule-10 byte-id violation)\n"); - fail++; - } - } - - /* fixture 2: overflow literal — BOTH stages must reject */ - snprintf(src, sizeof src, "%s/wwflit_%d_ovf.ww", tmpdir, getpid()); - if (writesrc(src, overflow_src) != 0) { runwait(rmcmd); return 1; } - snprintf(cmd, sizeof cmd, "%s %s >/dev/null 2>&1", w6c, src); - if (runwait(cmd) == 0) { - fprintf(stderr, "floatlit: w6c ACCEPTED overflow literal\n"); - fail++; - } - snprintf(cmd, sizeof cmd, "%s %s >/dev/null 2>&1", w6c_ww, src); - if (runwait(cmd) == 0) { - fprintf(stderr, "floatlit: w6c_ww ACCEPTED overflow literal\n"); - fail++; - } - runwait(rmcmd); - - if (fail) { - fprintf(stderr, "floatlit: %d check(s) failed\n", fail); - return 1; - } - printf("floatlit: vectors byte-id + runtime-correct, " - "overflow rejected by both stages\n"); - return 0; -} diff --git a/test/wcc/989_globfloatstructarg_run.c b/test/wcc/989_globfloatstructarg_run.c deleted file mode 100644 index d7be1e01..00000000 --- a/test/wcc/989_globfloatstructarg_run.c +++ /dev/null @@ -1,155 +0,0 @@ -/* - * 989_globfloatstructarg_run — F8-c9 (report-item #31, the EXCEPTION): - * passing a module-GLOBAL float-bearing struct by value must drain its float - * eightbytes into the SSE arg regs (X0..), not all-GP. - * - * THE BUG (cat-A silent miscompile, align-UP): cgcall's per-arg drain decides - * the SysV eightbyte class via structfloatclass, but read it from the local - * slot's tnode ONLY (`if (lc != nil) stfc = structfloatclass(lc.tnode)`). A - * module-global struct arg (lc==nil) kept stfc=0, so the float eightbytes - * drained as integers (POPQ into DI/SI) and X0 was never loaded — the callee - * read its f64 fields from the wrong registers. cstage classifies off the - * operand TYPE, so it loaded X0 and ran correct — the cat-A divergence. - * - * THIS IS THE EXCEPTION among the F8 members: the bug is a float-CLASS - * decision keyed to a local-only slot, NOT an address-routing fallback — an - * F7-flavoured stamp/type-keying fix. THE FIX: for a global ident, key - * structfloatclass off the global's declared tnode (letvartnode), the same - * classification the local path uses. align ww UP; the .s is byte-identical. - * - * Rows (build+run on cstage `ww` and wwstage `ww_ww`; rule-10 — agree+hit; - * each take() returns 0 iff every field round-trips through the call): - * row | shape | want - * ----------------+----------------------------------+----- - * float_first | gp:{f:f64,i:i64}={1.5,9}; take | 0 [#31: f→X0] - * int_first | gp:{i:i64,f:f64}={9,2.5}; take | 0 [int eb→GP, f→X0] - * both_float | gp:{a:f64,b:f64}={1.5,2.5}; take | 0 [two SSE eightbytes] - */ -#include -#include -#include -#include -#include - -static int -runwait(const char *cmd) -{ - int rc = system(cmd); - if (rc == -1) return -1; - if (WIFEXITED(rc)) return WEXITSTATUS(rc); - return -1; -} - -struct row { - const char *label; - const char *src; - int want_exit; -}; - -static const struct row rows[] = { - { "float_first", - "package main;\n" - "type pair = struct { f: f64, i: i64 };\n" - "let gp: pair = pair { f = 1.5, i = 9 };\n" - "fn take(p: pair) int = { if (p.f == 1.5 && p.i == 9) { return 0; }; return 2; };\n" - "export fn main() int = { return take(gp); };\n", - 0 }, - - { "int_first", - "package main;\n" - "type pair = struct { i: i64, f: f64 };\n" - "let gp: pair = pair { i = 9, f = 2.5 };\n" - "fn take(p: pair) int = { if (p.i == 9 && p.f == 2.5) { return 0; }; return 2; };\n" - "export fn main() int = { return take(gp); };\n", - 0 }, - - { "both_float", - "package main;\n" - "type pp = struct { a: f64, b: f64 };\n" - "let gp: pp = pp { a = 1.5, b = 2.5 };\n" - "fn take(p: pp) int = { if (p.a == 1.5 && p.b == 2.5) { return 0; }; return 2; };\n" - "export fn main() int = { return take(gp); };\n", - 0 }, -}; - -/* run_build — build+run `src` via `driver`; returns the binary's exit - * code, or -1 on a build failure. */ -static int -run_build(const char *driver, const struct row *r, int i) -{ - char src[128], tmpdir[64], outbin[128], rmcmd[160], cmd[1024]; - snprintf(tmpdir, sizeof tmpdir, "/tmp/gfsa_%d_d_%d", getpid(), i); - mkdir(tmpdir, 0755); - snprintf(src, sizeof src, "%s/gfsa_%d_%d.ww", tmpdir, getpid(), i); - snprintf(outbin, sizeof outbin, "%s/gfsa_%d_%d", tmpdir, getpid(), i); - snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir); - - FILE *f = fopen(src, "wb"); - if (!f) { runwait(rmcmd); return -2; } - fputs(r->src, f); - fclose(f); - - snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null", - driver, outbin, src); - int brc = runwait(cmd); - - int got = -1; - if (brc == 0) got = runwait(outbin); - - runwait(rmcmd); - return brc == 0 ? got : -1; -} - -int -main(void) -{ - const char *bin = getenv("BIN"); - if (!bin) bin = "out/bin"; - char absbin[1024]; - if (bin[0] != '/') { - char cwd[1024]; - if (getcwd(cwd, sizeof cwd) == NULL) return 1; - snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); - bin = absbin; - } - - char cdrv[1024], wdrv[1024]; - snprintf(cdrv, sizeof cdrv, "%s/ww", bin); - snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); - - struct { const char *name; const char *drv; int gated; } - drivers[] = { - { "cstage", cdrv, 0 }, - { "wwstage", wdrv, 1 }, - { NULL, NULL, 0 }, - }; - - int n = (int)(sizeof rows / sizeof rows[0]); - int total = 0, fail = 0; - - for (int d = 0; drivers[d].name; d++) { - if (drivers[d].gated && access(drivers[d].drv, X_OK) != 0) { - fprintf(stderr, "globfloatstructarg_run: skip %s (no %s)\n", - drivers[d].name, drivers[d].drv); - continue; - } - for (int i = 0; i < n; i++) { - total++; - int got = run_build(drivers[d].drv, &rows[i], i); - if (got != rows[i].want_exit) { - fprintf(stderr, "globfloatstructarg_run[%s][%s]: " - "exit=%d want=%d\n", drivers[d].name, - rows[i].label, got, rows[i].want_exit); - fail++; - } - } - } - - if (fail) { - fprintf(stderr, "globfloatstructarg_run: %d/%d fixtures failed\n", - fail, total); - return 1; - } - printf("globfloatstructarg_run: %d/%d ok\n", total, total); - return 0; -} diff --git a/test/wcc/data/floatlit_overflow/case.ww b/test/wcc/data/floatlit_overflow/case.ww new file mode 100644 index 00000000..a424c380 --- /dev/null +++ b/test/wcc/data/floatlit_overflow/case.ww @@ -0,0 +1,10 @@ +//ww:error "bad float literal" +// 989_floatlit overflow leg: 1.7976931348623159e308 rounds above DBL_MAX — +// cstage strtod sets ERANGE, wwstage stof64 overflows; BOTH stages must reject +// (runww ERROR arm runs w6c AND w6c_ww). Migrated from 989_floatlit_run.c +// fixture 2 (#5-C4). +package main; +export fn main() i32 = { + let a: f64 = 1.7976931348623159e308; + return 0; +};