jit_tls_posix.c (3881B)
1 /* pthread_key-backed KitJitTls. Backs `kit run` on Mach-O targets: 2 * every JIT image with TLS gets one pthread_key, the per-thread block is 3 * allocated lazily on first access, and freed via the key's destructor 4 * when the thread exits. 5 * 6 * The ctx layout is fixed by the contract in src/jit/tlv_thunk.h: the 7 * first 8 bytes MUST be a function pointer the asm thunk calls with 8 * x0 = ctx and expects back an x0 = TLS block. We satisfy this by making 9 * `get_block` the first field. */ 10 11 #include <pthread.h> 12 #include <stdio.h> 13 #include <stdlib.h> 14 #include <string.h> 15 16 #include "env_posix.h" 17 18 typedef struct JitTlsCtx { 19 void* (*get_block)(void* ctx); /* first; matches tlv_thunk's expectation */ 20 pthread_key_t key; 21 size_t image_size; 22 size_t image_filesz; 23 size_t align; 24 void* init_bytes; /* heap-owned copy of init bytes, or NULL if all BSS */ 25 } JitTlsCtx; 26 27 static void jit_tls_thread_dtor(void* block) { 28 /* POSIX pthread_key destructor: called when a thread that touched the 29 * TLV exits. `block` is the void* set by pthread_setspecific, never 30 * NULL (POSIX skips the destructor if it was NULL). */ 31 free(block); 32 } 33 34 static void* jit_tls_alloc_block(JitTlsCtx* ctx) { 35 /* macOS aligned_alloc requires alignment >= sizeof(void*); bump 36 * smaller request alignments up. Size must be a multiple of 37 * alignment too. */ 38 size_t a = ctx->align ? ctx->align : sizeof(void*); 39 size_t sz; 40 void* block; 41 if (a < sizeof(void*)) a = sizeof(void*); 42 sz = (ctx->image_size + a - 1u) & ~(a - 1u); 43 if (sz == 0) sz = a; /* zero-size TLS image still needs a non-NULL block */ 44 block = aligned_alloc(a, sz); 45 if (!block) return NULL; 46 if (ctx->image_filesz && ctx->init_bytes) 47 memcpy(block, ctx->init_bytes, ctx->image_filesz); 48 if (ctx->image_size > ctx->image_filesz) 49 memset((char*)block + ctx->image_filesz, 0, 50 ctx->image_size - ctx->image_filesz); 51 return block; 52 } 53 54 static void* jit_tls_get_block(void* ctx_v) { 55 JitTlsCtx* ctx = (JitTlsCtx*)ctx_v; 56 void* block = pthread_getspecific(ctx->key); 57 if (block) return block; 58 block = jit_tls_alloc_block(ctx); 59 if (!block) { 60 fprintf(stderr, "kit run: out of memory allocating per-thread TLS block\n"); 61 abort(); 62 } 63 if (pthread_setspecific(ctx->key, block) != 0) { 64 fprintf(stderr, "kit run: pthread_setspecific failed in TLV thunk\n"); 65 abort(); 66 } 67 return block; 68 } 69 70 static void* jit_tls_ctx_new(void* user, const void* init_bytes, 71 size_t image_filesz, size_t image_size, 72 size_t align) { 73 JitTlsCtx* ctx; 74 (void)user; 75 ctx = (JitTlsCtx*)malloc(sizeof(*ctx)); 76 if (!ctx) return NULL; 77 ctx->get_block = jit_tls_get_block; 78 ctx->image_size = image_size; 79 ctx->image_filesz = image_filesz; 80 ctx->align = align ? align : sizeof(void*); 81 ctx->init_bytes = NULL; 82 if (image_filesz && init_bytes) { 83 ctx->init_bytes = malloc(image_filesz); 84 if (!ctx->init_bytes) { 85 free(ctx); 86 return NULL; 87 } 88 memcpy(ctx->init_bytes, init_bytes, image_filesz); 89 } 90 if (pthread_key_create(&ctx->key, jit_tls_thread_dtor) != 0) { 91 free(ctx->init_bytes); 92 free(ctx); 93 return NULL; 94 } 95 return ctx; 96 } 97 98 static void jit_tls_ctx_destroy(void* user, void* ctx_v) { 99 JitTlsCtx* ctx = (JitTlsCtx*)ctx_v; 100 void* my_block; 101 (void)user; 102 if (!ctx) return; 103 /* Free the calling thread's block (POSIX won't run our destructor for 104 * it; pthread_key_delete also doesn't fire destructors for live 105 * threads). Other threads' blocks are reaped when those threads exit. */ 106 my_block = pthread_getspecific(ctx->key); 107 if (my_block) { 108 pthread_setspecific(ctx->key, NULL); 109 free(my_block); 110 } 111 pthread_key_delete(ctx->key); 112 free(ctx->init_bytes); 113 free(ctx); 114 } 115 116 KitJitTls g_jit_tls_posix = { 117 .ctx_new = jit_tls_ctx_new, 118 .ctx_destroy = jit_tls_ctx_destroy, 119 .user = NULL, 120 };