new file mode 100644
@@ -0,0 +1,384 @@
+/* Copyright (C) 2026 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library. If not, see
+ <https://www.gnu.org/licenses/>. */
+
+/*
+ * EV6/EV7 memmove. Alpha previously used the generic C memmove.
+ *
+ * When the destination does not overlap the source from below -- i.e.
+ * (uintptr)dst - (uintptr)src >= n, which also covers dst < src -- the copy
+ * runs forward and is exactly the ev6 memcpy (same-alignment quadword fast
+ * path with the EV7 prefetch unrolled loop, and the rotating ldq_u path for
+ * mismatched alignment). Only the genuinely overlapping case (src < dst <
+ * src+n) copies backward; that is rare and not performance critical, so it
+ * uses a straightforward aligned-quadword-or-byte descending loop.
+ *
+ * Temp usage mirrors memcpy ($0 result, $1..$7,$22 scratch); the backward
+ * path additionally keeps the running pointers in $16/$17 and uses $1,$2.
+ */
+
+#include <sysdep.h>
+
+ .arch ev6
+ .set noreorder
+ .set noat
+
+ENTRY(memmove)
+ .prologue 0
+
+ mov $16, $0 # E : return value = dest
+ ble $18, $nomoredata # U : nothing to do
+ subq $16, $17, $1 # E : dst - src
+ cmpult $1, $18, $2 # E : (dst-src) < n -> overlap from below
+
+ bne $2, $backward # U : overlapping -> copy backward
+ xor $16, $17, $1 # E : forward: same alignment?
+ and $1, 7, $1 # E : ... mod 8
+ bne $1, $misaligned # U : no -> rotating path
+
+ and $16, 7, $1 # E : both 0mod8?
+ beq $1, $both_0mod8 # U : yes
+ nop # E :
+
+ /* Same misalignment: copy bytes until both reach a 0mod8 boundary. */
+$head_align:
+ ldbu $1, 0($17) # L : grab a byte
+ subq $18, 1, $18 # E : count--
+ addq $17, 1, $17 # E : src++
+ stb $1, 0($16) # L :
+ addq $16, 1, $16 # E : dest++
+ and $16, 7, $1 # E : 0mod8 yet?
+ ble $18, $nomoredata # U : done?
+ bne $1, $head_align # U :
+
+$both_0mod8:
+ cmple $18, 127, $1 # E : Can we unroll the loop?
+ bne $1, $no_unroll # U :
+ and $16, 63, $1 # E : get mod64 alignment
+ beq $1, $do_unroll # U : no single quads to fiddle
+
+$single_head_quad:
+ ldq $1, 0($17) # L : get 8 bytes
+ subq $18, 8, $18 # E : count -= 8
+ addq $17, 8, $17 # E : src += 8
+ nop # E :
+
+ stq $1, 0($16) # L : store
+ addq $16, 8, $16 # E : dest += 8
+ and $16, 63, $1 # E : get mod64 alignment
+ bne $1, $single_head_quad # U : still not fully aligned
+
+$do_unroll:
+ /*
+ * Same IMPLVER dispatch as memcpy: the 21264 (EV6/67/68) uses wh64 two
+ * lines ahead; the 21364 (EV7) takes a loop that software-prefetches both
+ * streams (read on the source, modify intent on the destination) six
+ * cache lines ahead. IMPLVER returns 2 for the 21264 family, 3 for EV7.
+ */
+ cmple $18, 127, $1 # E : Can we go through the unrolled loop?
+ bne $1, $tail_quads # U : Nope
+ implver $2 # E : 2 = 21264, 3 = 21364
+ subq $2, 3, $2 # E : EV7 -> 0
+
+ beq $2, $ev7_unroll # U : EV7 takes the prefetch-modify path
+
+ /*
+ * wh64 claims a cache line for writing without filling it from memory,
+ * which makes the entire 64-byte block UNPREDICTABLE unless every byte
+ * of it is stored. In a forward overlapping copy (dst < src) the hint
+ * runs ahead of the stores and can therefore land on source bytes that
+ * have not been read yet, destroying them before the copy consumes
+ * them.
+ *
+ * At trip k the hint covers [dst_k+128, dst_k+192) and the unread
+ * source begins at src_k = dst_k + (src-dst), so the copy is safe
+ * exactly when src-dst >= 192. Note this bound tracks the wh64
+ * distance: it is the hint's far edge, so it has to be updated whenever
+ * that distance changes. When dst >= src+n (no overlap at all) src-dst
+ * wraps to a huge unsigned value, cmpult is false, and the wh64 path is
+ * used as normal.
+ *
+ * Too close a gap falls through to $ev7_unroll rather than the scalar
+ * $tail_quads loop. That path copies 64 bytes a trip like this one and
+ * merely swaps wh64 for LDS to F31, a prefetch-with-modify-intent that
+ * only reads and so cannot destroy the source; ReadBlkMod is
+ * implemented on the 21264 (it is the evict-next variants that are
+ * 21364-only). It reads a whole block into registers before storing
+ * any of it, so it is correct at any overlap. Landing on the scalar
+ * loop instead would cost roughly 8x on exactly the small-gap copies
+ * this guard catches -- a Python list.remove() moves its ob_item array
+ * with a gap of one pointer, so it always takes this path.
+ */
+ subq $17, $16, $1 # E : src - dst
+ cmpult $1, 192, $2 # E : (src-dst) < 192 ?
+ bne $2, $ev7_unroll # U : too close -- use the non-wh64 loop
+
+ /*
+ * Initial wh64 target, two lines ahead of the first block. Only 128
+ * bytes are known to remain here, so unlike the steady-state hint this
+ * one can overrun; fall back to the first block when it would.
+ */
+ addq $16, 128, $7 # E : initial (+2 line) wh64 address
+ subq $18, 192, $2 # E : room for [$16+128, $16+192)?
+ cmovlt $2, $16, $7 # E : no -- hint the first block instead
+
+$unroll_body:
+ wh64 ($7) # L1 : 64 bytes at ($7) about to be overwritten
+ ldq $6, 0($17) # L0 : bytes 0..7
+ nop # E :
+ nop # E :
+
+ ldq $4, 8($17) # L : bytes 8..15
+ ldq $5, 16($17) # L : bytes 16..23
+ addq $7, 64, $7 # E : Update next wh64 address
+ nop # E :
+
+ ldq $3, 24($17) # L : bytes 24..31
+ addq $16, 64, $23 # E : fallback wh64 address (see below)
+ nop # E :
+ nop # E :
+
+ addq $17, 32, $17 # E : src += 32 bytes
+ stq $6, 0($16) # L : bytes 0..7
+ nop # E :
+ nop # E :
+
+ stq $4, 8($16) # L : bytes 8..15
+ stq $5, 16($16) # L : bytes 16..23
+ lda $2, -256($18) # E : room for the +2 line hint next trip?
+ # it covers [$16+128, $16+192) there,
+ # so that trip needs 192 and this one
+ # needs 256
+ nop # E :
+
+ stq $3, 24($16) # L : bytes 24..31
+ addq $16, 32, $16 # E : dest += 32 bytes
+ nop # E :
+ nop # E :
+
+ ldq $6, 0($17) # L : bytes 0..7
+ ldq $4, 8($17) # L : bytes 8..15
+ nop # E :
+ nop # E :
+
+ ldq $5, 16($17) # L : bytes 16..23
+ ldq $3, 24($17) # L : bytes 24..31
+ addq $16, 32, $16 # E : dest += 32
+ subq $18, 64, $18 # E : count -= 64
+
+ addq $17, 32, $17 # E : src += 32
+ stq $6, -32($16) # L : bytes 0..7
+ stq $4, -24($16) # L : bytes 8..15
+ cmple $18, 63, $1 # E : At least one more trip?
+
+ /*
+ * $7 is deliberately finalized late, in the group with the loop branch
+ * rather than alongside the loads above, and the fallback address lives
+ * in $23 so that it survives the cmple that reuses $1 for the loop
+ * condition. This is not cosmetic: $7 is what the next trip's wh64
+ * issues against, and letting that hint issue while the previous trip's
+ * stores are still in flight provokes Mbox replay traps in the store
+ * stream. Measured on an EV68CB over a cold 256KB copy, moving the
+ * cmov down cuts replays (perf r4) from about 1000 per call to about
+ * 750 and is worth 1.4-1.8% from 2KB upwards, for no extra instructions.
+ * Moving it back up, or reusing $1 so that it has to move back up,
+ * gives up that gain.
+ */
+ stq $5, -16($16) # L : bytes 16..23
+ stq $3, -8($16) # L : bytes 24..31
+ cmovlt $2, $23, $7 # E : fallback if < 2 more trips (late; see
+ # the note above -- do not hoist this)
+ beq $1, $unroll_body
+ br $31, $tail_quads # U : 1..15 trailing quads/bytes
+
+ .align 4
+$ev7_unroll:
+ lds $f31, 6*64($16) # L : dest prefetch w/ modify intent
+ ldl $31, 6*64($17) # L : src prefetch (read)
+ ldq $1, 0($17) # L : bytes 0..7
+ ldq $2, 8($17) # L : bytes 8..15
+ ldq $3, 16($17) # L : bytes 16..23
+ ldq $4, 24($17) # L : bytes 24..31
+ ldq $5, 32($17) # L : bytes 32..39
+ ldq $6, 40($17) # L : bytes 40..47
+ ldq $7, 48($17) # L : bytes 48..55
+ ldq $22, 56($17) # L : bytes 56..63
+
+ stq $1, 0($16) # L :
+ stq $2, 8($16) # L :
+ stq $3, 16($16) # L :
+ stq $4, 24($16) # L :
+ stq $5, 32($16) # L :
+ stq $6, 40($16) # L :
+ stq $7, 48($16) # L :
+ stq $22, 56($16) # L :
+
+ subq $18, 64, $18 # E : count -= 64
+ addq $17, 64, $17 # E : src += 64
+ addq $16, 64, $16 # E : dest += 64
+ cmple $18, 63, $1 # E : less than one more full trip?
+ beq $1, $ev7_unroll # U : keep going
+
+$tail_quads:
+$no_unroll:
+ .align 4
+ subq $18, 8, $18 # E : At least a quad left?
+ blt $18, $less_than_8 # U : Nope
+ nop # E :
+ nop # E :
+
+$move_a_quad:
+ ldq $1, 0($17) # L : fetch 8
+ subq $18, 8, $18 # E : count -= 8
+ addq $17, 8, $17 # E : src += 8
+ nop # E :
+
+ stq $1, 0($16) # L : store 8
+ addq $16, 8, $16 # E : dest += 8
+ bge $18, $move_a_quad # U :
+ nop # E :
+
+$less_than_8:
+ .align 4
+ addq $18, 8, $18 # E : add back for trailing bytes
+ ble $18, $nomoredata # U : All-done
+ nop # E :
+ nop # E :
+
+$tail_bytes:
+ subq $18, 1, $18 # E : count--
+ ldbu $1, 0($17) # L : fetch a byte
+ addq $17, 1, $17 # E : src++
+ nop # E :
+
+ stb $1, 0($16) # L : store a byte
+ addq $16, 1, $16 # E : dest++
+ bgt $18, $tail_bytes # U : more to be done?
+ nop # E :
+
+ ret $31, ($26), 1 # L0 :
+ nop # E :
+ nop # E :
+ nop # E :
+
+$misaligned:
+ mov $0, $4 # E : dest temp
+ and $0, 7, $1 # E : dest alignment mod8
+ beq $1, $dest_0mod8 # U : life doesn't totally suck
+ nop
+
+$aligndest:
+ ble $18, $nomoredata # U :
+ ldbu $1, 0($17) # L : fetch a byte
+ subq $18, 1, $18 # E : count--
+ addq $17, 1, $17 # E : src++
+
+ stb $1, 0($4) # L : store it
+ addq $4, 1, $4 # E : dest++
+ and $4, 7, $1 # E : dest 0mod8 yet?
+ bne $1, $aligndest # U : go until we are aligned.
+
+ /* Source has unknown alignment, but dest is known to be 0mod8 */
+$dest_0mod8:
+ subq $18, 8, $18 # E : At least a quad left?
+ blt $18, $misalign_tail # U : Nope
+ ldq_u $3, 0($17) # L : seed (rotating load) of 8 bytes
+ nop # E :
+
+$mis_quad:
+ ldq_u $16, 8($17) # L : Fetch next 8
+ extql $3, $17, $3 # U : masking
+ extqh $16, $17, $1 # U : masking
+ bis $3, $1, $1 # E : merged bytes to store
+
+ subq $18, 8, $18 # E : count -= 8
+ addq $17, 8, $17 # E : src += 8
+ stq $1, 0($4) # L : store 8 (aligned)
+ mov $16, $3 # E : "rotate" source data
+
+ addq $4, 8, $4 # E : dest += 8
+ bge $18, $mis_quad # U : More quads to move
+ nop
+ nop
+
+$misalign_tail:
+ addq $18, 8, $18 # E : account for tail stuff
+ ble $18, $nomoredata # U :
+ nop
+ nop
+
+$misalign_byte:
+ ldbu $1, 0($17) # L : fetch 1
+ subq $18, 1, $18 # E : count--
+ addq $17, 1, $17 # E : src++
+ nop # E :
+
+ stb $1, 0($4) # L : store
+ addq $4, 1, $4 # E : dest++
+ bgt $18, $misalign_byte # U : more to go?
+ nop
+
+$nomoredata:
+ ret $31, ($26), 1 # L0 :
+
+ /*
+ * Overlapping move (src < dst < src+n): copy from the high end down.
+ * Aligned quadwords when the two ends share alignment, else bytes.
+ * $0 already holds the original dest for the return value.
+ */
+$backward:
+ addq $16, $18, $16 # E : dest end (one past last byte)
+ addq $17, $18, $17 # E : src end
+ xor $16, $17, $1 # E : ends share alignment?
+ and $1, 7, $1 # E :
+ bne $1, $back_bytes # U : no -> byte copy
+
+$back_head:
+ and $16, 7, $1 # E : dest end 0mod8 yet?
+ beq $1, $back_quads # U :
+ lda $16, -1($16) # E : dest--
+ lda $17, -1($17) # E : src--
+ ldbu $1, 0($17) # L :
+ stb $1, 0($16) # L :
+ subq $18, 1, $18 # E : count--
+ beq $18, $nomoredata # U :
+ br $31, $back_head # U :
+
+$back_quads:
+ subq $18, 8, $2 # E : (count - 8)
+ blt $2, $back_bytes # U : < 8 left -> bytes
+$back_q:
+ lda $16, -8($16) # E : dest -= 8
+ lda $17, -8($17) # E : src -= 8
+ ldq $1, 0($17) # L :
+ stq $1, 0($16) # L :
+ mov $2, $18 # E : commit count -= 8
+ subq $18, 8, $2 # E : (count - 8)
+ bge $2, $back_q # U :
+
+$back_bytes:
+ beq $18, $nomoredata # U :
+$back_b:
+ lda $16, -1($16) # E :
+ lda $17, -1($17) # E :
+ ldbu $1, 0($17) # L :
+ stb $1, 0($16) # L :
+ subq $18, 1, $18 # E :
+ bne $18, $back_b # U :
+ ret $31, ($26), 1 # L0 :
+
+END(memmove)
+libc_hidden_builtin_def (memmove)