kit

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

reloc.c (18671B)


      1 /* AArch64 relocation descriptors (width + classification).
      2  *
      3  * One row per relocation kind this backend applies.  Reached through
      4  * LinkArchDesc.reloc_desc (wired in link.c) and the arch-aware reloc_desc()
      5  * dispatcher.  The wire encoding + diagnostic name live in
      6  * src/obj/<fmt>/reloc_aarch64.c; the instruction byte encoders live in the
      7  * shared byte-patcher (src/obj/reloc_apply.c) until WS-C moves them here.
      8  *
      9  * Kinds with no row (the dynamic-only GLOB_DAT/JUMP_SLOT/RELATIVE/COPY, the
     10  * MCEmitter-only INTRA_LABEL_ADDR, and the unused TLSLE LDST variants) are
     11  * never applied through the static reloc record path and intentionally carry
     12  * no descriptor.  R_ABS16 and R_PREL16 are now neutral and live in the
     13  * neutral_rows table; R_TPOFF64 is neutral and also lives there. */
     14 
     15 #include "obj/reloc.h"
     16 
     17 #include "core/bytes.h"
     18 #include "link/link_arch.h"
     19 
     20 static const RelocDescRow aa64_rows[] = {
     21     {R_AARCH64_JUMP26, {4, RELOC_IS_BRANCH}},
     22     {R_AARCH64_CALL26, {4, RELOC_IS_BRANCH}},
     23     {R_AARCH64_CONDBR19, {4, 0}},
     24     {R_AARCH64_TSTBR14, {4, 0}},
     25     {R_AARCH64_LD_PREL_LO19, {4, 0}},
     26     {R_AARCH64_ADR_PREL_LO21, {4, 0}},
     27     {R_AARCH64_ADR_PREL_PG_HI21, {4, RELOC_DIRECT_PAGE}},
     28     {R_AARCH64_ADR_PREL_PG_HI21_NC, {4, RELOC_DIRECT_PAGE}},
     29     {R_AARCH64_ADD_ABS_LO12_NC, {4, RELOC_DIRECT_PAGE}},
     30     {R_AARCH64_LDST8_ABS_LO12_NC, {4, RELOC_DIRECT_PAGE}},
     31     {R_AARCH64_LDST16_ABS_LO12_NC, {4, RELOC_DIRECT_PAGE}},
     32     {R_AARCH64_LDST32_ABS_LO12_NC, {4, RELOC_DIRECT_PAGE}},
     33     {R_AARCH64_LDST64_ABS_LO12_NC, {4, RELOC_DIRECT_PAGE}},
     34     {R_AARCH64_LDST128_ABS_LO12_NC, {4, RELOC_DIRECT_PAGE}},
     35     {R_AARCH64_ADR_GOT_PAGE, {4, RELOC_USES_GOT}},
     36     {R_AARCH64_LD64_GOT_LO12_NC, {4, RELOC_USES_GOT}},
     37     {R_AARCH64_POINTER_TO_GOT, {4, RELOC_USES_GOT}},
     38     {R_AARCH64_TLSDESC_ADR_PAGE21, {4, 0}},
     39     {R_AARCH64_TLSDESC_LD64_LO12, {4, 0}},
     40     {R_AARCH64_TLSDESC_ADD_LO12, {4, 0}},
     41     {R_AARCH64_TLSDESC_CALL, {4, 0}},
     42     {R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21, {4, RELOC_IS_TLS_GOT}},
     43     {R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC, {4, RELOC_IS_TLS_GOT}},
     44     {R_AARCH64_TLSLE_ADD_TPREL_HI12, {4, RELOC_IS_TLS_LE}},
     45     {R_AARCH64_TLSLE_ADD_TPREL_LO12_NC, {4, RELOC_IS_TLS_LE}},
     46     {R_AARCH64_TLVP_LOAD_PAGE21, {4, RELOC_IS_TLVP}},
     47     {R_AARCH64_TLVP_LOAD_PAGEOFF12, {4, RELOC_IS_TLVP}},
     48     /* COFF AArch64 TLS SECREL imm12 pair: ADD-imm12 instruction relocs,
     49      * AArch64-only, applied only into PE/COFF output. TLS-only, so the JIT
     50      * classifies them as Local-Exec accesses via RELOC_IS_TLS_LE. */
     51     {R_COFF_AARCH64_SECREL_LOW12A, {4, RELOC_IS_TLS_LE}},
     52     {R_COFF_AARCH64_SECREL_HIGH12A, {4, RELOC_IS_TLS_LE}},
     53     {R_COFF_AARCH64_SECREL_LOW12L, {4, RELOC_IS_TLS_LE}},
     54 };
     55 
     56 const RelocDesc* aa64_reloc_desc(RelocKind k) {
     57   return reloc_desc_row_find(aa64_rows,
     58                              (u32)(sizeof aa64_rows / sizeof aa64_rows[0]), k);
     59 }
     60 
     61 static u32 aa64_movz64(u32 rd, u16 imm, u32 hw) {
     62   return 0xd2800000u | ((hw & 3u) << 21) | ((u32)imm << 5) | (rd & 31u);
     63 }
     64 
     65 static u32 aa64_movk64(u32 rd, u16 imm, u32 hw) {
     66   return 0xf2800000u | ((hw & 3u) << 21) | ((u32)imm << 5) | (rd & 31u);
     67 }
     68 
     69 /* AArch64 instruction-immediate byte encoders (WS-C).  Moved verbatim from the
     70  * format-neutral byte-patcher; reached via LinkArchDesc.reloc_apply_insn for
     71  * the instruction-embedded kinds.  Encoding references: ARM ARMv8-A "ELF for
     72  * the ARM 64-bit Architecture (AArch64)" §5.7.  Returns 1 if it owns `k`. */
     73 int aa64_reloc_apply_insn(Compiler* c, RelocKind k, u8* P_bytes, u64 S, i64 A,
     74                           u64 P) {
     75   switch (k) {
     76     case R_AARCH64_TLSDESC_ADR_PAGE21:
     77     case R_AARCH64_TLSDESC_LD64_LO12:
     78     case R_AARCH64_TLSDESC_ADD_LO12:
     79     case R_AARCH64_TLSDESC_CALL: {
     80       /* Static ELF local relaxation: replace the standard TLSDESC
     81        * ADRP/LDR/ADD/BLR sequence with a 4-insn materialization of the
     82        * local-exec TP-relative offset in x0, matching the TLSDESC resolver's
     83        * return register. Dynamic TLSDESC descriptors are rejected earlier by
     84        * the ELF linker unless the target is defined TLS in this image. */
     85       u64 v = (u64)((i64)S + A);
     86       u16 imm = 0;
     87       if (k == R_AARCH64_TLSDESC_ADR_PAGE21) {
     88         imm = (u16)(v & 0xffffu);
     89         wr_u32_le(P_bytes, aa64_movz64(0, imm, 0));
     90       } else if (k == R_AARCH64_TLSDESC_LD64_LO12) {
     91         imm = (u16)((v >> 16) & 0xffffu);
     92         wr_u32_le(P_bytes, aa64_movk64(0, imm, 1));
     93       } else if (k == R_AARCH64_TLSDESC_ADD_LO12) {
     94         imm = (u16)((v >> 32) & 0xffffu);
     95         wr_u32_le(P_bytes, aa64_movk64(0, imm, 2));
     96       } else {
     97         imm = (u16)((v >> 48) & 0xffffu);
     98         wr_u32_le(P_bytes, aa64_movk64(0, imm, 3));
     99       }
    100       (void)P;
    101       return 1;
    102     }
    103     case R_AARCH64_POINTER_TO_GOT: {
    104       i64 disp = (i64)S + A - (i64)P;
    105       if (disp < -(i64)(1ll << 31) || disp > (i64)0x7fffffff)
    106         compiler_panic(c, SRCLOC_NONE,
    107                        "link: POINTER_TO_GOT out of range (need +/-2GiB)");
    108       wr_u32_le(P_bytes, (u32)(u64)disp);
    109       return 1;
    110     }
    111     case R_AARCH64_CONDBR19:
    112     case R_AARCH64_LD_PREL_LO19: {
    113       /* B.cond / CB(N)Z / LDR (literal) — imm19 in 4-byte units,
    114        * signed, at bits [23:5]. Range: ±1MiB. */
    115       i64 disp = (i64)S + A - (i64)P;
    116       u32 instr;
    117       u32 imm19;
    118       if (disp & 3)
    119         compiler_panic(c, SRCLOC_NONE,
    120                        "link: imm19 reloc misaligned displacement");
    121       if (disp < -(i64)(1 << 20) || disp >= (i64)(1 << 20))
    122         compiler_panic(c, SRCLOC_NONE,
    123                        "link: imm19 reloc out of range (need ±1MiB)");
    124       imm19 = (u32)((disp >> 2) & 0x7ffffu);
    125       instr = rd_u32_le(P_bytes);
    126       instr = (instr & ~(0x7ffffu << 5)) | (imm19 << 5);
    127       wr_u32_le(P_bytes, instr);
    128       return 1;
    129     }
    130     case R_AARCH64_TSTBR14: {
    131       /* TBZ/TBNZ — imm14 in 4-byte units, signed, at bits [18:5].
    132        * Range: ±32KiB. */
    133       i64 disp = (i64)S + A - (i64)P;
    134       u32 instr;
    135       u32 imm14;
    136       if (disp & 3)
    137         compiler_panic(c, SRCLOC_NONE, "link: TSTBR14 misaligned displacement");
    138       if (disp < -(i64)(1 << 15) || disp >= (i64)(1 << 15))
    139         compiler_panic(c, SRCLOC_NONE,
    140                        "link: TSTBR14 out of range (need ±32KiB)");
    141       imm14 = (u32)((disp >> 2) & 0x3fffu);
    142       instr = rd_u32_le(P_bytes);
    143       instr = (instr & ~(0x3fffu << 5)) | (imm14 << 5);
    144       wr_u32_le(P_bytes, instr);
    145       return 1;
    146     }
    147     case R_AARCH64_ADR_PREL_LO21: {
    148       /* ADR — byte-granularity imm21, encoded as immlo[30:29] +
    149        * immhi[23:5]. No 12-bit shift (unlike ADRP). Range: ±1MiB. */
    150       i64 disp = (i64)S + A - (i64)P;
    151       u32 instr;
    152       u32 immlo, immhi;
    153       if (disp < -(i64)(1 << 20) || disp >= (i64)(1 << 20))
    154         compiler_panic(c, SRCLOC_NONE,
    155                        "link: ADR_PREL_LO21 out of range (need ±1MiB)");
    156       immlo = (u32)(disp & 0x3u);
    157       immhi = (u32)((disp >> 2) & 0x7ffffu);
    158       instr = rd_u32_le(P_bytes);
    159       instr = (instr & 0x9f00001fu) | (immlo << 29) | (immhi << 5);
    160       wr_u32_le(P_bytes, instr);
    161       return 1;
    162     }
    163     case R_AARCH64_JUMP26:
    164     case R_AARCH64_CALL26: {
    165       /* B/BL imm26 — branch displacement in 4-byte units, signed.
    166        * Clear bits [25:0] of the existing instruction and OR in the
    167        * new imm26. Range check: ±128MiB. */
    168       i64 disp = (i64)S + A - (i64)P;
    169       u32 instr;
    170       u32 imm26;
    171       if (disp & 3)
    172         compiler_panic(c, SRCLOC_NONE, "link: CALL26 misaligned displacement");
    173       if (disp < -(i64)(1 << 27) || disp >= (i64)(1 << 27))
    174         compiler_panic(c, SRCLOC_NONE,
    175                        "link: CALL26 out of range (need ±128MiB)");
    176       imm26 = (u32)((disp >> 2) & 0x3ffffffu);
    177       instr = rd_u32_le(P_bytes);
    178       instr = (instr & 0xfc000000u) | imm26;
    179       wr_u32_le(P_bytes, instr);
    180       return 1;
    181     }
    182     case R_AARCH64_TLVP_LOAD_PAGE21:
    183     case R_AARCH64_ADR_GOT_PAGE:
    184     case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21:
    185     case R_AARCH64_ADR_PREL_PG_HI21:
    186     case R_AARCH64_ADR_PREL_PG_HI21_NC: {
    187       /* ADRP — page-relative imm21, encoded as immlo[30:29] +
    188        * immhi[23:5]. Effective immediate is (S+A) page minus P page,
    189        * shifted right by 12, sign-extended to 33 bits. _NC variant
    190        * skips the range check (compiler asserts it can't overflow,
    191        * e.g. when paired with explicit page bracketing). */
    192       i64 page_s = ((i64)S + A) & ~(i64)0xfff;
    193       i64 page_p = (i64)P & ~(i64)0xfff;
    194       i64 disp = page_s - page_p;
    195       i64 imm21 = disp >> 12;
    196       u32 instr;
    197       u32 immlo, immhi;
    198       if (k != R_AARCH64_ADR_PREL_PG_HI21_NC &&
    199           (imm21 < -(i64)(1 << 20) || imm21 >= (i64)(1 << 20)))
    200         compiler_panic(c, SRCLOC_NONE,
    201                        "link: ADR_PREL_PG_HI21 out of range (need ±4GiB)");
    202       immlo = (u32)(imm21 & 0x3u);
    203       immhi = (u32)((imm21 >> 2) & 0x7ffffu);
    204       instr = rd_u32_le(P_bytes);
    205       instr = (instr & 0x9f00001fu) | (immlo << 29) | (immhi << 5);
    206       wr_u32_le(P_bytes, instr);
    207       return 1;
    208     }
    209     case R_AARCH64_ADD_ABS_LO12_NC: {
    210       /* ADD (immediate) imm12 at bits [21:10]. NC = no overflow check. */
    211       u64 v = ((u64)S + (u64)A) & 0xfffu;
    212       u32 instr = rd_u32_le(P_bytes);
    213       instr = (instr & ~(0xfffu << 10)) | ((u32)v << 10);
    214       wr_u32_le(P_bytes, instr);
    215       return 1;
    216     }
    217     case R_AARCH64_TLSLE_ADD_TPREL_HI12:
    218     case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC: {
    219       /* AArch64 TLS local-exec.  Caller passes S already as the
    220        * TP-relative offset (target's image offset minus the TLS
    221        * image base, plus the 16-byte AArch64 TCB).  HI12 takes
    222        * bits 23:12, LO12_NC takes bits 11:0; both encoded as
    223        * imm12 at instruction bits [21:10] of an ADD (immediate).
    224        * The HI12 form's instruction carries LSL #12 in its opcode,
    225        * so bits 11:0 of the operand naturally land at scale 4096. */
    226       u64 v = (u64)((i64)S + A);
    227       u32 imm12 = (k == R_AARCH64_TLSLE_ADD_TPREL_HI12)
    228                       ? (u32)((v >> 12) & 0xfffu)
    229                       : (u32)(v & 0xfffu);
    230       u32 instr = rd_u32_le(P_bytes);
    231       instr = (instr & ~(0xfffu << 10)) | (imm12 << 10);
    232       wr_u32_le(P_bytes, instr);
    233       return 1;
    234     }
    235     case R_AARCH64_LDST8_ABS_LO12_NC:
    236     case R_AARCH64_LDST16_ABS_LO12_NC:
    237     case R_AARCH64_LDST32_ABS_LO12_NC:
    238     case R_AARCH64_LDST64_ABS_LO12_NC:
    239     case R_AARCH64_LDST128_ABS_LO12_NC:
    240     case R_AARCH64_LD64_GOT_LO12_NC:
    241     case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
    242     case R_AARCH64_TLVP_LOAD_PAGEOFF12: {
    243       /* LDR/STR with imm12 at bits [21:10]; the imm is scaled by the
    244        * access size, so we right-shift the low 12 bits of (S+A) by
    245        * the size scale before encoding. NC = no overflow check.
    246        *
    247        * LD64_GOT_LO12_NC has the same encoding as LDST64_ABS_LO12_NC;
    248        * the linker has already redirected `S` to the GOT slot. */
    249       u32 shift = (k == R_AARCH64_LDST8_ABS_LO12_NC)    ? 0u
    250                   : (k == R_AARCH64_LDST16_ABS_LO12_NC) ? 1u
    251                   : (k == R_AARCH64_LDST32_ABS_LO12_NC) ? 2u
    252                   : (k == R_AARCH64_LDST64_ABS_LO12_NC ||
    253                      k == R_AARCH64_LD64_GOT_LO12_NC ||
    254                      k == R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC ||
    255                      k == R_AARCH64_TLVP_LOAD_PAGEOFF12)
    256                       ? 3u
    257                       : 4u;
    258       u64 lo12 = ((u64)S + (u64)A) & 0xfffu;
    259       u64 imm12 = lo12 >> shift;
    260       u32 instr = rd_u32_le(P_bytes);
    261       if (lo12 & ((1u << shift) - 1u))
    262         compiler_panic(c, SRCLOC_NONE,
    263                        "link: LDST%u_ABS_LO12_NC misaligned address "
    264                        "(kind=%u S=0x%llx A=%lld P=0x%llx)",
    265                        1u << (3 + shift), (unsigned)k, (unsigned long long)S,
    266                        (long long)A, (unsigned long long)P);
    267       instr = (instr & ~(0xfffu << 10)) | ((u32)(imm12 & 0xfffu) << 10);
    268       wr_u32_le(P_bytes, instr);
    269       return 1;
    270     }
    271     default:
    272       return 0;
    273   }
    274 }
    275 
    276 /* In-process JIT TLS Local-Exec relaxation (LinkArchDesc.jit_tls_le_relax).
    277  *
    278  * ELF, per access:
    279  *   mrs rd, tpidr_el0           (no reloc)
    280  *   add rd, rd, #hi12           R_AARCH64_TLSLE_ADD_TPREL_HI12   <- `site`
    281  *   add rd, rd, #lo12           R_AARCH64_TLSLE_ADD_TPREL_LO12_NC
    282  *
    283  * Windows/COFF, per access — the 7-instruction TEB idiom (see
    284  * aa_tls_addr_of_win):
    285  *   site-20  ldr  rd, [x18, #0x58]            TEB.ThreadLocalStoragePointer
    286  *   site-16  adrp x16, _tls_index             }
    287  *   site-12  add  x16, x16, :lo12:_tls_index  } &_tls_index (relocs dropped)
    288  *   site-8   ldr  w16, [x16]                  module TLS index
    289  *   site-4   ldr  rd, [rd, x16, lsl #3]       this module's TLS block base
    290  *   site     add  rd, rd, :secrel_hi12:sym    R_COFF_AARCH64_SECREL_HIGH12A <-
    291  *   site+4   add  rd, rd, :secrel_lo12:sym    R_COFF_AARCH64_SECREL_LOW12A
    292  *
    293  * Single-threaded JIT: in both cases address the in-image storage directly,
    294  * dropping the thread-pointer read (and, on Windows, the `_tls_index` / TEB
    295  * indirection):  adrp rd, &var ; add rd, rd, :lo12:&var ; nop(s). */
    296 void aa64_jit_tls_le_relax(Compiler* c, RelocKind k, u8* site, u64 storage,
    297                            u64 site_pc) {
    298   u8* mrs;
    299   u8* add_lo;
    300   u32 rd;
    301   /* Windows COFF idiom: the terminal HIGH12A drives the whole rewrite; the
    302    * LOW12A half is then a no-op (mirrors the ELF HI12/LO12 split). */
    303   if (k == R_COFF_AARCH64_SECREL_LOW12A) return; /* handled with HIGH12A */
    304   if (k == R_COFF_AARCH64_SECREL_HIGH12A) {
    305     u8* p;
    306     rd = rd_u32_le(site) & 0x1fu;
    307     /* nop the TEB read, the _tls_index materialize + load, and the block load
    308      * (site-20 .. site-8); reuse the block-load slot at site-4 for the ADRP. */
    309     for (p = site - 20; p <= site - 8; p += 4) wr_u32_le(p, 0xd503201fu);
    310     wr_u32_le(site - 4, 0x90000000u | rd); /* adrp rd, #0 */
    311     aa64_reloc_apply_insn(c, R_AARCH64_ADR_PREL_PG_HI21, site - 4, storage, 0,
    312                           site_pc - 4u);
    313     wr_u32_le(site, 0x91000000u | (rd << 5) | rd); /* add rd, rd, #0 */
    314     aa64_reloc_apply_insn(c, R_AARCH64_ADD_ABS_LO12_NC, site, storage, 0,
    315                           site_pc);
    316     wr_u32_le(site + 4, 0xd503201fu); /* nop the secrel_lo12 add */
    317     return;
    318   }
    319   if (k == R_AARCH64_TLSLE_ADD_TPREL_LO12_NC) return; /* handled with HI12 */
    320   if (k != R_AARCH64_TLSLE_ADD_TPREL_HI12)
    321     compiler_panic(c, SRCLOC_NONE, "aa64 jit tls: unexpected reloc kind %u",
    322                    (unsigned)k);
    323   mrs = site - 4;    /* mrs rd, tpidr_el0 */
    324   add_lo = site + 4; /* add rd, rd, #lo12 -> nop */
    325   rd = rd_u32_le(site) & 0x1fu;
    326   if (rd_u32_le(mrs) != (0xd53bd040u | rd))
    327     compiler_panic(c, SRCLOC_NONE, "aa64 jit tls: unexpected access sequence");
    328   wr_u32_le(mrs, 0x90000000u | rd); /* adrp rd, #0 */
    329   aa64_reloc_apply_insn(c, R_AARCH64_ADR_PREL_PG_HI21, mrs, storage, 0,
    330                         site_pc - 4u);
    331   wr_u32_le(site, 0x91000000u | (rd << 5) | rd); /* add rd, rd, #0 */
    332   aa64_reloc_apply_insn(c, R_AARCH64_ADD_ABS_LO12_NC, site, storage, 0,
    333                         site_pc);
    334   wr_u32_le(add_lo, 0xd503201fu); /* nop */
    335 }
    336 
    337 /* In-process JIT relaxation of AArch64 indirection idioms (LinkArchDesc
    338  * .jit_reloc_relax): the single-threaded JIT has no dynamic loader, GOT, or
    339  * TLV resolver, so each access idiom is rewritten to address the in-image
    340  * instance directly.  Returns 1 if it owned `k`, 0 to fall through to the
    341  * ordinary reloc apply. */
    342 int aa64_jit_reloc_relax(Compiler* c, RelocKind k, const JitRelaxCtx* ctx) {
    343   u8* P_bytes = ctx->site;
    344 
    345   /* Weak undefined target: address-of must evaluate to NULL.  An ADRP + ADD
    346    * pair would form a PC-relative address to vaddr 0 that exceeds ±4 GiB once
    347    * the JIT places segments far from 0 (tripping link_reloc's range check).
    348    * Rewrite the ADRP to MOVZ rd,#0 so rd becomes 0 directly; the paired ADD's
    349    * assembled imm12 of 0 already gives rd += 0, so leave it as add rd,rd,#0. */
    350   if (ctx->weak_undef_zero) {
    351     if (k == R_AARCH64_ADR_PREL_PG_HI21 || k == R_AARCH64_ADR_PREL_PG_HI21_NC) {
    352       u32 rd = rd_u32_le(P_bytes) & 0x1fu;
    353       wr_u32_le(P_bytes, 0xd2800000u | rd); /* movz rd, #0 */
    354       return 1;
    355     }
    356     if (k == R_AARCH64_ADD_ABS_LO12_NC) return 1; /* leave add rd,rd,#0 */
    357   }
    358 
    359   /* Mach-O TLV access -> ordinary in-image load.  Codegen emits the 4-insn
    360    * Apple TLV sequence:
    361    *   adrp x0, desc@TLVPPAGE          (PAGE21)
    362    *   ldr  x0, [x0, desc@TLVPPAGEOFF] (PAGEOFF12)   <- this reloc
    363    *   ldr  xN, [x0]                   -- load the resolver thunk from desc[0]
    364    *   blr  xN                         -- call thunk(desc) -> &var in x0
    365    * With one thread the in-image .tdata/.tbss IS the single instance, and
    366    * desc[+16] already holds the variable's in-image storage address (filled by
    367    * the normal R_ABS64 against the storage symbol).  Collapse to a direct load,
    368    * dropping the thunk and the per-thread block:
    369    *   PAGEOFF12 : ldr x0,[x0,#imm] -> add x0,x0,#(desc & 0xfff)   (x0 = &desc)
    370    *   +4        : ldr xN,[x0]       -> ldr x0,[x0,#16]            (x0 = &var)
    371    *   +8        : blr xN            -> nop
    372    * The thunk register N is scratch (the Apple TLV ABI fixes only x0:
    373    * descriptor in, &var out); kit's codegen uses x16, clang picks any free
    374    * register (e.g. x8).  Accept any N so long as the pair is `ldr xN,[x0]`
    375    * (Rn=x0, imm12=0) followed by `blr xN`. */
    376   if (k == R_AARCH64_TLVP_LOAD_PAGEOFF12) {
    377     u64 v = ((u64)ctx->S + (u64)ctx->addend) & 0xfffu;
    378     u32 instr = rd_u32_le(P_bytes);
    379     u8* i_thunk = P_bytes + 4u;
    380     u8* i_call = P_bytes + 8u;
    381     u32 thunk = rd_u32_le(i_thunk);
    382     u32 call = rd_u32_le(i_call);
    383     u32 n = thunk & 0x1fu;
    384     wr_u32_le(P_bytes, 0x91000000u | (instr & 0x3ffu) | ((u32)v << 10));
    385     if ((thunk & ~0x1fu) != 0xf9400000u || call != (0xd63f0000u | (n << 5u)))
    386       compiler_panic(c, SRCLOC_NONE,
    387                      "kit_jit: unexpected Mach-O TLV access sequence");
    388     wr_u32_le(i_thunk, 0xf9400800u); /* ldr x0, [x0, #16] -> &var */
    389     wr_u32_le(i_call, 0xd503201fu);  /* nop */
    390     return 1;
    391   }
    392 
    393   /* No real GOT in the append image: the GOT load becomes a direct add, so the
    394    * register holds the symbol address itself instead of loading it from a slot.
    395    * LD64_GOT_LO12_NC shares the LDR uimm12 encoding; rewrite to ADD imm12. */
    396   if (ctx->got_relaxed && k == R_AARCH64_LD64_GOT_LO12_NC) {
    397     u64 v = ((u64)ctx->S + (u64)ctx->addend) & 0xfffu;
    398     u32 instr = rd_u32_le(P_bytes);
    399     u32 rd = instr & 0x1fu;
    400     u32 rn = (instr >> 5) & 0x1fu;
    401     wr_u32_le(P_bytes, 0x91000000u | rd | (rn << 5) | ((u32)v << 10));
    402     return 1;
    403   }
    404 
    405   return 0;
    406 }