kit

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

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:
Mmk/rt.mk | 29++++++++++++++++++-----------
Msrc/arch/arm32/asm.c | 28+++++++++++++++++++++++-----
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)));