Compare commits

...

11 Commits

Author SHA1 Message Date
Quentin Carbonneaux
ac79f8fd02 mcf comments 2026-05-01 19:44:44 +02:00
Quentin Carbonneaux
84516c7484 relax assert in emitcon 2026-05-01 19:40:10 +02:00
Quentin Carbonneaux
f36c06c3ee rework docs 2026-04-29 11:22:19 +02:00
Quentin Carbonneaux
8c16de02ed rv64: got-free addresses for SGlo 2026-04-29 10:54:26 +02:00
Quentin Carbonneaux
63bb04f759 more fixes 2026-04-29 10:15:11 +02:00
Quentin Carbonneaux
6abea36b9b nits 2026-04-28 16:48:43 +02:00
Quentin Carbonneaux
b8b95a99df asserts in emitcon() 2026-04-28 16:23:47 +02:00
Quentin Carbonneaux
a9796317aa apple tls is global 2026-04-28 16:11:47 +02:00
Quentin Carbonneaux
5ac78c67c9 nits 2026-04-28 16:01:48 +02:00
Quentin Carbonneaux
fee64f4341 mcf review 2026-04-28 14:16:34 +02:00
Quentin Carbonneaux
7ab2fbe07c extern DYNCONST 2026-04-27 18:42:35 +02:00
9 changed files with 133 additions and 47 deletions

6
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;
};

View File

@ -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:

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

@ -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);

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 {