5dd239d01e5b015dd42d8f2439e5254cfa3710b1
240 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 5dd239d01e |
wcc/cgen: #146 wwstage str ==/!= via rt_streq, not ptr-only CMPQ (the #154 ww-twin)
wwstage compiled str ==/!= as a single CMPQ on the eager-eval'd ptr word (len ignored), so two distinct-pointer equal-content strings compared unequal. cstage was already correct (CALLs rt_streq, the #154 cbinop fix). The wwstage cgbin had no str-awareness -- every comparison fell to the generic CMPQ tail; the #154 fix was never mirrored. wwstage-only: a cgstreqpush helper + a str ==/!= branch at the top of cgbin (before the generic eval collapses the header), byte-matching cstage cbinop:4564-4623 -- push rhs/lhs (len,ptr), POPQ DI/SI/DX/CX, CALL rt_streq, XORQ $1 for !=. Gated on typeisstr (= cstage node_isstr, which also catches module-global str idents). cstage cgen unchanged (w6c md5 unchanged). The str== .s is byte-identical cs==ww for local, global, aliased, chained, and condition operands. Graduates 3 lib byte-id pins (test/wcc/989_lib_byteid #59.1 asciitest, #59.11 toktest, #59.12 asttest) M_DIVERGE->M_ID -- they used == on str and were pinned divergent because of this bug; now byte-identical. byte-id 990-997 8/8. test/wcc/827 table-driven. |
|||
| fca979470f |
wcc/cgen: #52 error-first tagged-union success tag — successtag helper not hardcoded 0 (wwstage)
An error-first tagged union -- error variant at tag 0, success at tag 1+, e.g. (myerr | u16) -- was silently miscompiled by wwstage: the try/propagate codegen hardcoded success = tag 0, so the actual success value (tag 1) failed the CMPQ $0 and fell to the error path -> exit(1) instead of the value (44). cstage was correct (computes the success tag via cg_tagged_success_tag = first non-error variant). wwstage-only: a successtag/successvariant helper (mirroring cstage) replaces the hardcoded tag-0 / first-param assumption at all four try sites -- cgtryprop (?), cgtryunw (!), and the two latent shift sites cgtrytupleshift + cgtrytaggedshift (which bite an error-first union with an aggregate success payload). Success-first unions (the Hare idiom + what the selfhost uses) keep successtag=0 -> CMPQ $0 unchanged -> byte-id-neutral on 990-997. cstage untouched (w6c md5 unchanged). byte-id 990-997 8/8. test/wcc/825 table-driven (errfirst must/prop + tuple-success + success-first control). A separate nested-tagged-union construction divergence is filed (#10/#125). |
|||
| ef6fcbfc04 |
wcc/cgen: #135 inferred-float module-global — default untyped_float to f64 (wwstage)
let pi = 3.5; pi * 2.0 (an inferred-type float module-global) was silently miscompiled by wwstage: untyped_float wasn't defaulted, so letemitsize sized it 0 -> no DATAW emitted -> the pi load was dropped, X0 kept a stale spill -> 2.0*2.0 = 4 not 7. cstage became correct via #150-B's sym-repoint (stamps f64 -> MOVSD), so this aligns wwstage UP, byte-identical. wwstage-only: cgen.ww defaultinferredlets gains the untyped_float->f64 arm (mirrors the untyped_int->int arm; the codebase's own #135-deferred carve-out at cgen.ww:1079-1082, unblocked now that #150-B killed the rule-10 divergence it feared), and cgenexpr.ww cgident gets a letfloatprim fallback (the same primitive-TNAME SSoT letemitsize already uses, since a renamed primitive TNAME carries no tinfo stamp). cstage cgen unchanged (w6c md5 unchanged). int-inferred globals stay integer. byte-id 990-997 8/8. test/wcc/824 table-driven. The N_CAST-no-recurse parity (check.c:1276) is filed separately (#19). |
|||
| 1aaa0a3670 |
wcc/cgen: #8 def str-array element load — emit + pre-intern def-twin + ww load (both stages)
def C:[N]str; C[i] was loud (undefined main.C) both stages. Three folded fixes, one commit (splitting would ship a bisect point where wwstage silently returns an element address instead of .len): P0: the str-array static-init emitter dropped its vestigial directive =="DATAW" gate so a def table rides the same DATAW-header + DATAR-reloc path as let. A def str/slice table lives in DATAW by w6a's A_DATAR-holder constraint -- placement only; def immutability stays checker-enforced. P1: let_pre_intern / letpreintern walked N_LET only, so a def str-array's element string-literals were never interned (dangling _S_n). Extracted a pre_intern_strarray SSoT helper, called for a def str-array arm too, both stages. Scoped to str fixed arrays; def []T / def [N][]T stay loud (#270). P2: wwstage cgenexpr lacked a defvartnode fallback in the indexed-element classify, so a def str-array element load returned the element address instead of the slice header -- a silent miscompile. One line, aligning wwstage up to cstage (which was correct). C[1].len now = 3 both stages, byte-identical. byte-id 990-997 8/8; w6c/w6c_ww move. test/wcc/819 table-driven. The def-global scalar str index sibling (def S:str; S[0]) stays task #14. |
|||
| 267e81b89e |
wcc/cgen: GAP-A.ptr global-array base — LEAQ name(SB) not (BP) (#11, both stages)
A global fixed array's .ptr (= &A[0]) must take the SB base, but cstage emitted frame-relative LEAQ off(BP) for BOTH let- and def-global arrays -> *A.ptr read frame garbage (0 instead of the element). cstage-SILENT; wwstage def-global was a loud link-error. The .ptr read arm now gates off==0 && (let_islet || def_isarraydef) -> LEAQ name(SB), reusing the def-array index base predicate (cgen.c:4367, the #94/#231/#48 class). Locals (off != 0) stay BP-relative -- the 14 toolchain backing-ptr sites unaffected. wwstage let-global was already correct; this adds the missing def-global arm (cgenexpr.ww), converging cstage/wwstage byte-identical across all three flavors (local / let-global / def-global) and closing a latent cstage-only let-global cs!=ww divergence. Byte-id 990-997 8/8 (corpus has no global .ptr); w6c/w6c_ww binaries move (cgen changed). test/wcc/818 table-driven, build+run+byte-id per flavor. |
|||
| 7b0e09e065 |
wcc/cgen: GAP-A.len def-global array .len — def-twin cgdot arm (#7 lineage, wwstage align-up)
wwstage .len on a def-global array fell to the cgdot SB-fallback (w6l: undefined reference to 'len') — the #7 let-array arm gates on letvartnode (c.lets only), so def-globals (c.defs) missed it. Add a def .len-only arm in cgdot using the existing defvartnode (the def-side mirror of letvartnode), emitting the length immediate from the #11-stamped N_TARRAY length child. cstage cgen.c was already correct, so this is a wwstage-only source change: w6c unchanged, w6c_ww + wwdump regen'd (they embed the wcc cgen). .ptr (cstage itself buggy — emits LEAQ (BP), filed GAP-A.ptr) and .cap (wwstage silent garbage; arrays have no cap, filed GAP-A.cap) are NOT folded (rule-11, separate concerns). Pin: table-driven test/wcc/816_def_arr_len (def [3] + [_] inferred + 1-elem + u8 stride .len, both stages + byte-id), teeth-proven. |
|||
| 6e1d958d9b |
wcc/cgen: #145 slice-copy-assign LHS s.arr[lo:hi]=bs — N_SLICE-LHS arm, runtime byte-copy loop, esz via type table (both stages)
Probe-first find for the path c2 appendlit (buf.buf[lo..hi]=bs): a slice-copy-assign into a struct-field array sub-range emitted ZERO code — silent NO-OP, both stages, both-wrong-identical (#263), so runtime is the only net. N_ASSIGN gains an N_SLICE-LHS arm (cgen.c + cgenexpr.ww slicebaseesz twin) reusing the N_SLICE-read base/esz cascade and copying (hi-lo)*esz bytes from rhs.ptr via a runtime loop (len is runtime; no REP/MOVSB). esz routed through the type table (rule 13; [N]u8->1). Hare len(bs)==hi-lo assert deferred to #149. |
|||
| 2c09d13ca3 |
wcc/cgen: #59 append/insert struct-literal value eval-order — eval-to-scratch pre-grow + precise copy (both-stage)
append/insert of a struct-LITERAL value evaluated the literal's field
exprs AFTER the grow, so a field reading the destination (e.g. len(xs))
saw the grown length. Both stages, #263 gate-blind (cs==ww byte-identical,
both wrong — runtime is the only net). #50 fixed the scalar/boxing value
arm; the struct-lit arm still post-grew.
Fix (mirror #50, both stages): resolve the struct, fill the literal into a
fresh per-site scratch (@appendstructscr, sized esz, survives rt_ensure +
nested-append clobber) BEFORE the grow, then copy scratch -> post-grow slot.
The copy uses the precise descending 8/4/2/1 ladder (the proven N_IDENT
struct arm directly below), NOT a raw 8B-word block copy: a struct's size
rounds to maxalign (check.c:916), so a sub-8B struct packs at a 4/2/1B
slice stride and an 8B copy over-writes past the slot — at a power-of-2
capacity boundary that clobbers the adjacent allocation (heap corruption,
both stages). The ladder never reads past esz (no uninit high bytes) nor
writes past the slot; esz=8 stays a single MOVQ (byte-id preserved).
insert() rides by construction: both stages desugar it to append and
re-dispatch into this arm. The #49 aplace path already uses the precise
ladder (verified, not exposed). #59 closes the last composite-value
eval-order hole in append/insert.
Pin: 946_append_structlit_evalorder_run — append / insert / narrow-neighbor
(i32-field at the cap boundary with an adjacent-allocation survival assert)
rows, each base-fail at
|
|||
| 39432f717c |
wcc/cgen: #64+#68 tuple-literal cursor-fill decl-blind — massign + call-arg widen (both-stage)
A tuple LITERAL with a declared-tagged element reached the cursor-fill
helper (cg_tuple_lit_to_cursor) through the generic cgexpr(N_TUPLE) arm
with no declared type, so the element was stored stamped-keyed at its
constructed scalar width rather than widened into the declared tagged box.
Both consumers ran silent and wrong on both stages (#263 gate-blind:
cs==ww byte-identical, both wrong — runtime is the only net).
#64 massign: N_MASSIGN derives a declared tuple type from the lvalue
binding types and threads it into cg_tuple_lit_to_cursor + the receive
loop (mirror of the #57 N_LET wire); a `_` target falls back to the rhs
literal element type for cursor stride.
#68 call-arg: the send is made param-aware (fill over the PARAM tuple) and
the restage guard graduates a declared-tagged element to a real widen
(reusing cg_widen_tagged_store); nested tuple/struct/array elements and
tagged elements with no param decl stay rule-7 loud. The matching
pop/drain is made param-aware too so push count == pop count: a
param-aware send pushes the box's N words, so the drain must pop N or the
SysV arg sequence skews. This is a push/pop balance requirement of the
send change, not a separate latent under-drain (the standalone trailing-
arg drain is already correct at HEAD).
Closed by construction: the only remaining cg_tuple_lit_to_cursor caller
passing NULL/nil is the generic cgexpr(N_TUPLE) arm, provably non-widening
(constructed type == governing type). The four widening consumers — LET,
RETURN, MASSIGN, call-arg — are all decl-wired. Whole-tuple single-ident
reassign from a tuple literal is rule-7 loud (task #49), not a silent
widening consumer, so the residual NULL arm stays non-widening.
Pin: 945_tuple_lit_declblind_run — massign / call-arg / `_`-control /
call-arg-drain / nested-tuple-ERR rows, each base-fail at
|
|||
| f3750ae3ce |
lib/io: empty() stream; wcc/cgen: #129 sretretsize + #130 global tagged-field store
io.empty (discard+EOF stream, ref/hare/io/empty.ha:4-17) — needed by getopt's two-pass printusage width measurement. Diverges from Hare's `const empty: *stream`: a `let _empty_vt` + `fn empty()` that wires the fn-ptr slots per call, because const-init of a vtable struct with fn-ptr fields is blocked (#118, ruled accept). Co-discovered while making empty() byte-identical across stages: three wwstage-only cgen fixes (cstage was already correct; wwstage aligned down): - #129 sretretsize: consult the same-module pointer-alias before structlookup's any-module struct fallback (io.stream = *vtable was mis-sized as memio's 56B struct -> spurious sret save). - #129 callsretsize: swap curmod to the callee's module before sret-size classification (cross-module callee context). - #130 cgassign global-struct tagged-union field store: add the missing arm (was a 1-word store) mirroring cstage cgen.c:4893-4912. The three are inseparable from io.empty here — splitting them out leaves a divergent-asm intermediate (993/995 red), so they ride one commit per the one-class gate-repair carve-out (#133-expanded precedent). Regenerates the embedded combined.ww; 989_lib_byteid pins bufio + fmt graduated to M_ID. (cgenexpr.ww fix-3 inline comment cites the #129 cluster; narrow to #130 on next touch to avoid a regen for a comment.) |
|||
| 754944a755 |
wcc/cgen: #121 indexed tuple-element read + literal-store round-trip (both-stage)
Reading or storing a tuple element of an indexed array element was
broken across the board (the fold-6 read-path). One fused commit,
both stages, four faces of indexed tuple-element access:
- FIELD read `tbl[i].N`: was loud ("unsupported field-read shape" --
the field-read dispatch keyed on an N_IDENT base; an INDEX base fell
to a fatal). Now resolves &tbl[i] via the place-spine and reads the
field at addr+foff through the existing per-kind arms (str-triple /
scalar / fn-ptr).
- WHOLE read `let e = tbl[i]`: was a silent word0-only truncation
(plain-tuple kin of #37/#58, which covered only tagged). Now a full
cursor fill from &tbl[i].
- STORE `a[i] = (3,4)` (N_TUPLE-literal rhs): was a silent word0-only
store -- the write face of the read. The aggregate-store-into-index
site handled ident/dot/deref tuple rhs but not the literal; now it
materializes the literal and word-copies. Narrow: N_IDENT base only
(N_DOT/chained stay deferred, #270).
- for-range over a const-slice-of-tuple: was a divergent SEGV; now a
symmetric loud-stop on both stages (filed #122).
The store and read were a round-trip that passed test 809 only by luck
(broken store XOR broken read canceled). Fixing the read alone exposed
the silent store; rule-7 obliges fixing both, so 809 is now genuinely
correct, not luck-correct. Both faces are byte-id-blind (#263) -- the
net is a runtime round-trip pin with distinct-per-word values and a
real call clobbering the cursor registers between store and read, so a
word0-only store or read is caught. Both stages byte-identical
(990-997 green). Pin 947_tuple_index_read_run.
|
|||
| 351abb0ab3 |
wcc/cgen: #58 indexed tagged-field read+assign cursor arm (both-stage)
Reading or writing a tagged field of an indexed array element (xs[i].field) was broken on BOTH stages, byte-identically and silently (#263 gate-blind): the arr[i].field branches had arms for array/str/slice/float but no TY_TAGGED arm, so the tagged field fell to the single-word scalar path. READ loaded only the tag word (stale payload -> `xs[i].min as T` read garbage); ASSIGN stored the raw unboxed scalar into the tag slot, corrupting the box. Insert a TY_TAGGED cursor arm before each scalar fallback, both sites both stages (cgen.c read + assign; cgenexpr.ww cgdot N_INDEX-lhs read + cgassign indexed-field). READ mirrors cg_tagged_memread (payload -> DX/CX/R8, tag -> AX last). ASSIGN synthesizes the tag for the concrete variant (taggedvariantindext) and stores tag+payload via the str/slice 3-word store spine -- not the source-remap widener (concrete rhs has no source tag to remap). >32B / multi-word / float payloads are loud-stopped at all four arms (emission not yet wired; see #114). That shape is reachable today via a narrow-variant ctor, so it louds rather than silently miscompiling. Both stages get the same arm -> byte-id preserved (990-997 green; the runtime is the net for this #263 class). Pin 944_idx_tagged_field_run (read/assign runtime rows + >32B expect-loud rows). |
|||
| 5d596206c6 |
wcc/cgen: #94 def-array indexed &-base leg (both-stage)
`&D[i]` over a module-level DEF array SEGV'd on BOTH stages: the TK_AMP N_INDEX N_IDENT base classify checked only the local and let legs, so a def-array base fell to a wrong else — cstage zero-based the addend (XORQ BX,BX -> wild pointer, cgen.c) while wwstage value-loaded the symbol (MOVQ name(SB) = D[0], not its address, cgenexpr.ww complex-base fallback). Divergent asm, both wild. Add one def-array leg per stage, mirroring the working let leg: - cs: `def_isarraydef(base) -> LEAQ name(SB),BX` alongside let_islet. - ww: the `defvartnode` fallback the read-side cgindex already takes (cgenexpr.ww:1762) -> N_TARRAY classifies isglobalarr -> LEAQ name(SB). The def DATA symbol already exists (plain &D + D[i]-read work), so once the base is the address the existing i*esz scale + ADDQ round-trips. cs and ww now emit BYTE-IDENTICAL LEAQ-SB asm — the both-broken -> both-correct convergence is the point (#263 class). Rows (944_def_amp_idx_run, all 0/0 byte-id): amp_int [3]int, amp_u32 [3]u32 esz=4 (narrow scale), amp_arg &D[2] as a func-arg; controls ctrl_plain (&D), ctrl_read (D[i]), ctrl_2d (&M[1][1]) keep working. *p spelled `let v: T = *p` — `*p: T` parses as `*(p: T)`. OUT (filed #112): &D[..] slicing a def-array is a distinct parse reject needing a Hare-fidelity ruling — not this leg. |
|||
| 26ba1ad1b5 |
wcc_ww/cgen+peellint: #109 close #101 primsize-alias family by construction
Route the 16 routable bare-primsize GUARD sites (is-primitive / struct-vs-prim dispatch) through the #101 aliasprimsize SSoT helper. Byte-NEUTRAL by construction: an alias-narrow name is already neutralized downstream by the same arm, so routing emits no new asm (the empty-flip-set ken oracled). Shape-A exclude-prim-early (3): cgenutil sretretsize / structparamsize / structfloatclass — `primsize>0 return` then structlookup→nil returns the same value; route returns it early, same. Shape-B prim-guard-then-structlookup (13): cgenutil 4604/4650 + cgenexpr 4136/10244 + the 9-site CALL/assign cluster — primsize==0 →structlookup→nil→fall to normal; route skips the block→same normal. Install the peellint bare-primsize FINALE (B7 lint-fuse contract): tools/peellint now rejects any bare primsize() in the ww stage outside the annotated whitelist. Evasion-hardened per the B7 lesson — a character scan (comments + string/char literals stripped first) and a LEFT+RIGHT word-bounded match of the bare `primsize` TOKEN (not just `primsize(`), so the aliasprimsize() wrapper is never a hit and every compiling spelling reds: the call primsize(nm), the paren-wrap (primsize)(nm), the function-value bind `let p = primsize`, and any line-split. ww-only (the C stage dealiases via type_chase_named, no primsize symbol). Two independent exemption windows (peel-ok vs primsize-ok) so neither rule blinds the other. Runs as a make-test dep. Whitelist the 6 designed exemptions with primsize-ok WHY-annotations: machinery — aliasprimsize body (SSoT chase) | typenodeprimresolved + exprprimresolved (#11/#33 prim-resolver chasers) | cgcast leaf-loop + cgenexpr #11 deref-store (own ps==0 fallback; route would regress #11) | the primsize oracle/definition itself (nothing below to chase). structural — elemsizeof x2 + paramfieldsize (chase lives in the -c twin elemsizeofc; threading c is the dormant #110). Empty-flip-set proof: zero C bytes; cstage binaries bit-identical; bootstrap byte-id 990-997 + 950 all green (w6c == w6c_ww on the full selfhost, self-rebuild identical); combined.ww (w6c + wwdump) regen idempotent; sizelint 0; peellint 0 (raw-peel AND bare-primsize over the whole tree = the close-by-construction proof, zero unwhitelisted survivors). Tests: 944_peellint_gate +14 rows (bare / space-before-paren / name-at-EOL split / string-blind opener / paren-wrap / fn-value-bind RED; aliasprimsize wrapper + primsize-ok annotated GREEN; corrupt annotation RED; independent peel/primsize windows; C-file out-of-scope). Closes the #101 primsize-alias family by construction. #109. |
|||
| 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.
|
|||
| 45f5415209 |
wcc_ww/cgen: #101 narrow-alias fill-stride via aliasprimsize SSoT
A struct-literal array fill sized a narrow-alias element off a bare
primsize(name): `type my32 = u32` gave primsize("my32")=0, so the
element width defaulted to 8 and a [3]my32 strode MOVQ -24/-16/-8 —
field n collided with arr[2] (kw1_101 run exit 1). cstage chases
my32->u32->4 (MOVL stride-4) at the twin sites and is runtime-correct;
this is a ww-only align-up, cs untouched.
Fix: a new aliasprimsize(c, nm) SSoT helper — primsize(nm), else an
aliaslookup-chase N_TNAME loop then primsize — and route the SIZE-use
primsize() family through it. The 7 c-bearing bare-no-chase size-use
sites are routed: cgen:993 (letemitsize), cgenstmt:1896 (cgarrlitfillbp),
cgenutil:1579 (elemsizeofc fallback)/1657+1665 (nodeprimwidth)/4791
(cgstructlitfill = the kw1_101 site), cgenexpr:6902 (cgcall vararg esz).
This is the rule-13 close-by-construction shape (one accessor for
"resolved primitive size"), not a per-site patch.
kw1_101 is the SOLE asm mover (byte-id NO->YES, run 1->0, MOVL
stride-4); every other routed site is latent/byte-neutral. Bootstrap:
all 5 combined units stay w6c==w6c_ww byte-identical. sizelint 0,
peellint 0, test-unit 296/296.
Scope fence (rob route-7-decline-6 ruling): three DESIGNED-exemption
sites carry inline primsize-ok annotations — elemsizeof :1475/:1499 and
paramfieldsize :3541 are structural (no-`c`, non-chasing) BY DESIGN;
their alias-chasing twin elemsizeofc is the routed :1579 leg. These are
the #109 peellint-whitelist seeds. Three further declines are already
correct chasing paths, not bare-no-chase bug shapes (typenodeprimresolved
:2026 / exprprimresolved :2063 are the chase machinery itself; cgassign
:7631 already chases via typenodeprimresolved, #11). The ~17 GUARD sites
(is-primitive dispatch) + the peellint finale are the committed #109
follow-on. Threading `c` into the structural sizers is dormant #110.
#101
|
|||
| fc50a27f3e |
cgen: #95 c3 reviewer-fold — is/as gate exact-only, no widening leak
c1/c2 widened flatvariantidxt (selfhost) with the chain + structural tag-synthesis arms and a >=2 ambiguity os.exit, scoped to the cgen WIDEN consumer. But flatvariantidxt is a choke-point: the wwstage is/as ACCEPTANCE gate (check.ww:4677, the #198 spread fallback) reuses it, so the widening leaked into checker acceptance — vs base |
|||
| 05f7af76d1 |
wcc_ww/cgen: W2 #102 indexed-elem field store + &-field gates chase the alias chain
The two ww-side gates from ken's B6-c3 STOP re-attribution
(.ai/ken-b6-oracle.md addendum): WWSTAGE was the alias-blind side at
exactly two cgenexpr.ww sites, both keyed on a bare name-keyed
structlookup that only holds struct-decl names, so an alias-NAMED
spelling missed and fell to the generic-but-runtime-correct route —
byte-divergent from the canonical dedicated shape cs pins post-B6-c3:
(1) `arr[i].f = v` indexed-elem struct-field STORE arm (the write
twin of the task-#8 READ arm, which already chases via tichase);
(2) `&p.f` ptr-field fallback in the TK_AMP N_DOT single-dot leg.
REVIEW AMENDMENT (reviewer-W2, rob's close-by-construction
obligation): the proof-sweep of the two arms found ONE same-class
survivor — (3) the value-struct `&x.f` leg, one leg below (2) in the
same single-DOT arm, same alias-blind bare structlookup. Probe
rW2_ampv reproduced the identical bit-proven mechanism (ww_alias !=
ww_plain; cs_alias == ww_plain) at both base and the frozen tip; the
other in-arm routes are clean (letvar* global helpers chase, str/len
pseudo-field alias legs are filed-loud #96, chained depth>=2 resolves
via tinfo). Same chase applied; arm survivor count is now zero by
the same grep+probe construction.
All three now resolve through structlookupchain (#22) — the name
layer's own alias-chain accessor (the cstage transitive-peel mirror),
already consumed by the sibling `p.f = v` assign arm and cgdot's
pointer-to-struct read. Plain rows short-circuit at its structlookup
head, so non-alias emission is byte-identical by construction; alias
rows land on the same structinfo as their plain twins, so the
dedicated emission converges bit-exactly. esz stays sound:
elemsizeofc reads the chased stamped tinfo (#8 leg).
Convergence proven with ken's pre-staged W2 instrument: ww_alias ==
ww_plain bit-IDENTICAL for kb6_idxf and kb6_ampf (cmp exit 0), which
also lands cs==ww byte-id on both rows; plain controls byte-id held
and their ww asm unchanged base->tip. Full ken-corpus matrix vs the
B6-tip baseline: movers are exactly {kb6_idxf, kb6_ampf} NO->YES;
detectors unmoved (kw1_101 cs0/ww1, fill2 both-loud, tuparg_c
cs0/ww1, fsarg2/fsarg0/try pinned texts verbatim, B5/B6 graduations
all byte-id). Zero behavioral change on any accepted program, zero
acceptance change in either direction.
ww-only: zero C-source bytes; cs binaries (w6c/ww/w6a/w6l) md5
bit-identical base->tip; w6c_ww + wwdump main.combined.ww regen'd via
make, idempotent. 989 lib ratchet: zero flips (31 byte-identical /
9 pinned-divergent #59 / 3 pinned-wwreject #59 across 43 units).
944_alias_cgen_b6_run: idxf_2lvl + ampf_2lvl graduate K_RUN_NOID ->
K_RUN (the two restored byte-id cells) — B6-c3's mutation teeth
restored (reviewer-B6 N-a closes); + amendment rows ampv_2lvl /
ampv_plain_ctl pin the third gate (checks 61 -> 69). All 9 944-family
suites green; sizelint 0; make test-unit "all 292 tests passed".
|
|||
| d5cb1bd69e |
wcc_ww/cgen: #82 cgun &base[i] classify off the chased stamped base type
The TK_AMP N_INDEX arm keyed arrayness off the SYNTACTIC tnode (local leg isarr at the baselocal read; global leg isglobalarr/isglobalptr at the letvartnode read) — an alias-typed base (tnode N_TNAME) missed the N_TARRAY gate, so the base materialized as MOVQ (element-0 VALUE) instead of LEAQ (storage address): wild pointer, SEGV/corruption on the deref. SILENT class (metric-1). The global leg graduated from latent to live when g-fold #77/#78 landed alias-global DATA emit. Fix re-keys both legs off tichase(base.type_) gated on TY_NAMED — the landed cgindex #60 idiom (cgenexpr.ww:1800-1820). cstage already classifies off the chased type (type_chase_named, cmd/w6c/cgen.c: 4172-4188) and is the runtime-correct reference: align ww UP. esz does NOT move — elemsizeofc chases internally since batch-2 (PREMISE-2 probe-confirmed via amp_narrow: stride right, base wrong pre-fix). Non-alias rows byte-id-neutral by construction (TY_NAMED gate). Pin: 944_alias_amp_idx_run, 8 rows through the taken pointer (plain local/global+str controls hold 0/0; 1/2-level alias local + global, fwd-ref decl order, narrow [4]u32 graduate cs0/wwSEGV-byte-id-NO -> 0/0 byte-id). Probed OUT, filed not fixed (spec §1 NOTE-2): &D[i] def-array base breaks at a DIFFERENT site both stages (cs XORQ BX,BX zero-base cgen.c:4209-4212, ww complex-base fallback; both SEGV 139). Light gates: test-unit 289 green; sizelint 0; 989 ratchet zero flips (31 ID / 9 DIVERGE / 3 WWREJECT pins hold); five-mains NEUTRAL vs master-74195ac scratch build on identical inputs + cs==ww on all five. combined.ww regens ride along (#110). |
|||
| ef93b1637e |
wcc_ww/cgen: collapse cgenexpr.ww's plain TY_NAMED chase loops into tichase — byte-id neutral
Mechanical sweep, F2a batch 1 commit 2 (alias arc #5, rob F2 ruling: one chased accessor is the only spelled way to dealias; this enumeration seeds the future peellint whitelist). Every loop matching the exact plain shape for (X != nil && X.kind == tykind.TY_NAMED) { X = X.under; }; (modulo variable name and one-line vs three-line layout, body exactly the peel, guard exactly nil+NAMED) becomes `X = tichase(X);`. Census of cgenexpr.ww at commit 1: 71 `.under` lines — 49 one-line peels + 21 multi-line-spelled peels, all eyes-classified as the plain shape, all 70 converted (count-asserted). Survivor enumeration (raw `.under` occurrences left in cgenexpr.ww): - cgenexpr.ww:2886 — comment text only ("follows the resolved NAMED.under pointer"), no code read. Classification: not a read. Zero code-level raw `.under` reads remain in this file. Byte-id evidence: pre-sweep w6c_ww (commit-1 build) vs post-sweep w6c_ww compared over a 31-input corpus — all selfhost/cmd/*/ main.combined.ww (the compiler's own full source) plus the #60/#79 probe set — identical exit codes and byte-identical .s for every input. make test-unit green (287). combined.ww regens ride along (cgenexpr.ww is an embedded source). |
|||
| da81a4c86e |
wcc_ww/cgen: #60+#79 alias-NAMED array/slice ELEMENT paths read the chased tinfo — tichase lands, SEGV families graduate byte-id
One class: alias-blind base+esz at the array/slice ELEMENT paths — index read/write, slice-expr, for-range, and literal-init store. The wwstage cgen derived element size and base addressing from the type-AST tnode; an alias-typed base (`type arr = [4]int; let a: arr`) shows only the N_TNAME leaf, so esz fell to a sentinel (1 on the read side, 8 on the init-store side) and the base classified as a POINTER (MOVQ of array words, no IMULQ): m8b_idx1/range1 SEGV 139, m8b_slice1 silent-wrong past little-endian prefix-luck (m8c_slice1big exit 2), m7c global [2]row read SEGV via the alias-blind element-is-array classify, and (#79, ken F2a1 oracle) `type A=[4]u32; let a:A=[...]` stored MOVQ stride-8 over a stride-4 slot — elements 2/3 landed at 0(BP)/+8(BP), a saved-BP/RIP smash masked whenever esz==8. cstage reads everything off the chased stamped type (type_chase_named/ idx_eff, correct post-F1), so every fixed shape graduates ww-SEGV/silent-wrong -> 0/0 byte-id. New tichase() in cgenutil.ww: nil-passthrough transitive TY_NAMED peel, exact twin of cmd/wcc/type.c:160-162. Routed sites, all gated on the stamped type being TY_NAMED (non-alias paths byte-identical): - cgindex (cgenexpr.ww): elem facts (esz/signed/float/f32) off tichase(n.type_); etn falls back to n for the tagged/str/slice classify; LEAQ-vs-MOVQ base off the chased kind; elem-is-array supplemented by tinfoisarray(n.type_) for alias ELEMENTS (m7c). - cgassign N_INDEX store + compound arms (cgenexpr.ww): esz + elemtn=lhs (the stamped-element idiom of the N_DOT/N_INDEX arms); chased-kind base classify at all four LEAQ/MOVQ sites. - cgslice + cgbasecap (cgenexpr.ww): esz, base classify, default-hi (TY_ARRAY -> $alen / TY_SLICE|TY_STR -> +8 len), cap word at +16; global-str cap keeps the #73 carve-out. - cgforrange (cgenstmt.ww, cross-file leg: the range pin cannot green without it): esz, element-node synthesis off .sub (FC0 precedent), isarr/isslicestr classify, alen off the chased tinfo. - cgarrlitfillbp (cgenstmt.ww, #79): an alias [count]T arrtn is the N_TNAME leaf (elemn nil) — synthesise the element node off the chased sub so the existing prim/agg/slice/tagged/narrow dispatch works unchanged; `...` repeat bound off the chased alen (cstage cg_arrlit_fill_bp receives the pre-chased bu and reads bu->alen). #8-PAIR COVERAGE: this is the STORE half of #8's two size-sources. The elemsizeofc READ half chases the ELEMENT internally (idxeffti + esub peel, the #8 fix) but NOT an alias-typed INDEXABLE node — that leg is covered at its #60-family call sites by the gates above (cgindex/cgslice/store/compound/cgforrange/pusharg). Remaining alias-blind elemsizeofc callers are enumerated as residuals below. - bare-let classify (cgenstmt.ww, #79 rider): `let a: arrk;` with an alias-to-array type took the composite zero-fill cstage doesn't emit (cstage keys the no-init shape on the chased lu->kind: arrays keep the per-index-write contract; an 8B alias-array still falls to the single MOVQ $0 arm). Required for the loopfill_1024 pin's byte-id; closes the array kind of the uninit-alias divergence. - pusharg N_SLICE (cgenutil.ww, pulled in by the same pin rule: the 944 slice_of_alias_arg row is a distinct lowering from cgslice): esz, base classify, default-hi. Tests: new 944_alias_idx_family_run (19 rows: idx/slice/range/init controls + 1-level + 2-level + decl-order permutations + index store + compound (+=, *=) + #79 [4]u32 literal-init + alias `[v...]` repeat + uninit [1024] loop-fill + slice1big (1000 elems, values >255, LAST-element readback, default-hi, .cap, range count) + re-slice of an alias slice + range over an alias slice + m7c global 2D + GLOBAL alias-slice indexed read + slice-as-call-arg; dual-stage run + per-row byte-id; LAST elements asserted throughout). The six 944_alias_accept_run rows citing "#60 (F2 batch 1)" flip K_RUN_CS -> K_RUN (incl. slicefield_wholeread_2lvl: its 738d7f4-era receive-spine divergence no longer reproduces at the F1-merged base, verified byte-id + 0/0). 989_lib_byteid checked: no DIVERGE entry graduates (the test fails loudly on graduation; lib has no alias-base consumers — the shape SEGVed before this fix). NOT pinned (g-fold territory, #77/#78): direct alias-typed global ARRAY rows. Expected state probe-verified UNCHANGED by this diff: `let g: arr = [...]` -> ww link-ERR (no DATA emitted), cs 1-level runs 0, cs 2-level runs WRONG (silent). The alias-GLOBAL base legs added here (isglobalarr reclassify, global default-hi/cap) are cs-aligned but runtime-unreachable until the DATA emit lands. Residuals filed with the team: alias-blind elemsizeofc callers not in the #60 pin family — cgun &a[i] addr-of (cgenexpr.ww:4638 region, task #82), append() on an alias-typed slice local (:5287), `alloc([], n)` into an alias-slice let (cgenstmt.ww:2159), arr[i].field= float store (:8536); tagged-element READ under an alias base keeps the ident-arm nullable semantics; checker asserttyped on `untyped_lit * rangevar` over an alias slice (pre-existing, check.ww is batch 4, task #80); uninit alias-to-STRUCT zero-fill unchanged (correct: cstage fills composites); range-destructure over alias-to-tuple-slice. selfhost/cmd/{w6c,wwdump}/main.combined.ww regenerated (cgen*.ww are embedded sources). |
|||
| d14a23b85e |
cgen: #57 in-cap tuple cursor fill keys on the DECLARED element type — tagged elems from concrete rvalues widen, both stages
The N_TUPLE literal's stamped type is CONSTRUCTED from its elements
(check.c N_TUPLE keeps untyped/concrete element types; assignability
is consumer-side), so the in-cap cursor fill — count
(tuple_lit_gpwords/tuplitgpwords) + push (tuple_lit_push_elem/
tuplitpushelem) — never saw the DECLARED tuple type. A declared-TAGGED
element whose expr is a concrete rvalue (`return (5: size, 9)` into
(un16, size)) counted ONE word and skipped the widen entirely: 2 words
sent against the receiver's declared 3-word walk, every later element
read garbage. Both stages, byte-identical, gate-blind (ken /tmp/ken57
p8/p9: t.1 read entry-junk). The let-literal twin
(`let t: (un16, size) = (5: size, 9)`) and the tagged-SECOND-elem
shift broke identically (probes q1/q2). The over-cap (sret) arm
already walks declared params (#240/#22b) — only the in-cap path was
declared-blind.
Fix threads the declared tuple type into the ONE shared helper pair
and its two loop sites:
- tuple_lit_gpwords/tuplitpushelem take the declared elem type;
declared-TAGGED + concrete rvalue widens into the shared tagged
scratch (cg_tagscr_slot/tagscradd + cg_widen_tagged_store/
cgwidentaggedstore, the cgreturn tagged-@retscr shape) and pushes
the box words; declared-TAGGED gates the SSE row off (a (void|f64)
box rides INTEGER eightbytes). Tagged->tagged subset (eslot
mismatch) louds — the #23/#40 widening-remap family.
- cg_tuple_lit_to_cursor/cgtuplelittocursor grow a decl param;
cgreturn's in-cap N_TUPLE loops thread cg_ret_type/c.fnret.list
(the same pp/pt walk its over-cap arm does); the N_LET in-cap
tuple arm passes the declared type for an N_TUPLE rhs; the bare
cgexpr route passes NULL/nil (emission unchanged).
Ident-elem sources keep the existing slot-load push byte-identically
(t57_ident_no_regress); the CALL-elem tripwire stays loud (#41,
t57_loud_call_elem). RESIDUAL FILED, not folded (rule 11): the
N_MASSIGN destructure-reassign literal rhs routes through the bare
cgexpr path (decl=NULL) and stays silent-wrong — probe q5_massign,
task #64, cited at the massign arm both stages. The annotated
multi-let spelling (`let (a, b): (un, size) = lit`) does not parse
(both stages), so N_MLET has no declared-literal route.
941 rows t57_*: return (named + inline union), let-literal, tagged
second elem, float payload, bare-untyped payload (rides the #33
chooser through the new wire), ident anchor, loud CALL tripwire;
ken's adversarial shapes (tagged-MID elem, two tagged rvalue elems
incl. void, plain-f64 SSE coexisting with a declared-tagged box), the
in-cap/over-cap boundary loud (k57d), and the NEW #57 tag-remap loud
pinned. Pre-fix at
|
|||
| 80e7096f25 |
cgen: #50 append/insert tagged-dst VALUE boxes PRE-grow, both stages
The #34 value-store dispatch's tagged arm ran the widen choke-point (cg_widen_tagged_store / cgwidentaggedstore — which cgexprs the value internally) AFTER cg_append_grow, so a value expression reading the destination (xs.len) saw post-grow state. Hare evaluates arguments before the call's effects; the scalar/str/slice arms already ordered value-first. insert() inherited the bug through its desugar-to-append ( |
|||
| e8977a413d |
cgen: #38a chained-dot TAGGED leaf — cursor read + widen store, value-chain + ptr-chain, both stages
A tagged leaf at the end of a dot chain (depth >= 2) fell to the
one-word scalar tail in FOUR walkers — the value-chain read spine,
the value-chain assign walker, the chained-*struct read branch, and
the chained-*struct assign branch — in BOTH stages, byte-identical,
gate-blind:
READ: one MOVQ pulled the TAG word only; is-tests passed by
tag-luck while as/match/let consumers read stale DX/CX/R8
as payload (ken x5c: o.r.min as size added entry-zero DX —
want 28 got 20; ken b8 same through a mid-chain *rep).
ASSIGN: one sized store landed the rhs ON the tag slot, payload
kept its old bytes (ken x5d: `o.r.min = 8: size` made the
following is-test false).
The single-dot arms were already correct — each fix is that arm
verbatim at the chained site:
read: the tagged cursor (AX=tag, DX=val0, R8=val2 before
CX=val1 — the base may live in CX/AX; >32B box leaves its
ADDRESS in AX, the #37 mem-read convention). wwstage rides
the existing cgloadtaggedfield helper; the *struct-base
branches stage AX into BX first (cursor targets AX).
assign: full slot rewrite via the shared widener
(cg_widen_tagged_store / cgwidentaggedstore, base BX/BP),
plain `=` only — compound on tagged stays loud (#133).
The nested-LITERAL half of the old #38 filing dissolved: the
canonical fill's tagged-field widen delegation (#23,
|
|||
| 322667b820 |
cgen: #35/#46 Family C — tagged transport from deref/cast/unwrap sources goes mem-based, both stages
A tagged value reaching a transport consumer from a DEREF, CAST, or UNWRAP source materialized as ONE scalar word — the N_UN(STAR) arm's localloadop pulled word0 (the tag) and every cursor consumer then transported garbage payload (cs pushed stale DX, ww stored 0/garbage; divergent asm AND silent-wrong on both stages — ken f35 asm proof + ken37v D3a/D3b). Consumers × sources × sizes all wrong: arg push, let-init, assign, return, match scrutinee, as, widen — from *p at 16/24/32B, from identity/widening tagged casts, and from `?`/`!` whose success variant is itself tagged (nested box: payload words dropped). `is` and ww-match-16B passed only by stale-register luck. Fix extends the #37 mem-based machinery ( |
|||
| 4c46d3afde |
cgen: #49 aggregate-ASSIGN word0-only family — one mem-to-mem funnel (cg_aggcopy), both stages
Whole-aggregate reassignment `b = a` fell to the N_ASSIGN scalar tail
and copied ONE MOVQ — word 0 of any struct/array/tuple — in BOTH
stages, byte-identical, gate-blind (ken f49_min asm proof; latent
because lib style is let-init, whose #265/#268 copy is full-width).
Same class at three more positions: struct-lit FIELD init from an
ident source (`outer{.., r = r}`, the #38 non-tagged half), the deref
place `*p = s` (#31-A), and the module-let global `g = a` / `g = pt{..}`.
Fix: extract the C1.25 assign-resolver word-copy tail verbatim into
cg_aggcopy/aggcopy — the ONE place-resolved (SI)->(BX) aggregate copy
— and wire it at the N_ASSIGN ident-aggregate arm (local + global),
the deref-place divert into the existing resolver aggregate arm, and
the structlit-fill aggregate-field arm, all fed by aggarg_srcaddr
(the closed #265/#268 dispatch). The new arms key on the FULL alias
chase (type_chase_named / chased stamped tinfo, the #22 precedent) in
BOTH stages — the region's single-peel `lu`/`fu` would miss
`type b = a; type a = struct` on cstage while the wwstage twin fired
(ken R1, gA3b: master cs ran the word0 corruption, exit 2; now 0).
Non-addressable aggregate rhs (tuple-lit, unhandled call shapes) dies
LOUD (rule 7) instead of silently truncating: #31-E `*p = (3,4)` and
#31-G's deref flavor `*p = mk()` are now loud both stages (the INDEX
flavor `a[i] = mk()` stays in the legacy INDEX arm — receive
machinery, not this funnel; still filed under #31). #31-B rides: the
cstage-only <=24B gate before cg_structlit_fill_bp is lifted (the
wwstage twin never gated — a >24B literal reassign was
cs-zero/ww-filled, rule-10 break). Global structlit reassign rides
the existing DST_GLOBAL fill machinery.
Unsplit (rule 11): the assign arm, fill arm and deref divert all
route through the one new funnel (cg_aggcopy + aggarg_srcaddr) in
both stages; splitting by site or by stage would ship a transient
cs!=ww (gate-red) or a funnel with no consumer.
941 t2_reject_chain_arg: the row's tuple-LITERAL field fill now louds
at the #49 fill arm before reaching the pinned ARG-site reject; the
fill switches to an ident source (newly working via the fill arm) so
the original arg-site pin still fires.
test/wcc/812_agg_assign_width.c: 17 runtime-readback rows (the only
oracle for a gate-blind class) + per-row asm byte-id; every row fails
at
|
|||
| 26d375410e |
w6c+w6c_ww: >32B tagged INDEX/DOT reads go mem-based — box address in AX (#37)
A tagged box wider than the AX/DX/CX/R8 cursor (size > TUPLE_GPCAP*8)
read via N_INDEX or N_DOT now leaves its ADDRESS in AX — joining the
sret-call mem-based class the #38b gates already speak — and every
cursor consumer branches on one shared predicate (cg_tagged_memread /
taggedmemread) before reading AX as the tag. <=32B keeps the cursor
byte-for-byte (32B-at-cap asm proven identical to base on both the
t.N and INDEX routes).
Emitters: N_INDEX ident+fallback arms, N_DOT tuple-element (flips the
#22b loud bound), N_DOT struct-field + ptr-chained-field (were silent
clamps at 32B); wwstage twins gate cgloadtaggedfield at the helper
choke-point. Consumers: match spill x2 and the widen-store subset +
nested arms (the let/assign/return-widen/arg-widen/vararg choke-point)
copy the box from memory, then share the existing zero-pad + tag-remap
tail; `is` loads the tag through the address; `as` spills mem-based.
Rule-7 loud bounds replace silent cursor garbage for the non-mem-based
>32B kinds, `?`/`!`, and the 33-48B in-reg tagged arg push (mem-based
push stays the #35 family); the exact-type >32B return passthrough
from INDEX/DOT flips from its #38b loud into the widener route. The
pre-existing >48B memarg stack blit (cgplaceaddr) never used the
cursor and is pinned unchanged.
Reviewer-37 amendment: the non-mem-based >32B loud was ONE-SIDED on
two wwstage routes — cgwidentaggedstorebp had no fall-through guard
at all (`let w = *p` on a 56B box: cstage loud, wwstage silent word0
truncation), and cgmatch's guard keyed on matchscrutt-resolved
spillsz, which defaults under cap for kinds matchscrutt can't resolve
(N_UN deref et al), so `match (*p)` slipped it the same way. Both now
loud off the stamped src/scrut type_ (the kind-blind key cstage
already uses), restoring the rule-10 symmetry the body claims.
Emitters and consumers ship as ONE commit: they share the memread
contract, and splitting would open a transient window where a wired
emitter hands an address to an unwired consumer (silent garbage) —
the #61-precedent route-sharing fuse. The CX-global-tuple-base LEAQ
arm is TRIPWIRE wiring: a >32B tagged global-tuple element is
unreachable today (module-level tuple inits are int/str-literal-only;
tagged elements loud at the DATA emit), and the LEAQ keeps the same
base_reg generality as the cursor walk it replaces (ken note, task
record).
This was the last 5b compiler gate: `match insts[pc]` on the regex
inst union (inst_lit|inst_repeat, 56B) was silent-wrong gate-blind
byte-id on both stages (payload words 3+ dropped past the R8 clamp).
test 941 grows 165->200 checks: the #22b BUILDERR pin flips to a
runtime row, plus the 56B driver match, str+nested-tagged payload,
let/is/assign, indexed return, widening (identity and reversed-order
remap), 56B memarg, ken's X1 composition row, 32B-at-cap INDEX
boundary, the ptr-chained p.f match (BX-arm) and (*p)[i] fallback-arm
rows, and the two deref loud-symmetry BUILDERR pins. At base
|
|||
| a72e8158c7 |
w6c+w6c_ww: over-cap (sret) tuple returns carry tagged elements — MEMORY-class box copy (#22b)
The over-cap SEND gains a per-element tagged arm: a declared-TAGGED element copies its whole box mem-to-mem through the sret pointer (@sretarg reloaded into DX, eslot/8 AX-bounced word pairs) from its ident-local slot — cgexpr can't source it (tagged ident load is word0-only; the AX/DX/CX/R8 box cursor would collide with the DX dest-base reload). Non-ident/widening sources stay loud (call-source is task #41 per rob's loud+filed ruling, widening #23/#40). The receive legs were already eslot-generic from #22a except the wwstage MASSIGN sret copy-out, whose wide=(str||slice) key dropped a tagged element to the scalar 8B branch — re-keyed to the accessor's eslot>8. The over-cap DECISION now rides cg_sret_retsize/sretretsize on the DECLARED return type — the same SSoT the prologue @sretarg wiring and the caller use. The old expr-shape count disagreed with the classifier for declared-tagged elements with unwidened-payload exprs: the emit took the register path against an sret-reading caller — built clean, ran garbage, BOTH stages, byte-id, gate-blind (941 b22_reject_widen_ skew). A rule-7 net behind the branch catches any future inverse skew. The same classify/emit class on the LET side: wwstage's cgexpr cursor materialiser counts a void literal elem as 0 words, so a void-bearing over-cap literal init slipped its loud and fell past every store arm to NOTHING (cs louded via the ken-R1 net; ww ran silent-wrong) — wwstage gains the same net. foff advances ride tuple_eslot/tupeslotn (THE accessor): ken's K4 proved the old wide?esz:8 void-elem 8-advance was a LIVE silent miscompile at base (declared-void-elem over-cap return, send wrote +8 where every receive walks the 0-slot). The unwiring also made >32B boxes transportable, exposing the t.N tagged read's unbounded k<eslot/8 cursor walk: cstage OOB-indexed tuple_rseq[4] (invalid asm), wwstage tupreg-clamped k>=3 to R8 (silent payload-word-3 drop). Bounded loud both stages; the mem-based box read is #37's fold (transport itself is size-generic and correct — 941 b22_big_box_mlet). One commit by the #61 route-sharing precedent: unwiring the send is the only thing that makes the ww massign arm, both skew nets, and the >32B read reachable — the one split order that avoids a transient silent-wrong build (send last) lands those arms DEAD, unexercisable by any test until the unwiring commit, so a defect in them would bisect to the wrong commit; fused, every arm lands with the 941 rows that pin it, preserving the #22a exit invariant (zero silent tagged-tuple paths: correct-via-accessor or LOUD with a task cite; proof-greps in-tree — tuple_ebytes/tupebytes 0 hits, all remaining tuple_rseq/tupreg walks cap-validated upstream). Driver: regex fold-5b parse_repetition ((void|size),(void|size),size) = 5 GP eightbytes > TUPLE_GPCAP — pinned end-to-end (construct, send, let/MLET/MASSIGN receive, t.N is/as, destructure) plus head/mid/tail, str+tagged and SSE-over-cap mixes, `_` skip, and the reject pins: 941 165/165 (15 new rows), test-unit 281/281. ken runtime-oracle PASS x2 (independent hand-math layout audit, K1-K5 probes); reviewer-22b amendments (test-only): ken-K5 32B-exact box-at-cap row, both-tagged 2-elem row, chained-sret row, #37-family cite pinned in the big-box experr. Residuals filed: #39 return-forward divergence, #41 call-source tripwire, #37 mem-based box read. |
|||
| 66991585d6 |
wcc+w6c+w6c_ww: tagged tuple elements take their full slot — tuple_eslot accessor (#22a)
slot = roundup8(size(elem)) — 8B is a FLOOR, not a ceiling (user- ratified 2026-06-04; the #237 fieldslotsize precedent; (str,str)=48B predates this — tagged was the one truncated >8B kind). Pre-fix the checkers truncated a tagged element to one 8B slot and every cgen transport walk strode wide=(STR||SLICE)-else-8: cstage read the NEIGHBOR slot, wwstage read ZEROS — both-wrong-differently, so the byte-id gates were blind (prober-9 PG1, /tmp/p9). One stride accessor per stage — tuple_eslot (cgen.c) / tupeslot (cgenstmt.ww) — now feeds every tuple walk; the per-site predicates are deleted as absorbed. Sizer: check.c N_TTUPLE + check.ww tupleelemslot gain the TY_TAGGED arm (astsize already rides the type table since commit 0). Routes flipped to the accessor, both stages symmetric: cursor producers (lit/slot-to-cursor; tagged ident elements load their box from the slot — cgexpr's tagged ident load is word0-only), let-receive (tuple_store/tupstore generalized to eslot/8 words), N_RETURN send, by-value param receive, arg restage/drain (tagged stays loud per C-t2), destructure (MLET/MASSIGN, ident + sret + in-cap), t.N read + len(t.N) + global-g.N offset walks (t.N gains the tagged box load: AX=tag, DX/CX/R8=payload — the is/as spill cursor), sret classifier, DATA emit. wwstage cgtagvariantidx gains the #67 stamped-carrier arm (flatvariantidxt on .type_) — its AST-only key silently clamped 't.0 is size' to tag 0; fused here because the tuple-element read this commit wires is its only exercisable consumer. Exit invariant: zero silent tagged-tuple paths — in-cap shapes (<=4 GP eightbytes) are correct end-to-end; everything else is LOUD: over-cap sret return (#22b, task #28), call-arg (C-t2 #32), non-local literal element sources (#22b/#23), tuple-in-union payload (#242/#22b), global static-init, element write (pre-existing). Closure proof-grep at HEAD: 'tuple_ebytes|tupebytes' -> 0 hits; 'wide.*=.*(TY_SLICE|TY_STR)' tuple-walk survivors are all behind tagged loud-guards (cgen.c:2535/2568/12013 widen-store + over-cap send; cgenutil.ww:3527 twin). Latent cross-checks closed by the accessor: wwstage MLET-sret strode esz (4 for i32) vs cstage 8; wwstage param-receive strode slotsize (composite slotsize) vs cstage 8; both now the accessor's floor-8. Tagged inits in pins use the CAST form (5: size) — the bare untyped-int widen-store mis-tag is pre-existing at master and filed (task #33). 941 gains 13 rows: t22_* size/align folds (+ void-elem 0-slot pin), the full runtime round-trip (read, is/as, both element orders, void variant, destructure, literal-let), two-tagged-elem in-cap and float+tagged in BOTH orders (ken k1/k2 regression-pinned as rows), t.N-as-call-arg, 5 loud pins (arg, over-cap return, call-source element, global init, element write), and the sret_narrow_mix_* triple below. Runtime rows exit-checked under both drivers + byte-id. 129/129; unit tests green; sizelint clean. ken R1 (re-validation fix): an OVER-CAP tuple init whose rhs is not a CALL fell past every cstage N_LET store arm to NOTHING — silent uninitialized-frame reads — while wwstage loud-rejected the same shape. Pre-existing for (str,str) literals; the #22a tagged slots routed >16B-box tagged shapes into it (k5b/k5c/k5d, base-correct because base truncated them in-cap). cstage now routes the rhs through cgexpr (the cursor materialisers carry the exact wwstage loud texts) with a trailing fatal as the net; 941 pins both the tagged and the (str,str) spellings of the hole. reviewer-22 (review fixes, folded): the MLET-receive slot flip above landed ALONE on the wwstage over-cap sret family — the ww RETURN-send and MASSIGN-receive still strode packed esz (4 for u32), so `let (a,b,c) = f()` over (str,u32,str) read c at slot offset 32 while ww's send wrote it packed at 28: a ww runtime REGRESSION vs base (base was packed-consistent on both sides — ran right, byte-diff). Both walks now stride the slot (send mirrors cstage's `wide ? esz : 8`; MASSIGN strides tupeslotn) — closing, in the same stroke, the pre-existing base skews: ww `let t = f()` over-cap narrow-mix read (reader slot-laid vs send packed, runtime-wrong at base) and the cs≠ww asm on all three routings. Second find: t.N tagged element as a CALL ARG — cstage rides its generic node_istaggedarg cursor push, but wwstage's kind-gated aistagged missed N_DOT and mis-routed the box into the widening branch (taggedvariantindex -1 clamped to 0: callee read variant 0, silent, ww-only). cgenutil.ww gains the N_DOT arm (aistagged + pushargsrev), the #67 stamped-carrier twin of the N_INDEX (#12) arm. The N_DOT arm was a LIVE wwstage stdlib miscompile: hextest's import graph carried two t.N tagged-arg sites (base .s diff = exactly the clamped-tag PUSHQ pair) — the 989 lib-byteid ratchet caught the convergence and #59.4 graduates to M_ID (runtime-correct per 979_hex_run). Probes: /tmp/r22. Residual filed: tagged arg from deref/cast/unwrap sources is a word0-only read on BOTH stages (pre-existing, base-confirmed) — generalizing wwstage's kind gate to cstage's type-keyed check rides that task. |
|||
| 06b0fea98b |
w6c+w6c_ww: struct-lit store into indexed/deref/field place fills via resolver (#20)
A struct-LITERAL rhs aimed at an N_INDEX element (a[i] = pt{...},
(*ts)[i].caps[k] = capture{...}), an N_UN deref place (*p = pt{...}),
or an indexed-base FIELD place (a[i].f = pt{...}, reviewer-20 sibling)
fell to a scalar store tail in BOTH stages: cgexpr on a struct
literal emits nothing (AX=0) and one MOVQ zeroed the place's first
word — every field silently dropped, a str-leading element's
content.ptr nulled (downstream SEGFAULT). Byte-identically wrong, so
every byte-id gate was blind; runtime pins added.
Fix: divert struct-lit-rhs INDEX/UN-STAR/DOT-over-INDEX places past
the legacy arms and widen the F6 assign-resolver gate
(N_DOT -> N_DOT|N_INDEX|N_UN); the existing C1.25 aggregate arm
materialises the literal into a fresh per-use @placescr slot and
word-copies to the cgplaceaddr-resolved address. No new path;
@placescr alloc site stays single per stage. Rider (task #32): an
array-LITERAL rhs at assignment — unwired for EVERY place kind, same
silent zero-word tail — now dies loud at one choke-point until the
fill lands; build-fail rows pin it.
Gates regex fold-5a (run_thread groupstart capture store,
regex.ha:643-651). Residual adjacent gaps (deref ident-rhs truncation,
>24B ident reassign cs!=ww, struct compound acceptance, value-global
DATAW, tuple-lit deref truncation, CALL-rhs RAX-only store) probed
pre-existing and filed as tasks #31 A-G / #32.
|
|||
| 0ce98cc5dc |
wcc+w6c_ww: append() spread place-chain sources via cgplaceaddr (#35)
#35 (fold-5 blocker, PG6): the append() spread arm dispatched on SOURCE node kind — ident-local only; both fold-5 dup shapes loud-rejected on BOTH stages: deref-spine `append(dup, (*tsp)[0].caps...)` (add_thread regex.ha:569/572) and indexed `append(res, ts[0].caps...)` (search ha:820). Route every non-ident-local source through cgplaceaddr (the established place choke-point: C1 stores, C1.25 aggregate fields, FA1 append targets, #49 element sources): the source header ADDRESS resolves ONCE, pre-grow, into a fresh per-site @appsprscr spill, and every loop iteration re-reads .ptr/.len THROUGH the spilled header after the grow. This adapts the #49 split ruling to the spread's full-range copy: the chain's rvalues (deref-root pointer expr, index exprs) evaluate exactly once PRE-grow — an index reading the dst's len sees the pre-append value — while the source data base and len re-derive live each iteration, so a source header aliasing the dst header re-roots post-realloc. A header reached through a buffer the grow reallocs reads the STALE copy — bit-identical under the non-reclaiming rt/malloc, the same documented #49 stale-base hole (pinned by the spread_selfalias_chain row). Ident-local sources keep the legacy BP-disp emission byte-identical. New loudness with the same fix: the spread source's stamped type must chase to TY_SLICE/TY_STR — a [N]T array ident source previously read its first 16 DATA bytes as a {ptr,len} header, silently. Array wiring plus the remaining rvalue sources (CALL, slicing exprs) stay loud, filed task #27. Global-ident sources now resolve on cstage but are blocked by a pre-existing wwstage checker reject ("let: not assignable", task #29) — no dual-stage row until that closes. 806_append_place: reject_spread_src GRADUATES to a runtime row (spread_place_deref, the old reject source verbatim + readback); new rows spread_dup_copy (PG6 verbatim: both fold-5 shapes, 56B capture elements, copy-semantics mutation pin, want 139), spread_place_kinds (str 24B headers / narrow i32 / empty deref source), spread_growth_place (40-elem spread crossing cap doublings), spread_selfalias_chain (source header inside the dst's grown buffer), and the two new loud-tail rejects (CALL rvalue, [N]T array) pinning the "#35:" diagnostic on both stages. 87 fixtures green (was 70), per-row cs/ww asm byte-cmp included. Unblocks regex fold-5a: add_thread regex.ha:569/572 + search ha:820 spreads go from loud-bound to real spread. |
|||
| 1bcf2726cf |
wcc+w6c+w6c_ww: delete() range form delete(xs[lo:hi]) (fold-5a P2)
Hare's delete also takes a slicing place (harec check.c:1981-2027 EXPR_SLICE; Hare spells it delete(xs[i..j])): remove [lo, hi) — shift [hi..len) down count = hi-lo strides, len -= count, cap unchanged; lo defaults 0, hi defaults len, so delete(xs[:]) clears the slice with storage retained. Checker accepts N_SLICE next to N_INDEX (object must chase to a slice, harec :2024); the old range-unimplemented reject and its #35 cite drop. Lowering (both stages, converged byte-identical by construction) is the single-element arm's same-slice whole-stride word-copy loop with a DYNAMIC src offset (count*esz via a src register) instead of the constant one-stride. Base shapes: local slice ident, deref-of-local, plus NEW indexed local-slice base xs[g][lo:hi] — the fold-5a consumer shape (regex.ha:333 delete(jump_idxs[group_level][..]); outer stride off the type table). Bounds stay implicit, inheriting the documented single-element posture (no index checks anywhere in cgen). Operands evaluate left-to-right, exactly once, before the shift (harec order); only the header ADDRESS is taken before operand eval, so a bound expression's writes through the slice land before the copy. test/809: 64 fixtures — full/explicit/re-clear/head/mid/tail/empty a:a/end-boundary len:len/explicit 0:0 on a never-appended (nil-ptr) slice, single-vs-range equivalence, cap preservation, esz 1/2/4/8/16/24 copy tails against the dynamic src, operand order-of-eval (lo/hi CALLs fire once each, in order) + aliasing-visibility pins, the EXACT [][]size regex consumer shape, deref base, 2 reject rows w/ diagnostic text; every accept row cs==ww asm byte-id. test/804: reject_range row retired (form now accepted), reject_nonindex text follows the widened message. |
|||
| e091dfbdbe |
wcc-ww: assert/abort builtins — checker tag + cgen rt_abort lowering (#58)
wwstage had no EXPR_ASSERT-family intercept: the checker left bare assert/abort calls untyped (asserttyped gate 4 skipped them by design) and cgcall fell through to the regular call path, emitting CALL assert(SB) for a symbol that exists nowhere — link-fail. cstage was already correct (tag ty_err at check.c:1536-1572, lower inline via rt_abort at cgen.c:6618-6663). Mirror the same tag-then-lower pair: exprtype N_CALL stamps the call void and the callee TY_ERR behind the scopelookupprefer no-shadow gate (the isassertfam predicate), with the cstage arg diagnostics (cond must be bool, msg must be str, arity caps); cgcall keys on the TY_ERR tag and emits the identical CMPQ/JNE/rt_abort sequence. A user-shadowed assert/abort (same-module or cross-module, the #45 shape, task #14) stays untagged on the regular call path — byte-id for all existing lib code preserved. The cond check does NOT alias-peel: cstage compares ty_bool by identity (check.c:1560), so `type myb = bool` is rejected there; wwstage aligns down per rule 10 (a resolvealias here was accepting it — cs/ww accept-reject divergence). Widening both stages together belongs to the alias-peel choke-point arc (task #5, #47/#68). The resolvewalk N_IDENT resolution counter learns the builtin shape: an unshadowed abort/assert ident binds no sym BY DESIGN, so wwdump -r's zero-unresolved gate (990 probe 4) counts it resolved instead of failing builtin-using units. test 957: 13 rows — pass/fail/msg/bare abort (run exit + rt_abort stderr content; no-msg rows pin EMPTY stderr = the (NULL,0) shape), assert in an imported module, same-module + cross-module shadow controls, 5 checker rejects pinned on diagnostic CONTENT (shared substring; cstage prefixes pos, wwstage cerr is bare) incl. the alias-of-bool cond row pinning the rule-10 down-alignment; each positive row pins cstage run exit + cs==ww byte-id. On pre-fix master 11/13 rows trip (survivors = the two shadow controls). Residual (separate root, deferred diagnostic class): zero-arg assert() is not intercepted by either stage; cstage rejects via the generic undefined-ident path, wwstage's undefined-callee diagnostic is the class deferred behind wiring checkfile into w6c_ww. |
|||
| 33ec0fb1ac |
w6c+w6c_ww: (*p)[i] deref base materializes the array ADDRESS (#61 C)
Both stages SEGV'd identically (byte-id-blind): cgun's TK_STAR emitted a scalar MOVQ (AX),AX for an array pointee, so the index consumed a[0]'s VALUE as its base — a wild deref. An array value IS its address everywhere in this cgen (#270-1a), so the ARRAY pointee now takes the same skip as the #185 *fn deref in both stages: `*p` leaves AX = p's value, and every consumer that materializes a complex index base via cgexpr(base) — N_INDEX read fallback, cgassign store/compound, TK_AMP, N_SLICE — gets the array address from the one deref choke-point. wwstage additionally joins the N_UN-TK_STAR base to the stamped-tinfo esz arms (cgindex / cgassign store + compound / TK_AMP &(*p)[i]) where cstage reads base->type uniformly: without it, esz fell to the 8B default and a narrow element would mis-stride the moment the base started materializing (cs!=ww only reachable post-choke-point-fix, which is why it rides this commit). 949_ptrarr_index_run grows the deref_* rows: read (8B/4B/param-base), write (8B / 1B+neighbor-guards), compound — runtime + byte-id, the only nets that can see a both-stages-identical miscompile. |
|||
| eea3e197c2 |
w6c+w6c_ww: *[N]T indexing strides by element, not whole array (#61 A+B)
Indexing through a pointer-to-array auto-derefs, so esz and the element classification must come from the pointee array's ELEMENT (cstage idx_eff semantics, cgen.c:1163). Two halves of one root class: A (wwstage-only, cs!=ww, cstage runtime-correct): elemsizeofc's #270-2 nested-array block treated an N_TPTR pointee-array like a [N][M]T outer index and returned the whole-array size — every p[i] read/write/ compound scaled by N*size(T), and the same wrong element belief reached the store-width chooser (var-idx write emitted an N*8B aggregate copy sourced at the 8B rhs slot: caller-frame smash, the siphash round() corruption). Fixed via two wwstage choke-points mirroring idx_eff: idxeffti (tinfo: NAMED peel + TY_PTR->TY_ARRAY drill; feeds elemsizeofc and elemissignedc/elemisfloatc/elemisf32c) and idxelemtn (node: element tnode with the same drill; feeds every cgindex/cgassign/nodeisstr/ match-scrutinee elemtn resolution). B (BOTH stages identically wrong, byte-id-BLIND): the TK_AMP &base[i] arm read bu->sub->size without the ptr peel (&p[3]-&a[0] = 96, not 24). cstage now routes esz through idx_eff. A and B are FUSED by the pre-existing routing topology, not by choice (rule 11): wwstage's TK_AMP arm already reads its esz via elemsizeofc (selfhost/cmd/wcc/cgenexpr.ww:4095, the #11 addr-of twin of the #10 cgindex fix), so fixing A's choke-point flips wwstage's half of B in the same stroke. A standalone A leaves &p[i] transiently cs!=ww; B-first is the mirror transient; carving the TK_AMP caller out of the fixed choke-point to preserve the wrong stride for one commit would be a deliberate known-wrong intermediate (rule-7, vetoed by rob). One choke-point, two enrolled routes — un-fusable without a red intermediate. Close-by-construction proof-grep (both stages): every remaining raw sub->size index-stride read is TY_ARRAY-gated, a slice-only builtin (delete/insert), a checker-stamped element tinfo (indexresult already decays *[N]T, check.ww:2277-2284), or a non-index context (tuple slots, let-init elements). Two true residuals filed with site+symptom instead of silently absorbed: N_SLICE through *[N]T does not decay (LOUD type error, Hare divergence; team task #18) and non-ident cast-expression index bases keep wwstage's 8B-default esz (pre-existing #74-style cluster; team task #19). cstage's N_INDEX read-side str/slice header gates also move from u->sub to esub (identical for every non-ptr-to-array base; honest for *[N]str — pre-fix BOTH stages were runtime-wrong there, differently). 949_ptrarr_index_run pins the class at runtime + byte-id: {1,2,4,8}B elems, const+var idx, param/local/cast bases, read/write/compound, neighbor guards, &p[i] pointer-difference, siphash-round mix shape. 989_lib_byteid: siphash_test graduates #59.7 DIVERGE -> ID (ratchet tripped loud pre-update; no other #59.x pin flipped in the same run). (*p)[i] (sub-bug C) follows separately. |
|||
| 8578ad0533 |
wcc+w6c+w6c_ww: global tuple lets — DATA emit + element reads + len(g.N) (C-t3, #48)
Global tuple lets were WHOLLY unwired, silently: let_emit_size returned 0 so emit_lets SKIPPED the definition (no DATA, no diagnostic), then cstage's t.N read and #235 len arm read BP-frame garbage (localfind→0) while wwstage — with the tuple never in collectlets — mis-emitted the field index as a symbol (`MOVQ 0(SB), AX`). ken's #48 was the len() facet of this. Now: let_emit_size/letemitsize admit TY_TUPLE (slot-sum size, rides C-t0); emit_tuple_data/emittupledata lay the slot-format DATAW row — a scalar element one 8B LE word, a str element its 24B header slot with a DATAR ptr patch at the element's slot offset (the #18 [N]str per-element pattern; strlits pre-interned in element order) — and any element that doesn't reduce to an int/str literal dies LOUD instead of skipped. The t.N read and len arms gain the global base (LEAQ sym(SB) into CX, the struct-field-global pattern; wwstage's C5 len loud-stop graduates to the working path). A GLOBAL tuple as a first-class VALUE (`let q = g;`) loud-stops on both stages — pre-fix it byte-identically loaded word0 only and read a stale cursor for words 1+ (element reads are the supported surface). 941 grows the t3 rows: global element reads (str+i64 and packed u32,u32 incl. len(g.0)) + rejects (float-element init, whole-value use, pre-existing element-write anchor). 7/82 checks fail at the C-t2 parent (cs silent-garbage runtime, ww C5 build-fail, both rejects vacuous-or-absent). |
|||
| 6426fac6f2 |
w6c+w6c_ww: tuple by-value ARG send — every cursor-filling producer rides #163 (C-t2, #32)
node_tuplearg was N_CALL-scoped and its comment claimed non-call forms "loud-stop" — they did NOT: a tuple ident/literal/unwrap arg fell to the scalar single-PUSHQ default, skewing every later arg register so the callee read garbage word 2 (byte-id both stages, the gate-blind both-wrong class; packed shapes SIGSEGV'd pre-C-t0). The receive side (cgfn #163 walk) was already correct. cgexpr already fills the return-ABI cursor for every supported producer (#241: ident via slot-to-cursor, literal via lit-to-cursor, unwrap via payload shift; call via the return ABI) — the send now admits exactly those into the existing @tupargscr restage + per-class drain (node_tuplearg widened; wwstage gains nodetuplearg, mirroring it over the local tnode / inferletcalltype; rettupleof stays N_CALL-scoped for the destructure receives). Any OTHER tuple-typed source shape loud-stops at the push site — the false comment's claim, now true (rule 7). Literal tuple elements are stamped expr types, so the restage/drain wide test goes type_isstr/type_isslice (TY_UNTYPED_STR- aware) with the ty_str->size header stride; the wwstage twin walks a literal's VALUE exprs the way cgtuplelittocursor classifies them. Ken review demands folded in: (1) a NESTED composite element (tuple/struct/array/tagged inside the tuple) occupies more than the one GP word the restage walk counts — the checker accepted it and it ran WRONG (inner words skewed, wwstage SIGSEGV); both stages' restage walks now loud-stop the element kind (wiring is the filed follow-up, task #65). (2) the variadic interaction probed: a tuple arg ahead of a variadic tail rides the restage correctly (positive row); variadic-of-tuples stays bounded-loud via the tuple-in-slice read surface. 941 grows the t2 matrix: packed/16B params with branched callees, mixed arg orders both ways, literal arg, (f64,i64) param, unwrap arg, ken's >6-GP-pressure stress (4 leading scalars + tuple + a 7th stack-class word), variadic-after-tuple, plus rule-7 reject rows (chain-source arg, nested-element arg, variadic-of-tuples, over-cap ident arg) and the fold-4 charset substrate pin ([](u32,u32) append stays LOUD). At the C-t1 parent 18/73 checks fail: every runtime arg row except the (f64,i64) anchor on BOTH stages (byte-identically — the gate-blind both-wrong class) and the chain/nested args silently accepted. |
|||
| fdfc2ce318 |
wcc+w6c+w6c_ww: tuple slot layout SSoT — checker size = cgen slot stride (C-t0)
The checker computed TY_TUPLE size as the packed element-size sum ((u32,u32) = 8B) while every cgen cursor-transport site strode 8B slots (16B). 16B tuples were blind to the split (slot == packed); packed tuples hit it everywhere: cstage let-receive keyed on sz 16/32 missed sz 8 and dropped word 1, the cgfn param receive spilled 8B/element into a packed-sized local (saved-BP clobber, SIGSEGV), and mixed (u32,f64)/(u32,str) shapes missed the receive arms entirely. Slot layout is now the SSoT (user-ratified): the flip lives in the two checkers' N_TTUPLE size computation only (check.c, check.ww tupleelemslot + stamp); cgen's packed-keyed walks (t.N read, #235 len arm, over-cap sret send/receive pair) align onto the slot stride, and the wwstage t.N read gains the natural-width load (tnodeloadop) to byte-id with cstage's fldloadop. ttupleelem.offset re-stamped slot-cumulative (no consumers yet). The #242/#243 eightbyte-share loud-stop dissolves by construction (no two narrows ever share an eightbyte) — 940's eightbyte_share row graduates to a runtime round-trip. Hare-layout divergence documented at both checker sites; re-alignment is task #60. #32 send skew and #33 wwstage literal-let receive are separate commits on this base. 941_tuple_slot_layout_run pins the matrix: 4 packed rows fail at the parent (8/21 checks), 3 neutral anchors prove 16B/32B emission untouched. |
|||
| 9861f73bbb |
wcc+w6c+w6c_ww: insert() builtin — single-element slice insertion (part of #35)
Hare's insert(xs[idx], v) (ref/harec/src/check.c:745 check_expr_append_insert — append/insert share the checker arm, "insert" at :786): checker accepts an INDEX place over a slice plus one value, stamps void; idx == len is a legal end-insert (the ref/hare os/exec/platform_cmd.ha:86 idiom). Loud-rejects with exact texts: spread form insert(xs[i], vs...) (filed, #35 — also covers harec's with-length form via the arity check), range place (not Hare; harec only parses ACCESS_INDEX, :784), non-index operands, array bases, wrong arity. delete()-parity throughout. Lowering (both stages, converged byte-identical by construction) is a DESUGAR: append(xs, v) — reusing append's grow (rt_ensure) and the entire #34 value-store dispatch (scalar / str-slice header / tagged widen / struct fill) verbatim, one boxing choke-point — lands v at slot len-1; then a rotate-right of [idx, len) moves it home through a fresh per-site esz frame scratch (@insscr). The rotate is delete's shift loop in reverse (descending j, the safe memmove-up direction) and is a same-slice whole-stride raw byte move — no boxing exists for any element kind. idx evaluates BEFORE the grow (Hare's left-to-right operand order — pinned by the pregrow_len_idx row, insert(xs[len(xs)-1], v): pre-grow [7,13,11] vs post-grow [7,11,13]; an idx==len(xs) end-insert cannot discriminate, the rotate degenerates either way). Base shapes: local slice ident (LEAQ) and deref-of-local ptr-to-slice (MOVQ); others rule-7 loud-stop, like delete. test/807: 57 fixtures — front/middle/end + idx==len via len(xs) + the pre-grow eval-order pin, esz 1/2/4/8/16/24/56 (MOVB/MOVW/MOVL tails, struct body, str header, 7-qword tagged from a typed local [the regex fold-3 ha:347 newinst shape] and from a cast rvalue [ha:419/441]), empty-slice grow, (*p)[i] deref base, front-insert loop, 6 checker reject rows with diagnostic-text checks; every accept row cs==ww asm byte-id. |
|||
| c34a48a81f |
wcc+w6c_ww: append() struct-element sources via split place-resolve (#49)
#49 (#35's single-element sibling, tranche-C pre-check PC2): the
struct-element append arm dispatched on SOURCE node kind — N_STRUCTLIT
(literal fill) and N_IDENT (local word-copy) only; every
place-resolvable chain died on the rule-7 fatal in BOTH stages,
including search()'s result-build line
`append(res, threads[best_idx].root_capture)` (regex.ha:819).
Wire those shapes with a SPLIT resolve around the grow (the #49
ruling): the chain's rvalues — deref-root pointer expr, index expr —
evaluate exactly once PRE-grow into @appendsroot/@appendsoff (an index
reading the slice header sees the pre-append len, Hare's argument
order), then only the BASE re-derives POST-grow from the live storage
and the stashed offsets land back on top, so a self-append source
re-roots in the post-realloc buffer. harec resolves an aggregate
source address wholly PRE-grow (gen.c: gen_load returns the address
for STORAGE_STRUCT, gen_store copies after rt.ensure) — a
use-after-free under a reclaiming allocator; per #263 we align to the
runtime-correct side, not the reference. A pointer ALIASING the grown
buffer keeps Hare's own stale-base hole (sound today only because
rt/malloc.ww never reclaims). Supported shapes are bounded: root
(local/global ident | deref) + at most one index + trailing direct
fields; all else stays on the #34 fatal, including CALL rvalues (the
#42-style bound, new reject row pins the text in both stages). The
N_STRUCTLIT/N_IDENT fast-paths keep their emission byte-identical.
806_append_place grows eight rows: indexed-field 56B capture (the
ha:819 shape, header readback), computed-index whole element,
deref-spine param pair, deref source, self-append ×33 crossing three
cap-doubling reallocs, the split-order semantics pin (a CALLED index
helper reading len must run once and see the PRE-grow len — the
pre-split emission failed exactly there), an element-kind ×
place-source matrix row (scalar/narrow/str/slice/tagged route via the
pre-existing arms — regression net), and the CALL-source reject. The
six fix rows verified FAILING against a pristine
|
|||
| 796d41bb9f |
wcc+w6c_ww: len() over place-resolved operands (F2/FA2)
C5 (tasks #10 + #41): the len() builtin's operand handling was an arm enumeration that leaked FOUR siblings over time (#235 tuple-elem → #19 indexed-elem → F2 len(xs[i].field) → FA2/FB1 len(*p)) — every unhandled slice/str operand shape fell to a bare cgexpr fallback that returned the slice DATA POINTER as the length. Silent ptr-garbage, byte-id both stages, gate-blind. Probing at |
|||
| d642017643 |
wcc+w6c_ww: aggregate let/range element copies via cgplaceaddr (F5)
F5 (task #7): the N_LET aggregate-copy arm's source-addr enumeration
(cgen.c #265/#268) had TY_ARRAY-ident/N_DOT/N_INDEX bases but no
TY_SLICE base and no deref-spine shapes, so `let e: th = xs[0]` fell
out with havesrc=0 — cstage emitted NOTHING (slot uninitialised),
wwstage fell to its scalar default (8B truncation): gate-blind cs≠ww
(p6min13). Every remaining ADDRESSABLE rhs now resolves through
cgplaceaddr (the C1 resolver; enumerated arms dispatch first, their
asm untouched), and the arm closes by construction with a loud tail —
nothing below it can initialise a >8B struct/array slot, so any
unhandled rhs shape dies loud instead of silently. A pre-tail #38b
guard keeps the established `?`/`!`-on-sret loud-stop marker in
wwstage (mirror of cstage's pre-arm fatal; pre-fix that shape reached
the cgtryunw/cgtryprop gates which the tail now pre-empts in let
position).
Reviewer-C2 inheritance: `let c: capture = (*ts)[i].cap` (aggregate
leaf behind a deref spine) — wwstage's documented cgdot aggregate-leaf
loud is retired for let position (cglet routes the copy through the
resolver before cgexpr sees the leaf; the loud stays as the guard for
non-let expr positions), and cstage's silent no-copy on the same shape
is fixed by the same resolver fallback.
By-value RANGE payloads ride the same class: N_FORRANGE's single-bind
load truncated every aggregate element to one fldloadop word. Both
stages now word-copy the full element extent (MOVQ run + sized
MOVL/MOVW/MOVB tail, the #270-1b idiom) for esz > 8. wwstage esz is
re-keyed elemsizeof→elemsizeofc (the 8-sentinel hid struct elements
from the copy gate — the #8 named-narrow precedent), with a
stamped-slc.type_ fallback + element-tnode synthesis for non-ident
scrutinees (tinfo SSoT, #209/#211). The wwstage checker now binds the
ELEMENT type on single-bind ranges via a synthetic N_LET binder node
(mirror of cstage check.c N_FORRANGE scope_define(..., elem, ...));
pre-fix the binding's decl was the N_FORRANGE node itself, so any
field read off a by-value binding asserttyped-bailed. The checker
half folds in under rule 11 because the split is unsound in either
order: cgen-first is untestable (every field read off the binding
still bails), checker-first converts that loud bail into the 8B
SILENT truncation — only the pair closes the class.
FC0 graduates: regex.finish's by-value range over 24B charset elems
(non-ident scrutinee re.charsets) was the lib/regex byte-cmp's ONLY
hunk since fold-1 — cstage 8-of-24-byte copy + IMULQ $24 vs wwstage
1-byte MOVZBQ, runtime-masked by the no-op loop body. The byte-cmp is
now ZERO hunks (regex_test.combined.ww, w6c vs w6c_ww).
#36 disposition: NOT folded. p6min9/p6min10's remaining failure is the
struct-ident field rhs inside a struct LITERAL (cg_structlit_fill
under-copy) — a different choke-point from the let-copy source-addr
machinery; they still exit 4 here and stay blocked on #36 (read half
landed in C2).
Residual filed as task #43: an UNANNOTATED aggregate let
(`let e = xs[0]`) still skips the wwstage arm (aggn/letslotsize are
annotation-keyed; cstage keys the stamped n->type and now full-copies)
— cs≠ww on that shape remains, #38-family. A landmine comment in
test 805 marks the gap.
test 805: +6 rows — let-from-slice-elem 16B (p6min13 verbatim) /
24B/40B/12B(MOVQ+MOVL tail) matrix / deref-spine leaf / by-value range
([]struct both-fields sum, []capture 40B, []str 24B header) / range
edges (empty slice, by-VALUE binder-mutation pin, 12B elem MOVL tail)
/ reject row pinning the loud-tail text on both stages. All six fail
at the pristine parent
|
|||
| 074e68f05d |
w6c_ww: tinfo re-key of the cgdot N_INDEX-base arm (F7/F10)
The wwstage `arr[i].field` read arm was syntactic where cstage is type-table-driven: element typing keyed on tnode KINDs (N_TSLICE/ N_TARRAY/N_TPTR) with an N_TNAME element resolved by structlookup NAME — any base typed via an alias (`type result = []capture`, p11b/F10) missed every gate and died at the interim C2 loud guard (pre-C2: fell silently to the SB fallback). Re-key the arm onto the checker-stamped tinfo (lhs.type_ element / idxbase.type_ base, NAMED peeled), mirroring cstage cgen.c case N_DOT's N_INDEX-lhs arm 1:1 — the #209/#211 name-keyed->tinfo-SSoT cluster. Emission sequence is unchanged; the C2 "C3/task #8" guard retires with the arm wired (task #37's ptr-chain guard is untouched). Global classification mirrors cstage let_islet || def_isarraydef via isletvar / defvartnode-N_TARRAY (the cgplaceaddr C2 pattern). F7/FA5/FA3-ww/FA6 (non-ident idxbase shapes, task #17's ww halves) were already closed by C2's read-resolver recursion; p7_composed, pA5, pA9 run exit-0 byte-id and are pinned as rows here. Task #29's asserttyped bail (strings.frombytes over a slice-expr in the composed context) no longer reproduces at HEAD — dissolved during the C1.25->C2 arc; p7_composed builds clean on w6c_ww, runs 0, byte-id. Behind the retired guard three alias-blind NON-cgdot sites surface (`let l: wlist = []` checker reject / alias-array global emits no DATA / alias-array arrlit-init under-copies 8B per element, cs!=ww runtime): filed as task #38, same name-keyed class, separate sites. test/805: +5 rows — alias-slice field-kind matrix, alias-array + viaptr-element bases, p11b-essence let-bound alias reads (the task-#14 `?` factored out), the composed p7 match/compound hot shape, and the pA9 free()-operand acceptance row (FA6). Task #8; the last cgen gate before regex tranche B. |
|||
| 76994a8279 |
wcc+w6c_ww: typed dot-read resolver — kill both silent N_DOT fallbacks (F4)
A typed depth-2+ field chain behind an index/deref spine (threads[0].cap.end, (*p)[i].f.g) aborted the cgdot chain walker (N_IDENT roots only) and fell into the module-qualified-leaf fallback — a silent `MOVQ <leaf>(SB)` global read of a colliding symbol (p6min10 exit 66) or a link error. Single-dot reads behind a deref- index ((*ts)[i].pc, (*p)[i].slicefield) fell into the bottom catch-all instead, which is offset- and header-blind: offset-0 scalars read correctly by COINCIDENCE, nonzero offsets and slice headers were silently wrong in BOTH stages (FA3, pA5). `&threads[0].cap` dropped the address-of silently and SEGFAULTed at the deref (task #6, reviewer-A route). cgplaceaddr (C1) grows an N_IDENT root (local / let / DATA-backed def) and the N_INDEX base gate relaxes to recursion, so indexed-ident and deref-rooted spines resolve; enumerated arms still dispatch first, keeping every pre-C1 shape's asm. case N_DOT routes any TYPED read no arm matched through the resolver (scalar fldloadop, float X0, str/ slice 3-word header, [N]T address); the module-leaf fallback is gated to UNTYPED chains, the catch-all to untyped-str pseudo-fields, and the TK_AMP tail is resolver-or-loud. Leaf kinds without a register convention (tagged, aggregate) and unaddressable shapes die LOUD (rule 7). wwstage mirrors symmetrically; two of its arm gaps must not take the resolver (its sequence differs from cstage's arms — cs!=ww): ident-indexed alias reads loud-cite C3 (task #8) and non-local-rooted ptr-chains loud-cite task #37. A third verdict divergence is comment- documented at the wwstage aggregate gate: cstage's let-init consumes `let c = (*ts)[i].cap` BEFORE its N_DOT tail (emitting NO copy — the F5 bug), so that shape cs-builds/ww-louds until the F5 let-copy lands (task #7); absent from the gate corpus. 806 identroot_dot graduates from BUILD_FAIL: the C2 ident root makes append(h.xs, v) through *holder resolve via C1.5's place consumer (run-verified, byte-id). p6min9/p6min10 read-halves are fixed but the probes stay blocked on the #36 literal under-copy this commit unmasked (struct-ident field rhs copies 8B; repro filed with the task). test/805: +7 rows (typed depth-2 behind ident-index incl the 777 global-collision pin, deref-index, width/float/[N]u8 matrix, FA3 slice-field + .cap-behind-spine, &-route with compound-through- pointer, C1's reject_tail graduated to stores, neutrality pins) and +4 reject rows pinning the new loud texts; the C1.25 raw-byte readbacks graduate to typed depth-2 reads. |
|||
| b630a7cf20 |
wcc+w6c_ww: append through pointer-to-slice place via cgplaceaddr (FA1)
Re-key the append() lowering from BP-displacement assumptions onto a resolver-provided header PLACE (task #15, the add_thread hard-blocker; cgplaceaddr's third consumer after C1/C1.25). One mirrored choke-point, two failure modes: cstage 0-defaulted sn_off for any non-ident target, so 0(BP)/8(BP) became the "slice header" and rt_ensure corrupted the CALLER frame (SIGSEGV); wwstage cgappend silently emitted nothing (gate-blind cs!=ww). cg_append_grow/cg_append_slot (mirror cgappendgrow/cgappendslot) factor the 5 grow + 5 slot header-access sites. Ident-local targets keep the legacy BP-disp emission byte-identical (probed across all 9 existing source shapes, before/after .s). Non-ident targets resolve once through cgplaceaddr and spill the header address to an @apphdrscr slot: rt_ensure may realloc .ptr but never moves the header, so the slot stays valid; every access reloads from it. The slot is allocated fresh per append SITE, not cached per fn: a nested append-through-pointer inside a value expression (match-yield arm) spills its own resolve, and a shared slot would hand the outer grow/slot reloads the inner target's header — silent cross-slice corruption (pinned by the reentrant_value row). Indirect mode keys esz/element-kind/load-op off the checker-stamped target tinfo (no declared tnode behind `*p`; the #209/#211 discipline). Unwired target places die LOUD "#15: append() target place unsupported (rule-7)" on BOTH stages — the silent-corruption class is closed by construction. The FA4/#35 boundary is unchanged: non-ident spread SOURCES stay loud (pinned by a reject row). Surfaced pre-existing checker divergence filed as task #34 (wwstage rejects global slice-lit let). test/wcc/806: 14 runtime rows (element kinds x target shapes, spread, narrow-signed spread load, cap-crossing realloc loop with branched callee + caller-frame sentinels, deref-spine target, nested-append reentrancy, direct-arm neutrality pin) + 2 exact-text reject rows, both drivers + per-row cs==ww asm byte-id. |
|||
| cfc2985c61 |
wcc+w6c_ww: aggregate-field stores via cgplaceaddr (C1.25)
Wire struct/array/tuple field STORE through the C1 assign-resolver (task #23): structlit rhs materialises into a FRESH-per-use @placescr slot (the @slicescr discipline — a cached slot is the #31 multi-live trap) then word-copies to the resolved address; addressable rhs (ident/global/dot/deref) sources via aggarg_srcaddr with the dest spilled around the dispatch (#270-1b order). Kept loud: compound on aggregate, sret call rhs (#234-tail), <=24B call rhs (task #24), unaddressable literal rhs, ww-only anonymous-struct structinfo miss. test/wcc/805: +6 rows (40B structlit incl ... autofill, ident+deref source, nested literal, [3]u8 MOVW/MOVB and [3]u32 MOVL tails, two same-size stores in one fn pinning fresh-per-use) +3 exact-text reject rows. Tuple-field row blocked by the pre-existing tuple param/let-init word-2 drops (tasks #32/#33, documented in-row). p7b_capstore_only graduates byte-id and runs; p7_composed builds and runs on cstage, wwstage stays behind the pre-existing #29 asserttyped bail (verified identical at master with w6c_ww). |
|||
| 32063d0da0 |
wcc+w6c_ww: >48B tagged by-value args — MEMORY-class two-phase push (#38b)
Task #19 (the #38b residual surfaced by FC2 evidence): a tagged arg whose slot exceeds the 6-reg convention (>48B) is MEMORY-class per ref/qbe/amd64/sysv.c:80-85 (inmem) / :411-426 (stack blit). Caller stages the whole slot below every register-class word (two-phase push, rightmost-first, leftmost mem arg at 16(BP)); callee registers the param in place at positive BP offsets with zero prologue bytes; the merged slot count feeds the existing caller-cleanup ADDQ. Argument-side mirror of the #38 tagged-sret fix, same classify machinery (tagged_memarg_size / taggedmemargsize beside their register-class siblings). Pre-fix, the exact-typed arg loud-stopped on both stages, but WIDENING a concrete variant into a >48B param slipped the old guard silently — cstage pushed one scalar word while wwstage emitted an uncapped greedy stitch (wrong on both AND cs≠ww, gate-blind). Widen sources now route through the @tagscr scratch for mem slots. Loud boundaries kept (rule 7), each with its own diagnostic: sret-class tagged CALL result as mem-arg source (#40-family follow-up), global tagged let (task #25, broken at any size pre-existing), >48B variadic element, and mem-arg + register- overflow mixing (caller check + callee prologue mirror). Single commit: caller staging, callee receive, and both stages are one inseparable ABI class — landing any half alone breaks byte-id or runtime correctness (the #38 flip precedent); test/929 (15 table-driven rows: 56B/64B slots, widen-slip pin, source shapes, mixed orders both ways, two-mem call, 200k-call loop, 48B-boundary absence pin byte-id'd vs master, 5 reject rows pinning the exact per-guard diagnostic on both stages) rides with it. |
|||
| e3e6b5a820 |
wcc+w6c_ww: cgplaceaddr resolver — deref-base assign stores (F6)
(*ts)[i].field = v / OP= v (the regex run_thread hot shape, task #4) compiled to NOTHING in both stages, byte-identically: the N_DOT lhs roots at N_UN(STAR), so the arr[i].field arm (idxbase must be IDENT) and the chained-ptr-field arm (base must be *struct) both miss and the N_ASSIGN dispatch fell off the switch silently, rhs unevaluated. cgplaceaddr (one per stage) is ADDRESS COMPUTATION ONLY — N_UN(STAR) root, N_INDEX hop over a slice/array place (.ptr hop for slice), N_DOT struct-field hop with one deref for a *struct base. Call-sites keep their own emission: scalar fldstoreop store, str/slice 3-word header store staged through DX, 10-op compound template with the chained-ptr-field register roles. Ident-rooted spines stay with the enumerated arms — verified asm-neutral over the 84 fold2b probe sources against fresh master-HEAD binaries (7 diffs = the F6 family now emitting stores; 2 verdict flips = aggregate-field stores, now loud). Silent dispatch tails go LOUD for N_DOT lvalues the resolver can't address and for unresolved-identifier targets (cstage float-ident arm aligned to wwstage's resolve-first order). Aggregate-field stores loud-reject pending the follow-up resolver commit (task #23, ≤24B N_CALL rhs split to #24). The non-DOT tail stays silent deliberately: going loud there would asymmetrically surface the pre-existing str-base element-store divergence — task #22, cited at both sites. test/805: 17 rows x 2 drivers + 12 cs==ww byte-id fixtures — widths (incl narrow-compound fldloadop sign/zero-extension), all 10 compound ops (DIVQ/IDIVQ/SHLQ/SARQ/SHRQ), str + slice 3-word stores, *[N]T base, runtime call index, ident-base neutrality pins, and 5 reject rows asserting exact diagnostic text. |
|||
| 37febab9d5 |
wcc+w6c+w6c_ww: delete() builtin — single-element slice removal (part of #35)
Hare's delete(xs[i]) (ref/harec/src/check.c:1981-2027): checker accepts an N_INDEX over a slice-typed base, stamps void; loud-rejects the range form delete(xs[i..j]) (stays filed on #35 — regex fold-2b's consumers are all single-element), non-index operands, array bases, wrong arity. Lowering (both stages, converged byte-identical by construction): ascending word-copy loop shifts [i+1..len) down one esz stride, then hdr.len -= 1; cap unchanged. The move is a same-type whole-stride byte copy — src and dst are elements of the SAME slice, so no boxing exists for any element kind; one loop serves scalar/narrow/str/struct/tagged. esz off the STAMPED base type (#34/#48 discipline). Base shapes: local slice ident (LEAQ) and deref-of-local ptr-to-slice (MOVQ — the fold-2b delete_thread shape); others rule-7 loud-stop. test/804: 38 fixtures — first/middle/last/to-empty, esz 1/4/8/24/56 (MOVB/MOVL tails + 7-qword tagged), cap-unchanged, (*threads)[i], 4 checker reject rows; every accept row cs==ww asm byte-id. |
|||
| 9732061a7e |
w6c+w6c_ww: free() compiles to a no-op (ww has no free) (fix #27)
The free(x) builtin lowered to CALL ffi_resolve("free") in cstage and
fell through to a generic CALL free in wwstage (which had no free arm
at all) -- an undefined reference at w6l unless an @symbol decl
happened to be in scope. ww has no free by design (rt/alloc.s:30 --
the bump allocator cannot reclaim a mid-chunk pointer; process exit
does), so both stages now evaluate the operand for side effects
(Hare's free(expr) evaluates expr) and emit nothing else, letting
Hare code that calls free() port verbatim (regex fold-2b calls it at
4+ sites). The 2-arg os.free(p, n) public API is untouched: the
builtin gate requires exactly one bare-ident-callee arg.
930_free_noop_run pins per row: w6c/w6c_ww byte-id, no free symbol
in the .s, deref-after-free validity, and the operand side effect
running once per free() via a global counter.
|