wcc+w6c_ww: cgplaceaddr resolver — deref-base assign stores (F6)
(*ts)[i].field = v / OP= v (the regex run_thread hot shape, task #4) compiled to NOTHING in both stages, byte-identically: the N_DOT lhs roots at N_UN(STAR), so the arr[i].field arm (idxbase must be IDENT) and the chained-ptr-field arm (base must be *struct) both miss and the N_ASSIGN dispatch fell off the switch silently, rhs unevaluated. cgplaceaddr (one per stage) is ADDRESS COMPUTATION ONLY — N_UN(STAR) root, N_INDEX hop over a slice/array place (.ptr hop for slice), N_DOT struct-field hop with one deref for a *struct base. Call-sites keep their own emission: scalar fldstoreop store, str/slice 3-word header store staged through DX, 10-op compound template with the chained-ptr-field register roles. Ident-rooted spines stay with the enumerated arms — verified asm-neutral over the 84 fold2b probe sources against fresh master-HEAD binaries (7 diffs = the F6 family now emitting stores; 2 verdict flips = aggregate-field stores, now loud). Silent dispatch tails go LOUD for N_DOT lvalues the resolver can't address and for unresolved-identifier targets (cstage float-ident arm aligned to wwstage's resolve-first order). Aggregate-field stores loud-reject pending the follow-up resolver commit (task #23, ≤24B N_CALL rhs split to #24). The non-DOT tail stays silent deliberately: going loud there would asymmetrically surface the pre-existing str-base element-store divergence — task #22, cited at both sites. test/805: 17 rows x 2 drivers + 12 cs==ww byte-id fixtures — widths (incl narrow-compound fldloadop sign/zero-extension), all 10 compound ops (DIVQ/IDIVQ/SHLQ/SARQ/SHRQ), str + slice 3-word stores, *[N]T base, runtime call index, ident-base neutrality pins, and 5 reject rows asserting exact diagnostic text.
This commit is contained in:
11
Makefile
11
Makefile
@@ -389,6 +389,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_tuple_sret_receive_run \
|
||||
$(BIN)/test_append_wide_elem \
|
||||
$(BIN)/test_delete_elem \
|
||||
$(BIN)/test_placeaddr_store \
|
||||
$(BIN)/test_struct_tuple_field_slot \
|
||||
$(BIN)/test_widen_pad_zero_run \
|
||||
$(BIN)/test_named_ptr_alias_variant_widen \
|
||||
@@ -1011,6 +1012,16 @@ $(BIN)/test_delete_elem: test/wcc/804_delete_elem.c \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# F6 (task #4, regex fold-2b): (*ts)[i].field = v / OP= v through the
|
||||
# cgplaceaddr resolver — runtime rows across widths/signedness/str +
|
||||
# compound ops + loud-tail reject rows w/ exact diagnostic text + cs==ww
|
||||
# asm byte-id.
|
||||
$(BIN)/test_placeaddr_store: test/wcc/805_placeaddr_store.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# #237: a tuple-typed struct field must contribute its real slot width to
|
||||
# the enclosing struct's slotsize — pure cs==ww .s byte-id (frame size).
|
||||
$(BIN)/test_struct_tuple_field_slot: test/wcc/930_struct_tuple_field_slot.c \
|
||||
|
||||
251
cmd/w6c/cgen.c
251
cmd/w6c/cgen.c
@@ -1989,6 +1989,88 @@ aggarg_srcaddr(Cg *c, Node *src, int dst, Local *locals)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* cgplaceaddr — compute the ADDRESS of an arbitrary place (lvalue)
|
||||
* expression into dst_reg; returns 1 when the shape is wired, 0
|
||||
* otherwise (the caller loud-stops — rule 7, never a silent drop).
|
||||
* F6 resolver, commit C1: only the deref-rooted spine is wired —
|
||||
* `(*p)[i].f` as N_UN(STAR) root, N_INDEX hop over a slice/array
|
||||
* place, N_DOT struct-field hop with one deref for a *struct base.
|
||||
* Ident-rooted spines stay with the enumerated N_ASSIGN arms so this
|
||||
* resolver never perturbs their asm; the F4 read-walker and F5
|
||||
* let-copy accrete here in follow-up commits. ADDRESS COMPUTATION
|
||||
* ONLY — every call-site keeps its own load/store/copy emission.
|
||||
* Clobbers AX/CX (cgexpr on index / pointer operands) and balances
|
||||
* its own PUSHQ/POPQ; dst_reg must not be AX or CX. */
|
||||
static int
|
||||
cgplaceaddr(Cg *c, Node *n, int dst_reg, Local *locals)
|
||||
{
|
||||
if (n == NULL) return 0;
|
||||
if (n->kind == N_UN && n->op == TK_STAR) {
|
||||
/* &(*e) is e's value — no load. */
|
||||
cgexpr(c, n->lhs, locals);
|
||||
ins2(c, A_MOVQ, areg(D_AX), areg(dst_reg));
|
||||
return 1;
|
||||
}
|
||||
if (n->kind == N_INDEX) {
|
||||
Node *base = n->lhs;
|
||||
Node *idx = n->rhs;
|
||||
if (base == NULL || idx == NULL) return 0;
|
||||
/* Deref base only: ident/dot index bases all have
|
||||
* enumerated arms; routing them here would change
|
||||
* their asm. */
|
||||
if (!(base->kind == N_UN && base->op == TK_STAR))
|
||||
return 0;
|
||||
Type *bu = type_chase_named(base->type);
|
||||
if (bu == NULL) return 0;
|
||||
if (bu->kind != TY_SLICE && bu->kind != TY_ARRAY)
|
||||
return 0;
|
||||
Type *et = type_chase_named(n->type);
|
||||
if (et == NULL) return 0;
|
||||
int esz = (int)et->size;
|
||||
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));
|
||||
}
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
if (!cgplaceaddr(c, base, dst_reg, locals)) return 0;
|
||||
/* A slice place holds the {ptr,len,cap} header — the
|
||||
* element base is its .ptr word; an array place IS the
|
||||
* element storage. */
|
||||
if (bu->kind == TY_SLICE)
|
||||
ins2(c, A_MOVQ, amem(dst_reg, 0), areg(dst_reg));
|
||||
ins1(c, A_POPQ, areg(D_AX));
|
||||
ins2(c, A_ADDQ, areg(D_AX), areg(dst_reg));
|
||||
return 1;
|
||||
}
|
||||
if (n->kind == N_DOT) {
|
||||
Node *base = n->lhs;
|
||||
if (base == NULL) return 0;
|
||||
Type *bu = type_chase_named(base->type);
|
||||
if (bu == NULL) return 0;
|
||||
int viaptr = 0;
|
||||
Type *st = NULL;
|
||||
if (bu->kind == TY_PTR) {
|
||||
Type *p = type_chase_named(bu->sub);
|
||||
if (p && p->kind == TY_STRUCT) { st = p; viaptr = 1; }
|
||||
} else if (bu->kind == TY_STRUCT) {
|
||||
st = bu;
|
||||
}
|
||||
if (st == NULL) return 0;
|
||||
Tfield *f = NULL;
|
||||
for (Tfield *fl = st->fields; fl; fl = fl->next)
|
||||
if (strcmp(fl->name, n->str) == 0) { f = fl; break; }
|
||||
if (f == NULL) return 0;
|
||||
if (!cgplaceaddr(c, base, dst_reg, locals)) return 0;
|
||||
if (viaptr)
|
||||
ins2(c, A_MOVQ, amem(dst_reg, 0), areg(dst_reg));
|
||||
if ((int)f->offset != 0)
|
||||
ins2(c, A_ADDQ, aimm((int)f->offset), areg(dst_reg));
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* cg_structlit_fill modes — see helper docstring. */
|
||||
enum {
|
||||
DST_BP = 0,
|
||||
@@ -5167,7 +5249,14 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
int divop = op_for(n, A_DIVSD, A_DIVSS);
|
||||
int off = localfind(locals, n->lhs->str);
|
||||
int isglobal = (off == 0) && let_islet(n->lhs->str);
|
||||
if (off == 0 && !isglobal) break;
|
||||
/* Loud twin of the IDENT-tail unresolved-name stop
|
||||
* below (C1): wwstage resolves the name BEFORE its
|
||||
* float dispatch, so a silent break here would make
|
||||
* the stages disagree on the build verdict. */
|
||||
if (off == 0 && !isglobal)
|
||||
fatal("unsupported assign target: "
|
||||
"unresolved identifier '%s'",
|
||||
n->lhs->str);
|
||||
if (n->op == TK_ASSIGN) {
|
||||
if (off != 0) {
|
||||
ins2(c, mvop, areg(D_X0), amem(D_BP, off));
|
||||
@@ -5946,16 +6035,157 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
}
|
||||
}
|
||||
/* F6 (cgplaceaddr, commit C1): an N_DOT lvalue none of the
|
||||
* enumerated arms above matched — today the deref-rooted
|
||||
* spine `(*p)[i].f = v` / `OP= v`. Base-address derivation
|
||||
* routes through cgplaceaddr; the load/store emission stays
|
||||
* here. Any N_DOT shape the resolver can't address dies
|
||||
* LOUD below: the pre-C1 dispatch tail silently emitted
|
||||
* NOTHING (rhs unevaluated) for every such shape. */
|
||||
if (n->lhs && n->lhs->kind == N_DOT) {
|
||||
Type *ft = n->lhs->type;
|
||||
Type *fu = type_chase_named(ft);
|
||||
int fsz = (int)(ft ? ft->size : 8);
|
||||
int pa_isf32 = 0;
|
||||
if (fld_isfloat(ft, &pa_isf32))
|
||||
fatal("assign-resolver: float field not "
|
||||
"wired (rule-7)");
|
||||
if (fu && fu->kind == TY_TAGGED)
|
||||
fatal("assign-resolver: tagged field not "
|
||||
"wired (rule-7)");
|
||||
if (fu && (fu->kind == TY_STRUCT
|
||||
|| fu->kind == TY_ARRAY
|
||||
|| fu->kind == TY_TUPLE))
|
||||
fatal("assign-resolver: aggregate field not "
|
||||
"wired (rule-7)");
|
||||
if (fu && (fu->kind == TY_STR
|
||||
|| fu->kind == TY_SLICE)) {
|
||||
if (n->op != TK_ASSIGN)
|
||||
fatal("assign-resolver: compound on "
|
||||
"str/slice field not wired "
|
||||
"(rule-7)");
|
||||
/* str IS []u8: store the whole {ptr,len,cap}
|
||||
* triple from (AX,BX,CX); the place address
|
||||
* goes in DX so the three pops survive
|
||||
* (#1/Phase 3). */
|
||||
cgexpr(c, n->rhs, locals);
|
||||
ins1(c, A_PUSHQ, areg(D_CX));
|
||||
ins1(c, A_PUSHQ, areg(D_BX));
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
if (cgplaceaddr(c, n->lhs, D_DX, locals)) {
|
||||
ins1(c, A_POPQ, areg(D_AX));
|
||||
ins1(c, A_POPQ, areg(D_BX));
|
||||
ins1(c, A_POPQ, areg(D_CX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_DX, 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_DX, 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_DX, 16));
|
||||
break;
|
||||
}
|
||||
} else if (n->op == TK_ASSIGN) {
|
||||
cgexpr(c, n->rhs, locals);
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
if (cgplaceaddr(c, n->lhs, D_BX, locals)) {
|
||||
ins1(c, A_POPQ, areg(D_AX));
|
||||
ins2(c, fldstoreop(ft, fsz),
|
||||
areg(D_AX), amem(D_BX, 0));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
/* Compound: AX=old, CX=rhs, BX=addr — the
|
||||
* same register roles as the chained-ptr-
|
||||
* field compound template above. */
|
||||
cgexpr(c, n->rhs, locals);
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
if (cgplaceaddr(c, n->lhs, D_BX, locals)) {
|
||||
ins2(c, fldloadop(ft, fsz),
|
||||
amem(D_BX, 0), areg(D_AX));
|
||||
ins1(c, A_POPQ, areg(D_CX));
|
||||
int unsignd = type_isunsigned(ft);
|
||||
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;
|
||||
case TK_SLASHEQ:
|
||||
if (unsignd)
|
||||
ins2(c, A_MOVQ,
|
||||
aimm(0),
|
||||
areg(D_DX));
|
||||
else
|
||||
ins0(c, A_CQO);
|
||||
ins1(c, unsignd ? A_DIVQ
|
||||
: A_IDIVQ, areg(D_CX));
|
||||
break;
|
||||
case TK_PERCENTEQ:
|
||||
if (unsignd)
|
||||
ins2(c, A_MOVQ,
|
||||
aimm(0),
|
||||
areg(D_DX));
|
||||
else
|
||||
ins0(c, A_CQO);
|
||||
ins1(c, unsignd ? A_DIVQ
|
||||
: A_IDIVQ, areg(D_CX));
|
||||
ins2(c, A_MOVQ, areg(D_DX),
|
||||
areg(D_AX));
|
||||
break;
|
||||
case TK_LSHIFTEQ:
|
||||
ins2(c, A_SHLQ, areg(D_CX),
|
||||
areg(D_AX));
|
||||
break;
|
||||
case TK_RSHIFTEQ:
|
||||
ins2(c, unsignd ? A_SHRQ
|
||||
: A_SARQ, areg(D_CX),
|
||||
areg(D_AX));
|
||||
break;
|
||||
default:
|
||||
fatal("assign-resolver: "
|
||||
"unknown compound op "
|
||||
"(rule-7)");
|
||||
}
|
||||
ins2(c, fldstoreop(ft, fsz),
|
||||
areg(D_AX), amem(D_BX, 0));
|
||||
break;
|
||||
}
|
||||
}
|
||||
fatal("unsupported assign target shape");
|
||||
}
|
||||
if (n->lhs->kind == N_IDENT) {
|
||||
int off = localfind(locals, n->lhs->str);
|
||||
if (off == 0) {
|
||||
/* Top-level let target — RIP-relative store
|
||||
* (or load→combine→store for compound). Names
|
||||
* we don't recognise as scalar lets fall through
|
||||
* to the existing drop behaviour, which produces
|
||||
* a clean link-time undefined-symbol error if
|
||||
* the binding was ever supposed to exist. */
|
||||
if (!let_islet(n->lhs->str)) break;
|
||||
* (or load→combine→store for compound). A
|
||||
* name that is neither a local nor a let
|
||||
* dies LOUD: the pre-C1 break dropped the
|
||||
* whole statement silently (no symbol was
|
||||
* ever referenced, so not even a link error
|
||||
* surfaced). */
|
||||
if (!let_islet(n->lhs->str))
|
||||
fatal("unsupported assign target: "
|
||||
"unresolved identifier '%s'",
|
||||
n->lhs->str);
|
||||
cgexpr(c, n->rhs, locals);
|
||||
if (n->op == TK_ASSIGN) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
@@ -6113,6 +6343,13 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off));
|
||||
skip_assign_store: ;
|
||||
}
|
||||
/* C1 residual (task #22): a non-DOT lvalue no arm above
|
||||
* matched still falls out SILENT here — the known member is
|
||||
* the str-base element store family (`s[i] = v`: cstage
|
||||
* drops, wwstage emits MOVB; pre-existing gate-blind
|
||||
* divergence) plus tuple-member writes. The tail goes loud
|
||||
* for the remaining kinds with #22, after the family gets a
|
||||
* symmetric verdict. */
|
||||
break;
|
||||
}
|
||||
case N_CALL: {
|
||||
|
||||
@@ -21209,6 +21209,124 @@ fn aggargsrcaddr(c: *cgen, src: *node, dst: str) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// cgplaceaddr — compute the ADDRESS of an arbitrary place (lvalue)
|
||||
// expression into dstreg; returns true when the shape is wired, false
|
||||
// otherwise (the caller loud-stops — rule 7, never a silent drop).
|
||||
// F6 resolver, commit C1 — mirror of cstage cmd/w6c/cgen.c
|
||||
// cgplaceaddr: only the deref-rooted spine is wired (`(*p)[i].f` as
|
||||
// N_UN(STAR) root, N_INDEX hop over a slice/array place, N_DOT
|
||||
// struct-field hop with one deref for a *struct base). All type keys
|
||||
// come off the checker-STAMPED tinfo (.type_), never tnode names —
|
||||
// the #209/#211 discipline. Ident-rooted spines stay with the
|
||||
// enumerated cgassign arms so this resolver never perturbs their
|
||||
// asm. ADDRESS COMPUTATION ONLY — every call-site keeps its own
|
||||
// load/store/copy emission. Clobbers AX/CX (cgexpr on index /
|
||||
// pointer operands) and balances its own PUSHQ/POPQ; dstreg must
|
||||
// not be AX or CX.
|
||||
fn cgplaceaddr(c: *cgen, n: *node, dstreg: str) bool = {
|
||||
if (n == nil) { return false; };
|
||||
if (n.kind == nkind.N_UN) {
|
||||
if (n.op != tkind.TK_STAR) { return false; };
|
||||
// &(*e) is e's value — no load.
|
||||
cgexpr(c, n.lhs);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
return true;
|
||||
};
|
||||
if (n.kind == nkind.N_INDEX) {
|
||||
let base: *node = n.lhs;
|
||||
let idx: *node = n.rhs;
|
||||
if (base == nil || idx == nil) { return false; };
|
||||
// Deref base only: ident/dot index bases all have
|
||||
// enumerated arms; routing them here would change
|
||||
// their asm.
|
||||
if (base.kind != nkind.N_UN) { return false; };
|
||||
if (base.op != tkind.TK_STAR) { return false; };
|
||||
let bu: *tinfo = base.type_: *tinfo;
|
||||
for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
|
||||
if (bu == nil) { return false; };
|
||||
if (bu.kind != tykind.TY_SLICE && bu.kind != tykind.TY_ARRAY) {
|
||||
return false;
|
||||
};
|
||||
let et: *tinfo = n.type_: *tinfo;
|
||||
for (et != nil && et.kind == tykind.TY_NAMED) { et = et.under; };
|
||||
if (et == nil) { return false; };
|
||||
let esz: i32 = et.size: i32;
|
||||
cgexpr(c, idx);
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (!cgplaceaddr(c, base, dstreg)) { return false; };
|
||||
// A slice place holds the {ptr,len,cap} header — the
|
||||
// element base is its .ptr word; an array place IS the
|
||||
// element storage.
|
||||
if (bu.kind == tykind.TY_SLICE) {
|
||||
emitline("\tMOVQ\t(");
|
||||
emitline(dstreg);
|
||||
emitline("), ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
};
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
emitline("\tADDQ\tAX, ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
return true;
|
||||
};
|
||||
if (n.kind == nkind.N_DOT) {
|
||||
let base: *node = n.lhs;
|
||||
if (base == nil) { return false; };
|
||||
let bu: *tinfo = base.type_: *tinfo;
|
||||
for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
|
||||
if (bu == nil) { return false; };
|
||||
let viaptr: bool = false;
|
||||
let st: *tinfo = nil;
|
||||
if (bu.kind == tykind.TY_PTR) {
|
||||
let p: *tinfo = bu.sub;
|
||||
for (p != nil && p.kind == tykind.TY_NAMED) { p = p.under; };
|
||||
if (p != nil) { if (p.kind == tykind.TY_STRUCT) {
|
||||
st = p;
|
||||
viaptr = true;
|
||||
}; };
|
||||
} else { if (bu.kind == tykind.TY_STRUCT) {
|
||||
st = bu;
|
||||
}; };
|
||||
if (st == nil) { return false; };
|
||||
let f: *tfield = st.fields;
|
||||
let foff: i64 = -1;
|
||||
for (f != nil) {
|
||||
if (streq(f.name, n.str)) {
|
||||
foff = f.offset: i64;
|
||||
break;
|
||||
};
|
||||
f = f.tnext;
|
||||
};
|
||||
if (foff < 0) { return false; };
|
||||
if (!cgplaceaddr(c, base, dstreg)) { return false; };
|
||||
if (viaptr) {
|
||||
emitline("\tMOVQ\t(");
|
||||
emitline(dstreg);
|
||||
emitline("), ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
};
|
||||
if (foff != 0) {
|
||||
emitline("\tADDQ\t$");
|
||||
emitint(foff);
|
||||
emitline(", ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
};
|
||||
return true;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
fn cgindex(c: *cgen, n: *node) void = {
|
||||
// Element-size-aware load: u8 → MOVZBQ, i32 → MOVSXD, u32 → MOVL,
|
||||
// str → (ptr, len) into (AX, BX), everything else → MOVQ. Fast
|
||||
@@ -28076,7 +28194,18 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
// halves (plus cap for slice — stashed via
|
||||
// DI since LEAQ overwrites CX); the asm has
|
||||
// no `name+8(SB)` operand form.
|
||||
if (!isletvar(c, nm)) { return; };
|
||||
// C1: a name that is neither a local nor a
|
||||
// let dies LOUD — the pre-C1 return dropped
|
||||
// the whole statement silently (cstage twin:
|
||||
// the N_ASSIGN IDENT-tail fatal).
|
||||
if (!isletvar(c, nm)) {
|
||||
let mi1: str = "unsupported assign target: unresolved identifier '";
|
||||
os.write(2, mi1.ptr, mi1.len: u64);
|
||||
os.write(2, nm.ptr, nm.len: u64);
|
||||
let mi2: str = "'\n";
|
||||
os.write(2, mi2.ptr, mi2.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
// Float global: rhs lands in X0; store via
|
||||
// LEAQ+indirect since MOVSS/MOVSD have no
|
||||
// D_EXTERN operand form.
|
||||
@@ -28710,6 +28839,147 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
};
|
||||
// F6 (cgplaceaddr, commit C1): an N_DOT lvalue none of the
|
||||
// enumerated arms above matched — today the deref-rooted spine
|
||||
// `(*p)[i].f = v` / `OP= v`. Base-address derivation routes
|
||||
// through cgplaceaddr; the load/store emission stays here. Any
|
||||
// N_DOT shape the resolver can't address dies LOUD below: the
|
||||
// pre-C1 fall-off-the-function tail silently emitted NOTHING
|
||||
// (rhs unevaluated). Mirror of the cstage cgen.c N_ASSIGN arm.
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_DOT) {
|
||||
let ft: *tinfo = lhs.type_: *tinfo;
|
||||
let fu: *tinfo = ft;
|
||||
for (fu != nil && fu.kind == tykind.TY_NAMED) {
|
||||
fu = fu.under;
|
||||
};
|
||||
let fsz: i32 = 8;
|
||||
if (ft != nil) { fsz = ft.size: i32; };
|
||||
if (typeisfloat(ft)) {
|
||||
let mf: str = "assign-resolver: float field not wired (rule-7)\n";
|
||||
os.write(2, mf.ptr, mf.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (fu != nil) {
|
||||
if (fu.kind == tykind.TY_TAGGED) {
|
||||
let mt: str = "assign-resolver: tagged field not wired (rule-7)\n";
|
||||
os.write(2, mt.ptr, mt.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (fu.kind == tykind.TY_STRUCT
|
||||
|| fu.kind == tykind.TY_ARRAY
|
||||
|| fu.kind == tykind.TY_TUPLE) {
|
||||
let ma: str = "assign-resolver: aggregate field not wired (rule-7)\n";
|
||||
os.write(2, ma.ptr, ma.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
let fstrsl: bool = false;
|
||||
if (fu != nil) {
|
||||
if (fu.kind == tykind.TY_STR
|
||||
|| fu.kind == tykind.TY_SLICE) {
|
||||
fstrsl = true;
|
||||
};
|
||||
};
|
||||
if (fstrsl) {
|
||||
if (n.op != tkind.TK_ASSIGN) {
|
||||
let ms: str = "assign-resolver: compound on str/slice field not wired (rule-7)\n";
|
||||
os.write(2, ms.ptr, ms.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
// str IS []u8: store the whole {ptr,len,cap}
|
||||
// triple from (AX,BX,CX); the place address
|
||||
// goes in DX so the three pops survive
|
||||
// (#1/Phase 3).
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tCX\n");
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (cgplaceaddr(c, lhs, "DX")) {
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tPOPQ\tCX\n");
|
||||
emitline("\tMOVQ\tAX, (DX)\n");
|
||||
emitline("\tMOVQ\tBX, 8(DX)\n");
|
||||
emitline("\tMOVQ\tCX, 16(DX)\n");
|
||||
return;
|
||||
};
|
||||
} else { if (n.op == tkind.TK_ASSIGN) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (cgplaceaddr(c, lhs, "BX")) {
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
let sop: str = "MOVQ";
|
||||
if (fsz == 1) { sop = "MOVB"; };
|
||||
if (fsz == 2) { sop = "MOVW"; };
|
||||
if (fsz == 4) { sop = "MOVL"; };
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, (BX)\n");
|
||||
return;
|
||||
};
|
||||
} else {
|
||||
// Compound: AX=old, CX=rhs, BX=addr — the same
|
||||
// register roles as the chained-ptr-field
|
||||
// compound template above.
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (cgplaceaddr(c, lhs, "BX")) {
|
||||
let lop: str = loadopsz(typeissigned(ft), fsz);
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t(BX), AX\n");
|
||||
emitline("\tPOPQ\tCX\n");
|
||||
let unsignd: bool = typeisunsigned(ft);
|
||||
let wired: bool = false;
|
||||
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_CARETEQ) { emitline("\tXORQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_SLASHEQ) {
|
||||
if (unsignd) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); }
|
||||
else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); };
|
||||
wired = true;
|
||||
};
|
||||
if (n.op == tkind.TK_PERCENTEQ) {
|
||||
if (unsignd) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); }
|
||||
else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); };
|
||||
emitline("\tMOVQ\tDX, AX\n");
|
||||
wired = true;
|
||||
};
|
||||
if (n.op == tkind.TK_LSHIFTEQ) { emitline("\tSHLQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_RSHIFTEQ) {
|
||||
if (unsignd) { emitline("\tSHRQ\tCX, AX\n"); }
|
||||
else { emitline("\tSARQ\tCX, AX\n"); };
|
||||
wired = true;
|
||||
};
|
||||
if (!wired) {
|
||||
let mu: str = "assign-resolver: unknown compound op (rule-7)\n";
|
||||
os.write(2, mu.ptr, mu.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let sop: str = "MOVQ";
|
||||
if (fsz == 1) { sop = "MOVB"; };
|
||||
if (fsz == 2) { sop = "MOVW"; };
|
||||
if (fsz == 4) { sop = "MOVL"; };
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, (BX)\n");
|
||||
return;
|
||||
};
|
||||
}; };
|
||||
let mtl: str = "unsupported assign target shape\n";
|
||||
os.write(2, mtl.ptr, mtl.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
// C1 residual (task #22): a non-DOT lvalue no arm above matched
|
||||
// still falls out SILENT here — known member: the str-base element
|
||||
// store family (`s[i] = v`: cstage drops, wwstage emits MOVB;
|
||||
// pre-existing gate-blind divergence) plus tuple-member writes.
|
||||
// The tail goes loud for the remaining kinds with #22.
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -1299,6 +1299,124 @@ fn aggargsrcaddr(c: *cgen, src: *node, dst: str) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// cgplaceaddr — compute the ADDRESS of an arbitrary place (lvalue)
|
||||
// expression into dstreg; returns true when the shape is wired, false
|
||||
// otherwise (the caller loud-stops — rule 7, never a silent drop).
|
||||
// F6 resolver, commit C1 — mirror of cstage cmd/w6c/cgen.c
|
||||
// cgplaceaddr: only the deref-rooted spine is wired (`(*p)[i].f` as
|
||||
// N_UN(STAR) root, N_INDEX hop over a slice/array place, N_DOT
|
||||
// struct-field hop with one deref for a *struct base). All type keys
|
||||
// come off the checker-STAMPED tinfo (.type_), never tnode names —
|
||||
// the #209/#211 discipline. Ident-rooted spines stay with the
|
||||
// enumerated cgassign arms so this resolver never perturbs their
|
||||
// asm. ADDRESS COMPUTATION ONLY — every call-site keeps its own
|
||||
// load/store/copy emission. Clobbers AX/CX (cgexpr on index /
|
||||
// pointer operands) and balances its own PUSHQ/POPQ; dstreg must
|
||||
// not be AX or CX.
|
||||
fn cgplaceaddr(c: *cgen, n: *node, dstreg: str) bool = {
|
||||
if (n == nil) { return false; };
|
||||
if (n.kind == nkind.N_UN) {
|
||||
if (n.op != tkind.TK_STAR) { return false; };
|
||||
// &(*e) is e's value — no load.
|
||||
cgexpr(c, n.lhs);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
return true;
|
||||
};
|
||||
if (n.kind == nkind.N_INDEX) {
|
||||
let base: *node = n.lhs;
|
||||
let idx: *node = n.rhs;
|
||||
if (base == nil || idx == nil) { return false; };
|
||||
// Deref base only: ident/dot index bases all have
|
||||
// enumerated arms; routing them here would change
|
||||
// their asm.
|
||||
if (base.kind != nkind.N_UN) { return false; };
|
||||
if (base.op != tkind.TK_STAR) { return false; };
|
||||
let bu: *tinfo = base.type_: *tinfo;
|
||||
for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
|
||||
if (bu == nil) { return false; };
|
||||
if (bu.kind != tykind.TY_SLICE && bu.kind != tykind.TY_ARRAY) {
|
||||
return false;
|
||||
};
|
||||
let et: *tinfo = n.type_: *tinfo;
|
||||
for (et != nil && et.kind == tykind.TY_NAMED) { et = et.under; };
|
||||
if (et == nil) { return false; };
|
||||
let esz: i32 = et.size: i32;
|
||||
cgexpr(c, idx);
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (!cgplaceaddr(c, base, dstreg)) { return false; };
|
||||
// A slice place holds the {ptr,len,cap} header — the
|
||||
// element base is its .ptr word; an array place IS the
|
||||
// element storage.
|
||||
if (bu.kind == tykind.TY_SLICE) {
|
||||
emitline("\tMOVQ\t(");
|
||||
emitline(dstreg);
|
||||
emitline("), ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
};
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
emitline("\tADDQ\tAX, ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
return true;
|
||||
};
|
||||
if (n.kind == nkind.N_DOT) {
|
||||
let base: *node = n.lhs;
|
||||
if (base == nil) { return false; };
|
||||
let bu: *tinfo = base.type_: *tinfo;
|
||||
for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
|
||||
if (bu == nil) { return false; };
|
||||
let viaptr: bool = false;
|
||||
let st: *tinfo = nil;
|
||||
if (bu.kind == tykind.TY_PTR) {
|
||||
let p: *tinfo = bu.sub;
|
||||
for (p != nil && p.kind == tykind.TY_NAMED) { p = p.under; };
|
||||
if (p != nil) { if (p.kind == tykind.TY_STRUCT) {
|
||||
st = p;
|
||||
viaptr = true;
|
||||
}; };
|
||||
} else { if (bu.kind == tykind.TY_STRUCT) {
|
||||
st = bu;
|
||||
}; };
|
||||
if (st == nil) { return false; };
|
||||
let f: *tfield = st.fields;
|
||||
let foff: i64 = -1;
|
||||
for (f != nil) {
|
||||
if (streq(f.name, n.str)) {
|
||||
foff = f.offset: i64;
|
||||
break;
|
||||
};
|
||||
f = f.tnext;
|
||||
};
|
||||
if (foff < 0) { return false; };
|
||||
if (!cgplaceaddr(c, base, dstreg)) { return false; };
|
||||
if (viaptr) {
|
||||
emitline("\tMOVQ\t(");
|
||||
emitline(dstreg);
|
||||
emitline("), ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
};
|
||||
if (foff != 0) {
|
||||
emitline("\tADDQ\t$");
|
||||
emitint(foff);
|
||||
emitline(", ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
};
|
||||
return true;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
fn cgindex(c: *cgen, n: *node) void = {
|
||||
// Element-size-aware load: u8 → MOVZBQ, i32 → MOVSXD, u32 → MOVL,
|
||||
// str → (ptr, len) into (AX, BX), everything else → MOVQ. Fast
|
||||
@@ -8166,7 +8284,18 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
// halves (plus cap for slice — stashed via
|
||||
// DI since LEAQ overwrites CX); the asm has
|
||||
// no `name+8(SB)` operand form.
|
||||
if (!isletvar(c, nm)) { return; };
|
||||
// C1: a name that is neither a local nor a
|
||||
// let dies LOUD — the pre-C1 return dropped
|
||||
// the whole statement silently (cstage twin:
|
||||
// the N_ASSIGN IDENT-tail fatal).
|
||||
if (!isletvar(c, nm)) {
|
||||
let mi1: str = "unsupported assign target: unresolved identifier '";
|
||||
os.write(2, mi1.ptr, mi1.len: u64);
|
||||
os.write(2, nm.ptr, nm.len: u64);
|
||||
let mi2: str = "'\n";
|
||||
os.write(2, mi2.ptr, mi2.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
// Float global: rhs lands in X0; store via
|
||||
// LEAQ+indirect since MOVSS/MOVSD have no
|
||||
// D_EXTERN operand form.
|
||||
@@ -8800,6 +8929,147 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
};
|
||||
// F6 (cgplaceaddr, commit C1): an N_DOT lvalue none of the
|
||||
// enumerated arms above matched — today the deref-rooted spine
|
||||
// `(*p)[i].f = v` / `OP= v`. Base-address derivation routes
|
||||
// through cgplaceaddr; the load/store emission stays here. Any
|
||||
// N_DOT shape the resolver can't address dies LOUD below: the
|
||||
// pre-C1 fall-off-the-function tail silently emitted NOTHING
|
||||
// (rhs unevaluated). Mirror of the cstage cgen.c N_ASSIGN arm.
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_DOT) {
|
||||
let ft: *tinfo = lhs.type_: *tinfo;
|
||||
let fu: *tinfo = ft;
|
||||
for (fu != nil && fu.kind == tykind.TY_NAMED) {
|
||||
fu = fu.under;
|
||||
};
|
||||
let fsz: i32 = 8;
|
||||
if (ft != nil) { fsz = ft.size: i32; };
|
||||
if (typeisfloat(ft)) {
|
||||
let mf: str = "assign-resolver: float field not wired (rule-7)\n";
|
||||
os.write(2, mf.ptr, mf.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (fu != nil) {
|
||||
if (fu.kind == tykind.TY_TAGGED) {
|
||||
let mt: str = "assign-resolver: tagged field not wired (rule-7)\n";
|
||||
os.write(2, mt.ptr, mt.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (fu.kind == tykind.TY_STRUCT
|
||||
|| fu.kind == tykind.TY_ARRAY
|
||||
|| fu.kind == tykind.TY_TUPLE) {
|
||||
let ma: str = "assign-resolver: aggregate field not wired (rule-7)\n";
|
||||
os.write(2, ma.ptr, ma.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
let fstrsl: bool = false;
|
||||
if (fu != nil) {
|
||||
if (fu.kind == tykind.TY_STR
|
||||
|| fu.kind == tykind.TY_SLICE) {
|
||||
fstrsl = true;
|
||||
};
|
||||
};
|
||||
if (fstrsl) {
|
||||
if (n.op != tkind.TK_ASSIGN) {
|
||||
let ms: str = "assign-resolver: compound on str/slice field not wired (rule-7)\n";
|
||||
os.write(2, ms.ptr, ms.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
// str IS []u8: store the whole {ptr,len,cap}
|
||||
// triple from (AX,BX,CX); the place address
|
||||
// goes in DX so the three pops survive
|
||||
// (#1/Phase 3).
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tCX\n");
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (cgplaceaddr(c, lhs, "DX")) {
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tPOPQ\tCX\n");
|
||||
emitline("\tMOVQ\tAX, (DX)\n");
|
||||
emitline("\tMOVQ\tBX, 8(DX)\n");
|
||||
emitline("\tMOVQ\tCX, 16(DX)\n");
|
||||
return;
|
||||
};
|
||||
} else { if (n.op == tkind.TK_ASSIGN) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (cgplaceaddr(c, lhs, "BX")) {
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
let sop: str = "MOVQ";
|
||||
if (fsz == 1) { sop = "MOVB"; };
|
||||
if (fsz == 2) { sop = "MOVW"; };
|
||||
if (fsz == 4) { sop = "MOVL"; };
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, (BX)\n");
|
||||
return;
|
||||
};
|
||||
} else {
|
||||
// Compound: AX=old, CX=rhs, BX=addr — the same
|
||||
// register roles as the chained-ptr-field
|
||||
// compound template above.
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (cgplaceaddr(c, lhs, "BX")) {
|
||||
let lop: str = loadopsz(typeissigned(ft), fsz);
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t(BX), AX\n");
|
||||
emitline("\tPOPQ\tCX\n");
|
||||
let unsignd: bool = typeisunsigned(ft);
|
||||
let wired: bool = false;
|
||||
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_CARETEQ) { emitline("\tXORQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_SLASHEQ) {
|
||||
if (unsignd) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); }
|
||||
else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); };
|
||||
wired = true;
|
||||
};
|
||||
if (n.op == tkind.TK_PERCENTEQ) {
|
||||
if (unsignd) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); }
|
||||
else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); };
|
||||
emitline("\tMOVQ\tDX, AX\n");
|
||||
wired = true;
|
||||
};
|
||||
if (n.op == tkind.TK_LSHIFTEQ) { emitline("\tSHLQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_RSHIFTEQ) {
|
||||
if (unsignd) { emitline("\tSHRQ\tCX, AX\n"); }
|
||||
else { emitline("\tSARQ\tCX, AX\n"); };
|
||||
wired = true;
|
||||
};
|
||||
if (!wired) {
|
||||
let mu: str = "assign-resolver: unknown compound op (rule-7)\n";
|
||||
os.write(2, mu.ptr, mu.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let sop: str = "MOVQ";
|
||||
if (fsz == 1) { sop = "MOVB"; };
|
||||
if (fsz == 2) { sop = "MOVW"; };
|
||||
if (fsz == 4) { sop = "MOVL"; };
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, (BX)\n");
|
||||
return;
|
||||
};
|
||||
}; };
|
||||
let mtl: str = "unsupported assign target shape\n";
|
||||
os.write(2, mtl.ptr, mtl.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
// C1 residual (task #22): a non-DOT lvalue no arm above matched
|
||||
// still falls out SILENT here — known member: the str-base element
|
||||
// store family (`s[i] = v`: cstage drops, wwstage emits MOVB;
|
||||
// pre-existing gate-blind divergence) plus tuple-member writes.
|
||||
// The tail goes loud for the remaining kinds with #22.
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
@@ -21209,6 +21209,124 @@ fn aggargsrcaddr(c: *cgen, src: *node, dst: str) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// cgplaceaddr — compute the ADDRESS of an arbitrary place (lvalue)
|
||||
// expression into dstreg; returns true when the shape is wired, false
|
||||
// otherwise (the caller loud-stops — rule 7, never a silent drop).
|
||||
// F6 resolver, commit C1 — mirror of cstage cmd/w6c/cgen.c
|
||||
// cgplaceaddr: only the deref-rooted spine is wired (`(*p)[i].f` as
|
||||
// N_UN(STAR) root, N_INDEX hop over a slice/array place, N_DOT
|
||||
// struct-field hop with one deref for a *struct base). All type keys
|
||||
// come off the checker-STAMPED tinfo (.type_), never tnode names —
|
||||
// the #209/#211 discipline. Ident-rooted spines stay with the
|
||||
// enumerated cgassign arms so this resolver never perturbs their
|
||||
// asm. ADDRESS COMPUTATION ONLY — every call-site keeps its own
|
||||
// load/store/copy emission. Clobbers AX/CX (cgexpr on index /
|
||||
// pointer operands) and balances its own PUSHQ/POPQ; dstreg must
|
||||
// not be AX or CX.
|
||||
fn cgplaceaddr(c: *cgen, n: *node, dstreg: str) bool = {
|
||||
if (n == nil) { return false; };
|
||||
if (n.kind == nkind.N_UN) {
|
||||
if (n.op != tkind.TK_STAR) { return false; };
|
||||
// &(*e) is e's value — no load.
|
||||
cgexpr(c, n.lhs);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
return true;
|
||||
};
|
||||
if (n.kind == nkind.N_INDEX) {
|
||||
let base: *node = n.lhs;
|
||||
let idx: *node = n.rhs;
|
||||
if (base == nil || idx == nil) { return false; };
|
||||
// Deref base only: ident/dot index bases all have
|
||||
// enumerated arms; routing them here would change
|
||||
// their asm.
|
||||
if (base.kind != nkind.N_UN) { return false; };
|
||||
if (base.op != tkind.TK_STAR) { return false; };
|
||||
let bu: *tinfo = base.type_: *tinfo;
|
||||
for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
|
||||
if (bu == nil) { return false; };
|
||||
if (bu.kind != tykind.TY_SLICE && bu.kind != tykind.TY_ARRAY) {
|
||||
return false;
|
||||
};
|
||||
let et: *tinfo = n.type_: *tinfo;
|
||||
for (et != nil && et.kind == tykind.TY_NAMED) { et = et.under; };
|
||||
if (et == nil) { return false; };
|
||||
let esz: i32 = et.size: i32;
|
||||
cgexpr(c, idx);
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", CX\n");
|
||||
emitline("\tIMULQ\tCX, AX\n");
|
||||
};
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (!cgplaceaddr(c, base, dstreg)) { return false; };
|
||||
// A slice place holds the {ptr,len,cap} header — the
|
||||
// element base is its .ptr word; an array place IS the
|
||||
// element storage.
|
||||
if (bu.kind == tykind.TY_SLICE) {
|
||||
emitline("\tMOVQ\t(");
|
||||
emitline(dstreg);
|
||||
emitline("), ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
};
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
emitline("\tADDQ\tAX, ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
return true;
|
||||
};
|
||||
if (n.kind == nkind.N_DOT) {
|
||||
let base: *node = n.lhs;
|
||||
if (base == nil) { return false; };
|
||||
let bu: *tinfo = base.type_: *tinfo;
|
||||
for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
|
||||
if (bu == nil) { return false; };
|
||||
let viaptr: bool = false;
|
||||
let st: *tinfo = nil;
|
||||
if (bu.kind == tykind.TY_PTR) {
|
||||
let p: *tinfo = bu.sub;
|
||||
for (p != nil && p.kind == tykind.TY_NAMED) { p = p.under; };
|
||||
if (p != nil) { if (p.kind == tykind.TY_STRUCT) {
|
||||
st = p;
|
||||
viaptr = true;
|
||||
}; };
|
||||
} else { if (bu.kind == tykind.TY_STRUCT) {
|
||||
st = bu;
|
||||
}; };
|
||||
if (st == nil) { return false; };
|
||||
let f: *tfield = st.fields;
|
||||
let foff: i64 = -1;
|
||||
for (f != nil) {
|
||||
if (streq(f.name, n.str)) {
|
||||
foff = f.offset: i64;
|
||||
break;
|
||||
};
|
||||
f = f.tnext;
|
||||
};
|
||||
if (foff < 0) { return false; };
|
||||
if (!cgplaceaddr(c, base, dstreg)) { return false; };
|
||||
if (viaptr) {
|
||||
emitline("\tMOVQ\t(");
|
||||
emitline(dstreg);
|
||||
emitline("), ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
};
|
||||
if (foff != 0) {
|
||||
emitline("\tADDQ\t$");
|
||||
emitint(foff);
|
||||
emitline(", ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
};
|
||||
return true;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
fn cgindex(c: *cgen, n: *node) void = {
|
||||
// Element-size-aware load: u8 → MOVZBQ, i32 → MOVSXD, u32 → MOVL,
|
||||
// str → (ptr, len) into (AX, BX), everything else → MOVQ. Fast
|
||||
@@ -28076,7 +28194,18 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
// halves (plus cap for slice — stashed via
|
||||
// DI since LEAQ overwrites CX); the asm has
|
||||
// no `name+8(SB)` operand form.
|
||||
if (!isletvar(c, nm)) { return; };
|
||||
// C1: a name that is neither a local nor a
|
||||
// let dies LOUD — the pre-C1 return dropped
|
||||
// the whole statement silently (cstage twin:
|
||||
// the N_ASSIGN IDENT-tail fatal).
|
||||
if (!isletvar(c, nm)) {
|
||||
let mi1: str = "unsupported assign target: unresolved identifier '";
|
||||
os.write(2, mi1.ptr, mi1.len: u64);
|
||||
os.write(2, nm.ptr, nm.len: u64);
|
||||
let mi2: str = "'\n";
|
||||
os.write(2, mi2.ptr, mi2.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
// Float global: rhs lands in X0; store via
|
||||
// LEAQ+indirect since MOVSS/MOVSD have no
|
||||
// D_EXTERN operand form.
|
||||
@@ -28710,6 +28839,147 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
};
|
||||
// F6 (cgplaceaddr, commit C1): an N_DOT lvalue none of the
|
||||
// enumerated arms above matched — today the deref-rooted spine
|
||||
// `(*p)[i].f = v` / `OP= v`. Base-address derivation routes
|
||||
// through cgplaceaddr; the load/store emission stays here. Any
|
||||
// N_DOT shape the resolver can't address dies LOUD below: the
|
||||
// pre-C1 fall-off-the-function tail silently emitted NOTHING
|
||||
// (rhs unevaluated). Mirror of the cstage cgen.c N_ASSIGN arm.
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_DOT) {
|
||||
let ft: *tinfo = lhs.type_: *tinfo;
|
||||
let fu: *tinfo = ft;
|
||||
for (fu != nil && fu.kind == tykind.TY_NAMED) {
|
||||
fu = fu.under;
|
||||
};
|
||||
let fsz: i32 = 8;
|
||||
if (ft != nil) { fsz = ft.size: i32; };
|
||||
if (typeisfloat(ft)) {
|
||||
let mf: str = "assign-resolver: float field not wired (rule-7)\n";
|
||||
os.write(2, mf.ptr, mf.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (fu != nil) {
|
||||
if (fu.kind == tykind.TY_TAGGED) {
|
||||
let mt: str = "assign-resolver: tagged field not wired (rule-7)\n";
|
||||
os.write(2, mt.ptr, mt.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (fu.kind == tykind.TY_STRUCT
|
||||
|| fu.kind == tykind.TY_ARRAY
|
||||
|| fu.kind == tykind.TY_TUPLE) {
|
||||
let ma: str = "assign-resolver: aggregate field not wired (rule-7)\n";
|
||||
os.write(2, ma.ptr, ma.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
let fstrsl: bool = false;
|
||||
if (fu != nil) {
|
||||
if (fu.kind == tykind.TY_STR
|
||||
|| fu.kind == tykind.TY_SLICE) {
|
||||
fstrsl = true;
|
||||
};
|
||||
};
|
||||
if (fstrsl) {
|
||||
if (n.op != tkind.TK_ASSIGN) {
|
||||
let ms: str = "assign-resolver: compound on str/slice field not wired (rule-7)\n";
|
||||
os.write(2, ms.ptr, ms.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
// str IS []u8: store the whole {ptr,len,cap}
|
||||
// triple from (AX,BX,CX); the place address
|
||||
// goes in DX so the three pops survive
|
||||
// (#1/Phase 3).
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tCX\n");
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (cgplaceaddr(c, lhs, "DX")) {
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tPOPQ\tCX\n");
|
||||
emitline("\tMOVQ\tAX, (DX)\n");
|
||||
emitline("\tMOVQ\tBX, 8(DX)\n");
|
||||
emitline("\tMOVQ\tCX, 16(DX)\n");
|
||||
return;
|
||||
};
|
||||
} else { if (n.op == tkind.TK_ASSIGN) {
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (cgplaceaddr(c, lhs, "BX")) {
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
let sop: str = "MOVQ";
|
||||
if (fsz == 1) { sop = "MOVB"; };
|
||||
if (fsz == 2) { sop = "MOVW"; };
|
||||
if (fsz == 4) { sop = "MOVL"; };
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, (BX)\n");
|
||||
return;
|
||||
};
|
||||
} else {
|
||||
// Compound: AX=old, CX=rhs, BX=addr — the same
|
||||
// register roles as the chained-ptr-field
|
||||
// compound template above.
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (cgplaceaddr(c, lhs, "BX")) {
|
||||
let lop: str = loadopsz(typeissigned(ft), fsz);
|
||||
emitline("\t");
|
||||
emitline(lop);
|
||||
emitline("\t(BX), AX\n");
|
||||
emitline("\tPOPQ\tCX\n");
|
||||
let unsignd: bool = typeisunsigned(ft);
|
||||
let wired: bool = false;
|
||||
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_MINUSEQ) { emitline("\tSUBQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_STAREQ) { emitline("\tIMULQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_AMPEQ) { emitline("\tANDQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_PIPEEQ) { emitline("\tORQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_CARETEQ) { emitline("\tXORQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_SLASHEQ) {
|
||||
if (unsignd) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); }
|
||||
else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); };
|
||||
wired = true;
|
||||
};
|
||||
if (n.op == tkind.TK_PERCENTEQ) {
|
||||
if (unsignd) { emitline("\tMOVQ\t$0, DX\n"); emitline("\tDIVQ\tCX\n"); }
|
||||
else { emitline("\tCQO\n"); emitline("\tIDIVQ\tCX\n"); };
|
||||
emitline("\tMOVQ\tDX, AX\n");
|
||||
wired = true;
|
||||
};
|
||||
if (n.op == tkind.TK_LSHIFTEQ) { emitline("\tSHLQ\tCX, AX\n"); wired = true; };
|
||||
if (n.op == tkind.TK_RSHIFTEQ) {
|
||||
if (unsignd) { emitline("\tSHRQ\tCX, AX\n"); }
|
||||
else { emitline("\tSARQ\tCX, AX\n"); };
|
||||
wired = true;
|
||||
};
|
||||
if (!wired) {
|
||||
let mu: str = "assign-resolver: unknown compound op (rule-7)\n";
|
||||
os.write(2, mu.ptr, mu.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let sop: str = "MOVQ";
|
||||
if (fsz == 1) { sop = "MOVB"; };
|
||||
if (fsz == 2) { sop = "MOVW"; };
|
||||
if (fsz == 4) { sop = "MOVL"; };
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, (BX)\n");
|
||||
return;
|
||||
};
|
||||
}; };
|
||||
let mtl: str = "unsupported assign target shape\n";
|
||||
os.write(2, mtl.ptr, mtl.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
// C1 residual (task #22): a non-DOT lvalue no arm above matched
|
||||
// still falls out SILENT here — known member: the str-base element
|
||||
// store family (`s[i] = v`: cstage drops, wwstage emits MOVB;
|
||||
// pre-existing gate-blind divergence) plus tuple-member writes.
|
||||
// The tail goes loud for the remaining kinds with #22.
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
611
test/wcc/805_placeaddr_store.c
Normal file
611
test/wcc/805_placeaddr_store.c
Normal file
@@ -0,0 +1,611 @@
|
||||
/*
|
||||
* 805_placeaddr_store — cstage and wwstage agree, byte-for-byte and at
|
||||
* runtime, that a store through a deref-of-pointer-to-slice/array
|
||||
* element field — `(*ts)[i].field = v` and `(*ts)[i].field OP= v` —
|
||||
* lands the value (F6, task #4 of the regex fold-2b blockers; the
|
||||
* run_thread hot shape `threads: *[]thread`).
|
||||
*
|
||||
* Pre-C1 BOTH stages compiled this shape to NOTHING, byte-identically:
|
||||
* the lhs N_DOT spine roots at N_UN(STAR), so the `arr[i].field` arm
|
||||
* (idxbase must be N_IDENT) and the chained-ptr-field arm (base must
|
||||
* be *struct) both miss, and the N_ASSIGN dispatch fell off the switch
|
||||
* silently — rhs never even evaluated. The fix routes base-address
|
||||
* derivation through the cgplaceaddr resolver (cmd/w6c/cgen.c, mirror
|
||||
* selfhost/cmd/wcc/cgenexpr.ww); each call-site keeps its own
|
||||
* load/store emission, and every N_DOT lvalue the resolver can't
|
||||
* address now dies LOUD ("unsupported assign target shape") instead of
|
||||
* silently dropping (rule 7). Field kinds the resolver arm does not
|
||||
* wire yet (float / tagged / aggregate / str-slice compound) hard-stop
|
||||
* with their own diagnostics — the reject rows pin the exact text on
|
||||
* BOTH stages. Non-DOT lvalue tail residue is task #22.
|
||||
*
|
||||
* row | shape | want
|
||||
* --------------------+----------------------------------------+------
|
||||
* store_size_neighbor | (*ts)[1].pc = 9, elem 0 intact | 19
|
||||
* store_bool_compound | p7a4 verbatim: =, +=, bool store | 0
|
||||
* widths_unsigned | u8/u16/u32 stores (MOVB/MOVW/MOVL) | 42
|
||||
* widths_signed | i8/i16/i32 negative stores + readback | 43
|
||||
* compound_ops | += -= *= |= /= (DIVQ + IDIVQ) %= <<= | 44
|
||||
* str_field_member | (*ts)[i].name = "hi" (3-word header) | 45
|
||||
* array_base_deref | (*ta)[i].pc via *[3]t, = and += | 46
|
||||
* runtime_call_idx | (*ts)[geti()].pc — call-idx clobber | 47
|
||||
* compound_bits_shr | &= ^= >>= signed (SARQ) + uns (SHRQ) | 48
|
||||
* compound_narrow | u8/i8/i16/u16/i32/u32 OP= (narrow | 49
|
||||
* | fldloadop: MOVSBQ/MOVZBQ/MOVSWQ/...) |
|
||||
* slice_field_member | (*ts)[i].xs = s ([]i64, 3-word header) | 50
|
||||
* neutral_ident_bases | x.f / a[i].f / p.f = and += (untouched | 51
|
||||
* | enumerated arms — runtime-pins the |
|
||||
* | resolver's asm-neutrality claim) |
|
||||
* reject_aggregate | struct-typed field store | BUILD_FAIL
|
||||
* reject_float | f64 field store | BUILD_FAIL
|
||||
* reject_tagged | tagged-union field store | BUILD_FAIL
|
||||
* reject_str_compound | (*ts)[i].name += — str/slice compound | BUILD_FAIL
|
||||
* reject_tail | h.arr[1].pc (resolver-unwired N_DOT) | BUILD_FAIL
|
||||
*
|
||||
* BUILD_FAIL rows also assert the diagnostic TEXT (stderr substring,
|
||||
* both stages) — a build that fails for any other reason (parse error,
|
||||
* crash) is a vacuous reject and fails the row.
|
||||
*
|
||||
* Every non-BUILD_FAIL row also asserts cstage/wwstage asm byte-id.
|
||||
*/
|
||||
#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;
|
||||
}
|
||||
|
||||
/* want == BUILD_FAIL: the row must FAIL to build on both stages AND
|
||||
* emit expect_err on stderr (rule 7 — never a silent acceptance;
|
||||
* without the message check a row would pass vacuously on any
|
||||
* unrelated build failure). */
|
||||
#define BUILD_FAIL (-2147483647 - 1)
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
int want;
|
||||
const char *expect_err; /* BUILD_FAIL rows: required stderr substring */
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "store_size_neighbor",
|
||||
"package main;\n"
|
||||
"type thread = struct { pc: size, matched: bool };\n"
|
||||
"fn setpc(ts: *[]thread, i: size) void = { (*ts)[i].pc = 9; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet ts: []thread = [];\n"
|
||||
"\tappend(ts, thread { pc = 1, matched = false });\n"
|
||||
"\tappend(ts, thread { pc = 2, matched = false });\n"
|
||||
"\tsetpc(&ts, 1);\n"
|
||||
"\treturn (ts[0].pc*10 + ts[1].pc): i32;\n"
|
||||
"};\n",
|
||||
19, NULL },
|
||||
|
||||
/* The original F6 probe (scratch/fold2b_probes/p7a4), graduated
|
||||
* verbatim: plain scalar store, compound +=, bool store. */
|
||||
{ "store_bool_compound",
|
||||
"package main;\n"
|
||||
"type thread = struct { pc: size, matched: bool };\n"
|
||||
"fn setpc(ts: *[]thread, i: size) void = {\n"
|
||||
"\t(*ts)[i].pc = 9;\n"
|
||||
"};\n"
|
||||
"fn bump(ts: *[]thread, i: size) void = {\n"
|
||||
"\t(*ts)[i].pc += 1;\n"
|
||||
"};\n"
|
||||
"fn markm(ts: *[]thread, i: size) void = {\n"
|
||||
"\t(*ts)[i].matched = true;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet ts: []thread = [];\n"
|
||||
"\tappend(ts, thread { pc = 7, matched = false });\n"
|
||||
"\tsetpc(&ts, 0);\n"
|
||||
"\tif (ts[0].pc != 9) { return 1; };\n"
|
||||
"\tbump(&ts, 0);\n"
|
||||
"\tif (ts[0].pc != 10) { return 2; };\n"
|
||||
"\tmarkm(&ts, 0);\n"
|
||||
"\tif (!ts[0].matched) { return 3; };\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
0, NULL },
|
||||
|
||||
{ "widths_unsigned",
|
||||
"package main;\n"
|
||||
"type t = struct { a: u8, b: u16, c: u32 };\n"
|
||||
"fn seta(ts: *[]t, i: size) void = { (*ts)[i].a = 200u8; };\n"
|
||||
"fn setb(ts: *[]t, i: size) void = { (*ts)[i].b = 60000u16; };\n"
|
||||
"fn setc(ts: *[]t, i: size) void = { (*ts)[i].c = 70000u32; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet ts: []t = [];\n"
|
||||
"\tappend(ts, t { a = 5u8, b = 6u16, c = 7u32 });\n"
|
||||
"\tappend(ts, t { a = 5u8, b = 6u16, c = 7u32 });\n"
|
||||
"\tseta(&ts, 1); setb(&ts, 1); setc(&ts, 1);\n"
|
||||
"\tif (ts[1].a != 200u8) { return 1; };\n"
|
||||
"\tif (ts[1].b != 60000u16) { return 2; };\n"
|
||||
"\tif (ts[1].c != 70000u32) { return 3; };\n"
|
||||
"\tif (ts[0].a != 5u8) { return 4; };\n"
|
||||
"\tif (ts[0].c != 7u32) { return 5; };\n"
|
||||
"\treturn 42;\n"
|
||||
"};\n",
|
||||
42, NULL },
|
||||
|
||||
{ "widths_signed",
|
||||
"package main;\n"
|
||||
"type t = struct { a: i8, b: i16, c: i32 };\n"
|
||||
"fn seta(ts: *[]t, i: size) void = { (*ts)[i].a = -7i8; };\n"
|
||||
"fn setb(ts: *[]t, i: size) void = { (*ts)[i].b = -300i16; };\n"
|
||||
"fn setc(ts: *[]t, i: size) void = { (*ts)[i].c = -70000i32; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet ts: []t = [];\n"
|
||||
"\tappend(ts, t { a = 1i8, b = 2i16, c = 3i32 });\n"
|
||||
"\tappend(ts, t { a = 1i8, b = 2i16, c = 3i32 });\n"
|
||||
"\tseta(&ts, 1); setb(&ts, 1); setc(&ts, 1);\n"
|
||||
"\tif (ts[1].a != -7i8) { return 1; };\n"
|
||||
"\tif (ts[1].b != -300i16) { return 2; };\n"
|
||||
"\tif (ts[1].c != -70000i32) { return 3; };\n"
|
||||
"\tif (ts[0].a != 1i8) { return 4; };\n"
|
||||
"\treturn 43;\n"
|
||||
"};\n",
|
||||
43, NULL },
|
||||
|
||||
/* All-arm compound coverage: unsigned size /= (DIVQ), signed int
|
||||
* /= and i64 %= on negatives (CQO+IDIVQ), <<=, and the plain
|
||||
* += -= *= |= trio on a fourth field. */
|
||||
{ "compound_ops",
|
||||
"package main;\n"
|
||||
"type t = struct { s: int, u: size, n: i64, m: i64 };\n"
|
||||
"fn divs(ts: *[]t, i: size) void = { (*ts)[i].s /= 4; };\n"
|
||||
"fn modn(ts: *[]t, i: size) void = { (*ts)[i].n %= 5; };\n"
|
||||
"fn divu(ts: *[]t, i: size) void = { (*ts)[i].u /= 3; };\n"
|
||||
"fn shl(ts: *[]t, i: size) void = { (*ts)[i].u <<= 2; };\n"
|
||||
"fn addm(ts: *[]t, i: size) void = { (*ts)[i].m += 7; };\n"
|
||||
"fn subm(ts: *[]t, i: size) void = { (*ts)[i].m -= 2; };\n"
|
||||
"fn mulm(ts: *[]t, i: size) void = { (*ts)[i].m *= 3; };\n"
|
||||
"fn orm(ts: *[]t, i: size) void = { (*ts)[i].m |= 4; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet ts: []t = [];\n"
|
||||
"\tappend(ts, t { s = 4, u = 5, n = 6, m = 1 });\n"
|
||||
"\tappend(ts, t { s = -12, u = 9, n = -13, m = 4 });\n"
|
||||
"\tdivs(&ts, 1);\n"
|
||||
"\tif (ts[1].s != -3) { return 1; };\n"
|
||||
"\tmodn(&ts, 1);\n"
|
||||
"\tif (ts[1].n != -3) { return 2; };\n"
|
||||
"\tdivu(&ts, 1);\n"
|
||||
"\tif (ts[1].u != 3) { return 3; };\n"
|
||||
"\tshl(&ts, 1);\n"
|
||||
"\tif (ts[1].u != 12) { return 4; };\n"
|
||||
"\taddm(&ts, 1);\n"
|
||||
"\tsubm(&ts, 1);\n"
|
||||
"\tmulm(&ts, 1);\n"
|
||||
"\torm(&ts, 1);\n"
|
||||
"\tif (ts[1].m != 31) { return 5; };\n"
|
||||
"\tif (ts[0].s != 4) { return 6; };\n"
|
||||
"\treturn 44;\n"
|
||||
"};\n",
|
||||
44, NULL },
|
||||
|
||||
/* str field: the rhs leaves {ptr,len,cap} in (AX,BX,CX); the
|
||||
* place address stages through DX so the triple survives. */
|
||||
{ "str_field_member",
|
||||
"package main;\n"
|
||||
"type t = struct { pc: size, name: str };\n"
|
||||
"fn setname(ts: *[]t, i: size) void = { (*ts)[i].name = \"hi\"; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet ts: []t = [];\n"
|
||||
"\tappend(ts, t { pc = 1, name = \"\" });\n"
|
||||
"\tappend(ts, t { pc = 2, name = \"\" });\n"
|
||||
"\tsetname(&ts, 1);\n"
|
||||
"\tlet nm: str = ts[1].name;\n"
|
||||
"\tif (nm.len != 2) { return 1; };\n"
|
||||
"\tif (nm[0] != 104u8) { return 2; };\n"
|
||||
"\tif (ts[0].name.len != 0) { return 3; };\n"
|
||||
"\treturn 45;\n"
|
||||
"};\n",
|
||||
45, NULL },
|
||||
|
||||
/* *[N]T base: the array place IS the element storage (no .ptr
|
||||
* hop), unlike the slice-header deref the rows above take. */
|
||||
{ "array_base_deref",
|
||||
"package main;\n"
|
||||
"type t = struct { pc: size, matched: bool };\n"
|
||||
"fn setarr(ta: *[3]t, i: size) void = { (*ta)[i].pc = 9; };\n"
|
||||
"fn bumparr(ta: *[3]t, i: size) void = { (*ta)[i].pc += 2; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet ta: [3]t;\n"
|
||||
"\tta[0].pc = 1; ta[1].pc = 2; ta[2].pc = 3;\n"
|
||||
"\tsetarr(&ta, 1);\n"
|
||||
"\tif (ta[1].pc != 9) { return 1; };\n"
|
||||
"\tbumparr(&ta, 1);\n"
|
||||
"\tif (ta[1].pc != 11) { return 2; };\n"
|
||||
"\tif (ta[0].pc != 1) { return 3; };\n"
|
||||
"\tif (ta[2].pc != 3) { return 4; };\n"
|
||||
"\treturn 46;\n"
|
||||
"};\n",
|
||||
46, NULL },
|
||||
|
||||
/* Call-result index: cgexpr(idx) inside the resolver clobbers
|
||||
* caller-saved regs; the spilled rhs and place address must
|
||||
* survive. */
|
||||
{ "runtime_call_idx",
|
||||
"package main;\n"
|
||||
"type t = struct { pc: size, matched: bool };\n"
|
||||
"fn geti() size = { return 1; };\n"
|
||||
"fn setpc(ts: *[]t) void = { (*ts)[geti()].pc = 9; };\n"
|
||||
"fn bump(ts: *[]t) void = { (*ts)[geti()].pc += 1; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet ts: []t = [];\n"
|
||||
"\tappend(ts, t { pc = 1, matched = false });\n"
|
||||
"\tappend(ts, t { pc = 2, matched = false });\n"
|
||||
"\tsetpc(&ts);\n"
|
||||
"\tif (ts[1].pc != 9) { return 1; };\n"
|
||||
"\tbump(&ts);\n"
|
||||
"\tif (ts[1].pc != 10) { return 2; };\n"
|
||||
"\tif (ts[0].pc != 1) { return 3; };\n"
|
||||
"\treturn 47;\n"
|
||||
"};\n",
|
||||
47, NULL },
|
||||
|
||||
/* The remaining compound arms: ANDQ/XORQ, and BOTH >>= forms —
|
||||
* SARQ (signed, sign bit must replicate) vs SHRQ (unsigned). */
|
||||
{ "compound_bits_shr",
|
||||
"package main;\n"
|
||||
"type t = struct { s: int, u: size, n: i64, m: i64 };\n"
|
||||
"fn ands(ts: *[]t, i: size) void = { (*ts)[i].m &= 6; };\n"
|
||||
"fn xors(ts: *[]t, i: size) void = { (*ts)[i].m ^= 3; };\n"
|
||||
"fn sars(ts: *[]t, i: size) void = { (*ts)[i].s >>= 2; };\n"
|
||||
"fn shrs(ts: *[]t, i: size) void = { (*ts)[i].u >>= 1; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet ts: []t = [];\n"
|
||||
"\tappend(ts, t { s = -16, u = 8, n = 0, m = 7 });\n"
|
||||
"\tands(&ts, 0);\n"
|
||||
"\tif (ts[0].m != 6) { return 1; };\n"
|
||||
"\txors(&ts, 0);\n"
|
||||
"\tif (ts[0].m != 5) { return 2; };\n"
|
||||
"\tsars(&ts, 0);\n"
|
||||
"\tif (ts[0].s != -4) { return 3; };\n"
|
||||
"\tshrs(&ts, 0);\n"
|
||||
"\tif (ts[0].u != 4) { return 4; };\n"
|
||||
"\treturn 48;\n"
|
||||
"};\n",
|
||||
48, NULL },
|
||||
|
||||
/* Narrow-width compounds: the widths rows above only exercise
|
||||
* plain `=` (fldstoreop); compounds also take the fldloadop
|
||||
* narrow LOAD (MOVSBQ/MOVZBQ/MOVSWQ/MOVZWQ/MOVSXD/MOVL) — i8/i16
|
||||
* negatives pin the sign-extension, u8 250 pins zero-extension. */
|
||||
{ "compound_narrow",
|
||||
"package main;\n"
|
||||
"type t = struct { a: i8, b: u8, c: i16, d: u16, e: i32, f: u32 };\n"
|
||||
"fn adda(ts: *[]t, i: size) void = { (*ts)[i].a += 1i8; };\n"
|
||||
"fn addb(ts: *[]t, i: size) void = { (*ts)[i].b += 200u8; };\n"
|
||||
"fn subc(ts: *[]t, i: size) void = { (*ts)[i].c -= 300i16; };\n"
|
||||
"fn shld(ts: *[]t, i: size) void = { (*ts)[i].d <<= 3u16; };\n"
|
||||
"fn mule(ts: *[]t, i: size) void = { (*ts)[i].e *= -3i32; };\n"
|
||||
"fn orf(ts: *[]t, i: size) void = { (*ts)[i].f |= 8u32; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet ts: []t = [];\n"
|
||||
"\tappend(ts, t { a = -5i8, b = 50u8, c = 100i16, d = 2u16, e = 7i32, f = 5u32 });\n"
|
||||
"\tadda(&ts, 0);\n"
|
||||
"\tif (ts[0].a != -4i8) { return 1; };\n"
|
||||
"\taddb(&ts, 0);\n"
|
||||
"\tif (ts[0].b != 250u8) { return 2; };\n"
|
||||
"\tsubc(&ts, 0);\n"
|
||||
"\tif (ts[0].c != -200i16) { return 3; };\n"
|
||||
"\tshld(&ts, 0);\n"
|
||||
"\tif (ts[0].d != 16u16) { return 4; };\n"
|
||||
"\tmule(&ts, 0);\n"
|
||||
"\tif (ts[0].e != -21i32) { return 5; };\n"
|
||||
"\torf(&ts, 0);\n"
|
||||
"\tif (ts[0].f != 13u32) { return 6; };\n"
|
||||
"\treturn 49;\n"
|
||||
"};\n",
|
||||
49, NULL },
|
||||
|
||||
/* Slice field, same 3-word path as str. Field init goes through
|
||||
* an ident-bound empty slice, NOT `xs = []`: a bare empty-slice
|
||||
* literal as a struct-lit field inside an append arg leaves a
|
||||
* garbage header on MASTER too (pre-existing, task #9 family) —
|
||||
* this row pins the F6 store, not that bug. */
|
||||
{ "slice_field_member",
|
||||
"package main;\n"
|
||||
"type t = struct { pc: size, xs: []i64 };\n"
|
||||
"fn setxs(ts: *[]t, i: size, s: []i64) void = { (*ts)[i].xs = s; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet empty: []i64 = [];\n"
|
||||
"\tlet ts: []t = [];\n"
|
||||
"\tappend(ts, t { pc = 1, xs = empty });\n"
|
||||
"\tappend(ts, t { pc = 2, xs = empty });\n"
|
||||
"\tlet s: []i64 = [];\n"
|
||||
"\tappend(s, 11);\n"
|
||||
"\tappend(s, 22);\n"
|
||||
"\tsetxs(&ts, 1, s);\n"
|
||||
"\tif (ts[1].xs.len != 2) { return 1; };\n"
|
||||
"\tif (ts[1].xs[1] != 22) { return 2; };\n"
|
||||
"\tif (ts[0].xs.len != 0) { return 3; };\n"
|
||||
"\tif (ts[0].pc != 1) { return 4; };\n"
|
||||
"\tif (ts[1].pc != 2) { return 5; };\n"
|
||||
"\treturn 50;\n"
|
||||
"};\n",
|
||||
50, NULL },
|
||||
|
||||
/* Ident-rooted lvalues stay with the enumerated arms (the
|
||||
* resolver must never fire for them) — this row runtime-pins the
|
||||
* shapes the 78-probe asm-neutrality sweep diffed statically. */
|
||||
{ "neutral_ident_bases",
|
||||
"package main;\n"
|
||||
"type t = struct { pc: size, matched: bool };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet x: t = t { pc = 1, matched = false };\n"
|
||||
"\tx.pc = 5;\n"
|
||||
"\tx.pc += 2;\n"
|
||||
"\tlet a: [2]t;\n"
|
||||
"\ta[0].pc = 3; a[1].pc = 4;\n"
|
||||
"\ta[1].pc += 10;\n"
|
||||
"\tlet p: *t = &x;\n"
|
||||
"\tp.pc = 20;\n"
|
||||
"\tp.pc += 1;\n"
|
||||
"\tif (x.pc != 21) { return 1; };\n"
|
||||
"\tif (a[1].pc != 14) { return 2; };\n"
|
||||
"\tif (a[0].pc != 3) { return 3; };\n"
|
||||
"\treturn 51;\n"
|
||||
"};\n",
|
||||
51, NULL },
|
||||
|
||||
/* The fold-2b 40B capture store (p7b/p7_composed) — wired in a
|
||||
* follow-up resolver commit; until then it must die LOUD, never
|
||||
* the pre-C1 silent drop. */
|
||||
{ "reject_aggregate",
|
||||
"package main;\n"
|
||||
"type capture = struct { content: str, start: size, end: size };\n"
|
||||
"type t = struct { pc: size, cap: capture };\n"
|
||||
"fn setcap(ts: *[]t, i: size) void = {\n"
|
||||
"\t(*ts)[i].cap = capture { content = \"x\", start = 1, end = 2 };\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet ts: []t = [];\n"
|
||||
"\tsetcap(&ts, 0);\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
BUILD_FAIL, "assign-resolver: aggregate field not wired (rule-7)" },
|
||||
|
||||
{ "reject_float",
|
||||
"package main;\n"
|
||||
"type t = struct { pc: size, f: f64 };\n"
|
||||
"fn setf(ts: *[]t, i: size) void = { (*ts)[i].f = 1.5; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet ts: []t = [];\n"
|
||||
"\tsetf(&ts, 0);\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
BUILD_FAIL, "assign-resolver: float field not wired (rule-7)" },
|
||||
|
||||
{ "reject_tagged",
|
||||
"package main;\n"
|
||||
"type v = (i64 | bool);\n"
|
||||
"type t = struct { pc: size, tg: v };\n"
|
||||
"fn settg(ts: *[]t, i: size) void = { (*ts)[i].tg = 5; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet ts: []t = [];\n"
|
||||
"\tsettg(&ts, 0);\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
BUILD_FAIL, "assign-resolver: tagged field not wired (rule-7)" },
|
||||
|
||||
/* Compound on a str field reaches cgen (the checker admits it) —
|
||||
* it must hit the resolver's own loud stop, not the silent tail. */
|
||||
{ "reject_str_compound",
|
||||
"package main;\n"
|
||||
"type t = struct { pc: size, name: str };\n"
|
||||
"fn addname(ts: *[]t, i: size) void = { (*ts)[i].name += \"x\"; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet ts: []t = [];\n"
|
||||
"\taddname(&ts, 0);\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
BUILD_FAIL,
|
||||
"assign-resolver: compound on str/slice field not wired (rule-7)" },
|
||||
|
||||
/* An N_DOT lvalue the C1 resolver does not wire (index base is a
|
||||
* dot chain, not a deref) — pre-C1 this was a SILENT no-op store;
|
||||
* the loud dispatch tail is the close-by-construction net. */
|
||||
{ "reject_tail",
|
||||
"package main;\n"
|
||||
"type t = struct { pc: size, matched: bool };\n"
|
||||
"type holder = struct { arr: [3]t, n: i64 };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet h: holder = holder { n = 0, ... };\n"
|
||||
"\th.arr[1].pc = 5;\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
BUILD_FAIL, "unsupported assign target shape" },
|
||||
};
|
||||
|
||||
/* errlog_has — the build-failure stderr must carry the row's expected
|
||||
* diagnostic; any other failure (parse error, crash) is a vacuous
|
||||
* reject and must not pass. */
|
||||
static int
|
||||
errlog_has(const char *path, const char *needle)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
char buf[8192];
|
||||
size_t got = fread(buf, 1, sizeof buf - 1, f);
|
||||
fclose(f);
|
||||
buf[got] = '\0';
|
||||
return strstr(buf, needle) != NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[64], tmpdir[64], errlog[80], cmd[1200];
|
||||
snprintf(src, sizeof src, "/tmp/plad_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/plad_%d_d_%d", getpid(), i);
|
||||
snprintf(errlog, sizeof errlog, "%s.err", src);
|
||||
|
||||
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 2>%s",
|
||||
tmpdir, driver, src, errlog);
|
||||
if (runwait(cmd) != 0) {
|
||||
int rc = -1;
|
||||
if (r->want != BUILD_FAIL) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
} else if (r->expect_err &&
|
||||
!errlog_has(errlog, r->expect_err)) {
|
||||
fprintf(stderr, "row[%s]: %s build failed without "
|
||||
"expected diagnostic \"%s\"\n",
|
||||
r->label, driver, r->expect_err);
|
||||
rc = -3; /* failed, but for the wrong reason */
|
||||
}
|
||||
unlink(src); unlink(errlog); rmdir(tmpdir);
|
||||
return rc;
|
||||
}
|
||||
|
||||
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(errlog); unlink(outbin); rmdir(tmpdir);
|
||||
return got;
|
||||
}
|
||||
|
||||
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
|
||||
* w6c_ww and diff. The resolver emission is written fresh on both
|
||||
* sides, so this is the converged-by-construction gate: any drift in
|
||||
* the idx-scale/spill/deref sequence shows here. */
|
||||
static int
|
||||
asm_byte_identical(const char *bin, const struct row *r, int i)
|
||||
{
|
||||
char src[64], cs[64], ws[64], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/plad_asm_%d_%d.ww", getpid(), i);
|
||||
snprintf(cs, sizeof cs, "/tmp/plad_asm_%d_%d_c.s", getpid(), i);
|
||||
snprintf(ws, sizeof ws, "/tmp/plad_asm_%d_%d_w.s", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
|
||||
unlink(src);
|
||||
return -1;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
|
||||
bin, ws, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
|
||||
unlink(src); unlink(cs);
|
||||
return -1;
|
||||
}
|
||||
|
||||
FILE *fc = fopen(cs, "rb");
|
||||
FILE *fw = fopen(ws, "rb");
|
||||
int rc = 0;
|
||||
if (!fc || !fw) {
|
||||
rc = -1;
|
||||
} else {
|
||||
for (;;) {
|
||||
int a = fgetc(fc);
|
||||
int b = fgetc(fw);
|
||||
if (a != b) { rc = -1; break; }
|
||||
if (a == EOF) break;
|
||||
}
|
||||
}
|
||||
if (fc) fclose(fc);
|
||||
if (fw) fclose(fw);
|
||||
if (rc != 0)
|
||||
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
|
||||
r->label);
|
||||
unlink(src); unlink(cs); unlink(ws);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[2080];
|
||||
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[2120];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[2120];
|
||||
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, "placeaddr_store: 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++;
|
||||
int bad = rows[i].want == BUILD_FAIL
|
||||
? (got != -1) : (got != rows[i].want);
|
||||
if (bad) {
|
||||
fprintf(stderr,
|
||||
"placeaddr_store[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (access(wdrv, X_OK) == 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (rows[i].want == BUILD_FAIL)
|
||||
continue;
|
||||
total++;
|
||||
if (asm_byte_identical(bin, &rows[i], i) != 0)
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"placeaddr_store: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("placeaddr_store: %d fixtures passed\n", total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user