kit

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

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:
Msrc/arch/arm32/native.c | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
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]));