kit

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

commit 9fd6b74d8e1db3d5afe450867e52e4ea6544456d
parent cfb1975a7bd0c912109521795062f394e28aba18
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri, 29 May 2026 15:33:22 -0700

aa64 asm: parse :lo12:/:got:/:got_lo12: relocation-operator operands

Adds GNU-as relocation-modifier syntax to the aarch64 standalone assembler:
adrp accepts :got: (R_AARCH64_ADR_GOT_PAGE) vs the default page reloc; add
accepts :lo12: (R_AARCH64_ADD_ABS_LO12_NC); loads/stores accept :lo12:
(size-keyed R_AARCH64_LDST{8,16,32,64}_ABS_LO12_NC) and :got_lo12:
(R_AARCH64_LD64_GOT_LO12_NC). A leading ':' is unambiguous at an operand
position. Corpus aa64_reloc_modifiers is byte- and reloc-identical to llvm-mc
(verified via llvm-objdump -r on the cfree-emitted object).

Diffstat:
Msrc/arch/aa64/asm.c | 113++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Atest/asm/encode/aa64_reloc_modifiers.expected.hex | 1+
Atest/asm/encode/aa64_reloc_modifiers.s | 10++++++++++
Atest/asm/encode/aa64_reloc_modifiers.targets | 2++
4 files changed, 123 insertions(+), 3 deletions(-)

diff --git a/src/arch/aa64/asm.c b/src/arch/aa64/asm.c @@ -269,6 +269,55 @@ static void parse_imm_sym(AsmDriver* d, ObjSymId* sym_out, i64* val_out) { asm_driver_parse_sym_expr(d, sym_out, val_out); } +/* GNU-as relocation modifier on an aarch64 operand (`:lo12:`, `:got:`, + * `:got_lo12:`). AA64_RELMOD_NONE means no modifier was present. */ +typedef enum AA64RelMod { + AA64_RELMOD_NONE = 0, + AA64_RELMOD_LO12, + AA64_RELMOD_GOT, + AA64_RELMOD_GOT_LO12, +} AA64RelMod; + +/* If the next token is ':', consume a `:name:` relocation modifier prefix and + * return its kind. A leading ':' is unambiguous at an operand position (a + * label's ':' only appears at end-of-mnemonic). Returns AA64_RELMOD_NONE and + * leaves the stream untouched when there is no modifier. */ +static AA64RelMod parse_reloc_mod(AsmDriver* d) { + if (!tok_punct(asm_driver_peek(d), ':')) return AA64_RELMOD_NONE; + (void)asm_driver_next(d); /* eat ':' */ + AsmTok name = asm_driver_next(d); + if (name.kind != ASM_TOK_IDENT) + asm_driver_panic(d, "asm: expected relocation modifier name after ':'"); + Slice s = pool_slice(asm_driver_pool(d), name.v.ident); + AA64RelMod mod; + if (icase_eq(s.s, s.len, "lo12")) + mod = AA64_RELMOD_LO12; + else if (icase_eq(s.s, s.len, "got")) + mod = AA64_RELMOD_GOT; + else if (icase_eq(s.s, s.len, "got_lo12")) + mod = AA64_RELMOD_GOT_LO12; + else + asm_driver_panic(d, "asm: unsupported relocation modifier"); + asm_driver_expect_punct(d, ':', "':' closing relocation modifier"); + return mod; +} + +/* The R_AARCH64_LDST{8,16,32,64}_ABS_LO12_NC reloc for an access log2-size. */ +static RelocKind aa64_ldst_lo12_reloc(AsmDriver* d, u32 size) { + switch (size) { + case 0: + return R_AARCH64_LDST8_ABS_LO12_NC; + case 1: + return R_AARCH64_LDST16_ABS_LO12_NC; + case 2: + return R_AARCH64_LDST32_ABS_LO12_NC; + case 3: + return R_AARCH64_LDST64_ABS_LO12_NC; + default: + asm_driver_panic(d, "asm: ldr/str: :lo12: not valid for this access size"); + } +} + static void emit32(AsmDriver* d, u32 word) { MCEmitter* mc = asm_driver_mc(d); (void)asm_driver_cur_section(d); @@ -585,6 +634,30 @@ static void p_addsub(AsmDriver* d, int is_sub, int set_flags) { AA64Reg rn = parse_reg(d); expect_comma(d, "add/sub"); AsmTok t = asm_driver_peek(d); + if (!is_sub && !set_flags && tok_punct(t, ':')) { + /* `add Rd, Rn, :lo12:sym` — ADD (immediate) with a zero imm12 plus an + * R_AARCH64_ADD_ABS_LO12_NC relocation (the low-12 PIC/abs sequence). */ + AA64RelMod mod = parse_reloc_mod(d); + if (mod != AA64_RELMOD_LO12) + asm_driver_panic(d, "asm: add: only :lo12: is valid here"); + if (rd.is64 != rn.is64) + asm_driver_panic(d, "asm: add :lo12:: width mismatch"); + ObjSymId sym = OBJ_SYM_NONE; + i64 off = 0; + parse_imm_sym(d, &sym, &off); + u32 word = aa64_addsubimm_pack((AA64AddSubImm){.sf = rd.is64, + .op = 0, + .S = 0, + .sh = 0, + .imm12 = 0, + .Rn = rn.num, + .Rd = rd.num}); + emit32(d, word); + MCEmitter* mc = asm_driver_mc(d); + mc->emit_reloc_at(mc, asm_driver_cur_section(d), mc->pos(mc) - 4, + R_AARCH64_ADD_ABS_LO12_NC, sym, off, 1, 0); + return; + } if (tok_punct(t, '#') || t.kind == ASM_TOK_NUM || tok_punct(t, '-') || tok_punct(t, '+')) { /* immediate form */ @@ -932,6 +1005,9 @@ typedef struct AA64Mem { i64 imm; /* byte offset (literal as written) */ u32 option; u32 shift; + AA64RelMod reloc_mod; /* :lo12: / :got_lo12: on the offset, or NONE */ + ObjSymId reloc_sym; /* symbol when reloc_mod != NONE */ + i64 reloc_off; /* addend when reloc_mod != NONE */ u8 pre_index; u8 post_index; u8 has_offset; @@ -992,11 +1068,17 @@ static AA64Mem parse_mem(AsmDriver* d) { asm_driver_panic(d, "asm: ldr/str: base register must be 64-bit"); require_sp_spelling(d, m.base, "ldr/str base"); if (asm_driver_eat_comma(d)) { - /* Either `#imm`/expression (immediate offset) or a register index. */ + /* `:lo12:sym` / `:got_lo12:sym` relocation offset, a register index, or a + * plain `#imm`/expression. */ AsmTok t = asm_driver_peek(d); AA64Reg idx; memset(&idx, 0, sizeof idx); - if (t.kind == ASM_TOK_IDENT && parse_reg_from_ident(d, t.v.ident, &idx)) { + if (tok_punct(t, ':')) { + m.reloc_mod = parse_reloc_mod(d); + parse_imm_sym(d, &m.reloc_sym, &m.reloc_off); + m.has_offset = 1; /* imm field stays 0; reloc supplies the low bits */ + } else if (t.kind == ASM_TOK_IDENT && + parse_reg_from_ident(d, t.v.ident, &idx)) { (void)asm_driver_next(d); reject_sp_reg(d, idx, "ldr/str index"); m.index = idx; @@ -1041,6 +1123,24 @@ static void p_ldst_core(AsmDriver* d, int is_load, int fixed_size, : !sign_ext ? AA64_LDST_OPC_LDR : rt.is64 ? 2u /* LDRS*, 64-bit dst */ : 3u; /* LDRS*, 32-bit dst */ + if (m.reloc_mod != AA64_RELMOD_NONE) { + /* [Xn, :lo12:sym] / [Xn, :got_lo12:sym] — unsigned-imm12 form with a zero + * immediate; the relocation supplies the low 12 bits. */ + u32 word = aa64_ldst_uimm_pack((AA64LdStUimm){.size = size, + .V = 0, + .opc = opc, + .imm12 = 0, + .Rn = m.base.num, + .Rt = rt.num}); + emit32(d, word); + RelocKind k = (m.reloc_mod == AA64_RELMOD_GOT_LO12) + ? R_AARCH64_LD64_GOT_LO12_NC + : aa64_ldst_lo12_reloc(d, size); + MCEmitter* mc = asm_driver_mc(d); + mc->emit_reloc_at(mc, asm_driver_cur_section(d), mc->pos(mc) - 4, k, + m.reloc_sym, m.reloc_off, 1, 0); + return; + } if (m.has_index) { /* Register-offset form. The S bit (scale by access size) is set * when an explicit shift was written; the amount must equal the @@ -1178,6 +1278,11 @@ static void p_ldp_stp(AsmDriver* d, int is_load) { static void p_adr(AsmDriver* d, int is_adrp) { AA64Reg rd = parse_reg(d); expect_comma(d, "adr"); + /* `adrp Rd, :got:sym` selects the GOT-page relocation; a bare symbol uses + * the PC-relative page reloc. `:got:` is only meaningful for adrp. */ + AA64RelMod mod = parse_reloc_mod(d); + if (mod != AA64_RELMOD_NONE && (!is_adrp || mod != AA64_RELMOD_GOT)) + asm_driver_panic(d, "asm: adr/adrp: only :got: (with adrp) is valid here"); ObjSymId sym = OBJ_SYM_NONE; i64 off = 0; parse_imm_sym(d, &sym, &off); @@ -1190,7 +1295,9 @@ static void p_adr(AsmDriver* d, int is_adrp) { emit32(d, aa64_pcrel_adr_pack(f)); MCEmitter* mc = asm_driver_mc(d); u32 ofs = mc->pos(mc) - 4; - RelocKind k = is_adrp ? R_AARCH64_ADR_PREL_PG_HI21 : R_AARCH64_ADR_PREL_LO21; + RelocKind k = !is_adrp ? R_AARCH64_ADR_PREL_LO21 + : mod == AA64_RELMOD_GOT ? R_AARCH64_ADR_GOT_PAGE + : R_AARCH64_ADR_PREL_PG_HI21; mc->emit_reloc_at(mc, asm_driver_cur_section(d), ofs, k, sym, off, 1, 0); } diff --git a/test/asm/encode/aa64_reloc_modifiers.expected.hex b/test/asm/encode/aa64_reloc_modifiers.expected.hex @@ -0,0 +1 @@ +0000009000000091010040f902004039010000f903000090630040f9c0035fd6 diff --git a/test/asm/encode/aa64_reloc_modifiers.s b/test/asm/encode/aa64_reloc_modifiers.s @@ -0,0 +1,10 @@ +.text +t: + adrp x0, sym + add x0, x0, :lo12:sym + ldr x1, [x0, :lo12:sym] + ldrb w2, [x0, :lo12:sym] + str x1, [x0, :lo12:sym] + adrp x3, :got:sym + ldr x3, [x3, :got_lo12:sym] + ret diff --git a/test/asm/encode/aa64_reloc_modifiers.targets b/test/asm/encode/aa64_reloc_modifiers.targets @@ -0,0 +1 @@ +aa64 +\ No newline at end of file