Files
ww/test/wcc/data/attest_pass.ww
Hojun-Cho 4459a49d3a wcc/cgen: #87 plain tagged-union module-global DATA + match SB-resolution (both-stage)
A PLAIN (non-alias) module-level tagged-union global SEGV'd on BOTH
stages: no static DATA was emitted (let_emit_size/letemitsize returned 0
for TY_TAGGED) so the global was never registered, and the match
scrutinee resolved it as a frame-local at offset 0 — reading saved BP as
the tag. Two sub-sites, one route (neither half ships alone — DATA
without SB-resolution still SEGVs; SB-resolution without DATA reads
nothing):

(a) DATA-emitter — a non-nullable TY_TAGGED arm emits the box that
    byte-MIRRORS a runtime LOCAL of the same type: tag word at +0 (the
    const-selected variant index via cg_tag_for_variant / taggedvariant-
    index), payload at +8, zero-padded to the union box size. int and
    str/slice literal variants are wired (str carries a DATAR ptr patch
    at +8); any other variant payload loud-stops (rule 7). emit_tagged_
    data + emittaggeddata are the per-stage twins; let_pre_intern/
    letpreintern gain the matching str-variant intern. Nullable stays 0
    so the (*T|void) one-word fold keeps the 8B scalar arm.

(b) match-scrutinee global resolution — the PLAIN-tagged twin of #78:
    a global tagged ident scrutinee LEAQs name(SB) and copies the box
    into an @match_spill slot the dispatch indexes off BP.

DATA target (mirror of the local box, verified byte-for-byte): for
(i32|str)=42 the 32B box is tag0 | 42@8 | zero-pad; for ="x" it is
tag1 | ptr0@8(DATAR _S_n) | len@16 | cap@24. cs and ww emit byte-
identical asm.

Pins (rob §3, dual-stage 910 cstage + 997 wwstage, attest_pass.ww): the
tagged global-vs-local byte-identity pin (match over the GLOBAL gives the
same arm/value as over a LOCAL — was SEGV both stages) and the str-
variant tag-1 pin, plus the #86 tuple global-vs-local lock-pin guarding
the already-correct emitter path.

929 fail_global_src graduates: a >48B tagged GLOBAL by-value arg now
resolves through the cgplaceaddr MEMORY-class arm (LEAQ g(SB) + blit)
instead of the #38b loud-stop, and runs correctly (uninit zero box ->
first variant); the row becomes a positive run pin. The struct-variant
>48B init still loud-stops via the data emitter.

The first-class-VALUE copy of a tagged ident (`let q = g`) stays a
pre-existing silent #49/#46 sibling (local and global identically),
filed separately — out of this fold's two sub-sites.
2026-06-06 12:05:56 +09:00

179 lines
5.7 KiB
Plaintext

// @test fixture: every test passes (exits without aborting).
package data;
import rt; // #3/B': check_empty_alloc_annotated calls alloc → rt_malloc.
type point = struct { x: i32, y: i32 };
// #87: a PLAIN (non-alias) module-level tagged-union global SEGV'd BOTH
// stages — no static DATA emitted (read as a frame-local at BP) + the
// match scrutinee read saved BP as the tag. Fix: emit the 32B box that
// byte-MIRRORS a runtime LOCAL (tag@0 | payload@8 | zero-pad) + resolve
// the match scrutinee via LEAQ name(SB). These globals back the rob §3
// global-vs-local byte-identity PIN below; the tuple global locks the
// already-correct #86 path against any emitter regression.
let g87_int: (i32 | str) = 42;
let g87_str: (i32 | str) = "x";
let g86_tup: (u32, u32) = (1u32, 2u32);
@test fn check_add() void = {
let a: i32 = 2;
let b: i32 = 3;
let c: i32 = a + b;
if (c != 5) {
let _: i32 = 1 / 0; // abort via div-by-zero would also work
};
};
@test fn check_match() void = {
let r: (i32 | str) = 7;
let v: i32 = match (r) {
case let n: i32 => yield n;
case let s: str => yield 0;
};
if (v != 7) {
let _: i32 = 1 / 0;
};
};
// #227: compound assign (`-=`/`+=`) on a local field must
// load-combine-store, not drop the op. Pre-fix wwstage stored the bare
// rhs (p.x->4, p.y->5, view.len->1), so each mismatch aborts via 1/0.
@test fn check_local_field_compound() void = {
let p: point = point { x = 10i32, y = 3i32 };
p.x -= 4i32;
p.y += 5i32;
if (p.x != 6) {
let _: i32 = 1 / 0;
};
if (p.y != 8) {
let _: i32 = 1 / 0;
};
let view: str = "hello";
view.len -= 1;
let n: i32 = view.len: i32;
if (n != 4) {
let _: i32 = 1 / 0;
};
};
// #264: a match-bound binder used inside a `yield` arm of a match-AS-
// EXPRESSION. Pre-fix the wwstage checker stamped the operand in-scope
// during the N_MCASE arm walk, then RE-TYPED it out of the (popped) arm
// scope while deriving the match's yield type (exprtype N_MATCH →
// matchyieldtype). Out of scope the operand couldn't re-resolve, the
// re-derive returned nil, and the N_UN/N_BIN/N_INDEX restamp arms
// overwrote the good in-scope stamp with nil → asserttyped:un/bin/index
// aborted (cstage accepted it). Root fix: matchyieldtype now READS the
// cached operand tinfo at the post-walk call instead of re-deriving
// (mirrors cstage match_yield_type reading body->lhs->type, check.c:121),
// so no operand shape can be clobbered. These rows pin the whole operand
// class by construction: deref (*p), bin (*p+1), slice-index (p[i]), and
// deref-then-field ((*p).x). (The *[N]T ptr-to-array index variant is
// blocked separately by #278's exhaustiveness false-reject, so it uses a
// []i32 slice binder here.)
@test fn check_match_ptr_deref() void = {
let n: i32 = 42i32;
let xi: (*i32 | void) = &n;
let v: i32 = match (xi) {
case let p: *i32 => yield *p;
case void => yield -1i32;
};
if (v != 42) {
let _: i32 = 1 / 0;
};
// N_BIN operand: `yield *p + 1`.
let m: i32 = 9i32;
let xb: (*i32 | void) = &m;
let vb: i32 = match (xb) {
case let p: *i32 => yield *p + 1i32;
case void => yield -1i32;
};
if (vb != 10) {
let _: i32 = 1 / 0;
};
// N_INDEX operand: `yield p[1]` over a []i32 binder.
let arr: [3]i32 = [4i32, 5i32, 6i32];
let sl: []i32 = arr;
let xs: ([]i32 | void) = sl;
let vs: i32 = match (xs) {
case let p: []i32 => yield p[1];
case void => yield -1i32;
};
if (vs != 5) {
let _: i32 = 1 / 0;
};
let pt: point = point { x = 7i32, y = 9i32 };
let xp: (*point | void) = &pt;
let w: i32 = match (xp) {
case let p: *point => yield (*p).x;
case void => yield -1i32;
};
if (w != 7) {
let _: i32 = 1 / 0;
};
};
// #3/B': the let-annotation context path keeps `alloc([], n)` inferring
// its element type for ANY T (u8 default + non-u8 #45 retype). The bare /
// return / arg empty allocs that lost the silent u8 default are covered by
// the negative test (test/wcc/729). Here we pin that annotated allocs of
// both an 8-bit and a wide element still compile and index correctly.
// alloc([], n) is Hare's len=0 / cap=n empty slice, so we write into the
// cap-backed memory and read it back (ref/hare expects len 0, not n).
@test fn check_empty_alloc_annotated() void = {
let b: []u8 = alloc([], 8u64)!;
b[0] = 7u8;
b[3] = 9u8;
if (b[0] != 7 || b[3] != 9) {
let _: i32 = 1 / 0;
};
let w: []i32 = alloc([], 4u64)!;
w[2] = 5i32;
if (w[2] != 5) {
let _: i32 = 1 / 0;
};
};
// #87 PIN (rob §3, tagged kind): a module-GLOBAL tagged scrutinee must
// match to the SAME arm/value as a LOCAL of the same type — both stages.
// Pre-fix the global match SEGV'd (saved-BP-as-tag) on cs AND ww.
@test fn check_tagged_global_int() void = {
let l: (i32 | str) = 42; // the known-correct runtime box
let gv: i32 = match (g87_int) {
case let n: i32 => yield n;
case let s: str => yield 0;
};
let lv: i32 = match (l) {
case let n: i32 => yield n;
case let s: str => yield 0;
};
if (gv != 42) { let _: i32 = 1 / 0; };
if (gv != lv) { let _: i32 = 1 / 0; }; // global == local
};
// #87 PIN: the str variant (tag 1) exercises the DATAR ptr patch + the
// 24B {ptr,len,cap} payload at +8.
@test fn check_tagged_global_str() void = {
let n: i32 = match (g87_str) {
case let v: i32 => yield v;
case let s: str => yield s.len: i32;
};
if (n != 1) { let _: i32 = 1 / 0; }; // "x".len, via tag-1 arm
};
// #86 LOCK PIN (tuple kind): the already-correct tuple-global emitter
// must keep byte-mirroring a local — guards the working path against any
// regression from the #87 tagged arm sharing emitletdataw.
@test fn check_tuple_global_lock() void = {
let l: (u32, u32) = (1u32, 2u32);
if (g86_tup.0 != 1) { let _: i32 = 1 / 0; };
if (g86_tup.1 != 2) { let _: i32 = 1 / 0; };
if (g86_tup.0 != l.0) { let _: i32 = 1 / 0; };
if (g86_tup.1 != l.1) { let _: i32 = 1 / 0; };
};