kit

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

commit aa49b4c0bc19392da1ef6159929122d5e7cdcb77
parent 8fa59182120ef726749e340bca33f554eee98588
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri, 29 May 2026 19:28:32 -0700

asm: round-trip .L local symbols + complete the unscaled ld/st family

Three gaps surfaced by round-tripping codegen output through cc -S | as:

1. .L local symbols. The assembler rejected .L-prefixed names (string
   literals, jump-table bases, static data all reference .Lcfree_* symbols).
   Lex a .L-prefixed token — leading and embedded dots included, e.g.
   .Lcfree_ro.0 / .L.str — as a single identifier; unambiguous since no
   directive starts with .L. The -S symbolizer now emits .L names instead of
   falling back to numeric operands (section symbols like .text still defer).

2. Unscaled load/store coverage. The assembler only knew ldur/stur (32/64-bit
   GPR); codegen emits sturb/ldurb/sturh/ldurh and the signed ldursb/ldursh/
   ldursw. Generalize p_ldur_stur with fixed_size/sign_ext, mirroring
   p_ldst_core, and add the mnemonics.

3. Signed unscaled load decode. The disassembler had no rows for the signed
   forms (opc=10/11) and keyed register width on size only; add the rows and
   key Wt/Xt on opc in print_ldst_simm9.

Regression: new encode case test/asm/encode/aa64_ldur_family. Round-trip
aa64 still 36/0; asm, ISA-unit, and inline-asm corpora green.

Diffstat:
Msrc/api/asm_emit.c | 16+++++++++++++---
Msrc/arch/aa64/asm.c | 46++++++++++++++++++++++++++++++++++------------
Msrc/arch/aa64/isa.c | 15++++++++++++++-
Msrc/asm/asm_lex.c | 29+++++++++++++++++++++++++++++
Atest/asm/encode/aa64_ldur_family.expected.hex | 1+
Atest/asm/encode/aa64_ldur_family.s | 14++++++++++++++
Atest/asm/encode/aa64_ldur_family.targets | 1+
7 files changed, 106 insertions(+), 16 deletions(-)

diff --git a/src/api/asm_emit.c b/src/api/asm_emit.c @@ -318,8 +318,18 @@ static const char* reloc_modifier(u16 kind, SurgKind* surg) { } } +/* A `.L`-prefixed name is an assembler-local label (e.g. `.Lcfree_ro.0`, + * `.Lcfree_jt.0`): the assembler's lexer accepts it as an identifier. Other + * `.`-prefixed names (section symbols like `.text`, `.rodata`) are not yet + * re-assemblable as operands, so the symbolizer keeps the numeric form. */ +static int sym_is_assemblable(Slice s) { + if (s.len == 0) return 0; + if (s.s[0] != '.') return 1; + return s.len >= 2 && s.s[1] == 'L'; +} + /* Build "<mod><sym>[+/-addend]" into buf. Returns length, or -1 if the symbol - * has no usable name (anonymous, or a `.`-prefixed section/local symbol the + * has no usable name (anonymous, or a `.`-prefixed section symbol the * assembler's expression parser does not accept). */ static int build_symref(char* buf, u32 cap, Compiler* c, const char* mod, Sym name, i64 addend) { @@ -327,7 +337,7 @@ static int build_symref(char* buf, u32 cap, Compiler* c, const char* mod, u32 p = 0, i; if (!name) return -1; s = pool_slice(c->global, name); - if (s.len == 0 || s.s[0] == '.') return -1; + if (!sym_is_assemblable(s)) return -1; for (i = 0; mod[i] && p + 1 < cap; ++i) buf[p++] = mod[i]; for (i = 0; i < s.len && p + 1 < cap; ++i) buf[p++] = s.s[i]; if (addend != 0) { @@ -493,7 +503,7 @@ static Sym symbol_at(const EmitCtx* x, u32 off) { for (i = 0; i < x->nlabels; ++i) { if (x->labels[i].offset == off && x->labels[i].name) { Slice s = pool_slice(x->c->global, x->labels[i].name); - if (s.len && s.s[0] != '.') return x->labels[i].name; + if (sym_is_assemblable(s)) return x->labels[i].name; } } return (Sym)0; diff --git a/src/arch/aa64/asm.c b/src/arch/aa64/asm.c @@ -1229,23 +1229,31 @@ static void p_ldrsb(AsmDriver* d) { p_ldst_core(d, 1, 0, 1); } static void p_ldrsh(AsmDriver* d) { p_ldst_core(d, 1, 1, 1); } static void p_ldrsw(AsmDriver* d) { p_ldst_core(d, 1, 2, 1); } -/* ldur/stur — unscaled signed-imm9. */ -static void p_ldur_stur(AsmDriver* d, int is_load) { +/* ldur/stur — unscaled signed-imm9. `fixed_size` is the access log2-size + * (0=byte..3=dword) for sturb/ldurb/sturh/ldurh/ldursw etc., or -1 to derive + * it from the register width (stur/ldur). `sign_ext` selects the signed-load + * opc (ldursb/ldursh/ldursw), keyed on the destination register width — the + * unscaled mirror of p_ldst_core. */ +static void p_ldur_stur(AsmDriver* d, int is_load, int fixed_size, + int sign_ext) { AA64Reg rt = parse_reg(d); reject_sp_reg(d, rt, "ldur/stur"); expect_comma(d, "ldur/stur"); AA64Mem m = parse_mem(d); - u32 size = rt.is64 ? 3u : 2u; + u32 size = (fixed_size >= 0) ? (u32)fixed_size : (rt.is64 ? 3u : 2u); + u32 opc = !is_load ? AA64_LDST_OPC_STR + : !sign_ext ? AA64_LDST_OPC_LDR + : rt.is64 ? 2u /* LDURS*, 64-bit dst */ + : 3u; /* LDURS*, 32-bit dst */ if (m.imm < -256 || m.imm > 255) asm_driver_panic(d, "asm: ldur/stur: imm9 out of range"); u32 imm9 = (u32)((u64)m.imm & 0x1ffu); - u32 word = aa64_ldst_simm9_pack( - (AA64LdStSimm9){.size = size, - .V = 0, - .opc = is_load ? AA64_LDST_OPC_LDR : AA64_LDST_OPC_STR, - .imm9 = imm9, - .Rn = m.base.num, - .Rt = rt.num}); + u32 word = aa64_ldst_simm9_pack((AA64LdStSimm9){.size = size, + .V = 0, + .opc = opc, + .imm9 = imm9, + .Rn = m.base.num, + .Rt = rt.num}); emit32(d, word); } @@ -1505,8 +1513,15 @@ static void p_brk_(AsmDriver* d) { p_except(d, 1); } static void p_hlt_(AsmDriver* d) { p_except(d, 2); } static void p_ldr_(AsmDriver* d) { p_ldr_str(d, 1); } static void p_str_(AsmDriver* d) { p_ldr_str(d, 0); } -static void p_ldur_(AsmDriver* d) { p_ldur_stur(d, 1); } -static void p_stur_(AsmDriver* d) { p_ldur_stur(d, 0); } +static void p_ldur_(AsmDriver* d) { p_ldur_stur(d, 1, -1, 0); } +static void p_stur_(AsmDriver* d) { p_ldur_stur(d, 0, -1, 0); } +static void p_ldurb(AsmDriver* d) { p_ldur_stur(d, 1, 0, 0); } +static void p_sturb(AsmDriver* d) { p_ldur_stur(d, 0, 0, 0); } +static void p_ldurh(AsmDriver* d) { p_ldur_stur(d, 1, 1, 0); } +static void p_sturh(AsmDriver* d) { p_ldur_stur(d, 0, 1, 0); } +static void p_ldursb(AsmDriver* d) { p_ldur_stur(d, 1, 0, 1); } +static void p_ldursh(AsmDriver* d) { p_ldur_stur(d, 1, 1, 1); } +static void p_ldursw(AsmDriver* d) { p_ldur_stur(d, 1, 2, 1); } static void p_ldp_(AsmDriver* d) { p_ldp_stp(d, 1); } static void p_stp_(AsmDriver* d) { p_ldp_stp(d, 0); } static void p_adr_(AsmDriver* d) { p_adr(d, 0); } @@ -1999,6 +2014,13 @@ static const AA64Mn kTable[] = { {"ldrsw", p_ldrsw, 0}, {"ldur", p_ldur_, 0}, {"stur", p_stur_, 0}, + {"ldurb", p_ldurb, 0}, + {"sturb", p_sturb, 0}, + {"ldurh", p_ldurh, 0}, + {"sturh", p_sturh, 0}, + {"ldursb", p_ldursb, 0}, + {"ldursh", p_ldursh, 0}, + {"ldursw", p_ldursw, 0}, {"ldp", p_ldp_, 0}, {"stp", p_stp_, 0}, {"adr", p_adr_, 0}, diff --git a/src/arch/aa64/isa.c b/src/arch/aa64/isa.c @@ -250,6 +250,13 @@ const AA64InsnDesc aa64_insn_table[] = { {MN("ldurb"), 0x38400000u, 0xFFE00C00u, AA64_FMT_LDST_SIMM9, 0, {0, 0}}, {MN("sturh"), 0x78000000u, 0xFFE00C00u, AA64_FMT_LDST_SIMM9, 0, {0, 0}}, {MN("ldurh"), 0x78400000u, 0xFFE00C00u, AA64_FMT_LDST_SIMM9, 0, {0, 0}}, + /* Signed unscaled loads (opc=10 → 64-bit Xt, opc=11 → 32-bit Wt). The + * printer keys the register width on opc; size selects the mnemonic. */ + {MN("ldursb"), 0x38800000u, 0xFFE00C00u, AA64_FMT_LDST_SIMM9, 0, {0, 0}}, + {MN("ldursb"), 0x38C00000u, 0xFFE00C00u, AA64_FMT_LDST_SIMM9, 0, {0, 0}}, + {MN("ldursh"), 0x78800000u, 0xFFE00C00u, AA64_FMT_LDST_SIMM9, 0, {0, 0}}, + {MN("ldursh"), 0x78C00000u, 0xFFE00C00u, AA64_FMT_LDST_SIMM9, 0, {0, 0}}, + {MN("ldursw"), 0xB8800000u, 0xFFE00C00u, AA64_FMT_LDST_SIMM9, 0, {0, 0}}, {MN("stur"), 0xB8000000u, 0xFFE00C00u, @@ -711,7 +718,13 @@ static void print_ldst_simm9(StrBuf* sb, u32 w, const AA64InsnDesc* d) { u32 sz = f.size & 3u; (void)d; if (f.V == 0) { - emit_reg(sb, f.Rt, /*sf=*/(int)(sz == 3u), 0); + /* opc 00/01 = STUR/LDUR (register width from size); opc 10/11 = signed + * load LDURS* (width from opc: 10 sign-extends to 64-bit Xt, 11 to + * 32-bit Wt). */ + int sf = (f.opc == 2u) ? 1 + : (f.opc == 3u) ? 0 + : (int)(sz == 3u); + emit_reg(sb, f.Rt, sf, 0); } else { char p = (sz == 0u) ? 'b' : (sz == 1u) ? 'h' : (sz == 2u) ? 's' : 'd'; emit_vreg(sb, f.Rt, p); diff --git a/src/asm/asm_lex.c b/src/asm/asm_lex.c @@ -386,6 +386,35 @@ AsmTok asm_lex_next(AsmLexer* l) { } } + /* Local-label identifier: a `.L`-prefixed symbol name (the universal GNU + * convention for assembler-local labels, e.g. `.Lcfree_ro.0`, `.L.str`, + * `.LBB0_1`). Lexed as a single ASM_TOK_IDENT — including the leading dot + * and any embedded dots — so it flows through the same operand / label / + * `.type` paths as an ordinary identifier. This is unambiguous against + * directives: no assembler directive begins with `.L`, so `.text`, + * `.section`, `.quad` etc. still tokenize as PUNCT('.') + IDENT and reach + * the directive dispatcher. Embedded `.` is consumed only when followed by + * another symbol char, so `.Lfoo, x` and `.Lfoo+4` stop at the delimiter. */ + if (ch == '.' && peek(l, 1) == 'L') { + bump(l); /* '.' */ + bump(l); /* 'L' */ + for (;;) { + int c = peek(l, 0); + if (is_alnum(c) || c == '$') { + bump(l); + } else if (c == '.' && (is_alnum(peek(l, 1)) || peek(l, 1) == '$' || + peek(l, 1) == '_')) { + bump(l); + } else { + break; + } + } + t.kind = ASM_TOK_IDENT; + t.spelling = intern_spliced(l, start, l->pos); + t.v.ident = t.spelling; + return t; + } + /* Identifier (§6.4.2). Encoding-prefix candidates above are matched * before this since L/u/U followed by a quote is a literal, not an * identifier. The grammar's identifier-nondigit covers letters, _, diff --git a/test/asm/encode/aa64_ldur_family.expected.hex b/test/asm/encode/aa64_ldur_family.expected.hex @@ -0,0 +1 @@ +200010f862f04ff8a44000b8e6c05fb8281100386af15f38ac210078eee15f783032803872d2df38b4428078f6c2df78388380b8 diff --git a/test/asm/encode/aa64_ldur_family.s b/test/asm/encode/aa64_ldur_family.s @@ -0,0 +1,14 @@ + .text + stur x0, [x1, #-256] + ldur x2, [x3, #255] + stur w4, [x5, #4] + ldur w6, [x7, #-4] + sturb w8, [x9, #1] + ldurb w10, [x11, #-1] + sturh w12, [x13, #2] + ldurh w14, [x15, #-2] + ldursb x16, [x17, #3] + ldursb w18, [x19, #-3] + ldursh x20, [x21, #4] + ldursh w22, [x23, #-4] + ldursw x24, [x25, #8] diff --git a/test/asm/encode/aa64_ldur_family.targets b/test/asm/encode/aa64_ldur_family.targets @@ -0,0 +1 @@ +aa64