w6c+selfhost: cgen N_DOT slice-field through *T root in call args (closes #29)

This commit is contained in:
2026-05-14 23:29:35 +09:00
parent 9706513e59
commit 6402d8deb7
7 changed files with 857 additions and 24 deletions

View File

@@ -220,6 +220,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
$(BIN)/test_amp_dot $(BIN)/test_arr_elem_field \
$(BIN)/test_arr_elem_field_write \
$(BIN)/test_dot_str_chained_arg \
$(BIN)/test_dot_slice_arg \
$(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 \
@@ -318,6 +319,12 @@ $(BIN)/test_dot_str_chained_arg: test/wcc/692_dot_str_chained_arg.c $(BIN)/ww \
$(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $<
$(BIN)/test_dot_slice_arg: test/wcc/691_dot_slice_arg.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 \

View File

@@ -4487,6 +4487,28 @@ cgexpr(Cg *c, Node *n, Local *locals)
areg(D_BX));
goto dot_done;
}
if (fu && fu->kind == TY_SLICE) {
/* Slice leaf: load all three header
* words into (AX=ptr, BX=len, CX=cap)
* so the value follows the canonical
* slice-rhs convention. base_reg may
* be CX (global / `*T` root); load
* .cap LAST so the base survives the
* earlier reads. */
ins2(c, A_MOVQ,
amem(base_reg,
base_disp + total_off + 0),
areg(D_AX));
ins2(c, A_MOVQ,
amem(base_reg,
base_disp + total_off + 8),
areg(D_BX));
ins2(c, A_MOVQ,
amem(base_reg,
base_disp + total_off + 16),
areg(D_CX));
goto dot_done;
}
int g_isf32 = 0;
if (fld_isfloat(leaf_type, &g_isf32)) {
int mov = g_isf32 ? A_MOVSS : A_MOVSD;
@@ -4658,6 +4680,22 @@ cgexpr(Cg *c, Node *n, Local *locals)
areg(D_BX));
break;
}
/* slice field: load (ptr, len, cap) into (AX, BX, CX)
* so the value flows through the slice-rhs convention.
* base_reg may be CX for globals; load .cap LAST so
* the base survives the earlier reads. */
if (str_fu && str_fu->kind == TY_SLICE) {
ins2(c, A_MOVQ,
amem(base_reg, base_disp + (int)f->offset + 0),
areg(D_AX));
ins2(c, A_MOVQ,
amem(base_reg, base_disp + (int)f->offset + 8),
areg(D_BX));
ins2(c, A_MOVQ,
amem(base_reg, base_disp + (int)f->offset + 16),
areg(D_CX));
break;
}
/* f64/f32 field: route through X0 (MOVSD/MOVSS).
* Loading via MOVQ AX would put the bits in the
* integer reg, and any downstream consumer that
@@ -4722,6 +4760,22 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, A_MOVQ, areg(D_CX), areg(D_BX));
break;
}
/* slice field through *struct: load (ptr, len,
* cap) into (AX, BX, CX). BX holds the *struct
* pointer, so load .len LAST — the earlier loads
* still index off the original base. */
if (str_fu && str_fu->kind == TY_SLICE) {
ins2(c, A_MOVQ,
amem(D_BX, (int)f->offset + 0),
areg(D_AX));
ins2(c, A_MOVQ,
amem(D_BX, (int)f->offset + 16),
areg(D_CX));
ins2(c, A_MOVQ,
amem(D_BX, (int)f->offset + 8),
areg(D_BX));
break;
}
/* f64/f32 field via *struct: load into X0.
* BX already holds the struct pointer from
* the MOVQ amem(D_BP,off) above. */
@@ -4772,6 +4826,21 @@ cgexpr(Cg *c, Node *n, Local *locals)
areg(D_AX));
goto dot_done;
}
/* slice field: load (ptr, len, cap) into
* (AX, BX, CX). AX is the *struct base, so
* load .ptr (which targets AX) LAST. */
if (fu && fu->kind == TY_SLICE) {
ins2(c, A_MOVQ,
amem(D_AX, (int)f->offset + 8),
areg(D_BX));
ins2(c, A_MOVQ,
amem(D_AX, (int)f->offset + 16),
areg(D_CX));
ins2(c, A_MOVQ,
amem(D_AX, (int)f->offset + 0),
areg(D_AX));
goto dot_done;
}
/* f64/f32 chained field: read into X0. */
int g_isf32 = 0;
if (fld_isfloat(ft, &g_isf32)) {

View File

@@ -5983,6 +5983,75 @@ fn nodeisslice(c: *cgen, n: *node) bool = {
};
if (k == nkind.N_SLICE) { return true; };
if (k == nkind.N_CAST) { return isslicetype(c, n.rhs); };
// N_DOT to a slice field: resolve the field through the struct
// (or *struct) the base ident / inner chain lands on, then check
// the field tnode. Mirrors nodeisstr's N_DOT branch so call-arg
// push/pop counts 3 words for `p.sl` and `p.inner.sl` shapes.
// `.ptr` / `.len` / `.cap` are pseudo-fields — they yield ptr
// (*u8) and i32, not a slice — so we exclude them up front.
if (k == nkind.N_DOT) {
let base: *node = n.lhs;
let fld: str = n.str;
if (streq(fld, "ptr")) { return false; };
if (streq(fld, "len")) { return false; };
if (streq(fld, "cap")) { return false; };
if (base != nil) {
let sname: str;
sname.ptr = nil; sname.len = 0;
if (base.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, base.str);
if (lc != nil) {
let tn: *node = lc.tnode;
let lkind: nkind = nkind.N_NONE;
if (tn != nil) { lkind = tn.kind; };
if (lkind == nkind.N_TNAME) { sname = tn.str; };
if (lkind == nkind.N_TPTR) {
let inner: *node = tn.lhs;
if (inner != nil) {
if (inner.kind == nkind.N_TNAME) { sname = inner.str; };
};
};
};
};
if (base.kind == nkind.N_DOT) {
let innert: *node = dotinnerstructptr(c, base);
if (innert != nil) {
if (innert.kind == nkind.N_TNAME) { sname = innert.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)) {
return isslicetype(c, fi.tnode);
};
fi = fi.finext;
};
};
};
// Chained dot through value-struct hops (`o.inner.sl`,
// `p.inner.sl`): dotinnerstructptr above only walks
// *struct fields, so a value-struct chain falls through.
// dotchainresolve handles arbitrary depth through value
// struct AND `*T` root, returning the leaf fieldinfo.
let rootnm: str = "";
let rootoff: i32 = 0;
let totaloff: i32 = 0;
let lfi: *fieldinfo = nil;
let sdelta: i32 = -1;
let isglobal: bool = false;
let ptrroot: bool = false;
let ok: bool = dotchainresolve(c, n,
&rootnm, &rootoff, &totaloff,
&lfi, &sdelta, &isglobal, &ptrroot);
if (ok && sdelta < 0 && lfi != nil) {
return isslicetype(c, lfi.tnode);
};
};
return false;
};
return false;
};
@@ -6064,12 +6133,10 @@ fn nodeisstr(c: *cgen, n: *node) bool = {
};
};
// Chained dot through value-struct hops (`p.inner.s`):
// dotinnerstructptr above only walks *struct fields, so
// a value-struct chain falls through and the call-arg
// path then pushes only 1 word for the str instead of
// 2 (ptr+len), silently dropping the len half.
// dotchainresolve handles arbitrary depth through value
// struct AND `*T` root, returning the leaf fieldinfo.
// dotinnerstructptr above only walks *struct fields;
// dotchainresolve handles arbitrary depth through
// value struct AND `*T` root. Mirror of the nodeisslice
// fallback so chained str-field args also push 2 words.
let rootnm: str = "";
let rootoff: i32 = 0;
let totaloff: i32 = 0;
@@ -9335,6 +9402,21 @@ fn cgdot(c: *cgen, n: *node) void = {
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
emitline("\tMOVQ\tCX, BX\n");
} else { if (isslicetype(c, fi.tnode)) {
// slice field via *struct: load
// (ptr, len, cap) into (AX, BX, CX).
// BX holds the *struct pointer, so
// load .len LAST so the earlier
// reads still index off the base.
emitline("\tMOVQ\t");
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 16): i64, "BX");
emitline(", CX\n");
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 8): i64, "BX");
emitline(", BX\n");
} else { if (isfloattype(c, fi.tnode)) {
// f64/f32 via *struct: route through X0.
// MOVQ into AX leaves the SSE reg stale
@@ -9355,7 +9437,7 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\t");
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
}; };
}; }; };
return;
};
fi = fi.finext;
@@ -9381,6 +9463,19 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\tMOVQ\t");
emitoff((lc.off + fi.foff + 8): i64);
emitline("(BP), BX\n");
} else { if (isslicetype(c, fi.tnode)) {
// slice field: load (ptr, len, cap)
// into (AX, BX, CX). Base is BP so
// no aliasing — order doesn't matter.
emitline("\tMOVQ\t");
emitoff((lc.off + fi.foff): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\t");
emitoff((lc.off + fi.foff + 8): i64);
emitline("(BP), BX\n");
emitline("\tMOVQ\t");
emitoff((lc.off + fi.foff + 16): i64);
emitline("(BP), CX\n");
} else { if (isfloattype(c, fi.tnode)) {
// f64/f32 field: route through X0.
let mov: str = "MOVSD";
@@ -9397,7 +9492,7 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\t");
emitoff((lc.off + fi.foff): i64);
emitline("(BP), AX\n");
}; };
}; }; };
return;
};
fi = fi.finext;
@@ -9806,6 +9901,45 @@ fn cgdot(c: *cgen, n: *node) void = {
};
return;
};
if (isslicetype(c, leaffi.tnode)) {
// Slice leaf: load all three header words into
// (AX=ptr, BX=len, CX=cap). For the viacx path
// (global or `*T` root) CX is the base; load
// .cap LAST so the base survives the earlier
// reads. For BP-rooted locals the registers
// don't alias so order is free.
if (viacx) {
if (ptrroot) {
emitline("\tMOVQ\t");
emitoff(rootoff: i64);
emitline("(BP), CX\n");
} else {
emitline("\tLEAQ\t");
emitsymname(c, rootname);
emitline("(SB), CX\n");
};
emitline("\tMOVQ\t");
emitdispreg(totaloff: i64, "CX");
emitline(", AX\n");
emitline("\tMOVQ\t");
emitdispreg((totaloff + 8): i64, "CX");
emitline(", BX\n");
emitline("\tMOVQ\t");
emitdispreg((totaloff + 16): i64, "CX");
emitline(", CX\n");
} else {
emitline("\tMOVQ\t");
emitoff((rootoff + totaloff): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\t");
emitoff((rootoff + totaloff + 8): i64);
emitline("(BP), BX\n");
emitline("\tMOVQ\t");
emitoff((rootoff + totaloff + 16): i64);
emitline("(BP), CX\n");
};
return;
};
if (isfloattype(c, leaffi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, leaffi.tnode)) { mov = "MOVSS"; };
@@ -9900,6 +10034,22 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline(", AX\n");
return;
};
// slice field: load (ptr, len, cap)
// into (AX, BX, CX). AX is the *struct
// base, so load .ptr (which targets
// AX) LAST.
if (isslicetype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 8): i64, "AX");
emitline(", BX\n");
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 16): i64, "AX");
emitline(", CX\n");
emitline("\tMOVQ\t");
emitdispreg(fi.foff: i64, "AX");
emitline(", AX\n");
return;
};
// f64/f32 chained field: route through X0.
if (isfloattype(c, fi.tnode)) {
let mov: str = "MOVSD";

View File

@@ -1202,6 +1202,21 @@ fn cgdot(c: *cgen, n: *node) void = {
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
emitline("\tMOVQ\tCX, BX\n");
} else { if (isslicetype(c, fi.tnode)) {
// slice field via *struct: load
// (ptr, len, cap) into (AX, BX, CX).
// BX holds the *struct pointer, so
// load .len LAST so the earlier
// reads still index off the base.
emitline("\tMOVQ\t");
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 16): i64, "BX");
emitline(", CX\n");
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 8): i64, "BX");
emitline(", BX\n");
} else { if (isfloattype(c, fi.tnode)) {
// f64/f32 via *struct: route through X0.
// MOVQ into AX leaves the SSE reg stale
@@ -1222,7 +1237,7 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\t");
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
}; };
}; }; };
return;
};
fi = fi.finext;
@@ -1248,6 +1263,19 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\tMOVQ\t");
emitoff((lc.off + fi.foff + 8): i64);
emitline("(BP), BX\n");
} else { if (isslicetype(c, fi.tnode)) {
// slice field: load (ptr, len, cap)
// into (AX, BX, CX). Base is BP so
// no aliasing — order doesn't matter.
emitline("\tMOVQ\t");
emitoff((lc.off + fi.foff): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\t");
emitoff((lc.off + fi.foff + 8): i64);
emitline("(BP), BX\n");
emitline("\tMOVQ\t");
emitoff((lc.off + fi.foff + 16): i64);
emitline("(BP), CX\n");
} else { if (isfloattype(c, fi.tnode)) {
// f64/f32 field: route through X0.
let mov: str = "MOVSD";
@@ -1264,7 +1292,7 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\t");
emitoff((lc.off + fi.foff): i64);
emitline("(BP), AX\n");
}; };
}; }; };
return;
};
fi = fi.finext;
@@ -1673,6 +1701,45 @@ fn cgdot(c: *cgen, n: *node) void = {
};
return;
};
if (isslicetype(c, leaffi.tnode)) {
// Slice leaf: load all three header words into
// (AX=ptr, BX=len, CX=cap). For the viacx path
// (global or `*T` root) CX is the base; load
// .cap LAST so the base survives the earlier
// reads. For BP-rooted locals the registers
// don't alias so order is free.
if (viacx) {
if (ptrroot) {
emitline("\tMOVQ\t");
emitoff(rootoff: i64);
emitline("(BP), CX\n");
} else {
emitline("\tLEAQ\t");
emitsymname(c, rootname);
emitline("(SB), CX\n");
};
emitline("\tMOVQ\t");
emitdispreg(totaloff: i64, "CX");
emitline(", AX\n");
emitline("\tMOVQ\t");
emitdispreg((totaloff + 8): i64, "CX");
emitline(", BX\n");
emitline("\tMOVQ\t");
emitdispreg((totaloff + 16): i64, "CX");
emitline(", CX\n");
} else {
emitline("\tMOVQ\t");
emitoff((rootoff + totaloff): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\t");
emitoff((rootoff + totaloff + 8): i64);
emitline("(BP), BX\n");
emitline("\tMOVQ\t");
emitoff((rootoff + totaloff + 16): i64);
emitline("(BP), CX\n");
};
return;
};
if (isfloattype(c, leaffi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, leaffi.tnode)) { mov = "MOVSS"; };
@@ -1767,6 +1834,22 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline(", AX\n");
return;
};
// slice field: load (ptr, len, cap)
// into (AX, BX, CX). AX is the *struct
// base, so load .ptr (which targets
// AX) LAST.
if (isslicetype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 8): i64, "AX");
emitline(", BX\n");
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 16): i64, "AX");
emitline(", CX\n");
emitline("\tMOVQ\t");
emitdispreg(fi.foff: i64, "AX");
emitline(", AX\n");
return;
};
// f64/f32 chained field: route through X0.
if (isfloattype(c, fi.tnode)) {
let mov: str = "MOVSD";

View File

@@ -413,6 +413,75 @@ fn nodeisslice(c: *cgen, n: *node) bool = {
};
if (k == nkind.N_SLICE) { return true; };
if (k == nkind.N_CAST) { return isslicetype(c, n.rhs); };
// N_DOT to a slice field: resolve the field through the struct
// (or *struct) the base ident / inner chain lands on, then check
// the field tnode. Mirrors nodeisstr's N_DOT branch so call-arg
// push/pop counts 3 words for `p.sl` and `p.inner.sl` shapes.
// `.ptr` / `.len` / `.cap` are pseudo-fields — they yield ptr
// (*u8) and i32, not a slice — so we exclude them up front.
if (k == nkind.N_DOT) {
let base: *node = n.lhs;
let fld: str = n.str;
if (streq(fld, "ptr")) { return false; };
if (streq(fld, "len")) { return false; };
if (streq(fld, "cap")) { return false; };
if (base != nil) {
let sname: str;
sname.ptr = nil; sname.len = 0;
if (base.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, base.str);
if (lc != nil) {
let tn: *node = lc.tnode;
let lkind: nkind = nkind.N_NONE;
if (tn != nil) { lkind = tn.kind; };
if (lkind == nkind.N_TNAME) { sname = tn.str; };
if (lkind == nkind.N_TPTR) {
let inner: *node = tn.lhs;
if (inner != nil) {
if (inner.kind == nkind.N_TNAME) { sname = inner.str; };
};
};
};
};
if (base.kind == nkind.N_DOT) {
let innert: *node = dotinnerstructptr(c, base);
if (innert != nil) {
if (innert.kind == nkind.N_TNAME) { sname = innert.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)) {
return isslicetype(c, fi.tnode);
};
fi = fi.finext;
};
};
};
// Chained dot through value-struct hops (`o.inner.sl`,
// `p.inner.sl`): dotinnerstructptr above only walks
// *struct fields, so a value-struct chain falls through.
// dotchainresolve handles arbitrary depth through value
// struct AND `*T` root, returning the leaf fieldinfo.
let rootnm: str = "";
let rootoff: i32 = 0;
let totaloff: i32 = 0;
let lfi: *fieldinfo = nil;
let sdelta: i32 = -1;
let isglobal: bool = false;
let ptrroot: bool = false;
let ok: bool = dotchainresolve(c, n,
&rootnm, &rootoff, &totaloff,
&lfi, &sdelta, &isglobal, &ptrroot);
if (ok && sdelta < 0 && lfi != nil) {
return isslicetype(c, lfi.tnode);
};
};
return false;
};
return false;
};
@@ -494,12 +563,10 @@ fn nodeisstr(c: *cgen, n: *node) bool = {
};
};
// Chained dot through value-struct hops (`p.inner.s`):
// dotinnerstructptr above only walks *struct fields, so
// a value-struct chain falls through and the call-arg
// path then pushes only 1 word for the str instead of
// 2 (ptr+len), silently dropping the len half.
// dotchainresolve handles arbitrary depth through value
// struct AND `*T` root, returning the leaf fieldinfo.
// dotinnerstructptr above only walks *struct fields;
// dotchainresolve handles arbitrary depth through
// value struct AND `*T` root. Mirror of the nodeisslice
// fallback so chained str-field args also push 2 words.
let rootnm: str = "";
let rootoff: i32 = 0;
let totaloff: i32 = 0;

View File

@@ -5983,6 +5983,75 @@ fn nodeisslice(c: *cgen, n: *node) bool = {
};
if (k == nkind.N_SLICE) { return true; };
if (k == nkind.N_CAST) { return isslicetype(c, n.rhs); };
// N_DOT to a slice field: resolve the field through the struct
// (or *struct) the base ident / inner chain lands on, then check
// the field tnode. Mirrors nodeisstr's N_DOT branch so call-arg
// push/pop counts 3 words for `p.sl` and `p.inner.sl` shapes.
// `.ptr` / `.len` / `.cap` are pseudo-fields — they yield ptr
// (*u8) and i32, not a slice — so we exclude them up front.
if (k == nkind.N_DOT) {
let base: *node = n.lhs;
let fld: str = n.str;
if (streq(fld, "ptr")) { return false; };
if (streq(fld, "len")) { return false; };
if (streq(fld, "cap")) { return false; };
if (base != nil) {
let sname: str;
sname.ptr = nil; sname.len = 0;
if (base.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, base.str);
if (lc != nil) {
let tn: *node = lc.tnode;
let lkind: nkind = nkind.N_NONE;
if (tn != nil) { lkind = tn.kind; };
if (lkind == nkind.N_TNAME) { sname = tn.str; };
if (lkind == nkind.N_TPTR) {
let inner: *node = tn.lhs;
if (inner != nil) {
if (inner.kind == nkind.N_TNAME) { sname = inner.str; };
};
};
};
};
if (base.kind == nkind.N_DOT) {
let innert: *node = dotinnerstructptr(c, base);
if (innert != nil) {
if (innert.kind == nkind.N_TNAME) { sname = innert.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)) {
return isslicetype(c, fi.tnode);
};
fi = fi.finext;
};
};
};
// Chained dot through value-struct hops (`o.inner.sl`,
// `p.inner.sl`): dotinnerstructptr above only walks
// *struct fields, so a value-struct chain falls through.
// dotchainresolve handles arbitrary depth through value
// struct AND `*T` root, returning the leaf fieldinfo.
let rootnm: str = "";
let rootoff: i32 = 0;
let totaloff: i32 = 0;
let lfi: *fieldinfo = nil;
let sdelta: i32 = -1;
let isglobal: bool = false;
let ptrroot: bool = false;
let ok: bool = dotchainresolve(c, n,
&rootnm, &rootoff, &totaloff,
&lfi, &sdelta, &isglobal, &ptrroot);
if (ok && sdelta < 0 && lfi != nil) {
return isslicetype(c, lfi.tnode);
};
};
return false;
};
return false;
};
@@ -6064,12 +6133,10 @@ fn nodeisstr(c: *cgen, n: *node) bool = {
};
};
// Chained dot through value-struct hops (`p.inner.s`):
// dotinnerstructptr above only walks *struct fields, so
// a value-struct chain falls through and the call-arg
// path then pushes only 1 word for the str instead of
// 2 (ptr+len), silently dropping the len half.
// dotchainresolve handles arbitrary depth through value
// struct AND `*T` root, returning the leaf fieldinfo.
// dotinnerstructptr above only walks *struct fields;
// dotchainresolve handles arbitrary depth through
// value struct AND `*T` root. Mirror of the nodeisslice
// fallback so chained str-field args also push 2 words.
let rootnm: str = "";
let rootoff: i32 = 0;
let totaloff: i32 = 0;
@@ -9335,6 +9402,21 @@ fn cgdot(c: *cgen, n: *node) void = {
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
emitline("\tMOVQ\tCX, BX\n");
} else { if (isslicetype(c, fi.tnode)) {
// slice field via *struct: load
// (ptr, len, cap) into (AX, BX, CX).
// BX holds the *struct pointer, so
// load .len LAST so the earlier
// reads still index off the base.
emitline("\tMOVQ\t");
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 16): i64, "BX");
emitline(", CX\n");
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 8): i64, "BX");
emitline(", BX\n");
} else { if (isfloattype(c, fi.tnode)) {
// f64/f32 via *struct: route through X0.
// MOVQ into AX leaves the SSE reg stale
@@ -9355,7 +9437,7 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\t");
emitdispreg(fi.foff: i64, "BX");
emitline(", AX\n");
}; };
}; }; };
return;
};
fi = fi.finext;
@@ -9381,6 +9463,19 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\tMOVQ\t");
emitoff((lc.off + fi.foff + 8): i64);
emitline("(BP), BX\n");
} else { if (isslicetype(c, fi.tnode)) {
// slice field: load (ptr, len, cap)
// into (AX, BX, CX). Base is BP so
// no aliasing — order doesn't matter.
emitline("\tMOVQ\t");
emitoff((lc.off + fi.foff): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\t");
emitoff((lc.off + fi.foff + 8): i64);
emitline("(BP), BX\n");
emitline("\tMOVQ\t");
emitoff((lc.off + fi.foff + 16): i64);
emitline("(BP), CX\n");
} else { if (isfloattype(c, fi.tnode)) {
// f64/f32 field: route through X0.
let mov: str = "MOVSD";
@@ -9397,7 +9492,7 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline("\t");
emitoff((lc.off + fi.foff): i64);
emitline("(BP), AX\n");
}; };
}; }; };
return;
};
fi = fi.finext;
@@ -9806,6 +9901,45 @@ fn cgdot(c: *cgen, n: *node) void = {
};
return;
};
if (isslicetype(c, leaffi.tnode)) {
// Slice leaf: load all three header words into
// (AX=ptr, BX=len, CX=cap). For the viacx path
// (global or `*T` root) CX is the base; load
// .cap LAST so the base survives the earlier
// reads. For BP-rooted locals the registers
// don't alias so order is free.
if (viacx) {
if (ptrroot) {
emitline("\tMOVQ\t");
emitoff(rootoff: i64);
emitline("(BP), CX\n");
} else {
emitline("\tLEAQ\t");
emitsymname(c, rootname);
emitline("(SB), CX\n");
};
emitline("\tMOVQ\t");
emitdispreg(totaloff: i64, "CX");
emitline(", AX\n");
emitline("\tMOVQ\t");
emitdispreg((totaloff + 8): i64, "CX");
emitline(", BX\n");
emitline("\tMOVQ\t");
emitdispreg((totaloff + 16): i64, "CX");
emitline(", CX\n");
} else {
emitline("\tMOVQ\t");
emitoff((rootoff + totaloff): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\t");
emitoff((rootoff + totaloff + 8): i64);
emitline("(BP), BX\n");
emitline("\tMOVQ\t");
emitoff((rootoff + totaloff + 16): i64);
emitline("(BP), CX\n");
};
return;
};
if (isfloattype(c, leaffi.tnode)) {
let mov: str = "MOVSD";
if (isf32type(c, leaffi.tnode)) { mov = "MOVSS"; };
@@ -9900,6 +10034,22 @@ fn cgdot(c: *cgen, n: *node) void = {
emitline(", AX\n");
return;
};
// slice field: load (ptr, len, cap)
// into (AX, BX, CX). AX is the *struct
// base, so load .ptr (which targets
// AX) LAST.
if (isslicetype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 8): i64, "AX");
emitline(", BX\n");
emitline("\tMOVQ\t");
emitdispreg((fi.foff + 16): i64, "AX");
emitline(", CX\n");
emitline("\tMOVQ\t");
emitdispreg(fi.foff: i64, "AX");
emitline(", AX\n");
return;
};
// f64/f32 chained field: route through X0.
if (isfloattype(c, fi.tnode)) {
let mov: str = "MOVSD";

View File

@@ -0,0 +1,307 @@
/*
* 691_dot_slice_arg — pass `<expr>.slicefield` as a call argument
* (and return one too).
*
* Task #29: the cgen N_DOT-slice-field paths previously loaded only
* .ptr into AX, leaving BX/CX stale. The slice-arg push at the call
* site then pushed three words from AX/BX/CX, so .len/.cap silently
* came from whatever the prior expression left in those registers.
* Surfaced as `998_bufio_run`'s bstreamunread mid-read len mismatch.
*
* Each fixture passes (or returns) a slice header through a call and
* reads .len / .cap / .ptr on the callee side to prove all three
* words round-trip. Rows cover:
* - single dot through `*T` root (`p.sl`)
* - chained through `*T` root (`p.inner.sl`) — pins
* task #22's spine walker still works with the slice-leaf fix
* in place
* - local value-struct root, single dot (`o.sl`)
* - local value-struct root, chained (`o.inner.sl`)
* - direct slice arg (no dot) — baseline / negative
* control: confirms the existing fast-path still works
* - slice + int neighbor arg — pins call-arg eval
* order doesn't drop the integer slot beside the slice
* - slice mutation through `.ptr` — pins `.ptr` half
* still arrives correctly post-fix
* - N_RETURN of slice N_DOT (probe 3) — exercises the same
* cgen N_DOT slice-load path on the return-stmt site, not
* just the call-arg site
*
* Exercises both stages via `ww` (cstage) and `ww_ww` (wwstage).
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
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[] = {
/* Single dot through *T root. `slen(p.sl)` should see len=5,
* cap=8, ptr[0]=7 (we set arr[0]=7 in main). */
{ "ptr_root_slice_arg",
"type outer = struct { sl: []u8, x: i32 };\n"
"fn slen(s: []u8) i32 = { return s.len: i32; };\n"
"fn scap(s: []u8) i32 = { return s.cap: i32; };\n"
"fn sfirst(s: []u8) i32 = { return s[0]: i32; };\n"
"fn main() i32 = {\n"
" let arr: [8]u8; arr[0] = 7u8;\n"
" let o: outer;\n"
" o.sl.ptr = &arr[0]; o.sl.len = 5; o.sl.cap = 8;\n"
" let p: *outer = &o;\n"
" if (slen(p.sl) != 5) { return 1; };\n"
" if (scap(p.sl) != 8) { return 2; };\n"
" if (sfirst(p.sl) != 7) { return 3; };\n"
" return 42;\n"
"};\n",
42 },
/* Chained through *T root with a value-struct hop — the #22
* spine walker shape, leaf is a slice. */
{ "ptr_root_chained_slice_arg",
"type inner = struct { sl: []u8, pad: i32 };\n"
"type outer = struct { i: inner, tag: i32 };\n"
"fn slen(s: []u8) i32 = { return s.len: i32; };\n"
"fn scap(s: []u8) i32 = { return s.cap: i32; };\n"
"fn sfirst(s: []u8) i32 = { return s[0]: i32; };\n"
"fn main() i32 = {\n"
" let arr: [16]u8; arr[0] = 11u8;\n"
" let o: outer;\n"
" o.i.sl.ptr = &arr[0]; o.i.sl.len = 9; o.i.sl.cap = 16;\n"
" let p: *outer = &o;\n"
" if (slen(p.i.sl) != 9) { return 1; };\n"
" if (scap(p.i.sl) != 16) { return 2; };\n"
" if (sfirst(p.i.sl) != 11) { return 3; };\n"
" return 42;\n"
"};\n",
42 },
/* Local value-struct rooted, single dot. The previous bug was
* present here too — the "real struct field" cgen branch loaded
* only AX. */
{ "local_root_slice_arg",
"type outer = struct { sl: []u8, x: i32 };\n"
"fn slen(s: []u8) i32 = { return s.len: i32; };\n"
"fn scap(s: []u8) i32 = { return s.cap: i32; };\n"
"fn sfirst(s: []u8) i32 = { return s[0]: i32; };\n"
"fn main() i32 = {\n"
" let arr: [8]u8; arr[0] = 3u8;\n"
" let o: outer;\n"
" o.sl.ptr = &arr[0]; o.sl.len = 4; o.sl.cap = 8;\n"
" if (slen(o.sl) != 4) { return 1; };\n"
" if (scap(o.sl) != 8) { return 2; };\n"
" if (sfirst(o.sl) != 3) { return 3; };\n"
" return 42;\n"
"};\n",
42 },
/* Local value-struct rooted, chained value-struct hop. Spine
* walker without ptr_root. */
{ "local_root_chained_slice_arg",
"type inner = struct { sl: []u8, pad: i32 };\n"
"type outer = struct { i: inner, tag: i32 };\n"
"fn slen(s: []u8) i32 = { return s.len: i32; };\n"
"fn scap(s: []u8) i32 = { return s.cap: i32; };\n"
"fn main() i32 = {\n"
" let arr: [16]u8;\n"
" let o: outer;\n"
" o.i.sl.ptr = &arr[0]; o.i.sl.len = 6; o.i.sl.cap = 16;\n"
" if (slen(o.i.sl) != 6) { return 1; };\n"
" if (scap(o.i.sl) != 16) { return 2; };\n"
" return 42;\n"
"};\n",
42 },
/* Chained through a `*struct` mid-hop (`o.i.sl` where `i: *inner`).
* Distinct cgen site from rows 2/4: the spine walker (dotchain-
* resolve) only walks value-struct intermediate hops, so a *struct
* mid-hop falls through to the "chained N_DOT through *struct
* field" branch (cgen.c ~4832, cgenexpr.ww ~1834) — the fourth
* TY_SLICE site touched by this commit. AX is the *struct base
* after `cgexpr(o.i)`, so the load order must end with .ptr → AX. */
{ "ptr_field_chained_slice_arg",
"type inner = struct { sl: []u8, pad: i32 };\n"
"type outer = struct { i: *inner, tag: i32 };\n"
"fn slen(s: []u8) i32 = { return s.len: i32; };\n"
"fn scap(s: []u8) i32 = { return s.cap: i32; };\n"
"fn sfirst(s: []u8) i32 = { return s[0]: i32; };\n"
"fn main() i32 = {\n"
" let arr: [8]u8; arr[0] = 17u8;\n"
" let inn: inner;\n"
" inn.sl.ptr = &arr[0]; inn.sl.len = 3; inn.sl.cap = 8;\n"
" let o: outer; o.i = &inn; o.tag = 0;\n"
" if (slen(o.i.sl) != 3) { return 1; };\n"
" if (scap(o.i.sl) != 8) { return 2; };\n"
" if (sfirst(o.i.sl) != 17) { return 3; };\n"
" return 42;\n"
"};\n",
42 },
/* Negative control: direct slice arg, no dot. Pre-existing fast
* path; confirms the fix didn't disturb it. */
{ "direct_slice_arg_baseline",
"fn slen(s: []u8) i32 = { return s.len: i32; };\n"
"fn scap(s: []u8) i32 = { return s.cap: i32; };\n"
"fn sfirst(s: []u8) i32 = { return s[0]: i32; };\n"
"fn main() i32 = {\n"
" let arr: [8]u8; arr[0] = 9u8;\n"
" let s: []u8;\n"
" s.ptr = &arr[0]; s.len = 5; s.cap = 8;\n"
" if (slen(s) != 5) { return 1; };\n"
" if (scap(s) != 8) { return 2; };\n"
" if (sfirst(s) != 9) { return 3; };\n"
" return 42;\n"
"};\n",
42 },
/* Mixed args: pass a slice-field next to another arg, so the
* fix has to leave neighbouring arg-slot registers undisturbed.
* AX/BX/CX are clobbered while building the slice; the integer
* `tag` arg goes in DI on SysV, so this pins the call-arg eval
* order doesn't drop the tag. */
{ "ptr_root_slice_with_neighbor",
"type outer = struct { sl: []u8, x: i32 };\n"
"fn slen_tag(s: []u8, t: i32) i32 = { return s.len: i32 + t; };\n"
"fn main() i32 = {\n"
" let arr: [8]u8;\n"
" let o: outer;\n"
" o.sl.ptr = &arr[0]; o.sl.len = 5; o.sl.cap = 8;\n"
" let p: *outer = &o;\n"
" let v: i32 = slen_tag(p.sl, 37);\n"
" if (v != 42) { return 1; };\n"
" return 42;\n"
"};\n",
42 },
/* Mutation through the passed slice: callee writes via s[0],
* caller reads arr[0] after. Proves the .ptr half made it
* across — even before the fix this *would have* worked (only
* .len/.cap dropped), but co-testing it guards against a future
* regression that swaps ptr for len. */
{ "ptr_root_slice_mutate_ptr",
"type outer = struct { sl: []u8, x: i32 };\n"
"fn poke(s: []u8) void = { s[0] = 0x42u8; };\n"
"fn main() i32 = {\n"
" let arr: [8]u8; arr[0] = 0u8;\n"
" let o: outer;\n"
" o.sl.ptr = &arr[0]; o.sl.len = 1; o.sl.cap = 8;\n"
" let p: *outer = &o;\n"
" poke(p.sl);\n"
" if (arr[0]: i32 != 0x42) { return 1; };\n"
" return 42;\n"
"};\n",
42 },
/* Probe 3: N_RETURN of a slice N_DOT — a function returns
* `p.sl` directly. cgen's return-stmt site also has to leave
* (AX=ptr, BX=len, CX=cap), or the caller's `let s: []u8 =
* fetch(p)` will lose .len/.cap. Distinct site from the call-
* arg push but exercises the same cgen N_DOT slice-load path. */
{ "ptr_root_slice_return",
"type outer = struct { sl: []u8, x: i32 };\n"
"fn fetch(p: *outer) []u8 = { return p.sl; };\n"
"fn main() i32 = {\n"
" let arr: [8]u8; arr[0] = 19u8;\n"
" let o: outer;\n"
" o.sl.ptr = &arr[0]; o.sl.len = 6; o.sl.cap = 8;\n"
" let p: *outer = &o;\n"
" let s: []u8 = fetch(p);\n"
" if (s.len: i32 != 6) { return 1; };\n"
" if (s.cap: i32 != 8) { return 2; };\n"
" if (s[0]: i32 != 19) { return 3; };\n"
" return 42;\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/wdsa_%d_%d.ww", getpid(), i);
snprintf(tmpdir, sizeof tmpdir, "/tmp/wdsa_%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[1024];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
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, "dot_slice_arg: 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,
"dot_slice_arg[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (fail) {
fprintf(stderr,
"dot_slice_arg: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("dot_slice_arg: %d/%d ok\n", total, total);
return 0;
}