w6c+selfhost: cgen N_DOT slice-field through *T root in call args (closes #29)
This commit is contained in:
7
Makefile
7
Makefile
@@ -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 \
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
307
test/wcc/691_dot_slice_arg.c
Normal file
307
test/wcc/691_dot_slice_arg.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user