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
This commit is contained in:
Quentin Carbonneaux 2026-04-27 16:32:28 +02:00
parent 577b4667db
commit ac48f83f17
9 changed files with 133 additions and 47 deletions

6
all.h
View File

@ -293,8 +293,10 @@ struct Use {
struct Sym { struct Sym {
enum { enum {
SGlo, SGlo = 0, /* direct access */
SThr, SThr = 1, /* local-exec TLS */
SExt = 2, /* GOT/PLT access */
SExtThr = SExt|SThr, /* initial-exec TLS */
} type; } type;
uint32_t id; uint32_t id;
}; };

View File

@ -196,12 +196,12 @@ emitcon(Con *con, E *e)
l = str(con->sym.id); l = str(con->sym.id);
p = l[0] == '"' ? "" : T.assym; p = l[0] == '"' ? "" : T.assym;
if (con->sym.type == SThr) { if (con->sym.type == SThr) {
if (T.apple) assert(!T.apple);
fprintf(e->f, "%s%s@TLVP", p, l);
else
fprintf(e->f, "%%fs:%s%s@tpoff", p, l); fprintf(e->f, "%%fs:%s%s@tpoff", p, l);
} else } else {
assert((con->sym.type & ~SExt) == SGlo);
fprintf(e->f, "%s%s", p, l); fprintf(e->f, "%s%s", p, l);
}
if (con->bits.i) if (con->bits.i)
fprintf(e->f, "%+"PRId64, con->bits.i); fprintf(e->f, "%+"PRId64, con->bits.i);
break; break;
@ -380,7 +380,7 @@ Next:
off = e->fn->con[ref.val]; off = e->fn->con[ref.val];
emitcon(&off, e); emitcon(&off, e);
if (off.type == CAddr) if (off.type == CAddr)
if (off.sym.type != SThr || T.apple) if (off.sym.type != SThr)
fprintf(e->f, "(%%rip)"); fprintf(e->f, "(%%rip)");
break; break;
case RTmp: case RTmp:
@ -532,14 +532,22 @@ emitins(Ins i, E *e)
emitf("mov%k %0, %=", &i, e); emitf("mov%k %0, %=", &i, e);
break; break;
case Oaddr: case Oaddr:
if (!T.apple if (rtype(i.arg[0]) != RCon)
&& rtype(i.arg[0]) == RCon goto Table;
&& e->fn->con[i.arg[0].val].sym.type == SThr) { 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 /* derive the symbol address from the TCB
* address at offset 0 of %fs */ * 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); emitf("movq %%fs:0, %L=", &i, e);
fprintf(e->f, "\tleaq %s%s@tpoff", fprintf(e->f, "\tleaq %s%s@tpoff",
sym[0] == '"' ? "" : T.assym, sym); sym[0] == '"' ? "" : T.assym, sym);
@ -550,15 +558,40 @@ emitins(Ins i, E *e)
regtoa(i.to.val, SLong), regtoa(i.to.val, SLong),
regtoa(i.to.val, SLong)); regtoa(i.to.val, SLong));
break; 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: case Ocall:
/* calls simply have a weird syntax in AT&T /* calls simply have a weird syntax in AT&T
* assembly... */ * assembly... */
switch (rtype(i.arg[0])) { switch (rtype(i.arg[0])) {
case RCon: case RCon:
con = &e->fn->con[i.arg[0].val];
fprintf(e->f, "\tcallq "); 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"); fprintf(e->f, "\n");
break; break;
case RTmp: case RTmp:

View File

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

View File

@ -278,6 +278,10 @@ loadaddr(Con *c, char *rn, E *e)
s = "\tadrp\tR, SO\n" s = "\tadrp\tR, SO\n"
"\tadd\tR, R, #:lo12:SO\n"; "\tadd\tR, R, #:lo12:SO\n";
break; break;
case SExtThr:
if (!T.apple)
die("extern thread unavailable on arm64");
/* fall through */
case SThr: case SThr:
if (T.apple) if (T.apple)
s = "\tadrp\tR, S@tlvppage\n" 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_hi12:SO, lsl #12\n"
"\tadd\tR, R, #:tprel_lo12_nc:SO\n"; "\tadd\tR, R, #:tprel_lo12_nc:SO\n";
break; 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); l = str(c->sym.id);
@ -468,7 +480,7 @@ emitins(Ins *i, E *e)
goto Table; goto Table;
c = &e->fn->con[i->arg[0].val]; c = &e->fn->con[i->arg[0].val];
if (c->type != CAddr if (c->type != CAddr
|| c->sym.type != SGlo || (c->sym.type & SThr)
|| c->bits.i) || c->bits.i)
die("invalid call argument"); die("invalid call argument");
l = str(c->sym.id); 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]; c = &fn->con[r0.val];
if (T.apple if (T.apple
&& c->type == CAddr && c->type == CAddr
&& c->sym.type == SThr) { && (c->sym.type & SThr)) {
r1 = newtmp("isel", Kl, fn); r1 = newtmp("isel", Kl, fn);
*pr = r1; *pr = r1;
if (c->bits.i) { if (c->bits.i) {

View File

@ -181,6 +181,8 @@ by zero-extension, or by sign-extension.
DYNCONST := DYNCONST :=
CONST 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 := VAL :=
DYNCONST DYNCONST
@ -225,6 +227,15 @@ When the `thread` keyword prefixes a symbol name, the
symbol's numeric value is resolved at runtime in the symbol's numeric value is resolved at runtime in the
thread-local storage. 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 Vals are used as arguments in regular, phi, and jump
instructions within function definitions. They are instructions within function definitions. They are
either constants or function-scope temporaries. either constants or function-scope temporaries.

26
parse.c
View File

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

View File

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

View File

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