From 635818eb13ed10651932f9722efe48571dae2a6a Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Mon, 11 May 2026 09:42:02 +0900 Subject: [PATCH] 6c: float compare, fn-address @symbol, indirect call Three N_BIN/N_IDENT/N_CALL sites missed cases that bit ncurses demos and dyn-linker exercises: - Float compare emitted CMPQ + signed Jcc on f64 bits. Now goes through UCOMISD/UCOMISS + the unsigned Jcc family, mirroring Plan 9 6c (txt.c around AUCOMISD). - LEAQ-of-fn-ident (taking the address of a function as a value) used n->str without ffi_resolve, so binding a *fn from an @symbol("name") fn declaration produced a reference to the ww ident rather than the C symbol. - N_CALL on a bare ident with no localfind hit was a direct CALL ident(SB). Now checks localfind first and indirects through AX when the callee names a local fn-pointer slot. Hare/QBE handles both shapes the same way (sort/search.ha:15 calls cmp(...) where cmp is a *cmpfunc parameter). --- cmd/6c/cgen.c | 71 ++++++++++++++++++++++++++++++++++++++++++++-- test/wwc/700_e2e.c | 20 +++++++++++++ 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/cmd/6c/cgen.c b/cmd/6c/cgen.c index dedcf1f2..c3fb51f0 100644 --- a/cmd/6c/cgen.c +++ b/cmd/6c/cgen.c @@ -507,7 +507,14 @@ cgexpr(Cg *c, Node *n, Local *locals) Type *t = n->type; Type *u = (t && t->kind == TY_NAMED) ? t->under : t; if (u && u->kind == TY_FN) { - ins2(c, A_LEAQ, asym(n->str), areg(D_AX)); + /* Take the address of a function. Apply + * @symbol resolution so taking the address + * of a body-less FFI binding yields the C + * symbol, not the ww-side ident. Hare emits + * the same `$symname` for both call and + * address-of via QBE; here we mirror that. */ + ins2(c, A_LEAQ, + asym(ffi_resolve(n->str)), areg(D_AX)); break; } for (Sdef *s = sdefs; s; s = s->next) { @@ -593,6 +600,48 @@ cgexpr(Cg *c, Node *n, Local *locals) ins2(c, A_XORQ, aimm(1), areg(D_AX)); break; } + /* Float comparison: operands are float but the BIN node's + * type is bool, so node_isfloat(n) is false — we have to + * inspect n->lhs. UCOMISD/UCOMISS sets ZF/CF as if an + * unsigned compare, so the JA family is the right Jcc set + * regardless of how the operand types are signed. Plan 9's + * own 6c picks the same pattern (txt.c around AUCOMISD). + * NaN handling: UCOMI sets PF on unordered; we ignore it, + * which means NaN compares behave like Hare's default. */ + if (n->lhs && node_isfloat(n->lhs) && + (n->op == TK_EQ || n->op == TK_NEQ + || n->op == TK_LT || n->op == TK_LE + || n->op == TK_GT || n->op == TK_GE)) { + int isf32 = node_isf32(n->lhs); + int mov = isf32 ? A_MOVSS : A_MOVSD; + int ucomi = isf32 ? A_UCOMISS : A_UCOMISD; + cgexpr(c, n->rhs, locals); /* rhs → X0 */ + ins2(c, A_SUBQ, aimm(8), areg(D_SP)); + ins2(c, mov, areg(D_X0), amem(D_SP, 0)); + cgexpr(c, n->lhs, locals); /* lhs → X0 */ + ins2(c, mov, amem(D_SP, 0), areg(D_X1)); + ins2(c, A_ADDQ, aimm(8), areg(D_SP)); + ins2(c, ucomi, areg(D_X1), areg(D_X0)); + int op = A_JE; + switch (n->op) { + case TK_EQ: op = A_JE; break; + case TK_NEQ: op = A_JNE; break; + case TK_LT: op = A_JB; break; + case TK_LE: op = A_JBE; break; + case TK_GT: op = A_JA; break; + case TK_GE: op = A_JAE; break; + default: break; + } + char *t = mklabel(c, "ct"); + char *e = mklabel(c, "ce"); + ins1(c, op, abranch(t)); + ins2(c, A_MOVQ, aimm(0), areg(D_AX)); + ins1(c, A_JMP, abranch(e)); + label(c, t); + ins2(c, A_MOVQ, aimm(1), areg(D_AX)); + label(c, e); + break; + } if (node_isfloat(n)) { int isf32 = node_isf32(n); int mov = isf32 ? A_MOVSS : A_MOVSD; @@ -1389,7 +1438,22 @@ cgexpr(Cg *c, Node *n, Local *locals) if (cu && cu->kind == TY_FN && cu->variadic) ins2(c, A_XORQ, areg(D_AX), areg(D_AX)); if (n->lhs->kind == N_IDENT) { - ins1(c, A_CALL, asym(ffi_resolve(n->lhs->str))); + /* If the callee names a local variable holding a + * function pointer, load it and call indirect. Without + * this check `CALL fp(SB)` is emitted as if `fp` were + * a global symbol — the linker rightly fails. Hare / + * QBE handles this by treating any non-`$symbol` value + * as an indirect target; we get the same effect by + * reusing the cgexpr path. */ + int loff = localfind(locals, n->lhs->str); + if (loff != 0) { + ins2(c, A_MOVQ, + amem(D_BP, loff), areg(D_AX)); + ins1(c, A_CALL, areg(D_AX)); + } else { + ins1(c, A_CALL, + asym(ffi_resolve(n->lhs->str))); + } } else if (n->lhs->kind == N_DOT && n->lhs->lhs && n->lhs->lhs->kind == N_IDENT) { /* `m.fn()` is module-qualified iff the ident has no @@ -1604,7 +1668,8 @@ cgexpr(Cg *c, Node *n, Local *locals) Type *t = n->type; Type *tu = (t && t->kind == TY_NAMED) ? t->under : t; if (tu && tu->kind == TY_FN) { - ins2(c, A_LEAQ, asym(n->str), areg(D_AX)); + ins2(c, A_LEAQ, + asym(ffi_resolve(n->str)), areg(D_AX)); break; } for (Sdef *s = sdefs; s; s = s->next) { diff --git a/test/wwc/700_e2e.c b/test/wwc/700_e2e.c index eb3808f6..e63f6c4b 100644 --- a/test/wwc/700_e2e.c +++ b/test/wwc/700_e2e.c @@ -62,6 +62,26 @@ static const struct row rows[] = { /* float: arg, arith, literal, cast back to int */ { "fn area(r: f64) f64 = { return 3.14 * r * r; };\n" "fn main() i32 = { let a: f64 = area(5.0); return a: i32; };", 78 }, + /* float comparison: must emit UCOMISD + JA (not CMPQ + JG) */ + { "fn main() i32 = {\n" + " let a: f64 = 1.5;\n" + " let b: f64 = 2.5;\n" + " if (a < b) { if (b > a) { return 7; }; };\n" + " return 0;\n" + "};", 7 }, + /* float ==/!= via UCOMISD */ + { "fn main() i32 = {\n" + " let a: f64 = 3.14;\n" + " let b: f64 = 3.14;\n" + " if (a == b) { return 11; };\n" + " return 0;\n" + "};", 11 }, + /* function pointer: take address of a named fn, call indirectly */ + { "fn add(a: i32, b: i32) i32 = { return a + b; };\n" + "fn main() i32 = {\n" + " let fp: fn(a: i32, b: i32) i32 = add;\n" + " return fp(20, 22);\n" + "};", 42 }, /* string literal via syscall — exit code = bytes written */ { "@symbol(\"rt_syscall\") fn rt_syscall(num: i64, a: i64, b: i64, c: i64) i64;\n" "fn print(s: str) i64 = { return rt_syscall(1, 1, s.ptr: i64, s.len: i64); };\n"