The str==/!= arm of cbinop had an N_IDENT fast-path that assumed the operand was a local: localfind returns 0 for a module-global str, so it loaded (BP)/8(BP) — saved-BP/retaddr garbage — into rt_streq. `p == sepstr` silently compared garbage (returned wrong). Mirror #148's global branch at both sub-sites (rhs/lhs): off==0 && let_islet -> LEAQ name(SB) base, load ptr/len. Distinct per-site fast-path, not a shared choke (the by-value-global-arg family #148/#150/#151 closes separately). cstage-only; the wwstage str== twin is #146 (-> #125 batch). Pin test/wcc/989_strglobeq (table-driven: const+let globals, rhs+lhs ident, ==/!=, unequal + len>1 rows; teeth-proven). Surfaced by the lib/path c3 buffer-ops gate-1 oracle.
68 lines
2.4 KiB
Plaintext
68 lines
2.4 KiB
Plaintext
// strglobeq — #154 regression pin. `str == str` / `str != str` delegate to
|
|
// rt_streq, and cgen's str-compare arm had an N_IDENT fast-path that ALWAYS
|
|
// read the header off BP+localfind(name). For a module-GLOBAL str ident
|
|
// localfind→0, so it loaded saved-BP/retaddr garbage instead of name(SB) —
|
|
// a silent cstage miscompile (the header lives at name(SB), not the frame).
|
|
// Fix mirrors #148's slice global branch: LEAQ name(SB), load ptr/len off it.
|
|
//
|
|
// Table-driven: each row is {input, expected}; the loop feeds every input
|
|
// through the five compile-time-distinct comparison shapes (the bug is per
|
|
// SHAPE in cgen, so the shapes are separate fns the rows drive). Shapes cover
|
|
// const-global AND let-global operands, ident on RHS AND on LHS, == AND !=,
|
|
// and a length>1 global ("/usr") so the LEN word — not just the ptr — is read
|
|
// off name(SB) on both sub-sites. signalled = row*10+shape pinpoints failures.
|
|
//
|
|
// Run with `out/bin/ww run test/wcc/989_strglobeq.ww`; exit 0 = all pass.
|
|
|
|
package main;
|
|
|
|
import os;
|
|
|
|
const csep: str = "/";
|
|
let lsep: str = "/";
|
|
const longsep: str = "/usr";
|
|
|
|
// rhs-ident global (p == g): const, let, len>1, and !=.
|
|
fn eqr_const(p: str) bool = { return p == csep; };
|
|
fn eqr_let(p: str) bool = { return p == lsep; };
|
|
fn eqr_long(p: str) bool = { return p == longsep; };
|
|
fn ner_const(p: str) bool = { return p != csep; };
|
|
|
|
// lhs-ident global (g == p): const and len>1.
|
|
fn eql_const(p: str) bool = { return csep == p; };
|
|
fn eql_long(p: str) bool = { return longsep == p; };
|
|
|
|
type row = struct {
|
|
in: str,
|
|
eqsep: bool, // in == "/"
|
|
eqlong: bool, // in == "/usr"
|
|
};
|
|
|
|
let signalled: i32 = 0;
|
|
fn fail() void = { os.exit(signalled + 10); };
|
|
|
|
export fn main() i32 = {
|
|
let rows: [_]row = [
|
|
row { in = "/", eqsep = true, eqlong = false },
|
|
row { in = "foo", eqsep = false, eqlong = false },
|
|
row { in = "/usr", eqsep = false, eqlong = true },
|
|
];
|
|
// len() stamps i32 on cstage (#26); the index must match for the bound.
|
|
for (let i: i32 = 0; i < len(rows); i += 1) {
|
|
let r = rows[i];
|
|
signalled = i * 10 + 1;
|
|
if (eqr_const(r.in) != r.eqsep) { fail(); };
|
|
signalled = i * 10 + 2;
|
|
if (eqr_let(r.in) != r.eqsep) { fail(); };
|
|
signalled = i * 10 + 3;
|
|
if (eql_const(r.in) != r.eqsep) { fail(); };
|
|
signalled = i * 10 + 4;
|
|
if (ner_const(r.in) != !r.eqsep) { fail(); };
|
|
signalled = i * 10 + 5;
|
|
if (eqr_long(r.in) != r.eqlong) { fail(); };
|
|
signalled = i * 10 + 6;
|
|
if (eql_long(r.in) != r.eqlong) { fail(); };
|
|
};
|
|
return 0;
|
|
};
|