Commit Graph

290 Commits

Author SHA1 Message Date
24ca570a7e w6c,ww: re-emit ... union-spread marker in .wwi producer (M4 E3, #95)
The N_TTAGGED serializer emitted each variant via wwi_type but never
re-emitted the `...` prefix for TK_ELLIPSIS spread variants, so an
exported `(...inner | str)` round-tripped through .wwi as `(inner | str)`.
The consumer's checker then could not flatten inner's members into the
alias and variadic-assignability rejected bare members — under separate
compilation this broke fmt/log/getopt. Re-emit `...` before the variant
type, both stages; the producer stays purely syntactic (flatten/dedup
remain the consumer's type-store job, per ref/hare/hare/unparse/type.ha:290-300).

Gate: test/wcc/989_wwispread_sep.c — table-driven (2-arm + 3-arm spreads)
x both stages, asserts the marker survives the .wwi, the consumer binds
bare members under --sep (exit 0), and cs==ww .wwi byte-identity.
2026-06-18 09:52:19 +09:00
08cfb5b2dd wcc,ww: bare-module fn mangles bare, not an imported same-leaf (M4 E2, #84)
A package-less primary's bare fn (module="") whose leaf collided with an
imported module's same-leaf exported fn was mis-mangled to the imported
qualified name (a user `fn run` emitted as `test.run`), producing a
dead-duplicate symbol the linker silently shadowed -- a #263-class silent
miscompile, gate-blind and symmetric across both stages. cgen now registers
bare-module fns and resolves a bare-ident reference to its own bare leaf:
mod_lookup_for_fn prefers the bare entry when the call carries no module
hint and skips bare entries when it does, so the moduled-caller path stays
byte-identical. The moduled `main` entry carve-out is an orthogonal rule
(the linker entry is force-bared) and is retained. Prereq for the @test
user-`run` coexist (#80). The bare non-fn (let/def/type) sibling is the
same class but hint-less; deferred as #85, noted at the retained skip.
2026-06-17 21:25:06 +09:00
10d005ef58 wcc/ww: serialize aggregate exported defs as value-less .wwi prototypes (BUG-2, #70)
The .wwi (separate-compile interface) producer could not serialize an
exported def whose initializer is a struct/array literal (N_STRUCTLIT/
N_ARRLIT) — `export def f64info: floatinfo = floatinfo{...}` aborted with
"unhandled const-expr node kind 15". Such a def is a DATA-global per the
#52 model, so its value lives once in the defining package's .o; the
interface needs only the type+symbol. Emit a value-less prototype
`export def X: T;` for aggregate-initializer defs; scalar fold-eligible
defs keep their value (the importer const-folds those). The parser gains
an optional-init arm so the importer can parse the prototype — value-less
`def X: T;` is now legal in any source, symmetric with the existing
bodyless-fn prototype `fn f();` (USER ruling: unconditional; a value-less
def with no defining .o is a loud undefined-symbol error at link, never
silent). Both stages; producer + parser fold into one commit (the
producer's output is unparseable without the parser arm).

M3-tail commit-6 prerequisite #2 (surfaced by the c6 scout). The
aggregate-def-field const-fold boundary is documented inline (#71). Gate
989_sepstructdef_run proves struct+array exported defs sep-build, link,
and run via external DATA refs, cs==ww, with a value-less .wwi.
2026-06-16 18:04:54 +09:00
0c4a5ecea0 wcc/ww: path-qualify exported decls + drop exact-or-bare value mangle (#53)
Under M1 mangling, EXPORTED non-fn decls (let/def/type) skipped path-
qualification and emitted a BARE symbol (`types.I64_MAX` -> `I64_MAX`).
Under separate compilation two packages exporting the same data leaf
would then collide at w6l. Masked in-tree only because no two packages
export the same non-fn leaf.

§7-A (USER-locked, harec's model): path-qualify EVERY exported decl
(fn AND data) at the single mangle choke-point — mod_collect /
collectmods. Retire the `!isfn && d->export` (cstage) and `exported==0`
(wwstage) skips: every decl with a module now mangles `<mod>.<name>`.
The ONLY bare symbols left are @symbol FFI overrides (ffi_resolve at
emit) and the ROOT unit's `main` — both already carved out before the
map insert.

With exported decls in the map the exact-(name,hint)-or-bare value
dance is dead — its sole purpose was the bare-exported case. Delete
mod_lookup_value / mod_mangle_value / mahint (cstage) and
modlookupvalue / emitsymnamehint (wwstage); the value-global sites now
route through the same hint-aware-with-fallback lookup as fns
(mod_mangle_fn/mafn, emitfnname). Net negative LOC in the mangler.

Transparent rename on the live combined path: ref and def move in
lockstep, so cs==ww byte-id holds and the self-host still builds + runs
(fixed-point/995). Byte-id REBASELINE — all 5 ww binaries shift. The
w6c/wwdump combined.ww embed wcc cgen and are regenerated.
2026-06-16 02:43:01 +09:00
f77739b1de wcc/ww: per-unit prefix on _S_ strlit labels (#49)
Strlit labels were emitted as `_S_<n>` from a global counter with no
per-unit prefix (cgen.c intern_strlit + wwstage internstrlit twin).
Under separate compilation two str-bearing packages both emit `_S_0`..
-> w6l link collision.

Prefix the label with the owning package PATH (`<module>._S_<n>`,
matching mklabel's spelling). The prefix is c->cur_mod, set per-fn by
cgfn and now per-decl by let_pre_intern (save/restore so the later
emit passes, which read cur_mod for fn-ptr relocs, are unaffected).
Pure function of the module path — NOT a build-nonce — so the
self-host fixed-point holds across ww2/ww3/ww4. Both stages, symmetric.

Transparent rename on the live combined path: the label is interned
once and shared by every reference, so ref and def move in lockstep.
cs==ww byte-id holds; the w6c/wwdump combined.ww embed wcc/cgen.ww and
are regenerated.

746_strdef_inline: the strdef-inline sentinel pinned the bare
`LEAQ\t_S_` shape; update to the module-prefixed form (alpha._S_ for
the in-module def, beta._S_ for the use-site-interned cross-module
inline).

989_m3sep_run: add the #49 LINK leg. The str sub-fixture (sleaf+smid)
was keystone-only — never linked — precisely because the global
counter made both emit `_S_0`. With the prefix, compile both `-c`
separately, link (w6l) + run (sroot reads a distinguishing byte through
each string's .ptr, so a collided label would corrupt the exit), both
stages + cs==ww final exe.
2026-06-16 01:36:14 +09:00
f69ef9b9da lib/types,wcc/ww: export the limit constants (#48)
lib/types limit consts were bare `def`s, so the .wwi (sep-compile's
interface) correctly omitted them while the flat combined.ww let a
cross-package user (lib/strings splitn → types.I32_MAX) reach the
private def — sep-compile then failed (wwstage `asserttyped: dot
'I32_MAX'`; cstage undefined-ref). Hare exports types::I32_MAX and the
whole limit family (ref/hare/types/limits.ha, arch+x86_64.ha); ww not
exporting them was the divergence.

export the 24 existing limit defs ({I,U}{8,16,32,64}_{MIN,MAX},
INT/UINT/SIZE/UINTPTR_{MIN,MAX}) and the existing RUNE_MIN, and add
exported RUNE_MAX. ww's derived machine-word int/uint/size/uintptr
VALUES are kept verbatim (user-ratified 64-bit-int divergence); fidelity
here is the NAME SET + export-visibility, not the values. RUNE_MAX is
written `0x10ffff: rune` — same codepoint as Hare's '\U0010ffff', forced
because ww's lexer has no \u/\U escape (#50).

Exporting the consts made `w6c -I` walk them and fatal on RUNE_MIN
('\0'): the .wwi const-expr unparser had no N_RUNELIT arm. Add one,
both stages (wwi_rune / wwirune), rendering a \xHH-escaped rune literal
(>0xFF fails loud, #50). Const casts need no arm — the checker folds
them to integer literals before the producer runs. 989_m2wwi_run gains
a types.wwi gate (byte-id + re-parse + asserts export def I32_MAX and
RUNE_MAX reach the interface). byte-id-neutral: a def emits no symbol.
2026-06-15 23:10:35 +09:00
4622556c62 wcc/ww: round-trip @symbol in the .wwi producer (#47)
The M2 .wwi producer rendered an exported fn carrying @symbol("...")
as a bare prototype, dropping the FFI link-symbol binding. A
sep-compiled consumer reading the .wwi then emitted `CALL malloc`
instead of `CALL rt_malloc` for `@symbol("rt_malloc") export fn
malloc`, breaking the bodies-vs-.wwi byte-id and the link. Affects
every package whose closure reaches rt/os.

Emit codegen/link-relevant attributes through a single named
predicate (wwi_attr_relevant / wwiattrrelevant), today true iff the
name is "symbol" — the only such attribute that exists. @align/@offset
are NOT field attributes in ww (the parser parses no field attrs, the
N_TFIELD node has no attr slot); the predicate is named for the class
so they slot in if ww ever grows them (#51). attr is assigned at
exactly one site per stage (parse.c:1351 / decl.ww:168), both inside
parsefn, so only N_FNDECL carries attrs and the fn-decl render path
covers the whole class.

Both stages, byte-identical (rule 10). 989_m2wwi_run synth gate gains
an @symbol fn + a content assertion that the .wwi carries it verbatim.
2026-06-15 22:56:12 +09:00
13e5e35f81 wcc/ww: .wwi separate-compile consumer — w6c -c codegen filter (#22 M3)
New `w6c -c` (both stages): separate-compile / primary-only codegen.
Emit code+DATA ONLY for a package's own (imported==0) decls; treat every
`.wwi`-sourced (imported==1) dep decl as an external. Pure addition behind
the flag — combined.ww stays the LIVE path, `-c` is off on every existing
invocation, so the 990-997 byte-id gates + all prior tests are unperturbed.

The keystone (rob): a `.wwi` is body-less/init-less prototype source, and
cgen already skips body-less fns as externs, so dep fns/types/defs emit
NOTHING for free. The single genuinely-new guard is an imported value-
global (`export let`): its DATAW would DUPLICATE the dep's own definition
(link collision), so it is skipped. The `imported==0` gate is applied at
all top-level emit sites for uniformity (close-by-construction): the fn
loop, emit_lets/emitletdataw, emit_defs/emitdefconstants, and
let_pre_intern/letpreintern — that last one because an imported dep's body
initializer interns strlits while its rhs-stripped `.wwi` does not, which
would shift the _S_ sequence; gating it keeps the strlit table a pure
function of the package's own decls. EXACTLY symmetric with M2's producer
`imported==0` filter — same predicate both directions.

Driver `--sep` build_one_sep + per-package archives + multi-.a link +
cache + BROAD real-target dual-path soak are M3-tail (#46, rob ruling B):
M3-core ships the codegen spine + a self-contained gate that proves all
codegen correctness without a production driver.

Gate 989_m3sep_run: a synth leaf->mid->root fixture carrying all four
cross-boundary fact-classes (fn signature, struct LAYOUT, `def` const
VALUE, `export let` value-global). Per package, holding `-c` constant:
`w6c -c` of (deps-as-bodies) == (deps-as-.wwi) byte-for-byte (the .wwi
conveys exactly the dep facts P's codegen needs); cs==ww at the .s AND
final-exe level (rule 10); sep-path determinism; the value-global guard
(imported origin_tag never re-emits DATAW); and behavioral identity (the
linked program's exit code is the real cross-boundary computation). COLD:
.wwi materialized fresh every run (no warm cache).

combined.ww regen'd for wwdump + w6c (both embed cgen.ww); diff is exactly
the four guards + the flag wiring, nothing spurious.
2026-06-15 22:33:39 +09:00
e8d3d89fef wcc/ww: .wwi export-data producer + check_exported_type (#22 M2)
New `w6c -I <out.wwi>` flag (both stages) writes a re-parseable
ww-prototype rendering of a package's EXPORTED surface. M2 dead-code:
nothing consumes .wwi yet (combined.ww stays the live path); the flag is
off on every existing invocation, so the 990-997 byte-id gates and all
prior tests are unperturbed.

The unparse walks the AST type-expr subtree (N_T* nodes), not the
tinfo Type* (which collapses nominal pkg.Name identity). Deterministic
output: package line, byte-sorted imports, byte-sorted decls — a pure
function of the exported API. cmd/w6c/wwi.c + selfhost/cmd/wcc/wwi.ww
emit byte-identical .wwi (new cross-stage byte-id substrate, rule 10).

check_exported_type (drew) rides the producer entry, flag-gated: an
exported signature naming a non-exported nominal is loud-rejected before
any byte is written, identically on both stages. Ports harec
check.c:4092-4168, recursing the type-AST and gating on the resolved
SK_TYPE sym's decl export flag (Sym.exported is vestigial in both
stages; the predeclared synthetic `nomem` decl carries no source
position and is treated as a builtin leaf — cstage parity).

Two wwstage checker AST-mutations are normalized to cstage's pristine
view for byte-id: the N_TPARAM tuple-element wrapper (unwrapped) and the
variadic `T...`→`[]T` param desugar (peeled).

Gate 989_m2wwi_run: ascii/strings/getopt each produce a .wwi that
re-parses (wwdump -a) and is cs==ww byte-identical; a private-type-leak
fixture is rejected identically by both stages (non-vacuous check).
2026-06-15 20:58:08 +09:00
e9c11cb5ae wcc/ww: module-scope the cgen mangle-hint (#40)
use_hint/usehint were unit-global first-leaf-match: two directory-
packages exporting the same fn leaf, each imported by a different module
aliasing the same bareword, mis-routed every qualified call to whichever
use was collected first. Identically wrong on both stages (byte-id-green
#263-class). Key the hint on (owner-module, alias) and prefer cur_mod,
mirroring the checker's use_path curmod-preference (55f54fb).

989_m1usehint_run: two same-leaf pick() across a.math/b.math, each
module's call routes to its own import (111/222) + cs.s==ww.s.
2026-06-15 19:12:15 +09:00
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).
2026-06-15 17:37:18 +09:00
9767ff8fff wcc/ww: emit correct (tag,payload) for a tagged value in static-init (#19)
A tagged-union value nested in module-level array/struct static-init
mis-emitted in both stages: the lit-bytes emitters had no TY_TAGGED
arm, so a tagged element/field fell to the int path and the payload
landed in the TAG word -- match then read the wrong variant. The
zero-placeholder idiom (today the only way to declare a tagged global:
zero-init in static, write at runtime) was correct only by accident
(int-variant zero folds to (0,0), which equals the right (tag0,0)).

Extract a raw-byte core emittaggedbytes/emit_tagged_bytes -- variant
tag@+0, int payload@+8, zero-pad to the slot size; no directive, no
offset, no reloc -- and refactor the scalar tagged emitter to delegate
to it (byte-id-neutral). Add a TY_TAGGED member branch to the array
and struct lit-bytes emitters (both stages) that calls the core at the
existing full-slot stride, before the int fallthrough. Zero stays
(0,0) byte-identical; a non-zero element/field now emits (tag,payload)
correctly.

A wide (str/slice) or struct/>8B payload nested in an aggregate needs
reloc-at-member-offset machinery the aggregate byte-emitters don't
have, so it is loud-rejected (rule 7), deferred to #30; the existing
slice-of-tagged static-init reject is unchanged.

Regenerates the w6c and wwdump combined.ww. Table-driven 843 test:
non-zero array/struct (pre-fix returned the wrong variant), the
non-tag-0 bool-variant edge, byte-id-neutral zero-placeholder rows,
and wide-payload reject rows; each run row also pins cs-vs-ww asm.
2026-06-14 18:11:28 +09:00
c86c6a3bbf wcc/ww: match on a global value-struct tagged field reads g(SB) (#29)
A match whose scrutinee is a tagged field of a GLOBAL value-struct read
the tag/payload from the BP region (saved-BP + return-addr) instead of
g(SB) and returned garbage. Both stages were identical-wrong, so the
byte-id gate could not see it -- a gate-blind regression introduced by
M1 (#25): M1's in-place N_DOT match arm uses localfind(base), which
returns the 0 not-found sentinel for a global base, so 0+field.offset
landed in the frame.

Gate the in-place arm on a confirmed-local base -- `localfind(base)==0
&& let_islet/isletvar(base)`, verbatim from cstage's own global test at
cgen.c:2000 (both stages, same spelling). A global base now falls
through to the existing spill path, which cgexprs the scrutinee and
resolves g(SB). M1's local-field in-place ($32) path is untouched.

Regenerates the w6c and wwdump combined.ww. Table-driven 841 test
(global int/reassign/str-payload + a local-field M1 regression row),
runtime-discriminating: pre-fix returns garbage, post-fix 42 on both
stages; rob's direct-global-field spill caveat confirmed at runtime.
2026-06-14 16:52:00 +09:00
33f940e17c wcc/ww: compound OP= on a tagged index/ident is a loud reject (#20/#21)
Compound `OP=` through an index (gs[i]/a[i]) or a bare ident (g) on a
tagged union silently misbehaved: cstage dropped the index compound and
plain-stored, and BOTH stages compiled an ident compound into an add on
the tag word -- byte-identical, so the gate stayed green while the tag
was corrupted. A compound op on a whole union is nonsense.

Gate the index plain-store arm on TK_ASSIGN so a compound falls to the
existing #133 reject (wwstage's byte-id twin); add a dedicated #21 ident
reject in both stages. This closes the compound half of the tagged-payload
write class (deref #18, dot #34 already reject).

#19 (global tagged-array static-init DATA) is a separate emitter, still open.
2026-06-14 01:14:51 +09:00
323607d1d0 wcc: store to a global tagged-array element resolves the (SB) base (#16)
The indexed tagged-element assign arm computed its base without the
isglobal -> LEAQ name(SB) branch the scalar element arm already has, so
`gs[i] = v` on a global tagged array stored to a junk frame base and was
lost -- cstage rc=0 where wwstage (which has the branch) rc=42. Mirror
the scalar arm's base resolution; cstage aligns up to wwstage. Local
tagged arrays and scalar globals are unchanged.

The global tagged-array static initializer still mis-packs its DATA in
both stages -- a separate emitter path, filed as #19.
2026-06-14 00:14:42 +09:00
1074239859 wcc/ww: store through a *tagged pointer widens, both stages (#17)
The N_UN/TK_STAR plain-deref assign arm fell to a single fldstoreop for
every pointee, so `*p = v` with p:*tagged wrote the rhs into the tag word
and never the payload -- identically in both stages, leaving the byte-id
gate green while the store corrupted the tag (#263-class, gate-blind).

Gate on TY_TAGGED and route through cg_widen_tagged_store into a scratch
slot, then word-copy to the destination -- the proven runtime-index arm.
Scalar pointees keep the single-store path unchanged.
2026-06-13 23:54:12 +09:00
728d86518e wcc: nullable-global storage is a loud reject pending #15, both stages
A module-level nullable `(*T | void)` GLOBAL has no storage path in
either stage: let_emit_size / letemitsize returned 0 for the nullable
TY_TAGGED, so let_collect skipped registration and emit_lets skipped
DATA. The three READ paths then miscompiled SILENTLY and identically-
wrong (a #263-class both-wrong gap, not a wwstage align-up): match read
0(BP) = saved BP via the let_islet-gated #87 arm falling to localfind;
`g is *T` / `g as *T` emitted MOVQ name(SB) for a symbol with no DATA →
w6l undefined-reference. cstage's #87 match arm was itself `!is_nullable`-
gated, so both stages were wrong.

This is the silent→loud bridge: die loud at the size/storage layer the
instant a nullable global is declared, so all three read paths hit one
diagnostic instead of a silent miscompile. A silent gap here is exactly
what "stable before CSP" forbids — CSP's process/handle/chan singletons
(`let c: *Chan | void`) are THE canonical nullable-global consumer. The
full storage + read-class arc (real DATA, nil/void/address-of init, let-
registration, the three SB-resolution read arms) is deferred to task #15
(CSP-prereq); the `&`-init sub-problem additionally couples to the #48
static address-of relocation gap (which already bites a plain `*T` global
init the same way).

Diagnostic core text is identical both stages ("nullable-global storage
unimplemented (task #15)"); cstage's fatal() adds the harness-wide "ww: "
err.c prefix err.ww does not, the same per-stage asymmetry every existing
both-stage reject carries. Byte-id-neutral: the corpus declares zero
nullable globals (grep-verified), so the loud path is unreached in self-
compile and the emitted asm is zero-move; the embedded w6c/wwdump
combined.ww amalgamations are regenerated for the cgen.ww source change.

New 989_nullableglobal_reject: 6 reject rows (match/is/as on a &gv init,
plus nil-init and void-init match, plus an inline non-aliased nullable
form) prove rc!=0 + the shared diagnostic on both stages, init- and
form-invariant; 2 controls (non-nullable tagged global, plain nil-init
*T global) prove the reject is keyed on the nullable TY_TAGGED and the
#87 storage path is untouched.
2026-06-13 18:44:10 +09:00
dd24de1134 wcc: whole-struct field-copy completes the ragged tail greedily, both stages
A `x.f = o` copy of a whole struct field emits a MOVQ run for the
8-byte chunks plus a tail. Both stages inlined a tail that handled only
{4,1}: a 4-byte remainder went MOVL, a 1-byte MOVB, but {2,3,5,6,7} fell
through to an 8-byte MOVQ that OVER-READS the source and OVER-WRITES the
field's natural-offset successor. With #44 packing a successor at its
natural offset, that is a live clobber: outer2{i:inner2{u8,u8}, mark:i32}
copies i with `MOVQ -8(BP),AX; MOVQ AX,-16(BP)` and wipes mark@-12; the
correct move is a single MOVW. Same defect in cstage (cgen.c) and the
four wwstage field-copy sites (cgenexpr.ww: via-ptr, direct-BP-local,
global, and the multi-hop dot-chain CX variant).

Fix: replace each inline {4,1} tail with the descending greedy 4/2/1
(MOVL/MOVW/MOVB) the canonical aggregate-copy emitters already use, so
the tail is complete on every natural size. This is path (alpha) of the
#73 brief — a corpus-neutral, no-workaround completion of the inline
tail. Routing field copies through the shared aggcopy/cg_aggcopy choke-
point (beta) is the balloon: those emitters hardcode (SI)->(BX) at offset
k with zero base displacement, but the four field-copy dsts are
heterogeneous (foff(BX), boff+foff(BP) with no base reg, totaloff(CX)),
so routing forces per-site-per-stage LEAQ src->SI + LEAQ dst->BX rewrites
with no mechanical cross-stage mirror at the CX site = a gate-blind
cs!=ww risk. The emitter extraction is filed as a later addressing-
unification arc (#12). The ragged tail is corpus-absent (every corpus
field copy is tail in {0,4}, where greedy 4/2/1 emits exactly what the
old {4,1} tail did), so this is CLASS-N: zero corpus move on both stages,
byte-id holds by construction.

The cstage <=24 N_CALL receive site (cgen.c:5234) is a different copy
family (sret result read from AX/DX/CX, not a mem-to-mem field copy) and
already handles 4/2/1; left untouched. The str/slice/tagged/tuple 4/1
sites (#76) are likewise a separate family, filed not folded.

989_structcopytail_run pins it on both driver twins: tail2 (MOVW), tail6
(MOVL+MOVW), tail7 (the full MOVL+MOVW+MOVB ladder, the MOVB-path row),
plus an 8-aligned ctl8 (tail-0 control). Pre-fix cstage clobbers mark and
exits non-zero -> cs!=ww; post-fix 4/4 ok cs==ww.
2026-06-13 15:06:15 +09:00
f7845057a7 wcc: loop-label stack guards its depth loudly, both stages
wwstage's unguarded loop-label push wrote out of bounds at depth 17
(compiler-heap corruption); cstage guarded but emitted a wrong break
target. Loud cap error at the limit, both stages, agreeing wording.
2026-06-13 04:34:36 +09:00
92cd573197 wcc: defer capacity 32 with a loud cap error, both stages
wwstage capped defers at 16 and SILENTLY DROPPED the 17th; cstage
capped at 32. Align the cap at 32 and make exceeding it a loud
compile error in BOTH stages — the silent 16-vs-32 split was the bug
(a defer that never runs is a leaked resource). Both stages move in
one commit: one cap contract.
2026-06-13 04:31:12 +09:00
2a2ac49c64 wcc: for-range destructure copies the full str/slice binding, both stages
The per-binding copy loop moved ONE word of a 24B str/slice binding —
.len and .cap read zero/garbage in BOTH stages (byte-identical, the
deepest both-wrong-identical of the drain: the F7-era stride fix
asserted convergence without re-measuring the absolute). Copy the full
extent for an sz>8 str/slice binding; the rewritten 989_tupfieldsize
pins all three header words with sliced caps so cap!=len has teeth.
The tagged-binding arm remains open as task #53 (wwstage
paramfieldsize). Review-era task #40, recategorized #263 fused.

Both stages move in one commit: one emission contract; splitting
would leave the byte-id gates red between the halves.
2026-06-12 22:52:20 +09:00
77747061c6 w6c: global *struct base field store loads the pointer from g(SB)
cstage dereferenced 0(BP) for the base — a SEGV on every store
through a module-global struct pointer; wwstage was already correct
(the inverse-set member). Mirror ww's global-pointer load. New
989_globptrfield_run pins cs==ww both stages. Task #47.
2026-06-12 22:48:37 +09:00
e09a8c1775 w6c: 'as' on a module-global tagged ident loads the full box
cstage's N_TYPEASSERT assumed cgexpr filled the registers and spilled
an uninitialized payload (cs=0 for any stored value); mirror the
landed wwstage emission (tag/payload/cap from g(SB)). Flips the
residual row to cs==ww==correct. Task #46.
2026-06-12 22:45:10 +09:00
fc47c3d0f2 w6c: widen of a module-global tagged ident copies the whole box
cstage spilled frame garbage as the box; mirror the landed wwstage
emission (copy from gi(SB)). The F8-era divergence-only rows gain
pinned cs==ww values. Task #44.
2026-06-12 22:41:45 +09:00
ca4ff4b882 w6c: widen of a module-global struct ident copies the full payload
cstage zero-filled the payload; mirror the landed wwstage emission.
Flips the residual row to cs==ww==correct. Task #43.
2026-06-12 22:37:56 +09:00
960b1e796d w6c: return of a module-global struct ident copies the global's bytes
cstage zeroed the return scratch; mirror the landed wwstage emission
(copy from the g(SB) base). Flips the F8-era residual row to
cs==ww==correct. Task #42.
2026-06-12 22:34:31 +09:00
5ff5f4b4fe w6c: tagged GLOBAL reassign emits the store
cstage silently dropped the store on reassigning a module-global
tagged union; mirror the landed wwstage emission (tag+payload to
g(SB) via the widener). Closes the cs half of the F8 #263 pair; the
repro row flips to cs==ww==correct. Task #41.
2026-06-12 22:31:09 +09:00
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.
2026-06-12 21:14:57 +09:00
f00775759d wcc: chained-dot str leaf loads the cap word, both stages
A str field reached through a chained dot (o.i.s) emitted two loads
(ptr, len) and stored a stale CX as the cap — both stages, at any
non-zero chain depth. Emit the full header at the chained-dot leaf.
Review item #29.

Both stages move in one commit: one emission contract; splitting
would leave the byte-id gates red between the halves.
2026-06-12 21:07:55 +09:00
0a6f500b8c wcc: tuple slice-element read loads the full 24B header, both stages
Reading a slice-typed tuple element (t.0) loaded only the pointer
word; len and cap took whatever was left in BX/CX — silent garbage in
BOTH stages once anything clobbered the registers between build and
read. Load all three header words at the tuple-element arm. Review
item #28.

Both stages move in one commit: one emission contract; splitting
would leave the byte-id gates red between the halves.
2026-06-12 21:04:35 +09:00
c405e777d3 wcc: single-dot field compound assignment routes through one combine helper
s.f *= v silently became s.f = v (and the other non-+=/-= ops dropped
likewise) in BOTH stages across five lvalue sub-arms: via-ptr field,
direct local field, str/slice pseudo-field, and the two global-field
forms. Funnel all five through a shared combine dispatch
(cgdotfieldcombine / cg_dotfield_combine) emitting the load-OP-store
sequence at field width, hard-erroring the unhandled kinds — close-by-
construction so no arm stays on the old PLUSEQ-only path (the #133
BUS-routing lesson; #227 sites A/B are the closed siblings). The
refactor routes the corpus's existing +=/-= sites through the same
helper output-identically (byte-id held). Review item #34.

Both stages move in one commit: one emission contract; splitting the
halves would leave the byte-id gates red in between.
2026-06-12 19:29:58 +09:00
1be0e6b5db wcc: indexed-field compound assignment wires all ten ops, both stages
arr[i].field /= %= <<= >>= silently dropped the op (load-combine-store
emitted plain assignment) in BOTH stages — gate-blind, the #133 class.
Route every compound op through the combine dispatch at the indexed-
field arm and hard-error the unhandled operand kinds (float/str/slice/
tagged), per the #133 template (3986818). The runtime-correct target is
the op's own algebra (a OP= b == a = a OP b). Review item #33.

Both stages move in one commit: the fix is a single emission contract —
splitting cstage cgen.c from selfhost cgenexpr.ww would leave the
byte-id gates red between the halves.
2026-06-12 19:26:24 +09:00
5b212e51cf wcc/cgen: zero-init sub-8-byte bare lets, both stages; bytes test honest (#16-team)
A bare 'let x: T;' with 1 <= size(T) <= 7 matched no zero-fill arm in
either stage (8B and >8B were already zeroed) - 'let c: [3]u8;' read
stack garbage. User-ruled zero-value semantics: cstage gate sz>8 ->
sz>0; wwstage zsz==8 arm hoisted above the fill-run arm (required -
8B would otherwise route into the run and diverge) and run gate
zsz>0. New 840 pin: dirty-frame probe rows, dual-dim (run + cs/ww
byte-id); discriminators fail exit-154 on pre-fix binaries.

Fused with the lib/bytes test conversion (rule 11): either half alone
turns 967 red. The old exit(signalled+10) wrapped a real 1782-count
ltrim failure to exit 0 - green depended on the garbage. Converted to
assert form (completes the 35/35 @test conversion); ltrim rows keep
the bare 'let c: [3]u8;' as the consumer proof of the fix.
2026-06-10 20:08:58 +09:00
b795c320c9 wcc: -T test-mode collects @test fns + synthesizes entry, both stages (#15)
@test was parsed then dropped (no consumer); `ww test` needed a hand-written
main listing each test by hand, so adding a @test and forgetting the call
silently skipped it. -T makes the checker collect @test N_FNDECLs in source
order, loud-reject a user main, and append a synthetic
`export fn main() i32 { t0(); ...; return 0; }` at the install->body-check seam;
the existing cgfn emits it (cgen untouched) -> byte-identical by construction.
Mirrors harec's checker-side is_test placement.

Plan-9-lean reduction (user-sanctioned, reinstatable post-CSP): sequential,
abort/nonzero=fail; no setjmp isolation, no fnmatch filter, no file:line.

910/997 rewired from a regex scanner to driving `w6c -T` directly (thin trusted
drivers; the @test content stays ww), with a cross-stage byte-id assert on the
-T output. attest_userman/attest_badsig pin the user-main and bad-signature
rejects.
2026-06-10 02:01:20 +09:00
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.
2026-06-08 20:51:01 +09:00
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.
2026-06-08 18:52:55 +09:00
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.
2026-06-08 17:39:45 +09:00
3f6b68cbf2 wcc/cgen: #154 str==-global ident operand — name(SB) base in cbinop, not (BP) garbage (cstage)
The str==/!= arm of cbinop had an N_IDENT fast-path that assumed the operand
was a local: localfind returns 0 for a module-global str, so it loaded
(BP)/8(BP) — saved-BP/retaddr garbage — into rt_streq. `p == sepstr` silently
compared garbage (returned wrong). Mirror #148's global branch at both sub-sites
(rhs/lhs): off==0 && let_islet -> LEAQ name(SB) base, load ptr/len. Distinct
per-site fast-path, not a shared choke (the by-value-global-arg family
#148/#150/#151 closes separately). cstage-only; the wwstage str== twin is #146
(-> #125 batch).

Pin test/wcc/989_strglobeq (table-driven: const+let globals, rhs+lhs ident,
==/!=, unequal + len>1 rows; teeth-proven). Surfaced by the lib/path c3
buffer-ops gate-1 oracle.
2026-06-08 13:18:59 +09:00
feae910a9b wcc: #152 let-initializer scope — defer the binding's localfind link past its own init (both stages)
A let's own name was visible during its OWN initializer: cgen prepended the
new local into the name-keyed localfind chain BEFORE emitting the init, so
`let x = f(x)` read the fresh UNINIT slot, not the outer/param x. Both-wrong-
identical silent miscompile (gate-blind byte-id). Surfaced by path
dirname/basename (was the c3-posix path->p rename).

Align to Hare (harec check.c:1439 evals the init, then scope_insert). Fix,
both stages, IDENTICAL asm: reserve the frame slot BEFORE the init emits,
link the binding's name into the localfind chain only AFTER.
- cstage cgen.c: split localoff -> localslot(reserve)+link; N_LET's 12
  case-level breaks -> goto letlink (tail links once); the inner-for break
  is preserved; the 4 fatal() arms untouched.
- wwstage cgen.ww/cgenstmt.ww: new localreserve (= localalloc minus the
  chain-link); cglet -> cgletbody(c,n,off) + a cglet wrapper that
  reserves -> calls body -> links after.

Byte-id-safe on existing code: localfind is by-name, so deferring the link
is a no-op on every non-self-shadow let (grep = 0 self-shadow sites) — 990-997
stay green. Because both stages emit identical now-correct asm, byte-id
CANNOT catch this; the pin is a RUNTIME test, teeth-proven (revert -> pin
fails). test/wcc/989_letshadow{.ww,_run.c}: param-shadow, let-in-init shadow,
rename control, arrlit self-ref.

Embedded regen: selfhost/cmd/{w6c,wwdump}/main.combined.ww. Gate: all 325
passed, byte-id 990-997 green, w6c c587f4a1 / w6c_ww 7a69f898 (deterministic).
2026-06-08 12:17:18 +09:00
26d6e2abad wcc/cgen: #148 slice-global by-value call-arg — name(SB) base for global slice ident, not (BP) garbage (cstage)
The slice-IDENT call-arg fast path pushed the header words off off(BP)
where off=localfind(name); for a module-global slice localfind→0, so it
read saved-BP/RIP/caller garbage instead of name(SB). Add the global
branch (LEAQ name(SB) base, push 16/8/0 off it) mirroring the sibling
N_SLICE arm; local path unchanged. cstage-only: wwstage checker-rejects
the shape (#120), so byte-id-safe and the twin defers to #125. Unblocks
path c2-stack (dot/dotdot are faithful module-global []u8). Sibling
structarg fast-path filed #150.
2026-06-08 10:24:48 +09:00
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.
2026-06-08 09:58:22 +09:00
620e733444 wcc/cgen: #140 !void error-singleton variant as value — skip absent void payload load, emit tag-only (cstage, align up)
A void (size-0) error-singleton type-name used as a VALUE (return / let-init /
assign / call-arg) all share the N_IDENT non-local global-value load; the load
emitted MOVQ main.<singleton>(SB),AX for a payload symbol that never exists →
w6l undefined reference. Guard TY_VOID && !let && !def at the non-local
fallthrough so nothing is emitted; the enclosing widen arm stamps the variant
tag. wwstage was already tag-only correct — this aligns cstage up to it.

Pin test/wcc/949_void_error_singleton_run.c (6 rows, teeth = link-fail
pre-fix). cgen-first blocker for the path::buffer arc (error.ha is all !void).
2026-06-08 00:18:26 +09:00
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 39432f7 and post-pass with cs==ww byte-id.
2026-06-07 12:10:38 +09:00
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 abd97e6 and
post-pass with cs==ww byte-id.
2026-06-07 11:23:48 +09:00
66d69537a5 wcc/cgen: #124 cross-module &fn in a const — N_DOT reloc + checker accept (both-stage)
A cross-module `&module.fn` in a const emitted no static reloc (the
const was never defined -> w6l undefined-reference, both stages) and
wwstage's checker rejected the const fn-table. #117/#119 wired the
&fn->DATAR const-data reloc for SAME-module &fn only; charclass_map
(fold-6) needs cross-module (12x &ascii.isXXX).

cgen: add the N_DOT arm to the &fn->symbol helper (node_fnptr_sym /
nodefnptr + the two ww emit sites), emitting mafn(leaf, module-ident)
-- exactly the symbol a runtime &mod.fn or a direct cross-module call
already emits. The helper is the SSoT for both the scalar (#119) and
tuple-row (#117) const-data paths, so one arm closes both.

checker: type a cross-module `&mod.fn` as `*fn(...)` in the TK_AMP arm
(the N_DOT twin of #206's N_IDENT fn-ptr synthesis, gated on a resolved
SK_FN/N_FNDECL leaf), so isassignable affirmatively accepts the const
table -- aligning wwstage UP to cstage's actual acceptance reason
rather than by abdication. The SK_FN gate keeps a non-fn `&mod.var`
from synthesizing a fn type (the one pre-existing nonfn-scalar cs!=ww
slip is N_IDENT-base, untouched and reproduces same-module).

One consumer-coupled commit (the checker accept gates wwstage cgen, so
neither half is independently testable). Narrow: slice-row + scalar
only; fixed-array (#118) and struct-field (#129) stay separate. Both
stages emit the correct cross-module symbols at the right tuple-slot
offsets -> byte-identical (990-997 green). Pin 949_xmod_fnptr_const_run
(distinct fns so a wrong reloc is caught + the SK_FN-gate axis). This
was the last fold-6 cgen blocker; charclass_map is now unblocked.
2026-06-06 23:34:46 +09:00
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.
2026-06-06 22:23:43 +09:00
28396f012b wcc/cgen: #117 review — cite #120 at cstage tuple arm (cs-accept/ww-reject divergence)
The #117 emit_slice_data TY_TUPLE arm fires for any foldable tuple row,
but wwstage's checker admits only the (str,*fn) shape and loud-rejects
the rest before cgen. Document the retained acceptance divergence at the
site (rule 7/8) with the #120 pointer. Comment-only, asm-neutral.
2026-06-06 20:54:39 +09:00
1b4f25ac45 wcc/cgen: #119 scalar &fn global DATA via the #117 reloc helper (both-stage) 2026-06-06 20:28:50 +09:00
942abf0482 wcc/cgen: #117 const slice-of-(str,*fn) DATA + &fn->DATAR reloc (both-stage) 2026-06-06 20:27:43 +09:00
df1928182e wcc/cgen: #117 prep — factor emit_tuple_row backing-relative (byte-neutral) 2026-06-06 20:02:32 +09:00