e60297085dbac0de285bebdebbe2ff6ce3e15b3f
195 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| d6ea497da1 |
wwstage: align direct-ptr tagged-field READ word-order to cstage (#17)
A >32B tagged-union field (slice payload) read through a direct *struct pointer byte-diverged: wwstage's cgloadtaggedfield always loaded R8@+24 before CX@+16, but cstage's direct-*struct-ptr arm (cgen.c ~11926) loads in offset order CX@+16 then R8@+24. Both ran correct -- a pre-existing rule-10 asm divergence, for a local *struct ptr as well as a global one. Thread a cxlast flag through cgloadtaggedfield: the direct-ptr site (cgptrfieldload, the shared local+global chokepoint) passes cxlast=false to match cstage's offset order; the other 5 callers keep cxlast=true (byte unchanged). A global flip was rejected -- it would clobber the CX-base callers (CX@+16 first destroys the base before the R8@+24 read), and the chained-BX caller must stay R8-first to mirror cstage's chained twin (cgen.c ~12021); the order is a genuine per-arm property of cstage, not derivable from the base register. Test: +2 rows (tagged_slice_field via global *struct ptr, _local via local *struct ptr), runtime + byte-id; both proven to fail byte-id with only the compiler files reverted. |
|||
| ab3ac67afd |
wwstage: fold chained global-ptr field READ to cstage offset-fold (#16)
wwstage's chained-N_DOT resolver (dotchainresolve) didn't resolve a global
*struct root (only local *T and global value-struct), so gp.sf.len / gp.x.y
bailed to an inner-dot load + shuffle, byte-diverging from cstage's offset-fold.
Both stages already ran correct after #15 (
|
|||
| c814856550 |
wwstage: C-FFI variadic call codegen parity with cstage (#10)
Mirror cstage's C-variadic call handling in the ww self-host: parse a bare `...` param (decl.ww), skip param-keyed desugar for it to avoid a nil-deref (check.ww), and emit AL = XMM-reg count plus CVTSS2SD promotion of f32 args in the variadic tail (cgenutil.ww, cgenexpr.ww). Closes the cat-A wwstage silent miscompile (AL=0, unpromoted f32 tail). Parse/check/cgen are one atomic align-up (parse alone miscompiles, so not bisect-splittable). 989_ffivariadic now runs dual-stage (cstage ww + wwstage ww_ww), 12/12; w6c==w6c_ww byte-identical. Byte-id alone is blind here (the bootstrap calls no float-bearing C variadic), so the ww_ww runtime rows are the real net. |
|||
| 75a03a7d69 |
wcc: qualify all references to the syntax package (#75)
After the frontend consolidated into one syntax package (#74), wcc still referenced syntax symbols unqualified — residue of the old flat combined namespace, where bare refs resolved by accident. Under separate compilation Hare and Go both require the package qualifier, so those bare refs would not sep-resolve. Qualify every wcc reference to a syntax type, function, or enum member as syntax.X across the seven syntax-importing files. Resolution-only: the resolved symbol and emitted code are unchanged, so the two combined.ww regenerate textually but all five _ww binaries hold byte-for-byte. The struct-literal sites resolve via #76. This makes w6c fully separate-compilable. |
|||
| 7a8acfb952 |
lib/ww,wcc: consolidate frontend into one syntax package (Go-compiler model, #74)
The ww compiler frontend was split across packages lex (lex+tok), ww
(ast+sym+typ), and parse — mirroring Hare's ref/hare/hare/{ast,lex,parse}.
That split's only payoff is third-party reuse, which ww has zero of: the
frontend is consumed by exactly one client, the wcc backend. The split's
cost is a wide cross-package export surface — every fn over a sibling
package's type must export it, and under separate compilation that
re-triggers check_exported_type, plus a phantom `import tok;` (tok lives
in package lex). Consolidate into ONE package lib/ww/syntax/, modelled on
Go's cmd/compile/internal/syntax. The 9 files move in (package syntax);
the intra-frontend mutual references become same-package; wcc and the
tool mains import syntax. No cstage C change (the C frontend mangles from
the source package clause). Internal data shapes (AST kinds, token model,
lexer/parser state) still mirror ref/hare/hare per rule 6/12 — only the
module decomposition collapses; the stdlib is untouched.
USER-approved (#74); spec .ai/rob-frontend-reorg.md (drew2 fidelity-
confirmed). Rule-6 carve-out documented in CLAUDE.md. Dissolves the tok
phantom import; collapses the intra-frontend export sprawl. Byte-id
rebaseline (lex.X/parse.X/ww.X -> syntax.X); cs==ww held. The residual
syntax->wcc export surface (10 types) + the unqualified-ref question are
separate follow-ups (#72/#75).
|
|||
| f308818b4b |
wcc/ww: mangle imported symbols on dotted import path (#22 M1, #32)
Switch symbol mangling from the import leaf clause to the full dotted import path for directory packages; single-file imports keep package-clause mangling (isdir-gate: imported<=>directory-import). The root build unit's fn main stays bare, every other top-level decl mangles, closing #31's duplicate-main hazard by construction (#32). Both stages, byte-identical. Single commit, not split: the bare rename (f244af3) is red on its own because it unmasks cross-module resolution gaps that do not reproduce pre-M1, so the fixes are intrinsic to making the rename correct. Included: wwstage fnret/fnparamslookupmod map import alias->path (#199b cross-module union-variant scrutinee resolved the wrong fn's union); cstage use_path prefers the referencing module's import for an ambiguous leaf alias (sha256 crypto.math vs strconv math). Tests table-driven: 989_m1mangle_run/_sym, 989_m1union_run (gate-visible per-arm exit codes + cs==ww byte-id). |
|||
| 074e0e585e |
wcc/ww: struct-local stack slot is the checker's natural size, not slot-padded
wwstage's slotsize() shared its TY_STRUCT arm with TUPLE/ARRAY and
returned ti.slotsize — the SUM of the slot-padded field widths. For a
struct LOCAL that over-reserves the frame slot whenever a field is a
sub-8 nested composite: a nested inner{x:u8,y:u8} (size 2, slotsize 8)
pads its in-struct footprint, and the local inherits that pad. cstage
has no slotsize SSoT — it reserves the local at f->type->size, the
checker's NATURAL r.size (cmd/w6c/cgen.c). So on outer{a:u8,
p:inner{x:u8,y:u8}, z:i64} wwstage emitted frame $32 / struct-base
-24(BP) while cstage emitted $16 / -16(BP): a uniform -8 BP shift on
every field access. Both stages exit 0 (each self-consistent), so it is
runtime-invisible — but it is a cs!=ww .s divergence (rule 10) and a
latent byte-id gate-landmine the day such a struct enters the corpus.
Same dual-SSoT leak as #44 (field-OFFSET) / #55, one notion over:
struct-local-slot-SIZE.
Fix: split the TY_STRUCT arm out and return round8(ti.size). The TUPLE
arm (8B/elem slot, user ruling #60) and the ARRAY arm (element stride,
#48 [N]Alias 24B) keep ti.slotsize — those are deliberate, ruled
divergences and are untouched. The struct-local slot consumers
(cgendecl.ww letslotsize via cglet, cgenstmt.ww) all flow through this
arm; si.totsize (registerstruct → structabisize / global-emit) is a
separate consumer and is not this path.
CLASS-N corpus-neutral: every corpus struct local is 8-aligned, so
round8(ti.size) == slotsize for all of them and the w6c_ww/wwdump_ww
emission does not move (994 byte-id on 18 corpus inputs + 995 5-tool
self-rebuild both green post-fix). 989_structlocal_frame is the
FRAME-ABSOLUTE proof (w6c vs w6c_ww .s byte-diff; nested3 + tail_u32 +
flat control) — the .s twin of the runtime 989_nestfield_run, which
deliberately does not gate the frame and points here for it.
|
|||
| 037d59cf4e |
wcc/ww: registerstruct field offset is the checker's natural tfield.offset
wwstage carried TWO struct-layout sources. registerstruct (cgenutil.ww)
recomputed each field's `fi.foff` via fieldsize — slot-padded, round-8 —
for the WRITE (construction / field store) path, while the READ path
(cgplaceaddr / dotbaseaddr) used the checker's natural `tfield.offset`.
They diverged iff a struct had a nested sub-8 composite field
(slotsize != size) plus a successor: ww wrote the successor at the
slot-padded offset and read it at the natural offset, mis-addressing its
own field. cstage has no structinfo and reads tfield directly, self-
consistently natural (cmd/w6c/cgen.c).
Fix: make `fi.foff` a VIEW of the checker's already-built natural layout.
Lock-step walk tstruct.list (AST N_TFIELD) and ti.fields (tfield) — both
head-first declared order, both skip non-TFIELD identically — and copy
foff = tf.offset, fsz = tf.type_.size. si.totsize keeps the slot-padded
stack-slot number (ti.slotsize, already 8-rounded at check.ww:2259).
fieldsize is no longer called here (its `*p OP=` scalar-width caller is
untouched). LOUD nil-guards on tstruct.type_ / tichase / a tfield walk
desync — all unreachable post-check, never silent. fi.tnode stays the
AST node (its node-keyed readers need it); repointing the ~60 fi.foff
readers to tfield is the out-of-scope (ii-b) follow-up.
This unifies ww's second source onto the value cstage already emits, so
cs==ww is preserved, not newly created (wwstage-cgen only; no cstage
edit). The shape is corpus-absent — ww uses both sources on its own
structs, so a divergent struct would have broken the bootstrap — hence
gate-blind; 989_nestfield_run is the proof (nested inner{x:u8,y:u8} in
outer{a:u8,p:inner[,z:i64]}, every field read back == written, dual-stage
cs==ww). It also makes 681 ragged_tail_12B genuinely correct: the
predecessor #71 already shrank the whole-struct copy to the source's
natural length, so packing mark at natural offset 12 no longer clobbers.
|
|||
| ffe37deaee |
wcc/ww: def-dim array slice-arg default-hi takes the length from the type table
Passing buf[1:] of a [MAX]u8 as a call arg dropped the default-hi length (the arg-push N_SLICE arms' fallback covered only named-alias bases). Same type-table resolve at pushargsrev local+global. This closes the def-dim dimension family by construction: every N_INTLIT- keyed dim consumer (cgslice, cgdot, letemitsize, arg-push) now carries the tichase().alen fallback — grep-proven, no consumer remains. Fourth member surfaced by the family grep. |
|||
| ef7c0c1675 |
wcc: widen-push spills the float payload from X0, both stages
Widening a runtime f64 into a tagged slot pushed a stale AX as the payload while the value sat in X0 — both stages shared the push bug (float literals dodged it because TK_FLOAT loads AX too); the divergent pop sides then produced different garbage. Spill the payload from X0 (MOVSD) with the variant tag. Review item #49. Both stages move in one commit: one emission contract; splitting would leave the byte-id gates red between the halves. |
|||
| a4a4cd7c16 |
wcc/ww: cgcall drain pops widened GP words before the float arm
The arg-drain loop checked node_isfloat before popping a widened arg's GP words, so a float arg adjacent to a widened (tagged) arg read the wrong stack slot: the f64 took the widened payload (#30), or the float arm ate the tag word into X0 and the payload landed in DI as the tag (#48). One missing branch, two manifestations — mirror cstage's widen-first pop (cgen.c:9650-9665). Review items #30+#48 (fold reviewer-verified one-mechanism against the cstage twin). |
|||
| e416885d96 |
wcc/ww: widen of a module-global tagged ident copies the whole box
Widening a global tagged union into a wider tagged slot copied nothing of the box; treat the global ident as a tagged source and copy the full box from g(SB) through the nested arm. Both-wrong pair: cstage spills frame garbage as the box (filed task #44, residual non-deterministic so the rows assert divergence only). The non-nested SUBSET-widen shape remains open as task #49 (site comment at the fall-through). Review item #51. |
|||
| 32848194d7 |
wcc/ww: widen of a module-global struct ident copies the full payload
Widening a global struct into a tagged slot copied word0 only; copy the full payload from g(SB). Both-wrong pair: cstage zero-fills the payload (filed task #43); rows pin ww-runtime-correct with the documented cstage residual. Review item #50. |
|||
| 1eceb46ac3 |
wcc/ww: global-str default-hi slice arg loads its len word
Slicing a module-global str with default hi emitted nothing for the bound; load the len word from g(SB)+8 (mirror the cstage twin). Review item #47; dual-stage rows red-proven. |
|||
| e0df2adf47 |
wcc/ww: tagged-union normalization at tinfofornode (never-drop, dedup, collapse, nullable fold)
wwstage computed tagged sizes/tags off the raw variant list — size() folded wrong constants (size((*u8|void)) 16 vs 8, (i32|never) 16 vs 4) and duplicate variants got divergent tag numbering vs cstage, while ww's own cgen layout folded nullable but its size() didn't. Make tinfofornode's N_TTAGGED arm the normalization SSoT mirroring cstage resolve_type (check.c:801-882): never-drop, duplicate dedup via structural typeeq, single-variant collapse, nullable fold on the normalized pair; astsize/astalign delegate, and voidvariantindex reads the normalized ti.params (cgen.c:900-911) so construct/match/void tag readers agree. Corpus-neutral (zero-move on all combineds); 989_tagnorm_run pins the folds dual-stage, red-proven. Review items #1/#3; residual #45 filed (AST-keyed nullable gate at global emit). |
|||
| 0625af1309 |
wcc/ww: nodeisunsigned resolves module-global idents via the stamp
A bare module-global unsigned operand got signed IDIV/SAR/Jcc — the predicate's ident arm only consulted the local table, so globals fell through to signed (review finding #25). Read the stamp for the global arm; corpus emission is unmoved (no bootstrap code div/shift/cmps a bare unsigned global). 989_gunsigned_run pins cs==ww (red 3/8 pre-fix). |
|||
| cbbfd1a4a6 |
wcc/ww: nodeisslice/nodeisstr gain the N_INDEX arm (stamp-keyed)
A slice/str ELEMENT of an indexed expression passed as a call-arg pushed one word instead of the 24B/16B header — the predicates had no N_INDEX arm, so element-typed args fell to the scalar path (review findings #45/#46). Read the element-type stamp; dual-stage rows in 989_idxarg_run pin cs==ww (red 2/8 on pre-fix binaries). |
|||
| 6afa72afea |
wcc/ww: nodeisslice/nodeisstr local idents read the checker stamp
Collapse the localfindnode tnode read to the stamped n.type_ — a zero-delta mechanic validation for the F7 predicate type-keying arc: self-compile byte-id holds on all four bootstrap combineds, proving n.type_ matches the local-table truth on every corpus local before any behavior-changing conversion lands. The localfindnode gate stays so a global-not-def ident still routes false. |
|||
| 7e19d282f4 |
wcc/cgen: S1 loud-stop subset-cast tagged widen in let+return (wwstage align to cstage #35)
A widening sub-union cast (return/let `v: inner` into a union whose member is the nested `inner`) skipped the nested widen arm and emitted tag=0 (a 2nd-variant value returned the wrong payload). cstage loud-stops #35; wwstage now matches: cgwidentaggedstorebp louds on the subset cast, and cgreturn routes genuine-widening tagged returns (rhs TY_TAGGED, ru1!=fu1) through the widener choke-point so the same loud fires. Same-type returns stay on the proven passthrough. Faithful inner-tag->outer-index remap deferred. test/wcc/835 S1 row. |
|||
| f1cd0a3555 |
wcc/cgen: #21 def-str/slice by-value arg pushes all 3 header words (wwstage)
Recognizer flip: nodeisstr/nodeisslice type-key a def ident (was local-keyed → fell to scalar default PUSHQ, dropped len/cap). Mirrors cstage node_isstr/node_isslice; push/pop stay symmetric via the shared recognizer. wwstage-only; cstage already correct. |
|||
| d8e2a0692c |
wcc/cgen: #15 empty zero-length array emit — no spurious DATAW, cstage frame formula (wwstage)
An empty zero-length array diverged cs!=ww in asm (both ran correct=7): a [0]int global emitted a spurious DATAW main.X(SB),"", and a [0]int local reserved a $16 frame slot. cstage emits neither. wwstage-only, byte-id-only. The global DATAW emit is now gated on sz > 0 (skips the empty array). The local frame: localreserve dropped its sub-8 floor (if asz<8 asz=8) to mirror cstage's localslot formula (frame+sz+7)&~7 -- but that floor was MASKING slotsize(TY_VOID)=0 (a void local), which cstage defaults to 8B; removing the floor alone collided the zero-size void slot with a spilled param (a real miscompile -- 1132 self-compile hunks). So letslotsize now returns 8 for a void local, while empty-struct / [0]-array stay genuine 0. The frame formula is byte-id-neutral for every sz>=1 local (round8 already >= 8); only true zero-size cases change. cstage unchanged (w6c md5 unchanged). byte-id 990-997 8/8 (the full self-compile is what caught the void-local class); test/wcc/820 un-carves the #9 empty-[0] byte-id exclusion + adds void-local/local-[0] rows. |
|||
| ee8082a43b |
wcc/cgen: #151 push all 3 header words for let-global slice/str by-value arg (wwstage)
A let-global slice or str passed by value as an argument was silently
field-dropped by wwstage: pushargsrev's global branch had only the #150-A
struct arm, and wwstage's nodeisslice/nodeisstr are local-keyed (false for
a global), so a global slice/str ident fell to the scalar single-PUSHQ,
pushing one of the three header words {ptr,len,cap} -> the callee read
garbage for .len/.cap. cstage was already correct.
wwstage-only, caller-side only (slice/str params already received
correctly). A global slice/str arm in pushargsrev, type-keyed on
tichase(arg.type_).kind, with two arms byte-matching cstage's two distinct
sequences -- str via cgslicehdr (CX-base, cgen.c:1866), slice per-word
(BX-base, cgen.c:9124). Gate is isletvar-only (a def has no name(SB)
holder; cstage const-folds it -- def-str/slice-by-value is the residual
task #21). cstage unchanged (w6c md5 unchanged); byte-id 990-997 8/8,
no lib pin flips. test/wcc/829 table-driven, byte-id per type.
|
|||
| 5c3764828f |
wcc/cgen: #150 by-value module-global struct-arg base — load main.g(SB) all words (both stages)
Passing a module-global struct by value -- let g: pt = pt{...}; take(g)
-- was silently miscompiled, mirror-opposite on the two stages. cstage's
by-value struct-arg arm hit localfind(g)->0 and read 2 words from the
frame (MOVQ (BP)), never main.g(SB) -> returned garbage. wwstage used the
correct main.g(SB) base but fell through to the scalar single-PUSHQ
default, pushing one word for a 2-word struct -> dropped a field.
Both stages now take the off==0 global branch: LEAQ main.NAME(SB) and copy
all struct-size/8 eightbytes (reusing the GAP-A.ptr/#231 global-base
predicate), converging to one byte-identical sequence. The local path
(off!=0) is unchanged; >16B aggregates (#271) already resolved globals.
Commit A of the cluster; the cstage-only inferred-global-type Sym-repoint
(every let g = ... module-global yields <nil> downstream) is Commit B
(#18). Slice/str global-by-value args have the same wwstage field-drop --
filed (#10 G-valglobal-arg; struct closed here). byte-id 990-997 8/8.
test/wcc/822 table-driven, byte-id per stage.
|
|||
| 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.) |
|||
| f8be2ae8dd |
wcc/cgen: #116 non-literal tuple source into a tagged box (both-stage)
cg_widen_tagged_store only handled a tuple LITERAL (N_TUPLE / cast-of-
N_TUPLE) widened into a tagged box; any addressable non-literal tuple
source -- IDENT var, INDEX tbl[i], DEREF *p -- hit the `else fatal`
("tuple-typed source shape unwired"). Both stages loud-identical
(honest, no silent miscompile). This blocked indexing a const tuple
table into a union (regex charclass_map[i] -> charset union).
Add an addressable-tuple-source arm, both stages (cgen.c +
cgenutil.ww twin). It resolves the source address via the cgplaceaddr
place-spine (covering ident/index/deref -- one mechanism, so the trio
is family-closed) and block-copies the tuple's type-table ->size bytes
into the box payload (after the 8B tag), then stamps the variant tag.
No re-slotting: a tuple's in-memory layout uses the same eslot strides
(str=24B header, *fn=8B, ...) as the box payload the literal loop
fills, so source-layout == dest-layout. The existing narrow-pack and
tag-unresolved guards stay as the honest boundary; CALL/sret tuple
sources (different receive, #68-kin) stay loud.
align-BOTH: both stages were loud (no runtime reference), and byte-id
is structurally blind to an identical-wrong emission -- so correctness
is proven by a RUNTIME read-back pin (944_nonlit_tuple_widen_run, per
shape: match-extract + assert str header + call the fn-ptr elem with
distinct fns so a stale pointer is caught). 936's old reject row
graduates to a run row. Both stages byte-identical (990-997 green).
|
|||
| 6a5bb3efc9 |
wcc/cgen: #47 gap-A tuple-in-union tagged-element store (both-stage)
A tuple containing a tagged-union element, used as a union member (e.g. ((void|size),(void|size),size) | error), loud-stopped in the cgen return-store: the tuple-in-union store walk had scalar/float/ str/slice element arms but no TY_TAGGED-element arm. A PLAIN tuple-in-union already worked -- the blocker was the tagged element. Add the recursive two-level widen arm at both stages (cg_widen_tagged_store / cgwidentaggedstorebp): for each tagged element, re-enter the tagged-box store (inner tag@slot+0, payload@slot+8) at the element's tuple-payload offset, then stamp the outer tuple tag. Slot strides come from the type table (roundup8(eu->size)) -- the checker already sizes the shape correctly (tuple->size measured 40, union box 48; check.c:715-720). The recursion descends a finite type tree (a tagged element is never a tuple literal, so it can't re-enter the tuple arm); unsupported deeper nesting still louds via the existing size/tag guards. Both stages get the same arm -> byte-id (990-997 green; additive, bootstrap-neutral). cstage runs the full b1c shape (construct+return+match-extract) as the runtime reference; wwstage's store rides on byte-id until gap-B. gap-B (wwstage checker match-acceptance of the tuple-with-tagged case pattern) is a separate commit -- wwstage still louds the match honestly at the checker. Pin 944_tuple_tagged_union_run. |
|||
| cc896bd078 |
wcc_ww/cgen: #55 tagged-source arg-widen into wider tagged slot (align-up)
wwstage pushargsrev treated a narrower tagged-union argument widened into
a wider tagged param slot as a concrete variant: taggedvariantindex<0
clamped the tag to 0 and pushed word0 only (deref/index/dot silent-wrong;
ident ran correct only by prefix-union tag-index luck). cstage is correct
(cg_widen_tagged_push routes src_is_tagged unconditionally); align ww UP.
Three arms in cgenutil.ww, all mirroring cstage cgen.c:
- slot-gate the ident aistagged short-circuit so a slot-differ tagged
ident falls to the widen path instead of the raw 2-word push;
- route a tagged source in the widensz>0 arm through @tagscr +
cgwidentaggedstore + push high->low (cgen.c cg_widen_tagged_push);
- cgwidentaggedstore cursor arm (<=32B INDEX/DOT) spills by source
width, zero-pads, and tag-remaps (cgen.c 2698-2714) — was dst-slot
spill of stale high regs with no pad and no remap.
Same-slot tagged->tagged is byte-id-neutral by construction (empty pad +
identity remap). cstage untouched; 4 legs x {aligned, misaligned-tag}
converge ww->cs byte-identical. Pin 944_tagged_widen_arg_run.
|
|||
| 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. |
|||
| 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 |
|||
| 56aac85f6f |
cgen: #95 c2 structural variant fallback — both-stage fused
A nominally-unrelated, structurally-equal NAMED source into a NAMED
variant (kb95_unrel: ta/tb same-layout structs, src ta -> (void|tb))
was LIVE both-wrong-identical byte-id silent: both checkers accept,
both cgens tagged 0. After c1's chain arm finds no shared chain
node, match the variant whose CHASED type type_eq's/typeeq's the
source's chased bottom — chased type EQUALITY only, no
type_is_assignable scalar import, no int widening (ken's binding
scalar warning). Same NAMED-source branch, both stages
(cg_tag_for_variant / flatvariantidxt), forced fuse.
Correctness reference, cite 1 — harec tagged_select_subtype P2+P3
(ref/harec/src/types.c:702-739), verbatim:
if (t->id == subtype->id) {
return t;
}
if (type_is_assignable(ctx, t, subtype)) {
selected = t;
++nassign;
}
...
if (nassign == 1) {
return selected;
}
return NULL;
with type_is_assignable's non-tagged path dealiasing both sides and
accepting composites only via interned pointer equality
(types.c:988-1002), verbatim:
if (type_dealias(ctx, to)->storage != STORAGE_TAGGED) {
to = type_dealias(ctx, to);
from = type_dealias(ctx, from);
}
...
if (to == from && to->storage != STORAGE_VOID) {
return true;
}
Cite 2 — type_hash interns bare composites STRUCTURALLY (banked as
types.c:72-81; verified in the vendored copy at types.c:444 +
struct/union arm :514-525), verbatim:
case STORAGE_UNION:
hash = fnv1a_size(hash, type->struct_union.packed);
for (const struct struct_field *field = type->struct_union.fields;
field; field = field->next) {
if (field->name) {
hash = fnv1a_s(hash, field->name);
}
hash = fnv1a_u32(hash, type_hash(field->type));
hash = fnv1a_size(hash, field->offset);
}
— no decl ident in the hash, so harec's two decls dealias to ONE
interned node and `to == from` holds: acceptance is DEFINITIONAL
under interning, not an arm whose text could be misread. Our store
does not intern; chased type equality is the non-interned rendering
of the same rule.
Honest divergence (the >=2-structural-match hard-error STAYS): under
harec's interning two structurally-identical variants are ONE type —
a union cannot contain it twice — so the ambiguity case is
unrepresentable there; our hard-error (twin texts, shared tail
"source structurally matches >=2 variants — ambiguous without
nominal layout (#95)") is the correct nominal-lossy-model rendering,
not a harec deviation.
Pin table: unrel_struct row added (kb95_unrel graduates ok/1-ok/1 ->
0/0, byte-id held) — suite now 48/48. All c1 rows unmoved.
Invariants: 163-row dissolution matrix at tip — same 3 family
graduations as c1, ZERO new movers; five mains cs-vs-ww byte-id OK;
make all 0; sizelint 0; peellint 0 (no new peel sites — the
structural leg reads only chased ends); all 944 suites + 808 green.
w6c_ww/wwdump_ww main.combined.ww regen'd.
|
|||
| 34c86bd681 |
cgen: #95 c1 chain-membership variant arm — both-stage fused
A NAMED struct source that was not pointer-identical to a NAMED variant fell through every pass of cg_tag_for_variant (cmd/w6c/ cgen.c) / flatvariantidxt (selfhost/cmd/wcc/cgenutil.ww) and the widen stored tag 0 — both stages, byte-identical, gate-blind: wrong tag on VALID code at any alias depth, in both chain directions (.ai/ken-95-oracle.md §2: kb5_v2s1i, kb95_2lvl_i, kb95_deep_src, kb95_deep_var all both-wrong-identical at base). New pass 1b, identical both stages (the same route — forced fuse): after pass-1 exact (unchanged, FIRST — the (str|linerr) protection, harec's P1 short-circuit), a NAMED source matches the variant whose NAMED chain shares a pointer-identical node with the source's chain (an alias IS-A its base through the chain). Two linear NAMED chains intersect iff they share their chased bottom node (ken §1), so the walk is implemented as pointer identity of the chased ends through type_chase_named/tichase — the blessed chase choke-point. NO raw .under/->under hops were added, so the anticipated `peel-ok: nominal chain walk (#95)` annotations are unnecessary and the peellint whitelist is UNCHANGED (continues the B6/B7 fold-peels-into-chase arc; peellint green). Variants are counted UNGATED (bare prims are type-table singletons, so a bare variant node can BE the source's chased bottom): the >=2 guard stays equivalent to harec's nassign>=2 -> NULL (ref/harec/src/types.c:734-738, tagged_select_subtype P2/P3). >=2 chain hits hard-error with twin texts (prefix convention, shared tail "source alias chain reaches >=2 variants — ambiguous without nominal layout (#95)") — drew's ambiguity proviso extended to the chained set; was a SILENT member-0 tag. Pass-2 bare-source fallback unchanged. Chased type EQUALITY only — no type_is_assignable scalar import, no int widening (ken's binding scalar warning). Pin table (new suite test/wcc/944_variant_chain_b95_run.c, 45 checks, Makefile-wired): GRADUATIONS exit 1->0 both stages: chain_1lvl_i (kb5_v2s1i HEADLINE, byte-id held), chain_2lvl_i, chain_deep_src, chain_deep_var (byte-id held), chain_call_bound81 (kb5_v2s1), chain_call2_bound81 (kb4_v2_struct2, #95's original) — the two CALL-src rows waive byte-id, pre-existing #81 zero-fill asm noise (NO at base too). NEW LOUD: chain_amb_loud (kb95_amb) — silent tag 0 -> hard-error both stages. MUST-NOT-MOVE held: chain_amb_srcA/B (pass-1 precedence), nom_str/nom_err (#218 nominal regression pin), exact_ctl (kb5_v2sE2), bare_ctl/bare_2lvl/bare_ambig/bare_ambig2 (pass-2 controls), callret_bound277 (kb5_v2sE #277 cells unchanged, dual-cell pin). Invariants: ken's 163-row dissolution matrix rerun — exactly 3 movers, all family graduations (v2s1i/v2s1/v2_struct2 1->0), zero non-family movers, detectors unmoved. Five mains cs-vs-ww byte-id OK (ww/w6c/w6a/w6l/wwdump). make all 0; sizelint 0; peellint 0; all 944 suites + 808 green. w6c_ww/wwdump_ww main.combined.ww regen'd (cgenutil.ww embeds). |
|||
| 4b118fa8f8 |
cgen: B7 emitter elem chases + tools/peellint gate — #5 alias-arc cs side closed by construction
The last four raw `->under` reads outside the whitelist were the
static-DATA emitters' ELEMENT-type single peels (the outer type already
chased): emit_array_lit_bytes:14356, emit_strarray_data:14574,
emit_slice_data:14788, let_pre_intern:15088 -> type_chase_named.
:15088 is the :14574 row's label-order leg and must flip in the same
commit or _S_ labels intern in emit order, not decl order (the in-tree
comment at the site); the strarr row's byte-id is the coupling proof.
Behavior moves (ken B7 first-position oracle + impl pre-state, all
pre-observed at
|
|||
| 4cac1cbb89 |
wcc_ww/cgen: W1 #100 struct-lit fill gate keys off the chased stamped tinfo
The ww twin of B5-c1's :3195 cg_structlit_fill chase — closes the R4-blessed one-train acceptance divergence (cs-loud / ww-SILENT-WRONG on alias-element array fills, ken FLAG-3 / kb5_fill2). The #249 fill arm's loud-gate element classification was alias-blind on two of four keys: a raw N_TSLICE kind test and a structlookup leaf-name probe — `type el = el0;` matched neither, bypassed the gate, and fell to the scalar tail (one word per element, silent exit 1, gate-blind #263 class). Now: issliceel via isslicetype (stamped tinfo, the str/tagged keys' existing route) and isstructel via tichase + TY_STRUCT kind — the tnodeisagg discipline the neighboring #49 arm already uses, twin of cstage type_chase_named at cgen.c:3179. NO new helper. Loudness-alignment only, zero acceptance movement elsewhere: probe matrix holds direct str/slice/struct/tagged elem LOUD (twin texts), int/alias-int/f64 elem fills build + run 0/0 byte-id, 1- and 2-level alias struct/slice/tagged elems now LOUD both stages. ww-only: cs side untouched (zero C bytes; w6c up-to-date across the edit). B5 detector rows hold post-B5 expected state (targ/tmem/wpush/ null/succ/f32p/def93 all 0/0 byte-id). 989 ratchet ZERO flips (31 ID / 9 pinned-divergent / 3 pinned-wwreject). combined.ww regen'd for both embedded targets (w6c_ww, wwdump). 944_alias_cgen_b5_run: fill2 graduates the dual-cell K_CSERR_WWRUN pin to the K_BUILDERR LOUD-HOLD pair (fill0-class, shared experr tail); fill0 control holds. Check count holds at 84/84 — the kind flip is cell-count-neutral (2 driver cells either side, no byte-id cell). All 944-family suites green; sizelint 0; test-unit all 291 passed. Closes #100; the R4 divergence window closes with it. |
|||
| 4e174099bd |
cgen: c2 variant pass-2 structural fallback chases the full NAMED chain — both stages fused
F2a batch-4 c2. Site: cgenutil.ww flatvariantidxt pass-2 (was :2895 at |
|||
| b26b3dbdc5 |
wcc_ww: c1 collapse plain TY_NAMED chase loops into tichase across cgenstmt/cgen/check — byte-id neutral
F2a batch-4 c1, mechanical, per rob's spec census (re-verified at
4adf914; check.ww/cgenstmt.ww cites were stable as predicted, the
two cgenutil [B3-SHIFT] cites moved +8: :2746->:2754, :2895->:2903).
42 conversions, every site eyes-classified plain nil+NAMED:
- cgenstmt.ww 26: one-line :149 :325 :352 :532 :551 :594 :715 :732
:938 :940 :1577 :1629 :1782 :2230 :2575 :3372 :3678 + multi-line
:986 :1912 :2287 :2291 :2702 :2759 :2823 :3037 :3715 (all plain,
no extra guards).
- cgen.ww 13: one-line :1505 :1541 :1658 :1691 :1697 :1947 :1951
:2065 :2109 :2118 + multi-line :2239 :2648 :2663 (incl. the
emitdefconstants struct/array arms — plain).
- check.ww 3: :1627 tupleelemslot, :1673 fieldslotsize, :4111 arrlit
elem chase; WHY comments kept. tichase visibility verified: same
`package wcc` (cgenstmt/cgen already call it) — no second helper.
Survivors (classified, untouched):
- cgenutil.ww :1301 tichase's own body (the accessor) + :2754
nullableptrtag single peel (batch-2 c3-B2 PROBE-CLEARED,
|
|||
| 4adf914f2f |
wcc_ww/cgen: #92 rhsstructpayload N_STRUCTLIT arm through structlookupchain
The N_STRUCTLIT arm still did bare structlookup on the literal's type
name — an alias-named struct LITERAL widened into a union (`type ali =
base; let v: (void|ali) = ali{...}`) missed the registered structinfo
and fell to the scalar widen arm: word0-only/dropped payload, cs 0 /
ww EXIT 1, byte-id NO (reviewer-B2 find, pre-existing at 486f7f8's
parent). Exactly the class batch-2 c2 closed for the N_IDENT-local arm
of the SAME function; same funnel fix — route through structlookupchain
(cgenutil.ww:1758) and return si.sname so every consumer's re-lookup
hits the REGISTERED name.
One pre-authorized rider: structlookupchain's entry gate accepted only
N_TNAME, but a struct literal's type ref parses as N_IDENT (expression
position, lib/ww/parse/expr.ww builds s.lhs as N_IDENT — OBSERVED).
Widened to `N_TNAME || N_IDENT`; the entry-kind predicate is spelled
twice (the early-return and the chain loop's first iteration — the
loop reassigns cur to aliaslookup results, which are always N_TNAME,
so later iterations are untouched). Consumer census, all 10 call
sites: cgenutil:3187(+this arm; post-commit position), cgenexpr:2927/3006/8775/9011,
cgenstmt:3033 guard on N_TNAME explicitly before calling;
cgenexpr:5756 (etnode = type-spec .lhs), cgenstmt:1978 (arrtn.lhs or
the synthesized #79 N_TNAME), cgenstmt:2695 (cglet n.lhs declared
type spec) pass parsetype products only — parsetype builds zero
N_IDENT nodes (lib/ww/parse/parse.ww). No existing caller can pass
N_IDENT: the widening is strictly accepting-more; existing callers
unmoved. NO second inline chase in the arm (close-by-construction).
Base spellings byte-id: structlookup hits at the chain entry and
si.sname == the literal's own name — same string out, same asm.
Pin: 944_alias_accept_run union_slit_{base_ctl,alias,alias2,order} —
base control holds 0/0; 1/2-level alias + permuted decl order graduate
cs0/ww1-byte-id-NO -> 0/0 byte-id. #63 PROBED post-c3 per the
enrollment rule: does NOT green at this site (8B alias struct-lit
let-init still ww silent-zero-fill exit 1; 16B still loud "aggregate
init from unhandled rhs shape") — its miss is the cgenstmt let-init
dispatch, not rhsstructpayload; documented on task #63, left red, no
row enrolled.
Light gates: test-unit 290 green; sizelint 0; 989 ratchet zero flips;
five-mains NEUTRAL vs master-74195ac scratch on identical inputs +
cs==ww on all five. combined.ww regens ride along (#110).
|
|||
| 74195ac4dc |
wcc_ww/cgen: c4 tyassignableuntyped full-chase — structural alignment, zero acceptance graduations, graduations ride #90
F2a batch-2 c4, per rob's FINAL RULING (i) (banked, .ai/rob-batch2-spec.md):
ww-only — the cs twin (cmd/wcc/type.c:369-385) is already harec-shaped
since F1
|
|||
| 018ef6680e |
wcc_ww/cgen: c3 in-file peel sites — exprprimresolved base walk chases (B1); elemsizeofc alias-indexable leg (B3); B2 probe-cleared
F2a batch-2 c3, probe-decided per rob's spec. B1 (exprprimresolved N_DOT, was :2073/:2075/:2078): the hand-rolled NAMED->PTR->NAMED->STRUCT walk ran out at a 3-level alias base or a ptr-to-2-level base — the cast-source width stayed unknowable on wwstage only, so the #33 identity clamp was emitted where cstage skipped it (runtime-correct both, byte-id NO, gate-blind). cs twin castsrcprim is F1-chased at both hops (type_chase_named) -> ww-only align-up, no fuse. Probes: b1b_3lvl + b1b_ptr2lvl graduate 0/0 byte-id; 1/2-level controls held throughout (the 2-peel covered 2 levels by accident); the FIELD-u chase is asm-neutral (cs keeps a single peel there, sound via type_isint NAMED recursion + NAMED size carrying the underlying's — probe b1c_fld2lvl byte-id). 944 rows castprim_3lvl_base / castprim_ptr2lvl_base / castprim_2lvl_base_ctl. B2 (nullableptrtag variant scan :2719): PROBE-CLEARED, NO FIX. The 2-level ptr-alias nullable variant (b|void), the order-swapped (void|b) that defeats the return-0 fallback, AND 3-level all run 0/0 byte-id both stages — the constructible variant params never carry a 2+-level NAMED at this scan. cs twin nullable_ptr_tag (cgen.c:747) has the identical single peel: parity, both correct on every constructible shape. NOT nominal-sensitive (shape-keyed first-PTR scan, no name choice exists) — the spec's #209/#211 hold condition does not trip. The single-peel if stays, classified survivor. B3 (elemsizeofc): an alias-NAMED INDEXABLE (`type grid = [3]cell`) arrives as a bare N_TNAME — elemsizeof's name arm knows only str/prims and answered the 1-sentinel through the `direct != 8` short-circuit (#60 esz-1 family, outer-array leg). New N_TNAME leg answers from the chased stamped tinfo via idxeffti. #83 caller probes (rule: no unprobed caller row): - append leg: UNREACHABLE on ww — `let xs: nums = [];` rejected by the wwstage CHECKER ("let: not assignable") even at 1 level; cs accepts+runs. check.ww is batch-4 scope -> documented on #83. - alloc-empty leg: BOTH stages loud-reject the alias-typed binding hint ("cannot infer slice element type", check.ww:2547 + cs twin) — symmetric loud, alias-blind hint documented on #83. - arr[i].field= float leg: plain [3]cell runs 0/0 byte-id; the alias-named outer array LOUDS on ww at its own site (cgenexpr.ww:11033 assign-resolver float-field bound), cs runs — fails upstream of elemsizeofc, documented on #83. - subslice-of-alias-array call-arg probe: already 0/0 pre-change (the #60/#257 stamped-tinfo keying answers first) — the internal chase is presently LATENT defense-in-depth; ZERO callers enroll. Remaining elemsizeofc internal loops (eti/esub) fold to tichase. Byte-id: five-mains NEUTRAL vs the c2 build on identical inputs; cs==ww on the regenerated five; 944 138/138; test-unit 288 green. combined.ww regens ride along. |
|||
| f7fd3e4850 |
wcc_ww/cgen: #62 Layer-2 (ww half) — rhsstructpayload chases the alias chain; widen-store su at entry
The wwstage tagged widen's struct-payload classify was name-keyed: rhsstructpayload's N_IDENT arm did a bare structlookup on the local's declared TNAME, so an alias name (ali->base) missed the registered structinfo and the value fell past the struct arm to the SCALAR widen arm — word0-only box payload, words 1+ zero-filled. At normal decl order this was BOTH-WRONG-IDENTICAL with cstage pre-F1 (byte-id YES, gate-blind; F0 m5b_match1 exit 2/2). Choke-point fix: route the arm through structlookupchain (the name-domain twin of cstage's su = type_chase_named, |
|||
| f19d0cb14d |
wcc_ww/cgen: collapse cgenutil.ww's plain TY_NAMED chase loops into tichase — byte-id neutral
Mechanical sweep, F2a batch 2 commit 1 (alias arc #5, rob batch-2 spec c1; same conversion rule as batch-1 |
|||
| 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). |
|||
| 9fad59354f |
cgen: #33 wwstage untyped-source variant match — mirror cg_variant_match's type_assignable arm
A bare untyped init into a tagged union resolved its variant through
taggedvariantindext's str/slice SHAPE fallback, whose first
non-str/slice variant can be void: `let e: (void | size) = 5` stored
tag 0 while the is/as side resolved size to 1 — runtime-FALSE is-test
on wwstage only (cstage resolves untyped sources in cg_variant_match
:801 via type_assignable; cs=0/ww=1 on ken's f33, divergent asm,
gate-blind).
Fix adds the untyped-source arm at the top of cgvariantmatch — the
single flatvariantidxt pass-1 predicate, same funnel position as
cstage — backed by tyassignableuntyped, a focused tinfo-keyed mirror
of type_assignable's untyped→typed subset (cmd/wcc/type.c:355-370)
plus its concrete→tagged variant drill (:316-324). Typed/loose
sources keep the shape fallback unchanged; tuple-in-union keeps its
AST-shape loud-stops (#241/#242, checked: TY_TUPLE is never untyped).
Probes converge byte-id: f33 let-init, assign-after-void, bool-leading
skip, bare arg widen, untyped-str, cast no-drift control. 938 row
untyped_int_bare_widen pins all of them (pre-fix ww_run=1 at
|
|||
| 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
|
|||
| 413aafa599 |
w6c+w6c_ww: tagged-union struct-lit payload fills via the canonical fill (#23)
The widen choke-point's struct-payload arm carried its own inline N_STRUCTLIT field loop -- a parallel fill that drifted from cg_structlit_fill/cgstructlitfill: no tagged-field widen arm, so a (void|T)-typed field's raw scalar landed in the field's TAG word (silent truncation past the first tagged field, both stages, byte-id, gate-blind; prober-9 PG5). Delete both loops and delegate to the canonical fill at the payload base: one fill path, one widen path, mutually recursive. Inherits the nested-struct/call/arrlit field arms and closes a latent fsz==2 cs!=ww (old ww loop's fieldstoreop MOVW vs cstage MOVQ). Test 938: 15-row table-driven runtime readback (incl. ellipsis autofill, offset-0 tagged field, (void|str) payload, 3-level widen-fill recursion torture), all 13 bug rows silent-fail at master 6699158; 2 rows skip the byte-id check loudly (pre-existing match-on-tagged-FIELD readback cs!=ww, master-confirmed, separate family). |
|||
| 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. |
|||
| 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. |