From ac48f83f17c673e53ed08d3bfb4adab0a4d32fea Mon Sep 17 00:00:00 2001 From: Quentin Carbonneaux Date: Mon, 27 Apr 2026 16:32:28 +0200 Subject: [PATCH] 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 --- all.h | 6 ++++-- amd64/emit.c | 61 ++++++++++++++++++++++++++++++++++++++++------------ amd64/isel.c | 26 +++++++++++----------- arm64/emit.c | 14 +++++++++++- arm64/isel.c | 2 +- doc/il.txt | 13 ++++++++++- parse.c | 28 ++++++++++++++++++------ rv64/emit.c | 28 +++++++++++++++++------- tools/lexh.c | 2 +- 9 files changed, 133 insertions(+), 47 deletions(-) diff --git a/all.h b/all.h index eb79fc0..560f5bd 100644 --- a/all.h +++ b/all.h @@ -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; }; diff --git a/amd64/emit.c b/amd64/emit.c index 99e4d1f..3398f7a 100644 --- a/amd64/emit.c +++ b/amd64/emit.c @@ -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: diff --git a/amd64/isel.c b/amd64/isel.c index 889c647..379244a 100644 --- a/amd64/isel.c +++ b/amd64/isel.c @@ -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); diff --git a/arm64/emit.c b/arm64/emit.c index 108a928..64d85f7 100644 --- a/arm64/emit.c +++ b/arm64/emit.c @@ -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); diff --git a/arm64/isel.c b/arm64/isel.c index ff3b30b..7b78202 100644 --- a/arm64/isel.c +++ b/arm64/isel.c @@ -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) { diff --git a/doc/il.txt b/doc/il.txt index 746a7d2..6d1e979 100644 --- a/doc/il.txt +++ b/doc/il.txt @@ -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. diff --git a/parse.c b/parse.c index a063367..9ce04a3 100644 --- a/parse.c +++ b/parse.c @@ -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) diff --git a/rv64/emit.c b/rv64/emit.c index c1f29ac..f4b3236 100644 --- a/rv64/emit.c +++ b/rv64/emit.c @@ -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)); diff --git a/tools/lexh.c b/tools/lexh.c index efc30fe..82f3826 100644 --- a/tools/lexh.c +++ b/tools/lexh.c @@ -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 {