test: migrate Fam11 float value tests to @test, keep ABI-conformance pins (#5-C4)
fold-2 chunk C4 (drew's Fam8-13 plan): 13 float value-row C drivers re-homed. 11 migrate to test/lang/*_test.ww @test row-tables (exact IEEE-bit asserts); 1 float-overflow reject row -> a runww //ww:error carrier. 956_tuprecv_f64 slims to a w6c_ww asserttyped pin (20 value rows -> @test; the stamp dimension can't be a value/byte-id @test) -- mutation-proven non-vacuous (break #121 stamp -> RED 6/6 -> restore -> GREEN) + an in-test vacuity self-check. 946_structparam/structret stay whole: their SSE register-class .s-grep (SysV ABI conformance, #165/#171a) is the genuine defect-guard, not @test-expressible. Float was the predicted SSE-cursor byte-id hotspot -- zero fresh cs!=ww surfaced; 951_f64cgen (cstage-only before) byte-ids clean. LANGBYTEID floor 82->93; test count 384->374 (10 deleted drivers; 956 + the 2 946 kept).
This commit is contained in:
72
Makefile
72
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; \
|
||||
|
||||
56
test/lang/arr_float_call_index_test.ww
Normal file
56
test/lang/arr_float_call_index_test.ww
Normal file
@@ -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);
|
||||
};
|
||||
56
test/lang/f32arg_test.ww
Normal file
56
test/lang/f32arg_test.ww
Normal file
@@ -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);
|
||||
};
|
||||
43
test/lang/f32lit_test.ww
Normal file
43
test/lang/f32lit_test.ww
Normal file
@@ -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);
|
||||
};
|
||||
40
test/lang/f32stamp_test.ww
Normal file
40
test/lang/f32stamp_test.ww
Normal file
@@ -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);
|
||||
};
|
||||
143
test/lang/f64cgen_test.ww
Normal file
143
test/lang/f64cgen_test.ww
Normal file
@@ -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);
|
||||
};
|
||||
55
test/lang/f64xmm_test.ww
Normal file
55
test/lang/f64xmm_test.ww
Normal file
@@ -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);
|
||||
};
|
||||
56
test/lang/f9_float_test.ww
Normal file
56
test/lang/f9_float_test.ww
Normal file
@@ -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);
|
||||
};
|
||||
44
test/lang/floatarr_test.ww
Normal file
44
test/lang/floatarr_test.ww
Normal file
@@ -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);
|
||||
};
|
||||
45
test/lang/floatlit_test.ww
Normal file
45
test/lang/floatlit_test.ww
Normal file
@@ -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);
|
||||
};
|
||||
40
test/lang/globfloatstructarg_test.ww
Normal file
40
test/lang/globfloatstructarg_test.ww
Normal file
@@ -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);
|
||||
};
|
||||
230
test/lang/tuprecv_f64_test.ww
Normal file
230
test/lang/tuprecv_f64_test.ww
Normal file
@@ -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 <reg>, 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);
|
||||
};
|
||||
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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
|
||||
* <reg>, 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -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",
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
fprintf(stderr, "row[%s]: w6c_ww emitted asserttyped "
|
||||
"(destructure float 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;
|
||||
}
|
||||
|
||||
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
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;
|
||||
}
|
||||
10
test/wcc/data/floatlit_overflow/case.ww
Normal file
10
test/wcc/data/floatlit_overflow/case.ww
Normal file
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user