commit 5ac31d5d4d6a900f1dd1aeb053a70f2930021437
parent 4fe46147cf5ededdb020009c7d0b93e5842cc34c
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 17 Jun 2026 01:24:18 -0700
arm32 as: high-register T3 ldr/str; self-build the coroutine rt layer
A 16-bit ldr/str can only encode a low (r0-r7) transfer register, so saving
sp/lr in the coroutine context switch (`str lr, [r0, #36]`) silently truncated
the register (lr -> r6). Fall to the 32-bit T3 form for a high transfer/base
register or an out-of-range/unscalable displacement.
With that, the arm-eabi-thumb2 runtime's coroutine layer (coro/arm32.c +
coro/coro.c) self-builds with kit's own assembler — RT_..._CORO = arm32. The
save/restore asm is verified correct under qemu (a setjmp/longjmp round-trip
delivers the right value at -O0 and -O1); the full coro_init/resume/yield path
still faults on this target (a coro/coro.c-level layout interaction, surfaced
now that the layer builds at all — the switch/trampoline disassemble
byte-correct). test-isa/test-asm green; smoke + toy corpus unaffected.
Diffstat:
2 files changed, 41 insertions(+), 16 deletions(-)
diff --git a/mk/rt.mk b/mk/rt.mk
@@ -210,17 +210,24 @@ RT_arm-eabi-thumb2_TARGET = arm-none-eabi
RT_arm-eabi-thumb2_ABI = ilp32
RT_arm-eabi-thumb2_INT128 = 0
# Coroutine support is a hand-written register-switch in ARM asm (coro/arm32.c +
-# coro/coro.c's file-scope asm) that the kit assembler does not yet cover; like
-# the AEABI layer below it is dropped from the kit self-build (a follow-on once
-# kit-as covers the idioms). The C runtime kit needs for ordinary programs is
-# unaffected.
-RT_arm-eabi-thumb2_CORO =
-# AEABI alias layer (aeabi.c + aeabi_thumb2.S) is hand-written ARM asm that the
-# kit assembler does not yet fully cover; kit-compiled code references the
-# libgcc-named helpers (__udivdi3 / __adddf3 / ...) the generic compiler-rt C
-# sources provide, not the __aeabi_* aliases, so kit self-builds this runtime
-# without the AEABI layer. Full kit-as coverage of the AEABI asm (re-enabling
-# `= thumb2`) is a follow-on.
+# coro/coro.c's file-scope asm). kit-as now covers those idioms (cmp/mov #imm,
+# SP-relative add/str/ldr incl. high-register T3, UDF, the soft-float
+# __ARM_FP-undefined path), so the coroutine layer self-builds with kit's own
+# toolchain — and the save/restore asm is verified correct under qemu
+# (setjmp/longjmp round-trips). The full coro_init/resume/yield path still
+# faults on this target (a coro/coro.c-level issue surfaced now that the layer
+# is built at all; the asm itself disassembles byte-correct) — tracked in
+# doc/plan/ARM32.md.
+RT_arm-eabi-thumb2_CORO = arm32
+# AEABI alias layer (aeabi.c + aeabi_thumb2.S) is hand-written ARM asm. kit-as
+# assembles most of it (the ';' SEPARATOR, '#imm'-in-macro under __ASSEMBLER__,
+# __ELF__/.type %function path, SP-relative stack manipulation) but the
+# soft-float compare helpers (DEFINE_AEABI_DCMP/FCMP) use GNU numeric local
+# labels (1:/1f/1b) the assembler does not yet support — so the AEABI layer is
+# still dropped from the kit self-build. kit-compiled code references the
+# libgcc-named helpers (__udivdi3 / __adddf3 / ...) the compiler-rt C sources
+# provide, not the __aeabi_* aliases, so ordinary programs are unaffected.
+# Re-enabling `= thumb2` is gated on numeric-local-label support in kit-as.
RT_arm-eabi-thumb2_AEABI =
RT_arm-eabi-thumb1_TARGET = arm-none-eabi
diff --git a/src/arch/arm32/asm.c b/src/arch/arm32/asm.c
@@ -770,17 +770,35 @@ static void assemble_one(AsmDriver* d, const Arm32InsnDesc* desc, u32 cond) {
u32 op, scale, imm5;
expect_comma(d);
parse_mem(d, &base, &disp);
+ op = (desc->match >> 11) & 0x1fu;
+ scale = (op <= 0x0du) ? 4u : (op <= 0x0fu) ? 1u : 2u;
/* 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"))) {
+ * sp(r13) to r5. */
+ if (base == 13u && rt <= 7u &&
+ (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;
+ /* The 16-bit T1 forms only encode low transfer + base registers and a
+ * scaled imm5. A high register (e.g. saving sp/lr in the coroutine switch,
+ * `str lr, [r0, #36]`), a high base, or an out-of-range / unscalable
+ * displacement falls to the 32-bit T3 form (hw1 op|rn, hw2 rt<<12|imm12). */
+ if (rt > 7u || base > 7u || disp < 0 || (u32)disp > 31u * scale ||
+ (u32)disp % scale != 0u) {
+ u32 t3 = (desc->match == 0x6000u) ? 0xf8c0u /* str */
+ : (desc->match == 0x6800u) ? 0xf8d0u /* ldr */
+ : (desc->match == 0x7000u) ? 0xf880u /* strb */
+ : (desc->match == 0x7800u) ? 0xf890u /* ldrb */
+ : (desc->match == 0x8000u) ? 0xf8a0u /* strh */
+ : 0xf8b0u; /* ldrh */
+ if (disp < 0 || disp > 4095)
+ asm_driver_panic(d, "arm32 asm: ldr/str offset out of T3 range");
+ emit_t32(d, arm_t32(t3 | (base & 0xfu),
+ ((rt & 0xfu) << 12) | ((u32)disp & 0xfffu)));
+ return;
+ }
imm5 = ((u32)disp / scale) & 0x1fu;
emit_t16(d, (u16)((desc->match & 0xf800u) | (imm5 << 6) |
((base & 7u) << 3) | (rt & 7u)));