qbe/arm64/isel.c
Michael Forney 80d745c842 arm64: fix extern with offset
We need to add the offset after loading from the GOT.

This is a fix for the issue reported in [0], and removes the offsets
in isel to avoid the problem with large offsets in the naive patch.
This is similar to what is done on amd64.

Tested with cproc bootstrap using extern on aarch64.

[0] https://lists.sr.ht/~mpu/qbe/%3Cd71fac06-1208-4e4f-85a4-e5edc7fd979c@app.fastmail.com%3E#%3C2F4DUTRJ9GA5V.3VK2QXK848F7U@mforney.org%3E
2026-06-08 11:56:20 +02:00

319 lines
5.6 KiB
C

#include "all.h"
enum Imm {
Iother,
Iplo12,
Iphi12,
Iplo24,
Inlo12,
Inhi12,
Inlo24
};
static enum Imm
imm(Con *c, int k, int64_t *pn)
{
int64_t n;
int i;
if (c->type != CBits)
return Iother;
n = c->bits.i;
if (k == Kw)
n = (int32_t)n;
i = Iplo12;
if (n < 0) {
i = Inlo12;
n = -(uint64_t)n;
}
*pn = n;
if ((n & 0x000fff) == n)
return i;
if ((n & 0xfff000) == n)
return i + 1;
if ((n & 0xffffff) == n)
return i + 2;
return Iother;
}
int
arm64_logimm(uint64_t x, int k)
{
uint64_t n;
if (k == Kw)
x = (x & 0xffffffff) | x << 32;
if (x & 1)
x = ~x;
if (x == 0)
return 0;
if (x == 0xaaaaaaaaaaaaaaaa)
return 1;
n = x & 0xf;
if (0x1111111111111111 * n == x)
goto Check;
n = x & 0xff;
if (0x0101010101010101 * n == x)
goto Check;
n = x & 0xffff;
if (0x0001000100010001 * n == x)
goto Check;
n = x & 0xffffffff;
if (0x0000000100000001 * n == x)
goto Check;
n = x;
Check:
return (n & (n + (n & -n))) == 0;
}
static void
fixarg(Ref *pr, int k, int phi, Fn *fn)
{
char buf[32];
Con *c, cc;
Ref r0, r1, r2, r3;
int s, n;
r0 = *pr;
switch (rtype(r0)) {
case RCon:
c = &fn->con[r0.val];
if (c->type == CAddr && ((c->sym.type & SExt)
|| (T.apple && (c->sym.type & SThr)))) {
r1 = newtmp("isel", Kl, fn);
*pr = r1;
if (c->bits.i) {
r2 = newtmp("isel", Kl, fn);
cc = (Con){.type = CBits};
cc.bits.i = c->bits.i;
r3 = newcon(&cc, fn);
emit(Oadd, Kl, r1, r2, r3);
r1 = r2;
}
if (T.apple && (c->sym.type & SThr)) {
emit(Ocopy, Kl, r1, TMP(R0), R);
r1 = newtmp("isel", Kl, fn);
r2 = newtmp("isel", Kl, fn);
emit(Ocall, 0, R, r1, CALL(33));
emit(Ocopy, Kl, TMP(R0), r2, R);
emit(Oload, Kl, r1, r2, R);
r1 = r2;
}
cc = *c;
cc.bits.i = 0;
r3 = newcon(&cc, fn);
emit(Ocopy, Kl, r1, r3, R);
break;
}
if (KBASE(k) == 0 && phi)
return;
r1 = newtmp("isel", k, fn);
if (KBASE(k) == 0) {
emit(Ocopy, k, r1, r0, R);
} else {
n = stashbits(c->bits.i, KWIDE(k) ? 8 : 4);
vgrow(&fn->con, ++fn->ncon);
c = &fn->con[fn->ncon-1];
sprintf(buf, "\"%sfp%d\"", T.asloc, n);
*c = (Con){.type = CAddr};
c->sym.id = intern(buf);
r2 = newtmp("isel", Kl, fn);
emit(Oload, k, r1, r2, R);
emit(Ocopy, Kl, r2, CON(c-fn->con), R);
}
*pr = r1;
break;
case RTmp:
s = fn->tmp[r0.val].slot;
if (s == -1)
break;
r1 = newtmp("isel", Kl, fn);
emit(Oaddr, Kl, r1, SLOT(s), R);
*pr = r1;
break;
}
}
static int
selcmp(Ref arg[2], int k, Fn *fn)
{
Ref r, *iarg;
Con *c;
int swap, cmp, fix;
int64_t n;
if (KBASE(k) == 1) {
emit(Oafcmp, k, R, arg[0], arg[1]);
iarg = curi->arg;
fixarg(&iarg[0], k, 0, fn);
fixarg(&iarg[1], k, 0, fn);
return 0;
}
swap = rtype(arg[0]) == RCon;
if (swap) {
r = arg[1];
arg[1] = arg[0];
arg[0] = r;
}
fix = 1;
cmp = Oacmp;
r = arg[1];
if (rtype(r) == RCon) {
c = &fn->con[r.val];
switch (imm(c, k, &n)) {
default:
break;
case Iplo12:
case Iphi12:
fix = 0;
break;
case Inlo12:
case Inhi12:
cmp = Oacmn;
r = getcon(n, fn);
fix = 0;
break;
}
}
emit(cmp, k, R, arg[0], r);
iarg = curi->arg;
fixarg(&iarg[0], k, 0, fn);
if (fix)
fixarg(&iarg[1], k, 0, fn);
return swap;
}
static int
callable(Ref r, Fn *fn)
{
Con *c;
if (rtype(r) == RTmp)
return 1;
if (rtype(r) == RCon) {
c = &fn->con[r.val];
if (c->type == CAddr)
if (c->bits.i == 0)
return 1;
}
return 0;
}
static void
sel(Ins i, Fn *fn)
{
Ref *iarg;
Ins *i0;
int ck, cc;
if (INRANGE(i.op, Oalloc, Oalloc1)) {
i0 = curi - 1;
salloc(i.to, i.arg[0], fn);
fixarg(&i0->arg[0], Kl, 0, fn);
return;
}
if (iscmp(i.op, &ck, &cc)) {
emit(Oflag, i.cls, i.to, R, R);
i0 = curi;
if (selcmp(i.arg, ck, fn))
i0->op += cmpop(cc);
else
i0->op += cc;
return;
}
if (i.op == Ocall)
if (callable(i.arg[0], fn)) {
emiti(i);
return;
}
if (i.op != Onop) {
emiti(i);
iarg = curi->arg; /* fixarg() can change curi */
fixarg(&iarg[0], argcls(&i, 0), 0, fn);
fixarg(&iarg[1], argcls(&i, 1), 0, fn);
}
}
static void
seljmp(Blk *b, Fn *fn)
{
Ref r;
Ins *i, *ir;
int ck, cc, use;
if (b->jmp.type == Jret0
|| b->jmp.type == Jjmp
|| b->jmp.type == Jhlt)
return;
assert(b->jmp.type == Jjnz);
r = b->jmp.arg;
use = -1;
b->jmp.arg = R;
ir = 0;
i = &b->ins[b->nins];
while (i > b->ins)
if (req((--i)->to, r)) {
use = fn->tmp[r.val].nuse;
ir = i;
break;
}
if (ir && use == 1
&& iscmp(ir->op, &ck, &cc)) {
if (selcmp(ir->arg, ck, fn))
cc = cmpop(cc);
b->jmp.type = Jjf + cc;
*ir = (Ins){.op = Onop};
}
else {
selcmp((Ref[]){r, CON_Z}, Kw, fn);
b->jmp.type = Jjfine;
}
}
void
arm64_isel(Fn *fn)
{
Blk *b, **sb;
Ins *i;
Phi *p;
uint n, al;
int64_t sz;
/* assign slots to fast allocs */
b = fn->start;
/* specific to NAlign == 3 */ /* or change n=4 and sz /= 4 below */
for (al=Oalloc, n=4; al<=Oalloc1; al++, n*=2)
for (i=b->ins; i<&b->ins[b->nins]; i++)
if (i->op == al) {
if (rtype(i->arg[0]) != RCon)
break;
sz = fn->con[i->arg[0].val].bits.i;
if (sz < 0 || sz >= INT_MAX-15)
err("invalid alloc size %"PRId64, sz);
sz = (sz + n-1) & -n;
sz /= 4;
fn->tmp[i->to.val].slot = fn->slot;
fn->slot += sz;
*i = (Ins){.op = Onop};
}
for (b=fn->start; b; b=b->link) {
curi = &insb[NIns];
for (sb=(Blk*[3]){b->s1, b->s2, 0}; *sb; sb++)
for (p=(*sb)->phi; p; p=p->link) {
for (n=0; p->blk[n] != b; n++)
assert(n+1 < p->narg);
fixarg(&p->arg[n], p->cls, 1, fn);
}
seljmp(b, fn);
for (i=&b->ins[b->nins]; i!=b->ins;)
sel(*--i, fn);
idup(b, curi, &insb[NIns]-curi);
}
if (debug['I']) {
fprintf(stderr, "\n> After instruction selection:\n");
printfn(fn, stderr);
}
}