test: retire 15 T2+T1-covered byte-id .c twins (fold-6 batch-1)
These test/wcc/*_run.c carriers were migrated to test/lang @test packages (value-asserting under test-lang/T1) and now have a byte-id home under test-lang-byteid/T2. With T1 wired into make test + test-commit (prior commit), union(T1 runtime value-asserts + T2 .s byte-id) covers everything each .c proved, so the C twins are pure-deletion redundant. Each retired family below names the T1 twin that now carries its value obligation; every twin was verified to READ the value/slot and ASSERT it (not merely compile), including the pre-zeroed-slot families (it poisons cap!=len / spoils the base so a wrong data word fails the read-back, which byte-id alone cannot catch). 989_gunsigned_run -> gunsigned_test (global unsigned opcode select) 989_chainidx_run -> chainidx_test (chained m[i][k] header load) 989_idxarg_run -> idxarg_test (indexed elem call-arg header push) 906_callret_unsigned_arith -> callret_unsigned_test (call-return unsigned arm) 912_sar_shr_run -> sar_shr_test (signed >> emits SAR) 793_widen_pad_zero_run -> widen_pad_test (#227 high-pad zeroing; reads pad) 932_str_elem_cap_run -> str_elem_cap_test (str-elem .cap full 24B load) 957_size_type_run -> size_type_test (`size` type-position bind) 933_str_field_cap_run -> str_field_cap_test (str field .cap read) 934_str_chained_field_cap_run-> str_chained_field_cap_test (chained field .cap) 935_str_tuple_elem_cap_run -> str_tuple_elem_cap_test (tuple-elem str .cap) 936_str_arrfield_cap_run -> str_arrfield_cap_test (array-field str .cap) 937_str_arrfield_store_cap_run -> str_arrfield_store_cap_test (array-field store) 938_str_chainfield_store_cap_run-> str_chainfield_store_cap_test (chain-field store) 939_str_massign_store_cap_run -> str_massign_store_cap_test (multi-assign store) 110_uniesc_run.c is KEPT: it carries a .wwi round-trip sep-build (exit 42) the uniesc_test twin does not replicate (HAS-UNIQUE-MODE, deferred).
This commit is contained in:
115
Makefile
115
Makefile
@@ -264,9 +264,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_taggedcompoundderef_reject \
|
||||
$(BIN)/test_taggedcompoundplace_reject \
|
||||
$(BIN)/test_libprecond_abort \
|
||||
$(BIN)/test_idxarg_run \
|
||||
$(BIN)/test_ffivariadic_run \
|
||||
$(BIN)/test_chainidx_run \
|
||||
$(BIN)/test_m1mangle_run \
|
||||
$(BIN)/test_m1mangle_sym \
|
||||
$(BIN)/test_m1usehint_run \
|
||||
@@ -292,7 +290,6 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_zerotest_run \
|
||||
$(BIN)/test_ampfncollide_run \
|
||||
$(BIN)/test_trycallcollide_run \
|
||||
$(BIN)/test_gunsigned_run \
|
||||
$(BIN)/test_taggedidx_run \
|
||||
$(BIN)/test_fnptrcollide_run \
|
||||
$(BIN)/test_tagnorm_run \
|
||||
@@ -383,14 +380,6 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_match_slice_variant \
|
||||
$(BIN)/test_match_slice_variant_run \
|
||||
$(BIN)/test_str_abi_run \
|
||||
$(BIN)/test_str_elem_cap_run \
|
||||
$(BIN)/test_str_field_cap_run \
|
||||
$(BIN)/test_str_chained_field_cap_run \
|
||||
$(BIN)/test_str_tuple_elem_cap_run \
|
||||
$(BIN)/test_str_arrfield_cap_run \
|
||||
$(BIN)/test_str_arrfield_store_cap_run \
|
||||
$(BIN)/test_str_chainfield_store_cap_run \
|
||||
$(BIN)/test_str_massign_store_cap_run \
|
||||
$(BIN)/test_slice_store_cap_run \
|
||||
$(BIN)/test_subslice_cap_run \
|
||||
$(BIN)/test_subslice_ptresz_run \
|
||||
@@ -528,7 +517,6 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_tryprop_multisuccess \
|
||||
$(BIN)/test_append_place \
|
||||
$(BIN)/test_struct_tuple_field_slot \
|
||||
$(BIN)/test_widen_pad_zero_run \
|
||||
$(BIN)/test_named_ptr_alias_variant_widen \
|
||||
$(BIN)/test_single_field_struct_zeroinit \
|
||||
$(BIN)/test_structvariant_largeunion_return \
|
||||
@@ -602,8 +590,6 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_oddstruct_byval_ret_run \
|
||||
$(BIN)/test_chained_dot_struct_copy_run \
|
||||
$(BIN)/test_continue_run \
|
||||
$(BIN)/test_callret_unsigned_arith_run \
|
||||
$(BIN)/test_sar_shr_run \
|
||||
$(BIN)/test_def_mangle_run \
|
||||
$(BIN)/test_arr_u16_store_run \
|
||||
$(BIN)/test_arr_module_index_run \
|
||||
@@ -629,7 +615,6 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_structparam_run \
|
||||
$(BIN)/test_structret_run \
|
||||
$(BIN)/test_floats_run \
|
||||
$(BIN)/test_size_type_run \
|
||||
$(BIN)/test_types_sizelim_run \
|
||||
$(BIN)/test_types_intlim_run \
|
||||
$(BIN)/test_opaque_decl_run \
|
||||
@@ -819,17 +804,6 @@ $(BIN)/test_taggedcompoundplace_reject: test/wcc/989_taggedcompoundplace_reject.
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# 989_idxarg_run (F7-c2, #45/#46): an indexed slice/str element passed as
|
||||
# a call arg must push its full multi-word header. Builds+runs each fixture
|
||||
# on BOTH the cstage `ww` and wwstage `ww_ww` drivers (rule-10), so it needs
|
||||
# the full cstage + wwstage tool sets plus libwwrt for the link.
|
||||
$(BIN)/test_idxarg_run: test/wcc/989_idxarg_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 $@ $<
|
||||
|
||||
# 989_ffivariadic_run (C1, catB-54): a ww caller of a C variadic fn must set
|
||||
# the SysV AL register to the count of XMM regs used for the variadic float
|
||||
# args — a RUNTIME-only correctness property (byte-id is blind to AL). The cc
|
||||
@@ -853,16 +827,6 @@ $(BIN)/test_ffivariadic_run: test/wcc/989_ffivariadic_run.c \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# 989_chainidx_run (F7-c3, #22): a chained index `m[i][k]` whose element is
|
||||
# a str/slice must load the full 24B/16B header. Builds+runs each fixture on
|
||||
# BOTH the cstage `ww` and wwstage `ww_ww` drivers (rule-10).
|
||||
$(BIN)/test_chainidx_run: test/wcc/989_chainidx_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 $@ $<
|
||||
|
||||
# 989_m1mangle_run / _sym (M1, #22 + #32): path-qualified symbol mangling
|
||||
# and root-unit entry detection. _run builds+runs a tiny package tree on
|
||||
# BOTH stages (rule-10); _sym asserts the emitted symbol names + cs.s==ww.s.
|
||||
@@ -1168,16 +1132,6 @@ $(BIN)/test_trycallcollide_run: test/wcc/989_trycallcollide_run.c \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# 989_gunsigned_run (F7-c5, #25): a module-global unsigned ident on the
|
||||
# divide/shift/relational path must pick the unsigned opcode. Builds+runs on
|
||||
# BOTH driver twins (rule-10). CLASS-M — see the test header.
|
||||
$(BIN)/test_gunsigned_run: test/wcc/989_gunsigned_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 $@ $<
|
||||
|
||||
# 989_taggedidx_run (F7-c6, #23): a tagged-union element indexed off a call
|
||||
# or slice base must load the full tag+payload cursor. Builds+runs on BOTH
|
||||
# driver twins (rule-10).
|
||||
@@ -1943,12 +1897,6 @@ $(BIN)/test_spread_variant_match: test/wcc/792_spread_variant_match.c \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_widen_pad_zero_run: test/wcc/793_widen_pad_zero_run.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/w6c_ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# #55: bare value-ident in an imported module must resolve same-module-
|
||||
# preferred (exprtype N_IDENT scopelookup -> scopelookupprefer). cstage
|
||||
# driver build + run pins routing; the ww-sep build (w6c_ww) must ACCEPT
|
||||
@@ -2467,54 +2415,6 @@ $(BIN)/test_str_abi_run: test/wcc/928_str_abi_run.c \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_str_elem_cap_run: test/wcc/932_str_elem_cap_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_str_field_cap_run: test/wcc/933_str_field_cap_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_str_chained_field_cap_run: test/wcc/934_str_chained_field_cap_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_str_tuple_elem_cap_run: test/wcc/935_str_tuple_elem_cap_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_str_arrfield_cap_run: test/wcc/936_str_arrfield_cap_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_str_arrfield_store_cap_run: test/wcc/937_str_arrfield_store_cap_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_str_chainfield_store_cap_run: test/wcc/938_str_chainfield_store_cap_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_str_massign_store_cap_run: test/wcc/939_str_massign_store_cap_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_str_forrange_loopvar_run: test/wcc/940_str_forrange_loopvar_run.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||
@@ -3489,17 +3389,6 @@ $(BIN)/test_continue_run: test/wcc/911_continue_run.c $(BIN)/ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_sar_shr_run: test/wcc/912_sar_shr_run.c $(BIN)/ww \
|
||||
$(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_callret_unsigned_arith_run: \
|
||||
test/wcc/906_callret_unsigned_arith_run.c $(BIN)/ww \
|
||||
$(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_def_mangle_run: test/wcc/913_def_mangle_run.c $(BIN)/ww \
|
||||
$(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
@@ -3563,10 +3452,6 @@ $(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 $@ $<
|
||||
|
||||
$(BIN)/test_size_type_run: test/wcc/957_size_type_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_types_sizelim_run: test/wcc/958_types_sizelim_run.c $(BIN)/ww \
|
||||
$(BIN)/w6c $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
@@ -1,215 +0,0 @@
|
||||
/*
|
||||
* 793_widen_pad_zero_run — widening a NARROW scalar/float variant into a
|
||||
* tagged-union slot whose payload is WIDER than one word must zero the
|
||||
* high pad words (slot+16, slot+24), not leave them at whatever the
|
||||
* frame slot last held. Project #227.
|
||||
*
|
||||
* cg_widen_tagged_store (cmd/w6c/cgen.c) and the wwstage twin
|
||||
* cgwidentaggedstorebp (selfhost/cmd/wcc/cgenutil.ww) wrote only the tag
|
||||
* (slot+0) and the value (slot+8) in their scalar and float arms; the
|
||||
* remaining slot words were left uninitialised. For a >16B union (e.g.
|
||||
* `(i64 | str)`, whose str variant makes the slot 32B = 4 words) a
|
||||
* passthrough return / slot copy / `*u8` reinterpret of the now
|
||||
* scalar-tagged value then read stack garbage at slot+16 / slot+24.
|
||||
*
|
||||
* Both stages were wrong the SAME way, so the 990-997 byte-id gates and
|
||||
* the cs==ww asm gate were GREEN while the runtime was wrong — the bug
|
||||
* is dead in the bootstrap corpus (the fmt `field` spread-union scalar
|
||||
* widen is the first real consumer). The fix adds, in BOTH stages'
|
||||
* scalar and float arms, an unconditional (size>16) tail-zero of
|
||||
* slot+16..slot_sz that mirrors the existing tagged-subset / struct
|
||||
* tail-zeros — so the two stages stay byte-identical (rule 10) and the
|
||||
* full destination payload width is always defined.
|
||||
*
|
||||
* Observability: each row first widens a STR into the union (which fills
|
||||
* slot+16/+24 with the str's len/cap), then reassigns a SCALAR / FLOAT
|
||||
* into the SAME slot, then reads slot+16/+24 back through a `*u8`
|
||||
* reinterpret (the bit-pinning idiom from 715_tagged_widen_f64). Pre-fix
|
||||
* the reassign left the str's stale len/cap in the pad; the row returns
|
||||
* 2 (pad1 nonzero) on a broken compiler and 0 when the pad is zeroed.
|
||||
* Confirmed: pre-fix both `ww` and `ww_ww` exit 2 on row 0; post-fix
|
||||
* both exit 0; and the emitted .s is byte-identical across stages.
|
||||
*
|
||||
* Rows (each its own single-module program, built + run under cstage
|
||||
* `ww` and, when present, wwstage `ww_ww`; want = 0):
|
||||
* scalar_i64_after_str — reassign i64 over a str-occupied (i64|str)
|
||||
* slot; assert value survives and pad == 0.
|
||||
* float_f64_after_str — reassign f64 over a str-occupied (f64|str)
|
||||
* slot; the float arm path. Asserts the f64
|
||||
* bits survive at slot+8 and pad == 0.
|
||||
* scalar_let_fresh — a plain `let r:(i64|str) = n;` (no prior str)
|
||||
* in a deliberately dirtied frame; pad == 0.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.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; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* Reassign a scalar i64 over a str-occupied (i64|str) slot. The
|
||||
* str write fills slot+16 (len) and slot+24 (cap); the scalar
|
||||
* reassign must zero them. payload bits = 123, tag = 0 (i64). */
|
||||
{ "scalar_i64_after_str",
|
||||
"export fn main() i32 = {\n"
|
||||
" let a: (i64 | str) = \"abcdefgh\";\n"
|
||||
" a = 123i64;\n"
|
||||
" let pp: *(i64 | str) = &a;\n"
|
||||
" let pu: *u8 = pp: *u8;\n"
|
||||
" let tagp: *i64 = pu: *i64;\n"
|
||||
" let valp: *i64 = (pu + 8u64): *i64;\n"
|
||||
" let pad1: *i64 = (pu + 16u64): *i64;\n"
|
||||
" let pad2: *i64 = (pu + 24u64): *i64;\n"
|
||||
" if (*valp != 123i64) { return 1; };\n"
|
||||
" if (*pad1 != 0i64) { return 2; };\n"
|
||||
" if (*pad2 != 0i64) { return 3; };\n"
|
||||
" if (*tagp != 0i64) { return 4; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* Reassign an f64 over a str-occupied (f64|str) slot — the float
|
||||
* arm. f64 1.0 == 0x3FF0000000000000 == 4607182418800017408.
|
||||
* tag = 0 (f64 is variant 0). */
|
||||
{ "float_f64_after_str",
|
||||
"export fn main() i32 = {\n"
|
||||
" let a: (f64 | str) = \"abcdefgh\";\n"
|
||||
" a = 1.0;\n"
|
||||
" let pp: *(f64 | str) = &a;\n"
|
||||
" let pu: *u8 = pp: *u8;\n"
|
||||
" let tagp: *i64 = pu: *i64;\n"
|
||||
" let valp: *u64 = (pu + 8u64): *u64;\n"
|
||||
" let pad1: *i64 = (pu + 16u64): *i64;\n"
|
||||
" let pad2: *i64 = (pu + 24u64): *i64;\n"
|
||||
" if (*valp != 4607182418800017408u64) { return 1; };\n"
|
||||
" if (*pad1 != 0i64) { return 2; };\n"
|
||||
" if (*pad2 != 0i64) { return 3; };\n"
|
||||
" if (*tagp != 0i64) { return 4; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* Reassign a scalar over a str TWICE — exercises the scalar widen
|
||||
* arm on a slot whose pad currently holds a str's len/cap, then
|
||||
* again, confirming the tail-zero is emitted on every scalar widen
|
||||
* (not just a first-write). Reads pad after the second reassign. */
|
||||
{ "scalar_after_str_twice",
|
||||
"export fn main() i32 = {\n"
|
||||
" let a: (i64 | str) = \"firstone\";\n"
|
||||
" a = 11i64;\n"
|
||||
" a = \"secondxx\";\n"
|
||||
" a = 222i64;\n"
|
||||
" let pp: *(i64 | str) = &a;\n"
|
||||
" let pu: *u8 = pp: *u8;\n"
|
||||
" let valp: *i64 = (pu + 8u64): *i64;\n"
|
||||
" let pad1: *i64 = (pu + 16u64): *i64;\n"
|
||||
" let pad2: *i64 = (pu + 24u64): *i64;\n"
|
||||
" if (*valp != 222i64) { return 1; };\n"
|
||||
" if (*pad1 != 0i64) { return 2; };\n"
|
||||
" if (*pad2 != 0i64) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[96], tmpdir[96], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/wpz_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wpz_%d_d_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s > /dev/null 2>&1",
|
||||
tmpdir, driver, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
unlink(src);
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", tmpdir);
|
||||
runwait(cmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[160];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
int got = runwait(outbin);
|
||||
|
||||
unlink(src);
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s", tmpdir);
|
||||
runwait(cmd);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640], wdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
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_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "widen_pad_zero_run: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"widen_pad_zero_run[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"widen_pad_zero_run: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("widen_pad_zero_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,263 +0,0 @@
|
||||
/*
|
||||
* 906_callret_unsigned_arith_run — runtime + byte-id net for #168, the
|
||||
* N_CALL twin of #134. The cgen signedness classifier must treat a
|
||||
* div/mod/shift whose operand is a *call result* (`f() / d`, `f() % d`,
|
||||
* `f() >> n`) as unsigned iff the callee's return type is unsigned —
|
||||
* picking DIVQ/SHRQ over IDIVQ/SARQ.
|
||||
*
|
||||
* The wwstage `nodeisunsigned` (cgenutil.ww) walked surface nodes but
|
||||
* had no N_CALL arm, so a call-result operand fell through to
|
||||
* `return false` (signed) → signed IDIVQ/SARQ on an unsigned-returning
|
||||
* call → silent wrong arithmetic. The cstage was already correct: it
|
||||
* reads `type_isunsigned(n->lhs->type)` on the operand's stamped tinfo
|
||||
* (cgen.c TK_SLASH/TK_PERCENT/TK_RSHIFT), and the N_CALL result type is
|
||||
* stamped at check.c N_CALL (`n->type = u->ret`). #168 brings the
|
||||
* wwstage arm up to read the same stamp — exactly as #134 did for the
|
||||
* N_INDEX arm (which also touched no C).
|
||||
*
|
||||
* Gate-blind: the bootstrap never divides/shifts a call result by an
|
||||
* unsigned type (strconv dodged it via local-bind, which lands on the
|
||||
* N_IDENT arm). So only a direct call-result probe catches it — a
|
||||
* `let a = f();` bind would hide the bug.
|
||||
*
|
||||
* Discrimination:
|
||||
* - byte-id (.s cmp, the primary #168 gate): for ANY unsigned-
|
||||
* returning call, w6c picks DIVQ/SHRQ but pre-fix w6c_ww picks
|
||||
* IDIVQ/SARQ → .s DIFFER. Holds for every unsigned width (the
|
||||
* classifier diverges regardless of the runtime value).
|
||||
* - cstage runtime: validates the unsigned *semantics*. Only the
|
||||
* machine-word rows (u64/uint, high bit set) differ at runtime
|
||||
* between signed/unsigned div on the 64-bit register; the u32 row
|
||||
* is a byte-id discriminator + runtime control (zero-extends to a
|
||||
* positive 64-bit value, so the value alone can't tell the ops
|
||||
* apart — the .s cmp does).
|
||||
* - signed rows (i64/i32/int) guard against an over-broad fix: a
|
||||
* signed-returning call must STILL pick IDIVQ/SARQ in both stages.
|
||||
*
|
||||
* Same dual-driver shape as 912_sar_shr_run.c: each row is (a) cstage
|
||||
* `ww build` + run asserting exit 42, and (b) w6c vs w6c_ww `.s` cmp
|
||||
* (rule-10 byte-id). Hard-fails if w6c_ww is missing so the wwstage
|
||||
* side — the side that carried the bug — can never silently skip.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.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[] = {
|
||||
/* ---- unsigned: call result drives DIVQ/SHRQ ------------------- */
|
||||
/* u64 div: 0x8000000000000001 / 2. unsigned = 0x4000000000000000;
|
||||
* signed IDIVQ (CQO sign-extends the high-bit-set dividend) =
|
||||
* 0xC000000000000001. */
|
||||
{ "u64_div_callret",
|
||||
"package main;\n"
|
||||
"fn uval() u64 = { return 0x8000000000000001u64; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (uval() / 2u64 == 0x4000000000000000u64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 42 },
|
||||
/* u64 mod: unsigned rem = 1; signed rem = -1. */
|
||||
{ "u64_mod_callret",
|
||||
"package main;\n"
|
||||
"fn uval() u64 = { return 0x8000000000000001u64; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (uval() % 2u64 == 1u64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 42 },
|
||||
/* u64 shr: SHRQ (logical) = 0x4000000000000000; SARQ (arithmetic,
|
||||
* sign-fills the set MSB) = 0xC000000000000000. */
|
||||
{ "u64_shr_callret",
|
||||
"package main;\n"
|
||||
"fn uval() u64 = { return 0x8000000000000001u64; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (uval() >> 1u64 == 0x4000000000000000u64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 42 },
|
||||
/* u32 div: byte-id discriminator (w6c DIVQ vs pre-fix w6c_ww
|
||||
* IDIVQ); runtime control — 0x80000001 zero-extends to a positive
|
||||
* 64-bit value, so signed/unsigned div agree at runtime. */
|
||||
{ "u32_div_callret",
|
||||
"package main;\n"
|
||||
"fn uval() u32 = { return 0x80000001u32; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (uval() / 2u32 == 0x40000000u32) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 42 },
|
||||
/* uint (machine word): exercises the TY_UINT arm of typeisunsigned
|
||||
* on a call result. High bit set → discriminates like u64. */
|
||||
{ "uint_div_callret",
|
||||
"package main;\n"
|
||||
"fn uval() uint = { return (0x8000000000000001u64): uint; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (uval() / 2 == (0x4000000000000000u64): uint) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 42 },
|
||||
|
||||
/* ---- signed guards: call result STAYS IDIVQ/SARQ -------------- */
|
||||
/* i64 div: -100 / 7 = -14 (toward zero). If wrongly unsigned, -100
|
||||
* as u64 is huge → quotient huge ≠ -14. */
|
||||
{ "i64_div_callret",
|
||||
"package main;\n"
|
||||
"fn sval() i64 = { return -100i64; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (sval() / 7i64 == -14i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 42 },
|
||||
/* i64 shr: -8 >> 1 = -4 via SARQ (sign-fill). SHRQ would zero-fill
|
||||
* → large positive. */
|
||||
{ "i64_shr_callret",
|
||||
"package main;\n"
|
||||
"fn sval() i64 = { return -8i64; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (sval() >> 1i64 == -4i64) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 42 },
|
||||
/* i32 div: -100 / 7 = -14. */
|
||||
{ "i32_div_callret",
|
||||
"package main;\n"
|
||||
"fn sval() i32 = { return -100i32; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (sval() / 7i32 == -14i32) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 42 },
|
||||
/* int (machine word, signed): -100 / 7 = -14; exercises the signed
|
||||
* machine-word path on a call result. */
|
||||
{ "int_div_callret",
|
||||
"package main;\n"
|
||||
"fn sval() int = { return -100; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" if (sval() / 7 == -14) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 42 },
|
||||
{ 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, "callret_unsigned: 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 src[64];
|
||||
snprintf(src, sizeof src, "/tmp/wwcr_%d_%d.ww", getpid(), i);
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (f == NULL) { fail++; continue; }
|
||||
fputs(rows[i].src, f);
|
||||
fclose(f);
|
||||
|
||||
char tmpdir[64];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwcr_%d_d_%d",
|
||||
getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
|
||||
char cmd[2048];
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s",
|
||||
tmpdir, bin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: cstage build failed\n",
|
||||
rows[i].label);
|
||||
fail++;
|
||||
unlink(src); rmdir(tmpdir);
|
||||
continue;
|
||||
}
|
||||
|
||||
char outbin[128];
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
|
||||
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++;
|
||||
}
|
||||
unlink(outbin); rmdir(tmpdir);
|
||||
|
||||
char cs_s[64], ws_s[64];
|
||||
snprintf(cs_s, sizeof cs_s, "/tmp/wwcr_%d_%d_cs.s",
|
||||
getpid(), i);
|
||||
snprintf(ws_s, sizeof ws_s, "/tmp/wwcr_%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++; unlink(src); 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++; unlink(src); 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++;
|
||||
}
|
||||
unlink(src); unlink(cs_s); unlink(ws_s);
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "%d/%d callret_unsigned tests failed\n",
|
||||
fail, n);
|
||||
return 1;
|
||||
}
|
||||
printf("callret_unsigned: %d/%d ok (cstage run + cs==ww byte-id)\n",
|
||||
n, n);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
/*
|
||||
* 912_sar_shr_run — runtime + byte-id net for #136: signed right-shift
|
||||
* (both plain `>>` and `>>=`) must use SAR (arithmetic, sign-extends
|
||||
* MSB), not SHR (logical, zero-fill). Pre-fix BOTH stages emitted SHRQ
|
||||
* for signed RSHIFT because A_SARQ wasn't in the w6a opcode table, so
|
||||
* `let i: i32 = -200; i >>= 2;` produced i = 0x3FFFFFCE (1073741774)
|
||||
* instead of -50. cs==ww held → gate-blind. #133-expanded shipped with
|
||||
* this pre-existing concern documented (parity with cgen.c:4145 deref-
|
||||
* lvalue compound site); #136 closes it.
|
||||
*
|
||||
* Fix: add A_SARQ to w6a + w6c + w6a_ww opcode tables (encoding REX.W
|
||||
* D3 /7, parallel to SHR's D3 /5), then sweep cgen sites in both
|
||||
* stages to dispatch SARQ vs SHRQ on the operand signedness. Sites
|
||||
* covered (per-stage 6 each):
|
||||
* - plain `>>` binop (cgbin TK_RSHIFT)
|
||||
* - chained-ptr-field compound RSHIFTEQ (#133-expanded site 1)
|
||||
* - N_INDEX-lhs compound RSHIFTEQ (#133-expanded site 2)
|
||||
* - deref-target compound RSHIFTEQ (pre-existing)
|
||||
* - top-level let compound RSHIFTEQ
|
||||
* - IDENT-local compound RSHIFTEQ
|
||||
*
|
||||
* The bootstrap audit at design time showed zero current signed-RSHIFT
|
||||
* callers in lib/+selfhost/, so 990-997 byte-id stays GREEN; the only
|
||||
* shifts are on the previously-broken paths.
|
||||
*
|
||||
* Rows cover the operator + signedness × storage matrix:
|
||||
* - i32_neg_rshifteq: lead's repro pattern, -200 >>= 2 = -50 (was
|
||||
* 1073741774 pre-fix on SHRQ)
|
||||
* - i64_neg_rshifteq: same shape, i64
|
||||
* - i32_pos_rshifteq: control, SARQ and SHRQ produce same result on
|
||||
* positives; asserts no regression
|
||||
* - u32_rshifteq: unsigned control, must still use SHRQ (no change)
|
||||
* - i32_neg_rshift_binop: plain `>>` (not compound) on negative i32,
|
||||
* covers the cgen 2596 / 3176 TK_RSHIFT N_BIN site
|
||||
*
|
||||
* Each row carries (a) cstage `ww build` + run asserting the exit
|
||||
* code and (b) w6c vs w6c_ww `.s` cmp (rule-10 byte-id). Exit codes
|
||||
* use small absolute values + 256 to encode signed expectations:
|
||||
* -50 → 206 = 256-50 (Unix exit is 8-bit unsigned). i64 row clamped
|
||||
* similarly.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.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 #136 repro: signed i32 >>=. Pre-fix produced wrong large
|
||||
* positive; post-fix produces -50. Exit code = -50 as u8 = 206. */
|
||||
{ "i32_neg_rshifteq",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let i: i32 = -200;\n"
|
||||
" i >>= 2;\n"
|
||||
" return i;\n"
|
||||
"};\n", 206 },
|
||||
/* signed i64 >>=. Same shape, wider type. Result -50 as exit u8
|
||||
* = 206. */
|
||||
{ "i64_neg_rshifteq",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let i: i64 = -200i64;\n"
|
||||
" i >>= 2i64;\n"
|
||||
" return i: i32;\n"
|
||||
"};\n", 206 },
|
||||
/* Positive control: SARQ and SHRQ agree. 200 >> 2 = 50. */
|
||||
{ "i32_pos_rshifteq",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let i: i32 = 200;\n"
|
||||
" i >>= 2;\n"
|
||||
" return i;\n"
|
||||
"};\n", 50 },
|
||||
/* Unsigned control: SHRQ unchanged. 200u32 >> 2 = 50. */
|
||||
{ "u32_rshifteq",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let i: u32 = 200u32;\n"
|
||||
" i >>= 2u32;\n"
|
||||
" return i: i32;\n"
|
||||
"};\n", 50 },
|
||||
/* Plain `>>` (not compound) on signed negative: covers cgbin
|
||||
* TK_RSHIFT site (cgen.c:2596 / cgenexpr.ww:3176). */
|
||||
{ "i32_neg_rshift_binop",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let i: i32 = -200;\n"
|
||||
" let r: i32 = i >> 2;\n"
|
||||
" return r;\n"
|
||||
"};\n", 206 },
|
||||
{ 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, "sarshr: 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 src[64];
|
||||
snprintf(src, sizeof src, "/tmp/wwsar_%d_%d.ww", getpid(), i);
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (f == NULL) { fail++; continue; }
|
||||
fputs(rows[i].src, f);
|
||||
fclose(f);
|
||||
|
||||
char tmpdir[64];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwsar_%d_d_%d",
|
||||
getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
|
||||
char cmd[2048];
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s",
|
||||
tmpdir, bin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: cstage build failed\n",
|
||||
rows[i].label);
|
||||
fail++;
|
||||
unlink(src); rmdir(tmpdir);
|
||||
continue;
|
||||
}
|
||||
|
||||
char outbin[128];
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
|
||||
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++;
|
||||
}
|
||||
unlink(outbin); rmdir(tmpdir);
|
||||
|
||||
char cs_s[64], ws_s[64];
|
||||
snprintf(cs_s, sizeof cs_s, "/tmp/wwsar_%d_%d_cs.s",
|
||||
getpid(), i);
|
||||
snprintf(ws_s, sizeof ws_s, "/tmp/wwsar_%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++; unlink(src); 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++; unlink(src); 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++;
|
||||
}
|
||||
unlink(src); unlink(cs_s); unlink(ws_s);
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "%d/%d sar/shr tests failed\n", fail, n);
|
||||
return 1;
|
||||
}
|
||||
printf("sarshr: %d/%d ok (cstage run + cs==ww byte-id)\n",
|
||||
n, n);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
/*
|
||||
* 932_str_elem_cap_run — runtime coverage for the F2 fold: a str-element
|
||||
* VALUE read via N_INDEX must load the full 24B {ptr,len,cap} header, not
|
||||
* just {ptr,len}. str is 24B since Phase 2 (#1); pre-F2 the N_INDEX
|
||||
* str-element arms dropped the cap word.
|
||||
*
|
||||
* The existing byte-id gates (990-997) can't catch a no-op fold here: a
|
||||
* shared 2-word miscompile passes byte-id silently. So this pins the
|
||||
* *runtime* contract — build each fixture through both the cstage `ww`
|
||||
* and the wwstage `ww_ww` driver and confirm the program's assertions
|
||||
* hold (exit 0).
|
||||
*
|
||||
* Each row POISONS the element so cap != len (a `.cap =` pseudo-field
|
||||
* write, no malloc / no import — keeps the fixture self-contained so
|
||||
* ww_ww writes intermediates only next to the /tmp source, not lib/).
|
||||
* A 2-word read leaves cap = len (Site A) or a stale slice-cap (Site B),
|
||||
* so the read-back .cap mismatches the poisoned value and the row fails.
|
||||
*
|
||||
* Both N_INDEX base forms are covered:
|
||||
* - Site A: N_IDENT base — `xs[0]` on a local `[N]str`.
|
||||
* - Site B: fallback base — `b.items[0]` where the base `b.items` is
|
||||
* an N_DOT (slice field), not a plain ident.
|
||||
*
|
||||
* The cap word is observed through `let e: str = <index>` (a 3-word
|
||||
* copy into the slot) then `e.cap` (an N_IDENT pseudo-field read off the
|
||||
* slot). A direct `<index>.cap` would NOT observe it — the non-ident
|
||||
* .cap N_DOT path only handles .ptr/.len today (a separate gap, out of
|
||||
* F2's scope).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.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; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* Site A — N_IDENT base. Poison cap=8 (len=2), read back via a
|
||||
* local [2]str. Broken 2-word read leaves cap = len = 2. */
|
||||
{ "sitea_ident_base",
|
||||
"export fn main() i32 = {\n"
|
||||
" let p: str = \"hi\";\n"
|
||||
" p.cap = 8i32;\n"
|
||||
" let xs: [2]str;\n"
|
||||
" xs[0] = p;\n"
|
||||
" let e: str = xs[0];\n"
|
||||
" if (e.cap: i32 != 8) { return 1; };\n"
|
||||
" if (e.len: i32 != 2) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* Site B — fallback base. Element behind an N_DOT slice field, so
|
||||
* the N_INDEX base isn't a plain ident. Poison cap=9 (len=5).
|
||||
* Broken read leaves cap = the slice header's own cap, not the
|
||||
* element's. */
|
||||
{ "siteb_fallback_base",
|
||||
"type box = struct { items: []str };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let q: str = \"world\";\n"
|
||||
" q.cap = 9i32;\n"
|
||||
" let arr: [2]str;\n"
|
||||
" arr[0] = q;\n"
|
||||
" let b: box;\n"
|
||||
" b.items = arr[0:2];\n"
|
||||
" let e: str = b.items[0];\n"
|
||||
" if (e.cap: i32 != 9) { return 1; };\n"
|
||||
" if (e.len: i32 != 5) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[96], tmpdir[96], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/strelemcap_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/strelemcap_%d_d_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s",
|
||||
tmpdir, driver, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
unlink(src); rmdir(tmpdir);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[160];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
int got = runwait(outbin);
|
||||
|
||||
unlink(src); unlink(outbin); rmdir(tmpdir);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[640];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
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_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr,
|
||||
"str_elem_cap_run: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"str_elem_cap_run[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "str_elem_cap_run: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("str_elem_cap_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,197 +0,0 @@
|
||||
/*
|
||||
* 933_str_field_cap_run — runtime coverage for the C4.6 fold: reading a
|
||||
* str-typed FIELD of a struct (N_DOT value read) must load the full 24B
|
||||
* {ptr,len,cap} header, not just {ptr,len}. str is 24B since Phase 2 (#1);
|
||||
* pre-C4.6 the N_DOT str-field arms loaded 2 words and dropped cap.
|
||||
*
|
||||
* The byte-id gates (990-997) can't catch a symmetric 2-word miscompile:
|
||||
* if both stages drop cap identically, byte-id passes silently. So this
|
||||
* pins the *runtime* contract — build each fixture through both the cstage
|
||||
* `ww` and the wwstage `ww_ww` driver and confirm the assertions hold
|
||||
* (exit 0). The global row is the load-bearing case: in ww the global
|
||||
* struct-field load is a separate arm with no slice sibling (cstage folds
|
||||
* local+global in one base_reg arm; ww splits them), so a cap-drop there
|
||||
* would hide from the local-field rows.
|
||||
*
|
||||
* Each row POISONS the source str so cap != len (a `.cap =` pseudo-field
|
||||
* write, no malloc / no import — self-contained, so ww_ww writes
|
||||
* intermediates only next to the /tmp source). A 2-word field read leaves
|
||||
* cap = stale, so the read-back .cap mismatches the poison and the row
|
||||
* fails. The cap word is observed through `let s: str = <field>` (a 3-word
|
||||
* copy into the slot) then `s.cap` (an N_IDENT pseudo-field read off the
|
||||
* slot) — mirrors the 932 observation shape.
|
||||
*
|
||||
* Sites (C4.6 S1 + S2, case A):
|
||||
* - s1local : `st.f` — direct struct local field (base BP).
|
||||
* - s1global : `g.f` — direct struct global field (base CX).
|
||||
* - s2ptr : `pst.f` — field via a *struct local (base BX, deref).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.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; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* S1 local — `st.f` direct struct local field. Poison cap=8
|
||||
* (len=2). spoil() interposes a call between the field store and
|
||||
* the read: a call clobbers caller-saved CX, so a broken 2-word
|
||||
* read leaves cap = spoil's leftover (44), not the lingering store
|
||||
* value — without it CX would coincidentally still hold the poison
|
||||
* the `st.f = p` store left behind and the row wouldn't discriminate. */
|
||||
{ "s1local_field",
|
||||
"type rec = struct { f: str };\n"
|
||||
"fn spoil() i32 = {\n"
|
||||
" let z: str = \"zzzz\";\n"
|
||||
" z.cap = 44i32;\n"
|
||||
" let w: str = z;\n"
|
||||
" return w.cap: i32;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let p: str = \"hi\";\n"
|
||||
" p.cap = 8i32;\n"
|
||||
" let st: rec;\n"
|
||||
" st.f = p;\n"
|
||||
" let junk: i32 = spoil();\n"
|
||||
" let s: str = st.f;\n"
|
||||
" if (s.cap: i32 != 8) { return 1; };\n"
|
||||
" if (s.len: i32 != 2) { return 2; };\n"
|
||||
" if (junk != 44) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* S1 global — `g.f` direct struct global field (base CX path).
|
||||
* The no-local-sibling arm in ww; poison cap=9 (len=5). */
|
||||
{ "s1global_field",
|
||||
"type rec = struct { f: str };\n"
|
||||
"let g: rec;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let p: str = \"world\";\n"
|
||||
" p.cap = 9i32;\n"
|
||||
" g.f = p;\n"
|
||||
" let s: str = g.f;\n"
|
||||
" if (s.cap: i32 != 9) { return 1; };\n"
|
||||
" if (s.len: i32 != 5) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* S2 case A — `pst.f` field via a *struct local (deref, base BX).
|
||||
* Poison cap=7 (len=3). */
|
||||
{ "s2ptr_field",
|
||||
"type rec = struct { f: str };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let p: str = \"abc\";\n"
|
||||
" p.cap = 7i32;\n"
|
||||
" let st: rec;\n"
|
||||
" st.f = p;\n"
|
||||
" let pst: *rec = &st;\n"
|
||||
" let s: str = pst.f;\n"
|
||||
" if (s.cap: i32 != 7) { return 1; };\n"
|
||||
" if (s.len: i32 != 3) { return 2; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[96], tmpdir[96], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/strfieldcap_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/strfieldcap_%d_d_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s",
|
||||
tmpdir, driver, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
unlink(src); rmdir(tmpdir);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[160];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
int got = runwait(outbin);
|
||||
|
||||
unlink(src); unlink(outbin); rmdir(tmpdir);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[640];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
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_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr,
|
||||
"str_field_cap_run: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"str_field_cap_run[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "str_field_cap_run: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("str_field_cap_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
/*
|
||||
* 934_str_chained_field_cap_run — runtime coverage for the C4.6-caseB fold:
|
||||
* a CHAINED str-field read `o.p.f` (depth >= 2, where p is a *struct field of
|
||||
* o and f is a str field of *p) must load the full 24B {ptr,len,cap} header,
|
||||
* not just {ptr,len}. str is 24B since Phase 2 (#1); pre-caseB the chained
|
||||
* *struct N_DOT str arm loaded 2 words (ptr in AX, len in BX) and dropped cap.
|
||||
*
|
||||
* This is the chained sibling of 933 (which covered the direct / single-deref
|
||||
* S1/S2 arms). The site is cgen.c's "Chained N_DOT through a *struct field"
|
||||
* branch (n->lhs is itself an N_DOT typed *struct): the lhs cgexpr leaves AX =
|
||||
* the inner *struct pointer, then the field is read off AX. caseB folds the
|
||||
* str arm onto the adjacent 3-word slice arm — cap loads from +16(AX) and the
|
||||
* base (AX = ptr) is read LAST so the earlier index loads still see the base.
|
||||
*
|
||||
* The byte-id gates (990-997) can't catch a symmetric 2-word miscompile: if
|
||||
* both stages drop cap identically, byte-id passes silently. So this pins the
|
||||
* *runtime* contract — build through both the cstage `ww` and wwstage `ww_ww`
|
||||
* driver and confirm the assertion holds (exit 0).
|
||||
*
|
||||
* DISCRIMINATION (heed the 933 lesson): a 2-word read leaves CX untouched, so
|
||||
* the row could coincidentally pass if CX happened to still carry the poison
|
||||
* cap. spoil() interposes a call between the field store and the chained read;
|
||||
* a call clobbers caller-saved CX (spoil's own str copy leaves cap=44 in CX),
|
||||
* so a broken 2-word read observes cap=44, not 8, and the row fails (return 1).
|
||||
* Verified fail-before (reverted fold: exit 1) / pass-after (exit 0), both
|
||||
* drivers.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.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; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* caseB — `o.p.f` chained read: o a struct holding p: *inr, inr
|
||||
* holding a str field f. Base AX = the inner *struct pointer; the
|
||||
* str field is read 3-word off AX (cap at +16). Poison cap=8
|
||||
* (len=2). spoil() interposes a CX-clobbering call between the
|
||||
* `ist.f = p` store and the chained `o.p.f` read so a broken
|
||||
* 2-word read cannot coincidentally pass on a stale CX. */
|
||||
{ "caseb_chained_field",
|
||||
"type inr = struct { f: str };\n"
|
||||
"type otr = struct { p: *inr };\n"
|
||||
"fn spoil() i32 = {\n"
|
||||
" let z: str = \"zzzz\";\n"
|
||||
" z.cap = 44i32;\n"
|
||||
" let w: str = z;\n"
|
||||
" return w.cap: i32;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let p: str = \"hi\";\n"
|
||||
" p.cap = 8i32;\n"
|
||||
" let ist: inr;\n"
|
||||
" ist.f = p;\n"
|
||||
" let o: otr;\n"
|
||||
" o.p = &ist;\n"
|
||||
" let junk: i32 = spoil();\n"
|
||||
" let s: str = o.p.f;\n"
|
||||
" if (s.cap: i32 != 8) { return 1; };\n"
|
||||
" if (s.len: i32 != 2) { return 2; };\n"
|
||||
" if (junk != 44) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[96], tmpdir[96], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/strchainfieldcap_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/strchainfieldcap_%d_d_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s",
|
||||
tmpdir, driver, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
unlink(src); rmdir(tmpdir);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[160];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
int got = runwait(outbin);
|
||||
|
||||
unlink(src); unlink(outbin); rmdir(tmpdir);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[640];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
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_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr,
|
||||
"str_chained_field_cap_run: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"str_chained_field_cap_run[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "str_chained_field_cap_run: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("str_chained_field_cap_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
/*
|
||||
* 935_str_tuple_elem_cap_run — runtime coverage for the C4.6-S3 fold: a
|
||||
* tuple POSITIONAL str element read `t.N` (N_IDENT base, TY_TUPLE) must load
|
||||
* the full 24B {ptr,len,cap} header, not just {ptr,len}. str is 24B since
|
||||
* Phase 2 (#1); pre-S3 the tuple str-element arm loaded 2 words (ptr in AX,
|
||||
* len in BX) and dropped cap.
|
||||
*
|
||||
* This is the "author-to-ABI" coda of C4.6: UNLIKE S1/S2/caseB there is no
|
||||
* adjacent slice-element arm to fold onto, so the 3-word triple is authored
|
||||
* directly to the canonical slice-header ABI (AX=ptr, BX=len, CX=cap). The
|
||||
* base is BP (the frame, not a target reg), so the canonical ptr/len/cap
|
||||
* order has no clobber subtlety. Sites: cgen.c's "tuple positional field
|
||||
* access" branch and the cgenexpr.ww N_TTUPLE twin.
|
||||
*
|
||||
* The byte-id gates (990-997) can't catch a symmetric 2-word miscompile: if
|
||||
* both stages drop cap identically, byte-id passes silently. So this pins the
|
||||
* *runtime* contract — build through both the cstage `ww` and wwstage `ww_ww`
|
||||
* driver and confirm the assertion holds (exit 0).
|
||||
*
|
||||
* The tuple is built by a (i64, str)-returning mk() so the poisoned cap=8
|
||||
* rides the 4-word return ABI (AX=scalar, DX=ptr, CX=len, R8=cap) into the
|
||||
* tuple slot's +24 word; `t.1` then reads it back 3-word (cap at slot+8+16).
|
||||
*
|
||||
* DISCRIMINATION (heed the 933/934 lesson): a 2-word read leaves CX untouched,
|
||||
* so the row could coincidentally pass on a stale CX. spoil() interposes a
|
||||
* call between the `let t = mk()` build and the `t.1` read; a call clobbers
|
||||
* caller-saved CX (spoil's own str copy leaves cap=44 in CX), so a broken
|
||||
* 2-word read observes cap=44, not 8, and the row fails (return 1). Verified
|
||||
* fail-before (reverted +16 cap load: exit 1) / pass-after (exit 0), both
|
||||
* drivers.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.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; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* S3 — `t.N` positional str element. t a local (i64, str) tuple
|
||||
* built by mk() so the poison cap=8 (len=2) rides the return ABI
|
||||
* into the str element's slot. spoil() interposes a CX-clobbering
|
||||
* call between the build and the `t.1` read so a broken 2-word read
|
||||
* cannot coincidentally pass on a stale CX. */
|
||||
{ "tuple_positional_str_elem",
|
||||
"fn mk() (i64, str) = {\n"
|
||||
" let p: str = \"hi\";\n"
|
||||
" p.cap = 8i32;\n"
|
||||
" return (5i64, p);\n"
|
||||
"};\n"
|
||||
"fn spoil() i32 = {\n"
|
||||
" let z: str = \"zzzz\";\n"
|
||||
" z.cap = 44i32;\n"
|
||||
" let w: str = z;\n"
|
||||
" return w.cap: i32;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let t: (i64, str) = mk();\n"
|
||||
" let junk: i32 = spoil();\n"
|
||||
" let s: str = t.1;\n"
|
||||
" if (s.cap: i32 != 8) { return 1; };\n"
|
||||
" if (s.len: i32 != 2) { return 2; };\n"
|
||||
" if (junk != 44) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[96], tmpdir[96], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/strtupleelemcap_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/strtupleelemcap_%d_d_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s",
|
||||
tmpdir, driver, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
unlink(src); rmdir(tmpdir);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[160];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
int got = runwait(outbin);
|
||||
|
||||
unlink(src); unlink(outbin); rmdir(tmpdir);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[640];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
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_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr,
|
||||
"str_tuple_elem_cap_run: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"str_tuple_elem_cap_run[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "str_tuple_elem_cap_run: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("str_tuple_elem_cap_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
/*
|
||||
* 936_str_arrfield_cap_run — runtime coverage for the C4.6-arrfield fold: a
|
||||
* str-typed FIELD of an INDEXED element `arr[i].f` (N_INDEX-rooted N_DOT) must
|
||||
* load the full 24B {ptr,len,cap} header, not just {ptr,len}. str is 24B since
|
||||
* Phase 2 (#1); pre-arrfield the `arr[i].f` str arm loaded 2 words (len in BX,
|
||||
* ptr in AX) and dropped cap.
|
||||
*
|
||||
* This is the LAST member of the 3-word-value-read cluster. UNLIKE the other
|
||||
* C4.6 arms there is no adjacent slice-element sibling at this leaf, so the
|
||||
* 3-word triple is authored directly to the canonical slice-header ABI. By the
|
||||
* leaf the element base is in AX, so the order is len->BX(foff+8),
|
||||
* cap->CX(foff+16), ptr->AX(foff+0) LAST — matching the proven in-tree oracle
|
||||
* cgslicehdr(D_AX) and the caseB chained-*struct slice arm. Sites: cgen.c's
|
||||
* "arr[i].field" branch (N_DOT, N_INDEX lhs) and the cgenexpr.ww cgdot twin.
|
||||
*
|
||||
* The byte-id gates (990-997) can't catch a symmetric 2-word miscompile: if
|
||||
* both stages drop cap identically, byte-id passes silently. So this pins the
|
||||
* *runtime* contract — build through both the cstage `ww` and wwstage `ww_ww`
|
||||
* driver and confirm the assertion holds (exit 0).
|
||||
*
|
||||
* Sub-cases (one leaf covers all; the differences — LEAQ vs MOVQ base, the
|
||||
* pointer-element deref — are upstream of the leaf):
|
||||
* A [N]S local array : `arr[i].f` (LEAQ base, value element).
|
||||
* B []S local slice : `sl[i].f` (MOVQ slice.ptr base, value element).
|
||||
* D [N]*S pointer-elem : `arr[i].f` (LEAQ base, MOVQ deref to the *S).
|
||||
*
|
||||
* POISONING: the store side of `arr[i].f = v` and whole-struct `arr[i] = st`
|
||||
* are SEPARATE, still-broken store-side gaps (filed; out of scope for this
|
||||
* read-side fold — they store only 1-2 words and drop cap). So the value
|
||||
* elements (A, B) are poisoned through `&arr[i]` + a *struct field write
|
||||
* (`let pr: *rec = &arr[i]; pr.f = p;` — both the &arr[i] index-address and
|
||||
* the chained pointer-field store are landed/working), which lands a real
|
||||
* cap into the element's +16 word. D points its element at a separately-built
|
||||
* struct (`st.f = p`, the proven s1local 3-word field store).
|
||||
*
|
||||
* DISCRIMINATION (heed the 933/934/935 lesson): a 2-word read leaves CX
|
||||
* holding whatever the index scale-multiply (`MOVQ $esz, CX; IMULQ`) left
|
||||
* there, never the poison. spoil() additionally interposes a CX-clobbering
|
||||
* call between the build and the `arr[i].f` read, so a broken 2-word read
|
||||
* observes spoil's leftover (44), never the poison cap. Verified fail-before
|
||||
* (stashed the +16 cap load: all rows exit 1 on both drivers) / pass-after
|
||||
* (exit 0), both drivers.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.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; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* A — `arr[i].f` value element of a [N]S local array (LEAQ base).
|
||||
* Poison cap=8 (len=2) via &arr[1] + a *struct field store. spoil()
|
||||
* clobbers CX between the build and the read so a broken 2-word read
|
||||
* cannot coincidentally pass on a stale CX. */
|
||||
{ "arrfield_array_value",
|
||||
"type rec = struct { f: str };\n"
|
||||
"fn spoil() i32 = {\n"
|
||||
" let z: str = \"zzzz\";\n"
|
||||
" z.cap = 44i32;\n"
|
||||
" let w: str = z;\n"
|
||||
" return w.cap: i32;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let p: str = \"hi\";\n"
|
||||
" p.cap = 8i32;\n"
|
||||
" let arr: [3]rec;\n"
|
||||
" let pr: *rec = &arr[1];\n"
|
||||
" pr.f = p;\n"
|
||||
" let junk: i32 = spoil();\n"
|
||||
" let s: str = arr[1].f;\n"
|
||||
" if (s.cap: i32 != 8) { return 1; };\n"
|
||||
" if (s.len: i32 != 2) { return 2; };\n"
|
||||
" if (junk != 44) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* B — `sl[i].f` value element of a []S local slice (MOVQ slice.ptr
|
||||
* base). The slice views the same poisoned backing array. Poison
|
||||
* cap=9 (len=5). */
|
||||
{ "arrfield_slice_value",
|
||||
"type rec = struct { f: str };\n"
|
||||
"fn spoil() i32 = {\n"
|
||||
" let z: str = \"zzzz\";\n"
|
||||
" z.cap = 44i32;\n"
|
||||
" let w: str = z;\n"
|
||||
" return w.cap: i32;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let p: str = \"world\";\n"
|
||||
" p.cap = 9i32;\n"
|
||||
" let arr: [3]rec;\n"
|
||||
" let pr: *rec = &arr[1];\n"
|
||||
" pr.f = p;\n"
|
||||
" let sl: []rec = arr[0:3];\n"
|
||||
" let junk: i32 = spoil();\n"
|
||||
" let s: str = sl[1].f;\n"
|
||||
" if (s.cap: i32 != 9) { return 1; };\n"
|
||||
" if (s.len: i32 != 5) { return 2; };\n"
|
||||
" if (junk != 44) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* D — `arr[i].f` pointer element of a [N]*S array (LEAQ base, MOVQ
|
||||
* deref to the *S, then the leaf field load). The element points at
|
||||
* a separately-built struct so the poison rides the proven s1local
|
||||
* 3-word field store, not the broken array-element store. Poison
|
||||
* cap=7 (len=3). */
|
||||
{ "arrfield_ptr_elem",
|
||||
"type rec = struct { f: str };\n"
|
||||
"fn spoil() i32 = {\n"
|
||||
" let z: str = \"zzzz\";\n"
|
||||
" z.cap = 44i32;\n"
|
||||
" let w: str = z;\n"
|
||||
" return w.cap: i32;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let p: str = \"abc\";\n"
|
||||
" p.cap = 7i32;\n"
|
||||
" let st: rec;\n"
|
||||
" st.f = p;\n"
|
||||
" let arr: [3]*rec;\n"
|
||||
" arr[1] = &st;\n"
|
||||
" let junk: i32 = spoil();\n"
|
||||
" let s: str = arr[1].f;\n"
|
||||
" if (s.cap: i32 != 7) { return 1; };\n"
|
||||
" if (s.len: i32 != 3) { return 2; };\n"
|
||||
" if (junk != 44) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[96], tmpdir[96], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/strarrfieldcap_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/strarrfieldcap_%d_d_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s",
|
||||
tmpdir, driver, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
unlink(src); rmdir(tmpdir);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[160];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
int got = runwait(outbin);
|
||||
|
||||
unlink(src); unlink(outbin); rmdir(tmpdir);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[640];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
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_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr,
|
||||
"str_arrfield_cap_run: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"str_arrfield_cap_run[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "str_arrfield_cap_run: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("str_arrfield_cap_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
/*
|
||||
* 937_str_arrfield_store_cap_run — runtime coverage for the G1 fold: STORING a
|
||||
* str into a FIELD of an INDEXED element `arr[i].f = v` must write the full 24B
|
||||
* {ptr,len,cap} header, not just {ptr,len}. str is 24B since Phase 2 (#1); the
|
||||
* write-side mirror of the landed arrfield READ (936) previously stored only 2
|
||||
* words (AX=ptr@foff+0, BX=len@foff+8) and silently DROPPED cap.
|
||||
*
|
||||
* This is only meaningful now that the arrfield READ is 3-word (936): before
|
||||
* that fix the store+read were 2-word-symmetric and cap was untouched at both
|
||||
* ends, so a dropped store-cap was invisible. Now the read returns the real
|
||||
* +16 word, so a 2-word store is observable.
|
||||
*
|
||||
* Sites: cgen.c's `arr[i].field = v` str branch (N_DOT lhs, N_INDEX lhs.lhs,
|
||||
* N_IDENT idxbase) and the cgenexpr.ww cgassign twin. The composed mechanic:
|
||||
* the rhs str leaves AX=ptr/BX=len/CX=cap, all three spilled across the
|
||||
* index/address computation (the index scale `MOVQ $esz, CX; IMULQ` would
|
||||
* clobber cap), the element address staged in DX off the str AX/BX/CX
|
||||
* convention (mirroring the s.f=v store), then the full triple stored at
|
||||
* foff+0/+8/+16.
|
||||
*
|
||||
* Rows mirror the 936 arrfield READ rows:
|
||||
* A [N]S local array : `arr[i].f = v` (LEAQ base, value element).
|
||||
* B []S local slice : `sl[i].f = v` (MOVQ slice.ptr base, value elem).
|
||||
* D [N]*S pointer-elem : `arr[i].f = v` (LEAQ base, MOVQ deref to the *S).
|
||||
*
|
||||
* RHS cap!=len: each test str is a literal whose .cap is mutated to a value
|
||||
* DISTINCT from its len (NOT a bare literal — literals carry cap==len, which
|
||||
* would hide a dropped cap; #12: global literal .cap reads 0). cap and len are
|
||||
* both nonzero and unequal so a 2-word store that drops cap is detectable.
|
||||
*
|
||||
* PRE-POISON (B != A, both nonzero, != len): before the G1 store under test,
|
||||
* all three slot words are seeded with a DIFFERENT str (ptr='q', len=4,
|
||||
* cap=5=A) via a PROVEN already-3-word store path — never the G1 store itself
|
||||
* (if G1 is broken its own poison write would also drop cap, leaving +16
|
||||
* uninit rather than a controlled poison). A/B poison via `&arr[i]` + a
|
||||
* *struct field store (`pr.f = q`); D poisons the pointee via the proven
|
||||
* s1local field store (`st.f = q`). The G1 store then writes the test str
|
||||
* (ptr='h', len=2, cap=8=B). A broken 2-word store never touches +16, so the
|
||||
* 3-word read-back observes the poison cap 5, never 8 — deterministic
|
||||
* discrimination with no reliance on a stale register.
|
||||
*
|
||||
* FULL-TRIPLE ASSERT: a register-reallocation slip in the 3-word store could
|
||||
* clobber ptr or len while wiring cap, and a cap-only assert would miss it. So
|
||||
* each row reads the value back (landed 3-word arrfield read) and checks all
|
||||
* three words: ptr (first byte through it — 'h'=104), len (2), cap (8). The
|
||||
* 2-word store writes ptr/len correctly (they ARE the two words it keeps), so
|
||||
* cap is the fail-before discriminator; ptr/len guard the fix.
|
||||
*
|
||||
* Verified fail-before (stashed the store edit on BOTH stages → all rows exit
|
||||
* 1, cap reads the poison 5) / pass-after (exit 0), both the cstage `ww` and
|
||||
* wwstage `ww_ww` drivers.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.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; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* A — `arr[i].f = v` into a [N]S local array (LEAQ base, value
|
||||
* element). Poison the slot (cap=5,len=4,'q') via &arr[1] + a
|
||||
* *struct field store; then the G1 store lands the test str
|
||||
* (cap=8,len=2,'h'). */
|
||||
{ "arrfield_store_array_value",
|
||||
"type rec = struct { f: str };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let q: str = \"qqqq\"; q.cap = 5i32;\n"
|
||||
" let p: str = \"hi\"; p.cap = 8i32;\n"
|
||||
" let arr: [3]rec;\n"
|
||||
" let pr: *rec = &arr[1];\n"
|
||||
" pr.f = q;\n"
|
||||
" arr[1].f = p;\n"
|
||||
" let s: str = arr[1].f;\n"
|
||||
" if (s.cap: i32 != 8) { return 1; };\n"
|
||||
" if (s.len: i32 != 2) { return 2; };\n"
|
||||
" if (s[0] != 104u8) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* B — `sl[i].f = v` into a []S local slice (MOVQ slice.ptr base,
|
||||
* value element). The slice views the same backing array; poison the
|
||||
* element through the array pointer, store the test str through the
|
||||
* slice, read it back through the slice. */
|
||||
{ "arrfield_store_slice_value",
|
||||
"type rec = struct { f: str };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let q: str = \"qqqq\"; q.cap = 5i32;\n"
|
||||
" let p: str = \"hi\"; p.cap = 8i32;\n"
|
||||
" let arr: [3]rec;\n"
|
||||
" let pr: *rec = &arr[1];\n"
|
||||
" pr.f = q;\n"
|
||||
" let sl: []rec = arr[0:3];\n"
|
||||
" sl[1].f = p;\n"
|
||||
" let s: str = sl[1].f;\n"
|
||||
" if (s.cap: i32 != 8) { return 1; };\n"
|
||||
" if (s.len: i32 != 2) { return 2; };\n"
|
||||
" if (s[0] != 104u8) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* D — `arr[i].f = v` into a [N]*S pointer element (LEAQ base, MOVQ
|
||||
* deref to the *S, then store at the field). The element points at a
|
||||
* separately-built struct poisoned via the proven s1local field store
|
||||
* (`st.f = q`); the G1 store derefs arr[1] and overwrites st.f. */
|
||||
{ "arrfield_store_ptr_elem",
|
||||
"type rec = struct { f: str };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let q: str = \"qqqq\"; q.cap = 5i32;\n"
|
||||
" let p: str = \"hi\"; p.cap = 8i32;\n"
|
||||
" let st: rec;\n"
|
||||
" st.f = q;\n"
|
||||
" let arr: [3]*rec;\n"
|
||||
" arr[1] = &st;\n"
|
||||
" arr[1].f = p;\n"
|
||||
" let s: str = arr[1].f;\n"
|
||||
" if (s.cap: i32 != 8) { return 1; };\n"
|
||||
" if (s.len: i32 != 2) { return 2; };\n"
|
||||
" if (s[0] != 104u8) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[96], tmpdir[96], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/strarrfieldstore_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/strarrfieldstore_%d_d_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s",
|
||||
tmpdir, driver, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
unlink(src); rmdir(tmpdir);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[160];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
int got = runwait(outbin);
|
||||
|
||||
unlink(src); unlink(outbin); rmdir(tmpdir);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[640];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
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_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr,
|
||||
"str_arrfield_store_cap_run: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"str_arrfield_store_cap_run[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "str_arrfield_store_cap_run: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("str_arrfield_store_cap_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
/*
|
||||
* 938_str_chainfield_store_cap_run — runtime coverage for the G2 fold: STORING a
|
||||
* str into a FIELD reached through a *struct-VALUED EXPRESSION (`r.sym.f = v`,
|
||||
* the chained-N_DOT store) must write the full 24B {ptr,len,cap} header, not
|
||||
* just {ptr,len}. str is 24B since Phase 2 (#1); the chained-store arm
|
||||
* previously stored only 2 words (AX=ptr@foff+0, BX=len@foff+8) and silently
|
||||
* DROPPED cap — the second STORE-cluster fold, after the arrfield store (937).
|
||||
*
|
||||
* This is the write-side mirror of the landed chained *struct str-field READ
|
||||
* (caseB, #11) and is only meaningful now that read is 3-word: before that the
|
||||
* store+read were 2-word-symmetric and cap was untouched at both ends, so a
|
||||
* dropped store-cap was invisible. Now the read returns the real +16 word, so a
|
||||
* 2-word store is observable.
|
||||
*
|
||||
* Sites: cgen.c's chained-N_DOT `<expr>.field = v` str branch (n->lhs is N_DOT,
|
||||
* n->lhs->lhs is N_DOT/non-IDENT evaluating to a *struct) and the cgenexpr.ww
|
||||
* cgassign twin (strict gate: chain root is a LOCAL ident, every dot resolves
|
||||
* through a *struct). The mechanic is a direct transfer of G1 (arrfield store):
|
||||
* the rhs str leaves AX=ptr/BX=len/CX=cap, all three spilled (PUSHQ CX/BX/AX)
|
||||
* across the base-expr eval (which may clobber any register), the resolved
|
||||
* *struct ptr staged in DX off the str AX/BX/CX convention (mirroring the
|
||||
* s.f=v store), then the full triple stored at foff+0/+8/+16. The base here is
|
||||
* a single-word *struct pointer, so it does not contend for the spilled slots.
|
||||
*
|
||||
* Rows exercise the CHAINED arm at two depths (a direct-field `s.f=` probe would
|
||||
* MISS this arm and test the already-3-word IDENT-base path):
|
||||
* A depth-2 `r.sym.f = v` (r: outer, outer.sym: *inner, inner.f: str).
|
||||
* B depth-3 `r.a.b.f = v` (two pointer hops; deeper base eval clobbers
|
||||
* more registers, stressing the spill).
|
||||
*
|
||||
* RHS cap!=len: each test str is a literal whose .cap is mutated to a value
|
||||
* DISTINCT from its len (NOT a bare literal — literals carry cap==len, which
|
||||
* would hide a dropped cap; a str sub-slice was rejected too: ww yields cap==len
|
||||
* for `buf[0:2]`, equally hiding the drop). cap=8 and len=2 are both nonzero and
|
||||
* unequal so a 2-word store that drops cap is detectable.
|
||||
*
|
||||
* PRE-POISON (cap=5 != 8, both nonzero, != len): before the G2 store under test,
|
||||
* all three slot words are seeded with a DIFFERENT str (ptr='q', len=4, cap=5)
|
||||
* via the PROVEN already-3-word DIRECT field store (`st.f = q`, the s.f=v
|
||||
* oracle, N_IDENT base) — never the G2 chained store itself (if G2 is broken its
|
||||
* own poison write would also drop cap, leaving +16 uninit rather than a
|
||||
* controlled poison). The pointee is a local struct; the chain's leaf points at
|
||||
* it, so the G2 store overwrites the poisoned field. The G2 store then writes
|
||||
* the test str (ptr='h', len=2, cap=8). A broken 2-word store never touches +16,
|
||||
* so the 3-word read-back observes the poison cap 5, never 8 — deterministic
|
||||
* discrimination with no reliance on a stale register.
|
||||
*
|
||||
* FULL-TRIPLE ASSERT: a register-reallocation slip in the 3-word store could
|
||||
* clobber ptr or len while wiring cap, and a cap-only assert would miss it. So
|
||||
* each row reads the value back (landed 3-word chained read, caseB) and checks
|
||||
* all three words: ptr (first byte through it — 'h'=104), len (2), cap (8). The
|
||||
* 2-word store writes ptr/len correctly (they ARE the two words it keeps), so
|
||||
* cap is the fail-before discriminator; ptr/len guard the fix.
|
||||
*
|
||||
* Verified fail-before (stashed the store edit on BOTH stages → all rows exit 1,
|
||||
* cap reads the poison 5) / pass-after (exit 0), both the cstage `ww` and
|
||||
* wwstage `ww_ww` drivers.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.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; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* A — depth-2 `r.sym.f = v`. r is a local outer; r.sym is a *inner
|
||||
* pointing at a local struct st. Poison st.f (cap=5,len=4,'q') via the
|
||||
* DIRECT s.f=v 3-word store; then the G2 chained store lands the test
|
||||
* str (cap=8,len=2,'h') through r.sym. */
|
||||
{ "chainfield_store_depth2",
|
||||
"type inner = struct { f: str };\n"
|
||||
"type outer = struct { sym: *inner };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let q: str = \"qqqq\"; q.cap = 5i32;\n"
|
||||
" let p: str = \"hi\"; p.cap = 8i32;\n"
|
||||
" let st: inner;\n"
|
||||
" st.f = q;\n"
|
||||
" let r: outer;\n"
|
||||
" r.sym = &st;\n"
|
||||
" r.sym.f = p;\n"
|
||||
" let s: str = r.sym.f;\n"
|
||||
" if (s.cap: i32 != 8) { return 1; };\n"
|
||||
" if (s.len: i32 != 2) { return 2; };\n"
|
||||
" if (s[0] != 104u8) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* B — depth-3 `r.a.b.f = v`. Two pointer hops (r.a: *mid, mid.b:
|
||||
* *inner). Poison the leaf via the DIRECT store (`leaf.f = q`); the G2
|
||||
* chained store derefs r.a then .b and overwrites leaf.f. The deeper
|
||||
* base eval clobbers more registers, confirming the spilled triple
|
||||
* survives an arbitrary base-formation. */
|
||||
{ "chainfield_store_depth3",
|
||||
"type inner = struct { f: str };\n"
|
||||
"type mid = struct { b: *inner };\n"
|
||||
"type outer = struct { a: *mid };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let q: str = \"qqqq\"; q.cap = 5i32;\n"
|
||||
" let p: str = \"hi\"; p.cap = 8i32;\n"
|
||||
" let leaf: inner;\n"
|
||||
" leaf.f = q;\n"
|
||||
" let m: mid;\n"
|
||||
" m.b = &leaf;\n"
|
||||
" let r: outer;\n"
|
||||
" r.a = &m;\n"
|
||||
" r.a.b.f = p;\n"
|
||||
" let s: str = r.a.b.f;\n"
|
||||
" if (s.cap: i32 != 8) { return 1; };\n"
|
||||
" if (s.len: i32 != 2) { return 2; };\n"
|
||||
" if (s[0] != 104u8) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[96], tmpdir[96], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/strchainfieldstore_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/strchainfieldstore_%d_d_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s",
|
||||
tmpdir, driver, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
unlink(src); rmdir(tmpdir);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[160];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
int got = runwait(outbin);
|
||||
|
||||
unlink(src); unlink(outbin); rmdir(tmpdir);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[640];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
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_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr,
|
||||
"str_chainfield_store_cap_run: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"str_chainfield_store_cap_run[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "str_chainfield_store_cap_run: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("str_chainfield_store_cap_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
/*
|
||||
* 939_str_massign_store_cap_run — runtime coverage for the G3 fold: a
|
||||
* tuple-destructure REASSIGNMENT `a, s = call()` (N_MASSIGN) whose str element
|
||||
* must write the full 24B {ptr,len,cap} header into the (already 24B) str slot,
|
||||
* not just {ptr}. str is 24B since Phase 2 (#1); the N_MASSIGN arm previously
|
||||
* stored the str element with the bare scalar path (PUSHQ DX; ...; MOVQ DX,
|
||||
* slot) — one word, dropping len/cap. This is the third and final STORE-cluster
|
||||
* fold (after arrfield 937 and chainfield 938), closing the store cluster on
|
||||
* the tuple-destructure REASSIGN shape.
|
||||
*
|
||||
* N_MASSIGN vs N_MLET: `let a, s = call()` is N_MLET (fresh bindings, slots
|
||||
* sized from each binding's type) and ALREADY destructures 3-word. `a, s =
|
||||
* call()` with a, s PRE-DECLARED is N_MASSIGN (reassignment, slots already
|
||||
* exist). The fix mirrors N_MLET's (DX,CX,R8)->(.ptr,.len,.cap) routing at the
|
||||
* N_MASSIGN arm but localfinds the existing slot instead of allocating it.
|
||||
* The fixture MUST pre-declare a and s then reassign WITHOUT `let`, or the
|
||||
* parser emits N_MLET and this arm is never reached.
|
||||
*
|
||||
* Sites: cgen.c's N_MASSIGN case (str-XOR branch, l0/l1 N_IDENT, str-ness from
|
||||
* the checker-stamped l->type) and the cgenstmt.ww cgmassign twin (wwstage has
|
||||
* no checker, so str-ness comes from the called fn's return-type tuple element,
|
||||
* exactly as cgmlet derives it). ONE-STR ONLY, matching N_MLET's coverage; the
|
||||
* two-str destructure is a shared N_MLET/N_MASSIGN gap (task #22).
|
||||
*
|
||||
* Rows exercise BOTH XOR positions (the routing is position-agnostic, as in
|
||||
* N_MLET):
|
||||
* A `a, s = mk()` mk() returns (i64, str) — str is the 2nd element (l1).
|
||||
* B `s, a = mk2()` mk2() returns (str, i64) — str is the 1st element (l0).
|
||||
*
|
||||
* RHS cap!=len: the returned str is a literal whose .cap is MUTATED to a value
|
||||
* DISTINCT from its len (NOT a bare literal — literals carry cap==len, which
|
||||
* would hide a dropped cap; a `buf[lo:hi]` sub-slice was rejected too: ww
|
||||
* yields cap==len for it, task #20, equally hiding the drop). cap=8 and len=2
|
||||
* are both nonzero and unequal so a 1-word store that drops cap is detectable.
|
||||
*
|
||||
* PRE-POISON (cap=5 != 8, len=4 != 2, both nonzero): before the G3 store under
|
||||
* test, all three slot words of `s` are seeded with a DIFFERENT str (ptr='q',
|
||||
* len=4, cap=5) via `let s: str = q` — a PROVEN already-3-word let-init copy
|
||||
* (the same path 935's spoil() relies on), NOT the G3 store itself (if G3 is
|
||||
* broken its own poison write would also drop cap, leaving uninit words rather
|
||||
* than a controlled poison). The let-init both DECLARES s (so the subsequent
|
||||
* comma-assign is a reassignment = N_MASSIGN) and poisons its slot. A broken
|
||||
* 1-word G3 store writes only s.ptr and never touches +8/+16, so the 3-word
|
||||
* read-back observes the poison len 4 / cap 5, never 2 / 8 — deterministic
|
||||
* discrimination with no reliance on a stale register.
|
||||
*
|
||||
* FULL-TRIPLE ASSERT: a register-reallocation slip in the 3-word store could
|
||||
* clobber ptr while wiring len/cap, so each row reads s back (landed 3-word
|
||||
* tuple/let reads) and checks ptr (first byte through it — 'h'=104), len (2),
|
||||
* cap (8), plus the scalar side a (5) — confirming the XOR branch wires the
|
||||
* scalar slot too. The broken 1-word store writes ptr correctly (it IS the one
|
||||
* word it keeps), so len AND cap are the fail-before discriminators; ptr and a
|
||||
* guard the fix.
|
||||
*
|
||||
* Verified fail-before (reverted the store edit on BOTH stages → all rows exit
|
||||
* 1, cap reads the poison 5) / pass-after (exit 0), both the cstage `ww` and
|
||||
* wwstage `ww_ww` drivers. Confirmed via emitted asm the fixture hits the
|
||||
* N_MASSIGN arm (CALL ...; MOVQ {AX,DX,CX,R8} -> slots), not N_MLET.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.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; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* A — `a, s = mk()`, str the 2nd tuple element (l1). a and s are
|
||||
* pre-declared (so the comma-assign is N_MASSIGN, not N_MLET); s is
|
||||
* poisoned (cap=5,len=4,'q') by its let-init copy. The G3 store then
|
||||
* lands the test str (cap=8,len=2,'h') via (DX,CX,R8)->s, AX->a. */
|
||||
{ "massign_store_str_pos1",
|
||||
"fn mk() (i64, str) = {\n"
|
||||
" let p: str = \"hi\"; p.cap = 8i32;\n"
|
||||
" return (5i64, p);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let q: str = \"qqqq\"; q.cap = 5i32;\n"
|
||||
" let a: i64 = 7i64;\n"
|
||||
" let s: str = q;\n"
|
||||
" a, s = mk();\n"
|
||||
" if (s.cap: i32 != 8) { return 1; };\n"
|
||||
" if (s.len: i32 != 2) { return 2; };\n"
|
||||
" if (s[0] != 104u8) { return 3; };\n"
|
||||
" if (a != 5) { return 4; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* B — `s, a = mk2()`, str the 1st tuple element (l0). Same poison and
|
||||
* test values; exercises the s0_is_str routing (DX,CX,R8)->s, AX->a
|
||||
* from the other XOR side. */
|
||||
{ "massign_store_str_pos0",
|
||||
"fn mk2() (str, i64) = {\n"
|
||||
" let p: str = \"hi\"; p.cap = 8i32;\n"
|
||||
" return (p, 5i64);\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let q: str = \"qqqq\"; q.cap = 5i32;\n"
|
||||
" let s: str = q;\n"
|
||||
" let a: i64 = 7i64;\n"
|
||||
" s, a = mk2();\n"
|
||||
" if (s.cap: i32 != 8) { return 1; };\n"
|
||||
" if (s.len: i32 != 2) { return 2; };\n"
|
||||
" if (s[0] != 104u8) { return 3; };\n"
|
||||
" if (a != 5) { return 4; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[96], tmpdir[96], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/strmassignstore_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/strmassignstore_%d_d_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s",
|
||||
tmpdir, driver, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
unlink(src); rmdir(tmpdir);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[160];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
int got = runwait(outbin);
|
||||
|
||||
unlink(src); unlink(outbin); rmdir(tmpdir);
|
||||
return got;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[512];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[256];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char cdrv[640];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[640];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
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_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr,
|
||||
"str_massign_store_cap_run: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"str_massign_store_cap_run[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "str_massign_store_cap_run: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("str_massign_store_cap_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* 957_size_type_run — runtime proof that the `size` type name is usable
|
||||
* in TYPE position (#85 fold-2) and that it coexists with the `size(T)`
|
||||
* size-of OPERATOR. The two are different paths — TYPE-position `size`
|
||||
* resolves via the type-name resolver (lookup_builtin / tinfofornode's
|
||||
* N_TNAME chain), while `size(T)` is a c.top SK_FN seed folded to an
|
||||
* N_INTLIT — so a program may use BOTH in the same scope with no clash.
|
||||
* Each row below exercises that coexistence; without the fold-2 resolver
|
||||
* arm the `let _: size` rows fail to compile ("unknown type 'size'"), so
|
||||
* a green row IS the proof the name binds.
|
||||
*
|
||||
* `size` mirrors uintptr: platform-width (8B amd64) unsigned int. The
|
||||
* rows assert it is usable in arithmetic, comparison, and an i32 cast.
|
||||
*
|
||||
* Table-driven like 952_floats_run: each row is a self-contained ww
|
||||
* program; the C-side cstage `ww build -I lib` compiles it, we run the
|
||||
* binary and assert the exit code. cstage-only by design (mirrors 951/
|
||||
* 952/969): `ww_ww run` is broken (#95) and per-program wwstage byte-id
|
||||
* is the 990-997 gates' job; the wwstage resolver arm is twinned for
|
||||
* rule-10 symmetry and stays dead on the size-free selfhost corpus.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.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[] = {
|
||||
/* type-position `size` + the size(T) operator in one scope. x is a
|
||||
* size-typed local; size(int)/size(i32) are folded operator results.
|
||||
* Both must resolve and agree (8/4 on amd64). */
|
||||
{ "package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let x: size = 0;\n"
|
||||
" if (x != 0) { return 1; };\n"
|
||||
" let y: size = 42;\n"
|
||||
" if (y != 42) { return 2; };\n"
|
||||
" if (size(int) != 8) { return 3; };\n"
|
||||
" if (size(i32) != 4) { return 4; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0 },
|
||||
/* size arithmetic + i32 cast value propagation: a size-typed sum
|
||||
* truncates to i32 and propagates (40 + 2 == 42). */
|
||||
{ "package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let x: size = 40;\n"
|
||||
" let y: size = 2;\n"
|
||||
" return (x + y): i32;\n"
|
||||
"};\n", 42 },
|
||||
/* size as a struct field type: 8-byte slot, written and read back. */
|
||||
{ "package main;\n"
|
||||
"type box = struct { n: size };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let b: box = box{ n = 7 };\n"
|
||||
" return b.n: i32;\n"
|
||||
"};\n", 7 },
|
||||
/* the size(T) operator value-propagated through an i32 cast (operator
|
||||
* path, must keep working alongside the new type-name arm). */
|
||||
{ "package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" return size(int): i32;\n"
|
||||
"};\n", 8 },
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
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 src[64];
|
||||
snprintf(src, sizeof src, "/tmp/wwsz_%d_%d.ww", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (f == NULL) { fail++; continue; }
|
||||
fputs(rows[i].src, f);
|
||||
fclose(f);
|
||||
|
||||
char tmpdir[64];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwsz_%d_d_%d", getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
|
||||
char cmd[2048];
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s/ww build -I %s/lib %s",
|
||||
tmpdir, bin, cwd, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row %d: build failed\n src: %s\n",
|
||||
i, rows[i].src);
|
||||
fail++;
|
||||
unlink(src); rmdir(tmpdir);
|
||||
continue;
|
||||
}
|
||||
|
||||
char outbin[128];
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
|
||||
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++;
|
||||
}
|
||||
unlink(src); unlink(outbin); rmdir(tmpdir);
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr, "%d/%d size_type tests failed\n", fail, n);
|
||||
return 1;
|
||||
}
|
||||
printf("size_type: %d/%d ok\n", n, n);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
/*
|
||||
* 989_chainidx_run — F7-c3 (#22): a CHAINED index `m[i][k]` whose element
|
||||
* is a str/slice must load the full 24B/16B header, both stages.
|
||||
*
|
||||
* THE BUG (cat-A silent miscompile, gate-blind): cgindex
|
||||
* (selfhost/cmd/wcc/cgenexpr.ww) has a dedicated arm for a chained index
|
||||
* (the outer index's base is itself an N_INDEX). That arm read the
|
||||
* checker-stamped element tinfo for esz/signedness/float-ness but NOT for
|
||||
* elemisstr/elemisslice — so a chained index over [N][M]str / [N][M][]T
|
||||
* loaded only the .ptr word and left .len/.cap as stale BX/CX. cstage's
|
||||
* idx_eff path classifies the element uniformly (type_isstr/type_isslice),
|
||||
* so it loaded the full header and ran correct — the cat-A divergence.
|
||||
* 990-997 stay green because the bootstrap corpus never chains an index
|
||||
* to a str/slice element; only a runtime row catches it. THE FIX: the
|
||||
* chained-index arm stamps elemisstr/elemisslice off the SAME element
|
||||
* tinfo it already reads for esz, aligning wwstage UP.
|
||||
*
|
||||
* NB: the [N][M] arrays are built by per-element store — the nested array
|
||||
* literal `[[..],[..]]` is independently #270-1c-blocked (orthogonal).
|
||||
*
|
||||
* Rows (each builds+runs on cstage `ww` and, when present, wwstage `ww_ww`;
|
||||
* rule-10 — both stages must agree AND hit want_exit):
|
||||
* row | shape | want
|
||||
* -------------------+------------------------------------+------
|
||||
* chain_str_read | let s = m[1][1]; len(s) m:[2][2]str | 5 [#22 bug]
|
||||
* chain_str_call | take(m[1][1]) m:[2][2]str | 5 [#22+#46:
|
||||
* | needs the c2 push AND this c3 read]
|
||||
* chain_slice_read | let xs = m[1][1]; len(xs) m:[2][2][]int | 7 [#22 bug]
|
||||
* chain_scalar_read | m[1][1] m:[2][2]int | 42 (control:
|
||||
* | the scalar chained index the arm already handled —
|
||||
* | c3 must not regress its byte-id)
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.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[] = {
|
||||
/* (1) #22 — chained str element READ. m[1][1] = "ddddd", len 5. The
|
||||
* chained-index arm dropped the .len load → garbage. */
|
||||
{ "chain_str_read",
|
||||
"package main;\n"
|
||||
"export fn main() int = {\n"
|
||||
" let m: [2][2]str;\n"
|
||||
" m[0][0] = \"aa\"; m[0][1] = \"bbb\";\n"
|
||||
" m[1][0] = \"c\"; m[1][1] = \"ddddd\";\n"
|
||||
" let s: str = m[1][1];\n"
|
||||
" return len(s): int;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
|
||||
/* (2) #22+#46 — chained str element as a CALL ARG. Needs both the c2
|
||||
* push-side recognizer (nodeisstr N_INDEX arm) AND this c3 read-side
|
||||
* header load; pinned here where both halves are present. len 5. */
|
||||
{ "chain_str_call",
|
||||
"package main;\n"
|
||||
"fn take(s: str) int = { return len(s): int; };\n"
|
||||
"export fn main() int = {\n"
|
||||
" let m: [2][2]str;\n"
|
||||
" m[0][0] = \"aa\"; m[0][1] = \"bbb\";\n"
|
||||
" m[1][0] = \"c\"; m[1][1] = \"ddddd\";\n"
|
||||
" return take(m[1][1]);\n"
|
||||
"};\n",
|
||||
5 },
|
||||
|
||||
/* (3) #22 — chained slice element READ. m[1][1] = b (len 7). The arm
|
||||
* dropped the .len/.cap load for the 24B slice header. */
|
||||
{ "chain_slice_read",
|
||||
"package main;\n"
|
||||
"export fn main() int = {\n"
|
||||
" let a: []int = [1, 2];\n"
|
||||
" let b: []int = [9, 9, 9, 9, 9, 9, 9];\n"
|
||||
" let m: [2][2][]int;\n"
|
||||
" m[0][0] = a; m[0][1] = a;\n"
|
||||
" m[1][0] = a; m[1][1] = b;\n"
|
||||
" let xs: []int = m[1][1];\n"
|
||||
" return len(xs): int;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
|
||||
/* (4) control — chained SCALAR element. The arm already handled the
|
||||
* scalar esz; c3 must not regress its byte-id. m[1][1] == 42. */
|
||||
{ "chain_scalar_read",
|
||||
"package main;\n"
|
||||
"export fn main() int = {\n"
|
||||
" let m: [2][2]int;\n"
|
||||
" m[0][0] = 1; m[0][1] = 2;\n"
|
||||
" m[1][0] = 3; m[1][1] = 42;\n"
|
||||
" return m[1][1]: int;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
};
|
||||
|
||||
/* 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[64], tmpdir[64], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/chainidx_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/chainidx_%d_d_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -2;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null",
|
||||
tmpdir, driver, src);
|
||||
int brc = runwait(cmd);
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[128];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
|
||||
int got = -1;
|
||||
if (brc == 0) got = runwait(outbin);
|
||||
|
||||
unlink(src); unlink(outbin); rmdir(tmpdir);
|
||||
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, "chainidx_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, "chainidx_run[%s][%s]: exit=%d "
|
||||
"want=%d\n", drivers[d].name, rows[i].label,
|
||||
got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "chainidx_run: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("chainidx_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
/*
|
||||
* 989_gunsigned_run — F7-c5 (#25): a module-GLOBAL unsigned ident on the
|
||||
* divide / shift / relational path must pick the UNSIGNED opcode.
|
||||
*
|
||||
* THE BUG (cat-A silent miscompile, gate-blind): nodeisunsigned
|
||||
* (selfhost/cmd/wcc/cgenutil.ww) read the LOCAL's declared tnode and
|
||||
* returned `false` (signed) for a module-global ident (localfindnode→nil
|
||||
* → fell through to `return false`). So a `u64` global counter fed to
|
||||
* `/ % >> >= >` got the signed opcode — IDIVQ/CQO, SARQ, JG/JGE — instead
|
||||
* of the unsigned DIVQ, SHRQ, JA/JAE cstage emits (type_isunsigned reads
|
||||
* the stamped n->type, cmd/w6c/cgen.c:2541). For a high-bit-set u64
|
||||
* global the two diverge at runtime (cs≠ww — the cat-A signature).
|
||||
* THE FIX: the N_IDENT arm collapses onto the checker-stamped n.type_
|
||||
* (the same stamp the N_DOT/N_CAST/N_INDEX/N_CALL arms already read),
|
||||
* aligning wwstage UP — the global's unsigned stamp now flows.
|
||||
*
|
||||
* CLASS-M: unlike c1-c4 this changes emission on a shape the bootstrap
|
||||
* corpus DOES hit (module-global unsigned counters on the divide/shift
|
||||
* path), so the self-compile .s MOVES; the bind verifies every move is
|
||||
* toward cstage (IDIV→DIV) and runtime-correct.
|
||||
*
|
||||
* Rows (cstage `ww` + gated wwstage `ww_ww`; rule-10 + absolute value):
|
||||
* row | shape | want
|
||||
* -----------------+-----------------------------+------
|
||||
* global_ushr | g:u64=1<<63; g >> 1 | 0 [bug: SAR]
|
||||
* global_udiv | g:u64=1<<63; g / (1<<62) | 2 [bug: IDIV]
|
||||
* global_ucmp | g:u64=1<<63; g > 1 | 7 [bug: JG]
|
||||
* signed_global_ctl| s:i64=-8; s >> 1 | 252 (control: a SIGNED
|
||||
* | global must STAY SARQ — c5 must not over-convert)
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.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[] = {
|
||||
/* (1) #25 — u64 global >> 1: unsigned SHRQ (cs) vs signed SARQ (bug).
|
||||
* 0x8000000000000000 >> 1 == 0x4000000000000000 unsigned → return 0. */
|
||||
{ "global_ushr",
|
||||
"package main;\n"
|
||||
"let g: u64 = 0;\n"
|
||||
"export fn main() int = {\n"
|
||||
" g = 9223372036854775808u64;\n"
|
||||
" let r: u64 = g >> 1;\n"
|
||||
" if (r == 4611686018427387904u64) { return 0; };\n"
|
||||
" return 1;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
/* (2) #25 — u64 global / (1<<62): unsigned DIVQ (cs) vs signed IDIVQ.
|
||||
* (1<<63) / (1<<62) == 2 unsigned; signed reads g as negative. */
|
||||
{ "global_udiv",
|
||||
"package main;\n"
|
||||
"let g: u64 = 0;\n"
|
||||
"export fn main() int = {\n"
|
||||
" g = 9223372036854775808u64;\n"
|
||||
" let r: u64 = g / 4611686018427387904u64;\n"
|
||||
" return r: int;\n"
|
||||
"};\n",
|
||||
2 },
|
||||
|
||||
/* (3) #25 — u64 global > 1: JA (cs) vs JG (bug). 1<<63 > 1 is true
|
||||
* unsigned, false signed (1<<63 is negative as i64). */
|
||||
{ "global_ucmp",
|
||||
"package main;\n"
|
||||
"let g: u64 = 0;\n"
|
||||
"export fn main() int = {\n"
|
||||
" g = 9223372036854775808u64;\n"
|
||||
" if (g > 1u64) { return 7; };\n"
|
||||
" return 9;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
|
||||
/* (4) control — a SIGNED i64 global must keep the signed shift (SARQ):
|
||||
* -8 >> 1 == -4 (exit byte 252). Pins that c5 reads the stamp, not a
|
||||
* blanket "global → unsigned" — no over-conversion. */
|
||||
{ "signed_global_ctl",
|
||||
"package main;\n"
|
||||
"let s: i64 = 0;\n"
|
||||
"export fn main() int = {\n"
|
||||
" s = -8;\n"
|
||||
" let r: i64 = s >> 1;\n"
|
||||
" return r: int;\n"
|
||||
"};\n",
|
||||
252 },
|
||||
};
|
||||
|
||||
/* 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[64], tmpdir[64], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/gun_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/gun_%d_d_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -2;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null",
|
||||
tmpdir, driver, src);
|
||||
int brc = runwait(cmd);
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[128];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
|
||||
int got = -1;
|
||||
if (brc == 0) got = runwait(outbin);
|
||||
|
||||
unlink(src); unlink(outbin); rmdir(tmpdir);
|
||||
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, "gunsigned_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, "gunsigned_run[%s][%s]: exit=%d "
|
||||
"want=%d\n", drivers[d].name, rows[i].label,
|
||||
got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "gunsigned_run: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("gunsigned_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
/*
|
||||
* 989_idxarg_run — F7-c2 (#45/#46): an INDEXED slice/str element passed as
|
||||
* a call argument must push its full multi-word header, both stages.
|
||||
*
|
||||
* THE BUG (cat-A silent miscompile, gate-blind): pushargsrev sizes a
|
||||
* call arg via nodeisslice/nodeisstr (selfhost/cmd/wcc/cgenutil.ww). A
|
||||
* str arg takes 2 slots (ptr+len), a slice 3 (ptr+len+cap); a scalar
|
||||
* takes 1. Pre-fix:
|
||||
* - nodeisslice had NO N_INDEX arm (#45) → `take(rows[i])` where the
|
||||
* element is a slice fell to the scalar default: one PUSHQ AX, the
|
||||
* callee read .len/.cap from stack residue (garbage len).
|
||||
* - nodeisstr's N_INDEX arm was a base-kind whitelist that only knew
|
||||
* N_IDENT and N_DOT bases (#46) → a chained index `take(m[1][1])`
|
||||
* (the outer index's base is itself an N_INDEX) fell through to
|
||||
* `return false` → 1-word push, garbage .len.
|
||||
* cstage is type-keyed (cmd/w6c/cgen.c node_isslice/node_isstr =
|
||||
* type_isXXX(n->type)), so it pushed the full header and ran correct —
|
||||
* the divergence is the cat-A signature. 990-997 stay green because the
|
||||
* bootstrap corpus never feeds an indexed slice/str element as a call
|
||||
* arg, so this value miscompile is invisible to byte-id; only a runtime
|
||||
* row catches it. THE FIX: both N_INDEX arms read the checker-stamped
|
||||
* element type n.type_ (exprtype N_INDEX, check.ww), aligning wwstage UP.
|
||||
*
|
||||
* Rows (every row builds+runs on cstage `ww` and, when present, wwstage
|
||||
* `ww_ww`; rule-10 — both stages must agree AND hit want_exit):
|
||||
* row | shape | want
|
||||
* -------------------+------------------------------------+------
|
||||
* slice_elem_arg | take(rows[1]) rows:[2][]int | 2 [#45 bug]
|
||||
* call_str_elem | take(getarr()[1]) getarr()->[]str | 5 [#46 bug:
|
||||
* | the index's base is an N_CALL — non-ident/non-dot,
|
||||
* | the shape the old base-kind whitelist missed. The
|
||||
* | slice-base READ already loads the header, so this
|
||||
* | isolates the PUSH-side fix (c2) cleanly.]
|
||||
* ident_str_elem | take(arr[1]) arr:[3]str | 5 (#46 control:
|
||||
* | the N_IDENT-base case the old whitelist DID handle —
|
||||
* | must still work through the stamp read)
|
||||
* slice_local_arg | take(xs) xs:[]int | 3 (control: the
|
||||
* | N_IDENT-local arm, unchanged since c1)
|
||||
*
|
||||
* The chained-index `take(m[1][1])` shape (an N_INDEX base) is covered by
|
||||
* the c3 sibling test (989_chainidx_run.c): its push side is fixed here,
|
||||
* but it ALSO needs the cgindex read-side header load (#22/c3), so it is
|
||||
* pinned where both halves are present.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.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[] = {
|
||||
/* (1) #45 — slice element `rows[1]` (a []int) as a call arg. Pre-fix
|
||||
* nodeisslice had no N_INDEX arm → 1-word push, callee read garbage
|
||||
* len. len(rows[1]) == len(b) == 2. */
|
||||
{ "slice_elem_arg",
|
||||
"package main;\n"
|
||||
"fn take(xs: []int) int = { return len(xs): int; };\n"
|
||||
"export fn main() int = {\n"
|
||||
" let a: []int = [10, 20, 30, 40];\n"
|
||||
" let b: []int = [1, 2];\n"
|
||||
" let rows: [2][]int = [a, b];\n"
|
||||
" return take(rows[1]);\n"
|
||||
"};\n",
|
||||
2 },
|
||||
|
||||
/* (2) #46 — index whose base is an N_CALL (`getarr()[1]`): a non-ident/
|
||||
* non-dot base the old base-kind whitelist missed. The slice-base READ
|
||||
* already loads the (ptr,len) header, so this isolates the PUSH-side
|
||||
* fix (nodeisstr N_INDEX arm). len("ddddd") == 5. */
|
||||
{ "call_str_elem",
|
||||
"package main;\n"
|
||||
"fn getarr() []str = {\n"
|
||||
" let a: []str = [\"x\", \"ddddd\", \"zz\"];\n"
|
||||
" return a;\n"
|
||||
"};\n"
|
||||
"fn take(s: str) int = { return len(s): int; };\n"
|
||||
"export fn main() int = { return take(getarr()[1]); };\n",
|
||||
5 },
|
||||
|
||||
/* (3) #46 control — `arr[1]` with an N_IDENT base (the case the old
|
||||
* whitelist DID handle): the stamp read must not regress it. */
|
||||
{ "ident_str_elem",
|
||||
"package main;\n"
|
||||
"fn take(s: str) int = { return len(s): int; };\n"
|
||||
"export fn main() int = {\n"
|
||||
" let arr: [3]str = [\"x\", \"ddddd\", \"zz\"];\n"
|
||||
" return take(arr[1]);\n"
|
||||
"};\n",
|
||||
5 },
|
||||
|
||||
/* (4) control — a plain str/slice local arg (the c1 N_IDENT-local
|
||||
* arm): pins that c2 leaves the non-indexed path alone. */
|
||||
{ "slice_local_arg",
|
||||
"package main;\n"
|
||||
"fn take(xs: []int) int = { return len(xs): int; };\n"
|
||||
"export fn main() int = {\n"
|
||||
" let xs: []int = [7, 8, 9];\n"
|
||||
" return take(xs);\n"
|
||||
"};\n",
|
||||
3 },
|
||||
};
|
||||
|
||||
/* 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[64], tmpdir[64], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/idxarg_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/idxarg_%d_d_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -2;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null",
|
||||
tmpdir, driver, src);
|
||||
int brc = runwait(cmd);
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[128];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
|
||||
int got = -1;
|
||||
if (brc == 0) got = runwait(outbin);
|
||||
|
||||
unlink(src); unlink(outbin); rmdir(tmpdir);
|
||||
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, "idxarg_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, "idxarg_run[%s][%s]: exit=%d "
|
||||
"want=%d\n", drivers[d].name, rows[i].label,
|
||||
got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "idxarg_run: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("idxarg_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user