commit 4786aeb7126315cdd1bffdd91c6d85a7b25b8aca
parent 0abbed1b027074bf9c596adb91d0fab896d8a1b1
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 16 Jun 2026 22:13:46 -0700
arm32 §2: @memmove (overlap-safe, direction-aware)
INTRIN_MEMMOVE panicked. Implement it sharing the granule ladder with copy_bytes
via arm_copy_granules_dir: compare dst vs src and copy forward (low→high) when
dst < src unsigned, else backward (high→low), so each granule's load precedes any
store that could clobber an overlapping source. Fixes 40_alloca_memmove.
Diffstat:
1 file changed, 53 insertions(+), 0 deletions(-)
diff --git a/src/arch/arm32/native.c b/src/arch/arm32/native.c
@@ -2048,6 +2048,33 @@ static void arm_copy_bytes(NativeTarget* t, NativeAddr dst, NativeAddr src,
}
if (borrowed != 0xffu) arm_emit_t32(a->base.mc, arm_pop_w(1u << borrowed));
}
+
+/* Copy `size` bytes between two register-based addresses with the same 4|2|1
+ * granule ladder as copy_bytes, but in a chosen direction: forward (low→high)
+ * or backward (high→low). memmove picks the overlap-safe direction at runtime.
+ * Transfers through `xfer`, which must alias neither base. */
+static void arm_copy_granules_dir(Arm32NativeTarget* a, u32 dbase, u32 sbase,
+ u32 size, u32 xfer, int backward) {
+ if (backward) {
+ u32 off = size;
+ while (off > 0u) {
+ u32 sz = off >= 4u ? 4u : off >= 2u ? 2u : 1u;
+ off -= sz;
+ arm_emit_mem_one(a, 1, xfer, sbase, (i32)off, sz);
+ arm_emit_mem_one(a, 0, xfer, dbase, (i32)off, sz);
+ }
+ } else {
+ u32 off = 0;
+ while (off < size) {
+ u32 rem = size - off;
+ u32 sz = rem >= 4u ? 4u : rem >= 2u ? 2u : 1u;
+ arm_emit_mem_one(a, 1, xfer, sbase, (i32)off, sz);
+ arm_emit_mem_one(a, 0, xfer, dbase, (i32)off, sz);
+ off += sz;
+ }
+ }
+}
+
/* set_bytes: store the materialized fill byte `v` across acc.size bytes. The
* common case is struct/array zero-init (v == 0); any fill byte is correct. One
* STRB per byte — correctness-first; word-splat widening is deferred (it would
@@ -2548,6 +2575,32 @@ static void arm_intrinsic(NativeTarget* t, IntrinKind kind,
case INTRIN_TRAP:
arm_trap(t);
return;
+ case INTRIN_MEMMOVE: {
+ /* Overlap-safe move: copy forward when dst < src (unsigned), else backward,
+ * so each granule's load precedes any store that could clobber it. dst/src
+ * are pointer registers, size a constant; the transfer reg aliases neither
+ * base. Shares the granule ladder with copy_bytes. */
+ MCLabel forward = arm_label_new(t), done = arm_label_new(t);
+ u32 dst, src, size, xfer;
+ if (narg != 3u || args[0].kind != NATIVE_LOC_REG ||
+ args[1].kind != NATIVE_LOC_REG || args[2].kind != NATIVE_LOC_IMM)
+ arm_panic(a, "memmove: expected (reg dst, reg src, imm size)");
+ if (args[2].v.imm < 0 || args[2].v.imm > 4096)
+ arm_panic(a, "memmove: size out of range (needs a runtime loop)");
+ dst = loc_reg(args[0]);
+ src = loc_reg(args[1]);
+ size = (u32)args[2].v.imm;
+ xfer = (dst != ARM_TMP && src != ARM_TMP) ? ARM_TMP : ARM_SCRATCH;
+ if (xfer == dst || xfer == src)
+ arm_panic(a, "memmove: no free transfer register");
+ arm_cmp_branch(t, CMP_LT_U, args[0], args[1], forward);
+ arm_copy_granules_dir(a, dst, src, size, xfer, /*backward=*/1);
+ arm_jump(t, done);
+ arm_label_place(t, forward);
+ arm_copy_granules_dir(a, dst, src, size, xfer, /*backward=*/0);
+ arm_label_place(t, done);
+ return;
+ }
case INTRIN_CLZ:
if (ndst == 1u && narg == 1u) {
NativeLoc a0 = arm_intrin_arg(a, args[0], loc_reg(dsts[0]));