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).
This commit is contained in:
2026-05-11 09:42:02 +09:00
parent cb57cd795d
commit 635818eb13
2 changed files with 88 additions and 3 deletions

View File

@@ -507,7 +507,14 @@ cgexpr(Cg *c, Node *n, Local *locals)
Type *t = n->type; Type *t = n->type;
Type *u = (t && t->kind == TY_NAMED) ? t->under : t; Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
if (u && u->kind == TY_FN) { 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; break;
} }
for (Sdef *s = sdefs; s; s = s->next) { 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)); ins2(c, A_XORQ, aimm(1), areg(D_AX));
break; 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)) { if (node_isfloat(n)) {
int isf32 = node_isf32(n); int isf32 = node_isf32(n);
int mov = isf32 ? A_MOVSS : A_MOVSD; 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) if (cu && cu->kind == TY_FN && cu->variadic)
ins2(c, A_XORQ, areg(D_AX), areg(D_AX)); ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
if (n->lhs->kind == N_IDENT) { 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 && } else if (n->lhs->kind == N_DOT && n->lhs->lhs &&
n->lhs->lhs->kind == N_IDENT) { n->lhs->lhs->kind == N_IDENT) {
/* `m.fn()` is module-qualified iff the ident has no /* `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 *t = n->type;
Type *tu = (t && t->kind == TY_NAMED) ? t->under : t; Type *tu = (t && t->kind == TY_NAMED) ? t->under : t;
if (tu && tu->kind == TY_FN) { 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; break;
} }
for (Sdef *s = sdefs; s; s = s->next) { for (Sdef *s = sdefs; s; s = s->next) {

View File

@@ -62,6 +62,26 @@ static const struct row rows[] = {
/* float: arg, arith, literal, cast back to int */ /* float: arg, arith, literal, cast back to int */
{ "fn area(r: f64) f64 = { return 3.14 * r * r; };\n" { "fn area(r: f64) f64 = { return 3.14 * r * r; };\n"
"fn main() i32 = { let a: f64 = area(5.0); return a: i32; };", 78 }, "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 */ /* string literal via syscall — exit code = bytes written */
{ "@symbol(\"rt_syscall\") fn rt_syscall(num: i64, a: i64, b: i64, c: i64) i64;\n" { "@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" "fn print(s: str) i64 = { return rt_syscall(1, 1, s.ptr: i64, s.len: i64); };\n"