kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

obj_tls.c (10058B)


      1 /* Format-aware thread-local storage emission.
      2  *
      3  * `_Thread_local` storage has the same source-level shape across object
      4  * formats (a global with a per-thread instance), but the on-disk
      5  * representation diverges sharply:
      6  *
      7  *   ELF   : one symbol in `.tdata` / `.tbss`; access via direct
      8  *           TP-relative offset (TLSLE relocs).
      9  *   Mach-O: storage and access are split.  The bytes live under a
     10  *           private `<name>$tlv$init` symbol in `__DATA,__thread_data`
     11  *           / `__DATA,__thread_bss`.  The user-visible symbol points
     12  *           at a 24-byte *descriptor* in `__DATA,__thread_vars`:
     13  *             +0  : ptr to `_tlv_bootstrap` (BIND; dyld rewrites to a
     14  *                   per-descriptor thunk after allocating a pthread key)
     15  *             +8  : pthread key (0 on disk; filled by dyld)
     16  *             +16 : ptr to the data symbol (REBASE; link_macho rewrites
     17  *                   the literal to (target_vaddr - tls_image_vaddr))
     18  *           Access from compiled code is an indirect call through the
     19  *           descriptor's slot[0]; the TLVP_LOAD_PAGE21/PAGEOFF12 reloc
     20  *           pair targets the descriptor symbol, not the data.
     21  *
     22  * Centralizing the split here keeps the frontend (parse_init.c) and the
     23  * codegen (per-arch ops.c) format-agnostic — they pass the canonical
     24  * SK_TLS symbol plus byte buffer to `obj_define_tls`, and consult
     25  * `obj_format_tls_via_descriptor` when choosing an access sequence.
     26  *
     27  * The `_tlv_bootstrap` undef extern is cached on ObjBuilder so multiple
     28  * TLV vars in one TU share one entry; the linker dedupes across TUs by
     29  * name. */
     30 
     31 #include <string.h>
     32 
     33 #include "core/core.h"
     34 #include "core/heap.h"
     35 #include "core/pool.h"
     36 #include "core/slice.h"
     37 #include "obj/format.h"
     38 #include "obj/obj.h"
     39 
     40 /* ObjBuilder is opaque outside obj.c; obj_tls.c reaches the bootstrap
     41  * cache via these accessors defined in obj.c.  Declared here to avoid
     42  * exposing the field in obj.h's public ObjBuilder layout. */
     43 ObjSymId obj_tlv_bootstrap_get(const ObjBuilder*);
     44 void obj_tlv_bootstrap_set(ObjBuilder*, ObjSymId);
     45 
     46 /* TLS-access model for the active (format, OS): COFF -> Windows TEB,
     47  * Mach-O -> per-variable descriptor + thunk, everything else -> ELF
     48  * Local-Exec / Initial-Exec.  The single source of truth for the
     49  * TLS-access decision. */
     50 ObjTlsModel obj_format_tls_model(const Compiler* c) {
     51   if (!c) return OBJ_TLS_ELF_LE;
     52   if (c->target.obj == KIT_OBJ_COFF) return OBJ_TLS_WINDOWS_TEB;
     53   if (c->target.obj == KIT_OBJ_MACHO) return OBJ_TLS_MACHO_DESCRIPTOR;
     54   return OBJ_TLS_ELF_LE;
     55 }
     56 
     57 /* Thin wrapper kept for existing callers (per-arch ops.c, NativeDirect,
     58  * internal define_tls dispatch); later waves migrate them to
     59  * obj_format_tls_model directly. */
     60 int obj_format_tls_via_descriptor(const Compiler* c) {
     61   return obj_format_tls_model(c) == OBJ_TLS_MACHO_DESCRIPTOR;
     62 }
     63 
     64 /* In-process JIT: 1 when a reference to symbol `name` is dropped because the
     65  * access idiom that materializes it is relaxed to in-image addressing. The
     66  * COFF Windows TEB model loads a per-module `_tls_index`; the JIT rewrites that
     67  * load away, so its relocs must not be applied. None elsewhere. Sits beside
     68  * obj_format_tls_model as the TLS-mechanism authority, so src/link stays free
     69  * of the format-specific pseudo-symbol name. */
     70 int obj_format_jit_drops_symbol_ref(const Compiler* c, Sym name) {
     71   Slice nm;
     72   if (!c || name == 0) return 0;
     73   if (obj_format_tls_model(c) != OBJ_TLS_WINDOWS_TEB) return 0;
     74   nm = pool_slice(c->global, name);
     75   return nm.len == 10u && memcmp(nm.s, "_tls_index", 10u) == 0;
     76 }
     77 
     78 /* In-process JIT: 1 when an *undefined* reference to `name` needs no external
     79  * definition because the JIT image satisfies it internally. Two format
     80  * pseudo-symbols qualify:
     81  *   - Mach-O `__tlv_bootstrap`: every TLV descriptor's slot[0] is rewritten to
     82  *     the JIT's own thunk, so the bootstrap value is never read.
     83  *   - COFF `_tls_index`: the JIT relaxes every TLS access to in-image
     84  *     addressing (its idiom relocs are dropped — see
     85  *     obj_format_jit_drops_symbol_ref), so the module index is never read.
     86  * The single arbiter funneling every JIT undef-accept site, so src/link names
     87  * no pseudo-symbol. Distinct from obj_format_jit_drops_symbol_ref, which gates
     88  * *reloc dropping* and is COFF-only: `__tlv_bootstrap` relocs are rewritten,
     89  * not dropped, so it must not appear there. */
     90 int obj_format_jit_undef_internal(const Compiler* c, Sym name) {
     91   Slice nm;
     92   if (!c || name == 0) return 0;
     93   nm = pool_slice(c->global, name);
     94   if (nm.len == 15u && memcmp(nm.s, "__tlv_bootstrap", 15u) == 0) return 1;
     95   if (nm.len == 10u && memcmp(nm.s, "_tls_index", 10u) == 0) return 1;
     96   return 0;
     97 }
     98 
     99 /* Emit the per-thread storage for a `_Thread_local` and bind `store_sym`
    100  * onto it.  Initialized data lands in `.tdata` / `__DATA,__thread_data`
    101  * (with the pointer-init relocs replayed); zero-init lands in `.tbss` /
    102  * `__DATA,__thread_bss`.  Identical across ELF and Mach-O — the only format
    103  * difference is *which* symbol receives the storage definition (ELF binds the
    104  * user-visible symbol directly; Mach-O binds the private `<name>$tlv$init`
    105  * data symbol, leaving the user symbol for the descriptor).  Section spelling
    106  * is already format-aware via obj_secname_t{data,bss}; macho_emit's
    107  * section_flags_for maps SF_TLS + sectname to the right S_THREAD_LOCAL_* type
    108  * on that side. */
    109 static void tls_emit_storage(ObjBuilder* ob, Compiler* c, ObjSymId store_sym,
    110                              const u8* data, u32 size, int has_nonzero_init,
    111                              u32 align, const ObjTlsReloc* relocs,
    112                              u32 nrelocs) {
    113   u32 a = align ? align : 1u;
    114   if (!data || !has_nonzero_init) {
    115     Sym sname = obj_secname_tbss(c);
    116     ObjSecId sec =
    117         obj_section_ex(ob, sname, SEC_BSS, SSEM_NOBITS,
    118                        SF_ALLOC | SF_WRITE | SF_TLS, a, 0, OBJ_SEC_NONE, 0);
    119     u32 base = obj_align_to(ob, sec, a);
    120     obj_reserve_bss(ob, sec, base + size, a);
    121     obj_symbol_define(ob, store_sym, sec, base, size);
    122     return;
    123   }
    124   Sym sname = obj_secname_tdata(c);
    125   ObjSecId sec =
    126       obj_section(ob, sname, SEC_DATA, SF_ALLOC | SF_WRITE | SF_TLS, a);
    127   u32 base = obj_align_to(ob, sec, a);
    128   {
    129     u8* dst = obj_reserve(ob, sec, size);
    130     if (dst) memcpy(dst, data, size);
    131   }
    132   obj_symbol_define(ob, store_sym, sec, base, size);
    133   for (u32 i = 0; i < nrelocs; ++i) {
    134     obj_reloc(ob, sec, base + relocs[i].offset, relocs[i].kind,
    135               relocs[i].target, relocs[i].addend);
    136   }
    137 }
    138 
    139 static void define_tls_elf(ObjBuilder* ob, Compiler* c, ObjSymId sym,
    140                            const u8* data, u32 size, int has_nonzero_init,
    141                            u32 align, const ObjTlsReloc* relocs, u32 nrelocs) {
    142   tls_emit_storage(ob, c, sym, data, size, has_nonzero_init, align, relocs,
    143                    nrelocs);
    144 }
    145 
    146 static ObjSymId tlv_bootstrap(ObjBuilder* ob, Compiler* c) {
    147   ObjSymId s = obj_tlv_bootstrap_get(ob);
    148   if (s != OBJ_SYM_NONE) return s;
    149   /* On-disk name carries the Mach-O leading underscore: source-level
    150    * `_tlv_bootstrap` becomes `__tlv_bootstrap`. */
    151   Sym name = pool_intern_slice(c->global, SLICE_LIT("__tlv_bootstrap"));
    152   s = obj_symbol(ob, name, SB_GLOBAL, SK_UNDEF, OBJ_SEC_NONE, 0, 0);
    153   obj_tlv_bootstrap_set(ob, s);
    154   return s;
    155 }
    156 
    157 static ObjSymId mint_init_sym(ObjBuilder* ob, Compiler* c, Sym desc_name) {
    158   Slice nm_s = pool_slice(c->global, desc_name);
    159   const char* nm = nm_s.s;
    160   size_t nlen = nm_s.len;
    161   static const char suffix[] = "$tlv$init";
    162   size_t slen = sizeof(suffix) - 1u;
    163   Heap* h = (Heap*)c->ctx->heap;
    164   char* buf = (char*)h->alloc(h, nlen + slen + 1u, 1);
    165   if (!buf)
    166     compiler_panic(c, (SrcLoc){0, 0, 0},
    167                    "obj_define_tls: oom interning init name");
    168   if (nlen) memcpy(buf, nm, nlen);
    169   memcpy(buf + nlen, suffix, slen);
    170   buf[nlen + slen] = 0;
    171   Sym n = pool_intern_slice(c->global,
    172                             (Slice){.s = buf, .len = (u32)(nlen + slen)});
    173   h->free(h, buf, nlen + slen + 1u);
    174   return obj_symbol(ob, n, SB_LOCAL, SK_TLS, OBJ_SEC_NONE, 0, 0);
    175 }
    176 
    177 static void define_tls_macho(ObjBuilder* ob, Compiler* c, ObjSymId sym,
    178                              const u8* data, u32 size, int has_nonzero_init,
    179                              u32 align, const ObjTlsReloc* relocs,
    180                              u32 nrelocs) {
    181   const ObjSym* desc_os = obj_symbol_get(ob, sym);
    182   if (!desc_os)
    183     compiler_panic(c, (SrcLoc){0, 0, 0},
    184                    "obj_define_tls: descriptor sym not found");
    185   ObjSymId data_sym = mint_init_sym(ob, c, desc_os->name);
    186 
    187   /* Storage under the private `<name>$tlv$init` data symbol — same emission
    188    * as ELF (shared with define_tls_elf via tls_emit_storage). */
    189   tls_emit_storage(ob, c, data_sym, data, size, has_nonzero_init, align, relocs,
    190                    nrelocs);
    191 
    192   /* Descriptor in __DATA,__thread_vars: 24 bytes aligned 8.
    193    * The user-visible `sym` lives here; the TLVP relocs in code target
    194    * this symbol so the linker can route them through __thread_ptrs. */
    195   Sym vars_name =
    196       pool_intern_slice(c->global, SLICE_LIT("__DATA,__thread_vars"));
    197   ObjSecId vars_sec =
    198       obj_section(ob, vars_name, SEC_DATA, SF_ALLOC | SF_WRITE | SF_TLS, 8u);
    199   u32 desc_base = obj_align_to(ob, vars_sec, 8u);
    200   {
    201     u8* dst = obj_reserve(ob, vars_sec, 24u);
    202     if (dst) memset(dst, 0, 24u);
    203   }
    204   obj_symbol_define(ob, sym, vars_sec, desc_base, 24u);
    205   obj_reloc(ob, vars_sec, desc_base + 0u, R_ABS64, tlv_bootstrap(ob, c), 0);
    206   obj_reloc(ob, vars_sec, desc_base + 16u, R_ABS64, data_sym, 0);
    207 }
    208 
    209 void obj_define_tls(Compiler* c, ObjBuilder* ob, ObjSymId sym, const u8* data,
    210                     u32 size, int has_nonzero_init, u32 align,
    211                     const ObjTlsReloc* relocs, u32 nrelocs) {
    212   if (obj_format_tls_via_descriptor(c)) {
    213     define_tls_macho(ob, c, sym, data, size, has_nonzero_init, align, relocs,
    214                      nrelocs);
    215     return;
    216   }
    217   define_tls_elf(ob, c, sym, data, size, has_nonzero_init, align, relocs,
    218                  nrelocs);
    219 }