From d053560b80e40e5e539d823639b2cfd002738c1d Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 13 May 2026 23:17:15 +0900 Subject: [PATCH] w6c+selfhost: TK_AMP through chained N_DOT (closes #9) Three shapes handled: value-struct chains (&o.i.a), pointer-field (&p.f), slice/str pseudo-fields (&s.len). cstage inlines #6's spine walker; wwstage reuses dotchainresolve unchanged. Silent-drop fallback preserved. Slice-header width mismatch in *&s.len writes filed as task #13. --- Makefile | 7 + cmd/w6c/cgen.c | 128 ++++++++++++++ selfhost/cmd/w6c/main.combined.ww | 163 ++++++++++++++++++ selfhost/cmd/wcc/cgenexpr.ww | 163 ++++++++++++++++++ selfhost/cmd/wwdump/main.combined.ww | 163 ++++++++++++++++++ test/wcc/690_amp_dot.c | 241 +++++++++++++++++++++++++++ 6 files changed, 865 insertions(+) create mode 100644 test/wcc/690_amp_dot.c diff --git a/Makefile b/Makefile index 8f062ab1..833c3804 100644 --- a/Makefile +++ b/Makefile @@ -217,6 +217,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_e2e $(BIN)/test_ffi $(BIN)/test_dyn $(BIN)/test_stdlib \ $(BIN)/test_at_test $(BIN)/test_let_global \ $(BIN)/test_int_cast_signed $(BIN)/test_dot_chain \ + $(BIN)/test_amp_dot \ $(BIN)/test_field_signed $(BIN)/test_frame_argcount \ $(BIN)/test_selfhost $(BIN)/test_w6a_ww $(BIN)/test_w6l_ww \ $(BIN)/test_w6c_ww $(BIN)/test_ww_ww $(BIN)/test_self_rebuild \ @@ -290,6 +291,12 @@ $(BIN)/test_dot_chain: test/wcc/650_dot_chain.c $(BIN)/ww \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_amp_dot: test/wcc/690_amp_dot.c $(BIN)/ww \ + $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ + $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ + $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + $(BIN)/test_field_signed: test/wcc/660_field_signed.c $(BIN)/ww \ $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 1430317d..f325d280 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -1287,6 +1287,134 @@ cgexpr(Cg *c, Node *n, Local *locals) } break; } + if (opnd && opnd->kind == N_DOT) { + /* Address-of through a DOT chain. The early-exit + * above handled `&ident` and `&base[i]`; everything + * else was silently dropped. Three shapes converge + * here, all returning an 8B address (so no + * fldloadop dispatch — just LEAQ). + * + * 1. Value-struct fields, any depth (`&o.f`, + * `&o.i.a`, `&o.a.b.c`): walk the spine to a + * root ident, sum field offsets, emit LEAQ at + * base + sum. Mirror of the read at line 3722. + * 2. Slice/str pseudo-field tail (`&s.len`, + * `&b.buf.len`): folds into the spine walk + * with slice_delta 0/8/16. + * 3. Pointer-field (`&p.f` where p:*T): the spine + * walk aborts at the *T base; the fallback + * below loads p into AX and adds field_off. + */ + int amped = 0; + /* Spine walk — same shape as the read at 3722. + * Records (parent_struct, field_name) leaf-first, + * then iterates root-first to sum offsets. */ + struct { Type *pu; const char *name; } steps[16]; + int nsteps = 0; + Node *cur = opnd; + int abort = 0; + while (cur && cur->kind == N_DOT && cur->lhs) { + Type *pt = cur->lhs->type; + Type *pu = (pt && pt->kind == TY_NAMED) + ? pt->under : pt; + if (!pu) { abort = 1; break; } + if (cur == opnd && (pu->kind == TY_SLICE + || pu->kind == TY_STR)) { + /* leaf pseudo on slice/str header */ + } else if (pu->kind != TY_STRUCT) { + abort = 1; + break; + } + if (nsteps >= 16) { abort = 1; break; } + steps[nsteps].pu = pu; + steps[nsteps].name = cur->str; + nsteps++; + cur = cur->lhs; + } + if (!abort && cur && cur->kind == N_IDENT + && nsteps > 0) { + int total_off = 0; + int slice_delta = -1; + int ok = 1; + for (int i = nsteps - 1; i >= 0; i--) { + Type *pu = steps[i].pu; + if (pu->kind == TY_SLICE + || pu->kind == TY_STR) { + if (strcmp(steps[i].name, "ptr") == 0) + slice_delta = 0; + else if (strcmp(steps[i].name, "len") == 0) + slice_delta = 8; + else if (strcmp(steps[i].name, "cap") == 0) + slice_delta = 16; + else { ok = 0; break; } + } else { + Tfield *f = NULL; + for (Tfield *fl = pu->fields; fl; fl = fl->next) + if (strcmp(fl->name, steps[i].name) == 0) + { f = fl; break; } + if (!f) { ok = 0; break; } + total_off += (int)f->offset; + } + } + if (ok) { + int extra = (slice_delta >= 0) + ? slice_delta : 0; + int root_off = localfind(locals, cur->str); + if (root_off != 0) { + ins2(c, A_LEAQ, + amem(D_BP, + root_off + total_off + extra), + areg(D_AX)); + amped = 1; + } else if (let_islet(cur->str)) { + /* Two-step global form mirrors the + * read path's `LEAQ name,CX → MOVQ + * disp(CX),AX`, swapping the MOVQ + * for LEAQ. */ + ins2(c, A_LEAQ, + masym(c, cur->str), areg(D_CX)); + ins2(c, A_LEAQ, + amem(D_CX, total_off + extra), + areg(D_AX)); + amped = 1; + } + } + } + /* Pointer-field fallback for `&p.f` where p:*T — + * the spine walker aborts on the *T base. Load p + * into AX, then LEAQ field_off(AX),AX. Mirror of + * the read at line 4033. */ + if (!amped && opnd->lhs + && opnd->lhs->kind == N_IDENT) { + Type *bt = opnd->lhs->type; + Type *bu = (bt && bt->kind == TY_NAMED) + ? bt->under : bt; + if (bu && bu->kind == TY_PTR && bu->sub) { + Type *inner = bu->sub; + if (inner->kind == TY_NAMED) + inner = inner->under; + if (inner && inner->kind == TY_STRUCT) { + for (Tfield *f = inner->fields; + f; f = f->next) { + if (strcmp(f->name, opnd->str) != 0) + continue; + int off = localfind(locals, + opnd->lhs->str); + ins2(c, A_MOVQ, + amem(D_BP, off), + areg(D_AX)); + ins2(c, A_LEAQ, + amem(D_AX, (int)f->offset), + areg(D_AX)); + amped = 1; + break; + } + } + } + } + if (amped) break; + /* Fall through to silent-drop fallback below. */ + } if (opnd && opnd->kind == N_INDEX) { /* &base[i] = base + i*esz, no dereference. */ Node *base = opnd->lhs; diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 3cf44df8..26bfb479 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -9918,6 +9918,169 @@ fn cgun(c: *cgen, n: *node) void = { }; return; }; + // Address-of through a DOT chain. Mirror of cstage + // cgen.c TK_AMP N_DOT branch. Three shapes converge + // here, all returning an 8B address (no fldloadop — + // just LEAQ / MOVQ+LEAQ). + // + // 1. Value-struct fields, any depth (`&o.f`, + // `&o.i.a`, `&o.a.b.c`) and slice/str pseudo-field + // tail (`&s.len`, `&b.buf.len`): the chained + // (depth ≥ 2) case reuses dotchainresolve; the + // single-DOT case is handled below by inspecting + // the IDENT base's tnode. Byte-identical to the + // cstage spine walker for both depths. + // 2. Pointer-field (`&p.f` where p:*T): single-DOT + // only; spine walker aborts on the *T base. Load + // p into AX, then LEAQ field_off(AX), AX. Mirror + // of the read at cgdot 1144. + if (opnd.kind == nkind.N_DOT) { + // Shape 1 chained: depth-≥2 via dotchainresolve. + // `opnd.lhs.kind == N_DOT` gates the helper at + // nsteps ≥ 2 (matches the read path's gate). + if (opnd.lhs != nil) { + if (opnd.lhs.kind == nkind.N_DOT) { + let r: dotchain; + let pok: bool = dotchainresolve(c, opnd, &r); + if (pok) { + let extra: i64 = 0i64; + if (r.slicedelta >= 0i64) { extra = r.slicedelta; }; + if (r.isglobal) { + emitline("\tLEAQ\t"); + emitsymname(c, r.rootname); + emitline("(SB), CX\n"); + emitline("\tLEAQ\t"); + emitdispreg(r.totaloff + extra, "CX"); + emitline(", AX\n"); + } else { + emitline("\tLEAQ\t"); + emitoff(r.rootoff + r.totaloff + extra); + emitline("(BP), AX\n"); + }; + return; + }; + }; + }; + // Shape 1/2 single-DOT on an IDENT base. Inspect + // the base's tnode to pick value-struct vs slice/ + // str pseudo vs pointer-field. + if (opnd.lhs != nil) { + if (opnd.lhs.kind == nkind.N_IDENT) { + let basenm: str = opnd.lhs.str; + let fld: str = opnd.str; + let lc: *local = localfindnode(c, basenm); + if (lc != nil) { + let tn: *node = lc.tnode; + let lkind: nkind = nkind.N_NONE; + if (tn != nil) { lkind = tn.kind; }; + // Pointer-field: &p.f where p:*T. + if (lkind == nkind.N_TPTR) { + let inner: *node = tn.lhs; + let sname: str; + sname.ptr = nil; sname.len = 0; + if (inner != nil) { + if (inner.kind == nkind.N_TNAME) { sname = inner.str; }; + }; + if (sname.len > 0) { + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + if (streq(fi.fname, fld)) { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), AX\n"); + emitline("\tLEAQ\t"); + emitdispreg(fi.foff: i64, "AX"); + emitline(", AX\n"); + return; + }; + fi = fi.finext; + }; + }; + }; + }; + // Value-struct local: &o.f. + if (lkind == nkind.N_TNAME) { + let sname: str = tn.str; + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + if (streq(fi.fname, fld)) { + emitline("\tLEAQ\t"); + emitoff((lc.off + fi.foff): i64); + emitline("(BP), AX\n"); + return; + }; + fi = fi.finext; + }; + }; + }; + // Slice/str pseudo-field on a local: + // &s.ptr / &s.len / &s.cap. Delta is + // 0/8/16 — matches the spine walker. + let delta: i32 = -1; + if (streq(fld, "ptr")) { delta = 0; }; + if (streq(fld, "len")) { delta = 8; }; + if (streq(fld, "cap")) { delta = 16; }; + if (delta >= 0) { + let isslor: bool = false; + if (lkind == nkind.N_TSLICE) { isslor = true; }; + if (lkind == nkind.N_TNAME) { + if (streq(tn.str, "str")) { isslor = true; }; + }; + if (isslor) { + emitline("\tLEAQ\t"); + emitoff((lc.off + delta): i64); + emitline("(BP), AX\n"); + return; + }; + }; + }; + // Global root: top-level let, either a + // struct or a slice/str. + if (isletvar(c, basenm)) { + let gsi: *structinfo = letvarstructinfo(c, basenm); + if (gsi != nil) { + let fi: *fieldinfo = gsi.fields; + for (fi != nil) { + if (streq(fi.fname, fld)) { + emitline("\tLEAQ\t"); + emitsymname(c, basenm); + emitline("(SB), CX\n"); + emitline("\tLEAQ\t"); + emitdispreg(fi.foff: i64, "CX"); + emitline(", AX\n"); + return; + }; + fi = fi.finext; + }; + }; + let isstr: bool = letvarisstr(c, basenm); + let issl: bool = letvarisslice(c, basenm); + if (isstr || issl) { + let gdelta: i32 = -1; + if (streq(fld, "ptr")) { gdelta = 0; }; + if (streq(fld, "len")) { gdelta = 8; }; + if (issl) { if (streq(fld, "cap")) { gdelta = 16; }; }; + if (gdelta >= 0) { + emitline("\tLEAQ\t"); + emitsymname(c, basenm); + emitline("(SB), CX\n"); + emitline("\tLEAQ\t"); + emitdispreg(gdelta: i64, "CX"); + emitline(", AX\n"); + return; + }; + }; + }; + }; + }; + // Fall through silently (mirrors cstage silent- + // drop fallback at the end of the TK_AMP block). + return; + }; if (opnd.kind == nkind.N_INDEX) { // &base[i] = base + i*esz, no dereference. let base: *node = opnd.lhs; diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index aeef97c1..bcf7a8a3 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -1868,6 +1868,169 @@ fn cgun(c: *cgen, n: *node) void = { }; return; }; + // Address-of through a DOT chain. Mirror of cstage + // cgen.c TK_AMP N_DOT branch. Three shapes converge + // here, all returning an 8B address (no fldloadop — + // just LEAQ / MOVQ+LEAQ). + // + // 1. Value-struct fields, any depth (`&o.f`, + // `&o.i.a`, `&o.a.b.c`) and slice/str pseudo-field + // tail (`&s.len`, `&b.buf.len`): the chained + // (depth ≥ 2) case reuses dotchainresolve; the + // single-DOT case is handled below by inspecting + // the IDENT base's tnode. Byte-identical to the + // cstage spine walker for both depths. + // 2. Pointer-field (`&p.f` where p:*T): single-DOT + // only; spine walker aborts on the *T base. Load + // p into AX, then LEAQ field_off(AX), AX. Mirror + // of the read at cgdot 1144. + if (opnd.kind == nkind.N_DOT) { + // Shape 1 chained: depth-≥2 via dotchainresolve. + // `opnd.lhs.kind == N_DOT` gates the helper at + // nsteps ≥ 2 (matches the read path's gate). + if (opnd.lhs != nil) { + if (opnd.lhs.kind == nkind.N_DOT) { + let r: dotchain; + let pok: bool = dotchainresolve(c, opnd, &r); + if (pok) { + let extra: i64 = 0i64; + if (r.slicedelta >= 0i64) { extra = r.slicedelta; }; + if (r.isglobal) { + emitline("\tLEAQ\t"); + emitsymname(c, r.rootname); + emitline("(SB), CX\n"); + emitline("\tLEAQ\t"); + emitdispreg(r.totaloff + extra, "CX"); + emitline(", AX\n"); + } else { + emitline("\tLEAQ\t"); + emitoff(r.rootoff + r.totaloff + extra); + emitline("(BP), AX\n"); + }; + return; + }; + }; + }; + // Shape 1/2 single-DOT on an IDENT base. Inspect + // the base's tnode to pick value-struct vs slice/ + // str pseudo vs pointer-field. + if (opnd.lhs != nil) { + if (opnd.lhs.kind == nkind.N_IDENT) { + let basenm: str = opnd.lhs.str; + let fld: str = opnd.str; + let lc: *local = localfindnode(c, basenm); + if (lc != nil) { + let tn: *node = lc.tnode; + let lkind: nkind = nkind.N_NONE; + if (tn != nil) { lkind = tn.kind; }; + // Pointer-field: &p.f where p:*T. + if (lkind == nkind.N_TPTR) { + let inner: *node = tn.lhs; + let sname: str; + sname.ptr = nil; sname.len = 0; + if (inner != nil) { + if (inner.kind == nkind.N_TNAME) { sname = inner.str; }; + }; + if (sname.len > 0) { + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + if (streq(fi.fname, fld)) { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), AX\n"); + emitline("\tLEAQ\t"); + emitdispreg(fi.foff: i64, "AX"); + emitline(", AX\n"); + return; + }; + fi = fi.finext; + }; + }; + }; + }; + // Value-struct local: &o.f. + if (lkind == nkind.N_TNAME) { + let sname: str = tn.str; + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + if (streq(fi.fname, fld)) { + emitline("\tLEAQ\t"); + emitoff((lc.off + fi.foff): i64); + emitline("(BP), AX\n"); + return; + }; + fi = fi.finext; + }; + }; + }; + // Slice/str pseudo-field on a local: + // &s.ptr / &s.len / &s.cap. Delta is + // 0/8/16 — matches the spine walker. + let delta: i32 = -1; + if (streq(fld, "ptr")) { delta = 0; }; + if (streq(fld, "len")) { delta = 8; }; + if (streq(fld, "cap")) { delta = 16; }; + if (delta >= 0) { + let isslor: bool = false; + if (lkind == nkind.N_TSLICE) { isslor = true; }; + if (lkind == nkind.N_TNAME) { + if (streq(tn.str, "str")) { isslor = true; }; + }; + if (isslor) { + emitline("\tLEAQ\t"); + emitoff((lc.off + delta): i64); + emitline("(BP), AX\n"); + return; + }; + }; + }; + // Global root: top-level let, either a + // struct or a slice/str. + if (isletvar(c, basenm)) { + let gsi: *structinfo = letvarstructinfo(c, basenm); + if (gsi != nil) { + let fi: *fieldinfo = gsi.fields; + for (fi != nil) { + if (streq(fi.fname, fld)) { + emitline("\tLEAQ\t"); + emitsymname(c, basenm); + emitline("(SB), CX\n"); + emitline("\tLEAQ\t"); + emitdispreg(fi.foff: i64, "CX"); + emitline(", AX\n"); + return; + }; + fi = fi.finext; + }; + }; + let isstr: bool = letvarisstr(c, basenm); + let issl: bool = letvarisslice(c, basenm); + if (isstr || issl) { + let gdelta: i32 = -1; + if (streq(fld, "ptr")) { gdelta = 0; }; + if (streq(fld, "len")) { gdelta = 8; }; + if (issl) { if (streq(fld, "cap")) { gdelta = 16; }; }; + if (gdelta >= 0) { + emitline("\tLEAQ\t"); + emitsymname(c, basenm); + emitline("(SB), CX\n"); + emitline("\tLEAQ\t"); + emitdispreg(gdelta: i64, "CX"); + emitline(", AX\n"); + return; + }; + }; + }; + }; + }; + // Fall through silently (mirrors cstage silent- + // drop fallback at the end of the TK_AMP block). + return; + }; if (opnd.kind == nkind.N_INDEX) { // &base[i] = base + i*esz, no dereference. let base: *node = opnd.lhs; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 7c3e2bfe..ae904c3f 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -9918,6 +9918,169 @@ fn cgun(c: *cgen, n: *node) void = { }; return; }; + // Address-of through a DOT chain. Mirror of cstage + // cgen.c TK_AMP N_DOT branch. Three shapes converge + // here, all returning an 8B address (no fldloadop — + // just LEAQ / MOVQ+LEAQ). + // + // 1. Value-struct fields, any depth (`&o.f`, + // `&o.i.a`, `&o.a.b.c`) and slice/str pseudo-field + // tail (`&s.len`, `&b.buf.len`): the chained + // (depth ≥ 2) case reuses dotchainresolve; the + // single-DOT case is handled below by inspecting + // the IDENT base's tnode. Byte-identical to the + // cstage spine walker for both depths. + // 2. Pointer-field (`&p.f` where p:*T): single-DOT + // only; spine walker aborts on the *T base. Load + // p into AX, then LEAQ field_off(AX), AX. Mirror + // of the read at cgdot 1144. + if (opnd.kind == nkind.N_DOT) { + // Shape 1 chained: depth-≥2 via dotchainresolve. + // `opnd.lhs.kind == N_DOT` gates the helper at + // nsteps ≥ 2 (matches the read path's gate). + if (opnd.lhs != nil) { + if (opnd.lhs.kind == nkind.N_DOT) { + let r: dotchain; + let pok: bool = dotchainresolve(c, opnd, &r); + if (pok) { + let extra: i64 = 0i64; + if (r.slicedelta >= 0i64) { extra = r.slicedelta; }; + if (r.isglobal) { + emitline("\tLEAQ\t"); + emitsymname(c, r.rootname); + emitline("(SB), CX\n"); + emitline("\tLEAQ\t"); + emitdispreg(r.totaloff + extra, "CX"); + emitline(", AX\n"); + } else { + emitline("\tLEAQ\t"); + emitoff(r.rootoff + r.totaloff + extra); + emitline("(BP), AX\n"); + }; + return; + }; + }; + }; + // Shape 1/2 single-DOT on an IDENT base. Inspect + // the base's tnode to pick value-struct vs slice/ + // str pseudo vs pointer-field. + if (opnd.lhs != nil) { + if (opnd.lhs.kind == nkind.N_IDENT) { + let basenm: str = opnd.lhs.str; + let fld: str = opnd.str; + let lc: *local = localfindnode(c, basenm); + if (lc != nil) { + let tn: *node = lc.tnode; + let lkind: nkind = nkind.N_NONE; + if (tn != nil) { lkind = tn.kind; }; + // Pointer-field: &p.f where p:*T. + if (lkind == nkind.N_TPTR) { + let inner: *node = tn.lhs; + let sname: str; + sname.ptr = nil; sname.len = 0; + if (inner != nil) { + if (inner.kind == nkind.N_TNAME) { sname = inner.str; }; + }; + if (sname.len > 0) { + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + if (streq(fi.fname, fld)) { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), AX\n"); + emitline("\tLEAQ\t"); + emitdispreg(fi.foff: i64, "AX"); + emitline(", AX\n"); + return; + }; + fi = fi.finext; + }; + }; + }; + }; + // Value-struct local: &o.f. + if (lkind == nkind.N_TNAME) { + let sname: str = tn.str; + let si: *structinfo = structlookup(c, sname); + if (si != nil) { + let fi: *fieldinfo = si.fields; + for (fi != nil) { + if (streq(fi.fname, fld)) { + emitline("\tLEAQ\t"); + emitoff((lc.off + fi.foff): i64); + emitline("(BP), AX\n"); + return; + }; + fi = fi.finext; + }; + }; + }; + // Slice/str pseudo-field on a local: + // &s.ptr / &s.len / &s.cap. Delta is + // 0/8/16 — matches the spine walker. + let delta: i32 = -1; + if (streq(fld, "ptr")) { delta = 0; }; + if (streq(fld, "len")) { delta = 8; }; + if (streq(fld, "cap")) { delta = 16; }; + if (delta >= 0) { + let isslor: bool = false; + if (lkind == nkind.N_TSLICE) { isslor = true; }; + if (lkind == nkind.N_TNAME) { + if (streq(tn.str, "str")) { isslor = true; }; + }; + if (isslor) { + emitline("\tLEAQ\t"); + emitoff((lc.off + delta): i64); + emitline("(BP), AX\n"); + return; + }; + }; + }; + // Global root: top-level let, either a + // struct or a slice/str. + if (isletvar(c, basenm)) { + let gsi: *structinfo = letvarstructinfo(c, basenm); + if (gsi != nil) { + let fi: *fieldinfo = gsi.fields; + for (fi != nil) { + if (streq(fi.fname, fld)) { + emitline("\tLEAQ\t"); + emitsymname(c, basenm); + emitline("(SB), CX\n"); + emitline("\tLEAQ\t"); + emitdispreg(fi.foff: i64, "CX"); + emitline(", AX\n"); + return; + }; + fi = fi.finext; + }; + }; + let isstr: bool = letvarisstr(c, basenm); + let issl: bool = letvarisslice(c, basenm); + if (isstr || issl) { + let gdelta: i32 = -1; + if (streq(fld, "ptr")) { gdelta = 0; }; + if (streq(fld, "len")) { gdelta = 8; }; + if (issl) { if (streq(fld, "cap")) { gdelta = 16; }; }; + if (gdelta >= 0) { + emitline("\tLEAQ\t"); + emitsymname(c, basenm); + emitline("(SB), CX\n"); + emitline("\tLEAQ\t"); + emitdispreg(gdelta: i64, "CX"); + emitline(", AX\n"); + return; + }; + }; + }; + }; + }; + // Fall through silently (mirrors cstage silent- + // drop fallback at the end of the TK_AMP block). + return; + }; if (opnd.kind == nkind.N_INDEX) { // &base[i] = base + i*esz, no dereference. let base: *node = opnd.lhs; diff --git a/test/wcc/690_amp_dot.c b/test/wcc/690_amp_dot.c new file mode 100644 index 00000000..5f95e697 --- /dev/null +++ b/test/wcc/690_amp_dot.c @@ -0,0 +1,241 @@ +/* + * 690_amp_dot — address-of through a DOT chain. + * + * cstage's TK_AMP early-exit historically only handled `&ident` and + * `&base[i]`; everything else fell through to a silent-drop fallback, + * so `&o.i.a` left AX undefined (not even the value — undefined). + * Filed as task #9 from worker-chained-dot judgement #3. Pinned here: + * + * - single-DOT `&o.f` on a value-struct local AND a global root, + * - chained `&o.i.a` on a value-struct (depth 2), + * - 3-deep `&o.a.b.c` (confirms the walker is loop-shaped, not + * hardcoded to depth 2), + * - pointer-field `&p.f` where p:*T, + * - slice-header `&s.len`: write through it (`*&s.len = 0;`) and + * read back via `s.len`. This is the motivating Hare-slice-header + * poke pattern — the whole reason this gap got filed. + * - address agrees with read: `*&o.i.a == o.i.a` round-trips so the + * spine walk's offset arithmetic matches the read path's. + * + * Exercises both stages via `ww` (cstage) and `ww_ww` (wwstage) when + * present. + */ +#include +#include +#include +#include +#include +#include + +static int +runwait(const char *cmd) +{ + int rc = system(cmd); + if (rc == -1) return -1; + if (WIFEXITED(rc)) return WEXITSTATUS(rc); + return -1; +} + +struct row { const char *label; const char *src; int want; }; + +static const struct row rows[] = { + /* &o.f single-DOT on a value-struct local. Round-trips a write + * through the address, returns the field directly. */ + { "amp_dot_single_local", + "type pt = struct { x: i32, y: i32 };\n" + "fn main() i32 = {\n" + " let o: pt;\n" + " let p: *i32 = &o.x;\n" + " *p = 21;\n" + " return o.x;\n" + "};\n", + 21 }, + /* &g.f single-DOT on a global (top-level let) struct root. + * Pins the LEAQ name(SB), CX → LEAQ disp(CX), AX form. */ + { "amp_dot_single_global", + "type pt = struct { x: i32, y: i32 };\n" + "let g: pt;\n" + "fn main() i32 = {\n" + " let p: *i32 = &g.x;\n" + " *p = 33;\n" + " return g.x;\n" + "};\n", + 33 }, + /* &o.i.a — Drew's chained value-struct shape. Address-of side + * of task #6's read fix. */ + { "amp_dot_chain_2deep", + "type inner = struct { a: i32, b: i32 };\n" + "type outer = struct { i: inner, x: i32 };\n" + "fn main() i32 = {\n" + " let o: outer;\n" + " let p: *i32 = &o.i.a;\n" + " *p = 17;\n" + " return o.i.a;\n" + "};\n", + 17 }, + /* 3-deep chain: confirms the spine walker is loop-shaped, not + * hardcoded to depth 2. Same shape as 650's value_struct_3deep + * but going through &. */ + { "amp_dot_chain_3deep", + "type a3 = struct { a: i32 };\n" + "type a2 = struct { a: a3 };\n" + "type a1 = struct { a: a2 };\n" + "fn main() i32 = {\n" + " let v: a1;\n" + " let p: *i32 = &v.a.a.a;\n" + " *p = 9;\n" + " return v.a.a.a;\n" + "};\n", + 9 }, + /* &p.f where p:*T (pointer-field). Spine walker aborts on the + * *T base; the pointer-field fallback should fire. */ + { "amp_dot_ptr_field", + "type pt = struct { x: i32, y: i32 };\n" + "fn main() i32 = {\n" + " let o: pt;\n" + " o.x = 0; o.y = 0;\n" + " let p: *pt = &o;\n" + " let q: *i32 = &p.y;\n" + " *q = 55;\n" + " return o.y;\n" + "};\n", + 55 }, + /* The motivating idiom: write through `&s.len` on a slice header + * to truncate without re-allocating. Mirrors the Hare slice- + * header poke pattern that bufio will eventually want. Test: + * fill a slice's len to 5, then *&s.len = 0; assert s.len == 0. */ + { "amp_dot_slice_len_writethrough", + "fn main() i32 = {\n" + " let arr: [4]u8;\n" + " let s: []u8;\n" + " s.ptr = &arr[0];\n" + " s.len = 4;\n" + " s.cap = 4;\n" + " let q: *i32 = &s.len;\n" + " *q = 0;\n" + " return s.len: i32;\n" + "};\n", + 0 }, + /* &o.s.ptr — slice-FIELD of a struct: combines shape 1 (walk + * into struct field at .s) and shape 3 (slice pseudo-tail + * .ptr at offset 0 of the header). Proves the spine walker's + * slice_delta fold composes with the struct-field offset; the + * write through `*&o.s.ptr` must land at the slice header's + * ptr slot inside the enclosing struct. */ + { "amp_dot_slice_field_ptr", + "type wrap = struct { s: []u8, x: i32 };\n" + "fn main() i32 = {\n" + " let arr: [4]u8;\n" + " let w: wrap;\n" + " w.s.len = 4; w.s.cap = 4;\n" + " let pp: **u8 = &w.s.ptr;\n" + " *pp = &arr[0];\n" + " arr[0] = 71;\n" + " return w.s.ptr[0]: i32;\n" + "};\n", + 71 }, + /* Address agrees with read: `*&o.i.a == o.i.a`. The deref load + * of the address must reproduce the same value the read path + * lowers — proves the spine walker's offset sum is identical + * across both directions. */ + { "amp_dot_addr_eq_read", + "type inner = struct { a: i32, b: i32 };\n" + "type outer = struct { i: inner, x: i32 };\n" + "fn main() i32 = {\n" + " let o: outer;\n" + " o.i.a = 41;\n" + " let p: *i32 = &o.i.a;\n" + " if (*p == o.i.a) { return 42; };\n" + " return 0;\n" + "};\n", + 42 }, +}; + +static int +run_driver(const char *driver, const struct row *r, int i) +{ + char src[64], tmpdir[64], cmd[1024]; + snprintf(src, sizeof src, "/tmp/wad_%d_%d.ww", getpid(), i); + snprintf(tmpdir, sizeof tmpdir, "/tmp/wad_%d_d_%d", getpid(), i); + + FILE *f = fopen(src, "wb"); + if (!f) return -1; + fputs(r->src, f); + fclose(f); + + mkdir(tmpdir, 0755); + snprintf(cmd, sizeof cmd, "cd %s && %s build %s", + tmpdir, driver, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: build via %s failed\n", + r->label, driver); + unlink(src); rmdir(tmpdir); + return -1; + } + + const char *base = strrchr(src, '/'); + base = base ? base + 1 : src; + char outbin[128]; + snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); + char *dot = strrchr(outbin, '.'); + if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; + int got = runwait(outbin); + + unlink(src); unlink(outbin); rmdir(tmpdir); + return got; +} + +int +main(void) +{ + const char *bin = getenv("BIN"); + if (!bin) bin = "out/bin"; + char absbin[1024]; + if (bin[0] != '/') { + char cwd[1024]; + if (getcwd(cwd, sizeof cwd) == NULL) return 1; + snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); + bin = absbin; + } + + char cdrv[1100]; + snprintf(cdrv, sizeof cdrv, "%s/ww", bin); + char wdrv[1100]; + snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); + + struct { const char *name; const char *path; int gated_on_existence; } + drivers[] = { + { "cstage", cdrv, 0 }, + { "wwstage", wdrv, 1 }, + { NULL, NULL, 0 }, + }; + + int n = (int)(sizeof rows / sizeof rows[0]); + int total = 0, fail = 0; + for (int d = 0; drivers[d].name; d++) { + if (drivers[d].gated_on_existence + && access(drivers[d].path, X_OK) != 0) { + fprintf(stderr, "amp_dot: skip %s (no %s)\n", + drivers[d].name, drivers[d].path); + continue; + } + for (int i = 0; i < n; i++) { + int got = run_driver(drivers[d].path, &rows[i], i); + total++; + if (got != rows[i].want) { + fprintf(stderr, + "amp_dot[%s][%s]: exit=%d want=%d\n", + drivers[d].name, rows[i].label, + got, rows[i].want); + fail++; + } + } + } + if (fail) { + fprintf(stderr, + "amp_dot: %d/%d fixtures failed\n", fail, total); + return 1; + } + printf("amp_dot: %d/%d ok\n", total, total); + return 0; +}