wcc-ww: assert/abort builtins — checker tag + cgen rt_abort lowering (#58)
wwstage had no EXPR_ASSERT-family intercept: the checker left bare assert/abort calls untyped (asserttyped gate 4 skipped them by design) and cgcall fell through to the regular call path, emitting CALL assert(SB) for a symbol that exists nowhere — link-fail. cstage was already correct (tag ty_err at check.c:1536-1572, lower inline via rt_abort at cgen.c:6618-6663). Mirror the same tag-then-lower pair: exprtype N_CALL stamps the call void and the callee TY_ERR behind the scopelookupprefer no-shadow gate (the isassertfam predicate), with the cstage arg diagnostics (cond must be bool, msg must be str, arity caps); cgcall keys on the TY_ERR tag and emits the identical CMPQ/JNE/rt_abort sequence. A user-shadowed assert/abort (same-module or cross-module, the #45 shape, task #14) stays untagged on the regular call path — byte-id for all existing lib code preserved. The cond check does NOT alias-peel: cstage compares ty_bool by identity (check.c:1560), so `type myb = bool` is rejected there; wwstage aligns down per rule 10 (a resolvealias here was accepting it — cs/ww accept-reject divergence). Widening both stages together belongs to the alias-peel choke-point arc (task #5, #47/#68). The resolvewalk N_IDENT resolution counter learns the builtin shape: an unshadowed abort/assert ident binds no sym BY DESIGN, so wwdump -r's zero-unresolved gate (990 probe 4) counts it resolved instead of failing builtin-using units. test 957: 13 rows — pass/fail/msg/bare abort (run exit + rt_abort stderr content; no-msg rows pin EMPTY stderr = the (NULL,0) shape), assert in an imported module, same-module + cross-module shadow controls, 5 checker rejects pinned on diagnostic CONTENT (shared substring; cstage prefixes pos, wwstage cerr is bare) incl. the alias-of-bool cond row pinning the rule-10 down-alignment; each positive row pins cstage run exit + cs==ww byte-id. On pre-fix master 11/13 rows trip (survivors = the two shadow controls). Residual (separate root, deferred diagnostic class): zero-arg assert() is not intercepted by either stage; cstage rejects via the generic undefined-ident path, wwstage's undefined-callee diagnostic is the class deferred behind wiring checkfile into w6c_ww.
This commit is contained in:
@@ -10657,11 +10657,20 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
if (nm.len > 0) {
|
||||
let s: *sym = scopelookupprefer(c.cur, c.curmod, nm);
|
||||
if (s == nil) {
|
||||
c.nunresolved += 1;
|
||||
if (c.verbose != 0) {
|
||||
cerr(" unresolved id: ");
|
||||
cerr(nm);
|
||||
cerr("\n");
|
||||
// Unshadowed abort/assert binds no sym BY
|
||||
// DESIGN (the EXPR_ASSERT family has no callee
|
||||
// object — see isassertfam); count it resolved
|
||||
// so wwdump -r's zero-unresolved gate holds
|
||||
// over builtin-using lib code (#58 respell).
|
||||
if (isassertfam(c, n)) {
|
||||
c.nresolved += 1;
|
||||
} else {
|
||||
c.nunresolved += 1;
|
||||
if (c.verbose != 0) {
|
||||
cerr(" unresolved id: ");
|
||||
cerr(nm);
|
||||
cerr("\n");
|
||||
};
|
||||
};
|
||||
} else { c.nresolved += 1; };
|
||||
};
|
||||
@@ -12915,6 +12924,74 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
// Hare-faithful path. This is the intercept-shim minimum to
|
||||
// unblock #15 (A.6.2.1e assertion enable).
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
// abort([msg]) / assert(cond[, msg]) — the EXPR_ASSERT
|
||||
// family (harec ref/harec/src/check.c:877,893), lowered
|
||||
// to rt_abort. Builtin only when no user symbol shadows
|
||||
// the name — the same scopelookupprefer gate as cstage
|
||||
// cmd/wcc/check.c:1536-1572 and isassertfam; a shadowed
|
||||
// call stays on the regular path (#58; the #45
|
||||
// flat-scope root is filed as task #14).
|
||||
// The TY_ERR stamp on the callee is cgen's routing key
|
||||
// (cstage spells it `n->lhs->type = ty_err`).
|
||||
if (streq(callee.str, "abort")
|
||||
&& scopelookupprefer(c.cur, c.curmod, "abort") == nil) {
|
||||
if (e.list != nil) {
|
||||
let mt: *node = exprtype(c, e.list, nil);
|
||||
let conf: bool = false;
|
||||
if (!isassignable(c, mktname(c, "str"), mt, &conf)) {
|
||||
cerr("abort: message must be str\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
if (e.list.next != nil) {
|
||||
cerr("abort: at most one arg\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
};
|
||||
let tn: *node = mktname(c, "void");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
callee.type_ = c.tc.tyerr: *void;
|
||||
return tn;
|
||||
};
|
||||
if (streq(callee.str, "assert") && e.list != nil
|
||||
&& scopelookupprefer(c.cur, c.curmod, "assert") == nil) {
|
||||
let ct: *node = exprtype(c, e.list, nil);
|
||||
if (ct != nil) {
|
||||
// No alias peel: cstage compares ty_bool by
|
||||
// IDENTITY (cmd/wcc/check.c:1560), so a
|
||||
// `type myb = bool` cond is rejected there —
|
||||
// align down (rule 10). Widening belongs to
|
||||
// the alias-peel choke-point arc (task #5,
|
||||
// #47/#68), both stages together.
|
||||
let cu: *node = unwrapbang(ct);
|
||||
let condok: bool = false;
|
||||
if (cu.kind == nkind.N_TNAME) {
|
||||
if (streq(cu.str, "bool")
|
||||
|| streq(cu.str, "untyped_bool")) {
|
||||
condok = true;
|
||||
};
|
||||
};
|
||||
if (!condok) {
|
||||
cerr("assert: cond must be bool\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
};
|
||||
if (e.list.next != nil) {
|
||||
let mt: *node = exprtype(c, e.list.next, nil);
|
||||
let conf: bool = false;
|
||||
if (!isassignable(c, mktname(c, "str"), mt, &conf)) {
|
||||
cerr("assert: message must be str\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
if (e.list.next.next != nil) {
|
||||
cerr("assert: at most two args\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
};
|
||||
let tn: *node = mktname(c, "void");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
callee.type_ = c.tc.tyerr: *void;
|
||||
return tn;
|
||||
};
|
||||
if (streq(callee.str, "len")) {
|
||||
let tn: *node = mktname(c, "i32");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
@@ -26006,6 +26083,46 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
let callee: *node = n.lhs;
|
||||
if (callee != nil) {
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
// abort([msg]) / assert(cond[, msg]) — checker-tagged
|
||||
// builtins (callee TY_ERR is the routing key, cstage's
|
||||
// `lhs->type == ty_err` at cmd/w6c/cgen.c:6618,6645);
|
||||
// lower to rt_abort. A user-shadowed abort/assert is
|
||||
// untagged and stays on the regular call path (#58).
|
||||
let bti: *tinfo = callee.type_: *tinfo;
|
||||
if (bti != nil && bti.kind == tykind.TY_ERR) {
|
||||
if (streq(callee.str, "abort")) {
|
||||
if (n.list != nil) {
|
||||
cgexpr(c, n.list);
|
||||
emitline("\tMOVQ\tAX, DI\n");
|
||||
emitline("\tMOVQ\tBX, SI\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t$0, DI\n");
|
||||
emitline("\tMOVQ\t$0, SI\n");
|
||||
};
|
||||
emitline("\tCALL\trt_abort(SB)\n");
|
||||
return;
|
||||
};
|
||||
if (streq(callee.str, "assert") && n.list != nil) {
|
||||
cgexpr(c, n.list);
|
||||
let skip: str = mklabel(c, "as");
|
||||
emitline("\tCMPQ\t$0, AX\n");
|
||||
emitline("\tJNE\t");
|
||||
emitline(skip);
|
||||
emitline("\n");
|
||||
let msg: *node = n.list.next;
|
||||
if (msg != nil) {
|
||||
cgexpr(c, msg);
|
||||
emitline("\tMOVQ\tAX, DI\n");
|
||||
emitline("\tMOVQ\tBX, SI\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t$0, DI\n");
|
||||
emitline("\tMOVQ\t$0, SI\n");
|
||||
};
|
||||
emitline("\tCALL\trt_abort(SB)\n");
|
||||
emitlabel(skip);
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (streq(callee.str, "append")) {
|
||||
if (n.list != nil) {
|
||||
if (n.list.next != nil) {
|
||||
|
||||
@@ -5634,6 +5634,46 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
let callee: *node = n.lhs;
|
||||
if (callee != nil) {
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
// abort([msg]) / assert(cond[, msg]) — checker-tagged
|
||||
// builtins (callee TY_ERR is the routing key, cstage's
|
||||
// `lhs->type == ty_err` at cmd/w6c/cgen.c:6618,6645);
|
||||
// lower to rt_abort. A user-shadowed abort/assert is
|
||||
// untagged and stays on the regular call path (#58).
|
||||
let bti: *tinfo = callee.type_: *tinfo;
|
||||
if (bti != nil && bti.kind == tykind.TY_ERR) {
|
||||
if (streq(callee.str, "abort")) {
|
||||
if (n.list != nil) {
|
||||
cgexpr(c, n.list);
|
||||
emitline("\tMOVQ\tAX, DI\n");
|
||||
emitline("\tMOVQ\tBX, SI\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t$0, DI\n");
|
||||
emitline("\tMOVQ\t$0, SI\n");
|
||||
};
|
||||
emitline("\tCALL\trt_abort(SB)\n");
|
||||
return;
|
||||
};
|
||||
if (streq(callee.str, "assert") && n.list != nil) {
|
||||
cgexpr(c, n.list);
|
||||
let skip: str = mklabel(c, "as");
|
||||
emitline("\tCMPQ\t$0, AX\n");
|
||||
emitline("\tJNE\t");
|
||||
emitline(skip);
|
||||
emitline("\n");
|
||||
let msg: *node = n.list.next;
|
||||
if (msg != nil) {
|
||||
cgexpr(c, msg);
|
||||
emitline("\tMOVQ\tAX, DI\n");
|
||||
emitline("\tMOVQ\tBX, SI\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t$0, DI\n");
|
||||
emitline("\tMOVQ\t$0, SI\n");
|
||||
};
|
||||
emitline("\tCALL\trt_abort(SB)\n");
|
||||
emitlabel(skip);
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (streq(callee.str, "append")) {
|
||||
if (n.list != nil) {
|
||||
if (n.list.next != nil) {
|
||||
|
||||
@@ -330,11 +330,20 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
if (nm.len > 0) {
|
||||
let s: *sym = scopelookupprefer(c.cur, c.curmod, nm);
|
||||
if (s == nil) {
|
||||
c.nunresolved += 1;
|
||||
if (c.verbose != 0) {
|
||||
cerr(" unresolved id: ");
|
||||
cerr(nm);
|
||||
cerr("\n");
|
||||
// Unshadowed abort/assert binds no sym BY
|
||||
// DESIGN (the EXPR_ASSERT family has no callee
|
||||
// object — see isassertfam); count it resolved
|
||||
// so wwdump -r's zero-unresolved gate holds
|
||||
// over builtin-using lib code (#58 respell).
|
||||
if (isassertfam(c, n)) {
|
||||
c.nresolved += 1;
|
||||
} else {
|
||||
c.nunresolved += 1;
|
||||
if (c.verbose != 0) {
|
||||
cerr(" unresolved id: ");
|
||||
cerr(nm);
|
||||
cerr("\n");
|
||||
};
|
||||
};
|
||||
} else { c.nresolved += 1; };
|
||||
};
|
||||
@@ -2588,6 +2597,74 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
// Hare-faithful path. This is the intercept-shim minimum to
|
||||
// unblock #15 (A.6.2.1e assertion enable).
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
// abort([msg]) / assert(cond[, msg]) — the EXPR_ASSERT
|
||||
// family (harec ref/harec/src/check.c:877,893), lowered
|
||||
// to rt_abort. Builtin only when no user symbol shadows
|
||||
// the name — the same scopelookupprefer gate as cstage
|
||||
// cmd/wcc/check.c:1536-1572 and isassertfam; a shadowed
|
||||
// call stays on the regular path (#58; the #45
|
||||
// flat-scope root is filed as task #14).
|
||||
// The TY_ERR stamp on the callee is cgen's routing key
|
||||
// (cstage spells it `n->lhs->type = ty_err`).
|
||||
if (streq(callee.str, "abort")
|
||||
&& scopelookupprefer(c.cur, c.curmod, "abort") == nil) {
|
||||
if (e.list != nil) {
|
||||
let mt: *node = exprtype(c, e.list, nil);
|
||||
let conf: bool = false;
|
||||
if (!isassignable(c, mktname(c, "str"), mt, &conf)) {
|
||||
cerr("abort: message must be str\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
if (e.list.next != nil) {
|
||||
cerr("abort: at most one arg\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
};
|
||||
let tn: *node = mktname(c, "void");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
callee.type_ = c.tc.tyerr: *void;
|
||||
return tn;
|
||||
};
|
||||
if (streq(callee.str, "assert") && e.list != nil
|
||||
&& scopelookupprefer(c.cur, c.curmod, "assert") == nil) {
|
||||
let ct: *node = exprtype(c, e.list, nil);
|
||||
if (ct != nil) {
|
||||
// No alias peel: cstage compares ty_bool by
|
||||
// IDENTITY (cmd/wcc/check.c:1560), so a
|
||||
// `type myb = bool` cond is rejected there —
|
||||
// align down (rule 10). Widening belongs to
|
||||
// the alias-peel choke-point arc (task #5,
|
||||
// #47/#68), both stages together.
|
||||
let cu: *node = unwrapbang(ct);
|
||||
let condok: bool = false;
|
||||
if (cu.kind == nkind.N_TNAME) {
|
||||
if (streq(cu.str, "bool")
|
||||
|| streq(cu.str, "untyped_bool")) {
|
||||
condok = true;
|
||||
};
|
||||
};
|
||||
if (!condok) {
|
||||
cerr("assert: cond must be bool\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
};
|
||||
if (e.list.next != nil) {
|
||||
let mt: *node = exprtype(c, e.list.next, nil);
|
||||
let conf: bool = false;
|
||||
if (!isassignable(c, mktname(c, "str"), mt, &conf)) {
|
||||
cerr("assert: message must be str\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
if (e.list.next.next != nil) {
|
||||
cerr("assert: at most two args\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
};
|
||||
let tn: *node = mktname(c, "void");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
callee.type_ = c.tc.tyerr: *void;
|
||||
return tn;
|
||||
};
|
||||
if (streq(callee.str, "len")) {
|
||||
let tn: *node = mktname(c, "i32");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
|
||||
@@ -10657,11 +10657,20 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
if (nm.len > 0) {
|
||||
let s: *sym = scopelookupprefer(c.cur, c.curmod, nm);
|
||||
if (s == nil) {
|
||||
c.nunresolved += 1;
|
||||
if (c.verbose != 0) {
|
||||
cerr(" unresolved id: ");
|
||||
cerr(nm);
|
||||
cerr("\n");
|
||||
// Unshadowed abort/assert binds no sym BY
|
||||
// DESIGN (the EXPR_ASSERT family has no callee
|
||||
// object — see isassertfam); count it resolved
|
||||
// so wwdump -r's zero-unresolved gate holds
|
||||
// over builtin-using lib code (#58 respell).
|
||||
if (isassertfam(c, n)) {
|
||||
c.nresolved += 1;
|
||||
} else {
|
||||
c.nunresolved += 1;
|
||||
if (c.verbose != 0) {
|
||||
cerr(" unresolved id: ");
|
||||
cerr(nm);
|
||||
cerr("\n");
|
||||
};
|
||||
};
|
||||
} else { c.nresolved += 1; };
|
||||
};
|
||||
@@ -12915,6 +12924,74 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
// Hare-faithful path. This is the intercept-shim minimum to
|
||||
// unblock #15 (A.6.2.1e assertion enable).
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
// abort([msg]) / assert(cond[, msg]) — the EXPR_ASSERT
|
||||
// family (harec ref/harec/src/check.c:877,893), lowered
|
||||
// to rt_abort. Builtin only when no user symbol shadows
|
||||
// the name — the same scopelookupprefer gate as cstage
|
||||
// cmd/wcc/check.c:1536-1572 and isassertfam; a shadowed
|
||||
// call stays on the regular path (#58; the #45
|
||||
// flat-scope root is filed as task #14).
|
||||
// The TY_ERR stamp on the callee is cgen's routing key
|
||||
// (cstage spells it `n->lhs->type = ty_err`).
|
||||
if (streq(callee.str, "abort")
|
||||
&& scopelookupprefer(c.cur, c.curmod, "abort") == nil) {
|
||||
if (e.list != nil) {
|
||||
let mt: *node = exprtype(c, e.list, nil);
|
||||
let conf: bool = false;
|
||||
if (!isassignable(c, mktname(c, "str"), mt, &conf)) {
|
||||
cerr("abort: message must be str\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
if (e.list.next != nil) {
|
||||
cerr("abort: at most one arg\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
};
|
||||
let tn: *node = mktname(c, "void");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
callee.type_ = c.tc.tyerr: *void;
|
||||
return tn;
|
||||
};
|
||||
if (streq(callee.str, "assert") && e.list != nil
|
||||
&& scopelookupprefer(c.cur, c.curmod, "assert") == nil) {
|
||||
let ct: *node = exprtype(c, e.list, nil);
|
||||
if (ct != nil) {
|
||||
// No alias peel: cstage compares ty_bool by
|
||||
// IDENTITY (cmd/wcc/check.c:1560), so a
|
||||
// `type myb = bool` cond is rejected there —
|
||||
// align down (rule 10). Widening belongs to
|
||||
// the alias-peel choke-point arc (task #5,
|
||||
// #47/#68), both stages together.
|
||||
let cu: *node = unwrapbang(ct);
|
||||
let condok: bool = false;
|
||||
if (cu.kind == nkind.N_TNAME) {
|
||||
if (streq(cu.str, "bool")
|
||||
|| streq(cu.str, "untyped_bool")) {
|
||||
condok = true;
|
||||
};
|
||||
};
|
||||
if (!condok) {
|
||||
cerr("assert: cond must be bool\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
};
|
||||
if (e.list.next != nil) {
|
||||
let mt: *node = exprtype(c, e.list.next, nil);
|
||||
let conf: bool = false;
|
||||
if (!isassignable(c, mktname(c, "str"), mt, &conf)) {
|
||||
cerr("assert: message must be str\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
if (e.list.next.next != nil) {
|
||||
cerr("assert: at most two args\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
};
|
||||
let tn: *node = mktname(c, "void");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
callee.type_ = c.tc.tyerr: *void;
|
||||
return tn;
|
||||
};
|
||||
if (streq(callee.str, "len")) {
|
||||
let tn: *node = mktname(c, "i32");
|
||||
e.type_ = tinfofornode(c, tn): *void;
|
||||
@@ -26006,6 +26083,46 @@ fn cgcall(c: *cgen, n: *node) void = {
|
||||
let callee: *node = n.lhs;
|
||||
if (callee != nil) {
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
// abort([msg]) / assert(cond[, msg]) — checker-tagged
|
||||
// builtins (callee TY_ERR is the routing key, cstage's
|
||||
// `lhs->type == ty_err` at cmd/w6c/cgen.c:6618,6645);
|
||||
// lower to rt_abort. A user-shadowed abort/assert is
|
||||
// untagged and stays on the regular call path (#58).
|
||||
let bti: *tinfo = callee.type_: *tinfo;
|
||||
if (bti != nil && bti.kind == tykind.TY_ERR) {
|
||||
if (streq(callee.str, "abort")) {
|
||||
if (n.list != nil) {
|
||||
cgexpr(c, n.list);
|
||||
emitline("\tMOVQ\tAX, DI\n");
|
||||
emitline("\tMOVQ\tBX, SI\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t$0, DI\n");
|
||||
emitline("\tMOVQ\t$0, SI\n");
|
||||
};
|
||||
emitline("\tCALL\trt_abort(SB)\n");
|
||||
return;
|
||||
};
|
||||
if (streq(callee.str, "assert") && n.list != nil) {
|
||||
cgexpr(c, n.list);
|
||||
let skip: str = mklabel(c, "as");
|
||||
emitline("\tCMPQ\t$0, AX\n");
|
||||
emitline("\tJNE\t");
|
||||
emitline(skip);
|
||||
emitline("\n");
|
||||
let msg: *node = n.list.next;
|
||||
if (msg != nil) {
|
||||
cgexpr(c, msg);
|
||||
emitline("\tMOVQ\tAX, DI\n");
|
||||
emitline("\tMOVQ\tBX, SI\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t$0, DI\n");
|
||||
emitline("\tMOVQ\t$0, SI\n");
|
||||
};
|
||||
emitline("\tCALL\trt_abort(SB)\n");
|
||||
emitlabel(skip);
|
||||
return;
|
||||
};
|
||||
};
|
||||
if (streq(callee.str, "append")) {
|
||||
if (n.list != nil) {
|
||||
if (n.list.next != nil) {
|
||||
|
||||
Reference in New Issue
Block a user