Compare commits

...

4 Commits

Author SHA1 Message Date
Quentin Carbonneaux
ac48f83f17 extern DYNCONST
New DYNCONST flag to access symbols from
dynamically-linked libraries. It can also
be used by frontends to implement PIC.

Review by mcf
2026-05-03 11:34:53 +02:00
Quentin Carbonneaux
577b4667db negated conditions rework
Floating point comparisons are subtle due to
unordered operands (i.e., when one or two of
the operands are nans).

In amd64 we arrange post isel to make sure we
only have comparisons that will return false
on unordered operands and for which we have a
negated version (returning true on unordered
operands).  I made this situation a bit more
explicit by marking unexpected comparisons
with "?" in amd64/emit.c.

In arm64, the instruction set is rich enough
to have a negated version for all operators
that returns true on unordered inputs. So we
build a backend specific table.

I made sure to remove cmpneg() from utils.c;
it is a footgun as it does not work well for
floating point ops because of the 'unordered'
edge case.

I found the ARM docs about condition codes
pretty bad; the following C program is a
good substitute:

    #include <stdio.h>
    int
    flags(float a, float b)
    {
    	int z, c, n, v, le;
    	__asm__(
    		"fcmpe %s5, %s6\n"
    		"\tcset %0, eq\n"
    		"\tcset %1, mi\n"
    		"\tcset %2, cs\n"
    		"\tcset %3, vs\n"
    		"\tcset %4, le\n"
    	: "=r"(z),"=r"(n),"=r"(c),"=r"(v),"=r"(le)
    	: "x"(a), "x"(b)
    	: "cc");
    	printf(
    		"cmp(%g,%g): z=%d n=%d c=%d v=%d le=%d\n",
    		a, b, z, n, c, v, le);
    }
    int
    main()
    {
    	flags(1.0, 1.0);
    	flags(0.0, 1.0);
    	flags(1.0, 0.0);
    	flags(0.0, 0.0/0.0);
    	flags(0.0/0.0, 0.0);
    }

Compile & run with:

    aarch64-linux-gnu-gcc -static -no-pie flags.c
    qemu-aarch64 ./a.out
2026-04-29 17:45:20 +02:00
Quentin Carbonneaux
bef981e9a1 no cmpneg() for float comparisons 2026-04-29 15:04:27 +02:00
Quentin Carbonneaux
0454fa259b arm64: fix unordered fp comparisons 2026-04-29 13:37:22 +02:00
10 changed files with 192 additions and 111 deletions

7
all.h
View File

@ -293,8 +293,10 @@ struct Use {
struct Sym {
enum {
SGlo,
SThr,
SGlo = 0, /* direct access */
SThr = 1, /* local-exec TLS */
SExt = 2, /* GOT/PLT access */
SExtThr = SExt|SThr, /* initial-exec TLS */
} type;
uint32_t id;
};
@ -500,7 +502,6 @@ void emiti(Ins);
void idup(Blk *, Ins *, ulong);
Ins *icpy(Ins *, Ins *, ulong);
int cmpop(int);
int cmpneg(int);
int cmpwlneg(int);
int clsmerge(short *, short);
int phicls(int, Tmp *);

View File

@ -22,8 +22,8 @@ struct E {
X(Ciuge, "ae", "b") \
X(Cieq, "z", "nz") \
X(Cine, "nz", "z") \
X(NCmpI+Cfle, "be", "a") \
X(NCmpI+Cflt, "b", "ae") \
X(NCmpI+Cfle, "?" , "?") \
X(NCmpI+Cflt, "?", "?") \
X(NCmpI+Cfgt, "a", "be") \
X(NCmpI+Cfge, "ae", "b") \
X(NCmpI+Cfo, "np", "p") \
@ -196,12 +196,12 @@ emitcon(Con *con, E *e)
l = str(con->sym.id);
p = l[0] == '"' ? "" : T.assym;
if (con->sym.type == SThr) {
if (T.apple)
fprintf(e->f, "%s%s@TLVP", p, l);
else
fprintf(e->f, "%%fs:%s%s@tpoff", p, l);
} else
assert(!T.apple);
fprintf(e->f, "%%fs:%s%s@tpoff", p, l);
} else {
assert((con->sym.type & ~SExt) == SGlo);
fprintf(e->f, "%s%s", p, l);
}
if (con->bits.i)
fprintf(e->f, "%+"PRId64, con->bits.i);
break;
@ -380,7 +380,7 @@ Next:
off = e->fn->con[ref.val];
emitcon(&off, e);
if (off.type == CAddr)
if (off.sym.type != SThr || T.apple)
if (off.sym.type != SThr)
fprintf(e->f, "(%%rip)");
break;
case RTmp:
@ -532,14 +532,22 @@ emitins(Ins i, E *e)
emitf("mov%k %0, %=", &i, e);
break;
case Oaddr:
if (!T.apple
&& rtype(i.arg[0]) == RCon
&& e->fn->con[i.arg[0].val].sym.type == SThr) {
if (rtype(i.arg[0]) != RCon)
goto Table;
con = &e->fn->con[i.arg[0].val];
assert(isreg(i.to) && con->type == CAddr);
sym = str(con->sym.id);
if (T.apple && (con->sym.type & SThr)) {
fprintf(e->f,
"\tmovq %s%s@tlvp(%%rip), %%%s\n",
sym[0] == '"' ? "" : T.assym, sym,
regtoa(i.to.val, SLong));
break;
}
switch (con->sym.type) {
case SThr:
/* derive the symbol address from the TCB
* address at offset 0 of %fs */
assert(isreg(i.to));
con = &e->fn->con[i.arg[0].val];
sym = str(con->sym.id);
emitf("movq %%fs:0, %L=", &i, e);
fprintf(e->f, "\tleaq %s%s@tpoff",
sym[0] == '"' ? "" : T.assym, sym);
@ -550,15 +558,40 @@ emitins(Ins i, E *e)
regtoa(i.to.val, SLong),
regtoa(i.to.val, SLong));
break;
case SExtThr:
/* initial-exec TLS: load offset from
* GOT, add to thread-base register */
assert(!con->bits.i);
emitf("movq %%fs:0, %L=", &i, e);
fprintf(e->f,
"\taddq %s%s@gottpoff(%%rip), %%%s\n",
sym[0] == '"' ? "" : T.assym, sym,
regtoa(i.to.val, SLong));
break;
case SExt:
/* load address from the GOT */
assert(!con->bits.i);
fprintf(e->f,
"\tmovq %s%s@gotpcrel(%%rip), %%%s\n",
sym[0] == '"' ? "" : T.assym, sym,
regtoa(i.to.val, SLong));
break;
default:
goto Table;
}
goto Table;
break;
case Ocall:
/* calls simply have a weird syntax in AT&T
* assembly... */
switch (rtype(i.arg[0])) {
case RCon:
con = &e->fn->con[i.arg[0].val];
fprintf(e->f, "\tcallq ");
emitcon(&e->fn->con[i.arg[0].val], e);
emitcon(con, e);
if (con->type == CAddr
&& (con->sym.type & SExt)
&& !T.apple)
fprintf(e->f, "@plt");
fprintf(e->f, "\n");
break;
case RTmp:
@ -627,8 +660,8 @@ sysv_framesz(E *e)
void
amd64_sysv_emitfn(Fn *fn, FILE *f)
{
static char *ctoa[] = {
#define X(c, s, _) [c] = s,
static char *ctoa[][2] = {
#define X(c, s, n) [c] = {s, n},
CMP(X)
#undef X
};
@ -714,9 +747,10 @@ amd64_sysv_emitfn(Fn *fn, FILE *f)
s = b->s1;
b->s1 = b->s2;
b->s2 = s;
n = 0;
} else
c = cmpneg(c);
fprintf(f, "\tj%s %sbb%d\n", ctoa[c],
n = 1;
fprintf(f, "\tj%s %sbb%d\n", ctoa[c][n],
T.asloc, id0+b->s2->id);
goto Jmp;
}
@ -752,15 +786,15 @@ winabi_framesz(E *e)
void
amd64_winabi_emitfn(Fn *fn, FILE *f)
{
static char *ctoa[] = {
#define X(c, s, _) [c] = s,
static char *ctoa[][2] = {
#define X(c, s, n) [c] = {s, n},
CMP(X)
#undef X
};
static int id0;
Blk *b, *s;
Ins *i, itmp;
int *r, c, lbl;
int *r, c, n, lbl;
E *e;
e = &(E){.f = f, .fn = fn};
@ -827,13 +861,14 @@ amd64_winabi_emitfn(Fn *fn, FILE *f)
default:
c = b->jmp.type - Jjf;
if (0 <= c && c <= NCmp) {
if (b->link == b->s2) {
if (b->link == b->s2 || c >= NCmpI) {
s = b->s1;
b->s1 = b->s2;
b->s2 = s;
n = 0;
} else
c = cmpneg(c);
fprintf(f, "\tj%s %sbb%d\n", ctoa[c],
n = 1;
fprintf(f, "\tj%s %sbb%d\n", ctoa[c][n],
T.asloc, id0+b->s2->id);
goto Jmp;
}

View File

@ -120,8 +120,9 @@ fixarg(Ref *r, int k, Ins *i, Fn *fn)
r1 = newtmp("isel", Kl, fn);
emit(Oaddr, Kl, r1, SLOT(s), R);
}
else if (T.apple && hascon(r0, &c, fn)
&& c->type == CAddr && c->sym.type == SThr) {
else if (op != Ocall && hascon(r0, &c, fn)
&& c->type == CAddr && ((c->sym.type & SExt)
|| (T.apple && c->sym.type == SThr))) {
r1 = newtmp("isel", Kl, fn);
if (c->bits.i) {
r2 = newtmp("isel", Kl, fn);
@ -131,16 +132,18 @@ fixarg(Ref *r, int k, Ins *i, Fn *fn)
emit(Oadd, Kl, r1, r2, r3);
} else
r2 = r1;
emit(Ocopy, Kl, r2, TMP(RAX), R);
r2 = newtmp("isel", Kl, fn);
r3 = newtmp("isel", Kl, fn);
emit(Ocall, 0, R, r3, CALL(17));
emit(Ocopy, Kl, TMP(RDI), r2, R);
emit(Oload, Kl, r3, r2, R);
if (T.apple && (c->sym.type & SThr)) {
emit(Ocopy, Kl, r2, TMP(RAX), R);
r2 = newtmp("isel", Kl, fn);
r3 = newtmp("isel", Kl, fn);
emit(Ocall, 0, R, r3, CALL(17));
emit(Ocopy, Kl, TMP(RDI), r2, R);
emit(Oload, Kl, r3, r2, R);
}
cc = *c;
cc.bits.i = 0;
r3 = newcon(&cc, fn);
emit(Oload, Kl, r2, r3, R);
emit(Oaddr, Kl, r2, r3, R);
if (rtype(r0) == RMem) {
m = &fn->mem[r0.val];
m->offset.type = CUndef;
@ -151,9 +154,8 @@ fixarg(Ref *r, int k, Ins *i, Fn *fn)
else if (!(isstore(op) && r == &i->arg[1])
&& !isload(op) && op != Ocall && rtype(r0) == RCon
&& fn->con[r0.val].type == CAddr) {
/* apple as does not support 32-bit
* absolute addressing, use a rip-
* relative leaq instead
/* turn address operands into
* lea/mov instructions
*/
r1 = newtmp("isel", Kl, fn);
emit(Oaddr, Kl, r1, r0, R);

View File

@ -10,24 +10,24 @@ struct E {
};
#define CMP(X) \
X(Cieq, "eq") \
X(Cine, "ne") \
X(Cisge, "ge") \
X(Cisgt, "gt") \
X(Cisle, "le") \
X(Cislt, "lt") \
X(Ciuge, "cs") \
X(Ciugt, "hi") \
X(Ciule, "ls") \
X(Ciult, "cc") \
X(NCmpI+Cfeq, "eq") \
X(NCmpI+Cfge, "ge") \
X(NCmpI+Cfgt, "gt") \
X(NCmpI+Cfle, "ls") \
X(NCmpI+Cflt, "mi") \
X(NCmpI+Cfne, "ne") \
X(NCmpI+Cfo, "vc") \
X(NCmpI+Cfuo, "vs")
X(Cieq, "eq", "ne") \
X(Cine, "ne", "eq") \
X(Cisge, "ge", "lt") \
X(Cisgt, "gt", "le") \
X(Cisle, "le", "gt") \
X(Cislt, "lt", "ge") \
X(Ciuge, "cs", "cc") \
X(Ciugt, "hi", "ls") \
X(Ciule, "ls", "hi") \
X(Ciult, "cc", "cs") \
X(NCmpI+Cfeq, "eq", "ne") \
X(NCmpI+Cfge, "ge", "lt") \
X(NCmpI+Cfgt, "gt", "le") \
X(NCmpI+Cfle, "ls", "hi") \
X(NCmpI+Cflt, "mi", "pl") \
X(NCmpI+Cfne, "ne", "eq") \
X(NCmpI+Cfo, "vc", "vs") \
X(NCmpI+Cfuo, "vs", "vc")
enum {
Ki = -1, /* matches Kw and Kl */
@ -102,7 +102,7 @@ static struct {
{ Oacmn, Ki, "cmn %0, %1" },
{ Oafcmp, Ka, "fcmpe %0, %1" },
#define X(c, str) \
#define X(c, str, _) \
{ Oflag+c, Ki, "cset %=, " str },
CMP(X)
#undef X
@ -278,6 +278,10 @@ loadaddr(Con *c, char *rn, E *e)
s = "\tadrp\tR, SO\n"
"\tadd\tR, R, #:lo12:SO\n";
break;
case SExtThr:
if (!T.apple)
die("extern thread unavailable on arm64");
/* fall through */
case SThr:
if (T.apple)
s = "\tadrp\tR, S@tlvppage\n"
@ -287,6 +291,14 @@ loadaddr(Con *c, char *rn, E *e)
"\tadd\tR, R, #:tprel_hi12:SO, lsl #12\n"
"\tadd\tR, R, #:tprel_lo12_nc:SO\n";
break;
case SExt:
if (T.apple)
s = "\tadrp\tR, S@gotpageO\n"
"\tldr\tR, [R, S@gotpageoffO]\n";
else
s = "\tadrp\tR, :got:SO\n"
"\tldr\tR, [R, #:got_lo12:SO]\n";
break;
}
l = str(c->sym.id);
@ -468,7 +480,7 @@ emitins(Ins *i, E *e)
goto Table;
c = &e->fn->con[i->arg[0].val];
if (c->type != CAddr
|| c->sym.type != SGlo
|| (c->sym.type & SThr)
|| c->bits.i)
die("invalid call argument");
l = str(c->sym.id);
@ -532,8 +544,8 @@ framelayout(E *e)
void
arm64_emitfn(Fn *fn, FILE *out)
{
static char *ctoa[] = {
#define X(c, s) [c] = s,
static char *ctoa[][2] = {
#define X(c, s, n) [c] = {s, n},
CMP(X)
#undef X
};
@ -664,11 +676,12 @@ arm64_emitfn(Fn *fn, FILE *out)
t = b->s1;
b->s1 = b->s2;
b->s2 = t;
n = 0;
} else
c = cmpneg(c);
n = 1;
fprintf(e->f,
"\tb%s\t%s%d\n",
ctoa[c], T.asloc, id0+b->s2->id
ctoa[c][n], T.asloc, id0+b->s2->id
);
goto Jmp;
}

View File

@ -80,7 +80,7 @@ fixarg(Ref *pr, int k, int phi, Fn *fn)
c = &fn->con[r0.val];
if (T.apple
&& c->type == CAddr
&& c->sym.type == SThr) {
&& (c->sym.type & SThr)) {
r1 = newtmp("isel", Kl, fn);
*pr = r1;
if (c->bits.i) {

View File

@ -180,7 +180,9 @@ by zero-extension, or by sign-extension.
DYNCONST :=
CONST
| 'thread' $IDENT # Thread-local symbol
| 'thread' $IDENT # Thread-local symbol
| 'extern' $IDENT # Extern symbol (GOT)
| 'extern' 'thread' $IDENT # Extern thread-local (initial-exec)
VAL :=
DYNCONST
@ -225,6 +227,15 @@ When the `thread` keyword prefixes a symbol name, the
symbol's numeric value is resolved at runtime in the
thread-local storage.
When the `extern` keyword prefixes a symbol name, the
symbol is accessed indirectly through a table edited
by the dynamic linker (e.g., GOT/PLT). This enables
PIE/PIC code generation. When `extern` is combined
with `thread`, the symbol is accessed using the
initial-exec TLS model, suitable for thread-local
variables defined in shared objects available at
startup time (i.e., not loaded through dlopen).
Vals are used as arguments in regular, phi, and jump
instructions within function definitions. They are
either constants or function-scope temporaries.

28
parse.c
View File

@ -57,6 +57,7 @@ enum Token {
Thlt,
Texport,
Tthread,
Textern,
Tcommon,
Tfunc,
Ttype,
@ -116,6 +117,7 @@ static char *kwmap[Ntok] = {
[Thlt] = "hlt",
[Texport] = "export",
[Tthread] = "thread",
[Textern] = "extern",
[Tcommon] = "common",
[Tfunc] = "function",
[Ttype] = "type",
@ -427,11 +429,10 @@ static Ref
parseref()
{
Con c;
int tok;
memset(&c, 0, sizeof c);
switch (next()) {
default:
return R;
switch ((tok = next())) {
case Ttmp:
return tmpref(tokval.str);
case Tint:
@ -448,9 +449,20 @@ parseref()
c.bits.d = tokval.fltd;
c.flt = 2;
break;
case Tthread:
c.sym.type = SThr;
expect(Tglo);
default:
for (;; tok=next()) {
switch (tok) {
case Textern:
c.sym.type |= SExt;
continue;
case Tthread:
c.sym.type |= SThr;
continue;
}
break;
}
if (tok != Tglo)
return R;
/* fall through */
case Tglo:
c.type = CAddr;
@ -1255,7 +1267,9 @@ printcon(Con *c, FILE *f)
case CUndef:
break;
case CAddr:
if (c->sym.type == SThr)
if (c->sym.type & SExt)
fprintf(f, "extern ");
if (c->sym.type & SThr)
fprintf(f, "thread ");
fprintf(f, "$%s", str(c->sym.id));
if (c->bits.i)

View File

@ -131,7 +131,7 @@ slot(Ref r, Fn *fn)
static void
emitaddr(Con *c, FILE *f)
{
assert(c->sym.type == SGlo);
assert((c->sym.type & ~SExt) == SGlo);
fputs(str(c->sym.id), f);
if (c->bits.i)
fprintf(f, "+%"PRIi64, c->bits.i);
@ -231,7 +231,20 @@ loadaddr(Con *c, char *rn, FILE *f)
{
char off[32];
if (c->sym.type == SThr) {
switch (c->sym.type) {
case SGlo:
fprintf(f, "\tlui %s, %%hi(", rn);
emitaddr(c, f);
fprintf(f, ")\n\taddi %s, %s, %%lo(", rn, rn);
emitaddr(c, f);
fputs(")\n", f);
break;
case SExt:
fprintf(f, "\tla %s, ", rn);
emitaddr(c, f);
fputc('\n', f);
break;
case SThr:
if (c->bits.i)
sprintf(off, "+%"PRIi64, c->bits.i);
else
@ -242,10 +255,9 @@ loadaddr(Con *c, char *rn, FILE *f)
rn, rn, str(c->sym.id), off);
fprintf(f, "\taddi %s, %s, %%tprel_lo(%s)%s\n",
rn, rn, str(c->sym.id), off);
} else {
fprintf(f, "\tla %s, ", rn);
emitaddr(c, f);
fputc('\n', f);
break;
case SExtThr:
die("extern thread unavailable on rv64");
}
}
@ -282,7 +294,7 @@ fixmem(Ref *pr, Fn *fn, FILE *f)
if (rtype(r) == RCon) {
c = &fn->con[r.val];
if (c->type == CAddr)
if (c->sym.type == SThr) {
if (c->sym.type != SGlo) {
loadcon(c, T6, Kl, f);
*pr = TMP(T6);
}
@ -387,7 +399,7 @@ emitins(Ins *i, Fn *fn, FILE *f)
case RCon:
con = &fn->con[i->arg[0].val];
if (con->type != CAddr
|| con->sym.type != SGlo
|| (con->sym.type & SThr)
|| con->bits.i)
goto Invalid;
fprintf(f, "\tcall %s\n", str(con->sym.id));

View File

@ -29,7 +29,7 @@ char *tok[] = {
"function", "type", "data", "section", "align", "dbgfile",
"blit", "l", "w", "sh", "uh", "h", "sb", "ub", "b",
"d", "s", "z", "loadw", "loadl", "loads", "loadd",
"alloc1", "alloc2", "thread", "common",
"alloc1", "alloc2", "thread", "extern", "common",
};
enum {

49
util.c
View File

@ -343,34 +343,27 @@ icpy(Ins *d, Ins *s, ulong n)
}
static int cmptab[][2] ={
/* negation swap */
[Ciule] = {Ciugt, Ciuge},
[Ciult] = {Ciuge, Ciugt},
[Ciugt] = {Ciule, Ciult},
[Ciuge] = {Ciult, Ciule},
[Cisle] = {Cisgt, Cisge},
[Cislt] = {Cisge, Cisgt},
[Cisgt] = {Cisle, Cislt},
[Cisge] = {Cislt, Cisle},
[Cieq] = {Cine, Cieq},
[Cine] = {Cieq, Cine},
[NCmpI+Cfle] = {NCmpI+Cfgt, NCmpI+Cfge},
[NCmpI+Cflt] = {NCmpI+Cfge, NCmpI+Cfgt},
[NCmpI+Cfgt] = {NCmpI+Cfle, NCmpI+Cflt},
[NCmpI+Cfge] = {NCmpI+Cflt, NCmpI+Cfle},
[NCmpI+Cfeq] = {NCmpI+Cfne, NCmpI+Cfeq},
[NCmpI+Cfne] = {NCmpI+Cfeq, NCmpI+Cfne},
[NCmpI+Cfo] = {NCmpI+Cfuo, NCmpI+Cfo},
[NCmpI+Cfuo] = {NCmpI+Cfo, NCmpI+Cfuo},
/* negation swap */
[Ciule] = {Ciugt, Ciuge},
[Ciult] = {Ciuge, Ciugt},
[Ciugt] = {Ciule, Ciult},
[Ciuge] = {Ciult, Ciule},
[Cisle] = {Cisgt, Cisge},
[Cislt] = {Cisge, Cisgt},
[Cisgt] = {Cisle, Cislt},
[Cisge] = {Cislt, Cisle},
[Cieq] = {Cine, Cieq},
[Cine] = {Cieq, Cine},
[NCmpI+Cfle] = {-1, NCmpI+Cfge},
[NCmpI+Cflt] = {-1, NCmpI+Cfgt},
[NCmpI+Cfgt] = {-1, NCmpI+Cflt},
[NCmpI+Cfge] = {-1, NCmpI+Cfle},
[NCmpI+Cfeq] = {-1, NCmpI+Cfeq},
[NCmpI+Cfne] = {-1, NCmpI+Cfne},
[NCmpI+Cfo] = {-1, NCmpI+Cfo},
[NCmpI+Cfuo] = {-1, NCmpI+Cfuo},
};
int
cmpneg(int c)
{
assert(0 <= c && c < NCmp);
return cmptab[c][0];
}
int
cmpop(int c)
{
@ -382,9 +375,9 @@ int
cmpwlneg(int op)
{
if (INRANGE(op, Ocmpw, Ocmpw1))
return cmpneg(op - Ocmpw) + Ocmpw;
return cmptab[op - Ocmpw][0] + Ocmpw;
if (INRANGE(op, Ocmpl, Ocmpl1))
return cmpneg(op - Ocmpl) + Ocmpl;
return cmptab[op - Ocmpl][0] + Ocmpl;
die("not a wl comparison");
}