w6c+selfhost: cgassign N_DOT N_INDEX-lhs branch (closes #16)
Symmetric write-side counterpart of #8, bundled across both stages. cstage [N]Struct write was broken (1986 gated on TY_PTR); wwstage had no N_DOT(N_INDEX) write branch at all. New branch covers both [N]*Struct and [N]Struct via viaptr flag, uses fldstoreop for scalar/sub-word, MOVSS/MOVSD for float, two-MOVQ for str rhs. Compound (PLUSEQ etc.) wired for integer scalar.
This commit is contained in:
7
Makefile
7
Makefile
@@ -218,6 +218,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_at_test $(BIN)/test_let_global \
|
||||
$(BIN)/test_int_cast_signed $(BIN)/test_dot_chain \
|
||||
$(BIN)/test_amp_dot $(BIN)/test_arr_elem_field \
|
||||
$(BIN)/test_arr_elem_field_write \
|
||||
$(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 \
|
||||
@@ -303,6 +304,12 @@ $(BIN)/test_arr_elem_field: test/wcc/680_arr_elem_field.c $(BIN)/ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_arr_elem_field_write: test/wcc/681_arr_elem_field_write.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 \
|
||||
|
||||
256
cmd/w6c/cgen.c
256
cmd/w6c/cgen.c
@@ -1976,6 +1976,262 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* `arr[i].field = v`: N_DOT lhs whose lhs is N_INDEX. Symmetric
|
||||
* write-side of the cgdot N_INDEX-lhs branch. Compute &arr[i]
|
||||
* inline (LEAQ for `[N]Struct`, MOVQ-load for `[N]*Struct` /
|
||||
* `[]Struct` / `*Struct`), deref once when the element is
|
||||
* `*Struct`, then store rhs at `field.offset(addr)`. The
|
||||
* chained-pointer-field branch below catches `[N]*Struct`
|
||||
* writes via its `!= N_IDENT` guard, but `[N]Struct` value-arrays
|
||||
* fall through and silently drop the store. Placed before the
|
||||
* `!= N_IDENT` branch so both shapes share one path. */
|
||||
if (n->lhs && n->lhs->kind == N_DOT && n->lhs->lhs
|
||||
&& n->lhs->lhs->kind == N_INDEX) {
|
||||
Node *idxbase = n->lhs->lhs->lhs;
|
||||
Node *idx = n->lhs->lhs->rhs;
|
||||
if (idxbase && idxbase->kind == N_IDENT && idx) {
|
||||
Type *elemt = n->lhs->lhs->type;
|
||||
Type *elemu = (elemt && elemt->kind == TY_NAMED)
|
||||
? elemt->under : elemt;
|
||||
Type *struct_t = NULL;
|
||||
int viaptr = 0;
|
||||
if (elemu && elemu->kind == TY_PTR) {
|
||||
Type *inner = elemu->sub;
|
||||
if (inner && inner->kind == TY_NAMED)
|
||||
inner = inner->under;
|
||||
if (inner && inner->kind == TY_STRUCT) {
|
||||
struct_t = inner;
|
||||
viaptr = 1;
|
||||
}
|
||||
} else if (elemu && elemu->kind == TY_STRUCT) {
|
||||
struct_t = elemu;
|
||||
}
|
||||
if (struct_t) {
|
||||
Tfield *f = NULL;
|
||||
for (Tfield *fl = struct_t->fields; fl;
|
||||
fl = fl->next)
|
||||
if (strcmp(fl->name,
|
||||
n->lhs->str) == 0)
|
||||
{ f = fl; break; }
|
||||
Type *bt = idxbase->type;
|
||||
Type *bu = (bt && bt->kind == TY_NAMED)
|
||||
? bt->under : bt;
|
||||
int is_arr = bu && bu->kind == TY_ARRAY;
|
||||
int is_sl = bu && bu->kind == TY_SLICE;
|
||||
int is_ptr = bu && bu->kind == TY_PTR;
|
||||
int off = localfind(locals, idxbase->str);
|
||||
if (f != NULL && (is_arr || is_sl || is_ptr)
|
||||
&& off != 0) {
|
||||
Type *ft = f->type;
|
||||
Type *fu = (ft && ft->kind == TY_NAMED)
|
||||
? ft->under : ft;
|
||||
int fsz = (int)(ft ? ft->size : 8);
|
||||
int store_op = fldstoreop(ft, fsz);
|
||||
int foff = (int)f->offset;
|
||||
int esz = (int)elemt->size;
|
||||
int h_isf32 = 0;
|
||||
if (n->op == TK_ASSIGN
|
||||
&& fld_isfloat(ft, &h_isf32)) {
|
||||
int mov = h_isf32
|
||||
? A_MOVSS : A_MOVSD;
|
||||
cgexpr(c, n->rhs, locals);
|
||||
ins2(c, A_SUBQ, aimm(8),
|
||||
areg(D_SP));
|
||||
ins2(c, mov, areg(D_X0),
|
||||
amem(D_SP, 0));
|
||||
cgexpr(c, idx, locals);
|
||||
if (esz > 1) {
|
||||
ins2(c, A_MOVQ,
|
||||
aimm(esz),
|
||||
areg(D_CX));
|
||||
ins2(c, A_IMULQ,
|
||||
areg(D_CX),
|
||||
areg(D_AX));
|
||||
}
|
||||
if (is_arr)
|
||||
ins2(c, A_LEAQ,
|
||||
amem(D_BP, off),
|
||||
areg(D_BX));
|
||||
else
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, off),
|
||||
areg(D_BX));
|
||||
ins2(c, A_ADDQ,
|
||||
areg(D_AX),
|
||||
areg(D_BX));
|
||||
if (viaptr)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BX, 0),
|
||||
areg(D_BX));
|
||||
ins2(c, mov,
|
||||
amem(D_SP, 0),
|
||||
areg(D_X0));
|
||||
ins2(c, A_ADDQ, aimm(8),
|
||||
areg(D_SP));
|
||||
ins2(c, mov, areg(D_X0),
|
||||
amem(D_BX, foff));
|
||||
break;
|
||||
}
|
||||
if (n->op == TK_ASSIGN
|
||||
&& fu && fu->kind == TY_STR) {
|
||||
/* str rhs: AX=ptr, BX=len.
|
||||
* Stash both, compute addr
|
||||
* in CX so the pop pair
|
||||
* restores AX/BX cleanly. */
|
||||
cgexpr(c, n->rhs, locals);
|
||||
ins1(c, A_PUSHQ,
|
||||
areg(D_BX));
|
||||
ins1(c, A_PUSHQ,
|
||||
areg(D_AX));
|
||||
cgexpr(c, idx, locals);
|
||||
if (esz > 1) {
|
||||
ins2(c, A_MOVQ,
|
||||
aimm(esz),
|
||||
areg(D_CX));
|
||||
ins2(c, A_IMULQ,
|
||||
areg(D_CX),
|
||||
areg(D_AX));
|
||||
}
|
||||
if (is_arr)
|
||||
ins2(c, A_LEAQ,
|
||||
amem(D_BP, off),
|
||||
areg(D_CX));
|
||||
else
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, off),
|
||||
areg(D_CX));
|
||||
ins2(c, A_ADDQ,
|
||||
areg(D_AX),
|
||||
areg(D_CX));
|
||||
if (viaptr)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_CX, 0),
|
||||
areg(D_CX));
|
||||
ins1(c, A_POPQ,
|
||||
areg(D_AX));
|
||||
ins1(c, A_POPQ,
|
||||
areg(D_BX));
|
||||
ins2(c, A_MOVQ,
|
||||
areg(D_AX),
|
||||
amem(D_CX, foff + 0));
|
||||
ins2(c, A_MOVQ,
|
||||
areg(D_BX),
|
||||
amem(D_CX, foff + 8));
|
||||
break;
|
||||
}
|
||||
if (n->op == TK_ASSIGN) {
|
||||
cgexpr(c, n->rhs, locals);
|
||||
ins1(c, A_PUSHQ,
|
||||
areg(D_AX));
|
||||
cgexpr(c, idx, locals);
|
||||
if (esz > 1) {
|
||||
ins2(c, A_MOVQ,
|
||||
aimm(esz),
|
||||
areg(D_CX));
|
||||
ins2(c, A_IMULQ,
|
||||
areg(D_CX),
|
||||
areg(D_AX));
|
||||
}
|
||||
if (is_arr)
|
||||
ins2(c, A_LEAQ,
|
||||
amem(D_BP, off),
|
||||
areg(D_BX));
|
||||
else
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, off),
|
||||
areg(D_BX));
|
||||
ins2(c, A_ADDQ,
|
||||
areg(D_AX),
|
||||
areg(D_BX));
|
||||
if (viaptr)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BX, 0),
|
||||
areg(D_BX));
|
||||
ins1(c, A_POPQ,
|
||||
areg(D_AX));
|
||||
ins2(c, store_op,
|
||||
areg(D_AX),
|
||||
amem(D_BX, foff));
|
||||
break;
|
||||
}
|
||||
/* compound: rhs→push; compute
|
||||
* struct addr→BX (deref if *T);
|
||||
* push addr; load old field→AX;
|
||||
* pop addr→BX, rhs→CX; combine;
|
||||
* store. Float/str compound
|
||||
* not wired. */
|
||||
cgexpr(c, n->rhs, locals);
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
cgexpr(c, idx, locals);
|
||||
if (esz > 1) {
|
||||
ins2(c, A_MOVQ,
|
||||
aimm(esz),
|
||||
areg(D_CX));
|
||||
ins2(c, A_IMULQ,
|
||||
areg(D_CX),
|
||||
areg(D_AX));
|
||||
}
|
||||
if (is_arr)
|
||||
ins2(c, A_LEAQ,
|
||||
amem(D_BP, off),
|
||||
areg(D_BX));
|
||||
else
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, off),
|
||||
areg(D_BX));
|
||||
ins2(c, A_ADDQ, areg(D_AX),
|
||||
areg(D_BX));
|
||||
if (viaptr)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BX, 0),
|
||||
areg(D_BX));
|
||||
ins1(c, A_PUSHQ, areg(D_BX));
|
||||
int load_op = fldloadop(ft, fsz);
|
||||
ins2(c, load_op,
|
||||
amem(D_BX, foff),
|
||||
areg(D_AX));
|
||||
ins1(c, A_POPQ, areg(D_BX));
|
||||
ins1(c, A_POPQ, areg(D_CX));
|
||||
switch (n->op) {
|
||||
case TK_PLUSEQ:
|
||||
ins2(c, A_ADDQ,
|
||||
areg(D_CX),
|
||||
areg(D_AX));
|
||||
break;
|
||||
case TK_MINUSEQ:
|
||||
ins2(c, A_SUBQ,
|
||||
areg(D_CX),
|
||||
areg(D_AX));
|
||||
break;
|
||||
case TK_STAREQ:
|
||||
ins2(c, A_IMULQ,
|
||||
areg(D_CX),
|
||||
areg(D_AX));
|
||||
break;
|
||||
case TK_AMPEQ:
|
||||
ins2(c, A_ANDQ,
|
||||
areg(D_CX),
|
||||
areg(D_AX));
|
||||
break;
|
||||
case TK_PIPEEQ:
|
||||
ins2(c, A_ORQ,
|
||||
areg(D_CX),
|
||||
areg(D_AX));
|
||||
break;
|
||||
case TK_CARETEQ:
|
||||
ins2(c, A_XORQ,
|
||||
areg(D_CX),
|
||||
areg(D_AX));
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
ins2(c, store_op, areg(D_AX),
|
||||
amem(D_BX, foff));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Chained `<expr>.field = v` where <expr> evaluates to a *struct.
|
||||
* cgexpr on the inner expression already returns the pointer;
|
||||
* we then store at (ptr + field.offset). Without this, only the
|
||||
|
||||
@@ -11289,6 +11289,216 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// `arr[i].field = v`: N_DOT lhs whose lhs is N_INDEX. Symmetric
|
||||
// write-side of the cgdot N_INDEX-lhs branch added for task #8.
|
||||
// Compute &arr[i] inline (LEAQ for `[N]Struct`, MOVQ for
|
||||
// `[N]*Struct` / `[]Struct` / `*Struct`), deref once when the
|
||||
// element is `*Struct`, then store rhs at field.offset(addr).
|
||||
// Without this both shapes silently drop the store — there is no
|
||||
// existing wwstage branch for N_DOT(N_INDEX,...) lhs at all (the
|
||||
// N_INDEX-lhs branch above handles bare `arr[i] = v`, not the
|
||||
// field write).
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_DOT && lhs.lhs != nil
|
||||
&& lhs.lhs.kind == nkind.N_INDEX) {
|
||||
let idxbase: *node = lhs.lhs.lhs;
|
||||
let idx: *node = lhs.lhs.rhs;
|
||||
let fld2: str = lhs.str;
|
||||
if (idxbase != nil) { if (idxbase.kind == nkind.N_IDENT) {
|
||||
if (idx != nil) {
|
||||
let lc: *local = localfindnode(c, idxbase.str);
|
||||
if (lc != nil) { if (lc.tnode != nil) {
|
||||
let tn: *node = lc.tnode;
|
||||
let elemt: *node = nil;
|
||||
let baseisarray: bool = false;
|
||||
let tk: nkind = tn.kind;
|
||||
if (tk == nkind.N_TSLICE) { elemt = tn.lhs; };
|
||||
if (tk == nkind.N_TARRAY) { elemt = tn.lhs; baseisarray = true; };
|
||||
if (tk == nkind.N_TPTR) { elemt = tn.lhs; };
|
||||
let sname: str;
|
||||
sname.ptr = nil; sname.len = 0;
|
||||
let viaptr: bool = false;
|
||||
if (elemt != nil) {
|
||||
if (elemt.kind == nkind.N_TPTR) {
|
||||
let inner: *node = elemt.lhs;
|
||||
if (inner != nil) { if (inner.kind == nkind.N_TNAME) {
|
||||
sname = inner.str;
|
||||
viaptr = true;
|
||||
};};
|
||||
} else { if (elemt.kind == nkind.N_TNAME) {
|
||||
sname = elemt.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, fld2)) {
|
||||
let esz: i32 = elemsizeofc(c, tn);
|
||||
// f64/f32: rhs in X0. Spill to stack,
|
||||
// compute &arr[i] in BX (deref if *T),
|
||||
// then reload X0 and MOVSD/MOVSS.
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tSUBQ\t$8, SP\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, (SP)\n");
|
||||
cgexpr(c, idx);
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
if (baseisarray) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
if (viaptr) { emitline("\tMOVQ\t(BX), BX\n"); };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t(SP), X0\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
// str rhs: AX=ptr, BX=len. Stash both,
|
||||
// compute addr in CX so the pop pair
|
||||
// restores AX/BX intact.
|
||||
if (isstrtype(c, fi.tnode)) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, idx);
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
if (baseisarray) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), CX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), CX\n");
|
||||
};
|
||||
emitline("\tADDQ\tAX, CX\n");
|
||||
if (viaptr) { emitline("\tMOVQ\t(CX), CX\n"); };
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "CX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitdispreg((fi.foff + 8): i64, "CX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
// scalar plain `=`
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, idx);
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
if (baseisarray) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
if (viaptr) { emitline("\tMOVQ\t(BX), BX\n"); };
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
let sop: str = fieldstoreop(c, fi);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
// compound: rhs→push; compute struct
|
||||
// addr→BX (deref if *T); push addr;
|
||||
// load old field→AX; pop addr→BX,
|
||||
// rhs→CX; combine; store. Float/str
|
||||
// compound not wired.
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, idx);
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
if (baseisarray) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
if (viaptr) { emitline("\tMOVQ\t(BX), BX\n"); };
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
let lop: str = fieldloadop(c, fi);
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tPOPQ\tCX\n");
|
||||
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tCX, AX\n"); };
|
||||
if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tCX, AX\n"); };
|
||||
if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tCX, AX\n"); };
|
||||
if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tCX, AX\n"); };
|
||||
if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tCX, AX\n"); };
|
||||
if (n.op == tkind.TK_CARETEQ) { emitline("\tXORQ\tCX, AX\n"); };
|
||||
let sop2: str = fieldstoreop(c, fi);
|
||||
emitline("\t");
|
||||
emitline(sop2);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
};};
|
||||
};
|
||||
};};
|
||||
};
|
||||
};
|
||||
// Struct/ptr-to-struct field assignment: `s.f = expr;` or
|
||||
// `p.f = expr;`. Only plain `=` is wired (compound on field
|
||||
// is rare and not yet needed by our fixtures).
|
||||
|
||||
@@ -3246,6 +3246,216 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// `arr[i].field = v`: N_DOT lhs whose lhs is N_INDEX. Symmetric
|
||||
// write-side of the cgdot N_INDEX-lhs branch added for task #8.
|
||||
// Compute &arr[i] inline (LEAQ for `[N]Struct`, MOVQ for
|
||||
// `[N]*Struct` / `[]Struct` / `*Struct`), deref once when the
|
||||
// element is `*Struct`, then store rhs at field.offset(addr).
|
||||
// Without this both shapes silently drop the store — there is no
|
||||
// existing wwstage branch for N_DOT(N_INDEX,...) lhs at all (the
|
||||
// N_INDEX-lhs branch above handles bare `arr[i] = v`, not the
|
||||
// field write).
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_DOT && lhs.lhs != nil
|
||||
&& lhs.lhs.kind == nkind.N_INDEX) {
|
||||
let idxbase: *node = lhs.lhs.lhs;
|
||||
let idx: *node = lhs.lhs.rhs;
|
||||
let fld2: str = lhs.str;
|
||||
if (idxbase != nil) { if (idxbase.kind == nkind.N_IDENT) {
|
||||
if (idx != nil) {
|
||||
let lc: *local = localfindnode(c, idxbase.str);
|
||||
if (lc != nil) { if (lc.tnode != nil) {
|
||||
let tn: *node = lc.tnode;
|
||||
let elemt: *node = nil;
|
||||
let baseisarray: bool = false;
|
||||
let tk: nkind = tn.kind;
|
||||
if (tk == nkind.N_TSLICE) { elemt = tn.lhs; };
|
||||
if (tk == nkind.N_TARRAY) { elemt = tn.lhs; baseisarray = true; };
|
||||
if (tk == nkind.N_TPTR) { elemt = tn.lhs; };
|
||||
let sname: str;
|
||||
sname.ptr = nil; sname.len = 0;
|
||||
let viaptr: bool = false;
|
||||
if (elemt != nil) {
|
||||
if (elemt.kind == nkind.N_TPTR) {
|
||||
let inner: *node = elemt.lhs;
|
||||
if (inner != nil) { if (inner.kind == nkind.N_TNAME) {
|
||||
sname = inner.str;
|
||||
viaptr = true;
|
||||
};};
|
||||
} else { if (elemt.kind == nkind.N_TNAME) {
|
||||
sname = elemt.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, fld2)) {
|
||||
let esz: i32 = elemsizeofc(c, tn);
|
||||
// f64/f32: rhs in X0. Spill to stack,
|
||||
// compute &arr[i] in BX (deref if *T),
|
||||
// then reload X0 and MOVSD/MOVSS.
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tSUBQ\t$8, SP\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, (SP)\n");
|
||||
cgexpr(c, idx);
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
if (baseisarray) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
if (viaptr) { emitline("\tMOVQ\t(BX), BX\n"); };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t(SP), X0\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
// str rhs: AX=ptr, BX=len. Stash both,
|
||||
// compute addr in CX so the pop pair
|
||||
// restores AX/BX intact.
|
||||
if (isstrtype(c, fi.tnode)) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, idx);
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
if (baseisarray) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), CX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), CX\n");
|
||||
};
|
||||
emitline("\tADDQ\tAX, CX\n");
|
||||
if (viaptr) { emitline("\tMOVQ\t(CX), CX\n"); };
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "CX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitdispreg((fi.foff + 8): i64, "CX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
// scalar plain `=`
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, idx);
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
if (baseisarray) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
if (viaptr) { emitline("\tMOVQ\t(BX), BX\n"); };
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
let sop: str = fieldstoreop(c, fi);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
// compound: rhs→push; compute struct
|
||||
// addr→BX (deref if *T); push addr;
|
||||
// load old field→AX; pop addr→BX,
|
||||
// rhs→CX; combine; store. Float/str
|
||||
// compound not wired.
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, idx);
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
if (baseisarray) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
if (viaptr) { emitline("\tMOVQ\t(BX), BX\n"); };
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
let lop: str = fieldloadop(c, fi);
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tPOPQ\tCX\n");
|
||||
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tCX, AX\n"); };
|
||||
if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tCX, AX\n"); };
|
||||
if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tCX, AX\n"); };
|
||||
if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tCX, AX\n"); };
|
||||
if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tCX, AX\n"); };
|
||||
if (n.op == tkind.TK_CARETEQ) { emitline("\tXORQ\tCX, AX\n"); };
|
||||
let sop2: str = fieldstoreop(c, fi);
|
||||
emitline("\t");
|
||||
emitline(sop2);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
};};
|
||||
};
|
||||
};};
|
||||
};
|
||||
};
|
||||
// Struct/ptr-to-struct field assignment: `s.f = expr;` or
|
||||
// `p.f = expr;`. Only plain `=` is wired (compound on field
|
||||
// is rare and not yet needed by our fixtures).
|
||||
|
||||
@@ -11289,6 +11289,216 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// `arr[i].field = v`: N_DOT lhs whose lhs is N_INDEX. Symmetric
|
||||
// write-side of the cgdot N_INDEX-lhs branch added for task #8.
|
||||
// Compute &arr[i] inline (LEAQ for `[N]Struct`, MOVQ for
|
||||
// `[N]*Struct` / `[]Struct` / `*Struct`), deref once when the
|
||||
// element is `*Struct`, then store rhs at field.offset(addr).
|
||||
// Without this both shapes silently drop the store — there is no
|
||||
// existing wwstage branch for N_DOT(N_INDEX,...) lhs at all (the
|
||||
// N_INDEX-lhs branch above handles bare `arr[i] = v`, not the
|
||||
// field write).
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_DOT && lhs.lhs != nil
|
||||
&& lhs.lhs.kind == nkind.N_INDEX) {
|
||||
let idxbase: *node = lhs.lhs.lhs;
|
||||
let idx: *node = lhs.lhs.rhs;
|
||||
let fld2: str = lhs.str;
|
||||
if (idxbase != nil) { if (idxbase.kind == nkind.N_IDENT) {
|
||||
if (idx != nil) {
|
||||
let lc: *local = localfindnode(c, idxbase.str);
|
||||
if (lc != nil) { if (lc.tnode != nil) {
|
||||
let tn: *node = lc.tnode;
|
||||
let elemt: *node = nil;
|
||||
let baseisarray: bool = false;
|
||||
let tk: nkind = tn.kind;
|
||||
if (tk == nkind.N_TSLICE) { elemt = tn.lhs; };
|
||||
if (tk == nkind.N_TARRAY) { elemt = tn.lhs; baseisarray = true; };
|
||||
if (tk == nkind.N_TPTR) { elemt = tn.lhs; };
|
||||
let sname: str;
|
||||
sname.ptr = nil; sname.len = 0;
|
||||
let viaptr: bool = false;
|
||||
if (elemt != nil) {
|
||||
if (elemt.kind == nkind.N_TPTR) {
|
||||
let inner: *node = elemt.lhs;
|
||||
if (inner != nil) { if (inner.kind == nkind.N_TNAME) {
|
||||
sname = inner.str;
|
||||
viaptr = true;
|
||||
};};
|
||||
} else { if (elemt.kind == nkind.N_TNAME) {
|
||||
sname = elemt.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, fld2)) {
|
||||
let esz: i32 = elemsizeofc(c, tn);
|
||||
// f64/f32: rhs in X0. Spill to stack,
|
||||
// compute &arr[i] in BX (deref if *T),
|
||||
// then reload X0 and MOVSD/MOVSS.
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
if (isfloattype(c, fi.tnode)) {
|
||||
let mov: str = "MOVSD";
|
||||
if (isf32type(c, fi.tnode)) { mov = "MOVSS"; };
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tSUBQ\t$8, SP\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, (SP)\n");
|
||||
cgexpr(c, idx);
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
if (baseisarray) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
if (viaptr) { emitline("\tMOVQ\t(BX), BX\n"); };
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\t(SP), X0\n");
|
||||
emitline("\tADDQ\t$8, SP\n");
|
||||
emitline("\t");
|
||||
emitline(mov);
|
||||
emitline("\tX0, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
// str rhs: AX=ptr, BX=len. Stash both,
|
||||
// compute addr in CX so the pop pair
|
||||
// restores AX/BX intact.
|
||||
if (isstrtype(c, fi.tnode)) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, idx);
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
if (baseisarray) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), CX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), CX\n");
|
||||
};
|
||||
emitline("\tADDQ\tAX, CX\n");
|
||||
if (viaptr) { emitline("\tMOVQ\t(CX), CX\n"); };
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "CX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitdispreg((fi.foff + 8): i64, "CX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
// scalar plain `=`
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, idx);
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
if (baseisarray) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
if (viaptr) { emitline("\tMOVQ\t(BX), BX\n"); };
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
let sop: str = fieldstoreop(c, fi);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
// compound: rhs→push; compute struct
|
||||
// addr→BX (deref if *T); push addr;
|
||||
// load old field→AX; pop addr→BX,
|
||||
// rhs→CX; combine; store. Float/str
|
||||
// compound not wired.
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, idx);
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
if (baseisarray) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(lc.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
if (viaptr) { emitline("\tMOVQ\t(BX), BX\n"); };
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
let lop: str = fieldloadop(c, fi);
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitline(", AX\n");
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tPOPQ\tCX\n");
|
||||
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tCX, AX\n"); };
|
||||
if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tCX, AX\n"); };
|
||||
if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tCX, AX\n"); };
|
||||
if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tCX, AX\n"); };
|
||||
if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tCX, AX\n"); };
|
||||
if (n.op == tkind.TK_CARETEQ) { emitline("\tXORQ\tCX, AX\n"); };
|
||||
let sop2: str = fieldstoreop(c, fi);
|
||||
emitline("\t");
|
||||
emitline(sop2);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
};};
|
||||
};
|
||||
};};
|
||||
};
|
||||
};
|
||||
// Struct/ptr-to-struct field assignment: `s.f = expr;` or
|
||||
// `p.f = expr;`. Only plain `=` is wired (compound on field
|
||||
// is rare and not yet needed by our fixtures).
|
||||
|
||||
284
test/wcc/681_arr_elem_field_write.c
Normal file
284
test/wcc/681_arr_elem_field_write.c
Normal file
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* 681_arr_elem_field_write — `arr[i].field = v` (write-side counterpart
|
||||
* of 680_arr_elem_field). Both stages had a silent store-drop:
|
||||
*
|
||||
* cstage's chained-pointer-field-write branch (cgen.c near 1986) caught
|
||||
* `[N]*Struct` writes via its `!= N_IDENT` guard but skipped `[N]Struct`
|
||||
* value-arrays (TY_STRUCT element fails the TY_PTR guard).
|
||||
*
|
||||
* wwstage had no N_DOT(N_INDEX,...) lhs branch at all in cgassign
|
||||
* (cgenexpr.ww). Both shapes silently emitted no store.
|
||||
*
|
||||
* Closed in task #16 by mirroring task #8's read-side N_INDEX-lhs branch
|
||||
* onto the write side (fldstoreop, str/float leaf coverage, deref-or-not
|
||||
* for `*Struct` vs `Struct` element).
|
||||
*
|
||||
* Coverage — both array shapes, scalar/sub-word/str/float leaves, dyn
|
||||
* idx, and read-modify-write compound (`arr[i].x += 5`). Cstage and
|
||||
* wwstage on every fixture; wwstage gated on access(X_OK).
|
||||
*/
|
||||
#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[] = {
|
||||
/* [N]*Struct, i32 field write. Sole write into stk[0].x; read it
|
||||
* back. Returns 42. */
|
||||
{ "ptr_arr_i32_write",
|
||||
"type nd = struct { name: str, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let v: nd; v.name = \"hi\"; v.x = 0;\n"
|
||||
" let stk: [16]*nd; stk[0] = &v;\n"
|
||||
" stk[0].x = 42;\n"
|
||||
" return stk[0].x;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* [N]*Struct, u8 field write. Sub-word store via MOVB through
|
||||
* fieldstoreop. Returns 9. */
|
||||
{ "ptr_arr_u8_write",
|
||||
"type nd = struct { tag: u8, pad: u8, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let v: nd; v.tag = 0u8; v.pad = 0u8; v.x = 0;\n"
|
||||
" let stk: [4]*nd; stk[0] = &v;\n"
|
||||
" stk[0].tag = 9u8;\n"
|
||||
" return stk[0].tag: i32;\n"
|
||||
"};\n",
|
||||
9 },
|
||||
/* [N]*Struct, str field write — 16B store of both ptr+len.
|
||||
* Returns len("hello") = 5. */
|
||||
{ "ptr_arr_str_write",
|
||||
"type nd = struct { a: i32, b: i32, name: str };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let v: nd; v.a = 0; v.b = 0; v.name = \"old\";\n"
|
||||
" let stk: [16]*nd; stk[0] = &v;\n"
|
||||
" stk[0].name = \"hello\";\n"
|
||||
" return stk[0].name.len: i32;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
/* [N]Struct, i32 field write (value-array). The cstage chained-
|
||||
* pointer-field branch's TY_PTR guard skips this; without the new
|
||||
* value-array path the store silently drops. Returns 50. */
|
||||
{ "val_arr_i32_write",
|
||||
"type nd = struct { name: str, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]nd;\n"
|
||||
" arr[2].x = 50;\n"
|
||||
" return arr[2].x;\n"
|
||||
"};\n",
|
||||
50 },
|
||||
/* [N]Struct, u8 field write — value-array sub-word store. Returns 7. */
|
||||
{ "val_arr_u8_write",
|
||||
"type nd = struct { tag: u8, pad: u8, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]nd;\n"
|
||||
" arr[1].tag = 7u8;\n"
|
||||
" return arr[1].tag: i32;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
/* [N]Struct, str field write — value-array 16B store. Returns
|
||||
* len("world") = 5. */
|
||||
{ "val_arr_str_write",
|
||||
"type nd = struct { name: str, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]nd;\n"
|
||||
" arr[1].name = \"world\";\n"
|
||||
" return arr[1].name.len: i32;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
/* Sub-word signed: write -3i8 then read back as i32. If MOVB
|
||||
* stored the truncated low byte and the field's fldloadop sign-
|
||||
* extends correctly, the i32 read returns -3. Returns 42 when
|
||||
* the equality check passes, 0 otherwise. */
|
||||
{ "ptr_arr_i8_signed_write",
|
||||
"type nd = struct { tag: i8, pad: u8, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let v: nd; v.tag = 0i8; v.pad = 0u8; v.x = 0;\n"
|
||||
" let stk: [4]*nd; stk[0] = &v;\n"
|
||||
" stk[0].tag = -3i8;\n"
|
||||
" let t: i32 = stk[0].tag: i32;\n"
|
||||
" if (t == -3) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Value-array sub-word signed: distinct from ptr_arr_i8_signed —
|
||||
* this path takes LEAQ &arr[i] (no MOVQ-to-deref) and stores the
|
||||
* truncated byte via fldstoreop MOVB. Read back via fldloadop
|
||||
* MOVSBQ sign-extends to i32; -3 round-trips. Returns 42. */
|
||||
{ "val_arr_i8_signed_write",
|
||||
"type nd = struct { tag: i8, pad: u8, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]nd;\n"
|
||||
" arr[1].tag = -3i8;\n"
|
||||
" let t: i32 = arr[1].tag: i32;\n"
|
||||
" if (t == -3) { return 42; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* f64 field write through value-array — pins MOVSD from X0 into
|
||||
* field offset of &arr[i]. Returns 23. */
|
||||
{ "val_arr_f64_write",
|
||||
"type nd = struct { x: i32, d: f64 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]nd;\n"
|
||||
" arr[1].d = 23.0f64;\n"
|
||||
" let v: f64 = arr[1].d;\n"
|
||||
" return v: i32;\n"
|
||||
"};\n",
|
||||
23 },
|
||||
/* Dyn idx through [N]*Struct write — index isn't a literal, so
|
||||
* the IMULQ path fires. Returns 99. */
|
||||
{ "ptr_arr_dyn_idx_write",
|
||||
"type nd = struct { name: str, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let a: nd; a.name = \"a\"; a.x = 0;\n"
|
||||
" let b: nd; b.name = \"b\"; b.x = 0;\n"
|
||||
" let c: nd; c.name = \"c\"; c.x = 0;\n"
|
||||
" let stk: [4]*nd;\n"
|
||||
" stk[0] = &a; stk[1] = &b; stk[2] = &c;\n"
|
||||
" let i: i32 = 2;\n"
|
||||
" stk[i].x = 99;\n"
|
||||
" return stk[2].x;\n"
|
||||
"};\n",
|
||||
99 },
|
||||
/* Read-modify-write compound on [N]Struct — exercises BOTH the
|
||||
* read (load old field) and the write (store combined). For
|
||||
* `arr[1].x += 5` with arr[1].x = 37 → 42. */
|
||||
{ "val_arr_compound_plus",
|
||||
"type nd = struct { name: str, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]nd;\n"
|
||||
" arr[1].x = 37;\n"
|
||||
" arr[1].x += 5;\n"
|
||||
" return arr[1].x;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Non-PLUSEQ compound (CARETEQ) on [N]Struct — exercises the XORQ
|
||||
* arm of the compound switch. Worker wired all six integer
|
||||
* compound ops; this pins one of the non-PLUSEQ arms so a future
|
||||
* regression in the switch table is caught. 0x2A ^ 0x14 = 0x3E
|
||||
* (62), then return 62 - 20 = 42. */
|
||||
{ "val_arr_compound_xor",
|
||||
"type nd = struct { name: str, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]nd;\n"
|
||||
" arr[2].x = 42;\n"
|
||||
" arr[2].x ^= 20;\n"
|
||||
" return arr[2].x - 20;\n"
|
||||
"};\n",
|
||||
42 },
|
||||
/* Compound through [N]*Struct — same shape but viaptr is true,
|
||||
* so the address compute deref-loads (BX), and the load_op picks
|
||||
* MOVSXD for i32. 10 += 22 → 32. */
|
||||
{ "ptr_arr_compound_plus",
|
||||
"type nd = struct { name: str, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let v: nd; v.name = \"a\"; v.x = 10;\n"
|
||||
" let stk: [4]*nd; stk[0] = &v;\n"
|
||||
" stk[0].x += 22;\n"
|
||||
" return stk[0].x;\n"
|
||||
"};\n",
|
||||
32 },
|
||||
};
|
||||
|
||||
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/waew_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/waew_%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, "arr_elem_field_write: 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,
|
||||
"arr_elem_field_write[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"arr_elem_field_write: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("arr_elem_field_write: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user