obj_tls.c (8699B)
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 static void define_tls_elf(ObjBuilder* ob, Compiler* c, ObjSymId sym, 79 const u8* data, u32 size, int has_nonzero_init, 80 u32 align, const ObjTlsReloc* relocs, u32 nrelocs) { 81 u32 a = align ? align : 1u; 82 if (!data || !has_nonzero_init) { 83 Sym sname = obj_secname_tbss(c); 84 ObjSecId sec = 85 obj_section_ex(ob, sname, SEC_BSS, SSEM_NOBITS, 86 SF_ALLOC | SF_WRITE | SF_TLS, a, 0, OBJ_SEC_NONE, 0); 87 u32 base = obj_align_to(ob, sec, a); 88 obj_reserve_bss(ob, sec, base + size, a); 89 obj_symbol_define(ob, sym, sec, base, size); 90 return; 91 } 92 Sym sname = obj_secname_tdata(c); 93 ObjSecId sec = 94 obj_section(ob, sname, SEC_DATA, SF_ALLOC | SF_WRITE | SF_TLS, a); 95 u32 base = obj_align_to(ob, sec, a); 96 { 97 u8* dst = obj_reserve(ob, sec, size); 98 if (dst) memcpy(dst, data, size); 99 } 100 obj_symbol_define(ob, sym, sec, base, size); 101 for (u32 i = 0; i < nrelocs; ++i) { 102 obj_reloc(ob, sec, base + relocs[i].offset, relocs[i].kind, 103 relocs[i].target, relocs[i].addend); 104 } 105 } 106 107 static ObjSymId tlv_bootstrap(ObjBuilder* ob, Compiler* c) { 108 ObjSymId s = obj_tlv_bootstrap_get(ob); 109 if (s != OBJ_SYM_NONE) return s; 110 /* On-disk name carries the Mach-O leading underscore: source-level 111 * `_tlv_bootstrap` becomes `__tlv_bootstrap`. */ 112 Sym name = pool_intern_slice(c->global, SLICE_LIT("__tlv_bootstrap")); 113 s = obj_symbol(ob, name, SB_GLOBAL, SK_UNDEF, OBJ_SEC_NONE, 0, 0); 114 obj_tlv_bootstrap_set(ob, s); 115 return s; 116 } 117 118 static ObjSymId mint_init_sym(ObjBuilder* ob, Compiler* c, Sym desc_name) { 119 Slice nm_s = pool_slice(c->global, desc_name); 120 const char* nm = nm_s.s; 121 size_t nlen = nm_s.len; 122 static const char suffix[] = "$tlv$init"; 123 size_t slen = sizeof(suffix) - 1u; 124 Heap* h = (Heap*)c->ctx->heap; 125 char* buf = (char*)h->alloc(h, nlen + slen + 1u, 1); 126 if (!buf) 127 compiler_panic(c, (SrcLoc){0, 0, 0}, 128 "obj_define_tls: oom interning init name"); 129 if (nlen) memcpy(buf, nm, nlen); 130 memcpy(buf + nlen, suffix, slen); 131 buf[nlen + slen] = 0; 132 Sym n = pool_intern_slice(c->global, 133 (Slice){.s = buf, .len = (u32)(nlen + slen)}); 134 h->free(h, buf, nlen + slen + 1u); 135 return obj_symbol(ob, n, SB_LOCAL, SK_TLS, OBJ_SEC_NONE, 0, 0); 136 } 137 138 static void define_tls_macho(ObjBuilder* ob, Compiler* c, ObjSymId sym, 139 const u8* data, u32 size, int has_nonzero_init, 140 u32 align, const ObjTlsReloc* relocs, 141 u32 nrelocs) { 142 const ObjSym* desc_os = obj_symbol_get(ob, sym); 143 if (!desc_os) 144 compiler_panic(c, (SrcLoc){0, 0, 0}, 145 "obj_define_tls: descriptor sym not found"); 146 ObjSymId data_sym = mint_init_sym(ob, c, desc_os->name); 147 148 /* Storage section: __thread_data (initialized) or __thread_bss (BSS). 149 * Same SF_TLS flag as ELF — macho_emit's section_flags_for maps SF_TLS 150 * + sectname to the right S_THREAD_LOCAL_* type. */ 151 u32 a = align ? align : 1u; 152 if (!data || !has_nonzero_init) { 153 Sym sname = obj_secname_tbss(c); 154 ObjSecId sec = 155 obj_section_ex(ob, sname, SEC_BSS, SSEM_NOBITS, 156 SF_ALLOC | SF_WRITE | SF_TLS, a, 0, OBJ_SEC_NONE, 0); 157 u32 base = obj_align_to(ob, sec, a); 158 obj_reserve_bss(ob, sec, base + size, a); 159 obj_symbol_define(ob, data_sym, sec, base, size); 160 } else { 161 Sym sname = obj_secname_tdata(c); 162 ObjSecId sec = 163 obj_section(ob, sname, SEC_DATA, SF_ALLOC | SF_WRITE | SF_TLS, a); 164 u32 base = obj_align_to(ob, sec, a); 165 { 166 u8* dst = obj_reserve(ob, sec, size); 167 if (dst) memcpy(dst, data, size); 168 } 169 obj_symbol_define(ob, data_sym, sec, base, size); 170 for (u32 i = 0; i < nrelocs; ++i) { 171 obj_reloc(ob, sec, base + relocs[i].offset, relocs[i].kind, 172 relocs[i].target, relocs[i].addend); 173 } 174 } 175 176 /* Descriptor in __DATA,__thread_vars: 24 bytes aligned 8. 177 * The user-visible `sym` lives here; the TLVP relocs in code target 178 * this symbol so the linker can route them through __thread_ptrs. */ 179 Sym vars_name = 180 pool_intern_slice(c->global, SLICE_LIT("__DATA,__thread_vars")); 181 ObjSecId vars_sec = 182 obj_section(ob, vars_name, SEC_DATA, SF_ALLOC | SF_WRITE | SF_TLS, 8u); 183 u32 desc_base = obj_align_to(ob, vars_sec, 8u); 184 { 185 u8* dst = obj_reserve(ob, vars_sec, 24u); 186 if (dst) memset(dst, 0, 24u); 187 } 188 obj_symbol_define(ob, sym, vars_sec, desc_base, 24u); 189 obj_reloc(ob, vars_sec, desc_base + 0u, R_ABS64, tlv_bootstrap(ob, c), 0); 190 obj_reloc(ob, vars_sec, desc_base + 16u, R_ABS64, data_sym, 0); 191 } 192 193 void obj_define_tls(Compiler* c, ObjBuilder* ob, ObjSymId sym, const u8* data, 194 u32 size, int has_nonzero_init, u32 align, 195 const ObjTlsReloc* relocs, u32 nrelocs) { 196 if (obj_format_tls_via_descriptor(c)) { 197 define_tls_macho(ob, c, sym, data, size, has_nonzero_init, align, relocs, 198 nrelocs); 199 return; 200 } 201 define_tls_elf(ob, c, sym, data, size, has_nonzero_init, align, relocs, 202 nrelocs); 203 }