From 1c781aff562ce26dc4b8e0ffcb83f476598d31a4 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Fri, 27 Mar 2026 13:49:26 +0300 Subject: [PATCH] chore: don't benchmark binary crate; centralize assembly helpers Signed-off-by: NotAShelf Change-Id: Ia24cb1647df93034937a8fcd6cad895c6a6a6964 --- Cargo.lock | 9 +- crates/asm/src/lib.rs | 210 +++++++++++++++++++++++++++++++++++---- crates/lib/Cargo.toml | 3 +- crates/lib/src/lib.rs | 2 + crates/lib/src/uptime.rs | 75 +------------- microfetch/Cargo.toml | 6 +- microfetch/src/main.rs | 1 - 7 files changed, 203 insertions(+), 103 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 64584ba..1b9a1e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -417,9 +417,9 @@ checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hotpath" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51462b35dc551217e1d1dd3f8c5eebbb5df7103370b1e55d24c19a3411d290f7" +checksum = "2fde50be006a0fe95cc2fd6d25d884aa6932218e4055d7df2fa0d95c386acf8d" dependencies = [ "arc-swap", "cfg-if", @@ -444,9 +444,9 @@ dependencies = [ [[package]] name = "hotpath-macros" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f022365c90cc04455f17a2b9ba5fe0e661cde1ab27434d382f1f8693fe124e7d" +checksum = "dd884cee056e269e41e1127549458e1c4e309f31897ebbc1416982a74d40a5b5" dependencies = [ "proc-macro2", "quote", @@ -550,6 +550,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" name = "microfetch" version = "0.4.13" dependencies = [ + "hotpath", "microfetch-lib", ] diff --git a/crates/asm/src/lib.rs b/crates/asm/src/lib.rs index 1a3cd87..a58fb96 100644 --- a/crates/asm/src/lib.rs +++ b/crates/asm/src/lib.rs @@ -5,11 +5,20 @@ //! What do you mean I wasted two whole hours to make the program only 100µs //! faster? //! -//! Supports `x86_64` and `aarch64` architectures. Riscv support will be -//! implemented when and ONLY WHEN I can be bothered to work on it. +//! Supports `x86_64`, `aarch64`, and `riscv64` architectures. use std::io; +// Ensure we're compiling for a supported architecture. +#[cfg(not(any( + target_arch = "x86_64", + target_arch = "aarch64", + target_arch = "riscv64" +)))] +compile_error!( + "Unsupported architecture: only x86_64, aarch64, and riscv64 are supported" +); + /// Direct syscall to open a file /// /// # Returns @@ -62,9 +71,24 @@ pub unsafe fn sys_open(path: *const u8, flags: i32) -> i32 { fd as i32 } } - #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] - { - compile_error!("Unsupported architecture for inline assembly syscalls"); + #[cfg(target_arch = "riscv64")] + unsafe { + let fd: i64; + std::arch::asm!( + "ecall", + in("a7") 56i64, // SYS_openat + in("a0") -100i32, // AT_FDCWD + in("a1") path, + in("a2") flags, + in("a3") 0i32, // mode + lateout("a0") fd, + options(nostack) + ); + + #[allow(clippy::cast_possible_truncation)] + { + fd as i32 + } } } @@ -122,9 +146,23 @@ pub unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize { } } - #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] - { - compile_error!("Unsupported architecture for inline assembly syscalls"); + #[cfg(target_arch = "riscv64")] + unsafe { + let ret: i64; + std::arch::asm!( + "ecall", + in("a7") 63i64, // SYS_read + in("a0") fd, + in("a1") buf, + in("a2") count, + lateout("a0") ret, + options(nostack) + ); + + #[allow(clippy::cast_possible_truncation)] + { + ret as isize + } } } @@ -183,9 +221,23 @@ pub unsafe fn sys_write(fd: i32, buf: *const u8, count: usize) -> isize { } } - #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] - { - compile_error!("Unsupported architecture for inline assembly syscalls"); + #[cfg(target_arch = "riscv64")] + unsafe { + let ret: i64; + std::arch::asm!( + "ecall", + in("a7") 64i64, // SYS_write + in("a0") fd, + in("a1") buf, + in("a2") count, + lateout("a0") ret, + options(nostack) + ); + + #[allow(clippy::cast_possible_truncation)] + { + ret as isize + } } } @@ -231,9 +283,20 @@ pub unsafe fn sys_close(fd: i32) -> i32 { } } - #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] - { - compile_error!("Unsupported architecture for inline assembly syscalls"); + #[cfg(target_arch = "riscv64")] + unsafe { + let ret: i64; + std::arch::asm!( + "ecall", + in("a7") 57i64, // SYS_close + in("a0") fd, + lateout("a0") ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } } } @@ -300,9 +363,21 @@ pub unsafe fn sys_uname(buf: *mut UtsNameBuf) -> i32 { } } - #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] - { - compile_error!("Unsupported architecture for inline assembly syscalls"); + #[cfg(target_arch = "riscv64")] + unsafe { + let ret: i64; + std::arch::asm!( + "ecall", + in("a7") 160i64, // SYS_uname + in("a0") buf, + lateout("a0") ret, + options(nostack) + ); + + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } } } @@ -381,9 +456,22 @@ pub unsafe fn sys_statfs(path: *const u8, buf: *mut StatfsBuf) -> i32 { } } - #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] - { - compile_error!("Unsupported architecture for inline assembly syscalls"); + #[cfg(target_arch = "riscv64")] + unsafe { + let ret: i64; + std::arch::asm!( + "ecall", + in("a7") 43i64, // SYS_statfs + in("a0") path, + in("a1") buf, + lateout("a0") ret, + options(nostack) + ); + + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } } } @@ -427,3 +515,85 @@ pub fn read_file_fast(path: &str, buffer: &mut [u8]) -> io::Result { } } } + +/// Raw buffer for the `sysinfo(2)` syscall. +/// +/// In the Linux ABI `uptime` is a `long` at offset 0. The remaining fields are +/// not needed, but are declared to give the struct its correct size (112 bytes +/// on 64-bit Linux). +/// +/// The layout matches the kernel's `struct sysinfo` *exactly*: +/// `mem_unit` ends at offset 108, then 4 bytes of implicit padding to 112. +#[repr(C)] +pub struct SysInfo { + pub uptime: i64, + pub loads: [u64; 3], + pub totalram: u64, + pub freeram: u64, + pub sharedram: u64, + pub bufferram: u64, + pub totalswap: u64, + pub freeswap: u64, + pub procs: u16, + _pad: u16, + _pad2: u32, /* alignment padding to reach 8-byte boundary for + * totalhigh */ + pub totalhigh: u64, + pub freehigh: u64, + pub mem_unit: u32, + // 4 bytes implicit trailing padding to reach 112 bytes total; no field + // needed +} + +/// Direct `sysinfo(2)` syscall +/// +/// # Returns +/// +/// 0 on success, negative errno on error +/// +/// # Safety +/// +/// The caller must ensure that `info` points to a valid `SysInfo` buffer. +#[inline] +pub unsafe fn sys_sysinfo(info: *mut SysInfo) -> i64 { + #[cfg(target_arch = "x86_64")] + unsafe { + let ret: i64; + std::arch::asm!( + "syscall", + in("rax") 99_i64, // __NR_sysinfo + in("rdi") info, + out("rcx") _, + out("r11") _, + lateout("rax") ret, + options(nostack) + ); + ret + } + + #[cfg(target_arch = "aarch64")] + unsafe { + let ret: i64; + std::arch::asm!( + "svc #0", + in("x8") 179_i64, // __NR_sysinfo + in("x0") info, + lateout("x0") ret, + options(nostack) + ); + ret + } + + #[cfg(target_arch = "riscv64")] + unsafe { + let ret: i64; + std::arch::asm!( + "ecall", + in("a7") 179_i64, // __NR_sysinfo + in("a0") info, + lateout("a0") ret, + options(nostack) + ); + ret + } +} diff --git a/crates/lib/Cargo.toml b/crates/lib/Cargo.toml index 25e8e67..290932b 100644 --- a/crates/lib/Cargo.toml +++ b/crates/lib/Cargo.toml @@ -9,13 +9,12 @@ license.workspace = true publish = false [dependencies] -hotpath = { optional = true, version = "0.13.0" } +hotpath = { optional = true, version = "0.14.0" } microfetch-asm.workspace = true [features] hotpath = [ "dep:hotpath", "hotpath/hotpath" ] hotpath-alloc = [ "hotpath/hotpath-alloc" ] -hotpath-off = [ "hotpath/hotpath-off" ] [dev-dependencies] criterion.workspace = true diff --git a/crates/lib/src/lib.rs b/crates/lib/src/lib.rs index 7a2c9c6..28147f7 100644 --- a/crates/lib/src/lib.rs +++ b/crates/lib/src/lib.rs @@ -13,12 +13,14 @@ use std::{ pub use microfetch_asm as syscall; pub use microfetch_asm::{ StatfsBuf, + SysInfo, UtsNameBuf, read_file_fast, sys_close, sys_open, sys_read, sys_statfs, + sys_sysinfo, sys_uname, sys_write, }; diff --git a/crates/lib/src/uptime.rs b/crates/lib/src/uptime.rs index c6c4b26..b529f53 100644 --- a/crates/lib/src/uptime.rs +++ b/crates/lib/src/uptime.rs @@ -1,5 +1,7 @@ use std::{io, mem::MaybeUninit}; +use crate::syscall::sys_sysinfo; + /// Faster integer to string conversion without the formatting overhead. #[inline] fn itoa(mut n: u64, buf: &mut [u8]) -> &str { @@ -17,79 +19,6 @@ fn itoa(mut n: u64, buf: &mut [u8]) -> &str { unsafe { std::str::from_utf8_unchecked(&buf[i..]) } } -/// Raw buffer for the `sysinfo(2)` syscall. -/// -/// In the Linux ABI `uptime` is a `long` at offset 0. The remaining fields are -/// not needed, but are declared to give the struct its correct size (112 bytes -/// on 64-bit Linux). -/// -/// The layout matches the kernel's `struct sysinfo` *exactly*: -/// `mem_unit` ends at offset 108, then 4 bytes of implicit padding to 112. -#[repr(C)] -struct SysInfo { - uptime: i64, - loads: [u64; 3], - totalram: u64, - freeram: u64, - sharedram: u64, - bufferram: u64, - totalswap: u64, - freeswap: u64, - procs: u16, - _pad: u16, - _pad2: u32, // alignment padding to reach 8-byte boundary for totalhigh - totalhigh: u64, - freehigh: u64, - mem_unit: u32, - // 4 bytes implicit trailing padding to reach 112 bytes total; no field - // needed -} - -/// Direct `sysinfo(2)` syscall using inline assembly -/// -/// # Safety -/// -/// The caller must ensure the sysinfo pointer is valid. -#[inline] -unsafe fn sys_sysinfo(info: *mut SysInfo) -> i64 { - #[cfg(target_arch = "x86_64")] - { - let ret: i64; - unsafe { - std::arch::asm!( - "syscall", - in("rax") 99_i64, // __NR_sysinfo - in("rdi") info, - out("rcx") _, - out("r11") _, - lateout("rax") ret, - options(nostack) - ); - } - ret - } - - #[cfg(target_arch = "aarch64")] - { - let ret: i64; - unsafe { - std::arch::asm!( - "svc #0", - in("x8") 179_i64, // __NR_sysinfo - in("x0") info, - lateout("x0") ret, - options(nostack) - ); - } - ret - } - - #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] - { - compile_error!("Unsupported architecture for inline assembly syscalls"); - } -} - /// Gets the current system uptime. /// /// # Errors diff --git a/microfetch/Cargo.toml b/microfetch/Cargo.toml index ce1cbfb..4680aa8 100644 --- a/microfetch/Cargo.toml +++ b/microfetch/Cargo.toml @@ -11,12 +11,12 @@ repository = "https://github.com/notashelf/microfetch" publish = false [dependencies] +hotpath = { optional = true, version = "0.14.0" } microfetch-lib.workspace = true [features] -hotpath = [ "microfetch-lib/hotpath" ] -hotpath-alloc = [ "microfetch-lib/hotpath-alloc" ] -hotpath-off = [ "microfetch-lib/hotpath-off" ] +hotpath = [ "dep:hotpath" ] +hotpath-alloc = [ "hotpath/hotpath-alloc" ] [lints] workspace = true diff --git a/microfetch/src/main.rs b/microfetch/src/main.rs index 36e19da..8f22041 100644 --- a/microfetch/src/main.rs +++ b/microfetch/src/main.rs @@ -1,4 +1,3 @@ -#[cfg_attr(feature = "hotpath", hotpath::main)] fn main() -> Result<(), Box> { microfetch_lib::run() }