test: migrate Fam8 tuple value tests to @test + reject carriers (#5-C2)

fold-2 chunk C2 (drew's Fam8-13 plan): 14 tuple value-row C drivers migrate to
15 test/lang/*_test.ww @test row-tables (the +1 is 954_tuprecv, slimmed not
deleted -- its value rows split out while the asserttyped-stamp dimension stays
as a carrier-split C pin, mutation-proven non-vacuous). Reject rows move to 32
test/wcc/data/*/case.ww //ww:error carriers (runww asserts the substring in
BOTH stages). The test-lang byte-id (LANGBYTEID) gate gives cs==ww automatically
and is strictly more sensitive than re-running the wwstage leg; floor 59->74.
Tuple surfaced zero cs!=ww as the plan predicted -- no value-only carve. The 945
trio folds in here; 940_global_sret / 940_str_forrange / 926_tagscr untouched
(routed to drew per-file). Test count 402->388 = the 14 retired drivers.
This commit is contained in:
2026-06-24 01:44:13 +09:00
parent 47c7dbc2c8
commit 07b3c74ab0
63 changed files with 3005 additions and 6577 deletions

122
Makefile
View File

@@ -274,8 +274,6 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
$(BIN)/test_m1mangle_sym \
$(BIN)/test_m1usehint_run \
$(BIN)/test_m1union_run \
$(BIN)/test_tupfieldsize_run \
$(BIN)/test_tagtupfieldsize_run \
$(BIN)/test_nestfield_run \
$(BIN)/test_structlocal_frame \
$(BIN)/test_arrlit_tail_zero_run \
@@ -379,27 +377,16 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
$(BIN)/test_alias_idx_family_run \
$(BIN)/test_idx_tagged_field_run \
$(BIN)/test_tagged_widen_arg_run \
$(BIN)/test_tuple_tagged_union_run \
$(BIN)/test_alias_global_decl_run \
$(BIN)/test_alias_cgen_b5_run \
$(BIN)/test_alias_cgen_b6_run \
$(BIN)/test_alias_emit_b7_run \
$(BIN)/test_variant_chain_b95_run \
$(BIN)/test_peellint_gate \
$(BIN)/test_tuple_nary_destructure_run \
$(BIN)/test_rvalue_tuple_destructure_run \
$(BIN)/test_tuple_lit_declblind_run \
$(BIN)/test_overcap_tuple_field_store_run \
$(BIN)/test_mixed_scalar_tuple_sret_run \
$(BIN)/test_tuple_in_union_run \
$(BIN)/test_nonlit_tuple_widen_run \
$(BIN)/test_const_slice_aggregate_run \
$(BIN)/test_append_structlit_evalorder_run \
$(BIN)/test_tuple_index_read_run \
$(BIN)/test_f9_float_run \
$(BIN)/test_xmod_fnptr_const_run \
$(BIN)/test_tuple_slot_layout_run \
$(BIN)/test_tagged_tuple_widen_run \
$(BIN)/test_tagged_structlit_payload_run \
$(BIN)/test_forrange_fieldbase_run \
$(BIN)/test_errtype_compare \
@@ -580,7 +567,6 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
$(BIN)/test_tuprecv_f64_run \
$(BIN)/test_assert_builtin_run \
$(BIN)/test_modqualdestr_run \
$(BIN)/test_tupparam_run \
$(BIN)/test_structparam_run \
$(BIN)/test_structret_run \
$(BIN)/test_floats_run \
@@ -835,28 +821,6 @@ $(BIN)/test_m1union_run: test/wcc/989_m1union_run.c \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
# 989_tupfieldsize_run (F7-c4, #43): a for-range destructure over an array
# of tuples must stride by the tuple's true size (a slice/str/tuple field
# carries its full width). Builds+runs on BOTH driver twins (rule-10), the
# slice-field row pinned cs==ww (see the test header on the absolute value).
$(BIN)/test_tupfieldsize_run: test/wcc/989_tupfieldsize_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_tagtupfieldsize_run (#53, sibling of #43): a for-range destructure over
# an array of tuples whose field is a tagged union must stride by the tuple's
# true size and copy the field's full box. Builds+runs on BOTH driver twins
# (rule-10), the tagged-field row pinned cs==ww at the absolute value.
$(BIN)/test_tagtupfieldsize_run: test/wcc/989_tagtupfieldsize_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_nestfield_run (#44/#55): a struct with a nested sub-8 composite field
# (inner{x:u8,y:u8}, slotsize 8 != size 2) plus a successor must address
# every field at the checker's NATURAL offset on BOTH the write
@@ -2266,12 +2230,6 @@ $(BIN)/test_tagged_widen_arg_run: test/wcc/944_tagged_widen_arg_run.c \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_tuple_tagged_union_run: test/wcc/944_tuple_tagged_union_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_alias_global_decl_run: test/wcc/944_alias_global_decl_run.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
@@ -2305,67 +2263,6 @@ $(BIN)/test_variant_chain_b95_run: test/wcc/944_variant_chain_b95_run.c \
$(BIN)/test_peellint_gate: test/wcc/944_peellint_gate.c tools/peellint | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_tuple_nary_destructure_run: test/wcc/945_tuple_nary_destructure_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 $@ $<
# #241: materialise an RVALUE tuple (literal / match-yield / ?-call payload)
# into the register cursor before a destructure (run + cs==ww byte-id).
$(BIN)/test_rvalue_tuple_destructure_run: test/wcc/945_rvalue_tuple_destructure_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 $@ $<
# #64 + #68: tuple LITERAL fills the register cursor DECL-BLIND — the #57
# decl wire (a declared-tagged element widens into its box) stopped at
# N_LET/N_RETURN; destructure-REASSIGN (#64) and CALL-ARG send (#68) still
# rode the decl-less route (box stored/sent word0-only, cursor skewed).
# Both stages, #263 gate-blind (byte-id cs==ww, both ran wrong). Run +
# cs==ww byte-id.
$(BIN)/test_tuple_lit_declblind_run: test/wcc/945_tuple_lit_declblind_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 $@ $<
# #234: over-cap tuple sret store into a local struct field / indexed local
# (run + cs==ww byte-id), deferred forms loud-stop (builderr both drivers).
$(BIN)/test_overcap_tuple_field_store_run: test/wcc/940_overcap_tuple_field_store_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 $@ $<
# #240: mixed-scalar tuple (e.g. (int,[]u8,str)) sret layout agrees in both
# stages — callee SEND foff via the return-type element size, caller RECEIVE
# falls an over-cap tuple through to the sret receive (size-gated).
$(BIN)/test_mixed_scalar_tuple_sret_run: test/wcc/940_mixed_scalar_tuple_sret_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_tuple_in_union_run: test/wcc/940_tuple_in_union_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_nonlit_tuple_widen_run: test/wcc/944_nonlit_tuple_widen_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_tuple_slot_layout_run: test/wcc/941_tuple_slot_layout_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_const_slice_aggregate_run: test/wcc/946_const_slice_aggregate_run.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
@@ -2378,12 +2275,6 @@ $(BIN)/test_append_structlit_evalorder_run: test/wcc/946_append_structlit_evalor
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_tuple_index_read_run: test/wcc/947_tuple_index_read_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_f9_float_run: test/wcc/949_f9_float_run.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
@@ -2396,12 +2287,6 @@ $(BIN)/test_xmod_fnptr_const_run: test/wcc/949_xmod_fnptr_const_run.c \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_tagged_tuple_widen_run: test/wcc/936_tagged_tuple_widen_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_tagged_structlit_payload_run: test/wcc/938_tagged_structlit_payload_run.c \
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
@@ -3229,11 +3114,6 @@ $(BIN)/test_modqualdestr_run: test/wcc/956_modqualdestr_run.c $(BIN)/ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_tupparam_run: test/wcc/905_tupparam_run.c $(BIN)/ww \
$(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_structparam_run: test/wcc/946_structparam_run.c $(BIN)/ww \
$(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \
$(LIB)/libwwrt.a | $(BIN)
@@ -3310,7 +3190,7 @@ test-lang: all
LANGBYTEID_DIR = $(OUT)/langbyteid
LANGBYTEID_FILES = $(filter-out %_runonly_test.ww,$(wildcard test/lang/*_test.ww))
LANGBYTEID_VERB = test -c
LANGBYTEID_EXPECTED_MIN = 59
LANGBYTEID_EXPECTED_MIN = 74
$(if $(LANGBYTEID_FILES),,$(error test-lang-byteid: empty corpus))
test-lang-byteid: all
@set -e; \

View File

@@ -0,0 +1,64 @@
// mixed_scalar_tuple_sret_test — project #240: an over-cap tuple whose elements
// mix a scalar with slices/str (e.g. `(int, []u8, str)`) must lay out
// identically in both stages and round-trip through an sret return. Migrated
// from test/wcc/940_mixed_scalar_tuple_sret_run.c. Two silent, gate-blind
// cs!=ww divergences: callee SEND packed a leading scalar at the literal's
// (untyped-int, size 0) stride so trailing elements packed 8 bytes low; caller
// RECEIVE had no capacity gate so a 56B over-cap tuple was received via
// registers instead of the sret dest. The @test fns pin the runtime element
// values; T2 byte-id keeps cs==ww (rule 10). Each row covers an annotated and
// an inferred receive and a distinct scalar position.
package mixed_scalar_tuple_sret_test;
fn mk_int_slice_str() (int, []u8, str) = {
let b: []u8; b.len = 5; b.cap = 9;
let s: str = "abcd";
return (42, b, s);
};
fn mk_int_slice_str_inferred() (int, []u8, str) = {
let b: []u8; b.len = 6; b.cap = 8;
let s: str = "xyz";
return (17, b, s);
};
fn mk_slice_str_int() ([]u8, str, int) = {
let b: []u8; b.len = 7; b.cap = 9;
let s: str = "hello";
return (b, s, 99);
};
fn mk_str_int_slice() (str, int, []u8) = {
let s: str = "abcde";
let b: []u8; b.len = 3; b.cap = 4;
return (s, 77, b);
};
@test fn int_slice_str() void = {
let t: (int, []u8, str) = mk_int_slice_str();
assert(!(t.0 != 42));
assert(!(len(t.1) != 5));
assert(!(len(t.2) != 4));
};
@test fn int_slice_str_inferred() void = {
let t = mk_int_slice_str_inferred();
assert(!(t.0 != 17));
assert(!(len(t.1) != 6));
assert(!(len(t.2) != 3));
};
@test fn slice_str_int() void = {
let t: ([]u8, str, int) = mk_slice_str_int();
assert(!(len(t.0) != 7));
assert(!(len(t.1) != 5));
assert(!(t.2 != 99));
};
@test fn str_int_slice() void = {
let t: (str, int, []u8) = mk_str_int_slice();
assert(!(len(t.0) != 5));
assert(!(t.1 != 77));
assert(!(len(t.2) != 3));
};

View File

@@ -0,0 +1,88 @@
// nonlit_tuple_widen_test — project #116: a NON-LITERAL tuple source widened
// into a tagged box (cg_widen_tagged_store / cgwidentaggedstore). Pre-#116 only a
// bare/cast tuple LITERAL carried a full payload into the union box; every
// ADDRESSABLE non-literal tuple source — a tuple IDENT var, a slice/array INDEX
// `tbl[i]`, a DEREF `*p` — hit the `else fatal` ("tuple-typed source shape
// unwired"), LOUD and IDENTICAL on both stages, blocking fold-6's
// `append(charsets[...], charclass_map[cc_idx])`. The fix resolves the source
// ADDRESS through cgplaceaddr and BLOCK-COPIES the tuple's table size into the
// box payload, stamping the variant tag. Byte-id is BLIND here (both stages were
// loud — #263), so the RUNTIME read-back is the correctness net: `len(x.0)`
// proves the str header rode over, `(*x.1)(arg)` indirect-calls the fn-ptr
// element. Migrated from test/wcc/944_nonlit_tuple_widen_run.c. The never-taken
// rune match arm -> assert(false) (the C original's `return 90`).
package nonlit_tuple_widen_test;
type ci = (str, *fn(c: rune) bool);
type u = (rune | ci);
fn fa(c: rune) bool = { return c == 'a'; };
fn fb(c: rune) bool = { return c == 'b'; };
@test fn literal() void = {
let nm: str = "ww";
let cf: *fn(c: rune) bool = &fa;
let s: []u = [];
append(s, ((nm, cf): ci));
match (s[0]) {
case let r: rune => { assert(false); };
case let x: ci => {
assert(!((len(x.0): i32) != 2));
assert(!(!(*x.1)('a')));
};
};
};
@test fn ident() void = {
let nm: str = "zz";
let cf: *fn(c: rune) bool = &fb;
let t: ci = (nm, cf);
let s: []u = [];
append(s, t);
match (s[0]) {
case let r: rune => { assert(false); };
case let x: ci => {
assert(!((len(x.0): i32) != 2));
assert(!(!(*x.1)('b')));
assert(!((*x.1)('a')));
};
};
};
@test fn index() void = {
let n0: str = "aa"; let c0: *fn(c: rune) bool = &fa;
let n1: str = "zzz"; let c1: *fn(c: rune) bool = &fb;
let t0: ci = (n0, c0);
let t1: ci = (n1, c1);
let tbl: [2]ci = [];
tbl[0] = t0;
tbl[1] = t1;
let s: []u = [];
append(s, tbl[1]);
match (s[0]) {
case let r: rune => { assert(false); };
case let x: ci => {
assert(!((len(x.0): i32) != 3));
assert(!(!(*x.1)('b')));
assert(!((*x.1)('a')));
};
};
};
@test fn deref() void = {
let nm: str = "qqq";
let cf: *fn(c: rune) bool = &fa;
let t: ci = (nm, cf);
let p: *ci = &t;
let s: []u = [];
append(s, *p);
match (s[0]) {
case let r: rune => { assert(false); };
case let x: ci => {
assert(!((len(x.0): i32) != 3));
assert(!(!(*x.1)('a')));
assert(!((*x.1)('b')));
};
};
};

View File

@@ -0,0 +1,50 @@
// overcap_tuple_field_store_test — project #234: storing the result of an
// over-capacity tuple-returning call (sret: ([]u8,[]u8) = 6 GP) into a struct
// field or an indexed lvalue. Migrated from
// test/wcc/940_overcap_tuple_field_store_run.c. A FIELD or INDEXED dest stayed
// unwired: the store dropped the callee's sret body (a truncated MOVQ through a
// stale RDI), a SILENT miscompile gate-blind because the bootstrap never
// field-stores a wide tuple. The fix points the callee's hidden sret dest
// straight at the destination slot when it is a LOCAL (BP-relative). These
// @test fns pin the runtime contract (T1, exit 0 both stages); the byte-id
// gate (T2) keeps cs==ww. Every other dest (via_ptr / global / runtime-index /
// N_DOT-base index / chained index) HARD-ERRORS LOUD (#234-tail) and lives as a
// reject carrier (test/wcc/data/overcap_*_be/). cap != len in every wide
// element so a dropped len OR cap is caught.
package overcap_tuple_field_store_test;
fn wide() ([]u8, []u8) = {
let a: []u8; a.len = 1; a.cap = 5;
let b: []u8; b.len = 2; b.cap = 6;
return (a, b);
};
type S_struct_field = struct { hdr: int, f: ([]u8, []u8) };
// indexed_local — `arr[1] = wide()`, arr: [2]([]u8,[]u8). Readback via
// `(&arr[1]):*int` p[i] (a tuple-element READ arr[1].N is a separate gap, filed
// #238) — len0=1, cap0=5, len1=2, cap1=6.
@test fn indexed_local() void = {
let arr: [2]([]u8, []u8);
arr[1] = wide();
let p: *int = (&arr[1]): *int;
assert(!(p[1] != 1));
assert(!(p[2] != 5));
assert(!(p[4] != 2));
assert(!(p[5] != 6));
};
// struct_field — `s.f = wide()`, S = struct { hdr:int, f:([]u8,[]u8) }. foff!=0
// (after hdr) + hdr-uncorrupted check. Pointer readback `(&s.f):*int`.
@test fn struct_field() void = {
let s: S_struct_field;
s.hdr = 99;
s.f = wide();
assert(!(s.hdr != 99));
let p: *int = (&s.f): *int;
assert(!(p[1] != 1));
assert(!(p[2] != 5));
assert(!(p[4] != 2));
assert(!(p[5] != 6));
};

View File

@@ -0,0 +1,58 @@
// rvalue_tuple_destructure_test — project #241: materialise an RVALUE tuple
// into the SysV register-return cursor before a destructure / let bind.
// Migrated from test/wcc/945_rvalue_tuple_destructure_run.c. cgexpr could not
// produce a tuple VALUE: a literal `(a, b)` fell to the MOVQ $0 default, a
// tuple-typed IDENT loaded only word0, and a `?`/`!` unwrap of a tuple-in-
// union payload lifted only word0 — so a destructure read GARBAGE past the
// first element (cstage), and the un-typed binder aborted wwstage's checker
// (asserttyped). A DANGEROUS gate-blind cs!=ww (the strconv blocker
// `let (sign, u) = parseint(s, base)?`). The fix packs an N_TUPLE literal and a
// tuple IDENT into the register-return cursor and shifts a `?`/`!` tuple
// success payload down past the tag; both stages byte-identical (rule 10).
//
// The C driver ran each row on both stages + asserted cs==ww .s byte-id; here
// the cstage run is test-lang and the byte-id is test-lang-byteid (T2), so both
// dimensions survive. The never-taken error arms abort() via assert(false), the
// migrated-test idiom for a diverging arm in a yield/destructure context (the C
// original `return 9`).
package rvalue_tuple_destructure_test;
type myerr = !void;
type invalid = !void;
fn mk(x: u64, s: bool) ((bool, u64) | myerr) = { return (s, x); };
fn parseint(base: int) ((bool, u64) | invalid) = {
let sg: bool = true;
let n: u64 = 42u64;
return (sg, n);
};
fn stoi(base: int) (u64 | invalid) = {
let (sign, u) = parseint(base)?;
if (!sign) { return 0u64; };
return u;
};
@test fn literal_destructure() void = {
let (a, b) = (true, 11u64);
assert(b == 11u64);
assert(a);
};
@test fn match_yield() void = {
let (sg, u) = match (mk(6u64, true)) {
case let t: (bool, u64) => yield t;
case myerr => { assert(false); };
};
assert(u == 6u64);
assert(sg);
};
@test fn trycall_strconv() void = {
match (stoi(10)) {
case let v: u64 => { assert(v == 42u64); };
case invalid => { assert(false); };
};
};

View File

@@ -0,0 +1,227 @@
// tagged_tuple_widen_test — #66 (fold-4 prereq): the cast-wrapped tuple literal
// `((a, b): variant_alias)` widened into a tagged-union payload. Pre-#66 the
// #242 tuple arm of the widen choke-point (cg_widen_tagged_store /
// cgwidentaggedstorebp) gated on a BARE N_TUPLE source; the cast-to-CONCRETE-
// VARIANT wrapper — the only spelling real code uses (ref/hare/regex/regex.ha:213
// `(start_b, end_b): charset_range_item`) — was not peeled and silently stored
// cursor word 0 only, zero-filling payload slot 1+ (both stages, byte-id —
// gate-blind). The read side (match-extract slot walk) was always correct.
// Migrated from test/wcc/936_tagged_tuple_widen_run.c. The K_RUN value rows are
// @test fns here; the bare_literal_reject K_BUILDERR row is carried at
// test/wcc/data/twiden_bare_literal_reject/case.ww. Never-taken match arms ->
// assert(false) (the migrated idiom for the C original's `return N`).
package tagged_tuple_widen_test;
type lit = rune;
type rng = (u32, u32);
type item = (lit | rng);
@test fn cast_let_packed() void = {
let v: item = ((65, 90): rng);
match (v) {
case let r: rng => {
assert(!(r.0 != 65));
assert(!(r.1 != 90));
};
case => assert(false);
};
};
type cls_3member = (str, *fn(c: rune) bool);
type item_3member = (lit | rng | cls_3member);
@test fn cast_let_3member() void = {
let v: item_3member = ((65, 90): rng);
match (v) {
case let r: rng => {
assert(!(r.0 != 65));
assert(!(r.1 != 90));
};
case => assert(false);
};
let w: item_3member = ('a': lit);
match (w) {
case let l: lit => { assert(!((l: rune) != 'a')); };
case => assert(false);
};
};
@test fn cast_append_local() void = {
let xs: []item;
append(xs, ('a': lit));
append(xs, ((65, 90): rng));
assert(!(len(xs) != 2));
let e: item = xs[1];
match (e) {
case let r: rng => {
assert(!(r.0 != 65));
assert(!(r.1 != 90));
};
case => assert(false);
};
};
@test fn cast_append_indexplace() void = {
let charsets: [][]item;
let empty: []item;
append(charsets, empty);
append(charsets[len(charsets) - 1], ((7, 9): rng));
assert(!(len(charsets[0]) != 1));
let e: item = charsets[0][0];
match (e) {
case let r: rng => {
assert(!(r.0 != 7));
assert(!(r.1 != 9));
};
case => assert(false);
};
};
fn addrange(cs: *[][]item, a: u32, b: u32) void = {
if (len(*cs) == 0) {
let empty: []item;
append(*cs, empty);
};
append((*cs)[len(*cs) - 1], ((a, b): rng));
};
@test fn cast_append_derefplace() void = {
let charsets: [][]item;
addrange(&charsets, 5, 8);
assert(!(len(charsets) != 1));
assert(!(len(charsets[0]) != 1));
let e: item = charsets[0][0];
match (e) {
case let r: rng => {
assert(!(r.0 != 5));
assert(!(r.1 != 8));
};
case => assert(false);
};
};
type holder = struct { cs: [][]item, n: i64 };
@test fn cast_append_ptrfield() void = {
let h: holder = holder { n = 0, ... };
let p: *holder = &h;
let empty: []item;
append(p.cs, empty);
append(p.cs[len(p.cs) - 1], ((7, 9): rng));
assert(!(len(h.cs[0]) != 1));
match (h.cs[0][0]) {
case let r: rng => {
assert(!(r.0 != 7));
assert(!(r.1 != 9));
};
case => assert(false);
};
};
@test fn cast_ident_elems() void = {
let start_b: u32 = 97;
let end_b: u32 = 122;
let v: item = ((start_b, end_b): rng);
match (v) {
case let r: rng => {
assert(!(r.0 != 97));
assert(!(r.1 != 122));
};
case => assert(false);
};
};
type mf = (u32, f64);
type item_float = (rune | mf);
@test fn cast_float_elem() void = {
let a: u32 = 9;
let x: f64 = 2.5;
let v: item_float = ((a, x): mf);
match (v) {
case let m: mf => {
assert(!(m.0 != 9));
assert(!(m.1 != 2.5));
};
case => assert(false);
};
};
type ms = (i64, str);
type item_str = (rune | ms);
@test fn cast_str_elem() void = {
let n: i64 = 12;
let s: str = "wxyz";
let v: item_str = ((n, s): ms);
match (v) {
case let m: ms => {
assert(!(m.0 != 12));
assert(!(len(m.1) != 4));
};
case => assert(false);
};
};
fn check_arg_direct(v: item) i32 = {
match (v) {
case let r: rng => {
assert(!(r.0 != 65));
assert(!(r.1 != 90));
return 0;
};
case => return 3;
};
};
@test fn cast_arg_direct() void = {
assert(check_arg_direct(((65, 90): rng)) == 0);
};
type item_ii = (rune | (i64, i64));
fn check_bare_ident_arg(v: item_ii) i32 = {
match (v) {
case let r: (i64, i64) => {
assert(!(r.0 != 5));
assert(!(r.1 != 6));
return 0;
};
case => return 3;
};
};
@test fn bare_ident_arg() void = {
let a: i64 = 5;
let b: i64 = 6;
assert(check_bare_ident_arg((a, b)) == 0);
};
fn mk_no_regress() ((i64, i64) | nomem) = {
let a: i64 = 5;
let b: i64 = 6;
return (a, b);
};
@test fn bare_ident_no_regress() void = {
match (mk_no_regress()) {
case let t: (i64, i64) => {
assert(!(t.0 != 5));
assert(!(t.1 != 6));
};
case => assert(false);
};
};
@test fn tuple_ident_widen() void = {
let t: rng = ((65, 90): rng);
let v: item = t;
match (v) {
case let r: rng => {
assert(!(r.0 != 65));
assert(!(r.1 != 90));
};
case => assert(false);
};
};

View File

@@ -0,0 +1,46 @@
// tagtupfieldsize_test — #53 (sibling of #43): a for-range destructure over an
// array of tuples whose field is a TAGGED UNION must stride by the tuple's true
// size and copy the field's full box (tag + payload), not the 8B scalar
// default. Migrated from test/wcc/989_tagtupfieldsize_run.c.
//
// THE BUG (cat-A silent miscompile, gate-blind): wwstage paramfieldsize
// (selfhost/cmd/wcc/cgenstmt.ww) had no tagged-union arm, so a `(i64|bool)`
// field sized 8 instead of its 16B box (tag + slot-padded payload); the 8B bind
// copied only the tag word and read the payload from stale stack (ww=9 vs
// cs=56). THE FIX: read the tagged box width from the checker-stamped tinfo
// (rule-13), mirroring the N_TSLICE arm; the stamp collapses the `tu` alias so
// an N_TNAME field naming a tagged union resolves too.
//
// The tagged value enters the tuple from a LOCAL ident box (the only wired
// tagged-tuple-element source); a cast source loud-rejects.
//
// The C driver ran each row on both stages + asserted cs==ww exit; here the
// cstage run is test-lang (T1) and the byte-id is test-lang-byteid (T2).
package tagtupfieldsize_test;
type tu = (i64 | bool);
@test fn tag_payload() void = {
let t0: tu = 7: tu;
let t1: tu = 9: tu;
let xs: [2](tu, i64);
xs[0] = (t0, 10);
xs[1] = (t1, 30);
let sum: i64 = 0;
for (let (a, n) .. xs) {
match (a) {
case let v: i64 => sum = sum + v + n;
case let b: bool => sum = sum + 1000;
};
};
assert(sum: int == 56);
};
@test fn scalar_tuple_ctl() void = {
let xs: [2](i64, i64);
xs[0] = (3, 10); xs[1] = (5, 30);
let sum: i64 = 0;
for (let (a, b) .. xs) { sum = sum + a + b; };
assert(sum: int == 48);
};

View File

@@ -0,0 +1,65 @@
// tupfieldsize_test — F7-c4 (#43) + #40: a for-range destructure over an array
// of tuples must stride by the tuple's TRUE size, and a slice/str/nested-tuple
// FIELD carries its full 24B width, not the 8B scalar default; the destructure
// LOAD-into-binding must copy the full extent of a sz>8 aggregate binding (the
// recurring dropped word is the CAP). Migrated from test/wcc/989_tupfieldsize_run.c.
//
// THE BUG (cat-A silent miscompile, gate-blind): wwstage paramfieldsize
// (selfhost/cmd/wcc/cgenstmt.ww) had no N_TSLICE / N_TTUPLE arm, so a `[]T`
// tuple-field sized 8 instead of its 24B header; and the destructure copy
// landed only the PTR word, dropping .len/.cap. The fix adds the N_TSLICE arm
// (tyslicesize()=24, rule-13) + the N_TTUPLE arm, and copies the binding's full
// extent (cgen.c N_FORRANGE + cgenstmt.ww cgforrange twin), aligning wwstage UP.
//
// The cap row slices the bases so cap != len, teething the recurring dropped
// CAP word; a ptr+len-only set would pass green while cap stayed stale.
//
// The C driver ran each row on both stages + asserted cs==ww exit; here the
// cstage run is test-lang (T1) and the byte-id is test-lang-byteid (T2).
package tupfieldsize_test;
@test fn len_read() void = {
let b0: []u8 = ['a', 'b'];
let b1: []u8 = ['x', 'y', 'z'];
let xs: [2]([]u8, i64);
xs[0] = (b0, 10);
xs[1] = (b1, 30);
let sum: i64 = 0;
for (let (b, n) .. xs) {
sum = sum + len(b): i64 + n;
};
assert(sum: int == 45);
};
@test fn cap_read() void = {
let base0: []u8 = ['a', 'b', 'c', 'd'];
let base1: []u8 = ['p', 'q', 'r', 's', 't'];
let xs: [2]([]u8, i64);
xs[0] = (base0[0:2], 10);
xs[1] = (base1[0:3], 30);
let sum: i64 = 0;
for (let (b, n) .. xs) {
sum = sum + b.cap: i64 + n;
};
assert(sum: int == 49);
};
@test fn ptr_read() void = {
let b0: []u8 = ['a', 'b'];
let b1: []u8 = ['x', 'y', 'z'];
let xs: [2]([]u8, i64);
xs[0] = (b0, 10);
xs[1] = (b1, 30);
let sum: i64 = 0;
for (let (b, n) .. xs) { sum = sum + b[0]: i64; };
assert(sum: int == 217);
};
@test fn scalar_tuple_ctl() void = {
let xs: [2](i64, i64);
xs[0] = (3, 10); xs[1] = (5, 30);
let sum: i64 = 0;
for (let (a, b) .. xs) { sum = sum + a + b; };
assert(sum: int == 48);
};

View File

@@ -0,0 +1,84 @@
// tuple_in_union_test — project #242: a mixed-scalar tuple wrapped in a tagged
// union must construct, match-bind, and destructure identically in both stages
// and round-trip every element. Migrated from test/wcc/940_tuple_in_union_run.c
// (K_RUN value rows). Three silent gate-blind cs!=ww divergences were met:
// cstage construction zeroed the whole value instead of packing the tuple
// variant; wwstage's checker left un-annotated destructure binders untyped for
// a non-call rhs; both stages destructured a tuple ident off a stale register
// cursor. The @test fns pin the runtime element values at variant 0 and at a
// non-zero variant index; T2 byte-id keeps cs==ww (rule 10). The K_BUILDERR
// bare-literal-element loud-stop row lives at test/wcc/data/tupunion_bool_literal/.
package tuple_in_union_test;
fn mku_bool_u64(x: u64, s: bool) ((bool, u64) | void) = { return (s, x); };
fn mku_void_arm(ok: bool, x: u64, s: bool) ((bool, u64) | void) = {
if (ok) { return (s, x); };
return;
};
fn mku_tuple_tag1(x: u64, s: bool) (void | (bool, u64)) = { return (s, x); };
fn mk_tuple_after_int(which: bool) (int | (bool, u64)) = {
let s: bool = true;
let v: u64 = 88u64;
if (which) { return (s, v); };
return 5;
};
fn f_eightbyte_share(a: i32, b: i32, c: u64) ((i32, i32, u64) | void) = {
return (a, b, c);
};
@test fn bool_u64() void = {
match (mku_bool_u64(7u64, true)) {
case let t: (bool, u64) => {
let (sg, u) = t;
assert(!(u != 7u64));
assert(!(!sg));
};
case void => { assert(false); };
};
};
@test fn void_arm() void = {
match (mku_void_arm(false, 1u64, true)) {
case let t: (bool, u64) => { assert(false); };
case void => { };
};
};
@test fn tuple_tag1() void = {
match (mku_tuple_tag1(9u64, true)) {
case void => { assert(false); };
case let t: (bool, u64) => {
let (sg, u) = t;
assert(!(u != 9u64));
assert(!(!sg));
};
};
};
@test fn tuple_after_int() void = {
match (mk_tuple_after_int(true)) {
case let n: int => { assert(false); };
case let t: (bool, u64) => {
let (sg, u) = t;
assert(!(u != 88u64));
assert(!(!sg));
};
};
};
@test fn eightbyte_share() void = {
match (f_eightbyte_share(-3, 4, 9u64)) {
case let t: (i32, i32, u64) => {
let (x, y, z) = t;
assert(!(x != -3));
assert(!(y != 4));
assert(!(z != 9u64));
};
case void => { assert(false); };
};
};

View File

@@ -0,0 +1,61 @@
// tuple_index_read_test — #121: const-slice tuple-element READ. Migrated from
// test/wcc/947_tuple_index_read_run.c.
//
// Three legs of indexed-tuple read off a `const [](str,*fn)` (fold-6's
// charclass_map shape), all sharing the &tbl[i] place-spine (#116):
//
// field_read — direct `tbl[i].N` field read, was LOUD both stages.
// whole_element — `let e = tbl[i]` then read e.N, was SILENT both-wrong-
// IDENTICAL (#263, word0-only truncation). The runtime read-
// back with DISTINCT-per-row values is the net: a wrong word
// is CAUGHT, not masked.
// store_roundtrip — write-face: a tuple-LITERAL store `a[i] = (3,4)`, read
// back whole; DISTINCT words per row catch the word0-only
// store.
//
// The for-range-over-module-global LOUD-STOP reject row migrated to
// test/wcc/data/tupidx_forrange_global_loud/case.ww (K_BUILDERR). Every value
// row's cs==ww .s byte-id rides T2 (test-lang-byteid).
package tuple_index_read_test;
fn fa(c: rune) bool = { return c == 'a'; };
fn fz(c: rune) bool = { return c == 'z'; };
const tbl: [](str, *fn(c: rune) bool) = [("ab", &fa), ("cdef", &fz)];
@test fn field_read() void = {
let s1 = tbl[1].0;
let s0 = tbl[0].0;
assert(len(s1) == 4);
assert(len(s0) == 2);
let f1 = tbl[1].1;
assert((*f1)('z'));
assert(!((*f1)('a')));
let f0 = tbl[0].1;
assert((*f0)('a'));
assert(!((*f0)('z')));
};
@test fn whole_element() void = {
let e = tbl[1];
assert(len(e.0) == 4);
assert((*e.1)('z'));
assert(!((*e.1)('a')));
let e0 = tbl[0];
assert(len(e0.0) == 2);
assert((*e0.1)('a'));
assert(!((*e0.1)('z')));
};
@test fn store_roundtrip() void = {
let a: [2](u64, u64);
a[1] = (3u64, 4u64);
a[0] = (7u64, 9u64);
let t = a[1];
assert(t.0 == 3);
assert(t.1 == 4);
let s = a[0];
assert(s.0 == 7);
assert(s.1 == 9);
};

View File

@@ -0,0 +1,65 @@
// tuple_lit_declblind_test — #64 + #68: a tuple LITERAL fills the register
// cursor DECL-BLIND. The #57 decl wire (a declared-tagged element's concrete
// rvalue widens into its box) stopped at N_LET / N_RETURN; the other two
// tuple-literal consumers — destructure-REASSIGN (N_MASSIGN, #64) and CALL-ARG
// send (#68) — rode the decl-less route, so a declared-tagged element was
// stored/sent WORD-0 ONLY (the box tag never set) and the cursor skewed every
// later element. Both stages, #263 gate-blind: the asm was byte-identical
// cs==ww and both ran wrong, so only RUNTIME catches it.
//
// Migrated from test/wcc/945_tuple_lit_declblind_run.c. Each value row reads
// the box PAYLOAD back (binds n and asserts its VALUE), not merely which match
// arm fired — a skew that sets the tag right but corrupts the payload still
// fails. The never-taken arms abort via assert(false), the migrated-test idiom
// for a diverging arm (the C original's non-`want` exit codes). The reject row
// (nested_tuple_arg) carries to test/wcc/data/tldb_nested_tuple_arg/. cstage
// run is test-lang; cs==ww byte-id is test-lang-byteid (T2) — both dimensions
// survive.
package tuple_lit_declblind_test;
type ev = (i32 | i64);
fn f(t: (ev, i64)) i32 = {
match (t.0) {
case let n: i64 => { if (n == 7) { return 1; }; return 3; };
case let m: i32 => { return 0; };
};
return 2;
};
fn g(t: (ev, i64)) i32 = {
let bp: i64 = 0;
match (t.0) {
case let n: i64 => { bp = n; };
case let m: i32 => { bp = 0; };
};
return (bp + t.1): i32;
};
@test fn massign_tagged() void = {
let a: ev = 5i32;
let b: i64 = 0;
a, b = (7i64, 99);
match (a) {
case let n: i64 => { assert(n == 7); };
case let m: i32 => { assert(false); };
};
};
@test fn callarg_tagged() void = {
assert(f((7i64, 99)) == 1);
};
@test fn massign_blank() void = {
let a: ev = 5i32;
_, a = (99, 7i64);
match (a) {
case let n: i64 => { assert(n == 7); };
case let m: i32 => { assert(false); };
};
};
@test fn callarg_drain() void = {
assert(g((7i64, 35)) == 42);
};

View File

@@ -0,0 +1,120 @@
// tuple_nary_destructure_test — project #83: the general N-ary tuple-
// destructure per-element register-return. The send (N_RETURN) and receive
// (N_MLET / N_MASSIGN) sites previously gated the 3-word {ptr,len,cap} store
// on a str-only XOR (e0_is_str ^ e1_is_str), which admitted EXACTLY ONE str
// element and was slice-BLIND: a tuple with a []u8 element fell to the
// scalar-pair fallback and silently DROPPED len+cap (only .ptr landed). The
// fix REPLACES the XOR with a positional per-element cursor (harec
// create_unpack_bindings, ref/harec/src/check.c:1354-1416): each element rides
// consecutive eightbytes over [AX,DX,CX,R8]; a slice/str rides its 3-word
// header, a scalar rides 1. SEND and RECEIVE walk the SAME cursor.
//
// Migrated from test/wcc/945_tuple_nary_destructure_run.c. Every wide row has
// cap!=len so a dropped len OR cap is caught; N_MASSIGN rows pre-poison the
// destination slot (distinct len/cap via a proven 3-word let-init copy) so a
// dropped len/cap reads the poison, never the test value. Each []u8 element
// borrows a str literal's .ptr (stable .rodata). Row F (slice_slice_recv) is
// the over-cap 6-eightbyte tuple that #10 Fold A sret's at the SEND and Fold B
// copies out at the RECEIVE — a working round-trip. cstage run is test-lang;
// cs==ww byte-id is test-lang-byteid (T2) — both dimensions survive.
package tuple_nary_destructure_test;
fn mk_mlet_islice() (i64, []u8) = {
let b: str = "hi";
let p: []u8; p.ptr = b.ptr; p.len = 2; p.cap = 5;
return (4i64, p);
};
fn mk_massign_islice() (i64, []u8) = {
let b: str = "hi";
let p: []u8; p.ptr = b.ptr; p.len = 2; p.cap = 5;
return (4i64, p);
};
fn mk_mlet_istr() (i64, str) = {
let p: str = "hi"; p.cap = 7i32;
return (4i64, p);
};
fn mk_massign_istr() (i64, str) = {
let p: str = "hi"; p.cap = 7i32;
return (4i64, p);
};
fn mk_str_control() str = {
let p: str = "hello"; p.cap = 9i32;
return p;
};
fn mk_massign_blank_wide() ([]u8, i64) = {
let b: str = "hi";
let p: []u8; p.ptr = b.ptr; p.len = 2; p.cap = 5;
return (p, 7i64);
};
fn mk_slice_slice_recv() ([]u8, []u8) = {
let a: []u8; a.len = 1; a.cap = 5;
let b: []u8; b.len = 2; b.cap = 6;
return (a, b);
};
@test fn mlet_islice() void = {
let (a, s) = mk_mlet_islice();
assert(a == 4);
assert(s.len: i32 == 2);
assert(s.cap: i32 == 5);
assert(s[0] == 104u8);
};
@test fn massign_islice() void = {
let qb: str = "zzzz";
let q: []u8; q.ptr = qb.ptr; q.len = 9; q.cap = 9;
let a: i64 = 0i64;
let s: []u8 = q;
a, s = mk_massign_islice();
assert(a == 4);
assert(s.len: i32 == 2);
assert(s.cap: i32 == 5);
assert(s[0] == 104u8);
};
@test fn mlet_istr() void = {
let (a, s) = mk_mlet_istr();
assert(a == 4);
assert(s.len: i32 == 2);
assert(s.cap: i32 == 7);
assert(s[0] == 104u8);
};
@test fn massign_istr() void = {
let q: str = "qqqq"; q.cap = 6i32;
let a: i64 = 0i64;
let s: str = q;
a, s = mk_massign_istr();
assert(a == 4);
assert(s.len: i32 == 2);
assert(s.cap: i32 == 7);
assert(s[0] == 104u8);
};
@test fn str_control() void = {
let s: str = mk_str_control();
assert(s.len: i32 == 5);
assert(s.cap: i32 == 9);
assert(s[0] == 104u8);
};
@test fn massign_blank_wide() void = {
let a: i64 = 0i64;
_, a = mk_massign_blank_wide();
assert(a == 7);
};
@test fn slice_slice_recv() void = {
let (x, y) = mk_slice_slice_recv();
assert(x.len: i32 == 1);
assert(x.cap: i32 == 5);
assert(y.len: i32 == 2);
assert(y.cap: i32 == 6);
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,98 @@
// tuple_tagged_union_test — project #47 gap-A/gap-B: a tuple whose elements
// include a TAGGED union, boxed as a member of a tagged union, must CONSTRUCT
// + RETURN-STORE identically in both stages. Migrated from
// test/wcc/944_tuple_tagged_union_run.c.
//
// A PLAIN tuple-in-union already worked (#242); the blocker was the TAGGED
// ELEMENT. gap-A adds a RECURSIVE two-level widen: each tagged element is boxed
// into its tuple slot, THEN the whole tuple is boxed. The element's tuple-slot
// stride is its OWN box size (roundup8) — a (void|size) elem is 16B (2 slots).
// gap-B taught typeeqast the N_TTAGGED arm so wwstage ACCEPTS the tuple-with-
// tagged case pattern (b1c_full). Both stages get the SAME arm → byte-identical
// asm (rule 10); the C driver's cs==ww .s byte-id rides T2 (test-lang-byteid).
// The never-taken error/match arms abort via assert(false) per the migrated-
// test idiom for a diverging arm.
package tuple_tagged_union_test;
type error = !str;
fn pr_ret_3tuple(sel: i32) (((void | size), (void | size), size) | error) = {
if (sel == 0) {
let min: (void | size) = void;
let max: (void | size) = 5: size;
return (min, max, 7: size);
};
return "neg": error;
};
fn pr_ret_2tuple(sel: i32) (((void | size), size) | error) = {
let min: (void | size) = 5: size;
return (min, 7: size);
};
fn pr_scalar_ctrl(sel: i32) ((size, size, size) | error) = {
return (3: size, 4: size, 5: size);
};
fn pr_b1c_full(sel: i32) (((void | size), (void | size), size) | error) = {
if (sel == 0) {
let min: (void | size) = void;
let max: (void | size) = 5: size;
return (min, max, 7: size);
};
if (sel == 1) {
let min: (void | size) = 3: size;
let max: (void | size) = void;
return (min, max, 9: size);
};
return "Negative repetition count": error;
};
@test fn ret_3tuple() void = {
let a: (((void | size), (void | size), size) | error) = pr_ret_3tuple(0);
assert(!(a is error));
let b: (((void | size), (void | size), size) | error) = pr_ret_3tuple(2);
assert(b is error);
};
@test fn ret_2tuple() void = {
let a: (((void | size), size) | error) = pr_ret_2tuple(0);
assert(!(a is error));
};
@test fn scalar_ctrl() void = {
let a: ((size, size, size) | error) = pr_scalar_ctrl(0);
assert(!(a is error));
};
@test fn b1c_full() void = {
let acc: size = 0;
let a: (((void | size), (void | size), size) | error) = pr_b1c_full(0);
match (a) {
case let t: ((void | size), (void | size), size) => {
if (t.0 is void) { acc += 1; };
if (t.1 is size) { acc += t.1 as size; };
acc += t.2 * 10;
};
case let e: error => { assert(false); };
};
let b: (((void | size), (void | size), size) | error) = pr_b1c_full(1);
match (b) {
case let t: ((void | size), (void | size), size) => {
if (t.0 is size && t.0 as size == 3) { acc += 100; };
if (t.1 is void) { acc += 50; };
acc += t.2;
};
case let e: error => { assert(false); };
};
let c: (((void | size), (void | size), size) | error) = pr_b1c_full(2);
match (c) {
case let t: ((void | size), (void | size), size) => { assert(false); };
case let e: error => {
let s: str = e: str;
assert(len(s) == 25);
};
};
assert(acc: i32 == 235);
};

View File

@@ -0,0 +1,84 @@
// tupparam_test — runtime contract for #163, the tuple-PARAM ABI (the param
// twin of #164's tuple RETURN). Migrated from test/wcc/905_tupparam_run.c.
// A tuple passed AS AN ARGUMENT was unhandled in both stages: a tuple-typed
// call result left its elements in the return-ABI cursor and the SEND fell to
// the 1-GP-word else, so all but the first element was dropped. The fix
// restages the tuple into a frame slot by SysV class then pushes the slot
// words; both stages byte-identical (rule 10). GATE-BLIND TO BYTE-ID ALONE:
// pre-fix both stages were symmetric-WRONG, so cs==ww held — the rows diverge
// only at RUNTIME; these @test fns pin that runtime contract (T1), the byte-id
// gate (T2) keeps cs==ww. The loud-stop arg-reg-overflow row is a reject
// carrier (test/wcc/data/tupparam_gp_overflow_loudstop/).
package tupparam_test;
fn pair_f64f64_arg(a: f64, b: f64) (f64, f64) = { return (a, b); };
fn add_f64f64_arg(t: (f64, f64)) f64 = { return t.0 + t.1; };
fn pair_i64i64_arg(a: i64, b: i64) (i64, i64) = { return (a, b); };
fn add_i64i64_arg(t: (i64, i64)) i64 = { return t.0 + t.1; };
fn mk_f64i64_arg(a: f64, b: i64) (f64, i64) = { return (a, b); };
fn add_f64i64_arg(t: (f64, i64)) i64 = { return (t.0: i64) + t.1; };
fn mk_i64f64_arg(a: i64, b: f64) (i64, f64) = { return (a, b); };
fn add_i64f64_arg(t: (i64, f64)) i64 = { return t.0 + (t.1: i64); };
fn mk_f64str_arg(a: f64) (f64, str) = { return (a, "hello"); };
fn add_f64str_arg(t: (f64, str)) i64 = {
return (t.0: i64) + (t.1.len: i64);
};
fn pair_two_tuple_args(a: i64, b: i64) (i64, i64) = { return (a, b); };
fn add4_two_tuple_args(s: (i64, i64), t: (i64, i64)) i64 = {
return s.0 + s.1 + t.0 + t.1;
};
fn pair_f64f64_chain(a: f64, b: f64) (f64, f64) = { return (a, b); };
fn id_f64f64_chain(t: (f64, f64)) f64 = { return t.0 * 10.0 + t.1; };
// HEADLINE — (f64, f64) arg. Pre-fix the SEND pushes only AX (the two floats
// stay stranded in X0/X1) and the callee reads one GP word. Post-fix each
// float rides the SSE arg cursor (X0, X1).
@test fn f64f64_arg() void = {
assert(!(add_f64f64_arg(pair_f64f64_arg(3.0, 5.0)) != 8.0));
};
// (i64, i64) arg — proves the broader INTEGER-tuple-param drop is fixed
// (master dropped the second i64 too). e0->DI, e1->SI.
@test fn i64i64_arg() void = {
assert(!(add_i64i64_arg(pair_i64i64_arg(3, 5)) != 8));
};
// (f64, i64) — class independent of position: f64@X0 (SSE cursor), i64@DI
// (INTEGER cursor), independent counters.
@test fn f64i64_arg() void = {
assert(!(add_f64i64_arg(mk_f64i64_arg(3.0, 5)) != 8));
};
// (i64, f64) — order-swap: i64@DI, f64@X0. Confirms the float lands in the
// next XMM regardless of its positional slot.
@test fn i64f64_arg() void = {
assert(!(add_i64f64_arg(mk_i64f64_arg(3, 5.0)) != 8));
};
// (f64, str) — SSE + wide (24B {ptr,len,cap}) coexist. The f64 rides X0 (SSE,
// consuming no GP slot); the str rides DI/SI/DX (INTEGER cursor). f=4.0,
// s.len=5 -> 4+5 = 9.
@test fn f64str_arg() void = {
assert(!(add_f64str_arg(mk_f64str_arg(4.0)) != 9));
};
// MULTI-TUPLE-ARG, ONE CALL — f(g(), h()) where both args are tuple-producing
// calls. Proves @tupargscr (single-slot-per-fn) is REUSED per arg, not
// COLLIDED. 1+2+3+4 = 10.
@test fn two_tuple_args() void = {
assert(!(add4_two_tuple_args(pair_two_tuple_args(1, 2),
pair_two_tuple_args(3, 4)) != 10));
};
// CONTROL — a tuple arg threaded through a chain of two calls, proving the
// SEND/RECV round-trips through the slot intact.
@test fn f64f64_chain() void = {
assert(!(id_f64f64_chain(pair_f64f64_chain(3.0, 5.0)) != 35.0));
};

74
test/lang/tuprecv_test.ww Normal file
View File

@@ -0,0 +1,74 @@
// tuprecv_test — runtime + byte-id net for #102: the 16B whole-tuple-from-call
// receive (`let t = mk()`, then field reads t.0 / t.1). Migrated from
// test/wcc/954_tuprecv_run.c.
//
// Root: wwstage's cglet had NO 16B tuple-from-call receive branch, so a
// `let t = call()` whose callee returns a 2-eightbyte (16B) tuple fell through
// to the generic single-word store and never spilled word1 (the DX eightbyte)
// — silent loss of t.1. cstage has the branch (cmd/w6c/cgen.c:6652). The fix
// mirrors it in cglet. The all-integer (i64,i64) receive dropped DX too, so
// that "control" row is ALSO a fix row, not a pure no-op. The destructure form
// `let (a,b) = mk()` (cgmlet + tupstore cursor) was already byte-id; the
// wide/str-element 32B and the struct{i64,i64} 16B receives are over-catch
// guards (the sz==16 + rettupleof!=nil gate must not hijack them).
//
// The C driver ran each row on both stages + asserted cs==ww .s byte-id; here
// the cstage run is test-lang (T1) and the byte-id is test-lang-byteid (T2), so
// both dimensions survive.
//
// NOTE: the #121 asserttyped sub-dimension of ctl_destr (the C driver asserted
// w6c_ww emits NO `asserttyped:` diagnostic on the float destructure binding,
// the #121 A-narrow stamp net) is NOT carried by test-lang (no stderr grep);
// flagged to advisor drew, not silently dropped.
package tuprecv_test;
fn mk_f64_i64() (f64, i64) = { return (2.5, 7); };
fn mk_i64_f64() (i64, f64) = { return (7, 2.5); };
fn mk_i64_i64() (i64, i64) = { return (5, 7); };
fn mk_ctl_destr() (f64, i64) = { return (2.5, 7); };
fn mk_ctl_str() (i64, str) = { return (7, "hi"); };
type pair = struct { a: i64, b: i64 };
fn mk_ctl_struct() pair = { return pair { a = 5, b = 7 }; };
@test fn f64_i64() void = {
let t = mk_f64_i64();
let f: f64 = t.0;
let i: i64 = t.1;
assert((f: i32) + (i: i32) == 9);
};
@test fn i64_f64() void = {
let t = mk_i64_f64();
let i: i64 = t.0;
let f: f64 = t.1;
assert((i: i32) + (f: i32) == 9);
};
@test fn i64_i64() void = {
let t = mk_i64_i64();
let a: i64 = t.0;
let b: i64 = t.1;
assert((a: i32) + (b: i32) == 12);
};
@test fn ctl_destr() void = {
let (f, i) = mk_ctl_destr();
assert((f: i32) + (i: i32) == 9);
};
@test fn ctl_str() void = {
let t: (i64, str) = mk_ctl_str();
assert(t.0: i32 == 7);
};
@test fn ctl_struct() void = {
let t = mk_ctl_struct();
assert((t.a: i32) + (t.b: i32) == 12);
};

View File

@@ -1,308 +0,0 @@
/*
* 905_tupparam_run — runtime + byte-id net for #163, the tuple-PARAM ABI
* (the param twin of #164's tuple RETURN).
*
* THE BUG (#163, LIVE drop on master): a tuple passed AS AN ARGUMENT was
* unhandled in BOTH stages — no tuple arm in the cgcall arg push, the
* cgcall arg pop, OR the callee cgfnparams receive. A tuple-typed call
* result (`f(g())` where g returns a tuple) left its elements in the
* return-ABI cursor (AX/DX/CX/R8 + X0/X1, per #164); the SEND fell to the
* 1-GP-word `else` (PUSHQ AX / POPQ DI) so ALL BUT THE FIRST ELEMENT was
* dropped, and the callee read its tuple param as a single GP word. This
* broke INTEGER tuple params too; float elements were doubly lost (they
* ride X0/X1, never AX).
*
* THE FIX: per-element SysV class placement reusing #164's helper. SEND —
* cgexpr leaves the tuple in the return cursor; restage it into a frame
* slot (@tupargscr) by class via tuple_store/tupstore, then push the slot
* words high->low so the pop drains slot+0 first into the SysV ARG cursor
* (DI/SI/.. + X0..X7). The frame slot decouples the return-class regs
* (which OVERLAP the arg-class regs) from the arg placement. RECV — the
* callee walks the tuple's elements over the arg cursor, storing each into
* its frame slot positionally. Symmetric across cstage (cmd/w6c/cgen.c)
* and wwstage (cgenutil.ww pushargsrev + cgenexpr.ww cgcall pop +
* cgendecl.ww cgfnparams).
*
* SCOPE: register-class tuple ARGS produced by a CALL (the only form that
* materialises a tuple value today — `let t = (1,2)` as a first-class
* value is a separate unimplemented gap, so the SEND scopes to the N_CALL
* producer and never pushes stale regs, rule 7). Arg-register overflow
* loud-stops (the partial-spill stitch is out of scope, twin of #164's
* cap); the loudstop row asserts BOTH stages ERROR.
*
* GATE-BLIND TO BYTE-ID ALONE: the bootstrap passes no tuple params, and
* pre-fix both stages were symmetric-WRONG (both PUSHQ AX), so the cs==ww
* .s gate HOLDS on master for the value rows — they diverge only at
* RUNTIME. Each value row carries BOTH dimensions (modelled on 956):
* (a) cstage `ww build` + run, asserting the exit code (catches #163:
* master returns the wrong exit / dropped element).
* (b) w6c vs w6c_ww `.s` cmp (rule-10: both stages fixed identically).
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
struct row {
const char *label;
const char *src;
int want_exit;
int want_compile_fail; /* loud-stop rows must NOT compile */
};
static const struct row rows[] = {
/* HEADLINE — (f64, f64) arg. Pre-fix the SEND pushes only AX (the
* two floats stay stranded in X0/X1) and the callee reads one GP
* word; t.0+t.1 != 8.0 -> return 1. Post-fix each float rides the
* SSE arg cursor (X0,X1). */
{ "f64f64_arg",
"package main;\n"
"fn pair(a: f64, b: f64) (f64, f64) = { return (a, b); };\n"
"fn add(t: (f64, f64)) f64 = { return t.0 + t.1; };\n"
"export fn main() i32 = {\n"
"\tif (add(pair(3.0, 5.0)) != 8.0) { return 1; };\n"
"\treturn 0;\n"
"};\n", 0 },
/* (i64, i64) arg — proves the broader INTEGER-tuple-param drop is
* fixed (master dropped the second i64 too). e0->DI, e1->SI. */
{ "i64i64_arg",
"package main;\n"
"fn pair(a: i64, b: i64) (i64, i64) = { return (a, b); };\n"
"fn add(t: (i64, i64)) i64 = { return t.0 + t.1; };\n"
"export fn main() i32 = {\n"
"\tif (add(pair(3, 5)) != 8) { return 1; };\n"
"\treturn 0;\n"
"};\n", 0 },
/* (f64, i64) — class independent of position: f64@X0 (SSE cursor),
* i64@DI (INTEGER cursor), independent counters. */
{ "f64i64_arg",
"package main;\n"
"fn mk(a: f64, b: i64) (f64, i64) = { return (a, b); };\n"
"fn add(t: (f64, i64)) i64 = { return (t.0: i64) + t.1; };\n"
"export fn main() i32 = {\n"
"\tif (add(mk(3.0, 5)) != 8) { return 1; };\n"
"\treturn 0;\n"
"};\n", 0 },
/* (i64, f64) — order-swap: i64@DI, f64@X0. Confirms the float lands
* in the next XMM regardless of its positional slot. */
{ "i64f64_arg",
"package main;\n"
"fn mk(a: i64, b: f64) (i64, f64) = { return (a, b); };\n"
"fn add(t: (i64, f64)) i64 = { return t.0 + (t.1: i64); };\n"
"export fn main() i32 = {\n"
"\tif (add(mk(3, 5.0)) != 8) { return 1; };\n"
"\treturn 0;\n"
"};\n", 0 },
/* (f64, str) — SSE + wide (24B {ptr,len,cap}) coexist. The f64
* rides X0 (SSE, consuming no GP slot); the str rides DI/SI/DX
* (INTEGER cursor). f=4.0, s.len=5 -> 4+5 = 9. */
{ "f64str_arg",
"package main;\n"
"fn mk(a: f64) (f64, str) = { return (a, \"hello\"); };\n"
"fn add(t: (f64, str)) i64 = {\n"
"\treturn (t.0: i64) + (t.1.len: i64);\n"
"};\n"
"export fn main() i32 = {\n"
"\tif (add(mk(4.0)) != 9) { return 1; };\n"
"\treturn 0;\n"
"};\n", 0 },
/* MULTI-TUPLE-ARG, ONE CALL — f(g(), h()) where BOTH args are
* tuple-producing calls. Proves @tupargscr (single-slot-per-fn) is
* REUSED per arg, not COLLIDED: pushargsrev evals right-to-left, so
* h() restages into the slot + drains it to the stack BEFORE g()
* restages into the SAME slot (h's words already pushed, safe to
* overwrite). Drain forward: s->DI,SI; t->DX,CX. 1+2+3+4 = 10. A
* collision (both restaged before either pushed) would corrupt the
* first-pushed tuple's words. */
{ "two_tuple_args",
"package main;\n"
"fn pair(a: i64, b: i64) (i64, i64) = { return (a, b); };\n"
"fn add4(s: (i64, i64), t: (i64, i64)) i64 = {\n"
"\treturn s.0 + s.1 + t.0 + t.1;\n"
"};\n"
"export fn main() i32 = {\n"
"\tif (add4(pair(1, 2), pair(3, 4)) != 10) { return 1; };\n"
"\treturn 0;\n"
"};\n", 0 },
/* CONTROL — a tuple arg threaded through a chain of two calls,
* proving the SEND/RECV round-trips through the slot intact. */
{ "f64f64_chain",
"package main;\n"
"fn pair(a: f64, b: f64) (f64, f64) = { return (a, b); };\n"
"fn id(t: (f64, f64)) f64 = { return t.0 * 10.0 + t.1; };\n"
"export fn main() i32 = {\n"
"\tif (id(pair(3.0, 5.0)) != 35.0) { return 1; };\n"
"\treturn 0;\n"
"};\n", 0 },
/* LOUD-STOP — 5 i64 scalars + an (i64,i64) tuple = 7 INTEGER arg
* eightbytes, overflowing the 6 GP arg regs (DI/SI/DX/CX/R8/R9).
* The partial-spill stitch is out of scope (twin of #164's cap), so
* BOTH stages must FAIL TO COMPILE (rule 7: surface, never silently
* drop). Master has no tuple-arg arm (pushes the tuple as 1 word ->
* 6 GP, no overflow) and builds the miscompile, so want_compile_fail
* discriminates. */
{ "gp_overflow_loudstop",
"package main;\n"
"fn pair(a: i64, b: i64) (i64, i64) = { return (a, b); };\n"
"fn f(a: i64, b: i64, c: i64, d: i64, e: i64, t: (i64, i64)) i64 = {\n"
"\treturn a + b + c + d + e + t.0 + t.1;\n"
"};\n"
"export fn main() i32 = {\n"
"\treturn (f(1, 2, 3, 4, 5, pair(6, 7)): i32);\n"
"};\n", 0, 1 },
{ NULL, NULL, 0, 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, "tupparam: w6c_ww missing — cannot run the "
"cs==ww byte-id gate\n");
return 1;
}
int n = 0, fail = 0;
for (int i = 0; rows[i].src; i++, n++) {
char tmpdir[64], src[128], outbin[128], rmcmd[160];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwtupp_%d_d_%d",
getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/wwtupp_%d_%d.ww",
tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/wwtupp_%d_%d",
tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (f == NULL) { fail++; runwait(rmcmd); continue; }
fputs(rows[i].src, f);
fclose(f);
char cmd[2048];
/* LOUD-STOP rows: the arg-reg overflow must FAIL TO COMPILE in
* BOTH stages (rule 7). Assert (a) cstage `ww build` errors and
* (b) w6c AND w6c_ww each return non-zero. No .s is produced, so
* the byte-id cmp is skipped. */
if (rows[i].want_compile_fail) {
snprintf(cmd, sizeof cmd,
"%s/ww build -o %s %s >/dev/null 2>&1",
bin, outbin, src);
if (runwait(cmd) == 0) {
fprintf(stderr, "row[%s]: cstage build SUCCEEDED, "
"want loud-stop (arg-reg overflow)\n",
rows[i].label);
fail++;
}
snprintf(cmd, sizeof cmd, "%s -o /dev/null %s 2>/dev/null",
w6c, src);
if (runwait(cmd) == 0) {
fprintf(stderr, "row[%s]: w6c emitted .s, want "
"loud-stop\n", rows[i].label);
fail++;
}
snprintf(cmd, sizeof cmd, "%s -o /dev/null %s 2>/dev/null",
w6c_ww, src);
if (runwait(cmd) == 0) {
fprintf(stderr, "row[%s]: w6c_ww emitted .s, want "
"loud-stop\n", rows[i].label);
fail++;
}
runwait(rmcmd);
continue;
}
/* (a) cstage build + run in a scratch dir. */
snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s",
bin, outbin, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: cstage build failed\n",
rows[i].label);
fail++;
runwait(rmcmd);
continue;
}
int got = runwait(outbin);
if (got != rows[i].want_exit) {
fprintf(stderr, "row[%s]: cstage exit %d, want %d\n",
rows[i].label, got, rows[i].want_exit);
fail++;
}
/* (b) cs==ww byte-id gate: emit .s from both stages, cmp. */
char cs_s[160], ws_s[160];
snprintf(cs_s, sizeof cs_s, "%s/wwtupp_%d_%d_cs.s",
tmpdir, getpid(), i);
snprintf(ws_s, sizeof ws_s, "%s/wwtupp_%d_%d_ww.s",
tmpdir, getpid(), i);
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c, cs_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label);
fail++; runwait(rmcmd); continue;
}
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c_ww, ws_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c_ww failed\n",
rows[i].label);
fail++; runwait(rmcmd); continue;
}
if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr,
"row[%s]: cstage/wwstage .s DIFFER (rule-10 "
"byte-id violation)\n", rows[i].label);
fail++;
}
runwait(rmcmd);
}
if (fail) {
fprintf(stderr, "%d/%d tuple-param tests failed\n", fail, n);
return 1;
}
printf("tupparam: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n);
return 0;
}

View File

@@ -1,526 +0,0 @@
/*
* 936_tagged_tuple_widen_run — #66 (fold-4 prereq): the cast-wrapped
* tuple literal `((a, b): variant_alias)` widened into a tagged-union
* payload. Pre-#66 the #242 tuple arm of the widen choke-point
* (cg_widen_tagged_store / cgwidentaggedstorebp) gated on a BARE
* N_TUPLE source; the cast-to-CONCRETE-VARIANT wrapper — the only
* spelling real code uses (ref/hare/regex/regex.ha:213
* `(start_b, end_b): charset_range_item`) — was not peeled (it is not
* a widen-cast: its destination is the variant, not the union), fell
* to the SCALAR arm and silently stored cursor word 0 only, zero-
* filling payload slot 1+ (both stages, byte-id — gate-blind). The
* read side (match-extract slot walk) was always correct.
*
* row | shape | want
* -----------------------+--------------------------------------+-----
* cast_let_packed | let v: item = ((65,90): rng); .0/.1 | 0
* cast_let_3member | + (str,*fn) member present, never |
* | constructed (layout-neutral pin) | 0
* cast_append_local | append(xs, cast) → index → extract | 0
* cast_append_indexplace | append(cs[len-1], cast), [][]item | 0
* cast_append_derefplace | through *[][]item param (via_outer) | 0
* cast_append_ptrfield | append(p.cs[len-1], cast) — ptr- |
* | FIELD-rooted place | 0
* cast_ident_elems | (start_b, end_b) u32 LOCALS — the |
* | handle_bracket shape | 0
* cast_float_elem | (u32, f64) — MOVSD element store | 0
* cast_str_elem | (i64, str) — 3-word wide element | 0
* cast_arg_direct | cast tuple as a DIRECT tagged ARG — |
* | cg_widen_tagged_push routes tuple |
* | srcs to the scratch store (the fast |
* | scalar push dropped slot 1+) | 0
* bare_ident_arg | bare (a, b) typed idents as a tagged |
* | ARG — same push routing, tag from |
* | the typed tuple | 0
* bare_ident_no_regress | bare (a, b) typed-ident return into |
* | tagged — the strconv parseint shape | 0
* bare_literal_reject | bare (5, 6) literal-element tuple |
* | into tagged stays the #242/#241 loud |
* | stop (the cast form lifts it; the |
* | bare form must NOT silently change) | err
* tuple_ident_widen | tuple-typed IDENT into tagged — #116 |
* | addressable block-copy arm (was the |
* | #72 loud-stop); both words round-trip| 0
*
* Every K_RUN row also asserts cstage/wwstage asm byte-id. NNN<950,
* self-contained (/tmp, no imports) — rule-14's selfhost-sibling race
* does not apply (941 precedent).
*/
#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;
}
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa), cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
#define K_RUN 0 /* build+run both drivers, exit==want, + cs==ww byte-id */
#define K_BUILDERR 1 /* build must FAIL with experr on BOTH drivers (rule 7) */
struct row { const char *label; const char *src; int want;
int kind; const char *experr; };
/* errlog_has — a BUILDERR row must fail WITH its diagnostic; any other
* failure (parse error, crash) is a vacuous reject (940 precedent). */
static int
errlog_has(const char *path, const char *needle)
{
FILE *f = fopen(path, "rb");
if (!f) return 0;
char buf[8192];
size_t got = fread(buf, 1, sizeof buf - 1, f);
fclose(f);
buf[got] = '\0';
return strstr(buf, needle) != NULL;
}
static const struct row rows[] = {
{ "cast_let_packed",
"package main;\n"
"type lit = rune;\n"
"type rng = (u32, u32);\n"
"type item = (lit | rng);\n"
"export fn main() i32 = {\n"
" let v: item = ((65, 90): rng);\n"
" match (v) {\n"
" case let r: rng => {\n"
" if (r.0 != 65) { return 1; };\n"
" if (r.1 != 90) { return 2; };\n"
" };\n"
" case => return 3;\n"
" };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* the real charset element: the (str,*fn) class member is PRESENT
* IN THE TYPE but never constructed — its presence must not alter
* the lit/range slot layout or walks (fold-4 PF1 caveat). */
{ "cast_let_3member",
"package main;\n"
"type lit = rune;\n"
"type rng = (u32, u32);\n"
"type cls = (str, *fn(c: rune) bool);\n"
"type item = (lit | rng | cls);\n"
"export fn main() i32 = {\n"
" let v: item = ((65, 90): rng);\n"
" match (v) {\n"
" case let r: rng => {\n"
" if (r.0 != 65) { return 1; };\n"
" if (r.1 != 90) { return 2; };\n"
" };\n"
" case => return 3;\n"
" };\n"
" let w: item = ('a': lit);\n"
" match (w) {\n"
" case let l: lit => { if ((l: rune) != 'a') { return 4; }; };\n"
" case => return 5;\n"
" };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
{ "cast_append_local",
"package main;\n"
"type lit = rune;\n"
"type rng = (u32, u32);\n"
"type item = (lit | rng);\n"
"export fn main() i32 = {\n"
" let xs: []item;\n"
" append(xs, ('a': lit));\n"
" append(xs, ((65, 90): rng));\n"
" if (len(xs) != 2) { return 1; };\n"
" let e: item = xs[1];\n"
" match (e) {\n"
" case let r: rng => {\n"
" if (r.0 != 65) { return 2; };\n"
" if (r.1 != 90) { return 3; };\n"
" };\n"
" case => return 4;\n"
" };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* append place = indexed inner slice — the handle_bracket
* `append(charsets[len(charsets)-1], ...)` route. */
{ "cast_append_indexplace",
"package main;\n"
"type lit = rune;\n"
"type rng = (u32, u32);\n"
"type item = (lit | rng);\n"
"export fn main() i32 = {\n"
" let charsets: [][]item;\n"
" let empty: []item;\n"
" append(charsets, empty);\n"
" append(charsets[len(charsets) - 1], ((7, 9): rng));\n"
" if (len(charsets[0]) != 1) { return 1; };\n"
" let e: item = charsets[0][0];\n"
" match (e) {\n"
" case let r: rng => {\n"
" if (r.0 != 7) { return 2; };\n"
" if (r.1 != 9) { return 3; };\n"
" };\n"
" case => return 4;\n"
" };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* append place behind a *[][]item param — the via_outer
* (pointer-rooted) widen route. */
{ "cast_append_derefplace",
"package main;\n"
"type lit = rune;\n"
"type rng = (u32, u32);\n"
"type item = (lit | rng);\n"
"fn addrange(cs: *[][]item, a: u32, b: u32) void = {\n"
" if (len(*cs) == 0) {\n"
" let empty: []item;\n"
" append(*cs, empty);\n"
" };\n"
" append((*cs)[len(*cs) - 1], ((a, b): rng));\n"
"};\n"
"export fn main() i32 = {\n"
" let charsets: [][]item;\n"
" addrange(&charsets, 5, 8);\n"
" if (len(charsets) != 1) { return 1; };\n"
" if (len(charsets[0]) != 1) { return 2; };\n"
" let e: item = charsets[0][0];\n"
" match (e) {\n"
" case let r: rng => {\n"
" if (r.0 != 5) { return 3; };\n"
" if (r.1 != 8) { return 4; };\n"
" };\n"
" case => return 5;\n"
" };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* append place rooted at a ptr-FIELD ([][]item field through
* *struct) — the re.charsets-style place spelling. */
{ "cast_append_ptrfield",
"package main;\n"
"type lit = rune;\n"
"type rng = (u32, u32);\n"
"type item = (lit | rng);\n"
"type holder = struct { cs: [][]item, n: i64 };\n"
"export fn main() i32 = {\n"
" let h: holder = holder { n = 0, ... };\n"
" let p: *holder = &h;\n"
" let empty: []item;\n"
" append(p.cs, empty);\n"
" append(p.cs[len(p.cs) - 1], ((7, 9): rng));\n"
" if (len(h.cs[0]) != 1) { return 1; };\n"
" match (h.cs[0][0]) {\n"
" case let r: rng => {\n"
" if (r.0 != 7) { return 2; };\n"
" if (r.1 != 9) { return 3; };\n"
" };\n"
" case => return 4;\n"
" };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* typed u32 LOCALS as the tuple elements — handle_bracket's
* exact `(start_b, end_b): charset_range_item` shape. */
{ "cast_ident_elems",
"package main;\n"
"type lit = rune;\n"
"type rng = (u32, u32);\n"
"type item = (lit | rng);\n"
"export fn main() i32 = {\n"
" let start_b: u32 = 97;\n"
" let end_b: u32 = 122;\n"
" let v: item = ((start_b, end_b): rng);\n"
" match (v) {\n"
" case let r: rng => {\n"
" if (r.0 != 97) { return 1; };\n"
" if (r.1 != 122) { return 2; };\n"
" };\n"
" case => return 3;\n"
" };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* float element rides the MOVSD store branch under the cast form */
{ "cast_float_elem",
"package main;\n"
"type mf = (u32, f64);\n"
"type item = (rune | mf);\n"
"export fn main() i32 = {\n"
" let a: u32 = 9;\n"
" let x: f64 = 2.5;\n"
" let v: item = ((a, x): mf);\n"
" match (v) {\n"
" case let m: mf => {\n"
" if (m.0 != 9) { return 1; };\n"
" if (m.1 != 2.5) { return 2; };\n"
" };\n"
" case => return 3;\n"
" };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* str element rides the 3-word wide branch under the cast form */
{ "cast_str_elem",
"package main;\n"
"type ms = (i64, str);\n"
"type item = (rune | ms);\n"
"export fn main() i32 = {\n"
" let n: i64 = 12;\n"
" let s: str = \"wxyz\";\n"
" let v: item = ((n, s): ms);\n"
" match (v) {\n"
" case let m: ms => {\n"
" if (m.0 != 12) { return 1; };\n"
" if (len(m.1) != 4) { return 2; };\n"
" };\n"
" case => return 3;\n"
" };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* cast tuple passed DIRECTLY as a tagged call arg — pre-fix
* cg_widen_tagged_push's scalar fast path pushed word 0 only
* (and coerced an unresolved tag to 0); tuple-typed sources now
* route through the scratch store choke-point. */
{ "cast_arg_direct",
"package main;\n"
"type lit = rune;\n"
"type rng = (u32, u32);\n"
"type item = (lit | rng);\n"
"fn check(v: item) i32 = {\n"
" match (v) {\n"
" case let r: rng => {\n"
" if (r.0 != 65) { return 1; };\n"
" if (r.1 != 90) { return 2; };\n"
" return 0;\n"
" };\n"
" case => return 3;\n"
" };\n"
"};\n"
"export fn main() i32 = {\n"
" return check(((65, 90): rng));\n"
"};\n", 0, K_RUN, NULL },
/* bare typed-ident tuple as a tagged call arg — the strconv
* `(neg, n)` shape at a CALL boundary; rides the same push
* routing, tag resolved from the typed tuple. */
{ "bare_ident_arg",
"package main;\n"
"type item = (rune | (i64, i64));\n"
"fn check(v: item) i32 = {\n"
" match (v) {\n"
" case let r: (i64, i64) => {\n"
" if (r.0 != 5) { return 1; };\n"
" if (r.1 != 6) { return 2; };\n"
" return 0;\n"
" };\n"
" case => return 3;\n"
" };\n"
"};\n"
"export fn main() i32 = {\n"
" let a: i64 = 5;\n"
" let b: i64 = 6;\n"
" return check((a, b));\n"
"};\n", 0, K_RUN, NULL },
/* bare N_TUPLE of TYPED idents (the strconv parseint `(neg, n)`
* return shape) must keep working — no-regress on the bare arm. */
{ "bare_ident_no_regress",
"package main;\n"
"fn mk() ((i64, i64) | nomem) = {\n"
" let a: i64 = 5;\n"
" let b: i64 = 6;\n"
" return (a, b);\n"
"};\n"
"export fn main() i32 = {\n"
" match (mk()) {\n"
" case let t: (i64, i64) => {\n"
" if (t.0 != 5) { return 1; };\n"
" if (t.1 != 6) { return 2; };\n"
" };\n"
" case => return 3;\n"
" };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* bare literal-element tuple into tagged stays the #242/#241
* loud-stop — the cast form lifts it (the cast supplies the
* variant type), the bare form must not silently change. */
{ "bare_literal_reject",
"package main;\n"
"fn mk() ((i64, i64) | nomem) = {\n"
" return (5, 6);\n"
"};\n"
"export fn main() i32 = {\n"
" match (mk()) {\n"
" case let t: (i64, i64) => { if (t.0 != 5) { return 1; }; };\n"
" case => return 2;\n"
" };\n"
" return 0;\n"
"};\n", 0,
K_BUILDERR, "tuple-in-union variant tag unresolved" },
/* #116 GRADUATION: a tuple-typed IDENT source widened into a tagged
* slot. Pre-#116 the widen choke-point had no arm for it and loud-
* stopped (the row pinned that loud); #116 added the addressable
* block-copy arm, so the IDENT source now boxes correctly — both
* payload words round-trip (r.0 AND r.1). Companion shapes (index,
* deref) + the runtime read-back live in 944_nonlit_tuple_widen_run. */
{ "tuple_ident_widen",
"package main;\n"
"type lit = rune;\n"
"type rng = (u32, u32);\n"
"type item = (lit | rng);\n"
"export fn main() i32 = {\n"
" let t: rng = ((65, 90): rng);\n"
" let v: item = t;\n"
" match (v) {\n"
" case let r: rng => {\n"
" if (r.0 != 65) { return 1; };\n"
" if (r.1 != 90) { return 3; };\n"
" };\n"
" case => return 2;\n"
" };\n"
" return 0;\n"
"};\n", 0,
K_RUN, NULL },
};
/* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[96], src[160], outbin[160], errf[160], rmcmd[176], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/ttw_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/ttw_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/ttw_%d_%d", tmpdir, getpid(), i);
snprintf(errf, sizeof errf, "%s/err", tmpdir);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
driver, outbin, src, errf);
int brc = runwait(cmd);
if (r->kind == K_BUILDERR) {
int ok = (brc != 0)
&& (r->experr == NULL || errlog_has(errf, r->experr));
if (!ok)
fprintf(stderr, "row[%s]: %s expected loud builderr "
"\"%s\" (brc=%d)\n", r->label, driver,
r->experr ? r->experr : "", brc);
runwait(rmcmd);
return ok ? 0 : 1;
}
if (brc != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
if (got != r->want) {
fprintf(stderr, "row[%s]: %s exit %d, want %d\n",
r->label, driver, got, r->want);
return 1;
}
return 0;
}
/* cs==ww .s byte-id (rule 10). */
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[96], cs[96], ws[96], cmd[1024];
snprintf(src, sizeof src, "/tmp/ttw_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/ttw_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/ttw_asm_%d_%d_w.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
unlink(src);
return -1;
}
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
bin, ws, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
unlink(src); unlink(cs);
return -1;
}
int rc = slurp_eq(cs, ws);
if (rc != 0)
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
r->label);
unlink(src); unlink(cs); unlink(ws);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[2080];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[2120], wdrv[2120];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
int n = (int)(sizeof rows / sizeof rows[0]);
int total = 0, fail = 0;
for (int i = 0; i < n; i++) {
total++;
if (run_driver(cdrv, &rows[i], i) != 0) fail++;
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (run_driver(wdrv, &rows[i], i) != 0) fail++;
}
for (int i = 0; i < n; i++) {
if (rows[i].kind == K_BUILDERR)
continue;
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0) fail++;
}
}
if (fail) {
fprintf(stderr, "tagged_tuple_widen: %d/%d checks failed\n",
fail, total);
return 1;
}
printf("tagged_tuple_widen: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,257 +0,0 @@
/*
* 940_mixed_scalar_tuple_sret_run — project #240: an over-cap tuple whose
* elements mix a scalar with slices/str (e.g. `(int, []u8, str)`) must lay
* out IDENTICALLY in both stages and round-trip through an sret return.
*
* Two SILENT, gate-blind (no bootstrap mixed-scalar tuple) cs!=ww
* divergences met here:
*
* (a) callee SEND (cstage cgen.c N_RETURN over-cap-tuple arm): the packed
* element offset advanced by the LITERAL expression's type size, not
* the declared return-type element size. A bare int literal element is
* stamped TY_UNTYPED_INT (size 0), so `foff += e->type->size` added 0
* for a leading scalar — the next element clobbered it at offset 0 and
* every trailing element packed 8 bytes low. wwstage already walked the
* return-type tuple (c.fnret.list) for the size, so the stages diverged
* on the callee frame's store offsets. Fix: size foff from cg_ret_type's
* tuple params (rule-13 type table), mirroring wwstage.
*
* (b) caller RECEIVE (wwstage cgenstmt.ww cglet N_TTUPLE arm): the register
* tuple-receive branch (designed for in-cap 16/32B tuples) had NO
* capacity gate, so a 56B over-cap tuple was received via AX/DX/CX/R8
* (+ R8 fill) instead of from the sret dest the callee actually wrote.
* cstage gates that branch on `sz == 16 || sz == 32` and falls an
* over-cap tuple through to the sret receive. Fix: add the same size
* gate to wwstage.
*
* Each program assigns DISTINCT, checkable values to every element (a scalar
* read directly, a slice/str via len()), and returns 0 only when all read
* back correctly — so a wrong offset (either bug) yields a nonzero exit.
* Both an annotated and an inferred `let t` are covered (same receive path).
*
* All K_RUN: build+run exit 0 on BOTH drivers AND cs==ww byte-identical.
* NNN<950, self-contained (/tmp, no imports), so rule-14's selfhost-sibling
* race does not apply (903/940/945 precedent).
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa), cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
struct row { const char *label; const char *src; int want; };
static const struct row rows[] = {
/* leading scalar (the canonical #240 case), annotated let. */
{ "int_slice_str",
"package main;\n"
"fn mk() (int, []u8, str) = {\n"
" let b: []u8; b.len = 5; b.cap = 9;\n"
" let s: str = \"abcd\";\n"
" return (42, b, s);\n"
"};\n"
"export fn main() i32 = {\n"
" let t: (int, []u8, str) = mk();\n"
" if (t.0 != 42) { return 1; };\n"
" if (len(t.1) != 5) { return 2; };\n"
" if (len(t.2) != 4) { return 3; };\n"
" return 0;\n"
"};\n", 0 },
/* same shape, inferred `let t = mk();` — exercises the receive
* path that picks up the type from the callee return. */
{ "int_slice_str_inferred",
"package main;\n"
"fn mk() (int, []u8, str) = {\n"
" let b: []u8; b.len = 6; b.cap = 8;\n"
" let s: str = \"xyz\";\n"
" return (17, b, s);\n"
"};\n"
"export fn main() i32 = {\n"
" let t = mk();\n"
" if (t.0 != 17) { return 1; };\n"
" if (len(t.1) != 6) { return 2; };\n"
" if (len(t.2) != 3) { return 3; };\n"
" return 0;\n"
"};\n", 0 },
/* trailing scalar: foff must reach the last element's offset. */
{ "slice_str_int",
"package main;\n"
"fn mk() ([]u8, str, int) = {\n"
" let b: []u8; b.len = 7; b.cap = 9;\n"
" let s: str = \"hello\";\n"
" return (b, s, 99);\n"
"};\n"
"export fn main() i32 = {\n"
" let t: ([]u8, str, int) = mk();\n"
" if (len(t.0) != 7) { return 1; };\n"
" if (len(t.1) != 5) { return 2; };\n"
" if (t.2 != 99) { return 3; };\n"
" return 0;\n"
"};\n", 0 },
/* scalar in the middle. */
{ "str_int_slice",
"package main;\n"
"fn mk() (str, int, []u8) = {\n"
" let s: str = \"abcde\";\n"
" let b: []u8; b.len = 3; b.cap = 4;\n"
" return (s, 77, b);\n"
"};\n"
"export fn main() i32 = {\n"
" let t: (str, int, []u8) = mk();\n"
" if (len(t.0) != 5) { return 1; };\n"
" if (t.1 != 77) { return 2; };\n"
" if (len(t.2) != 3) { return 3; };\n"
" return 0;\n"
"};\n", 0 },
};
/* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[96], src[128], outbin[128], errf[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/mxst_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/mxst_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/mxst_%d_%d", tmpdir, getpid(), i);
snprintf(errf, sizeof errf, "%s/err", tmpdir);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
driver, outbin, src, errf);
int brc = runwait(cmd);
if (brc != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
if (got != r->want) {
fprintf(stderr, "row[%s]: %s exit %d, want %d\n",
r->label, driver, got, r->want);
return 1;
}
return 0;
}
/* cs==ww .s byte-id (rule 10). */
static int
byteid(const char *w6c, const char *w6c_ww, const struct row *r, int i)
{
char src[96], cs_s[96], ws_s[96], cmd[1024];
snprintf(src, sizeof src, "/tmp/mxst_bi_%d_%d.ww", getpid(), i);
snprintf(cs_s, sizeof cs_s, "/tmp/mxst_bi_%d_%d_cs.s", getpid(), i);
snprintf(ws_s, sizeof ws_s, "/tmp/mxst_bi_%d_%d_ww.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
int rc = 0;
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", r->label); rc = 1; }
else {
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", r->label); rc = 1; }
else if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr, "row[%s]: cstage/wwstage .s DIFFER "
"(#240 mixed-scalar tuple sret-layout regression)\n",
r->label);
rc = 1;
}
}
unlink(src); unlink(cs_s); unlink(ws_s);
return rc;
}
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], w6c[640], w6c_ww[640];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
struct { const char *name; const char *path; 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].path, X_OK) != 0) {
fprintf(stderr, "mixed_scalar_tuple_sret: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
total++;
if (run_driver(drivers[d].path, &rows[i], i) != 0) fail++;
}
}
if (access(w6c_ww, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (byteid(w6c, w6c_ww, &rows[i], i) != 0) fail++;
}
}
if (fail) {
fprintf(stderr, "mixed_scalar_tuple_sret: %d/%d checks failed\n",
fail, total);
return 1;
}
printf("mixed_scalar_tuple_sret: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,347 +0,0 @@
/*
* 940_overcap_tuple_field_store_run — project #234: storing the result of an
* over-capacity tuple-returning call (sret: > 4 GP / > 2 SSE eightbytes,
* e.g. ([]u8,[]u8) = 6 GP) into a struct field or an indexed lvalue.
*
* The receive end (project #10 Fold B) wired single-var-let / destructure /
* reassign / return-forward, but a FIELD or INDEXED dest stayed unwired: the
* store dropped the callee's sret body (a truncated MOVQ through a stale
* RDI), a SILENT miscompile gate-blind because the bootstrap never field-
* stores a wide tuple. The fix points the callee's hidden sret dest (RDI)
* straight at the destination slot when it is a LOCAL (BP-relative), so the
* callee writes the WHOLE tuple there:
* - LOCAL struct field `s.f = wide();` dest = s.off + field.off.
* - INDEXED local array `arr[i] = wide();` with a CONSTANT index — dest
* = arr.off + idx*esz (the only indexed form whose
* dest is a static BP offset).
* Every other dest — via_ptr field (`p.f`), global field (`g.f`), runtime /
* slice / pointer index — needs a runtime RDI-pointer dest (deferred to
* #234-tail) and HARD-ERRORS LOUD, never a silent truncating store (rule 7).
*
* Both stages emit byte-identically (rule 10); the wwstage struct-field slot
* is correctly sized only with #237 underneath (a tuple struct field's slot
* width), committed first in this branch.
*
* Rows (cap != len in every wide element so a dropped len OR cap is caught):
* A indexed_local `arr[1] = wide()`, arr: [2]([]u8,[]u8). RUN both stages
* + cs==ww byte-id. Readback via `(&arr[1]):*int` p[i]
* (a tuple-element READ `arr[1].N` is a SEPARATE gap,
* filed #238) — len0=1,cap0=5,len1=2,cap1=6.
* B struct_field `s.f = wide()`, S = struct { hdr:int, f:([]u8,[]u8) }.
* foff!=0 (after hdr) + hdr-uncorrupted check. RUN both
* stages + cs==ww byte-id, pointer readback `(&s.f):*int`.
* C via_ptr_be `p.f = wide()` (p:*S) — BUILDERR, loud #234-tail on
* BOTH drivers.
* D global_be `g.f = wide()` (g a struct global) — BUILDERR, loud.
* E rtindex_be `arr[idx()] = wide()` (runtime index) — BUILDERR, loud.
* F ndot_index_be `s.arr[1] = wide()` (N_DOT-base index, array field of a
* local) — BUILDERR, loud. Regression witness: this form
* shipped SILENT on wwstage when the N_INDEX arm keyed its
* entry on sretretsize(elemtn) — elemtn is a value node for
* an N_DOT base, so the gate returned 0 and fell through to
* the truncating store. Re-keying on callsretsize(rhs)
* (callee-return SSoT) fires the base-shape loud-stop.
* G chained_index_be `a[0][1] = wide()` (chained N_INDEX base) — same class,
* BUILDERR, loud on both stages.
*
* GATE POLARITY: A/B must build+run exit 0 on BOTH stages AND be cs==ww
* byte-identical; C/D/E must FAIL the build with the cited #234-tail
* diagnostic on BOTH stages (rule 7 — loud, not an incidental error).
* NNN<950, self-contained (/tmp, no imports), so rule-14's selfhost-sibling
* race does not apply (945/799 precedent).
*/
#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;
}
static int
file_contains(const char *path, const char *needle)
{
FILE *f = fopen(path, "rb");
if (!f) return 0;
char buf[8192];
size_t n = fread(buf, 1, sizeof buf - 1, f);
fclose(f);
buf[n] = '\0';
return strstr(buf, needle) != NULL;
}
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa), cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
#define K_RUN 0 /* build+run both drivers, exit==want, + cs==ww byte-id */
#define K_BUILDERR 1 /* build must FAIL with experr on both drivers */
struct row { const char *label; const char *src; int kind; int want;
const char *experr; };
static const struct row rows[] = {
{ "indexed_local",
"package main;\n"
"fn wide() ([]u8, []u8) = {\n"
" let a: []u8; a.len = 1; a.cap = 5;\n"
" let b: []u8; b.len = 2; b.cap = 6;\n"
" return (a, b);\n"
"};\n"
"export fn main() i32 = {\n"
" let arr: [2]([]u8, []u8);\n"
" arr[1] = wide();\n"
" let p: *int = (&arr[1]): *int;\n"
" if (p[1] != 1) { return 1; };\n"
" if (p[2] != 5) { return 2; };\n"
" if (p[4] != 2) { return 3; };\n"
" if (p[5] != 6) { return 4; };\n"
" return 0;\n"
"};\n", K_RUN, 0, NULL },
{ "struct_field",
"package main;\n"
"fn wide() ([]u8, []u8) = {\n"
" let a: []u8; a.len = 1; a.cap = 5;\n"
" let b: []u8; b.len = 2; b.cap = 6;\n"
" return (a, b);\n"
"};\n"
"type S = struct { hdr: int, f: ([]u8, []u8) };\n"
"export fn main() i32 = {\n"
" let s: S;\n"
" s.hdr = 99;\n"
" s.f = wide();\n"
" if (s.hdr != 99) { return 9; };\n"
" let p: *int = (&s.f): *int;\n"
" if (p[1] != 1) { return 1; };\n"
" if (p[2] != 5) { return 2; };\n"
" if (p[4] != 2) { return 3; };\n"
" if (p[5] != 6) { return 4; };\n"
" return 0;\n"
"};\n", K_RUN, 0, NULL },
{ "via_ptr_be",
"package main;\n"
"fn wide() ([]u8, []u8) = {\n"
" let a: []u8; a.len = 1; let b: []u8; b.len = 2;\n"
" return (a, b);\n"
"};\n"
"type S = struct { f: ([]u8, []u8) };\n"
"export fn main() i32 = {\n"
" let s: S;\n"
" let p: *S = &s;\n"
" p.f = wide();\n"
" return 0;\n"
"};\n", K_BUILDERR, 0, "#234-tail" },
{ "global_be",
"package main;\n"
"fn wide() ([]u8, []u8) = {\n"
" let a: []u8; a.len = 1; let b: []u8; b.len = 2;\n"
" return (a, b);\n"
"};\n"
"type S = struct { f: ([]u8, []u8) };\n"
"let g: S;\n"
"export fn main() i32 = {\n"
" g.f = wide();\n"
" return 0;\n"
"};\n", K_BUILDERR, 0, "#234-tail" },
{ "rtindex_be",
"package main;\n"
"fn wide() ([]u8, []u8) = {\n"
" let a: []u8; a.len = 1; let b: []u8; b.len = 2;\n"
" return (a, b);\n"
"};\n"
"fn ix() int = { return 0; };\n"
"export fn main() i32 = {\n"
" let arr: [2]([]u8, []u8);\n"
" arr[ix()] = wide();\n"
" return 0;\n"
"};\n", K_BUILDERR, 0, "#234-tail" },
/* N_DOT-base index `s.arr[1] = wide()`: array-field-of-local. The
* dest is a static BP offset but the N_INDEX arm only wires a plain
* N_IDENT base, so this is a deferred form — MUST loud-stop. Witness
* for the gap that shipped silently on wwstage when the arm was keyed
* on sretretsize(elemtn) (a value node for an N_DOT base → 0 → fell
* through to the truncating store); now keyed on callsretsize(rhs). */
{ "ndot_index_be",
"package main;\n"
"fn wide() ([]u8, []u8) = {\n"
" let a: []u8; a.len = 1; let b: []u8; b.len = 2;\n"
" return (a, b);\n"
"};\n"
"type S = struct { arr: [2]([]u8, []u8) };\n"
"export fn main() i32 = {\n"
" let s: S;\n"
" s.arr[1] = wide();\n"
" return 0;\n"
"};\n", K_BUILDERR, 0, "#234-tail" },
/* Chained index `a[0][1] = wide()`: N_INDEX base, same deferred class
* — loud-stop on both stages. */
{ "chained_index_be",
"package main;\n"
"fn wide() ([]u8, []u8) = {\n"
" let a: []u8; a.len = 1; let b: []u8; b.len = 2;\n"
" return (a, b);\n"
"};\n"
"export fn main() i32 = {\n"
" let a: [2][2]([]u8, []u8);\n"
" a[0][1] = wide();\n"
" return 0;\n"
"};\n", K_BUILDERR, 0, "#234-tail" },
};
/* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */
static int
run_driver(const char *driver, const struct row *r, int i)
{
char src[128], tmpdir[96], errf[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/ocf_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/ocf_%d_%d.ww", tmpdir, getpid(), i);
snprintf(errf, sizeof errf, "%s/ocf_%d_e_%d", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/ocf_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
driver, outbin, src, errf);
int brc = runwait(cmd);
if (r->kind == K_BUILDERR) {
int ok = (brc != 0)
&& (r->experr == NULL || file_contains(errf, r->experr));
if (!ok)
fprintf(stderr, "row[%s]: %s expected loud #234-tail "
"builderr (brc=%d)\n", r->label, driver, brc);
runwait(rmcmd);
return ok ? 0 : 1;
}
if (brc != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
if (got != r->want) {
fprintf(stderr, "row[%s]: %s exit %d, want %d\n",
r->label, driver, got, r->want);
return 1;
}
return 0;
}
/* cs==ww .s byte-id (rule 10) for a K_RUN row. */
static int
byteid(const char *w6c, const char *w6c_ww, const struct row *r, int i)
{
char src[96], cs_s[96], ws_s[96], cmd[1024];
snprintf(src, sizeof src, "/tmp/ocf_bi_%d_%d.ww", getpid(), i);
snprintf(cs_s, sizeof cs_s, "/tmp/ocf_bi_%d_%d_cs.s", getpid(), i);
snprintf(ws_s, sizeof ws_s, "/tmp/ocf_bi_%d_%d_ww.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
int rc = 0;
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", r->label); rc = 1; }
else {
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", r->label); rc = 1; }
else if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr, "row[%s]: cstage/wwstage .s DIFFER "
"(rule-10 byte-id)\n", r->label);
rc = 1;
}
}
unlink(src); unlink(cs_s); unlink(ws_s);
return rc;
}
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], w6c[640], w6c_ww[640];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
struct { const char *name; const char *path; 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].path, X_OK) != 0) {
fprintf(stderr, "overcap_tuple_field_store: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
total++;
if (run_driver(drivers[d].path, &rows[i], i) != 0) fail++;
}
}
/* cs==ww byte-id for the K_RUN rows. */
if (access(w6c_ww, X_OK) == 0) {
for (int i = 0; i < n; i++) {
if (rows[i].kind != K_RUN) continue;
total++;
if (byteid(w6c, w6c_ww, &rows[i], i) != 0) fail++;
}
}
if (fail) {
fprintf(stderr, "overcap_tuple_field_store: %d/%d checks failed\n",
fail, total);
return 1;
}
printf("overcap_tuple_field_store: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,344 +0,0 @@
/*
* 940_tuple_in_union_run — project #242: a mixed-scalar tuple WRAPPED IN A
* TAGGED UNION must construct, match-bind, and destructure IDENTICALLY in
* both stages and round-trip every element. BARE tuples already worked
* (#240/#83); only the union-wrapped variant was broken.
*
* Three SILENT, gate-blind (no bootstrap tuple-in-union) cs!=ww divergences
* met here, all on the `(neg, n)`-shape tuple Hare's strconv parseint returns
* (`((bool, u64) | invalid | overflow)`, stou.ha):
*
* (a) cstage CONSTRUCTION (cgen.c N_RETURN tagged arm): a tuple variant fell
* through the scalar shuffle, which ZEROED the whole value (MOVQ $0 to
* tag + payload) — the operands were never packed. Fix: route the tuple
* variant through the scratch-slot widen path (cg_widen_tagged_store's
* new TY_TUPLE arm), packing each element into the union payload + tag.
*
* (b) wwstage CHECKER (check.ww N_MLET): `let (a,b)=t` over a plain tuple
* ident (the match-bound union payload) left the un-annotated binders
* UNTYPED — the bin node reading them was untyped → asserttyped abort.
* The element-type distribution only fired for an N_CALL rhs. Fix:
* consume the rhs tuple type for ANY rhs (mirror cstage check.c:2017).
*
* (c) BOTH stages DESTRUCTURE (N_MLET / cgmlet): the register-cursor receive
* assumes the rhs left every element in AX/DX/CX (a call's tuple-return
* ABI). For a tuple IDENT cgexpr loads only word0->AX, so the 2nd binder
* read a STALE DX. Fix: copy each element from the ident's slot at the
* register-ABI 8B stride.
*
* Construction is correct at ANY variant position (the variant tag, not a
* default-0) — `tuple_tag1` and `tuple_after_int` place the tuple at index 1.
*
* SCOPE (rule 7 loud-stop, PINNED by the K_BUILDERR row): a tuple built
* from a BARE LITERAL element (cstage mis-types `true`/`false`/untyped `7` →
* tag unresolved, #241 literal-init family; wwstage types them but mirrors
* cstage's CONDITION down per rule 10) loud-stops in cgen on BOTH stages
* rather than silently miscompile. The old eightbyte-share loud-stop
* ((i32,i32,u64), #243) DISSOLVED with the slot-SSoT tuple layout (tuple
* arc C-t0) — that row graduated to K_RUN below. The K_RUN rows build
* their tuple from TYPED expressions — the supported, byte-identical shape.
*
* K_RUN rows: build+run exit 0 on BOTH drivers AND cs==ww byte-identical.
* K_BUILDERR rows: build FAILS with the #242 diagnostic on BOTH drivers.
* NNN<950, self-contained (/tmp, no imports), so rule-14's selfhost-sibling
* race does not apply (903/940/945 precedent).
*/
#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;
}
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa), cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
static int
file_contains(const char *path, const char *needle)
{
FILE *f = fopen(path, "rb");
if (!f) return 0;
char buf[8192];
size_t n = fread(buf, 1, sizeof buf - 1, f);
fclose(f);
buf[n] = '\0';
return strstr(buf, needle) != NULL;
}
#define K_RUN 0 /* build+run both drivers, exit==want, + cs==ww byte-id */
#define K_BUILDERR 1 /* build must FAIL with experr on BOTH drivers (rule 7) */
struct row { const char *label; const char *src; int kind; int want;
const char *experr; };
static const struct row rows[] = {
/* canonical #242 case: (bool,u64) at variant 0, match-bind +
* destructure, both elements checked. */
{ "bool_u64",
"package main;\n"
"fn mku(x: u64, s: bool) ((bool, u64) | void) = { return (s, x); };\n"
"export fn main() i32 = {\n"
" match (mku(7u64, true)) {\n"
" case let t: (bool, u64) => {\n"
" let (sg, u) = t;\n"
" if (u != 7u64) { return 1; };\n"
" if (!sg) { return 2; };\n"
" return 0;\n"
" };\n"
" case void => { return 3; };\n"
" };\n"
" return 4;\n"
"};\n", K_RUN, 0, NULL },
/* the void variant: bare `return;` packs the void tag, the match
* takes the void arm. The tuple branch still compiles (typed vars). */
{ "void_arm",
"package main;\n"
"fn mku(ok: bool, x: u64, s: bool) ((bool, u64) | void) = {\n"
" if (ok) { return (s, x); };\n"
" return;\n"
"};\n"
"export fn main() i32 = {\n"
" match (mku(false, 1u64, true)) {\n"
" case let t: (bool, u64) => { return 1; };\n"
" case void => { return 0; };\n"
" };\n"
" return 2;\n"
"};\n", K_RUN, 0, NULL },
/* tuple at variant 1 (`(void | (bool,u64))`): the construct must emit
* tag 1, not a default 0 — and the match must dispatch to it. */
{ "tuple_tag1",
"package main;\n"
"fn mku(x: u64, s: bool) (void | (bool, u64)) = { return (s, x); };\n"
"export fn main() i32 = {\n"
" match (mku(9u64, true)) {\n"
" case void => { return 50; };\n"
" case let t: (bool, u64) => {\n"
" let (sg, u) = t;\n"
" if (u != 9u64) { return 1; };\n"
" if (!sg) { return 2; };\n"
" return 0;\n"
" };\n"
" };\n"
" return 4;\n"
"};\n", K_RUN, 0, NULL },
/* tuple variant 1 of `(int | (bool,u64))`, selected at runtime, built
* from typed locals (the parseint shape: an int-or-tuple result). */
{ "tuple_after_int",
"package main;\n"
"fn mk(which: bool) (int | (bool, u64)) = {\n"
" let s: bool = true;\n"
" let v: u64 = 88u64;\n"
" if (which) { return (s, v); };\n"
" return 5;\n"
"};\n"
"export fn main() i32 = {\n"
" match (mk(true)) {\n"
" case let n: int => { return 1; };\n"
" case let t: (bool, u64) => {\n"
" let (sg, u) = t;\n"
" if (u != 88u64) { return 2; };\n"
" if (!sg) { return 3; };\n"
" return 0;\n"
" };\n"
" };\n"
" return 4;\n"
"};\n", K_RUN, 0, NULL },
/* C-t0 graduation (was the #242/#243 loud-stop): under the slot-SSoT
* tuple layout narrow elements never share an eightbyte —
* (i32,i32,u64) is 24B (3 slots), the slotted union write fits by
* construction, and the whole tuple round-trips through the union.
* cg_widen_tagged_store's size guard stays as a safety net but can
* no longer fire for an in-cap tuple. */
{ "eightbyte_share",
"package main;\n"
"fn f(a: i32, b: i32, c: u64) ((i32, i32, u64) | void) = {\n"
" return (a, b, c);\n"
"};\n"
"export fn main() i32 = {\n"
" match (f(-3, 4, 9u64)) {\n"
" case let t: (i32, i32, u64) => {\n"
" let (x, y, z) = t;\n"
" if (x != -3) { return 1; };\n"
" if (y != 4) { return 2; };\n"
" if (z != 9u64) { return 3; };\n"
" return 0;\n"
" };\n"
" case void => { return 4; };\n"
" };\n"
" return 5;\n"
"};\n", K_RUN, 0, NULL },
/* rule-7 loud-stop (ii): a tuple built from a BARE LITERAL element
* (`true`). cstage's cg_tag_for_variant can't type the literal (#241)
* so it returns -1 and loud-stops; wwstage types `true` as bool and
* WOULD resolve the tag, so it mirrors cstage's CONDITION with an
* explicit bare-literal guard (rule 10 — align the richer side DOWN).
* Both stages MUST loud-stop with the SAME diagnostic. Lift both guards
* together when #241 lands cstage literal typing. */
{ "bool_literal",
"package main;\n"
"fn f(x: u64) ((bool, u64) | void) = { return (true, x); };\n"
"export fn main() i32 = { return 0; };\n",
K_BUILDERR, 0, "variant tag unresolved (untyped/literal tuple element" },
};
/* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */
static int
run_driver(const char *driver, const struct row *r, int i)
{
char src[128], tmpdir[96], errf[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/tiu_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/tiu_%d_%d.ww", tmpdir, getpid(), i);
snprintf(errf, sizeof errf, "%s/tiu_%d_e_%d", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/tiu_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
driver, outbin, src, errf);
int brc = runwait(cmd);
if (r->kind == K_BUILDERR) {
int ok = (brc != 0)
&& (r->experr == NULL || file_contains(errf, r->experr));
if (!ok)
fprintf(stderr, "row[%s]: %s expected loud #242 builderr "
"(brc=%d)\n", r->label, driver, brc);
runwait(rmcmd);
return ok ? 0 : 1;
}
if (brc != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
if (got != r->want) {
fprintf(stderr, "row[%s]: %s exit %d, want %d\n",
r->label, driver, got, r->want);
return 1;
}
return 0;
}
/* cs==ww .s byte-id (rule 10). */
static int
byteid(const char *w6c, const char *w6c_ww, const struct row *r, int i)
{
char src[96], cs_s[96], ws_s[96], cmd[1024];
snprintf(src, sizeof src, "/tmp/tiu_bi_%d_%d.ww", getpid(), i);
snprintf(cs_s, sizeof cs_s, "/tmp/tiu_bi_%d_%d_cs.s", getpid(), i);
snprintf(ws_s, sizeof ws_s, "/tmp/tiu_bi_%d_%d_ww.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
int rc = 0;
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", r->label); rc = 1; }
else {
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", r->label); rc = 1; }
else if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr, "row[%s]: cstage/wwstage .s DIFFER "
"(#242 tuple-in-union construct/destructure regression)\n",
r->label);
rc = 1;
}
}
unlink(src); unlink(cs_s); unlink(ws_s);
return rc;
}
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], w6c[640], w6c_ww[640];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
struct { const char *name; const char *path; 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].path, X_OK) != 0) {
fprintf(stderr, "tuple_in_union: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
total++;
if (run_driver(drivers[d].path, &rows[i], i) != 0) fail++;
}
}
/* cs==ww byte-id for the K_RUN rows only; a K_BUILDERR row emits no
* .s on either stage (both loud-stop), so byte-id does not apply. */
if (access(w6c_ww, X_OK) == 0) {
for (int i = 0; i < n; i++) {
if (rows[i].kind != K_RUN) continue;
total++;
if (byteid(w6c, w6c_ww, &rows[i], i) != 0) fail++;
}
}
if (fail) {
fprintf(stderr, "tuple_in_union: %d/%d checks failed\n",
fail, total);
return 1;
}
printf("tuple_in_union: %d/%d ok\n", total, total);
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,309 +0,0 @@
/*
* 944_nonlit_tuple_widen_run — project #116: a NON-LITERAL tuple source
* widened into a tagged box (cg_widen_tagged_store / cgwidentaggedstore).
*
* Pre-#116 only a bare/cast tuple LITERAL (N_TUPLE / N_CAST-of-N_TUPLE)
* carried a full payload into the union box; every ADDRESSABLE non-literal
* tuple source — a tuple IDENT var, a slice/array INDEX `tbl[i]`, a DEREF
* `*p` — hit the `else fatal` ("tuple-typed source shape unwired"), LOUD and
* IDENTICAL on both stages. This blocked fold-6's
* `append(charsets[...], charclass_map[cc_idx])` (the INDEX shape).
*
* THE FIX (align-BOTH; both stages were loud, so there is NO runtime
* reference and byte-id is BLIND — #263). A new arm resolves the source
* ADDRESS through the existing place-address spine (cgplaceaddr — the same
* one &base[i] / *p / ident use) into SI, then BLOCK-COPIES sum(tuple_eslot)
* bytes (== the tuple type's table size under the 8B-slot layout) from the
* source address into the box payload (write_off+8), and stamps the variant
* tag. The source-tuple-in-memory layout already EQUALS the box-payload
* layout (same tuple_eslot strides the literal loop fills), so it is a flat
* address block-copy — no re-slotting, and a tagged element rides over as
* its already-built box. One mechanism closes the addressable trio.
*
* SCOPE (kept loud, out of #116): a CALL/sret tuple result (the tuple sits
* at the sret address, #40-kin) and struct-field / array-literal-element
* tuple sources (loud EARLIER at their construction, #49 / #270-1c).
*
* Because byte-id is blind here, the RUNTIME read-back is the correctness
* net (rob's obligation): EACH covered shape (ident / index / deref) builds
* the box from its non-literal tuple source, match-extracts the variant, and
* ASSERTS the payload survived — `len(x.0)` proves the str header rode over,
* `(*x.1)(arg)` indirect-calls the fn-ptr element and checks its result
* (drew's P3/P4 read-back). The tuple-LITERAL row is the already-green
* regression guard. All four rows confirmed to LOUD on base da30f10.
*
* The read-back deliberately avoids `str !=` (a pre-existing cs!=ww
* comparison-codegen divergence, rt_streq vs inline CMPQ — unrelated to
* #116) so the K_RUN byte-id check isolates the widen arm.
*
* NNN<950, self-contained (/tmp, no imports), so rule-14's selfhost-sibling
* race does not apply (903/940/945 precedent).
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa), cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
#define K_RUN 0 /* build+run both drivers, exit==want, + cs==ww byte-id */
struct row { const char *label; const char *src; int kind; int want; };
static const struct row rows[] = {
/* control (already green pre-#116): a CAST tuple LITERAL. Regression
* guard — the literal arm must keep emitting the same fill. */
{ "literal",
"package main;\n"
"fn fa(c: rune) bool = { return c == 'a'; };\n"
"type ci = (str, *fn(c: rune) bool);\n"
"type u = (rune | ci);\n"
"export fn main() i32 = {\n"
" let nm: str = \"ww\";\n"
" let cf: *fn(c: rune) bool = &fa;\n"
" let s: []u = [];\n"
" append(s, ((nm, cf): ci));\n"
" match (s[0]) {\n"
" case let r: rune => { return 90; };\n"
" case let x: ci => {\n"
" if ((len(x.0): i32) != 2) { return 1; };\n"
" if (!(*x.1)('a')) { return 2; };\n"
" return 0;\n"
" };\n"
" };\n"
" return 4;\n"
"};\n", K_RUN, 0 },
/* IDENT: a tuple-typed local var as the widen source. */
{ "ident",
"package main;\n"
"fn fb(c: rune) bool = { return c == 'b'; };\n"
"type ci = (str, *fn(c: rune) bool);\n"
"type u = (rune | ci);\n"
"export fn main() i32 = {\n"
" let nm: str = \"zz\";\n"
" let cf: *fn(c: rune) bool = &fb;\n"
" let t: ci = (nm, cf);\n"
" let s: []u = [];\n"
" append(s, t);\n"
" match (s[0]) {\n"
" case let r: rune => { return 90; };\n"
" case let x: ci => {\n"
" if ((len(x.0): i32) != 2) { return 1; };\n"
" if (!(*x.1)('b')) { return 2; };\n"
" if ((*x.1)('a')) { return 3; };\n"
" return 0;\n"
" };\n"
" };\n"
" return 4;\n"
"};\n", K_RUN, 0 },
/* INDEX: `tbl[i]` — the fold-6 shape. The array is filled by tuple-
* ident element stores (the foldable construction at HEAD), then the
* indexed element is the widen source. */
{ "index",
"package main;\n"
"fn fb(c: rune) bool = { return c == 'b'; };\n"
"fn fa(c: rune) bool = { return c == 'a'; };\n"
"type ci = (str, *fn(c: rune) bool);\n"
"type u = (rune | ci);\n"
"export fn main() i32 = {\n"
" let n0: str = \"aa\"; let c0: *fn(c: rune) bool = &fa;\n"
" let n1: str = \"zzz\"; let c1: *fn(c: rune) bool = &fb;\n"
" let t0: ci = (n0, c0);\n"
" let t1: ci = (n1, c1);\n"
" let tbl: [2]ci = [];\n"
" tbl[0] = t0;\n"
" tbl[1] = t1;\n"
" let s: []u = [];\n"
" append(s, tbl[1]);\n"
" match (s[0]) {\n"
" case let r: rune => { return 90; };\n"
" case let x: ci => {\n"
" if ((len(x.0): i32) != 3) { return 1; };\n"
" if (!(*x.1)('b')) { return 2; };\n"
" if ((*x.1)('a')) { return 3; };\n"
" return 0;\n"
" };\n"
" };\n"
" return 4;\n"
"};\n", K_RUN, 0 },
/* DEREF: `*p` — a pointer to a tuple var as the widen source. */
{ "deref",
"package main;\n"
"fn fa(c: rune) bool = { return c == 'a'; };\n"
"type ci = (str, *fn(c: rune) bool);\n"
"type u = (rune | ci);\n"
"export fn main() i32 = {\n"
" let nm: str = \"qqq\";\n"
" let cf: *fn(c: rune) bool = &fa;\n"
" let t: ci = (nm, cf);\n"
" let p: *ci = &t;\n"
" let s: []u = [];\n"
" append(s, *p);\n"
" match (s[0]) {\n"
" case let r: rune => { return 90; };\n"
" case let x: ci => {\n"
" if ((len(x.0): i32) != 3) { return 1; };\n"
" if (!(*x.1)('a')) { return 2; };\n"
" if ((*x.1)('b')) { return 3; };\n"
" return 0;\n"
" };\n"
" };\n"
" return 4;\n"
"};\n", K_RUN, 0 },
};
/* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[96], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/ntw_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/ntw_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/ntw_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>&1",
driver, outbin, src);
int brc = runwait(cmd);
if (brc != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
if (got != r->want) {
fprintf(stderr, "row[%s]: %s exit %d, want %d\n",
r->label, driver, got, r->want);
return 1;
}
return 0;
}
/* cs==ww .s byte-id (rule 10). */
static int
byteid(const char *w6c, const char *w6c_ww, const struct row *r, int i)
{
char src[96], cs_s[96], ws_s[96], cmd[1024];
snprintf(src, sizeof src, "/tmp/ntw_bi_%d_%d.ww", getpid(), i);
snprintf(cs_s, sizeof cs_s, "/tmp/ntw_bi_%d_%d_cs.s", getpid(), i);
snprintf(ws_s, sizeof ws_s, "/tmp/ntw_bi_%d_%d_ww.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
int rc = 0;
/* Single-file package — w6c compiles the .ww source directly (no
* imports, no combined.ww amalgam needed); diff the two emissions. */
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", r->label);
rc = 1;
} else {
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", r->label);
rc = 1;
} else if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr, "row[%s]: cstage/wwstage .s DIFFER "
"(#116 non-literal tuple widen regression)\n",
r->label);
rc = 1;
}
}
unlink(src); unlink(cs_s); unlink(ws_s);
return rc;
}
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], w6c[640], w6c_ww[640];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
struct { const char *name; const char *path; 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].path, X_OK) != 0) {
fprintf(stderr, "nonlit_tuple_widen: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
total++;
if (run_driver(drivers[d].path, &rows[i], i) != 0) fail++;
}
}
if (access(w6c_ww, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (byteid(w6c, w6c_ww, &rows[i], i) != 0) fail++;
}
}
if (fail) {
fprintf(stderr, "nonlit_tuple_widen: %d/%d checks failed\n",
fail, total);
return 1;
}
printf("nonlit_tuple_widen: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,310 +0,0 @@
/*
* 944_tuple_tagged_union_run — project #47 gap-A: a tuple whose elements
* include a TAGGED union, boxed as a member of a tagged union, must
* CONSTRUCT + RETURN-STORE identically in both stages.
*
* A PLAIN tuple-in-union already worked (#242, 940_tuple_in_union); the
* blocker was specifically the TAGGED ELEMENT. cg_widen_tagged_store's
* tuple-in-union store walk had scalar/float/str/slice element arms but
* NO TY_TAGGED arm — it loud-stopped ("tagged element in a tuple-in-union
* payload unwired"). gap-A adds a RECURSIVE two-level widen: each tagged
* element is boxed into its tuple slot (inner tag@slot+0, payload@slot+8)
* by re-entering cg_widen_tagged_store per element, THEN the whole tuple is
* boxed (outer tag + 40B payload copy). The element's tuple-slot stride is
* its OWN box size (roundup8) — a (void|size) elem is 16B (2 slots), not
* 8B — mirroring the checker's N_TTUPLE accumulation (check.c:715-720 /
* check.ww:1640-1646). MEASURED at HEAD: tuple->size for
* ((void|size),(void|size),size) = 40 (16+16+8); union box = 48.
*
* Both stages get the SAME arm → byte-identical asm (rule 10).
*
* All rows are K_RUN: build+run exit==want on BOTH drivers AND cs==ww
* byte-id. The ret-shape rows (construct + return + `is error` consume, NO
* match case-arm) were the gap-A pin — wwstage's store correctness rides on
* byte-id with the cstage runtime reference. The full-b1c row (construct +
* return + MATCH case-arm + is/as elem reads) was cstage-only under gap-A
* because wwstage's checker rejected the tuple-with-tagged case pattern;
* #47 gap-B taught typeeqast the N_TTAGGED arm (cmd/wcc/type.c:288-300
* TY_TAGGED twin), so wwstage now ACCEPTS it and the row is promoted to
* both-stage K_RUN — that full-b1c byte-id IS the gap-B correctness proof.
*
* NNN<950, self-contained (/tmp, no imports), so rule-14's selfhost-sibling
* race does not apply (903/940/945 precedent). Cites #47 gap-A.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa), cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
#define K_RUN 0 /* build+run BOTH drivers, exit==want, + cs==ww byte-id */
struct row { const char *label; const char *src; int kind; int want; };
static const struct row rows[] = {
/* ret-shape 3-tuple ((void|size),(void|size),size) | error: the
* parse_repetition shape. pr(0) builds the tuple (min=void tag0,
* max=5:size tag1, rep=7), pr(2) the error variant. Consumed via
* `is error` only — no match arm — so both stages build+run. The
* recursive inner box exercises BOTH inner tags (void + size). */
{ "ret_3tuple",
"package main;\n"
"type error = !str;\n"
"fn pr(sel: i32) (((void | size), (void | size), size) | error) = {\n"
" if (sel == 0) {\n"
" let min: (void | size) = void;\n"
" let max: (void | size) = 5: size;\n"
" return (min, max, 7: size);\n"
" };\n"
" return \"neg\": error;\n"
"};\n"
"export fn main() i32 = {\n"
" let a: (((void | size), (void | size), size) | error) = pr(0);\n"
" if (a is error) { return 1; };\n"
" let b: (((void | size), (void | size), size) | error) = pr(2);\n"
" if (b is error) { return 235; };\n"
" return 2;\n"
"};\n", K_RUN, 235 },
/* ret-shape 2-tuple ((void|size), size) | error: min=5:size (tag1).
* Smaller box (8+16+8 = wait: 16+8 = 24B tuple, 32B union) exercises
* the same recursive arm at a different slot layout. */
{ "ret_2tuple",
"package main;\n"
"type error = !str;\n"
"fn pr(sel: i32) (((void | size), size) | error) = {\n"
" let min: (void | size) = 5: size;\n"
" return (min, 7: size);\n"
"};\n"
"export fn main() i32 = {\n"
" let a: (((void | size), size) | error) = pr(0);\n"
" if (a is error) { return 1; };\n"
" return 235;\n"
"};\n", K_RUN, 235 },
/* scalar-elem control (regression guard): a PLAIN tuple-in-union with
* no tagged element still takes the scalar arm and stays byte-id. */
{ "scalar_ctrl",
"package main;\n"
"type error = !str;\n"
"fn pr(sel: i32) ((size, size, size) | error) = {\n"
" return (3: size, 4: size, 5: size);\n"
"};\n"
"export fn main() i32 = {\n"
" let a: ((size, size, size) | error) = pr(0);\n"
" if (a is error) { return 1; };\n"
" return 235;\n"
"};\n", K_RUN, 235 },
/* full b1c (gap-B): construct + return + MATCH case-arm + is/as
* elem reads on the boxed tuple. pr(0): t.0 is void (+1), t.1 is size
* = 5 (+5), t.2*10 (+70) = 76. pr(1): t.0 is size==3 (+100), t.1 is
* void (+50), t.2 = 9 (+9) = 235. pr(2): error arm reads back the boxed
* error str (ptr+len) and checks its length (25). Reads back EVERY
* tuple-elem value the recursive store wrote. gap-B taught wwstage's
* typeeqast the N_TTAGGED arm, so its checker now ACCEPTS this tuple-
* with-tagged case pattern — both-stage K_RUN (build+run 235 + cs==ww
* byte-id) is the gap-B correctness proof. Self-contained (no import,
* per this file's invariant): the error payload is verified with the
* len() builtin over an ident-bound str (a cast-expr len() operand is
* not place-resolvable, #10/#41) rather than a strings.compare import. */
{ "b1c_full",
"package main;\n"
"type error = !str;\n"
"fn pr(sel: i32) (((void | size), (void | size), size) | error) = {\n"
" if (sel == 0) {\n"
" let min: (void | size) = void;\n"
" let max: (void | size) = 5: size;\n"
" return (min, max, 7: size);\n"
" };\n"
" if (sel == 1) {\n"
" let min: (void | size) = 3: size;\n"
" let max: (void | size) = void;\n"
" return (min, max, 9: size);\n"
" };\n"
" return \"Negative repetition count\": error;\n"
"};\n"
"export fn main() i32 = {\n"
" let acc: size = 0;\n"
" let a: (((void | size), (void | size), size) | error) = pr(0);\n"
" match (a) {\n"
" case let t: ((void | size), (void | size), size) => {\n"
" if (t.0 is void) { acc += 1; };\n"
" if (t.1 is size) { acc += t.1 as size; };\n"
" acc += t.2 * 10;\n"
" };\n"
" case let e: error => { return 250; };\n"
" };\n"
" let b: (((void | size), (void | size), size) | error) = pr(1);\n"
" match (b) {\n"
" case let t: ((void | size), (void | size), size) => {\n"
" if (t.0 is size && t.0 as size == 3) { acc += 100; };\n"
" if (t.1 is void) { acc += 50; };\n"
" acc += t.2;\n"
" };\n"
" case let e: error => { return 251; };\n"
" };\n"
" let c: (((void | size), (void | size), size) | error) = pr(2);\n"
" match (c) {\n"
" case let t: ((void | size), (void | size), size) => { return 252; };\n"
" case let e: error => {\n"
" let s: str = e: str;\n"
" if (len(s) != 25) { return 253; };\n"
" };\n"
" };\n"
" return (acc: i32);\n"
"};\n", K_RUN, 235 },
};
/* build+run via a driver; returns 0 pass, nonzero fail. */
static int
run_driver(const char *driver, const struct row *r, int i)
{
char src[128], tmpdir[96], outbin[128], errf[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/ttu_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/ttu_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/ttu_%d_%d", tmpdir, getpid(), i);
snprintf(errf, sizeof errf, "%s/err", tmpdir);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
driver, outbin, src, errf);
int brc = runwait(cmd);
if (brc != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
if (got != r->want) {
fprintf(stderr, "row[%s]: %s exit %d, want %d\n",
r->label, driver, got, r->want);
return 1;
}
return 0;
}
/* cs==ww .s byte-id (rule 10) — K_RUN rows only. */
static int
byteid(const char *w6c, const char *w6c_ww, const struct row *r, int i)
{
char src[96], cs_s[96], ws_s[96], cmd[1024];
snprintf(src, sizeof src, "/tmp/ttu_bi_%d_%d.ww", getpid(), i);
snprintf(cs_s, sizeof cs_s, "/tmp/ttu_bi_%d_%d_cs.s", getpid(), i);
snprintf(ws_s, sizeof ws_s, "/tmp/ttu_bi_%d_%d_ww.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
int rc = 0;
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", r->label); rc = 1; }
else {
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", r->label); rc = 1; }
else if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr, "row[%s]: cstage/wwstage .s DIFFER "
"(#47 gap-A tuple-w/-tagged-elem store regression)\n",
r->label);
rc = 1;
}
}
unlink(src); unlink(cs_s); unlink(ws_s);
return rc;
}
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], w6c[640], w6c_ww[640];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
struct { const char *name; const char *path; 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].path, X_OK) != 0) {
fprintf(stderr, "tuple_tagged_union: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
total++;
if (run_driver(drivers[d].path, &rows[i], i) != 0)
fail++;
}
}
/* cs==ww byte-id for every K_RUN row (gap-B promoted the full-b1c
* match-extract row, so all rows now emit wwstage .s). */
if (access(w6c_ww, X_OK) == 0) {
for (int i = 0; i < n; i++) {
if (rows[i].kind != K_RUN) continue;
total++;
if (byteid(w6c, w6c_ww, &rows[i], i) != 0) fail++;
}
}
if (fail) {
fprintf(stderr, "tuple_tagged_union: %d/%d checks failed\n",
fail, total);
return 1;
}
printf("tuple_tagged_union: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,236 +0,0 @@
/*
* 945_rvalue_tuple_destructure_run — project #241: materialise an RVALUE
* tuple into the register cursor before a destructure / let bind.
*
* cgexpr could not produce a tuple VALUE: a literal `(a, b)` fell to the
* `cgexpr_int(0)` / `MOVQ $0, AX` default, a tuple-typed IDENT loaded only
* word0 into AX, and the `?`/`!` unwrap of a tuple-in-union payload lifted
* only word0->AX (stranding word1 in CX). So `let (x, y) = <rvalue tuple>`
* read GARBAGE past the first element (cstage), and the un-typed binder left
* wwstage's checker aborting (asserttyped). DANGEROUS gate-blind cs!=ww — the
* strconv blocker (`let (sign, u) = parseint(s, base)?`).
*
* Fix (both stages, byte-identical per rule 10): cgexpr packs an N_TUPLE
* literal and a tuple IDENT into the SysV register-return cursor (the SAME
* ABI a tuple-returning call leaves, which the cgmlet/cgmassign consumers
* already read); the ?/! unwrap shifts a tuple success payload down one
* integer reg past the tag. wwstage's checker recovers the popped match-arm
* binder type for a `yield <binder>` (scope-free, via the arm's declared
* type) so the destructured binders stamp.
*
* Rows assert BOTH elements on BOTH drivers AND cs==ww byte-identical:
* literal_destructure `let (a, b) = (true, 11u64)`
* match_yield `let (sg, u) = match (mk()) { case let t => yield t }`
* trycall_strconv `let (sign, u) = parseint(base)?` (the strconv shape)
*
* NOT covered (separate root, deferred): single-var `let q = (true, 9u64)`
* then `q.N` rides #238 — the N_LET tuple-init sz==16||32 gate + the N_DOT
* tuple-field PACKED-offset reader vs tuple_store's 8B stride disagree for a
* narrow-first tuple. Out of scope for #241 (the rvalue-into-cursor fix); see
* task #6 / proj #238.
*
* All K_RUN: build+run exit 0 on BOTH drivers AND cs==ww byte-identical.
* NNN<950, self-contained (/tmp, no imports), so rule-14's selfhost-sibling
* race does not apply (903/940/945 precedent).
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa), cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
struct row { const char *label; const char *src; int want; };
static const struct row rows[] = {
{ "literal_destructure",
"package main;\n"
"export fn main() i32 = {\n"
" let (a, b) = (true, 11u64);\n"
" if (b != 11u64) { return 1; };\n"
" if (!a) { return 2; };\n"
" return 0;\n"
"};\n", 0 },
{ "match_yield",
"package main;\n"
"type myerr = !void;\n"
"fn mk(x: u64, s: bool) ((bool, u64) | myerr) = { return (s, x); };\n"
"export fn main() i32 = {\n"
" let (sg, u) = match (mk(6u64, true)) {\n"
" case let t: (bool, u64) => yield t;\n"
" case myerr => { return 9; };\n"
" };\n"
" if (u != 6u64) { return 1; };\n"
" if (!sg) { return 2; };\n"
" return 0;\n"
"};\n", 0 },
{ "trycall_strconv",
"package main;\n"
"type invalid = !void;\n"
"fn parseint(base: int) ((bool, u64) | invalid) = {\n"
" let sg: bool = true;\n"
" let n: u64 = 42u64;\n"
" return (sg, n);\n"
"};\n"
"fn stoi(base: int) (u64 | invalid) = {\n"
" let (sign, u) = parseint(base)?;\n"
" if (!sign) { return 0u64; };\n"
" return u;\n"
"};\n"
"export fn main() i32 = {\n"
" match (stoi(10)) {\n"
" case let v: u64 => { if (v != 42u64) { return 1; }; };\n"
" case invalid => { return 2; };\n"
" };\n"
" return 0;\n"
"};\n", 0 },
};
/* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */
static int
run_driver(const char *driver, const struct row *r, int i)
{
char src[128], tmpdir[96], errf[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/rvtd_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/rvtd_%d_%d.ww", tmpdir, getpid(), i);
snprintf(errf, sizeof errf, "%s/err", tmpdir);
snprintf(outbin, sizeof outbin, "%s/rvtd_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
driver, outbin, src, errf);
int brc = runwait(cmd);
if (brc != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
if (got != r->want) {
fprintf(stderr, "row[%s]: %s exit %d, want %d\n",
r->label, driver, got, r->want);
return 1;
}
return 0;
}
/* cs==ww .s byte-id (rule 10). */
static int
byteid(const char *w6c, const char *w6c_ww, const struct row *r, int i)
{
char src[96], cs_s[96], ws_s[96], cmd[1024];
snprintf(src, sizeof src, "/tmp/rvtd_bi_%d_%d.ww", getpid(), i);
snprintf(cs_s, sizeof cs_s, "/tmp/rvtd_bi_%d_%d_cs.s", getpid(), i);
snprintf(ws_s, sizeof ws_s, "/tmp/rvtd_bi_%d_%d_ww.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
int rc = 0;
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", r->label); rc = 1; }
else {
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", r->label); rc = 1; }
else if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr, "row[%s]: cstage/wwstage .s DIFFER "
"(rule-10 byte-id)\n", r->label);
rc = 1;
}
}
unlink(src); unlink(cs_s); unlink(ws_s);
return rc;
}
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], w6c[640], w6c_ww[640];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
struct { const char *name; const char *path; 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].path, X_OK) != 0) {
fprintf(stderr, "rvalue_tuple_destructure: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
total++;
if (run_driver(drivers[d].path, &rows[i], i) != 0) fail++;
}
}
if (access(w6c_ww, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (byteid(w6c, w6c_ww, &rows[i], i) != 0) fail++;
}
}
if (fail) {
fprintf(stderr, "rvalue_tuple_destructure: %d/%d checks failed\n",
fail, total);
return 1;
}
printf("rvalue_tuple_destructure: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,280 +0,0 @@
/*
* 945_tuple_lit_declblind_run — #64 + #68: a tuple LITERAL fills the
* register cursor DECL-BLIND. The #57 decl wire (a declared-tagged element's
* concrete rvalue widens into its box) stopped at N_LET / N_RETURN; the
* other two tuple-literal consumers — destructure-REASSIGN (N_MASSIGN, #64)
* and CALL-ARG send (#68) — still rode the decl-less route, so a declared-
* tagged element was stored/sent WORD-0 ONLY (the box tag never set) and the
* cursor skewed every later element. Both stages, #263 gate-blind: the asm
* was byte-identical cs==ww and both ran wrong, so only RUNTIME catches it.
*
* Fix (both stages, byte-identical per rule 10): N_MASSIGN builds a DECLARED
* tuple type from the lvalue binding types and threads it into the cursor
* fill + receive (a `_` lvalue, never type-stamped, falls back to the rhs
* literal element type for its stride); the call-arg send/restage/drain key
* on the PARAM tuple type (the literal's element-constructed type undercounts
* a wide tagged box). The single residual generic cgexpr(N_TUPLE) arm is
* provably non-widening (the constructed type IS the governing type there).
*
* Every RUN row reads the box PAYLOAD back (binds n and asserts its VALUE),
* not merely which match arm fired — a skew that sets the tag right but
* corrupts the payload still fails. Rows (RUN unless marked ERR):
* massign_tagged `a,b=(7i64,99)` into a:(i32|i64); n==7 ⇒ 1, else 3;
* wrong arm ⇒ 0; garbage-tag fall-through ⇒ 2 (base).
* callarg_tagged `f((7i64,99))` param (ev,i64); match t.0, n==7 ⇒ 1.
* base: garbage tag ⇒ fall-through ⇒ 2.
* massign_blank `_,a=(99,7i64)` — `_` stride + named box widen; the box
* is element 1 (off the `_`-aligned cursor). n==7 ⇒ 1.
* base: word-0-only store corrupts a's tag ⇒ fall-through ⇒
* 2 (the box's tag sits at word0; the lone scalar word
* overwrites it — verified on pristine abd97e6).
* callarg_drain `g((7i64, 35))` param (ev,i64); g returns (box payload)
* + t.1. This is the #68 DRAIN teeth (ken's verified probe
* shape): the param-aware drain must pop the box's 2 words
* AND the trailing i64 together. base: box is word-0 only
* so the i64 arm never fires (payload 0) ⇒ 0 + 35 = 35;
* post: 7 + 35 = 42. base 35 ≠ 42, and the +35 simultaneously
* proves the trailing arg still lands in its reg.
* nested_tuple_arg [ERR] `h(((1,2),3))` param ((i32,i32),i64): a NESTED-
* TUPLE tuple-arg element has no transport and stays rule-7
* LOUD (the fix graduates ONLY a declared-tagged element;
* the TY_TUPLE/STRUCT/ARRAY arms are preserved). Asserts the
* build FAILS (nonzero) on BOTH drivers — locks the
* preserved loudness so a future widen-everything regresses.
*
* RUN rows: build+run on BOTH drivers AND cs==ww .s byte-identical (rule 10).
* NNN<950, self-contained (/tmp, no imports), so rule-14's selfhost-sibling
* race does not apply (903/940/945 precedent).
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa), cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
/* builderr: the build must FAIL (rule-7 loud); RUN rows leave it 0. */
struct row { const char *label; const char *src; int want; int builderr; };
static const struct row rows[] = {
{ "massign_tagged",
"package main;\n"
"type ev = (i32 | i64);\n"
"export fn main() i32 = {\n"
" let a: ev = 5i32;\n"
" let b: i64 = 0;\n"
" a, b = (7i64, 99);\n"
" match (a) {\n"
" case let n: i64 => { if (n == 7) { return 1; }; return 3; };\n"
" case let m: i32 => { return 0; };\n"
" };\n"
" return 2;\n"
"};\n", 1, 0 },
{ "callarg_tagged",
"package main;\n"
"type ev = (i32 | i64);\n"
"fn f(t: (ev, i64)) i32 = {\n"
" match (t.0) {\n"
" case let n: i64 => { if (n == 7) { return 1; }; return 3; };\n"
" case let m: i32 => { return 0; };\n"
" };\n"
" return 2;\n"
"};\n"
"export fn main() i32 = {\n"
" return f((7i64, 99));\n"
"};\n", 1, 0 },
{ "massign_blank",
"package main;\n"
"type ev = (i32 | i64);\n"
"export fn main() i32 = {\n"
" let a: ev = 5i32;\n"
" _, a = (99, 7i64);\n"
" match (a) {\n"
" case let n: i64 => { if (n == 7) { return 1; }; return 3; };\n"
" case let m: i32 => { return 0; };\n"
" };\n"
" return 2;\n"
"};\n", 1, 0 },
{ "callarg_drain",
"package main;\n"
"type ev = (i32 | i64);\n"
"fn g(t: (ev, i64)) i32 = {\n"
" let bp: i64 = 0;\n"
" match (t.0) {\n"
" case let n: i64 => { bp = n; };\n"
" case let m: i32 => { bp = 0; };\n"
" };\n"
" return (bp + t.1): i32;\n"
"};\n"
"export fn main() i32 = {\n"
" return g((7i64, 35));\n"
"};\n", 42, 0 },
{ "nested_tuple_arg",
"package main;\n"
"fn h(t: ((i32, i32), i64)) i32 = {\n"
" return 0;\n"
"};\n"
"export fn main() i32 = {\n"
" return h(((1, 2), 3));\n"
"};\n", 0, 1 },
};
/* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[96], src[128], outbin[128], errf[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/tldb_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/tldb_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/tldb_%d_%d", tmpdir, getpid(), i);
snprintf(errf, sizeof errf, "%s/err", tmpdir);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
driver, outbin, src, errf);
int brc = runwait(cmd);
/* ERR row: the rule-7 loud arm must fire — build FAILS (nonzero). A
* clean build means the preserved loudness regressed. */
if (r->builderr) {
int ok = (brc != 0);
if (!ok)
fprintf(stderr, "row[%s]: %s built clean, expected "
"rule-7 loud build-fail\n", r->label, driver);
runwait(rmcmd);
return ok ? 0 : 1;
}
if (brc != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
if (got != r->want) {
fprintf(stderr, "row[%s]: %s exit %d, want %d\n",
r->label, driver, got, r->want);
return 1;
}
return 0;
}
/* cs==ww .s byte-id (rule 10). */
static int
byteid(const char *w6c, const char *w6c_ww, const struct row *r, int i)
{
char src[96], cs_s[96], ws_s[96], cmd[1024];
snprintf(src, sizeof src, "/tmp/tldb_bi_%d_%d.ww", getpid(), i);
snprintf(cs_s, sizeof cs_s, "/tmp/tldb_bi_%d_%d_cs.s", getpid(), i);
snprintf(ws_s, sizeof ws_s, "/tmp/tldb_bi_%d_%d_ww.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
int rc = 0;
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", r->label); rc = 1; }
else {
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", r->label); rc = 1; }
else if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr, "row[%s]: cstage/wwstage .s DIFFER "
"(rule-10 byte-id)\n", r->label);
rc = 1;
}
}
unlink(src); unlink(cs_s); unlink(ws_s);
return rc;
}
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], w6c[640], w6c_ww[640];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
struct { const char *name; const char *path; 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].path, X_OK) != 0) {
fprintf(stderr, "tuple_lit_declblind: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
total++;
if (run_driver(drivers[d].path, &rows[i], i) != 0) fail++;
}
}
if (access(w6c_ww, X_OK) == 0) {
for (int i = 0; i < n; i++) {
if (rows[i].builderr) continue; /* no .s to compare */
total++;
if (byteid(w6c, w6c_ww, &rows[i], i) != 0) fail++;
}
}
if (fail) {
fprintf(stderr, "tuple_lit_declblind: %d/%d checks failed\n",
fail, total);
return 1;
}
printf("tuple_lit_declblind: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,346 +0,0 @@
/*
* 945_tuple_nary_destructure_run — runtime coverage for project #83: the
* general N-ary tuple-destructure per-element register-return. The send
* (N_RETURN) and receive (N_MLET / N_MASSIGN) sites previously gated the
* 3-word {ptr,len,cap} store on a str-only XOR (`e0_is_str ^ e1_is_str`),
* which admitted EXACTLY ONE str element and was slice-BLIND: a tuple with
* a []u8 element fell to the scalar-pair fallback and silently DROPPED
* len+cap (only .ptr landed). The fix REPLACES the XOR with a positional
* per-element cursor (harec create_unpack_bindings,
* ref/harec/src/check.c:1354-1416): each element rides consecutive
* eightbytes over [AX,DX,CX,R8]; a slice/str rides its 3-word header
* (ref/hare/rt/ensure.ha:4-8), a scalar rides 1. SEND and RECEIVE walk the
* SAME cursor so element->register agrees. This closes the pre-existing
* (scalar,slice) cs!=ww divergence by construction (slice no longer rides
* the scalar fallback) and unifies both stages on the positional layout.
*
* LOUD-STOP (rule 7): the register file holds only 4 eightbytes. A tuple
* whose elements sum to > 4 (e.g. ([]u8,[]u8)/(str,str) = 3+3 = 6) CANNOT
* be register-returned; the send site emits a COMPILE-TIME abort citing
* the return-ABI capacity (project #10), NOT a silent miscompile. Row F
* asserts that loud stop fires on BOTH stages: the build must FAIL *and*
* stderr must carry the cited diagnostic (rule 7 — loud, not a silent
* crash that an exit-code-only check would mistake for the stop).
*
* N_MLET vs N_MASSIGN: `let (a, s) = f()` is N_MLET (fresh bindings, slots
* sized from each element type). `a, s = f()` with a, s PRE-DECLARED is
* N_MASSIGN (reassignment, existing slots) — a retained ww-EXTENSION
* beyond Hare (Hare tuple-unpack is binding-only; ww keeps the Go/rob-pike
* multi-assign idiom). The N_MASSIGN rows pre-poison the destination slot
* (distinct len/cap via a PROVEN 3-word let-init copy) so a dropped
* len/cap reads the poison, never the test value — deterministic
* fail-before. The N_MLET rows use cap!=len distinct values; a dropped
* cap reads fresh-slot garbage, ~never the asserted pair.
*
* Backing storage: each []u8 element borrows a str literal's .ptr (stable
* .rodata), so the header survives the producer's return without a
* use-after-return. str elements return a literal with a MUTATED .cap so
* cap!=len (a bare literal carries cap==len, hiding a dropped cap).
*
* Rows (cap!=len in every wide row so a dropped len OR cap is caught):
* A mlet_islice `let (a, s) = f()`, f()->(i64,[]u8). a=4,len=2,cap=5,'h'.
* B massign_islice`a, s = f()` predeclared+poisoned (len=9,cap=9).
* C mlet_istr `let (a, s) = f()`, f()->(i64,str). a=4,len=2,cap=7,'h'.
* D massign_istr `a, s = f()` predeclared+poisoned (len=4,cap=6).
* E str_control plain single-str return `let s = f()` f()->str — the
* non-tuple str path (cgen.c N_RETURN node_isstr branch)
* the fix leaves untouched; len=5,cap=9 regression guard.
* G massign_blank_wide `_, a = f()`, f()->([]u8,i64). The blank `_`
* rides a 3-word slot so a lands on R8 (the i64), not DX
* (slice.len). len=2,cap=5,i64=7 distinct (any desync !=7).
* F slice_slice_recv f()->([]u8,[]u8) returns (a,b) — 6 eightbytes >
* cap; the SEND sret's it (#10 Fold A) and the
* destructure RECEIVE copies all 3 words/element out
* of @sretscr (#10 Fold B). len AND cap of both halves
* asserted (cap!=len) so a dropped word is caught.
*
* Fold discriminators: A/B (slice) — the old str-only XOR was slice-blind,
* so the slice fell to the scalar-pair fallback and dropped len/cap; B's
* poison makes that deterministic. G (blank `_`) — cstage derived the
* receive width from the binding type, which is null for an unstamped `_`,
* so a wide `_` was mis-sized scalar and the cursor desynced (cstage read
* DX, wwstage R8); deriving width from the rhs tuple type fixes + aligns
* both stages. F (over-cap recv) — the pre-#10 code register-returned
* ([]u8,[]u8) with only the two .ptr words (built + ran WRONG); #10 Fold A
* loud-stopped it at the SEND, and Fold B now sret's + copies it out into
* the bindings, so it is a working round-trip (was a BUILDERR at Fold A).
* C/D/E are CONTROLS: the (i64,str) path was ALREADY 3-word
* under the old XOR, and the single-str return is a separate untouched
* branch — they confirm no regression. All 14
* fixtures pass on both the cstage `ww` and wwstage `ww_ww` drivers with
* byte-identical asm (smoke-verified pre-commit; the formal fail-before
* revert-run is ken's tuple83 gate). NNN<950, self-contained (/tmp, no
* imports), so rule-14's selfhost-sibling race does not apply (mirrors the
* 941/942/943/944 precedent).
*/
#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;
}
/* experr (builderr rows only): a stable substring of the loud-stop
* diagnostic. rule 7 — the over-capacity stop must be LOUD: the build must
* fail BECAUSE of the cited return-ABI diagnostic (project #10), not an
* incidental/silent error. NULL skips the message check. */
struct row { const char *label; const char *src; int want; int builderr;
const char *experr; };
static const struct row rows[] = {
/* A — mlet_islice: `let (a, s) = f()`, f()->(i64,[]u8). Slice borrows
* the "hi" literal's .ptr (stable). len=2, cap=5 (distinct). */
{ "mlet_islice",
"fn mk() (i64, []u8) = {\n"
" let b: str = \"hi\";\n"
" let p: []u8; p.ptr = b.ptr; p.len = 2; p.cap = 5;\n"
" return (4i64, p);\n"
"};\n"
"export fn main() i32 = {\n"
" let (a, s) = mk();\n"
" if (a != 4) { return 1; };\n"
" if (s.len: i32 != 2) { return 2; };\n"
" if (s.cap: i32 != 5) { return 3; };\n"
" if (s[0] != 104u8) { return 4; };\n"
" return 0;\n"
"};\n",
0, 0, NULL },
/* B — massign_islice: `a, s = f()` predeclared; s poisoned (len=9,
* cap=9) via the proven 3-word `s = q` let-init copy. A dropped
* len/cap leaves the poison 9; the fix lands 2 / 5. */
{ "massign_islice",
"fn mk() (i64, []u8) = {\n"
" let b: str = \"hi\";\n"
" let p: []u8; p.ptr = b.ptr; p.len = 2; p.cap = 5;\n"
" return (4i64, p);\n"
"};\n"
"export fn main() i32 = {\n"
" let qb: str = \"zzzz\";\n"
" let q: []u8; q.ptr = qb.ptr; q.len = 9; q.cap = 9;\n"
" let a: i64 = 0i64;\n"
" let s: []u8 = q;\n"
" a, s = mk();\n"
" if (a != 4) { return 1; };\n"
" if (s.len: i32 != 2) { return 2; };\n"
" if (s.cap: i32 != 5) { return 3; };\n"
" if (s[0] != 104u8) { return 4; };\n"
" return 0;\n"
"};\n",
0, 0, NULL },
/* C — mlet_istr: `let (a, s) = f()`, f()->(i64,str). cap mutated to
* 7 so cap!=len(2). */
{ "mlet_istr",
"fn mk() (i64, str) = {\n"
" let p: str = \"hi\"; p.cap = 7i32;\n"
" return (4i64, p);\n"
"};\n"
"export fn main() i32 = {\n"
" let (a, s) = mk();\n"
" if (a != 4) { return 1; };\n"
" if (s.len: i32 != 2) { return 2; };\n"
" if (s.cap: i32 != 7) { return 3; };\n"
" if (s[0] != 104u8) { return 4; };\n"
" return 0;\n"
"};\n",
0, 0, NULL },
/* D — massign_istr: `a, s = f()` predeclared; s poisoned (len=4,
* cap=6) via the proven 3-word `s = q` copy. */
{ "massign_istr",
"fn mk() (i64, str) = {\n"
" let p: str = \"hi\"; p.cap = 7i32;\n"
" return (4i64, p);\n"
"};\n"
"export fn main() i32 = {\n"
" let q: str = \"qqqq\"; q.cap = 6i32;\n"
" let a: i64 = 0i64;\n"
" let s: str = q;\n"
" a, s = mk();\n"
" if (a != 4) { return 1; };\n"
" if (s.len: i32 != 2) { return 2; };\n"
" if (s.cap: i32 != 7) { return 3; };\n"
" if (s[0] != 104u8) { return 4; };\n"
" return 0;\n"
"};\n",
0, 0, NULL },
/* E — str_control: plain single-str return, the non-tuple path the
* fix leaves untouched. cap mutated to 9 so cap!=len(5). */
{ "str_control",
"fn mk() str = {\n"
" let p: str = \"hello\"; p.cap = 9i32;\n"
" return p;\n"
"};\n"
"export fn main() i32 = {\n"
" let s: str = mk();\n"
" if (s.len: i32 != 5) { return 1; };\n"
" if (s.cap: i32 != 9) { return 2; };\n"
" if (s[0] != 104u8) { return 3; };\n"
" return 0;\n"
"};\n",
0, 0, NULL },
/* G — massign_blank_wide: `_, a = f()`, f()->([]u8,i64), wide-first
* blank. The `_` (N_IDENT, empty str) is never type-stamped by the
* checker, so cstage previously mis-sized it as a scalar (1 word) and
* the cursor desynced: a read DX (slice.len=2) instead of R8 (the
* i64). The fix reads element width from the rhs tuple type, so `_`
* consumes its 3-word slot and a lands on R8. len=2, cap=5, i64=7 all
* distinct so any desync misread (DX=2 / CX=5) is caught, never 7. */
{ "massign_blank_wide",
"fn mk() ([]u8, i64) = {\n"
" let b: str = \"hi\";\n"
" let p: []u8; p.ptr = b.ptr; p.len = 2; p.cap = 5;\n"
" return (p, 7i64);\n"
"};\n"
"export fn main() i32 = {\n"
" let a: i64 = 0i64;\n"
" _, a = mk();\n"
" if (a != 7) { return 1; };\n"
" return 0;\n"
"};\n",
0, 0, NULL },
/* F — slice_slice_recv: ([]u8,[]u8) = 6 eightbytes > 4 capacity. The
* SEND sret's the over-cap tuple (#10 Fold A) and the destructure
* RECEIVE copies all 3 words per element out of the @sretscr slot
* (#10 Fold B) — formerly a loud-stop, now a working round-trip.
* Asserts BOTH len AND cap of each half (cap != len) so a dropped
* word — the original register-return bug — is caught at runtime. */
{ "slice_slice_recv",
"fn mk() ([]u8, []u8) = {\n"
" let a: []u8; a.len = 1; a.cap = 5;\n"
" let b: []u8; b.len = 2; b.cap = 6;\n"
" return (a, b);\n"
"};\n"
"export fn main() i32 = {\n"
" let (x, y) = mk();\n"
" if (x.len: i32 != 1) { return 1; };\n"
" if (x.cap: i32 != 5) { return 2; };\n"
" if (y.len: i32 != 2) { return 3; };\n"
" if (y.cap: i32 != 6) { return 4; };\n"
" return 0;\n"
"};\n",
0, 0, NULL },
};
static int
file_contains(const char *path, const char *needle)
{
FILE *f = fopen(path, "rb");
if (!f) return 0;
char buf[8192];
size_t n = fread(buf, 1, sizeof buf - 1, f);
fclose(f);
buf[n] = '\0';
return strstr(buf, needle) != NULL;
}
/* Returns 0 on pass, nonzero on fail. For builderr rows the build must
* FAIL and (if experr is set) the diagnostic must carry the loud-stop
* message; for normal rows the build must succeed and the binary's exit
* must equal r->want. */
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[96], src[128], outbin[128], errf[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/tuplenary_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/tuplenary_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/tuplenary_%d_%d", tmpdir, getpid(), i);
snprintf(errf, sizeof errf, "%s/err", tmpdir);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
driver, outbin, src, errf);
int brc = runwait(cmd);
if (r->builderr) {
int ok = (brc != 0)
&& (r->experr == NULL || file_contains(errf, r->experr));
if (!ok)
fprintf(stderr,
"row[%s]: %s expected loud-stop builderr (brc=%d)\n",
r->label, driver, brc);
runwait(rmcmd);
return ok ? 0 : 1;
}
if (brc != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
return (got == r->want) ? 0 : (got ? got : 1);
}
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,
"tuple_nary_destructure_run: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int rc = run_driver(drivers[d].path, &rows[i], i);
total++;
if (rc != 0) {
fprintf(stderr,
"tuple_nary_destructure_run[%s][%s]: rc=%d\n",
drivers[d].name, rows[i].label, rc);
fail++;
}
}
}
if (fail) {
fprintf(stderr, "tuple_nary_destructure_run: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("tuple_nary_destructure_run: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,293 +0,0 @@
/*
* 947_tuple_index_read_run — #121: const-slice tuple-element READ.
*
* Three legs of indexed-tuple read off a `const [](str,*fn)` (fold-6's
* charclass_map shape), all sharing the &tbl[i] place-spine (#116):
*
* (a) FIELD `tbl[i].N` (bound) — was LOUD both stages ("unsupported
* field-read shape": the tuple-field dispatch fired only for an
* N_IDENT base; an N_INDEX base fell to the read-resolver fatal).
* Now resolved via cgplaceaddr + the per-element-kind arms
* (str-triple / fn-8B). align-loud→both-accept-correct.
*
* (b) WHOLE `let e = tbl[i]` then read e.N — was SILENT both-wrong-
* IDENTICAL (#263, word0-only: the in-cap tuple receive read a
* register cursor the N_INDEX load never filled past word0, so
* str.len→0 and the *fn word was garbage). Now fills all gptotal
* cursor words from &tbl[i]. align-both-correct.
*
* (c) for-range over a module-global slice — LOUD-STOP both stages
* (the for-range spine has no global-base resolution; pre-fix it
* read saved-BP → SEGV with divergent cs≠ww asm). Filed as a #121
* sibling; loud is byte-id-neutral + a pure improvement.
*
* POLARITY: leg (a) cstage is the byte-id ref (align-loud→accept); leg
* (b) byte-id is BLIND (#263) — both stages were identically truncated,
* so the runtime READ-BACK with DISTINCT-per-row values (str lengths
* differ; fns fa↔'a'/fz↔'z' differ) is the only net: a wrong/truncated
* word is CAUGHT, not masked.
*
* MUST use `[](str,*fn)`, NOT `[](str,int)` — the latter is #120
* wwstage-checker-rejected ("let: not assignable") BEFORE the read runs.
*
* NNN<950, self-contained (/tmp, no imports) — rule-14's selfhost-sibling
* race does not apply (941/944/945/946 precedent). Every K_RUN row also
* pins cstage/wwstage asm 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;
}
static int
slurp_eq(const char *a, const char *b)
{
FILE *fa = fopen(a, "rb");
FILE *fb = fopen(b, "rb");
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
int rc = 0;
for (;;) {
int ca = fgetc(fa), cb = fgetc(fb);
if (ca != cb) { rc = -1; break; }
if (ca == EOF) break;
}
fclose(fa); fclose(fb);
return rc;
}
#define K_RUN 0 /* build+run both drivers, exit==want, + cs==ww byte-id */
#define K_BUILDERR 1 /* build must FAIL with experr on BOTH drivers (rule 7) */
struct row { const char *label; const char *src; int want;
int kind; const char *experr; };
static int
errlog_has(const char *path, const char *needle)
{
FILE *f = fopen(path, "rb");
if (!f) return 0;
char buf[8192];
size_t got = fread(buf, 1, sizeof buf - 1, f);
fclose(f);
buf[got] = '\0';
return strstr(buf, needle) != NULL;
}
static const struct row rows[] = {
/* leg (a): direct `tbl[i].N` field read, bound then observed.
* str field (distinct lengths 2 vs 4) + fn field (distinct fns,
* each called back). Was LOUD both stages at base 761cfa4. */
{ "field_read",
"package main;\n"
"fn fa(c: rune) bool = { return c == 'a'; };\n"
"fn fz(c: rune) bool = { return c == 'z'; };\n"
"const tbl: [](str, *fn(c: rune) bool) = "
"[(\"ab\", &fa), (\"cdef\", &fz)];\n"
"export fn main() i32 = {\n"
" let s1 = tbl[1].0;\n"
" let s0 = tbl[0].0;\n"
" if (len(s1) != 4) { return 1; };\n"
" if (len(s0) != 2) { return 2; };\n"
" let f1 = tbl[1].1;\n"
" if (!(*f1)('z')) { return 3; };\n"
" if ((*f1)('a')) { return 4; };\n"
" let f0 = tbl[0].1;\n"
" if (!(*f0)('a')) { return 5; };\n"
" if ((*f0)('z')) { return 6; };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* leg (b): whole-element bind then read e.0 (str.len, catches the
* w1→0 truncation) + e.1 (the *fn word, catches w3 garbage).
* DISTINCT per row — a truncated/wrong word is caught not masked.
* Was SILENT both-wrong-identical (#263) at base 761cfa4. */
{ "whole_element",
"package main;\n"
"fn fa(c: rune) bool = { return c == 'a'; };\n"
"fn fz(c: rune) bool = { return c == 'z'; };\n"
"const tbl: [](str, *fn(c: rune) bool) = "
"[(\"ab\", &fa), (\"cdef\", &fz)];\n"
"export fn main() i32 = {\n"
" let e = tbl[1];\n"
" if (len(e.0) != 4) { return 1; };\n"
" if (!(*e.1)('z')) { return 2; };\n"
" if ((*e.1)('a')) { return 3; };\n"
" let e0 = tbl[0];\n"
" if (len(e0.0) != 2) { return 4; };\n"
" if (!(*e0.1)('a')) { return 5; };\n"
" if ((*e0.1)('z')) { return 6; };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* write-face of leg (b): a tuple-LITERAL store into an indexed
* element `a[i] = (3,4)`, read back whole. DISTINCT words per row
* (3≠4, 7≠9) so the pre-fix word0-only store (cgen.c:6249 fell to
* the scalar tail) is CAUGHT — t.1 would read 0. Both faces (this
* store + the leg-b read) must be correct or the round-trip fails;
* the read alone was green by stale-register luck (809 tuple_elem). */
{ "store_roundtrip",
"package main;\n"
"export fn main() i32 = {\n"
" let a: [2](u64, u64);\n"
" a[1] = (3u64, 4u64);\n"
" a[0] = (7u64, 9u64);\n"
" let t = a[1];\n"
" if (t.0 != 3) { return 1; };\n"
" if (t.1 != 4) { return 2; };\n"
" let s = a[0];\n"
" if (s.0 != 7) { return 3; };\n"
" if (s.1 != 9) { return 4; };\n"
" return 0;\n"
"};\n", 0, K_RUN, NULL },
/* leg (c): for-range over a module-global slice LOUD-STOPS,
* symmetric both stages (segfault→compile-error, byte-id-neutral).
* `n += 1` (not len()) sidesteps the #26 len-vs-int checker reject
* so the cgen loud is what fires, on BOTH stages. */
{ "forrange_global_loud",
"package main;\n"
"fn fa(c: rune) bool = { return c == 'a'; };\n"
"const tbl: [](str, *fn(c: rune) bool) = "
"[(\"ab\", &fa), (\"cdef\", &fa)];\n"
"export fn main() i32 = {\n"
" let n: int = 0;\n"
" for (let e .. tbl) { n += 1; };\n"
" return n: i32;\n"
"};\n", 0, K_BUILDERR,
"for-range over a module-global" },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char tmpdir[96], src[160], errf[160], outbin[160], rmcmd[176], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/tir_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/tir_%d_%d.ww", tmpdir, getpid(), i);
snprintf(errf, sizeof errf, "%s/tir_%d_e_%d", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/tir_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -1; }
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s >/dev/null 2>%s",
driver, outbin, src, errf);
int brc = runwait(cmd);
if (r->kind == K_BUILDERR) {
int ok = (brc != 0)
&& (r->experr == NULL || errlog_has(errf, r->experr));
if (!ok)
fprintf(stderr, "row[%s]: %s expected loud builderr "
"\"%s\" (brc=%d)\n", r->label, driver,
r->experr ? r->experr : "", brc);
runwait(rmcmd);
return ok ? 0 : 1;
}
if (brc != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
runwait(rmcmd);
return -1;
}
int got = runwait(outbin);
runwait(rmcmd);
if (got != r->want) {
fprintf(stderr, "row[%s]: %s exit %d, want %d\n",
r->label, driver, got, r->want);
return 1;
}
return 0;
}
static int
asm_byte_identical(const char *bin, const struct row *r, int i)
{
char src[96], cs[96], ws[96], cmd[1024];
snprintf(src, sizeof src, "/tmp/tir_asm_%d_%d.ww", getpid(), i);
snprintf(cs, sizeof cs, "/tmp/tir_asm_%d_%d_c.s", getpid(), i);
snprintf(ws, sizeof ws, "/tmp/tir_asm_%d_%d_w.s", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
unlink(src);
return -1;
}
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
bin, ws, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
unlink(src); unlink(cs);
return -1;
}
int rc = slurp_eq(cs, ws);
if (rc != 0)
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
r->label);
unlink(src); unlink(cs); unlink(ws);
return rc;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[2080];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[2120], wdrv[2120];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
int n = (int)(sizeof rows / sizeof rows[0]);
int total = 0, fail = 0;
for (int i = 0; i < n; i++) {
total++;
if (run_driver(cdrv, &rows[i], i) != 0) fail++;
}
if (access(wdrv, X_OK) == 0) {
for (int i = 0; i < n; i++) {
total++;
if (run_driver(wdrv, &rows[i], i) != 0) fail++;
}
for (int i = 0; i < n; i++) {
if (rows[i].kind == K_BUILDERR)
continue;
total++;
if (asm_byte_identical(bin, &rows[i], i) != 0) fail++;
}
}
if (fail) {
fprintf(stderr, "tuple_index_read: %d/%d checks failed\n",
fail, total);
return 1;
}
printf("tuple_index_read: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,43 +1,20 @@
/*
* 954_tuprecv_run — runtime + byte-id regression net for #102: the 16B
* whole-tuple-from-call receive (`let t = mk()`, then field reads t.0 /
* t.1).
* 954_tuprecv_run — carrier-split residue of #102 (16B whole-tuple-from-call
* receive). The VALUE rows and the cs==ww .s byte-id dimension migrated to the
* fold-2 row-table test/lang/tuprecv_test.ww (cstage run via test-lang + cs==ww
* via test-lang-byteid). What CANNOT ride a value/byte-id @test is the third
* dimension the old driver also carried: a tool-OUTPUT assertion that w6c_ww
* emits NO `asserttyped:` diagnostic on the un-annotated float-destructure
* binding `let (f, i) = mk()` — the #121 A-narrow stamp net. That is the same
* category as the Class-A asm-presence twins the Fam-7 commit deliberately KEPT
* (a diagnostic-observer grep, not a value or byte-id check), so it stays here
* as a minimal retained pin rather than being silently dropped (drew ruling,
* #5-C2).
*
* Root: wwstage's cglet had NO 16B tuple-from-call receive branch, so a
* `let t = call()` whose callee returns a 2-eightbyte (16B) tuple fell
* through to the generic single-word store (`MOVQ AX, off(BP)`) and
* never spilled word1 (the DX eightbyte) — silent loss of t.1. cstage
* has the branch (cmd/w6c/cgen.c:6652: spill AX -> off+0 AND DX ->
* off+8). The fix mirrors it in cglet.
*
* NOTE on the diagnosis: this is NOT a tupstore cursor off-by-one, and
* it is NOT f64-specific. The destructure form `let (a,b) = mk()`
* (cgmlet + tupstore cursor) was already byte-id; only the whole-tuple
* N_LET receive dropped word1. An f64 element only made it visible
* first, but the all-integer (i64,i64) receive dropped DX too — so the
* "all-integer control" row below is ALSO a fix row (it diverges on
* master 60c3e51, byte-id after the fix), not a pure no-op control.
*
* THIS TEST MUST CATCH A WWSTAGE-ONLY DIVERGENCE. cstage is correct
* before and after, so a cstage-only probe is gate-blind. Each row
* carries BOTH dimensions (modelled on 953_f64crossmod_run):
* (a) cstage `ww build` + run, asserting the exit code — pins the asm
* both stages converge on as runtime-correct.
* (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge. On master
* 60c3e51 (pre-fix) the three scalar-pair single-var rows each
* diverge by exactly one dropped `MOVQ DX, -8(BP)`; post-fix all
* are byte-identical.
*
* Three CONTROL rows pin that the fix touches nothing else: the
* destructure form `let (a,b) = mk()` (cgmlet + tupstore cursor), a
* 32B wide/str-element single-var tuple, and a 16B struct{i64,i64}
* single-var receive — all stay byte-id both pre- and post-fix. The fix
* is in cglet/N_LET, gated on sz==16 AND rettupleof != nil; the struct
* row is the over-catch guard: sz IS 16 but rettupleof returns nil for a
* non-tuple return, so the new branch must NOT hijack the struct's
* generic store. Without that guard a struct receive would route through
* the AX/DX tuple spill and miscompile any struct whose layout differs
* from {word0,word1}.
* Non-vacuous: pre-stamp (HEAD before #121) w6c_ww fired `asserttyped` on the
* f64 binding ident (nil n.type_); post-fix the binder carries the checker
* stamp and the diagnostic is absent. wwstage-only — cstage's check.c has no ww
* asserttyped walker, so the pin invokes w6c_ww directly.
*/
#include <stdio.h>
#include <stdlib.h>
@@ -54,100 +31,14 @@ runwait(const char *cmd)
return -1;
}
struct row { const char *label; const char *src; int want_exit; int chk_stamped; };
static const struct row rows[] = {
/* mixed (f64, i64): f=2.5 -> i32 2, i=7 -> i32 7, 2+7 = 9. The
* f64 element rides its eightbyte in AX at receive and is re-read
* from X0 at field-read; the i64 (t.1) rides DX — the dropped
* word. */
{ "f64_i64",
"package main;\n"
"fn mk() (f64, i64) = { return (2.5, 7); };\n"
"export fn main() i32 = {\n"
"\tlet t = mk();\n"
"\tlet f: f64 = t.0;\n"
"\tlet i: i64 = t.1;\n"
"\treturn (f: i32) + (i: i32);\n"
"};\n", 9 },
/* order-swap (i64, f64): f64 in the 2nd slot. i=7, f=2.5 -> 2,
* 7+2 = 9. Confirms the spill is position-agnostic (both words go
* over the integer cursor regardless of which holds the f64). */
{ "i64_f64",
"package main;\n"
"fn mk() (i64, f64) = { return (7, 2.5); };\n"
"export fn main() i32 = {\n"
"\tlet t = mk();\n"
"\tlet i: i64 = t.0;\n"
"\tlet f: f64 = t.1;\n"
"\treturn (i: i32) + (f: i32);\n"
"};\n", 9 },
/* all-integer (i64, i64): a=5, b=7, 5+7 = 12. NOT a no-op control
* — pre-fix wwstage dropped DX here too (proves the fix is not
* f64-gated). */
{ "i64_i64",
"package main;\n"
"fn mk() (i64, i64) = { return (5, 7); };\n"
"export fn main() i32 = {\n"
"\tlet t = mk();\n"
"\tlet a: i64 = t.0;\n"
"\tlet b: i64 = t.1;\n"
"\treturn (a: i32) + (b: i32);\n"
"};\n", 12 },
/* CONTROL — destructure form `let (f,i) = mk()` (cgmlet + tupstore
* cursor, a different and already-correct path; bootstrap relies on
* it, which is why 990-997 pass). The fix is in cglet/N_LET only, so
* this stays byte-id pre- and post-fix. f=2.5->2, i=7, 2+7 = 9. */
{ "ctl_destr",
/* the ctl_destr construct: an un-annotated float-destructure binding. */
static const char *src =
"package main;\n"
"fn mk() (f64, i64) = { return (2.5, 7); };\n"
"export fn main() i32 = {\n"
"\tlet (f, i) = mk();\n"
"\treturn (f: i32) + (i: i32);\n"
"};\n", 9, 1 },
/* CONTROL — wide/str element single-var (i64, str) is a 32B tuple
* handled by the separate 32B receive branch; the fix's sz==16 gate
* excludes it, so it stays byte-id pre- and post-fix. t.0 = 7. */
{ "ctl_str",
"package main;\n"
"fn mk() (i64, str) = { return (7, \"hi\"); };\n"
"export fn main() i32 = {\n"
"\tlet t: (i64, str) = mk();\n"
"\treturn t.0: i32;\n"
"};\n", 7 },
/* CONTROL — over-catch guard. A 16B struct{i64,i64} from-call receive
* is sz==16 like the bug rows, but rettupleof returns nil for a
* non-tuple return, so the fix's new branch must NOT hijack it; it
* stays on the generic struct store and byte-id pre- and post-fix.
* Pins that the fix's tuple/struct discrimination (#102) holds. a=5,
* b=7, 5+7 = 12. */
{ "ctl_struct",
"package main;\n"
"type pair = struct { a: i64, b: i64 };\n"
"fn mk() pair = { return pair { a = 5, b = 7 }; };\n"
"export fn main() i32 = {\n"
"\tlet t = mk();\n"
"\treturn (t.a: i32) + (t.b: i32);\n"
"};\n", 12 },
{ 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;
}
"};\n";
int
main(void)
@@ -162,116 +53,44 @@ main(void)
bin = absbin;
}
char w6c[1100], w6c_ww[1100];
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
char w6c_ww[1100];
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
if (access(w6c_ww, X_OK) != 0) {
fprintf(stderr, "tuprecv: w6c_ww missing — cannot run the "
"cs==ww byte-id gate (the whole point of this test)\n");
"asserttyped-absence pin\n");
return 1;
}
int n = 0, fail = 0;
for (int i = 0; rows[i].src; i++, n++) {
/* src reused across the build-phase (a) AND the w6c/w6c_ww
* byte-id phase (b)/(c); keep src, outbin and both .s under one
* tmpdir and defer a single rm -rf to the end so the compiler's
* .sepwork scratch (derived from the source path) lands in
* tmpdir, not bare /tmp. */
char tmpdir[64], rmcmd[160];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwtup_%d_d_%d",
getpid(), i);
char tmpdir[64], rmcmd[160], srcp[128], errf[128], cmd[2048];
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwtuprecv_%d", getpid());
mkdir(tmpdir, 0755);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
snprintf(srcp, sizeof srcp, "%s/ctl_destr.ww", tmpdir);
snprintf(errf, sizeof errf, "%s/err.txt", tmpdir);
char src[128];
snprintf(src, sizeof src, "%s/wwtup_%d_%d.ww",
tmpdir, getpid(), i);
FILE *f = fopen(src, "wb");
if (f == NULL) { fail++; runwait(rmcmd); continue; }
fputs(rows[i].src, f);
FILE *f = fopen(srcp, "wb");
if (f == NULL) { runwait(rmcmd); return 1; }
fputs(src, f);
fclose(f);
/* (a) cstage build + run; -o pins the binary into tmpdir. */
char outbin[128];
snprintf(outbin, sizeof outbin, "%s/wwtup_%d_%d",
tmpdir, getpid(), i);
int fail = 0;
char cmd[2048];
snprintf(cmd, sizeof cmd, "%s/ww build -o %s %s",
bin, outbin, src);
/* w6c_ww must compile the construct cleanly AND emit no asserttyped. */
snprintf(cmd, sizeof cmd, "%s -o /dev/null %s 2>%s", w6c_ww, srcp, errf);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: cstage build failed\n",
rows[i].label);
fail++;
runwait(rmcmd);
continue;
}
int got = runwait(outbin);
if (got != rows[i].want_exit) {
fprintf(stderr, "row[%s]: cstage exit %d, want %d\n",
rows[i].label, got, rows[i].want_exit);
fprintf(stderr, "tuprecv: w6c_ww failed to compile the "
"float-destructure construct\n");
fail++;
}
/* (b) cs==ww byte-id gate: emit .s from both stages, cmp.
* FAILS on the pre-fix wwstage divergence (dropped MOVQ DX). */
char cs_s[128], ws_s[128];
snprintf(cs_s, sizeof cs_s, "%s/wwtup_%d_%d_cs.s",
tmpdir, getpid(), i);
snprintf(ws_s, sizeof ws_s, "%s/wwtup_%d_%d_ww.s",
tmpdir, getpid(), i);
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c, cs_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label);
fail++; runwait(rmcmd); continue;
}
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
w6c_ww, ws_s, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: w6c_ww failed\n",
rows[i].label);
fail++; runwait(rmcmd); continue;
}
if (slurp_eq(cs_s, ws_s) != 0) {
fprintf(stderr,
"row[%s]: cstage/wwstage .s DIFFER (rule-10 "
"byte-id violation)\n", rows[i].label);
fail++;
}
/* (c) #121 A-narrow stamp gate: the un-annotated float-
* destructure binding must now carry a checker type stamp, so
* w6c_ww emits no `asserttyped:` diagnostic. Non-vacuous —
* pre-stamp (HEAD) w6c_ww fires asserttyped on the f64 binding
* ident (nil n.type_). */
if (rows[i].chk_stamped) {
char errf[128];
snprintf(errf, sizeof errf,
"%s/wwtup_%d_%d_err.txt", tmpdir, getpid(), i);
snprintf(cmd, sizeof cmd,
"%s -o /dev/null %s 2>%s", w6c_ww, src, errf);
runwait(cmd);
snprintf(cmd, sizeof cmd,
"grep -q asserttyped %s", errf);
snprintf(cmd, sizeof cmd, "grep -q asserttyped %s", errf);
if (runwait(cmd) == 0) {
fprintf(stderr, "row[%s]: w6c_ww emitted "
"asserttyped (destructure binding "
"unstamped)\n", rows[i].label);
fprintf(stderr, "tuprecv: w6c_ww emitted asserttyped on the "
"un-annotated float destructure binding (#121 stamp net)\n");
fail++;
}
}
runwait(rmcmd);
}
if (fail) {
fprintf(stderr, "%d/%d tuple-receive tests failed\n",
fail, n);
return 1;
}
printf("tuprecv: %d/%d ok (cstage run + cs==ww byte-id)\n", n, n);
runwait(rmcmd);
if (fail) return 1;
printf("tuprecv: asserttyped-absence pin ok (#121)\n");
return 0;
}

View File

@@ -1,178 +0,0 @@
/*
* 989_tagtupfieldsize_run (#53, sibling of #43): a for-range destructure
* over an array of tuples whose field is a TAGGED UNION must stride by the
* tuple's true size and copy the field's full box (tag + payload), not the
* 8B scalar default.
*
* THE BUG (cat-A silent miscompile, gate-blind): wwstage paramfieldsize
* (selfhost/cmd/wcc/cgenstmt.ww) — the structural sizer the for-range
* destructure uses for the tuple stride and each field's offset — had no
* tagged-union arm, so a `(i64|bool)` field sized 8 (the default) instead
* of its 16B box (tag + slot-padded payload). The for-range DESTRUCTURE-READ
* path is unguarded by the #270-1c array-literal construction gate (#43
* lineage; tuples built by whole-tuple element store). The 8B bind copied
* only the tag word and read the payload from stale stack: cs=56 (correct,
* cstage's tp->type->size=16) vs ww=9 — the cat-A signature. THE FIX: read
* the tagged box width from the checker-stamped tinfo (rule-13), mirroring
* the N_TSLICE arm's type-table SSoT; the stamp collapses the `tu` alias so
* an N_TNAME field naming a tagged union resolves too.
*
* The tagged value enters the tuple from a LOCAL ident box (the only wired
* tagged-tuple-element source, cgenexpr #22a); a cast source loud-rejects.
*
* Rows (cstage `ww` + wwstage `ww_ww`; rule-10; pin the absolute value):
* row | reads | exit (cs==ww)
* ------------------+--------------------------------------+--------------
* tag_payload | match(a){i64=>v}+n, payloads 7/9 | 56 (7+10+9+30)
* scalar_tuple_ctl | for(.. [2](i64,i64)) — arm untouched | 48 (control)
* Pre-fix (8B bind, payload word dropped): tag_payload ww=9 != cs=56.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
struct row {
const char *label;
const char *src;
int want_exit; /* >= 0: also pin the absolute value; -1: cs==ww only */
};
static const struct row rows[] = {
/* (1) tag_payload (#53 teeth): the tagged binding's payload word must
* be copied. Pre-fix the 8B bind dropped it → match reads stale → 9. */
{ "tag_payload",
"package main;\n"
"type tu = (i64 | bool);\n"
"export fn main() int = {\n"
" let t0: tu = 7: tu;\n"
" let t1: tu = 9: tu;\n"
" let xs: [2](tu, i64);\n"
" xs[0] = (t0, 10);\n"
" xs[1] = (t1, 30);\n"
" let sum: i64 = 0;\n"
" for (let (a, n) .. xs) {\n"
" match (a) {\n"
" case let v: i64 => sum = sum + v + n;\n"
" case let b: bool => sum = sum + 1000;\n"
" };\n"
" };\n"
" return sum: int;\n"
"};\n",
56 },
/* (2) control — scalar-only tuple (no tagged field): paramfieldsize
* already handled it; the new tagged arm must not touch it. 3+10+5+30. */
{ "scalar_tuple_ctl",
"package main;\n"
"export fn main() int = {\n"
" let xs: [2](i64, i64);\n"
" xs[0] = (3, 10); xs[1] = (5, 30);\n"
" let sum: i64 = 0;\n"
" for (let (a, b) .. xs) { sum = sum + a + b; };\n"
" return sum: int;\n"
"};\n",
48 },
};
/* 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 tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/tagtupfs_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/tagtupfs_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/tagtupfs_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -2; }
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
driver, outbin, src);
int brc = runwait(cmd);
int got = -1;
if (brc == 0) got = runwait(outbin);
runwait(rmcmd);
return brc == 0 ? got : -1;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[1024], wdrv[1024];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
int have_ww = (access(wdrv, X_OK) == 0);
int n = (int)(sizeof rows / sizeof rows[0]);
int total = 0, fail = 0;
for (int i = 0; i < n; i++) {
total++;
int gc = run_build(cdrv, &rows[i], i);
if (gc < 0) {
fprintf(stderr, "tagtupfieldsize_run[cstage][%s]: build/run "
"failed (got %d)\n", rows[i].label, gc);
fail++;
continue;
}
if (rows[i].want_exit >= 0 && gc != rows[i].want_exit) {
fprintf(stderr, "tagtupfieldsize_run[cstage][%s]: exit=%d "
"want=%d\n", rows[i].label, gc, rows[i].want_exit);
fail++;
}
if (!have_ww) {
fprintf(stderr, "tagtupfieldsize_run: skip wwstage (no %s)\n",
wdrv);
continue;
}
int gw = run_build(wdrv, &rows[i], i);
/* rule-10: the cat-A invariant is cs == ww */
if (gw != gc) {
fprintf(stderr, "tagtupfieldsize_run[%s]: cs=%d != ww=%d "
"(tagged tuple-field stride/offset divergence — #53)\n",
rows[i].label, gc, gw);
fail++;
}
if (rows[i].want_exit >= 0 && gw != rows[i].want_exit) {
fprintf(stderr, "tagtupfieldsize_run[wwstage][%s]: exit=%d "
"want=%d\n", rows[i].label, gw, rows[i].want_exit);
fail++;
}
}
if (fail) {
fprintf(stderr, "tagtupfieldsize_run: %d/%d checks failed\n",
fail, total);
return 1;
}
printf("tagtupfieldsize_run: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -1,226 +0,0 @@
/*
* 989_tupfieldsize_run — F7-c4 (#43): a for-range destructure over an
* array of tuples must stride by the tuple's TRUE size; a slice/str/nested
* tuple FIELD carries its full width, not the 8B scalar default.
*
* THE BUG (cat-A silent miscompile, gate-blind): paramfieldsize
* (selfhost/cmd/wcc/cgenstmt.ww) — the structural sizer the for-range
* destructure uses to compute the tuple stride and each field's offset —
* had no N_TSLICE / N_TTUPLE arm, so a `[]T` tuple-field sized 8 (the
* default) instead of its 24B header. The #270-1c array-literal guard
* blocks only the literal CONSTRUCTION; the for-range DESTRUCTURE-READ
* path is unguarded (ken's oracle refuted "unreachable"). For
* `[2]([]u8, i64)` the wwstage strode the tuple at 16 / read field-2 at
* offset 8, vs cstage's 32 / 24 — a cs≠ww divergence (the cat-A
* signature). cstage's tp->type->size (cmd/w6c/cgen.c N_FORRANGE) reads
* the true width. THE FIX: add the N_TSLICE arm (tyslicesize() = 24, the
* slice-header SSoT, rule-13) and the N_TTUPLE arm (sum of 8B-floored
* element slots, recursive), aligning wwstage UP.
*
* #40 (#263, now CLOSED both stages — fused): F7-c4 fixed only the STRIDE,
* so the slice-field row was pinned cs==ww (MATCH-ONLY) at 40, NOT the
* arithmetic-expected 45 — because the destructure LOAD-into-binding copied
* only the PTR word of the 24B slice binding, dropping .len/.cap (both stages
* identically; nobody had re-measured the ww absolute after F7-c4). The fused
* #40 fix (cgen.c N_FORRANGE destructure + cgenstmt.ww cgforrange twin: copy
* the binding's FULL extent for an aggregate sz>8 binding, same word-run +
* sized-tail idiom as the non-destructure copy) lands all three header words.
* The rows now pin the ABSOLUTE value cs==ww across all three words — the
* recurring dropped word in this 24B-aggregate-destructure family is the CAP
* (cf #29 chained-dot, #43 sizer), so the cap row reads b.cap with cap!=len
* (sliced) to teeth it; a ptr+len-only set would pass green while cap stayed
* stale (exactly how #40 hid).
*
* Tuples are built by whole-tuple element store (`xs[i] = (..)`).
*
* Rows (cstage `ww` + wwstage `ww_ww`; rule-10; all pin the absolute value):
* row | reads | exit (cs==ww)
* ------------------+------------------------------------+--------------
* len_read | len(b)+n over [2]([]u8,i64) | 45 (2+10+3+30)
* cap_read | b.cap+n, sliced caps 4/5 (cap!=len) | 49 (4+10+5+30)
* ptr_read | b[0]+ (byte through ptr) | 217 (97+120)
* scalar_tuple_ctl | for(.. [2](i64,i64)) | 48 (control)
* Pre-fix (single-word copy): len_read=40, cap_read=40 (len/cap words both 0),
* ptr_read=217 (word0 always copied — coverage, no teeth); cs==ww==wrong (the
* #263 both-wrong-identical residual the fused fix retires).
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
struct row {
const char *label;
const char *src;
int want_exit; /* >= 0: also pin the absolute value; -1: cs==ww only */
};
static const struct row rows[] = {
/* (1) len_read (#40 teeth, word 1): the slice binding's .len must be
* copied. Pre-fix the single-word copy dropped it → len(b)=0 → 40. */
{ "len_read",
"package main;\n"
"export fn main() int = {\n"
" let b0: []u8 = ['a', 'b'];\n"
" let b1: []u8 = ['x', 'y', 'z'];\n"
" let xs: [2]([]u8, i64);\n"
" xs[0] = (b0, 10);\n"
" xs[1] = (b1, 30);\n"
" let sum: i64 = 0;\n"
" for (let (b, n) .. xs) {\n"
" sum = sum + len(b): i64 + n;\n"
" };\n"
" return sum: int;\n"
"};\n",
45 },
/* (2) cap_read (#40 teeth, word 2 — THE recurring dropped word): caps
* 4/5 are SLICED so cap != len (2/3); pre-fix b.cap=0 → 40. */
{ "cap_read",
"package main;\n"
"export fn main() int = {\n"
" let base0: []u8 = ['a', 'b', 'c', 'd'];\n"
" let base1: []u8 = ['p', 'q', 'r', 's', 't'];\n"
" let xs: [2]([]u8, i64);\n"
" xs[0] = (base0[0:2], 10);\n"
" xs[1] = (base1[0:3], 30);\n"
" let sum: i64 = 0;\n"
" for (let (b, n) .. xs) {\n"
" sum = sum + b.cap: i64 + n;\n"
" };\n"
" return sum: int;\n"
"};\n",
49 },
/* (3) ptr_read (word 0 — coverage; word0 was always copied, no teeth):
* a byte read through the binding's .ptr. b0[0]='a'=97, b1[0]='x'=120. */
{ "ptr_read",
"package main;\n"
"export fn main() int = {\n"
" let b0: []u8 = ['a', 'b'];\n"
" let b1: []u8 = ['x', 'y', 'z'];\n"
" let xs: [2]([]u8, i64);\n"
" xs[0] = (b0, 10);\n"
" xs[1] = (b1, 30);\n"
" let sum: i64 = 0;\n"
" for (let (b, n) .. xs) { sum = sum + b[0]: i64; };\n"
" return sum: int;\n"
"};\n",
217 },
/* (4) control — scalar-only tuple (no aggregate field): paramfieldsize
* already handled it; the sz>8 arm must not touch it. 3+10+5+30 == 48. */
{ "scalar_tuple_ctl",
"package main;\n"
"export fn main() int = {\n"
" let xs: [2](i64, i64);\n"
" xs[0] = (3, 10); xs[1] = (5, 30);\n"
" let sum: i64 = 0;\n"
" for (let (a, b) .. xs) { sum = sum + a + b; };\n"
" return sum: int;\n"
"};\n",
48 },
};
/* 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 tmpdir[64], src[128], outbin[128], rmcmd[160], cmd[1024];
snprintf(tmpdir, sizeof tmpdir, "/tmp/tupfs_%d_d_%d", getpid(), i);
mkdir(tmpdir, 0755);
snprintf(src, sizeof src, "%s/tupfs_%d_%d.ww", tmpdir, getpid(), i);
snprintf(outbin, sizeof outbin, "%s/tupfs_%d_%d", tmpdir, getpid(), i);
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", tmpdir);
FILE *f = fopen(src, "wb");
if (!f) { runwait(rmcmd); return -2; }
fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s build -o %s %s 2>/dev/null",
driver, outbin, src);
int brc = runwait(cmd);
int got = -1;
if (brc == 0) got = runwait(outbin);
runwait(rmcmd);
return brc == 0 ? got : -1;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[1024], wdrv[1024];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
int have_ww = (access(wdrv, X_OK) == 0);
int n = (int)(sizeof rows / sizeof rows[0]);
int total = 0, fail = 0;
for (int i = 0; i < n; i++) {
total++;
int gc = run_build(cdrv, &rows[i], i);
/* cstage must build+run */
if (gc < 0) {
fprintf(stderr, "tupfieldsize_run[cstage][%s]: build/run "
"failed (got %d)\n", rows[i].label, gc);
fail++;
continue;
}
if (rows[i].want_exit >= 0 && gc != rows[i].want_exit) {
fprintf(stderr, "tupfieldsize_run[cstage][%s]: exit=%d "
"want=%d\n", rows[i].label, gc, rows[i].want_exit);
fail++;
}
if (!have_ww) {
fprintf(stderr, "tupfieldsize_run: skip wwstage (no %s)\n",
wdrv);
continue;
}
int gw = run_build(wdrv, &rows[i], i);
/* rule-10: the cat-A invariant is cs == ww */
if (gw != gc) {
fprintf(stderr, "tupfieldsize_run[%s]: cs=%d != ww=%d "
"(stride/offset divergence — #43)\n",
rows[i].label, gc, gw);
fail++;
}
if (rows[i].want_exit >= 0 && gw != rows[i].want_exit) {
fprintf(stderr, "tupfieldsize_run[wwstage][%s]: exit=%d "
"want=%d\n", rows[i].label, gw, rows[i].want_exit);
fail++;
}
}
if (fail) {
fprintf(stderr, "tupfieldsize_run: %d/%d checks failed\n",
fail, total);
return 1;
}
printf("tupfieldsize_run: %d/%d ok\n", total, total);
return 0;
}

View File

@@ -0,0 +1,11 @@
//ww:error "#234-tail"
package main;
fn wide() ([]u8, []u8) = {
let a: []u8; a.len = 1; let b: []u8; b.len = 2;
return (a, b);
};
export fn main() i32 = {
let a: [2][2]([]u8, []u8);
a[0][1] = wide();
return 0;
};

View File

@@ -0,0 +1,12 @@
//ww:error "#234-tail"
package main;
fn wide() ([]u8, []u8) = {
let a: []u8; a.len = 1; let b: []u8; b.len = 2;
return (a, b);
};
type S = struct { f: ([]u8, []u8) };
let g: S;
export fn main() i32 = {
g.f = wide();
return 0;
};

View File

@@ -0,0 +1,12 @@
//ww:error "#234-tail"
package main;
fn wide() ([]u8, []u8) = {
let a: []u8; a.len = 1; let b: []u8; b.len = 2;
return (a, b);
};
type S = struct { arr: [2]([]u8, []u8) };
export fn main() i32 = {
let s: S;
s.arr[1] = wide();
return 0;
};

View File

@@ -0,0 +1,12 @@
//ww:error "#234-tail"
package main;
fn wide() ([]u8, []u8) = {
let a: []u8; a.len = 1; let b: []u8; b.len = 2;
return (a, b);
};
fn ix() int = { return 0; };
export fn main() i32 = {
let arr: [2]([]u8, []u8);
arr[ix()] = wide();
return 0;
};

View File

@@ -0,0 +1,13 @@
//ww:error "#234-tail"
package main;
fn wide() ([]u8, []u8) = {
let a: []u8; a.len = 1; let b: []u8; b.len = 2;
return (a, b);
};
type S = struct { f: ([]u8, []u8) };
export fn main() i32 = {
let s: S;
let p: *S = &s;
p.f = wide();
return 0;
};

View File

@@ -0,0 +1,15 @@
//ww:error "tuple element must be a scalar, str, slice, or tagged-union"
// Lifted from test/wcc/945_tuple_lit_declblind_run.c reject row
// "nested_tuple_arg" (#64/#68/rule-7): a NESTED-TUPLE tuple-arg element has
// no transport and stays rule-7 LOUD. The #64/#68 fix graduates ONLY a
// declared-tagged element; the TY_TUPLE/STRUCT/ARRAY arms are preserved, so a
// future widen-everything regression re-breaks this. The substring is the
// shared diagnostic body, byte-identical on both stages (no file:line, no
// cstage "ww: " prefix).
package main;
fn h(t: ((i32, i32), i64)) i32 = {
return 0;
};
export fn main() i32 = {
return h(((1, 2), 3));
};

View File

@@ -0,0 +1,10 @@
//ww:error "over-cap tuple initialiser from a non-call source unwired"
package main;
fn pr() ((void | size), (void | size), size) = {
let t: ((void | size), (void | size), size) = (void, void, 1);
return t;
};
export fn main() i32 = {
let t = pr();
return t.2: i32;
};

View File

@@ -0,0 +1,11 @@
//ww:error "#22b: tagged element in an over-cap (sret) tuple return from a non-ident or widening source unwired"
package main;
fn g() (void | size) = { return 5: size; };
fn pr() ((void | size), (void | size), size) = {
let mx: (void | size) = 2: size;
return (g(), mx, 7);
};
export fn main() i32 = {
let t = pr();
return t.2: i32;
};

View File

@@ -0,0 +1,10 @@
//ww:error "#22b: tagged element in an over-cap (sret) tuple return from a non-ident or widening source unwired"
package main;
fn pr() ((void | size), (void | size), size) = {
let mx: (void | size) = 5: size;
return (1: size, mx, 9);
};
export fn main() i32 = {
let t = pr();
return t.2: i32;
};

View File

@@ -0,0 +1,8 @@
//ww:error "element kind unsupported"
package main;
export fn main() i32 = {
let s: [](u32, u32) = [];
append(s, (1, 2));
if (len(s) != 1) { return 1; };
return 0;
};

View File

@@ -0,0 +1,9 @@
//ww:error "tagged cast source shape unwired"
package main;
type un3 = (void | size | str);
type un4 = (void | size | str | *u8);
export fn main() i32 = {
let w: un4 = (5: size): un3;
if (!(w is size)) { return 1; };
return 0;
};

View File

@@ -0,0 +1,15 @@
//ww:error "on a global tagged ident unwired"
package main;
type err = !void;
let g: (size | err) = 7: size;
fn use() (size | err) = {
let x = g?;
return x: size;
};
export fn main() i32 = {
match (use()) {
case let s: size => { if (s != 7) { return 1; }; };
case err => { return 2; };
};
return 0;
};

View File

@@ -0,0 +1,8 @@
//ww:error "tuple arg element kind unsupported"
package main;
fn send(t: ((void | size), size)) size = { return t.1; };
export fn main() i32 = {
let mn: (void | size) = 5: size;
let t: ((void | size), size) = (mn, 4);
return send(t): i32;
};

View File

@@ -0,0 +1,8 @@
//ww:error "unsupported assign target shape"
package main;
export fn main() i32 = {
let mn: (void | size) = 5: size;
let t: ((void | size), size) = (mn, 4);
t.1 = 9;
return t.1: i32;
};

View File

@@ -0,0 +1,4 @@
//ww:error "unsupported element init (int/str literals only; rule 7)"
package main;
let g: ((void | size), i64) = (5, 4);
export fn main() i32 = { return g.1: i32; };

View File

@@ -0,0 +1,8 @@
//ww:error "tagged tuple element from a non-local source shape unwired"
package main;
fn g() (void | size) = { return 5: size; };
fn mk() ((void | size), size) = { return (g(), 4); };
export fn main() i32 = {
let t = mk();
return t.1: i32;
};

View File

@@ -0,0 +1,7 @@
//ww:error "tuple literal exceeds register-return ABI capacity"
package main;
export fn main() i32 = {
let t: (str, str) = ("ab", "cde");
if (t.0.len != 2) { return 1; };
return 0;
};

View File

@@ -0,0 +1,8 @@
//ww:error "tuple literal exceeds register-return ABI capacity"
package main;
export fn main() i32 = {
let e: (void | u32 | str) = "abc";
let t: (u64, (void | u32 | str)) = (7u64, e);
if (t.0 != 7u64) { return 1; };
return 0;
};

View File

@@ -0,0 +1,12 @@
//ww:error "tuple arg from unsupported source shape"
package main;
type holder = struct { t: (i64, i64) };
fn sum(t: (i64, i64)) i64 = {
return t.0 + t.1;
};
export fn main() i32 = {
let tt: (i64, i64) = (1, 2);
let h: holder = holder{ t = tt };
if (sum(h.t) != 3) { return 1; };
return 0;
};

View File

@@ -0,0 +1,11 @@
//ww:error "tuple element must be a scalar"
package main;
fn f(t: ((i64, i64), i64)) i64 = {
return t.1;
};
export fn main() i32 = {
let inner: (i64, i64) = (1, 2);
let t: ((i64, i64), i64) = (inner, 9);
if (f(t) != 9) { return 1; };
return 0;
};

View File

@@ -0,0 +1,13 @@
//ww:error "tuple ident exceeds register-return ABI capacity"
package main;
fn f(t: (str, str)) i64 = {
return (len(t.0) + len(t.1)): i64;
};
fn g() (str, str) = {
return ("ab", "cde");
};
export fn main() i32 = {
let t: (str, str) = g();
if (f(t) != 5) { return 1; };
return 0;
};

View File

@@ -0,0 +1,7 @@
//ww:error "unsupported element init (int/str literals only; rule 7)"
package main;
let g: (f64, i64) = (2.5, 4);
export fn main() i32 = {
if (g.1 != 4) { return 1; };
return 0;
};

View File

@@ -0,0 +1,7 @@
//ww:error "unsupported assign target shape"
package main;
let g: (i64, i64) = (3, 4);
export fn main() i32 = {
g.0 = 7;
return 0;
};

View File

@@ -0,0 +1,8 @@
//ww:error "global tuple as a first-class value unwired"
package main;
let g: (i64, i64) = (3, 4);
export fn main() i32 = {
let q: (i64, i64) = g;
if (q.0 != 3) { return 1; };
return 0;
};

View File

@@ -0,0 +1,7 @@
//ww:error "tuple element must be a scalar"
package main;
export fn main() i32 = {
let t = ((4, 2), 36);
if (t.1 != 36) { return 1; };
return 0;
};

View File

@@ -0,0 +1,11 @@
//ww:error "tagged tuple element from a non-local source shape unwired"
package main;
type un16 = (void | size);
fn mke() un16 = {
return 5: size;
};
export fn main() i32 = {
let t: (un16, size) = (mke(), 9);
if (t.1 != 9) { return 1; };
return 0;
};

View File

@@ -0,0 +1,11 @@
//ww:error "tagged element in an over-cap (sret) tuple return"
package main;
type un = (void | size);
fn mk() (un, un, size, size) = {
return (5: size, void, 8, 9);
};
export fn main() i32 = {
let t = mk();
if (t.2 != 8) { return 1; };
return 0;
};

View File

@@ -0,0 +1,14 @@
//ww:error "widening into a wider declared union slot needs a tag remap"
package main;
type st16 = struct { a: size, b: size };
type small = (void | u8);
type big = (void | u8 | st16);
fn mk() (big, size) = {
let s: small = 5u8;
return (s, 9);
};
export fn main() i32 = {
let t = mk();
if (t.1 != 9) { return 1; };
return 0;
};

View File

@@ -0,0 +1,9 @@
//ww:error "for-range over a module-global"
package main;
fn fa(c: rune) bool = { return c == 'a'; };
const tbl: [](str, *fn(c: rune) bool) = [("ab", &fa), ("cdef", &fa)];
export fn main() i32 = {
let n: int = 0;
for (let e .. tbl) { n += 1; };
return n: i32;
};

View File

@@ -0,0 +1,9 @@
//ww:error "tuple param element overflows integer arg regs (DI/SI/DX/CX/R8/R9); stitch out of scope, see #163"
package main;
fn pair(a: i64, b: i64) (i64, i64) = { return (a, b); };
fn f(a: i64, b: i64, c: i64, d: i64, e: i64, t: (i64, i64)) i64 = {
return a + b + c + d + e + t.0 + t.1;
};
export fn main() i32 = {
return (f(1, 2, 3, 4, 5, pair(6, 7)): i32);
};

View File

@@ -0,0 +1,4 @@
//ww:error "variant tag unresolved (untyped/literal tuple element"
package main;
fn f(x: u64) ((bool, u64) | void) = { return (true, x); };
export fn main() i32 = { return 0; };

View File

@@ -0,0 +1,12 @@
//ww:error "tuple-in-union variant tag unresolved"
package main;
fn mk() ((i64, i64) | nomem) = {
return (5, 6);
};
export fn main() i32 = {
match (mk()) {
case let t: (i64, i64) => { if (t.0 != 5) { return 1; }; };
case => return 2;
};
return 0;
};