commit 4fe46147cf5ededdb020009c7d0b93e5842cc34c
parent fd743afc8033264bba9f456a36810a97df568326
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 17 Jun 2026 01:12:50 -0700
arm32 as: SP-relative add/str/ldr, ';' statement separator, __ELF__
Harden the standalone assembler for hand-written runtime asm (the kit self-built
rt's AEABI helpers + coroutine switch):
- add rd,sp,#imm / add sp,sp,#imm and word str/ldr [sp,#imm] selected the
hi-register / low-register forms, dropping the immediate or truncating sp(r13)
to r5. Dispatch by operand shape to the SP-relative T1/T2 encodings.
- ';' is the GNU-as statement separator (line comment is @/// on these targets,
never ';'); a macro packing several statements onto one logical line via a ';'
SEPARATOR depends on it. The lexer now surfaces it as a newline boundary.
- arm-none-eabi is ELF with no symbol prefix; define __ELF__ and an empty
__USER_LABEL_PREFIX__ so compiler-rt's assembly.h takes the ELF path (.globl/
.type %function/.size) instead of the COFF path (.def/.scl/.endef, leading _).
test-isa (314) + test-asm green; coro self-builds, AEABI advances to GNU numeric
local labels (next).
Diffstat:
3 files changed, 43 insertions(+), 0 deletions(-)
diff --git a/src/arch/arm32/arch.c b/src/arch/arm32/arch.c
@@ -117,6 +117,12 @@ static int arm32_apply_label_fixup(Compiler* c, const ArchLabelFixup* fx) {
static const KitPredefinedMacro arm32_predefined_macros[] = {
{KIT_SLICE_LIT("__arm__"), KIT_SLICE_LIT("1")},
{KIT_SLICE_LIT("__ARMEL__"), KIT_SLICE_LIT("1")},
+ /* arm-none-eabi is an ELF target with no symbol-name prefix; gcc/clang
+ * define both. Without them, hand-written runtime asm that branches on the
+ * object format (compiler-rt's assembly.h) falls through to the COFF path
+ * (`.def/.scl/.endef`, a leading `_`). __USER_LABEL_PREFIX__ is empty. */
+ {KIT_SLICE_LIT("__ELF__"), KIT_SLICE_LIT("1")},
+ {KIT_SLICE_LIT("__USER_LABEL_PREFIX__"), KIT_SLICE_LIT("")},
{KIT_SLICE_LIT("__thumb__"), KIT_SLICE_LIT("1")},
{KIT_SLICE_LIT("__thumb2__"), KIT_SLICE_LIT("1")},
{KIT_SLICE_LIT("__THUMBEL__"), KIT_SLICE_LIT("1")},
diff --git a/src/arch/arm32/asm.c b/src/arch/arm32/asm.c
@@ -746,6 +746,20 @@ static void assemble_one(AsmDriver* d, const Arm32InsnDesc* desc, u32 cond) {
return;
}
rm = parse_reg(d);
+ /* `add rd, sp, #imm` / `add sp, sp, #imm` (a third immediate operand
+ * through SP) use the SP-relative encodings: ADD rd,sp,#imm8*4 (T1,
+ * 0xA800, rd<=r7) or ADD sp,sp,#imm7*4 (T2, 0xB000). The hi-register form
+ * below has no immediate and would drop it. */
+ if (slice_eq_cstr(mn, "add") && rm == 13u && asm_driver_eat_comma(d)) {
+ u32 imm = (u32)parse_imm(d);
+ if (rdn == 13u)
+ emit_t16(d, (u16)(0xb000u | ((imm / 4u) & 0x7fu)));
+ else if (rdn <= 7u)
+ emit_t16(d, (u16)(0xa800u | ((rdn & 7u) << 8) | ((imm / 4u) & 0xffu)));
+ else
+ asm_driver_panic(d, "arm32 asm: add sp immediate needs add.w");
+ return;
+ }
emit_t16(d, (u16)((desc->match & 0xff00u) | (((rdn >> 3) & 1u) << 7) |
((rm & 0xfu) << 3) | (rdn & 7u)));
return;
@@ -756,6 +770,15 @@ static void assemble_one(AsmDriver* d, const Arm32InsnDesc* desc, u32 cond) {
u32 op, scale, imm5;
expect_comma(d);
parse_mem(d, &base, &disp);
+ /* A word str/ldr through SP uses the dedicated SP-relative T2 encoding
+ * (0x9000/0x9800, imm8*4); the low-register T1 form here would truncate
+ * sp(r13) to r5. (strb/strh/ldrb/ldrh have no SP T2 form — they would need
+ * the 32-bit T3, not yet emitted from this 16-bit handler.) */
+ if (base == 13u && (slice_eq_cstr(mn, "str") || slice_eq_cstr(mn, "ldr"))) {
+ u32 sp_op = slice_eq_cstr(mn, "ldr") ? 0x9800u : 0x9000u;
+ emit_t16(d, (u16)(sp_op | ((rt & 7u) << 8) | (((u32)disp / 4u) & 0xffu)));
+ return;
+ }
op = (desc->match >> 11) & 0x1fu;
scale = (op <= 0x0du) ? 4u : (op <= 0x0fu) ? 1u : 2u;
imm5 = ((u32)disp / scale) & 0x1fu;
diff --git a/src/asm/asm_lex.c b/src/asm/asm_lex.c
@@ -312,6 +312,20 @@ AsmTok asm_lex_next(AsmLexer* l) {
l->had_space = 0;
return t;
}
+ /* `;` is the GNU-as statement separator (the line comment on these targets
+ * is `@`/`//`/`#`, never `;`). A macro that packs several directives/labels/
+ * instructions onto one logical line via a `;` SEPARATOR (e.g. compiler-rt's
+ * DEFINE_AEABI_*) depends on this — surface it as a newline boundary so each
+ * statement is parsed on its own. */
+ if (peek(l, 0) == ';') {
+ tloc = asm_lex_here(l);
+ bump(l);
+ t.kind = ASM_TOK_NEWLINE;
+ t.loc = tloc;
+ l->at_bol = 1;
+ l->had_space = 0;
+ return t;
+ }
break;
}