commit de680188bbd640e6176fdbc739a3a4a363611dd1
parent 07dcaf160e8114e93708a51433fca6e5157ee891
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 16 Jun 2026 17:47:11 -0700
arm32 Phase 2: switch jump tables + bitfields + byte-reverse/CLZ/CTZ + barrier intrinsics
Diffstat:
3 files changed, 176 insertions(+), 30 deletions(-)
diff --git a/src/arch/arm32/arch.c b/src/arch/arm32/arch.c
@@ -190,18 +190,29 @@ static int arm32_supports_call_conv(const Compiler* c, KitCgCallConv cc) {
return cc == KIT_CG_CC_TARGET_C;
}
-/* Kept in lockstep with arm32_intrinsic in native.c: returning 1 for an
+/* Kept in lockstep with arm_intrinsic in native.c: returning 1 for an
* intrinsic this backend does not lower panics at emit. No default: case so
- * -Wswitch enforces the twin-sync invariant. Phase 1 lowers only TRAP. */
+ * -Wswitch enforces the twin-sync invariant. Lowered: TRAP, CLZ, CTZ (RBIT+CLZ),
+ * BSWAP (REV/REV16; the 64-bit form is a __bswapdi2 libcall in cg), the no-op /
+ * event hints (NOP/YIELD/WFI/WFE/SEV), and the barriers (DMB/DSB/ISB). POPCOUNT,
+ * the overflow forms, FMA, syscall/setjmp, IRQ/cache ops are other tracks. */
static int arm32_supports_intrinsic(const Compiler* c, KitCgIntrinsic intrin) {
(void)c;
switch (intrin) {
case KIT_CG_INTRIN_TRAP:
- return 1;
case KIT_CG_INTRIN_CLZ:
case KIT_CG_INTRIN_CTZ:
- case KIT_CG_INTRIN_POPCOUNT:
case KIT_CG_INTRIN_BSWAP:
+ case KIT_CG_INTRIN_CPU_NOP:
+ case KIT_CG_INTRIN_CPU_YIELD:
+ case KIT_CG_INTRIN_ISB:
+ case KIT_CG_INTRIN_DMB:
+ case KIT_CG_INTRIN_DSB:
+ case KIT_CG_INTRIN_WFI:
+ case KIT_CG_INTRIN_WFE:
+ case KIT_CG_INTRIN_SEV:
+ return 1;
+ case KIT_CG_INTRIN_POPCOUNT:
case KIT_CG_INTRIN_SADD_OVERFLOW:
case KIT_CG_INTRIN_UADD_OVERFLOW:
case KIT_CG_INTRIN_SSUB_OVERFLOW:
@@ -211,14 +222,6 @@ static int arm32_supports_intrinsic(const Compiler* c, KitCgIntrinsic intrin) {
case KIT_CG_INTRIN_PREFETCH:
case KIT_CG_INTRIN_EXPECT:
case KIT_CG_INTRIN_ASSUME_ALIGNED:
- case KIT_CG_INTRIN_CPU_NOP:
- case KIT_CG_INTRIN_CPU_YIELD:
- case KIT_CG_INTRIN_ISB:
- case KIT_CG_INTRIN_DMB:
- case KIT_CG_INTRIN_DSB:
- case KIT_CG_INTRIN_WFI:
- case KIT_CG_INTRIN_WFE:
- case KIT_CG_INTRIN_SEV:
case KIT_CG_INTRIN_FRAME_ADDRESS:
case KIT_CG_INTRIN_RETURN_ADDRESS:
case KIT_CG_INTRIN_READCYCLECOUNTER:
diff --git a/src/arch/arm32/isa.h b/src/arch/arm32/isa.h
@@ -251,4 +251,52 @@ static inline u32 arm_nop32(void) { return arm_t32(0xf3afu, 0x8000u); }
static inline u32 arm_push_w(u32 reglist) { return arm_t32(0xe92du, reglist & 0xdfffu); }
static inline u32 arm_pop_w(u32 reglist) { return arm_t32(0xe8bdu, reglist & 0xffffu); }
+/* --------- bit-field extract / insert (Thumb-2 T1) --------- */
+/* lsb is split into imm3:imm2 = (lsb>>2):(lsb&3). UBFX/SBFX encode width-1;
+ * BFI/BFC encode msb = lsb + width - 1. */
+static inline u32 arm_ubfx(u32 rd, u32 rn, u32 lsb, u32 width) {
+ u32 imm3 = (lsb >> 2) & 7u, imm2 = lsb & 3u, widthm1 = (width - 1u) & 0x1fu;
+ return arm_t32(0xf3c0u | (rn & 0xfu),
+ (imm3 << 12) | (rd << 8) | (imm2 << 6) | widthm1);
+}
+static inline u32 arm_sbfx(u32 rd, u32 rn, u32 lsb, u32 width) {
+ u32 imm3 = (lsb >> 2) & 7u, imm2 = lsb & 3u, widthm1 = (width - 1u) & 0x1fu;
+ return arm_t32(0xf340u | (rn & 0xfu),
+ (imm3 << 12) | (rd << 8) | (imm2 << 6) | widthm1);
+}
+/* BFI rd, rn, #lsb, #width : insert width bits of rn into rd starting at lsb. */
+static inline u32 arm_bfi(u32 rd, u32 rn, u32 lsb, u32 width) {
+ u32 imm3 = (lsb >> 2) & 7u, imm2 = lsb & 3u,
+ msb = (lsb + width - 1u) & 0x1fu;
+ return arm_t32(0xf360u | (rn & 0xfu), (imm3 << 12) | (rd << 8) | (imm2 << 6) | msb);
+}
+/* BFC rd, #lsb, #width : clear width bits of rd (BFI with Rn=1111). */
+static inline u32 arm_bfc(u32 rd, u32 lsb, u32 width) {
+ return arm_bfi(rd, 0xfu, lsb, width);
+}
+
+/* --------- count / reverse --------- */
+static inline u32 arm_clz(u32 rd, u32 rm) {
+ return arm_t32(0xfab0u | (rm & 0xfu), 0xf080u | (rd << 8) | (rm & 0xfu));
+}
+static inline u32 arm_rbit(u32 rd, u32 rm) {
+ return arm_t32(0xfa90u | (rm & 0xfu), 0xf0a0u | (rd << 8) | (rm & 0xfu));
+}
+/* 16-bit byte-reverse (T1): REV / REV16 / REVSH. */
+static inline u16 arm_rev(u32 rd, u32 rm) { return (u16)(0xba00u | ((rm & 7u) << 3) | (rd & 7u)); }
+static inline u16 arm_rev16(u32 rd, u32 rm) { return (u16)(0xba40u | ((rm & 7u) << 3) | (rd & 7u)); }
+static inline u16 arm_revsh(u32 rd, u32 rm) { return (u16)(0xbac0u | ((rm & 7u) << 3) | (rd & 7u)); }
+
+/* --------- barriers (32-bit T1, hw2 = 0x8f00 | op<<4 | option) --------- */
+/* option = 0xf for the "sy" full-system barrier (the only form kit emits). */
+static inline u32 arm_dmb(u32 option) { return arm_t32(0xf3bfu, 0x8f50u | (option & 0xfu)); }
+static inline u32 arm_dsb(u32 option) { return arm_t32(0xf3bfu, 0x8f40u | (option & 0xfu)); }
+static inline u32 arm_isb(u32 option) { return arm_t32(0xf3bfu, 0x8f60u | (option & 0xfu)); }
+
+/* --------- 16-bit hint instructions (0xbf__) --------- */
+static inline u16 arm_yield16(void) { return (u16)0xbf10u; }
+static inline u16 arm_wfe16(void) { return (u16)0xbf20u; }
+static inline u16 arm_wfi16(void) { return (u16)0xbf30u; }
+static inline u16 arm_sev16(void) { return (u16)0xbf40u; }
+
#endif
diff --git a/src/arch/arm32/native.c b/src/arch/arm32/native.c
@@ -1381,10 +1381,22 @@ static void arm_load_label_addr(NativeTarget* t, NativeLoc dst, MCLabel l) {
}
static void arm_indirect_branch(NativeTarget* t, NativeLoc addr,
const MCLabel* targets, u32 n) {
- (void)addr;
+ /* The cg layer materialized the dense switch's target code address into
+ * `addr` (a load of table[idx] from the .Lkit_jt rodata table). The table
+ * entries are R_ARM_ABS32 relocations against per-block local label symbols,
+ * which carry NO Thumb bit (only STT_FUNC symbols get it in arm_func_end), so
+ * the loaded address is even. BX to an even address would switch to ARM
+ * state (UNPREDICTABLE on M-profile); force the Thumb bit with ORR #1 first.
+ * `addr` is released by the caller right after this, so clobbering it is free.
+ */
+ MCEmitter* mc = t->mc;
+ u32 ra = loc_reg(addr);
+ u32 enc;
(void)targets;
(void)n;
- ARM_UNIMPL("indirect_branch (switch table)");
+ thumb_expand_imm_encode(1u, &enc);
+ arm_emit_t32(mc, arm_dp_imm(2u, 0u, ra, ra, enc)); /* ORR ra, ra, #1 */
+ arm_emit_t16(mc, arm_bx(ra)); /* BX ra */
}
static void arm_tls_addr_of(NativeTarget* t, NativeLoc dst, ObjSymId sym,
i64 addend) {
@@ -1452,19 +1464,41 @@ static void arm_set_bytes(NativeTarget* t, NativeAddr dst, NativeLoc v,
arm_emit_mem(a, 0, v, d, mem); /* STRB v, [dst+off] */
}
}
+/* C bit-fields. The storage unit {bf.storage.type, bf.storage.size} lives at
+ * bf.storage_offset from `addr`; the field is `bf.bit_width` bits at bit
+ * bf.bit_offset within it. Load = load the unit (zero-extended) then UBFX/SBFX;
+ * store = load the unit, BFI the value in, store the unit back. Sub-word storage
+ * units (1/2 bytes) are loaded/stored at their natural width, so the BFI/UBFX
+ * geometry (offset+width <= storage_bits) stays in range. */
static void arm_bitfield_load(NativeTarget* t, NativeLoc dst, NativeAddr addr,
BitFieldAccess bf) {
- (void)dst;
- (void)addr;
- (void)bf;
- ARM_UNIMPL("bitfield_load");
+ Arm32NativeTarget* a = arm_of(t);
+ MCEmitter* mc = t->mc;
+ u32 rd = loc_reg(dst);
+ u32 width = bf.bit_width ? bf.bit_width : 1u;
+ NativeAddr saddr = addr;
+ NativeLoc unit = dst;
+ saddr.offset += (i32)bf.storage_offset;
+ unit.type = bf.storage.type ? bf.storage.type : dst.type;
+ arm_emit_mem(a, 1, unit, saddr, bf.storage); /* LDR(B/H) rd, [addr+soff] */
+ if (bf.signed_)
+ arm_emit_t32(mc, arm_sbfx(rd, rd, bf.bit_offset, width));
+ else
+ arm_emit_t32(mc, arm_ubfx(rd, rd, bf.bit_offset, width));
}
static void arm_bitfield_store(NativeTarget* t, NativeAddr addr, NativeLoc v,
BitFieldAccess bf) {
- (void)addr;
- (void)v;
- (void)bf;
- ARM_UNIMPL("bitfield_store");
+ Arm32NativeTarget* a = arm_of(t);
+ MCEmitter* mc = t->mc;
+ u32 rv = loc_reg(v);
+ u32 width = bf.bit_width ? bf.bit_width : 1u;
+ KitCgTypeId unit_ty = bf.storage.type ? bf.storage.type : v.type;
+ NativeLoc word = native_loc_reg(unit_ty, NATIVE_REG_INT, ARM_TMP);
+ NativeAddr saddr = addr;
+ saddr.offset += (i32)bf.storage_offset;
+ arm_emit_mem(a, 1, word, saddr, bf.storage); /* LDR tmp, [addr] */
+ arm_emit_t32(mc, arm_bfi(ARM_TMP, rv, bf.bit_offset, width)); /* insert v */
+ arm_emit_mem(a, 0, word, saddr, bf.storage); /* STR tmp, [addr] */
}
static void arm_alloca(NativeTarget* t, NativeLoc dst, NativeLoc size,
u32 align) {
@@ -1489,18 +1523,79 @@ static void arm_atomic_store(NativeTarget* t, NativeAddr addr, NativeLoc v,
(void)order;
ARM_UNIMPL("atomic_store");
}
+/* The ARMv7-M barrier option for a full-system barrier ("sy"). 64-bit clz/ctz/
+ * bswap on a 32-bit target are already routed to __*di2 libcalls by cg, so the
+ * backend only ever sees the 16/32-bit forms here. Each lowered intrinsic must
+ * stay in lockstep with arm32_supports_intrinsic (arch.c). */
+#define ARM_BARRIER_SY 0xfu
+
static void arm_intrinsic(NativeTarget* t, IntrinKind kind,
const NativeLoc* dsts, u32 ndst, const NativeLoc* args,
u32 narg) {
- (void)dsts;
- (void)ndst;
- (void)args;
- (void)narg;
- if (kind == INTRIN_TRAP) {
- arm_trap(t);
- return;
+ Arm32NativeTarget* a = arm_of(t);
+ MCEmitter* mc = t->mc;
+ switch (kind) {
+ case INTRIN_TRAP:
+ arm_trap(t);
+ return;
+ case INTRIN_CLZ:
+ if (ndst == 1u && narg == 1u) {
+ arm_emit_t32(mc, arm_clz(loc_reg(dsts[0]), loc_reg(args[0])));
+ return;
+ }
+ break;
+ case INTRIN_CTZ:
+ /* RBIT reverses the bit order, so the trailing-zero count becomes a
+ * leading-zero count of the reversed word. */
+ if (ndst == 1u && narg == 1u) {
+ arm_emit_t32(mc, arm_rbit(loc_reg(dsts[0]), loc_reg(args[0])));
+ arm_emit_t32(mc, arm_clz(loc_reg(dsts[0]), loc_reg(dsts[0])));
+ return;
+ }
+ break;
+ case INTRIN_BSWAP:
+ if (ndst == 1u && narg == 1u) {
+ u32 width = native_type_size(t, dsts[0].type);
+ if (width == 4u) {
+ arm_emit_t16(mc, arm_rev(loc_reg(dsts[0]), loc_reg(args[0])));
+ return;
+ }
+ if (width == 2u) {
+ /* REV16 byte-swaps within each halfword: the low halfword of the
+ * input becomes the swapped 16-bit result in the low halfword. */
+ arm_emit_t16(mc, arm_rev16(loc_reg(dsts[0]), loc_reg(args[0])));
+ return;
+ }
+ }
+ break;
+ case INTRIN_CPU_NOP:
+ arm_emit_t16(mc, arm_nop16());
+ return;
+ case INTRIN_CPU_YIELD:
+ arm_emit_t16(mc, arm_yield16());
+ return;
+ case INTRIN_WFI:
+ arm_emit_t16(mc, arm_wfi16());
+ return;
+ case INTRIN_WFE:
+ arm_emit_t16(mc, arm_wfe16());
+ return;
+ case INTRIN_SEV:
+ arm_emit_t16(mc, arm_sev16());
+ return;
+ case INTRIN_DMB:
+ arm_emit_t32(mc, arm_dmb(ARM_BARRIER_SY));
+ return;
+ case INTRIN_DSB:
+ arm_emit_t32(mc, arm_dsb(ARM_BARRIER_SY));
+ return;
+ case INTRIN_ISB:
+ arm_emit_t32(mc, arm_isb(ARM_BARRIER_SY));
+ return;
+ default:
+ break;
}
- ARM_UNIMPL("intrinsic");
+ arm_panic(a, "intrinsic not lowered (overflow/FMA/syscall/etc. are other tracks)");
}
/* ============================ construction ============================ */