wcc/cgen: #146 wwstage str ==/!= via rt_streq, not ptr-only CMPQ (the #154 ww-twin)

wwstage compiled str ==/!= as a single CMPQ on the eager-eval'd ptr word
(len ignored), so two distinct-pointer equal-content strings compared
unequal. cstage was already correct (CALLs rt_streq, the #154 cbinop fix).
The wwstage cgbin had no str-awareness -- every comparison fell to the
generic CMPQ tail; the #154 fix was never mirrored.

wwstage-only: a cgstreqpush helper + a str ==/!= branch at the top of
cgbin (before the generic eval collapses the header), byte-matching cstage
cbinop:4564-4623 -- push rhs/lhs (len,ptr), POPQ DI/SI/DX/CX, CALL
rt_streq, XORQ $1 for !=. Gated on typeisstr (= cstage node_isstr, which
also catches module-global str idents). cstage cgen unchanged (w6c md5
unchanged). The str== .s is byte-identical cs==ww for local, global,
aliased, chained, and condition operands.

Graduates 3 lib byte-id pins (test/wcc/989_lib_byteid #59.1 asciitest,
#59.11 toktest, #59.12 asttest) M_DIVERGE->M_ID -- they used == on str and
were pinned divergent because of this bug; now byte-identical. byte-id
990-997 8/8. test/wcc/827 table-driven.
This commit is contained in:
2026-06-08 23:49:16 +09:00
parent 83025b03a6
commit 5dd239d01e
6 changed files with 496 additions and 3 deletions

View File

@@ -26856,6 +26856,43 @@ fn cgun(c: *cgen, n: *node) void = {
return;
};
// cgstreqpush — push one str operand's (len, then ptr) header words for
// the rt_streq content-compare in cgbin. Mirrors cstage cgen.c:4568-4615:
// an ident loads its 2-word header (ptr+len, NOT cap) from name(SB) for a
// module-global str (#154 let_islet branch) or BP+off for a local; a
// non-ident evals via cgexpr (AX=ptr, BX=len) and pushes BX then AX. Push
// order is len then ptr so the matching POPQ pops ptr first.
fn cgstreqpush(c: *cgen, op: *node) void = {
if (op.kind == nkind.N_IDENT) {
let nm: str = op.str;
let lc: *local = localfindnode(c, nm);
if (lc == nil && isletvar(c, nm)) {
emitline("\tLEAQ\t");
emitsymnamehint(c, nm, c.curmod);
emitline("(SB), BX\n");
emitline("\tMOVQ\t8(BX), AX\n");
emitline("\tPUSHQ\tAX\n");
emitline("\tMOVQ\t(BX), AX\n");
emitline("\tPUSHQ\tAX\n");
return;
};
let off: i32 = 0;
if (lc != nil) { off = lc.off; };
emitline("\tMOVQ\t");
emitoff((off + 8): i64);
emitline("(BP), AX\n");
emitline("\tPUSHQ\tAX\n");
emitline("\tMOVQ\t");
emitoff(off: i64);
emitline("(BP), AX\n");
emitline("\tPUSHQ\tAX\n");
return;
};
cgexpr(c, op);
emitline("\tPUSHQ\tBX\n");
emitline("\tPUSHQ\tAX\n");
};
fn cgbin(c: *cgen, n: *node) void = {
// Short-circuit `&&` / `||`. Operands are bool (0/1); the type
// checker enforces it. Eval LHS into AX, branch over RHS on the
@@ -26877,6 +26914,26 @@ fn cgbin(c: *cgen, n: *node) void = {
return;
};
// #146 (#154 ww-twin): str ==/!= is a CONTENT compare via rt_streq,
// not a ptr compare. Must run before the generic eager-eval tail
// below collapses each str header to its ptr word (AX). Push rhs
// then lhs (len, ptr each); POPQ DI/SI/DX/CX lands a.ptr,a.len,
// b.ptr,b.len per rt/streq.s; CALL rt_streq -> AX in {0,1}; XOR 1
// for !=. The gate reads the checker stamp (typeisstr) exactly like
// cstage node_isstr. Byte-identical to cstage cbinop (cmd/w6c/
// cgen.c:4564-4623). cstage was fixed by #154; this is its mirror.
if ((n.op == tkind.TK_EQ || n.op == tkind.TK_NEQ)
&& n.lhs != nil && n.rhs != nil
&& typeisstr(n.lhs.type_: *tinfo)
&& typeisstr(n.rhs.type_: *tinfo)) {
cgstreqpush(c, n.rhs);
cgstreqpush(c, n.lhs);
emitline("\tPOPQ\tDI\n\tPOPQ\tSI\n\tPOPQ\tDX\n\tPOPQ\tCX\n");
emitline("\tCALL\trt_streq(SB)\n");
if (n.op == tkind.TK_NEQ) { emitline("\tXORQ\t$1, AX\n"); };
return;
};
let unsignd: bool = nodeisunsigned(c, n.lhs);
if (!unsignd) { unsignd = nodeisunsigned(c, n.rhs); };