From 62a572a0f0ae04da1e464592c65cc90dbc7ef303 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Sun, 14 Dec 2025 16:38:04 +0100 Subject: [PATCH] Use compiler-builtins for the memcpy family of functions --- Makefile | 2 +- src/externs.rs | 172 ------------------------------------------------- src/main.rs | 4 -- 3 files changed, 1 insertion(+), 177 deletions(-) delete mode 100644 src/externs.rs diff --git a/Makefile b/Makefile index fa5406d45a..47eb5659e7 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ $(BUILD)/kernel.all: $(LD_SCRIPT) $(LOCKFILE) $(MANIFEST) $(TARGET_SPEC) $(shell --manifest-path "$(MANIFEST)" \ --target "$(TARGET_SPEC)" \ --release \ - -Z build-std=core,alloc \ + -Z build-std=core,alloc -Zbuild-std-features=compiler-builtins-mem \ -- \ -C link-arg=-T -Clink-arg="$(LD_SCRIPT)" \ -C link-arg=-z -Clink-arg=max-page-size=0x1000 \ diff --git a/src/externs.rs b/src/externs.rs deleted file mode 100644 index a4c57e8f4a..0000000000 --- a/src/externs.rs +++ /dev/null @@ -1,172 +0,0 @@ -use core::mem; - -const WORD_SIZE: usize = mem::size_of::(); - -/// Memcpy -/// -/// Copy N bytes of memory from one location to another. -/// -/// This faster implementation works by copying bytes not one-by-one, but in -/// groups of 8 bytes (or 4 bytes in the case of 32-bit architectures). -#[unsafe(no_mangle)] -pub unsafe extern "C" fn memcpy(dest: *mut u8, src: *const u8, len: usize) -> *mut u8 { - unsafe { - // TODO: Alignment? Some sources claim that even on relatively modern ยต-arches, unaligned - // accesses spanning two pages, can take dozens of cycles. That means chunk-based memcpy can - // even be slower for small lengths if alignment is not taken into account. - // - // TODO: Optimize out smaller loops by first checking if len < WORD_SIZE, and possibly if - // dest + WORD_SIZE spans two pages, then doing one unaligned copy, then aligning up, and then - // doing one last unaligned copy? - // - // TODO: While we use the -fno-builtin equivalent, can we guarantee LLVM won't insert memcpy - // call inside here? Maybe write it in assembly? - - let mut i = 0_usize; - - // First we copy len / WORD_SIZE chunks... - - let chunks = len / WORD_SIZE; - - while i < chunks * WORD_SIZE { - dest.add(i) - .cast::() - .write_unaligned(src.add(i).cast::().read_unaligned()); - i += WORD_SIZE; - } - - // .. then we copy len % WORD_SIZE bytes - while i < len { - dest.add(i).write(src.add(i).read()); - i += 1; - } - - dest - } -} - -/// Memmove -/// -/// Copy N bytes of memory from src to dest. The memory areas may overlap. -/// -/// This faster implementation works by copying bytes not one-by-one, but in -/// groups of 8 bytes (or 4 bytes in the case of 32-bit architectures). -#[unsafe(no_mangle)] -pub unsafe extern "C" fn memmove(dest: *mut u8, src: *const u8, len: usize) -> *mut u8 { - unsafe { - let chunks = len / WORD_SIZE; - - // TODO: also require dest - src < len before choosing to copy backwards? - if src < dest as *const u8 { - // We have to copy backwards if copying upwards. - - let mut i = len; - - while i != chunks * WORD_SIZE { - i -= 1; - dest.add(i).write(src.add(i).read()); - } - - while i > 0 { - i -= WORD_SIZE; - - dest.add(i) - .cast::() - .write_unaligned(src.add(i).cast::().read_unaligned()); - } - } else { - // We have to copy forward if copying downwards. - - let mut i = 0_usize; - - while i < chunks * WORD_SIZE { - dest.add(i) - .cast::() - .write_unaligned(src.add(i).cast::().read_unaligned()); - - i += WORD_SIZE; - } - - while i < len { - dest.add(i).write(src.add(i).read()); - i += 1; - } - } - - dest - } -} - -/// Memset -/// -/// Fill a block of memory with a specified value. -/// -/// This faster implementation works by setting bytes not one-by-one, but in -/// groups of 8 bytes (or 4 bytes in the case of 32-bit architectures). -#[unsafe(no_mangle)] -pub unsafe extern "C" fn memset(dest: *mut u8, byte: i32, len: usize) -> *mut u8 { - unsafe { - let byte = byte as u8; - - let mut i = 0; - - let broadcasted = usize::from_ne_bytes([byte; WORD_SIZE]); - let chunks = len / WORD_SIZE; - - while i < chunks * WORD_SIZE { - dest.add(i).cast::().write_unaligned(broadcasted); - i += WORD_SIZE; - } - - while i < len { - dest.add(i).write(byte); - i += 1; - } - - dest - } -} - -/// Memcmp -/// -/// Compare two blocks of memory. -/// -/// This faster implementation works by comparing bytes not one-by-one, but in -/// groups of 8 bytes (or 4 bytes in the case of 32-bit architectures). -#[unsafe(no_mangle)] -pub unsafe extern "C" fn memcmp(s1: *const u8, s2: *const u8, len: usize) -> i32 { - unsafe { - let mut i = 0_usize; - - // First compare WORD_SIZE chunks... - let chunks = len / WORD_SIZE; - - while i < chunks * WORD_SIZE { - let a = s1.add(i).cast::().read_unaligned(); - let b = s2.add(i).cast::().read_unaligned(); - - if a != b { - // x86 has had bswap since the 80486, and the compiler will likely use the faster - // movbe. AArch64 has the REV instruction, which I think is universally available. - let diff = usize::from_be(a).wrapping_sub(usize::from_be(b)) as isize; - - // TODO: If chunk size == 32 bits, diff can be returned directly. - return diff.signum() as i32; - } - i += WORD_SIZE; - } - - // ... and then compare bytes. - while i < len { - let a = s1.add(i).read(); - let b = s2.add(i).read(); - - if a != b { - return i32::from(a) - i32::from(b); - } - i += 1; - } - - 0 - } -} diff --git a/src/main.rs b/src/main.rs index 453b76328a..6c3fa2b01d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -102,10 +102,6 @@ mod devices; /// Event handling mod event; -/// External functions -#[cfg(not(test))] -mod externs; - /// Logging mod log;