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 }