diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 99668ae..2eb3a1f 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -16,10 +16,47 @@ jobs: strategy: fail-fast: false matrix: - target: - - x86_64-unknown-linux-gnu - - aarch64-unknown-linux-gnu - - riscv64gc-unknown-linux-gnu + include: + - target: x86_64-unknown-linux-gnu + toolchain: stable + - target: aarch64-unknown-linux-gnu + toolchain: stable + - target: riscv64gc-unknown-linux-gnu + toolchain: stable + - target: loongarch64-unknown-linux-gnu + toolchain: stable + - target: s390x-unknown-linux-gnu + toolchain: stable + # powerpc64 uses experimental asm features + - target: powerpc64le-unknown-linux-gnu + toolchain: nightly + - target: powerpc64-unknown-linux-gnu + toolchain: nightly + - target: armv7-unknown-linux-gnueabihf + toolchain: stable + # riscv32 is tier-3 + - target: riscv32gc-unknown-linux-gnu + toolchain: nightly + components: rust-src + build_std: true + # sparc64 uses experimental asm features + - target: sparc64-unknown-linux-gnu + toolchain: nightly + # mips64 is tier-3 and uses experimental asm features + - target: mips64-unknown-linux-gnuabi64 + toolchain: nightly + components: rust-src + build_std: true + - target: i686-unknown-linux-gnu + toolchain: stable + # powerpc uses experimental asm features + - target: powerpc-unknown-linux-gnu + toolchain: nightly + # mips is tier-3 and uses experimental asm features + - target: mips-unknown-linux-gnu + toolchain: nightly + components: rust-src + build_std: true steps: - name: "Checkout" @@ -28,9 +65,14 @@ jobs: - name: "Setup Rust toolchain" uses: actions-rust-lang/setup-rust-toolchain@v1 with: - target: ${{ matrix.target }} + toolchain: ${{ matrix.toolchain }} + components: ${{ matrix.components || '' }} rustflags: "" + - name: "Add Rust target" + if: ${{ !matrix.build_std }} + run: rustup target add ${{ matrix.target }} + - name: "Make Mold the default linker" uses: rui314/setup-mold@v1 @@ -40,7 +82,9 @@ jobs: target: ${{ matrix.target }} - name: "Build" - run: cargo build --verbose + run: cargo build --verbose --target ${{ matrix.target }} ${{ matrix.build_std && '-Z build-std=core,alloc,panic_abort' || '' }} ${{ matrix.extra_args || '' }} + # tier-3 targets have no prebuilt std and tests are arch-agnostic, so + # run them against the host toolchain instead of the cross target. - name: "Run tests" - run: cargo test --workspace --exclude microfetch --verbose + run: cargo test --workspace --exclude microfetch --verbose ${{ matrix.build_std && '--target x86_64-unknown-linux-gnu' || '' }} diff --git a/Cargo.lock b/Cargo.lock index 465a747..27ba138 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,56 +26,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" -[[package]] -name = "anstream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - [[package]] name = "anstyle" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" -[[package]] -name = "anstyle-parse" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys", -] - [[package]] name = "arc-swap" version = "1.9.1" @@ -177,7 +133,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", - "clap_derive", ] [[package]] @@ -186,22 +141,8 @@ version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ - "anstream", "anstyle", "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", ] [[package]] @@ -210,21 +151,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" -[[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - -[[package]] -name = "colored" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" -dependencies = [ - "windows-sys", -] - [[package]] name = "criterion" version = "0.8.2" @@ -343,16 +269,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" -[[package]] -name = "eyre" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" -dependencies = [ - "indenter", - "once_cell", -] - [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -434,12 +350,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - [[package]] name = "hermit-abi" version = "0.5.2" @@ -448,21 +358,18 @@ checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hotpath" -version = "0.14.1" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d9982fcb4356a5260502f0e646411ec1feb2962cc98c230777104a8d1c5ed3" +checksum = "bff002d5c53fa1c6891f32156b9451d16654bc8a761894d7660b25c0a332d517" dependencies = [ "arc-swap", "cfg-if", - "clap", - "colored", "crossbeam-channel", - "eyre", "futures-util", "hdrhistogram", "hotpath-macros", + "hotpath-meta", "libc", - "mach2", "pin-project-lite", "prettytable-rs", "quanta", @@ -475,27 +382,36 @@ dependencies = [ [[package]] name = "hotpath-macros" -version = "0.14.1" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4398eddb78466298f1ddcc739abbbd8a942e541d1972c7590bd83de364e626e0" +checksum = "9e2f4ac4534511584b7082657e133dcf3d8727b2f456a6b2a2c3eb02b82c1277" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "hotpath-macros-meta" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a87070853e9402ec79184f8d8d930d7eb86cd274aecdcf973f73b6f40271b0" + +[[package]] +name = "hotpath-meta" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31cfef2b9d280ad754c23b40b50cc74676489597f3cebfe0c180389e08a53ed" +dependencies = [ + "hotpath-macros-meta", +] + [[package]] name = "httpdate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "indenter" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" - [[package]] name = "is-terminal" version = "0.4.17" @@ -507,12 +423,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - [[package]] name = "itertools" version = "0.13.0" @@ -565,12 +475,6 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" -[[package]] -name = "mach2" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" - [[package]] name = "memchr" version = "2.8.0" @@ -579,7 +483,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "microfetch" -version = "1.0.0" +version = "1.1.0" dependencies = [ "hotpath", "microfetch-alloc", @@ -589,15 +493,15 @@ dependencies = [ [[package]] name = "microfetch-alloc" -version = "1.0.0" +version = "1.1.0" [[package]] name = "microfetch-asm" -version = "1.0.0" +version = "1.1.0" [[package]] name = "microfetch-bench" -version = "1.0.0" +version = "1.1.0" dependencies = [ "criterion", "criterion-cycles-per-byte", @@ -606,7 +510,7 @@ dependencies = [ [[package]] name = "microfetch-lib" -version = "1.0.0" +version = "1.1.0" dependencies = [ "hotpath", "microfetch-asm", @@ -627,12 +531,6 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - [[package]] name = "oorandom" version = "11.1.5" @@ -868,12 +766,6 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - [[package]] name = "syn" version = "2.0.117" @@ -959,12 +851,6 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - [[package]] name = "walkdir" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index 661cb8f..391e0dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,8 +6,8 @@ resolver = "3" authors = [ "NotAShelf " ] edition = "2024" license = "GPL-3.0" -rust-version = "1.92.0" -version = "1.0.0" +rust-version = "1.95.0" +version = "1.1.0" [workspace.dependencies] microfetch-alloc = { path = "./crates/alloc" } diff --git a/crates/asm/src/aarch64.rs b/crates/asm/src/aarch64.rs new file mode 100644 index 0000000..6997f62 --- /dev/null +++ b/crates/asm/src/aarch64.rs @@ -0,0 +1,164 @@ +//! Syscall implementations for `aarch64`. + +use super::{StatfsBuf, SysInfo, UtsNameBuf}; + +pub(super) unsafe fn sys_open(path: *const u8, flags: i32) -> i32 { + unsafe { + let fd: i64; + core::arch::asm!( + "svc #0", + in("x8") 56i64, // SYS_openat + in("x0") -100i32, // AT_FDCWD + in("x1") path, + in("x2") flags, + in("x3") 0i32, // mode + lateout("x0") fd, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + fd as i32 + } + } +} + +pub(super) unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize { + unsafe { + let ret: i64; + core::arch::asm!( + "svc #0", + in("x8") 63i64, // SYS_read + in("x0") fd, + in("x1") buf, + in("x2") count, + lateout("x0") ret, + options(nostack) + ); + + #[allow(clippy::cast_possible_truncation)] + { + ret as isize + } + } +} + +pub(super) unsafe fn sys_write(fd: i32, buf: *const u8, count: usize) -> isize { + unsafe { + let ret: i64; + core::arch::asm!( + "svc #0", + in("x8") 64i64, // SYS_write + in("x0") fd, + in("x1") buf, + in("x2") count, + lateout("x0") ret, + options(nostack) + ); + + #[allow(clippy::cast_possible_truncation)] + { + ret as isize + } + } +} + +pub(super) unsafe fn sys_close(fd: i32) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "svc #0", + in("x8") 57i64, // SYS_close + in("x0") fd, + lateout("x0") ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_uname(buf: *mut UtsNameBuf) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "svc #0", + in("x8") 160i64, // SYS_uname + in("x0") buf, + lateout("x0") ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_statfs(path: *const u8, buf: *mut StatfsBuf) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "svc #0", + in("x8") 43i64, // SYS_statfs + in("x0") path, + in("x1") buf, + lateout("x0") ret, + options(nostack) + ); + + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_sysinfo(info: *mut SysInfo) -> i64 { + unsafe { + let ret: i64; + core::arch::asm!( + "svc #0", + in("x8") 179_i64, // __NR_sysinfo + in("x0") info, + lateout("x0") ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_sched_getaffinity( + pid: i32, + mask_size: usize, + mask: *mut u8, +) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "svc #0", + in("x8") 123i64, // __NR_sched_getaffinity + in("x0") pid, + in("x1") mask_size, + in("x2") mask, + lateout("x0") ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_exit(code: i32) -> ! { + unsafe { + core::arch::asm!( + "svc #0", + in("x8") 93i64, // SYS_exit + in("x0") code, + options(noreturn, nostack) + ); + } +} diff --git a/crates/asm/src/arm.rs b/crates/asm/src/arm.rs new file mode 100644 index 0000000..892b012 --- /dev/null +++ b/crates/asm/src/arm.rs @@ -0,0 +1,140 @@ +//! Syscall implementations for `arm`. + +use super::{StatfsBuf, SysInfo, UtsNameBuf}; + +pub(super) unsafe fn sys_open(path: *const u8, flags: i32) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "svc #0", + in("r7") 5i32, // SYS_open + in("r0") path, + in("r1") flags, + in("r2") 0i32, // mode + lateout("r0") ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize { + unsafe { + let ret: i32; + core::arch::asm!( + "svc #0", + in("r7") 3i32, // SYS_read + in("r0") fd, + in("r1") buf, + in("r2") count, + lateout("r0") ret, + options(nostack) + ); + ret as isize + } +} + +pub(super) unsafe fn sys_write(fd: i32, buf: *const u8, count: usize) -> isize { + unsafe { + let ret: i32; + core::arch::asm!( + "svc #0", + in("r7") 4i32, // SYS_write + in("r0") fd, + in("r1") buf, + in("r2") count, + lateout("r0") ret, + options(nostack) + ); + ret as isize + } +} + +pub(super) unsafe fn sys_close(fd: i32) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "svc #0", + in("r7") 6i32, // SYS_close + in("r0") fd, + lateout("r0") ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_uname(buf: *mut UtsNameBuf) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "svc #0", + in("r7") 122i32, // SYS_newuname + in("r0") buf, + lateout("r0") ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_statfs(path: *const u8, buf: *mut StatfsBuf) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "svc #0", + in("r7") 266i32, // SYS_statfs64 + in("r0") path, + in("r1") core::mem::size_of::(), + in("r2") buf, + lateout("r0") ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_sysinfo(info: *mut SysInfo) -> i64 { + unsafe { + let ret: i32; + core::arch::asm!( + "svc #0", + in("r7") 116_i32, // __NR_sysinfo + in("r0") info, + lateout("r0") ret, + options(nostack) + ); + i64::from(ret) + } +} + +pub(super) unsafe fn sys_sched_getaffinity( + pid: i32, + mask_size: usize, + mask: *mut u8, +) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "svc #0", + in("r7") 242i32, // __NR_sched_getaffinity + in("r0") pid, + in("r1") mask_size, + in("r2") mask, + lateout("r0") ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_exit(code: i32) -> ! { + unsafe { + core::arch::asm!( + "svc #0", + in("r7") 1i32, // SYS_exit + in("r0") code, + options(noreturn, nostack) + ); + } +} diff --git a/crates/asm/src/lib.rs b/crates/asm/src/lib.rs index 19abc0e..fff8d7f 100644 --- a/crates/asm/src/lib.rs +++ b/crates/asm/src/lib.rs @@ -5,19 +5,39 @@ //! What do you mean I wasted two whole hours to make the program only 100µs //! faster? //! -//! Supports `x86_64`, `aarch64`, and `riscv64` architectures. +//! Supports `x86_64`, `aarch64`, `riscv64`, `loongarch64`, `s390x`, +//! `powerpc64`, `arm` (armv7), `riscv32`, `sparc64`, `mips64`, `x86` (i686), +//! `powerpc` (ppc32), `sparc` (sparc32), and `mips` (O32) architectures. #![no_std] +#![cfg_attr( + any( + target_arch = "sparc64", + target_arch = "sparc", + target_arch = "mips64", + target_arch = "mips" + ), + feature(asm_experimental_arch) +)] -// 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" -); +// Per-arch syscall implementations live in their own module files. +core::cfg_select! { + target_arch = "x86_64" => { #[path = "x86_64.rs" ] mod arch; } + target_arch = "aarch64" => { #[path = "aarch64.rs" ] mod arch; } + target_arch = "riscv64" => { #[path = "riscv64.rs" ] mod arch; } + target_arch = "loongarch64" => { #[path = "loongarch64.rs"] mod arch; } + target_arch = "s390x" => { #[path = "s390x.rs" ] mod arch; } + target_arch = "powerpc64" => { #[path = "powerpc64.rs" ] mod arch; } + target_arch = "arm" => { #[path = "arm.rs" ] mod arch; } + target_arch = "riscv32" => { #[path = "riscv32.rs" ] mod arch; } + target_arch = "sparc64" => { #[path = "sparc64.rs" ] mod arch; } + target_arch = "mips64" => { #[path = "mips64.rs" ] mod arch; } + target_arch = "x86" => { #[path = "x86.rs" ] mod arch; } + target_arch = "powerpc" => { #[path = "powerpc.rs" ] mod arch; } + target_arch = "sparc" => { #[path = "sparc.rs" ] mod arch; } + target_arch = "mips" => { #[path = "mips.rs" ] mod arch; } + _ => { compile_error!("Unsupported architecture"); } +} /// Copies `n` bytes from `src` to `dest`. /// @@ -165,62 +185,7 @@ unsafe extern "C" { #[inline] #[must_use] pub unsafe fn sys_open(path: *const u8, flags: i32) -> i32 { - #[cfg(target_arch = "x86_64")] - unsafe { - let fd: i64; - core::arch::asm!( - "syscall", - in("rax") 2i64, // SYS_open - in("rdi") path, - in("rsi") flags, - in("rdx") 0i32, // mode (not used for reading) - lateout("rax") fd, - lateout("rcx") _, - lateout("r11") _, - options(nostack) - ); - #[allow(clippy::cast_possible_truncation)] - { - fd as i32 - } - } - #[cfg(target_arch = "aarch64")] - unsafe { - let fd: i64; - core::arch::asm!( - "svc #0", - in("x8") 56i64, // SYS_openat - in("x0") -100i32, // AT_FDCWD - in("x1") path, - in("x2") flags, - in("x3") 0i32, // mode - lateout("x0") fd, - options(nostack) - ); - #[allow(clippy::cast_possible_truncation)] - { - fd as i32 - } - } - #[cfg(target_arch = "riscv64")] - unsafe { - let fd: i64; - core::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 - } - } + unsafe { arch::sys_open(path, flags) } } /// Direct syscall to read from a file descriptor @@ -237,64 +202,7 @@ pub unsafe fn sys_open(path: *const u8, flags: i32) -> i32 { /// - `fd` is a valid open file descriptor #[inline] pub unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize { - #[cfg(target_arch = "x86_64")] - unsafe { - let ret: i64; - core::arch::asm!( - "syscall", - in("rax") 0i64, // SYS_read - in("rdi") fd, - in("rsi") buf, - in("rdx") count, - lateout("rax") ret, - lateout("rcx") _, - lateout("r11") _, - options(nostack) - ); - - #[allow(clippy::cast_possible_truncation)] - { - ret as isize - } - } - - #[cfg(target_arch = "aarch64")] - unsafe { - let ret: i64; - core::arch::asm!( - "svc #0", - in("x8") 63i64, // SYS_read - in("x0") fd, - in("x1") buf, - in("x2") count, - lateout("x0") ret, - options(nostack) - ); - - #[allow(clippy::cast_possible_truncation)] - { - ret as isize - } - } - - #[cfg(target_arch = "riscv64")] - unsafe { - let ret: i64; - core::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 - } - } + unsafe { arch::sys_read(fd, buf, count) } } /// Direct syscall to write to a file descriptor @@ -312,64 +220,7 @@ pub unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize { #[inline] #[must_use] pub unsafe fn sys_write(fd: i32, buf: *const u8, count: usize) -> isize { - #[cfg(target_arch = "x86_64")] - unsafe { - let ret: i64; - core::arch::asm!( - "syscall", - in("rax") 1i64, // SYS_write - in("rdi") fd, - in("rsi") buf, - in("rdx") count, - lateout("rax") ret, - lateout("rcx") _, - lateout("r11") _, - options(nostack) - ); - - #[allow(clippy::cast_possible_truncation)] - { - ret as isize - } - } - - #[cfg(target_arch = "aarch64")] - unsafe { - let ret: i64; - core::arch::asm!( - "svc #0", - in("x8") 64i64, // SYS_write - in("x0") fd, - in("x1") buf, - in("x2") count, - lateout("x0") ret, - options(nostack) - ); - - #[allow(clippy::cast_possible_truncation)] - { - ret as isize - } - } - - #[cfg(target_arch = "riscv64")] - unsafe { - let ret: i64; - core::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 - } - } + unsafe { arch::sys_write(fd, buf, count) } } /// Direct syscall to close a file descriptor @@ -380,55 +231,7 @@ pub unsafe fn sys_write(fd: i32, buf: *const u8, count: usize) -> isize { #[inline] #[must_use] pub unsafe fn sys_close(fd: i32) -> i32 { - #[cfg(target_arch = "x86_64")] - unsafe { - let ret: i64; - core::arch::asm!( - "syscall", - in("rax") 3i64, // SYS_close - in("rdi") fd, - lateout("rax") ret, - lateout("rcx") _, - lateout("r11") _, - options(nostack) - ); - #[allow(clippy::cast_possible_truncation)] - { - ret as i32 - } - } - - #[cfg(target_arch = "aarch64")] - unsafe { - let ret: i64; - core::arch::asm!( - "svc #0", - in("x8") 57i64, // SYS_close - in("x0") fd, - lateout("x0") ret, - options(nostack) - ); - #[allow(clippy::cast_possible_truncation)] - { - ret as i32 - } - } - - #[cfg(target_arch = "riscv64")] - unsafe { - let ret: i64; - core::arch::asm!( - "ecall", - in("a7") 57i64, // SYS_close - in("a0") fd, - lateout("a0") ret, - options(nostack) - ); - #[allow(clippy::cast_possible_truncation)] - { - ret as i32 - } - } + unsafe { arch::sys_close(fd) } } /// Raw buffer for the `uname(2)` syscall. @@ -459,57 +262,7 @@ pub struct UtsNameBuf { #[inline] #[allow(dead_code)] pub unsafe fn sys_uname(buf: *mut UtsNameBuf) -> i32 { - #[cfg(target_arch = "x86_64")] - unsafe { - let ret: i64; - core::arch::asm!( - "syscall", - in("rax") 63i64, // SYS_uname - in("rdi") buf, - lateout("rax") ret, - lateout("rcx") _, - lateout("r11") _, - options(nostack) - ); - - #[allow(clippy::cast_possible_truncation)] - { - ret as i32 - } - } - - #[cfg(target_arch = "aarch64")] - unsafe { - let ret: i64; - core::arch::asm!( - "svc #0", - in("x8") 160i64, // SYS_uname - in("x0") buf, - lateout("x0") ret, - options(nostack) - ); - #[allow(clippy::cast_possible_truncation)] - { - ret as i32 - } - } - - #[cfg(target_arch = "riscv64")] - unsafe { - let ret: i64; - core::arch::asm!( - "ecall", - in("a7") 160i64, // SYS_uname - in("a0") buf, - lateout("a0") ret, - options(nostack) - ); - - #[allow(clippy::cast_possible_truncation)] - { - ret as i32 - } - } + unsafe { arch::sys_uname(buf) } } /// Raw buffer for the `statfs(2)` syscall. @@ -518,6 +271,16 @@ pub unsafe fn sys_uname(buf: *mut UtsNameBuf) -> i32 { /// offsets on both architectures. Only the fields needed for disk usage are /// declared; the remainder of the 120-byte struct is covered by `_pad`. #[repr(C)] +#[cfg(not(any( + target_arch = "s390x", + target_arch = "arm", + target_arch = "riscv32", + target_arch = "x86", + target_arch = "powerpc", + target_arch = "sparc", + target_arch = "mips64", + target_arch = "mips" +)))] pub struct StatfsBuf { pub f_type: i64, pub f_bsize: i64, @@ -535,6 +298,96 @@ pub struct StatfsBuf { pub _pad: [i64; 4], } +/// on s390x `f_type` and `f_bsize` are 32-bit. +#[repr(C)] +#[cfg(target_arch = "s390x")] +pub struct StatfsBuf { + pub f_type: u32, + pub f_bsize: u32, + pub f_blocks: u64, + pub f_bfree: u64, + pub f_bavail: u64, + pub f_files: u64, + pub f_ffree: u64, + pub f_fsid: [i32; 2], + pub f_namelen: u32, + pub f_frsize: u32, + pub f_flags: u32, + + #[allow(clippy::pub_underscore_fields, reason = "This is not a public API")] + pub _pad: [u32; 5], +} + +/// on armv7 `statfs64(2)` has 32-bit word fields; see +/// https://github.com/torvalds/linux/blob/v6.19/include/uapi/asm-generic/statfs.h +#[repr(C)] +#[cfg(any( + target_arch = "arm", + target_arch = "riscv32", + target_arch = "x86", + target_arch = "powerpc", + target_arch = "sparc" +))] +pub struct StatfsBuf { + pub f_type: u32, + pub f_bsize: u32, + pub f_blocks: u64, + pub f_bfree: u64, + pub f_bavail: u64, + pub f_files: u64, + pub f_ffree: u64, + pub f_fsid: [i32; 2], + pub f_namelen: u32, + pub f_frsize: u32, + pub f_flags: u32, + + #[allow(clippy::pub_underscore_fields, reason = "This is not a public API")] + pub _pad: [u32; 4], +} + +/// mips (O32) uses `compat_statfs64`, same reordering with 32-bit words; see +/// https://github.com/torvalds/linux/blob/v6.19/arch/mips/include/uapi/asm/statfs.h +#[repr(C)] +#[cfg(target_arch = "mips")] +pub struct StatfsBuf { + pub f_type: u32, + pub f_bsize: u32, + pub f_frsize: u32, + _pad: u32, + pub f_blocks: u64, + pub f_bfree: u64, + pub f_files: u64, + pub f_ffree: u64, + pub f_bavail: u64, + pub f_fsid: [i32; 2], + pub f_namelen: u32, + pub f_flags: u32, + + #[allow(clippy::pub_underscore_fields, reason = "This is not a public API")] + pub _pad2: [u32; 5], +} + +/// mips reorders fields; see +/// https://github.com/torvalds/linux/blob/v6.19/arch/mips/include/uapi/asm/statfs.h +#[repr(C)] +#[cfg(target_arch = "mips64")] +pub struct StatfsBuf { + pub f_type: i64, + pub f_bsize: i64, + pub f_frsize: i64, + pub f_blocks: u64, + pub f_bfree: u64, + pub f_files: u64, + pub f_ffree: u64, + pub f_bavail: u64, + pub f_fsid: [i32; 2], + pub f_namelen: i64, + pub f_flags: i64, + + #[allow(clippy::pub_underscore_fields, reason = "This is not a public API")] + pub _pad: [i64; 5], +} + /// Direct `statfs(2)` syscall /// /// # Returns @@ -549,61 +402,7 @@ pub struct StatfsBuf { /// - `buf` points to a valid `StatfsBuf` #[inline] pub unsafe fn sys_statfs(path: *const u8, buf: *mut StatfsBuf) -> i32 { - #[cfg(target_arch = "x86_64")] - unsafe { - let ret: i64; - core::arch::asm!( - "syscall", - in("rax") 137i64, // SYS_statfs - in("rdi") path, - in("rsi") buf, - lateout("rax") ret, - lateout("rcx") _, - lateout("r11") _, - options(nostack) - ); - - #[allow(clippy::cast_possible_truncation)] - { - ret as i32 - } - } - - #[cfg(target_arch = "aarch64")] - unsafe { - let ret: i64; - core::arch::asm!( - "svc #0", - in("x8") 43i64, // SYS_statfs - in("x0") path, - in("x1") buf, - lateout("x0") ret, - options(nostack) - ); - - #[allow(clippy::cast_possible_truncation)] - { - ret as i32 - } - } - - #[cfg(target_arch = "riscv64")] - unsafe { - let ret: i64; - core::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 - } - } + unsafe { arch::sys_statfs(path, buf) } } /// Read entire file using direct syscalls. This avoids libc overhead and can be @@ -658,6 +457,14 @@ pub fn read_file_fast(path: &str, buffer: &mut [u8]) -> Result { /// 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)] +#[cfg(not(any( + target_arch = "arm", + target_arch = "riscv32", + target_arch = "x86", + target_arch = "powerpc", + target_arch = "sparc", + target_arch = "mips" +)))] pub struct SysInfo { pub uptime: i64, pub loads: [u64; 3], @@ -678,6 +485,35 @@ pub struct SysInfo { // needed } +/// on armv7 `__kernel_long_t` is 4 bytes; see +/// https://github.com/torvalds/linux/blob/v6.19/include/uapi/linux/sysinfo.h +#[repr(C)] +#[cfg(any( + target_arch = "arm", + target_arch = "riscv32", + target_arch = "x86", + target_arch = "powerpc", + target_arch = "sparc", + target_arch = "mips" +))] +pub struct SysInfo { + pub uptime: i32, + pub loads: [u32; 3], + pub totalram: u32, + pub freeram: u32, + pub sharedram: u32, + pub bufferram: u32, + pub totalswap: u32, + pub freeswap: u32, + pub procs: u16, + _pad: u16, + pub totalhigh: u32, + pub freehigh: u32, + pub mem_unit: u32, + #[allow(clippy::pub_underscore_fields, reason = "This is not a public API")] + pub _f: [u8; 8], +} + /// Direct `sysinfo(2)` syscall /// /// # Returns @@ -689,46 +525,27 @@ pub struct SysInfo { /// 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; - core::arch::asm!( - "syscall", - in("rax") 99_i64, // __NR_sysinfo - in("rdi") info, - out("rcx") _, - out("r11") _, - lateout("rax") ret, - options(nostack) - ); - ret - } + unsafe { arch::sys_sysinfo(info) } +} - #[cfg(target_arch = "aarch64")] - unsafe { - let ret: i64; - core::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; - core::arch::asm!( - "ecall", - in("a7") 179_i64, // __NR_sysinfo - in("a0") info, - lateout("a0") ret, - options(nostack) - ); - ret - } +/// Direct `sched_getaffinity(2)` syscall +/// +/// # Returns +/// +/// On success, the number of bytes written to the mask buffer (always a +/// multiple of `sizeof(long)`). On error, a negative errno. +/// +/// # Safety +/// +/// The caller must ensure that `mask` points to a buffer of at least +/// `mask_size` bytes. +#[inline] +pub unsafe fn sys_sched_getaffinity( + pid: i32, + mask_size: usize, + mask: *mut u8, +) -> i32 { + unsafe { arch::sys_sched_getaffinity(pid, mask_size, mask) } } /// Direct syscall to exit the process @@ -738,33 +555,5 @@ pub unsafe fn sys_sysinfo(info: *mut SysInfo) -> i64 { /// This syscall never returns. The process will terminate immediately. #[inline] pub unsafe fn sys_exit(code: i32) -> ! { - #[cfg(target_arch = "x86_64")] - unsafe { - core::arch::asm!( - "syscall", - in("rax") 60i64, // SYS_exit - in("rdi") code, - options(noreturn, nostack) - ); - } - - #[cfg(target_arch = "aarch64")] - unsafe { - core::arch::asm!( - "svc #0", - in("x8") 93i64, // SYS_exit - in("x0") code, - options(noreturn, nostack) - ); - } - - #[cfg(target_arch = "riscv64")] - unsafe { - core::arch::asm!( - "ecall", - in("a7") 93i64, // SYS_exit - in("a0") code, - options(noreturn, nostack) - ); - } + unsafe { arch::sys_exit(code) } } diff --git a/crates/asm/src/loongarch64.rs b/crates/asm/src/loongarch64.rs new file mode 100644 index 0000000..d5ab5ac --- /dev/null +++ b/crates/asm/src/loongarch64.rs @@ -0,0 +1,161 @@ +//! Syscall implementations for `loongarch64`. + +use super::{StatfsBuf, SysInfo, UtsNameBuf}; + +pub(super) unsafe fn sys_open(path: *const u8, flags: i32) -> i32 { + unsafe { + let fd: i64; + core::arch::asm!( + "syscall 0", + 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 + } + } +} + +pub(super) unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize { + unsafe { + let ret: i64; + core::arch::asm!( + "syscall 0", + 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 + } + } +} + +pub(super) unsafe fn sys_write(fd: i32, buf: *const u8, count: usize) -> isize { + unsafe { + let ret: i64; + core::arch::asm!( + "syscall 0", + 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 + } + } +} + +pub(super) unsafe fn sys_close(fd: i32) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "syscall 0", + in("$a7") 57i64, // SYS_close + in("$a0") fd, + lateout("$a0") ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_uname(buf: *mut UtsNameBuf) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "syscall 0", + in("$a7") 160i64, // SYS_uname + in("$a0") buf, + lateout("$a0") ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_statfs(path: *const u8, buf: *mut StatfsBuf) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "syscall 0", + in("$a7") 43i64, // SYS_statfs + in("$a0") path, + in("$a1") buf, + lateout("$a0") ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_sysinfo(info: *mut SysInfo) -> i64 { + unsafe { + let ret: i64; + core::arch::asm!( + "syscall 0", + in("$a7") 179_i64, // __NR_sysinfo + in("$a0") info, + lateout("$a0") ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_sched_getaffinity( + pid: i32, + mask_size: usize, + mask: *mut u8, +) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "syscall 0", + in("$a7") 123i64, // __NR_sched_getaffinity + in("$a0") pid, + in("$a1") mask_size, + in("$a2") mask, + lateout("$a0") ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_exit(code: i32) -> ! { + unsafe { + core::arch::asm!( + "syscall 0", + in("$a7") 93i64, // SYS_exit + in("$a0") code, + options(noreturn, nostack) + ); + } +} diff --git a/crates/asm/src/mips.rs b/crates/asm/src/mips.rs new file mode 100644 index 0000000..c5adf17 --- /dev/null +++ b/crates/asm/src/mips.rs @@ -0,0 +1,196 @@ +//! Syscall implementations for `mips`. + +use super::{StatfsBuf, SysInfo, UtsNameBuf}; + +pub(super) unsafe fn sys_open(path: *const u8, flags: i32) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "syscall", + "beqz $7, 1f", + "nop", + "subu $2, $0, $2", + "1:", + inlateout("$2") 4000i32 + 5 => ret, // SYS_open + in("$4") path, + in("$5") flags, + in("$6") 0i32, // mode + lateout("$7") _, + lateout("$8") _, lateout("$9") _, lateout("$10") _, lateout("$11") _, + lateout("$12") _, lateout("$13") _, lateout("$14") _, lateout("$15") _, + lateout("$24") _, lateout("$25") _, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize { + unsafe { + let ret: i32; + core::arch::asm!( + "syscall", + "beqz $7, 1f", + "nop", + "subu $2, $0, $2", + "1:", + inlateout("$2") 4000i32 + 3 => ret, // SYS_read + in("$4") fd, + in("$5") buf, + in("$6") count, + lateout("$7") _, + lateout("$8") _, lateout("$9") _, lateout("$10") _, lateout("$11") _, + lateout("$12") _, lateout("$13") _, lateout("$14") _, lateout("$15") _, + lateout("$24") _, lateout("$25") _, + options(nostack) + ); + ret as isize + } +} + +pub(super) unsafe fn sys_write(fd: i32, buf: *const u8, count: usize) -> isize { + unsafe { + let ret: i32; + core::arch::asm!( + "syscall", + "beqz $7, 1f", + "nop", + "subu $2, $0, $2", + "1:", + inlateout("$2") 4000i32 + 4 => ret, // SYS_write + in("$4") fd, + in("$5") buf, + in("$6") count, + lateout("$7") _, + lateout("$8") _, lateout("$9") _, lateout("$10") _, lateout("$11") _, + lateout("$12") _, lateout("$13") _, lateout("$14") _, lateout("$15") _, + lateout("$24") _, lateout("$25") _, + options(nostack) + ); + ret as isize + } +} + +pub(super) unsafe fn sys_close(fd: i32) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "syscall", + "beqz $7, 1f", + "nop", + "subu $2, $0, $2", + "1:", + inlateout("$2") 4000i32 + 6 => ret, // SYS_close + in("$4") fd, + lateout("$7") _, + lateout("$8") _, lateout("$9") _, lateout("$10") _, lateout("$11") _, + lateout("$12") _, lateout("$13") _, lateout("$14") _, lateout("$15") _, + lateout("$24") _, lateout("$25") _, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_uname(buf: *mut UtsNameBuf) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "syscall", + "beqz $7, 1f", + "nop", + "subu $2, $0, $2", + "1:", + inlateout("$2") 4000i32 + 122 => ret, // SYS_newuname + in("$4") buf, + lateout("$7") _, + lateout("$8") _, lateout("$9") _, lateout("$10") _, lateout("$11") _, + lateout("$12") _, lateout("$13") _, lateout("$14") _, lateout("$15") _, + lateout("$24") _, lateout("$25") _, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_statfs(path: *const u8, buf: *mut StatfsBuf) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "syscall", + "beqz $7, 1f", + "nop", + "subu $2, $0, $2", + "1:", + inlateout("$2") 4000i32 + 255 => ret, // SYS_statfs64 + in("$4") path, + in("$5") core::mem::size_of::(), + in("$6") buf, + lateout("$7") _, + lateout("$8") _, lateout("$9") _, lateout("$10") _, lateout("$11") _, + lateout("$12") _, lateout("$13") _, lateout("$14") _, lateout("$15") _, + lateout("$24") _, lateout("$25") _, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_sysinfo(info: *mut SysInfo) -> i64 { + unsafe { + let ret: i32; + core::arch::asm!( + "syscall", + "beqz $7, 1f", + "nop", + "subu $2, $0, $2", + "1:", + inlateout("$2") 4000_i32 + 116 => ret, // __NR_sysinfo + in("$4") info, + lateout("$7") _, + lateout("$8") _, lateout("$9") _, lateout("$10") _, lateout("$11") _, + lateout("$12") _, lateout("$13") _, lateout("$14") _, lateout("$15") _, + lateout("$24") _, lateout("$25") _, + options(nostack) + ); + i64::from(ret) + } +} + +pub(super) unsafe fn sys_sched_getaffinity( + pid: i32, + mask_size: usize, + mask: *mut u8, +) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "syscall", + "beqz $7, 1f", + "nop", + "subu $2, $0, $2", + "1:", + inlateout("$2") 4000i32 + 240 => ret, // __NR_sched_getaffinity + in("$4") pid, + in("$5") mask_size, + in("$6") mask, + lateout("$7") _, + lateout("$8") _, lateout("$9") _, lateout("$10") _, lateout("$11") _, + lateout("$12") _, lateout("$13") _, lateout("$14") _, lateout("$15") _, + lateout("$24") _, lateout("$25") _, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_exit(code: i32) -> ! { + unsafe { + core::arch::asm!( + "syscall", + in("$2") 4000i32 + 1, // SYS_exit + in("$4") code, + options(noreturn, nostack) + ); + } +} diff --git a/crates/asm/src/mips64.rs b/crates/asm/src/mips64.rs new file mode 100644 index 0000000..9127a1e --- /dev/null +++ b/crates/asm/src/mips64.rs @@ -0,0 +1,206 @@ +//! Syscall implementations for `mips64`. + +use super::{StatfsBuf, SysInfo, UtsNameBuf}; + +pub(super) unsafe fn sys_open(path: *const u8, flags: i32) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "syscall", + "beqz $7, 1f", + "nop", + "dsubu $2, $0, $2", + "1:", + inlateout("$2") 5000i64 + 2 => ret, // SYS_open + in("$4") path, + in("$5") flags, + in("$6") 0i32, // mode + lateout("$7") _, + lateout("$8") _, lateout("$9") _, lateout("$10") _, lateout("$11") _, + lateout("$12") _, lateout("$13") _, lateout("$14") _, lateout("$15") _, + lateout("$24") _, lateout("$25") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize { + unsafe { + let ret: i64; + core::arch::asm!( + "syscall", + "beqz $7, 1f", + "nop", + "dsubu $2, $0, $2", + "1:", + inlateout("$2") 5000i64 + 0 => ret, // SYS_read + in("$4") fd, + in("$5") buf, + in("$6") count, + lateout("$7") _, + lateout("$8") _, lateout("$9") _, lateout("$10") _, lateout("$11") _, + lateout("$12") _, lateout("$13") _, lateout("$14") _, lateout("$15") _, + lateout("$24") _, lateout("$25") _, + options(nostack) + ); + ret as isize + } +} + +pub(super) unsafe fn sys_write(fd: i32, buf: *const u8, count: usize) -> isize { + unsafe { + let ret: i64; + core::arch::asm!( + "syscall", + "beqz $7, 1f", + "nop", + "dsubu $2, $0, $2", + "1:", + inlateout("$2") 5000i64 + 1 => ret, // SYS_write + in("$4") fd, + in("$5") buf, + in("$6") count, + lateout("$7") _, + lateout("$8") _, lateout("$9") _, lateout("$10") _, lateout("$11") _, + lateout("$12") _, lateout("$13") _, lateout("$14") _, lateout("$15") _, + lateout("$24") _, lateout("$25") _, + options(nostack) + ); + ret as isize + } +} + +pub(super) unsafe fn sys_close(fd: i32) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "syscall", + "beqz $7, 1f", + "nop", + "dsubu $2, $0, $2", + "1:", + inlateout("$2") 5000i64 + 3 => ret, // SYS_close + in("$4") fd, + lateout("$7") _, + lateout("$8") _, lateout("$9") _, lateout("$10") _, lateout("$11") _, + lateout("$12") _, lateout("$13") _, lateout("$14") _, lateout("$15") _, + lateout("$24") _, lateout("$25") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_uname(buf: *mut UtsNameBuf) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "syscall", + "beqz $7, 1f", + "nop", + "dsubu $2, $0, $2", + "1:", + inlateout("$2") 5000i64 + 61 => ret, // SYS_newuname + in("$4") buf, + lateout("$7") _, + lateout("$8") _, lateout("$9") _, lateout("$10") _, lateout("$11") _, + lateout("$12") _, lateout("$13") _, lateout("$14") _, lateout("$15") _, + lateout("$24") _, lateout("$25") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_statfs(path: *const u8, buf: *mut StatfsBuf) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "syscall", + "beqz $7, 1f", + "nop", + "dsubu $2, $0, $2", + "1:", + inlateout("$2") 5000i64 + 134 => ret, // SYS_statfs + in("$4") path, + in("$5") buf, + lateout("$7") _, + lateout("$8") _, lateout("$9") _, lateout("$10") _, lateout("$11") _, + lateout("$12") _, lateout("$13") _, lateout("$14") _, lateout("$15") _, + lateout("$24") _, lateout("$25") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_sysinfo(info: *mut SysInfo) -> i64 { + unsafe { + let ret: i64; + core::arch::asm!( + "syscall", + "beqz $7, 1f", + "nop", + "dsubu $2, $0, $2", + "1:", + inlateout("$2") 5000_i64 + 97 => ret, // SYS_sysinfo + in("$4") info, + lateout("$7") _, + lateout("$8") _, lateout("$9") _, lateout("$10") _, lateout("$11") _, + lateout("$12") _, lateout("$13") _, lateout("$14") _, lateout("$15") _, + lateout("$24") _, lateout("$25") _, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_sched_getaffinity( + pid: i32, + mask_size: usize, + mask: *mut u8, +) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "syscall", + inlateout("$2") 5000i64 + 196 => ret, // __NR_sched_getaffinity + in("$4") pid, + in("$5") mask_size, + in("$6") mask, + lateout("$7") _, + lateout("$8") _, lateout("$9") _, lateout("$10") _, lateout("$11") _, + lateout("$12") _, lateout("$13") _, lateout("$14") _, lateout("$15") _, + lateout("$24") _, lateout("$25") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_exit(code: i32) -> ! { + unsafe { + core::arch::asm!( + "syscall", + in("$2") 5000i64 + 58, // SYS_exit + in("$4") code, + options(noreturn, nostack) + ); + } +} diff --git a/crates/asm/src/powerpc.rs b/crates/asm/src/powerpc.rs new file mode 100644 index 0000000..e2e7829 --- /dev/null +++ b/crates/asm/src/powerpc.rs @@ -0,0 +1,180 @@ +//! Syscall implementations for `powerpc`. + +use super::{StatfsBuf, SysInfo, UtsNameBuf}; + +pub(super) unsafe fn sys_open(path: *const u8, flags: i32) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 5i32 => _, // SYS_open + inlateout("r3") path => ret, + inlateout("r4") flags => _, + inlateout("r5") 0i32 => _, + out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize { + unsafe { + let ret: i32; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 3i32 => _, // SYS_read + inlateout("r3") fd => ret, + inlateout("r4") buf => _, + inlateout("r5") count => _, + out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + ret as isize + } +} + +pub(super) unsafe fn sys_write(fd: i32, buf: *const u8, count: usize) -> isize { + unsafe { + let ret: i32; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 4i32 => _, // SYS_write + inlateout("r3") fd => ret, + inlateout("r4") buf => _, + inlateout("r5") count => _, + out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + ret as isize + } +} + +pub(super) unsafe fn sys_close(fd: i32) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 6i32 => _, // SYS_close + inlateout("r3") fd => ret, + out("r4") _, out("r5") _, out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_uname(buf: *mut UtsNameBuf) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 122i32 => _, // SYS_newuname + inlateout("r3") buf => ret, + out("r4") _, out("r5") _, out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_statfs(path: *const u8, buf: *mut StatfsBuf) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 252i32 => _, // SYS_statfs64 + inlateout("r3") path => ret, + inlateout("r4") core::mem::size_of::() => _, + inlateout("r5") buf => _, + out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_sysinfo(info: *mut SysInfo) -> i64 { + unsafe { + let ret: i32; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 116_i32 => _, // __NR_sysinfo + inlateout("r3") info => ret, + out("r4") _, out("r5") _, out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + i64::from(ret) + } +} + +pub(super) unsafe fn sys_sched_getaffinity( + pid: i32, + mask_size: usize, + mask: *mut u8, +) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 223i32 => _, // __NR_sched_getaffinity + inlateout("r3") pid => ret, + inlateout("r4") mask_size => _, + inlateout("r5") mask => _, + out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_exit(code: i32) -> ! { + unsafe { + core::arch::asm!( + "li 0, 1", // SYS_exit + "sc", + in("r3") code, + options(noreturn, nostack) + ); + } +} diff --git a/crates/asm/src/powerpc64.rs b/crates/asm/src/powerpc64.rs new file mode 100644 index 0000000..13e0b33 --- /dev/null +++ b/crates/asm/src/powerpc64.rs @@ -0,0 +1,200 @@ +//! Syscall implementations for `powerpc64`. + +use super::{StatfsBuf, SysInfo, UtsNameBuf}; + +pub(super) unsafe fn sys_open(path: *const u8, flags: i32) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 5i64 => _, // SYS_open + inlateout("r3") path => ret, + inlateout("r4") flags => _, + inlateout("r5") 0i32 => _, // mode + out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize { + unsafe { + let ret: i64; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 3i64 => _, // SYS_read + inlateout("r3") fd as i64 => ret, + inlateout("r4") buf => _, + inlateout("r5") count => _, + out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as isize + } + } +} + +pub(super) unsafe fn sys_write(fd: i32, buf: *const u8, count: usize) -> isize { + unsafe { + let ret: i64; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 4i64 => _, // SYS_write + inlateout("r3") fd as i64 => ret, + inlateout("r4") buf => _, + inlateout("r5") count => _, + out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as isize + } + } +} + +pub(super) unsafe fn sys_close(fd: i32) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 6i64 => _, // SYS_close + inlateout("r3") fd as i64 => ret, + out("r4") _, out("r5") _, out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_uname(buf: *mut UtsNameBuf) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 122i64 => _, // SYS_uname + inlateout("r3") buf => ret, + out("r4") _, out("r5") _, out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_statfs(path: *const u8, buf: *mut StatfsBuf) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 99i64 => _, // SYS_statfs + inlateout("r3") path => ret, + inlateout("r4") buf => _, + out("r5") _, out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_sysinfo(info: *mut SysInfo) -> i64 { + unsafe { + let ret: i64; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 116_i64 => _, // __NR_sysinfo + inlateout("r3") info => ret, + out("r4") _, out("r5") _, out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_sched_getaffinity( + pid: i32, + mask_size: usize, + mask: *mut u8, +) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "sc", + "bns+ 2f", + "neg 3, 3", + "2:", + inlateout("r0") 223i64 => _, // __NR_sched_getaffinity + inlateout("r3") pid as i64 => ret, + inlateout("r4") mask_size => _, + inlateout("r5") mask => _, + out("r6") _, out("r7") _, out("r8") _, + out("r9") _, out("r10") _, out("r11") _, out("r12") _, + out("cr0") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_exit(code: i32) -> ! { + unsafe { + core::arch::asm!( + "li 0, 1", // SYS_exit + "sc", + in("r3") code, + options(noreturn, nostack) + ); + } +} diff --git a/crates/asm/src/riscv32.rs b/crates/asm/src/riscv32.rs new file mode 100644 index 0000000..1e4b29a --- /dev/null +++ b/crates/asm/src/riscv32.rs @@ -0,0 +1,142 @@ +//! Syscall implementations for `riscv32`. + +use super::{StatfsBuf, SysInfo, UtsNameBuf}; + +pub(super) unsafe fn sys_open(path: *const u8, flags: i32) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "ecall", + in("a7") 56i32, // SYS_openat + in("a0") -100i32, // AT_FDCWD + in("a1") path, + in("a2") flags, + in("a3") 0i32, // mode + lateout("a0") ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize { + unsafe { + let ret: i32; + core::arch::asm!( + "ecall", + in("a7") 63i32, // SYS_read + in("a0") fd, + in("a1") buf, + in("a2") count, + lateout("a0") ret, + options(nostack) + ); + ret as isize + } +} + +pub(super) unsafe fn sys_write(fd: i32, buf: *const u8, count: usize) -> isize { + unsafe { + let ret: i32; + core::arch::asm!( + "ecall", + in("a7") 64i32, // SYS_write + in("a0") fd, + in("a1") buf, + in("a2") count, + lateout("a0") ret, + options(nostack) + ); + ret as isize + } +} + +pub(super) unsafe fn sys_close(fd: i32) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "ecall", + in("a7") 57i32, // SYS_close + in("a0") fd, + lateout("a0") ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_uname(buf: *mut UtsNameBuf) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "ecall", + in("a7") 160i32, // SYS_uname + in("a0") buf, + lateout("a0") ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_statfs(path: *const u8, buf: *mut StatfsBuf) -> i32 { + unsafe { + // asm-generic __NR_statfs routes to sys_statfs64 on 32-bit: 3-arg form. + let ret: i32; + core::arch::asm!( + "ecall", + in("a7") 43i32, // __NR_statfs + in("a0") path, + in("a1") core::mem::size_of::(), + in("a2") buf, + lateout("a0") ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_sysinfo(info: *mut SysInfo) -> i64 { + unsafe { + let ret: i32; + core::arch::asm!( + "ecall", + in("a7") 179_i32, // __NR_sysinfo + in("a0") info, + lateout("a0") ret, + options(nostack) + ); + i64::from(ret) + } +} + +pub(super) unsafe fn sys_sched_getaffinity( + pid: i32, + mask_size: usize, + mask: *mut u8, +) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "ecall", + in("a7") 123i32, // __NR_sched_getaffinity + in("a0") pid, + in("a1") mask_size, + in("a2") mask, + lateout("a0") ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_exit(code: i32) -> ! { + unsafe { + core::arch::asm!( + "ecall", + in("a7") 93i32, // SYS_exit + in("a0") code, + options(noreturn, nostack) + ); + } +} diff --git a/crates/asm/src/riscv64.rs b/crates/asm/src/riscv64.rs new file mode 100644 index 0000000..2a0c10b --- /dev/null +++ b/crates/asm/src/riscv64.rs @@ -0,0 +1,166 @@ +//! Syscall implementations for `riscv64`. + +use super::{StatfsBuf, SysInfo, UtsNameBuf}; + +pub(super) unsafe fn sys_open(path: *const u8, flags: i32) -> i32 { + unsafe { + let fd: i64; + core::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 + } + } +} + +pub(super) unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize { + unsafe { + let ret: i64; + core::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 + } + } +} + +pub(super) unsafe fn sys_write(fd: i32, buf: *const u8, count: usize) -> isize { + unsafe { + let ret: i64; + core::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 + } + } +} + +pub(super) unsafe fn sys_close(fd: i32) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "ecall", + in("a7") 57i64, // SYS_close + in("a0") fd, + lateout("a0") ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_uname(buf: *mut UtsNameBuf) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "ecall", + in("a7") 160i64, // SYS_uname + in("a0") buf, + lateout("a0") ret, + options(nostack) + ); + + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_statfs(path: *const u8, buf: *mut StatfsBuf) -> i32 { + unsafe { + let ret: i64; + core::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 + } + } +} + +pub(super) unsafe fn sys_sysinfo(info: *mut SysInfo) -> i64 { + unsafe { + let ret: i64; + core::arch::asm!( + "ecall", + in("a7") 179_i64, // __NR_sysinfo + in("a0") info, + lateout("a0") ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_sched_getaffinity( + pid: i32, + mask_size: usize, + mask: *mut u8, +) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "ecall", + in("a7") 123i64, // __NR_sched_getaffinity + in("a0") pid, + in("a1") mask_size, + in("a2") mask, + lateout("a0") ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_exit(code: i32) -> ! { + unsafe { + core::arch::asm!( + "ecall", + in("a7") 93i64, // SYS_exit + in("a0") code, + options(noreturn, nostack) + ); + } +} diff --git a/crates/asm/src/s390x.rs b/crates/asm/src/s390x.rs new file mode 100644 index 0000000..3112ed6 --- /dev/null +++ b/crates/asm/src/s390x.rs @@ -0,0 +1,160 @@ +//! Syscall implementations for `s390x`. + +use super::{StatfsBuf, SysInfo, UtsNameBuf}; + +pub(super) unsafe fn sys_open(path: *const u8, flags: i32) -> i32 { + unsafe { + let fd: i64; + core::arch::asm!( + "svc 0", + in("r1") 5i64, // SYS_open + in("r2") path, + in("r3") flags, + in("r4") 0i32, // mode + lateout("r2") fd, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + fd as i32 + } + } +} + +pub(super) unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize { + unsafe { + let ret: i64; + core::arch::asm!( + "svc 0", + in("r1") 3i64, // SYS_read + in("r2") fd, + in("r3") buf, + in("r4") count, + lateout("r2") ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as isize + } + } +} + +pub(super) unsafe fn sys_write(fd: i32, buf: *const u8, count: usize) -> isize { + unsafe { + let ret: i64; + core::arch::asm!( + "svc 0", + in("r1") 4i64, // SYS_write + in("r2") fd, + in("r3") buf, + in("r4") count, + lateout("r2") ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as isize + } + } +} + +pub(super) unsafe fn sys_close(fd: i32) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "svc 0", + in("r1") 6i64, // SYS_close + in("r2") fd, + lateout("r2") ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_uname(buf: *mut UtsNameBuf) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "svc 0", + in("r1") 122i64, // SYS_uname + in("r2") buf, + lateout("r2") ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_statfs(path: *const u8, buf: *mut StatfsBuf) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "svc 0", + in("r1") 99i64, // SYS_statfs + in("r2") path, + in("r3") buf, + lateout("r2") ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_sysinfo(info: *mut SysInfo) -> i64 { + unsafe { + let ret: i64; + core::arch::asm!( + "svc 0", + in("r1") 116_i64, // __NR_sysinfo + in("r2") info, + lateout("r2") ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_sched_getaffinity( + pid: i32, + mask_size: usize, + mask: *mut u8, +) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "svc 0", + in("r1") 240i64, // __NR_sched_getaffinity + in("r2") pid, + in("r3") mask_size, + in("r4") mask, + lateout("r2") ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_exit(code: i32) -> ! { + unsafe { + core::arch::asm!( + "svc 0", + in("r1") 1i64, // SYS_exit + in("r2") code, + options(noreturn, nostack) + ); + } +} diff --git a/crates/asm/src/sparc.rs b/crates/asm/src/sparc.rs new file mode 100644 index 0000000..a4cc3f7 --- /dev/null +++ b/crates/asm/src/sparc.rs @@ -0,0 +1,165 @@ +//! Syscall implementations for `sparc`. + +use super::{StatfsBuf, SysInfo, UtsNameBuf}; + +pub(super) unsafe fn sys_open(path: *const u8, flags: i32) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "mov {nr}, %g1", + "ta 0x10", + "bcs,a 1f", + "sub %g0, %o0, %o0", + "1:", + nr = in(reg) 5i32, // SYS_open + inlateout("o0") path => ret, + in("o1") flags, + in("o2") 0i32, // mode + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize { + unsafe { + let ret: i32; + core::arch::asm!( + "mov {nr}, %g1", + "ta 0x10", + "bcs,a 1f", + "sub %g0, %o0, %o0", + "1:", + nr = in(reg) 3i32, // SYS_read + inlateout("o0") fd => ret, + in("o1") buf, + in("o2") count, + options(nostack) + ); + ret as isize + } +} + +pub(super) unsafe fn sys_write(fd: i32, buf: *const u8, count: usize) -> isize { + unsafe { + let ret: i32; + core::arch::asm!( + "mov {nr}, %g1", + "ta 0x10", + "bcs,a 1f", + "sub %g0, %o0, %o0", + "1:", + nr = in(reg) 4i32, // SYS_write + inlateout("o0") fd => ret, + in("o1") buf, + in("o2") count, + options(nostack) + ); + ret as isize + } +} + +pub(super) unsafe fn sys_close(fd: i32) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "mov {nr}, %g1", + "ta 0x10", + "bcs,a 1f", + "sub %g0, %o0, %o0", + "1:", + nr = in(reg) 6i32, // SYS_close + inlateout("o0") fd => ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_uname(buf: *mut UtsNameBuf) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "mov {nr}, %g1", + "ta 0x10", + "bcs,a 1f", + "sub %g0, %o0, %o0", + "1:", + nr = in(reg) 189i32, // SYS_newuname + inlateout("o0") buf => ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_statfs(path: *const u8, buf: *mut StatfsBuf) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "mov {nr}, %g1", + "ta 0x10", + "bcs,a 1f", + "sub %g0, %o0, %o0", + "1:", + nr = in(reg) 234i32, // SYS_statfs64 + inlateout("o0") path => ret, + in("o1") core::mem::size_of::(), + in("o2") buf, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_sysinfo(info: *mut SysInfo) -> i64 { + unsafe { + let ret: i32; + core::arch::asm!( + "mov {nr}, %g1", + "ta 0x10", + "bcs,a 1f", + "sub %g0, %o0, %o0", + "1:", + nr = in(reg) 214_i32, // __NR_sysinfo + inlateout("o0") info => ret, + options(nostack) + ); + i64::from(ret) + } +} + +pub(super) unsafe fn sys_sched_getaffinity( + pid: i32, + mask_size: usize, + mask: *mut u8, +) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "mov {nr}, %g1", + "ta 0x10", + "bcs,a 1f", + "sub %g0, %o0, %o0", + "1:", + nr = in(reg) 260i32, // __NR_sched_getaffinity + inlateout("o0") pid => ret, + in("o1") mask_size, + in("o2") mask, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_exit(code: i32) -> ! { + unsafe { + core::arch::asm!( + "mov {nr}, %g1", + "ta 0x10", + nr = in(reg) 1i32, // SYS_exit + in("o0") code, + options(noreturn, nostack) + ); + } +} diff --git a/crates/asm/src/sparc64.rs b/crates/asm/src/sparc64.rs new file mode 100644 index 0000000..020657e --- /dev/null +++ b/crates/asm/src/sparc64.rs @@ -0,0 +1,179 @@ +//! Syscall implementations for `sparc64`. + +use super::{StatfsBuf, SysInfo, UtsNameBuf}; + +pub(super) unsafe fn sys_open(path: *const u8, flags: i32) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "mov {nr}, %g1", + "t 0x6d", + "bcs,a %xcc, 1f", + "sub %g0, %o0, %o0", + "1:", + nr = in(reg) 5i64, // SYS_open + inlateout("o0") path => ret, + in("o1") flags, + in("o2") 0i32, // mode + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize { + unsafe { + let ret: i64; + core::arch::asm!( + "mov {nr}, %g1", + "t 0x6d", + "bcs,a %xcc, 1f", + "sub %g0, %o0, %o0", + "1:", + nr = in(reg) 3i64, // SYS_read + inlateout("o0") fd as i64 => ret, + in("o1") buf, + in("o2") count, + options(nostack) + ); + ret as isize + } +} + +pub(super) unsafe fn sys_write(fd: i32, buf: *const u8, count: usize) -> isize { + unsafe { + let ret: i64; + core::arch::asm!( + "mov {nr}, %g1", + "t 0x6d", + "bcs,a %xcc, 1f", + "sub %g0, %o0, %o0", + "1:", + nr = in(reg) 4i64, // SYS_write + inlateout("o0") fd as i64 => ret, + in("o1") buf, + in("o2") count, + options(nostack) + ); + ret as isize + } +} + +pub(super) unsafe fn sys_close(fd: i32) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "mov {nr}, %g1", + "t 0x6d", + "bcs,a %xcc, 1f", + "sub %g0, %o0, %o0", + "1:", + nr = in(reg) 6i64, // SYS_close + inlateout("o0") fd as i64 => ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_uname(buf: *mut UtsNameBuf) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "mov {nr}, %g1", + "t 0x6d", + "bcs,a %xcc, 1f", + "sub %g0, %o0, %o0", + "1:", + nr = in(reg) 189i64, // SYS_newuname + inlateout("o0") buf => ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_statfs(path: *const u8, buf: *mut StatfsBuf) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "mov {nr}, %g1", + "t 0x6d", + "bcs,a %xcc, 1f", + "sub %g0, %o0, %o0", + "1:", + nr = in(reg) 157i64, // SYS_statfs + inlateout("o0") path => ret, + in("o1") buf, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_sysinfo(info: *mut SysInfo) -> i64 { + unsafe { + let ret: i64; + core::arch::asm!( + "mov {nr}, %g1", + "t 0x6d", + "bcs,a %xcc, 1f", + "sub %g0, %o0, %o0", + "1:", + nr = in(reg) 214_i64, // __NR_sysinfo + inlateout("o0") info => ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_sched_getaffinity( + pid: i32, + mask_size: usize, + mask: *mut u8, +) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "mov {nr}, %g1", + "t 0x6d", + "bcs,a %xcc, 1f", + "sub %g0, %o0, %o0", + "1:", + nr = in(reg) 260i64, // __NR_sched_getaffinity + inlateout("o0") pid as i64 => ret, + in("o1") mask_size, + in("o2") mask, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_exit(code: i32) -> ! { + unsafe { + core::arch::asm!( + "mov {nr}, %g1", + "t 0x6d", + nr = in(reg) 1i64, // SYS_exit + in("o0") code, + options(noreturn, nostack) + ); + } +} diff --git a/crates/asm/src/x86.rs b/crates/asm/src/x86.rs new file mode 100644 index 0000000..f0e619e --- /dev/null +++ b/crates/asm/src/x86.rs @@ -0,0 +1,149 @@ +//! Syscall implementations for `x86`. + +use super::{StatfsBuf, SysInfo, UtsNameBuf}; + +pub(super) unsafe fn sys_open(path: *const u8, flags: i32) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "push ebx", + "mov ebx, {arg1:e}", + "int 0x80", + "pop ebx", + arg1 = in(reg) path, + inlateout("eax") 5i32 => ret, // SYS_open + in("ecx") flags, + in("edx") 0i32, // mode + ); + ret + } +} + +pub(super) unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize { + unsafe { + let ret: i32; + core::arch::asm!( + "push ebx", + "mov ebx, {arg1:e}", + "int 0x80", + "pop ebx", + arg1 = in(reg) fd, + inlateout("eax") 3i32 => ret, // SYS_read + in("ecx") buf, + in("edx") count, + ); + ret as isize + } +} + +pub(super) unsafe fn sys_write(fd: i32, buf: *const u8, count: usize) -> isize { + unsafe { + let ret: i32; + core::arch::asm!( + "push ebx", + "mov ebx, {arg1:e}", + "int 0x80", + "pop ebx", + arg1 = in(reg) fd, + inlateout("eax") 4i32 => ret, // SYS_write + in("ecx") buf, + in("edx") count, + ); + ret as isize + } +} + +pub(super) unsafe fn sys_close(fd: i32) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "push ebx", + "mov ebx, {arg1:e}", + "int 0x80", + "pop ebx", + arg1 = in(reg) fd, + inlateout("eax") 6i32 => ret, // SYS_close + ); + ret + } +} + +pub(super) unsafe fn sys_uname(buf: *mut UtsNameBuf) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "push ebx", + "mov ebx, {arg1:e}", + "int 0x80", + "pop ebx", + arg1 = in(reg) buf, + inlateout("eax") 122i32 => ret, // SYS_newuname + ); + ret + } +} + +pub(super) unsafe fn sys_statfs(path: *const u8, buf: *mut StatfsBuf) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "push ebx", + "mov ebx, {arg1:e}", + "int 0x80", + "pop ebx", + arg1 = in(reg) path, + inlateout("eax") 268i32 => ret, // SYS_statfs64 + in("ecx") core::mem::size_of::(), + in("edx") buf, + ); + ret + } +} + +pub(super) unsafe fn sys_sysinfo(info: *mut SysInfo) -> i64 { + unsafe { + let ret: i32; + core::arch::asm!( + "push ebx", + "mov ebx, {arg1:e}", + "int 0x80", + "pop ebx", + arg1 = in(reg) info, + inlateout("eax") 116_i32 => ret, // __NR_sysinfo + ); + i64::from(ret) + } +} + +pub(super) unsafe fn sys_sched_getaffinity( + pid: i32, + mask_size: usize, + mask: *mut u8, +) -> i32 { + unsafe { + let ret: i32; + core::arch::asm!( + "push ebx", + "mov ebx, {arg1:e}", + "int 0x80", + "pop ebx", + arg1 = in(reg) pid, + inlateout("eax") 242i32 => ret, // __NR_sched_getaffinity + in("ecx") mask_size, + in("edx") mask, + ); + ret + } +} + +pub(super) unsafe fn sys_exit(code: i32) -> ! { + unsafe { + core::arch::asm!( + "mov ebx, {code:e}", + "int 0x80", + code = in(reg) code, + in("eax") 1i32, // SYS_exit + options(noreturn) + ); + } +} diff --git a/crates/asm/src/x86_64.rs b/crates/asm/src/x86_64.rs new file mode 100644 index 0000000..967ddbf --- /dev/null +++ b/crates/asm/src/x86_64.rs @@ -0,0 +1,180 @@ +//! Syscall implementations for `x86_64`. + +use super::{StatfsBuf, SysInfo, UtsNameBuf}; + +pub(super) unsafe fn sys_open(path: *const u8, flags: i32) -> i32 { + unsafe { + let fd: i64; + core::arch::asm!( + "syscall", + in("rax") 2i64, // SYS_open + in("rdi") path, + in("rsi") flags, + in("rdx") 0i32, // mode (not used for reading) + lateout("rax") fd, + lateout("rcx") _, + lateout("r11") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + fd as i32 + } + } +} + +pub(super) unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize { + unsafe { + let ret: i64; + core::arch::asm!( + "syscall", + in("rax") 0i64, // SYS_read + in("rdi") fd, + in("rsi") buf, + in("rdx") count, + lateout("rax") ret, + lateout("rcx") _, + lateout("r11") _, + options(nostack) + ); + + #[allow(clippy::cast_possible_truncation)] + { + ret as isize + } + } +} + +pub(super) unsafe fn sys_write(fd: i32, buf: *const u8, count: usize) -> isize { + unsafe { + let ret: i64; + core::arch::asm!( + "syscall", + in("rax") 1i64, // SYS_write + in("rdi") fd, + in("rsi") buf, + in("rdx") count, + lateout("rax") ret, + lateout("rcx") _, + lateout("r11") _, + options(nostack) + ); + + #[allow(clippy::cast_possible_truncation)] + { + ret as isize + } + } +} + +pub(super) unsafe fn sys_close(fd: i32) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "syscall", + in("rax") 3i64, // SYS_close + in("rdi") fd, + lateout("rax") ret, + lateout("rcx") _, + lateout("r11") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_uname(buf: *mut UtsNameBuf) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "syscall", + in("rax") 63i64, // SYS_uname + in("rdi") buf, + lateout("rax") ret, + lateout("rcx") _, + lateout("r11") _, + options(nostack) + ); + + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_statfs(path: *const u8, buf: *mut StatfsBuf) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "syscall", + in("rax") 137i64, // SYS_statfs + in("rdi") path, + in("rsi") buf, + lateout("rax") ret, + lateout("rcx") _, + lateout("r11") _, + options(nostack) + ); + + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_sysinfo(info: *mut SysInfo) -> i64 { + unsafe { + let ret: i64; + core::arch::asm!( + "syscall", + in("rax") 99_i64, // __NR_sysinfo + in("rdi") info, + out("rcx") _, + out("r11") _, + lateout("rax") ret, + options(nostack) + ); + ret + } +} + +pub(super) unsafe fn sys_sched_getaffinity( + pid: i32, + mask_size: usize, + mask: *mut u8, +) -> i32 { + unsafe { + let ret: i64; + core::arch::asm!( + "syscall", + in("rax") 204i64, // __NR_sched_getaffinity + in("rdi") pid, + in("rsi") mask_size, + in("rdx") mask, + lateout("rax") ret, + lateout("rcx") _, + lateout("r11") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } +} + +pub(super) unsafe fn sys_exit(code: i32) -> ! { + unsafe { + core::arch::asm!( + "syscall", + in("rax") 60i64, // SYS_exit + in("rdi") code, + options(noreturn, nostack) + ); + } +} diff --git a/crates/lib/Cargo.toml b/crates/lib/Cargo.toml index 176502a..43c9eb8 100644 --- a/crates/lib/Cargo.toml +++ b/crates/lib/Cargo.toml @@ -9,7 +9,7 @@ license.workspace = true publish = false [dependencies] -hotpath = { optional = true, version = "0.14.0" } +hotpath = { optional = true, version = "0.16.1" } microfetch-asm.workspace = true [features] diff --git a/crates/lib/src/cpu.rs b/crates/lib/src/cpu.rs new file mode 100644 index 0000000..dca327b --- /dev/null +++ b/crates/lib/src/cpu.rs @@ -0,0 +1,392 @@ +use alloc::string::String; + +use crate::{Error, syscall::read_file_fast, system::write_u64}; + +/// Gets CPU model name (trimmed), or empty string if unavailable. +#[cfg_attr(feature = "hotpath", hotpath::measure)] +pub fn get_cpu_name() -> String { + get_model_name().unwrap_or_default() +} + +/// Gets CPU core/thread info as a string. +/// +/// Format: `{cores} cores ({p}p/{e}e), {threads} threads` on hybrid Intel, +/// `{cores} cores, {threads} threads` otherwise. +/// +/// # Errors +/// +/// Returns an error if the thread count cannot be determined. +#[cfg_attr(feature = "hotpath", hotpath::measure)] +pub fn get_cpu_cores() -> Result { + let threads = get_thread_count()?; + let cores = get_core_count(threads); + + let mut result = String::new(); + + write_u64(&mut result, u64::from(cores)); + result.push_str(" cores"); + + if let Some((p, e)) = get_pe_cores() { + result.push_str(" ("); + write_u64(&mut result, u64::from(p)); + result.push_str("p/"); + write_u64(&mut result, u64::from(e)); + result.push_str("e)"); + } + + if threads != cores { + result.push_str(", "); + write_u64(&mut result, u64::from(threads)); + result.push_str(" threads"); + } + + Ok(result) +} + +/// Count online threads via `sched_getaffinity(2)`. +fn get_thread_count() -> Result { + let mut mask = [0u8; 128]; + let ret = unsafe { + crate::syscall::sys_sched_getaffinity(0, mask.len(), mask.as_mut_ptr()) + }; + if ret < 0 { + return Err(Error::from_raw_os_error(-ret)); + } + + #[allow(clippy::cast_sign_loss)] + let bytes = ret as usize; + let mut count = 0u32; + for &byte in &mask[..bytes] { + count += byte.count_ones(); + } + Ok(count) +} + +/// Derive physical core count from thread count and topology. +fn get_core_count(threads: u32) -> u32 { + let Some(smt_width) = + count_cpulist("/sys/devices/system/cpu/cpu0/topology/thread_siblings_list") + else { + return threads; + }; + if smt_width == 0 { + return threads; + } + threads / smt_width +} + +/// Detect P-core and E-core counts via sysfs PMU device files, which is done +/// by reading `/sys/devices/cpu_core/cpus` and `/sys/devices/cpu_atom/cpus`. +fn get_pe_cores() -> Option<(u32, u32)> { + let p = count_cpulist("/sys/devices/cpu_core/cpus")?; + let e = count_cpulist("/sys/devices/cpu_atom/cpus").unwrap_or(0); + if p > 0 || e > 0 { Some((p, e)) } else { None } +} + +/// Parse a cpulist file and count listed CPUs. +fn count_cpulist(path: &str) -> Option { + let mut buf = [0u8; 64]; + let n = read_file_fast(path, &mut buf).ok()?; + let data = &buf[..n]; + + let mut count = 0u32; + let mut i = 0; + while i < data.len() { + // Parse start number + let start = parse_num(data, &mut i); + if i < data.len() && data[i] == b'-' { + i += 1; + let end = parse_num(data, &mut i); + // The Kernel always emits ascending ranges, so end is always >= start + // https://github.com/torvalds/linux/blob/v6.19/lib/vsprintf.c#L1276-L1303 + count += end - start + 1; + } else { + count += 1; + } + // Skip comma or newline + if i < data.len() && (data[i] == b',' || data[i] == b'\n') { + i += 1; + } + } + Some(count) +} + +/// Parse a decimal number from a byte slice, advancing the index. +fn parse_num(data: &[u8], i: &mut usize) -> u32 { + let mut n = 0u32; + while *i < data.len() && data[*i].is_ascii_digit() { + n = n * 10 + u32::from(data[*i] - b'0'); + *i += 1; + } + n +} + +/// Build `/sys/devices/system/cpu/cpu{n}/cpufreq/cpuinfo_max_freq` into buf, +/// returning the byte length written. +fn format_cpufreq_path(buf: &mut [u8; 64], cpu: u32) -> usize { + const PREFIX: &[u8] = b"/sys/devices/system/cpu/cpu"; + const SUFFIX: &[u8] = b"/cpufreq/cpuinfo_max_freq"; + buf[..PREFIX.len()].copy_from_slice(PREFIX); + let mut i = PREFIX.len(); + let mut tmp = [0u8; 3]; + let mut n = cpu; + let mut digits = 0; + loop { + tmp[digits] = b'0' + (n % 10) as u8; + digits += 1; + n /= 10; + if n == 0 { + break; + } + } + while digits > 0 { + digits -= 1; + buf[i] = tmp[digits]; + i += 1; + } + buf[i..i + SUFFIX.len()].copy_from_slice(SUFFIX); + i + SUFFIX.len() +} + +/// Read CPU frequency in MHz. Tries sysfs first, then cpuinfo fields. +fn get_cpu_freq_mhz() -> Option { + // Read cpuinfo_max_freq across all CPUs (in kHz) and take the max so + // heterogeneous (big.LITTLE) topologies report the performance cluster. + let mut max_khz = 0u32; + let mut path = [0u8; 64]; + for cpu in 0u32..64 { + let n = format_cpufreq_path(&mut path, cpu); + let p = match core::str::from_utf8(&path[..n]) { + Ok(s) => s, + Err(_) => continue, + }; + let mut buf = [0u8; 32]; + let Ok(m) = read_file_fast(p, &mut buf) else { + if cpu == 0 { + continue; + } + break; + }; + let mut khz = 0u32; + for &b in &buf[..m] { + if b.is_ascii_digit() { + khz = khz * 10 + u32::from(b - b'0'); + } + } + if khz > max_khz { + max_khz = khz; + } + } + if max_khz > 0 { + return Some(max_khz / 1000); + } + // Fall back to cpuinfo fields + let mut buf2 = [0u8; 4096]; + let n = read_file_fast("/proc/cpuinfo", &mut buf2).ok()?; + let data = &buf2[..n]; + for key in &[ + b"cpu MHz" as &[u8], + b"cpu MHz dynamic", + b"cpu MHz static", + b"CPU MHz", + b"clock", + // BogoMIPS on MIPS is calibrated to the clock frequency (unlike x86). + b"BogoMIPS", + ] { + if let Some(val) = extract_field(data, key) { + // Parse integer part of the MHz value (e.g. "5200.00" -> 5200) + let mut mhz = 0u32; + for &b in val.as_bytes() { + if b == b'.' { + break; + } + if b.is_ascii_digit() { + mhz = mhz * 10 + u32::from(b - b'0'); + } + } + if mhz > 0 { + return Some(mhz); + } + } + } + // SPARC exposes its clock as `Cpu0ClkTck : `, + // which signifies ticks per second in hex. + if let Some(val) = extract_field(data, b"Cpu0ClkTck") { + let mut hz = 0u64; + let mut seen = false; + for &b in val.as_bytes() { + let d = match b { + b'0'..=b'9' => Some(u64::from(b - b'0')), + b'a'..=b'f' => Some(u64::from(b - b'a' + 10)), + b'A'..=b'F' => Some(u64::from(b - b'A' + 10)), + _ => None, + }; + match d { + Some(d) => { + hz = hz * 16 + d; + seen = true; + }, + None if seen => break, + None => {}, + } + } + if hz > 0 { + #[allow(clippy::cast_possible_truncation)] + return Some((hz / 1_000_000) as u32); + } + } + None +} + +/// Parse CPU model name from `/proc/cpuinfo` and append frequency. +fn get_model_name() -> Option { + let mut buf = [0u8; 2048]; + let n = read_file_fast("/proc/cpuinfo", &mut buf).ok()?; + let data = &buf[..n]; + + let base = extract_name(data)?; + let mut name = base; + if let Some(mhz) = get_cpu_freq_mhz() { + name.push_str(" @ "); + // Round to nearest 0.01 GHz, then split so carries (e.g. 1999 MHz) + // roll into the integer part instead of overflowing the fraction. + let rounded_centesimal = (mhz + 5) / 10; + let ghz_int = rounded_centesimal / 100; + let ghz_frac = rounded_centesimal % 100; + write_u64(&mut name, u64::from(ghz_int)); + name.push('.'); + if ghz_frac < 10 { + name.push('0'); + } + write_u64(&mut name, u64::from(ghz_frac)); + name.push_str(" GHz"); + } + Some(name) +} + +/// Extract a human-readable CPU name. Tries cpuinfo fields first, then +/// falls back to the device-tree `compatible` string on SoCs that don't +/// expose a model through cpuinfo. +fn extract_name(data: &[u8]) -> Option { + for key in &[ + b"model name" as &[u8], + b"Model Name", + b"uarch", + b"cpu model", + b"isa", + b"cpu", + b"machine", + b"vendor_id", + ] { + if let Some(val) = extract_field(data, key) { + let trimmed = trim(val); + if !trimmed.is_empty() { + return Some(String::from(trimmed)); + } + } + } + parse_dt_compatible() +} + +/// Parse the SoC name from `/sys/firmware/devicetree/base/compatible`. +/// The file holds NUL-separated `vendor,model` strings from most-specific +/// (board) to most-generic (SoC); we take the last entry and return just +/// the model portion after the comma. +fn parse_dt_compatible() -> Option { + let mut buf = [0u8; 256]; + let n = read_file_fast("/sys/firmware/devicetree/base/compatible", &mut buf) + .ok()?; + // Drop the terminating NUL so the rposition below locates the entry + // separator rather than the end-of-string marker. + let end = if n > 0 && buf[n - 1] == 0 { n - 1 } else { n }; + let data = &buf[..end]; + let start = data.iter().rposition(|&b| b == 0).map_or(0, |p| p + 1); + let entry = &data[start..]; + let comma = entry.iter().position(|&b| b == b',')?; + let model = core::str::from_utf8(&entry[comma + 1..]).ok()?; + if model.is_empty() { + None + } else { + Some(String::from(model)) + } +} + +/// Extract value of first occurrence of `key` in cpuinfo. +fn extract_field<'a>(data: &'a [u8], key: &[u8]) -> Option<&'a str> { + let mut i = 0; + while i < data.len() { + let remaining = &data[i..]; + let eol = remaining + .iter() + .position(|&b| b == b'\n') + .unwrap_or(remaining.len()); + let line = &remaining[..eol]; + + if line.starts_with(key) { + let mut p = key.len(); + while p < line.len() && (line[p] == b'\t' || line[p] == b' ') { + p += 1; + } + if p < line.len() && line[p] == b':' { + p += 1; + while p < line.len() && line[p] == b' ' { + p += 1; + } + return core::str::from_utf8(&line[p..]).ok(); + } + } + + i += eol + 1; + } + None +} + +/// Strip noise from model names. +fn trim(name: &str) -> &str { + let b = name.as_bytes(); + let mut end = b.len(); + + while end > 0 && b[end - 1].is_ascii_whitespace() { + end -= 1; + } + + if end >= 10 && &b[end - 10..end] == b" Processor" { + end -= 10; + } else if end >= 4 && &b[end - 4..end] == b" CPU" { + end -= 4; + } + while end > 0 && b[end - 1].is_ascii_whitespace() { + end -= 1; + } + + if end >= 3 && &b[end - 3..end] == b"(R)" { + end -= 3; + } else if end >= 4 + && (&b[end - 4..end] == b"(TM)" || &b[end - 4..end] == b"(tm)") + { + end -= 4; + } + while end > 0 && b[end - 1].is_ascii_whitespace() { + end -= 1; + } + + if end > 7 && &b[end - 5..end] == b"-Core" { + let mut p = end - 5; + while p > 0 && b[p - 1].is_ascii_digit() { + p -= 1; + } + if p > 0 && b[p - 1] == b' ' { + end = p - 1; + } + } + while end > 0 && b[end - 1].is_ascii_whitespace() { + end -= 1; + } + + let mut start = 0; + while start < end && b[start].is_ascii_whitespace() { + start += 1; + } + + &name[start..end] +} diff --git a/crates/lib/src/lib.rs b/crates/lib/src/lib.rs index 1d89f8d..d33bee1 100644 --- a/crates/lib/src/lib.rs +++ b/crates/lib/src/lib.rs @@ -2,6 +2,7 @@ extern crate alloc; pub mod colors; +pub mod cpu; pub mod desktop; pub mod release; pub mod system; @@ -23,6 +24,7 @@ pub use microfetch_asm::{ sys_close, sys_open, sys_read, + sys_sched_getaffinity, sys_statfs, sys_sysinfo, sys_uname, @@ -281,6 +283,8 @@ struct Fields { user_info: String, os_name: String, kernel_version: String, + cpu_name: String, + cpu_cores: String, shell: String, uptime: String, desktop: String, @@ -320,7 +324,7 @@ impl core::fmt::Write for StackWriter<'_> { } /// Custom logo art embedded at compile time via the `MICROFETCH_LOGO` -/// environment variable. Set it to 9 newline-separated lines of ASCII/Unicode +/// environment variable. Set it to 11 newline-separated lines of ASCII/Unicode /// art when building to replace the default NixOS logo: /// /// `MICROFETCH_LOGO="$(cat my_logo.txt)"` cargo build --release @@ -338,6 +342,8 @@ fn print_system_info(fields: &Fields) -> Result<(), Error> { user_info, os_name, kernel_version, + cpu_name, + cpu_cores, shell, uptime, desktop, @@ -349,7 +355,7 @@ fn print_system_info(fields: &Fields) -> Result<(), Error> { let no_color = colors::is_no_color(); let c = colors::Colors::new(no_color); - let mut buf = [0u8; 2048]; + let mut buf = [0u8; 2560]; let mut w = StackWriter::new(&mut buf); if CUSTOM_LOGO.is_empty() { @@ -357,24 +363,25 @@ fn print_system_info(fields: &Fields) -> Result<(), Error> { core::fmt::write( &mut w, format_args!( - "\n {b} ▟█▖ {cy}▝█▙ ▗█▛ {user_info} ~{rs}\n {b} \ - ▗▄▄▟██▄▄▄▄▄{cy}▝█▙█▛ {b}▖ {cy}\u{F313} {b}System{rs} \ - {os_name}\n {b} ▀▀▀▀▀▀▀▀▀▀▀▘{cy}▝██ {b}▟█▖ {cy}\u{E712} \ - {b}Kernel{rs} {kernel_version}\n {cy} ▟█▛ \ - {cy}▝█▘{b}▟█▛ {cy}\u{E795} {b}Shell{rs} {shell}\n \ - {cy}▟█████▛ {b}▟█████▛ {cy}\u{F017} {b}Uptime{rs} \ - {uptime}\n {cy} ▟█▛{b}▗█▖ {b}▟█▛ {cy}\u{F2D2} \ - {b}Desktop{rs} {desktop}\n {cy} ▝█▛ \ - {b}██▖{cy}▗▄▄▄▄▄▄▄▄▄▄▄ {cy}\u{F035B} {b}Memory{rs} \ - {memory_usage}\n {cy} ▝ {b}▟█▜█▖{cy}▀▀▀▀▀██▛▀▀▘ \ - {cy}\u{F194E} {b}Storage (/){rs} {storage}\n {b} ▟█▘ ▜█▖ \ - {cy}▝█▛ {cy}\u{E22B} {b}Colors{rs} {colors}\n\n", + "\n {b}⠀⠀⠀⠀⠀⠀⢼⣿⣄⠀⠀⠀{cy}⠹⣿⣷⡀⠀⣠⣿⡧⠀⠀⠀⠀⠀⠀{rs} {user_info} ~{rs}\ + \n {b}⠀⠀⠀⠀⠀⠀⠈⢿⣿⣆⠀⠀⠀{cy}⠘⣿⣿⣴⣿⡿⠁⠀⠀⠀⠀⠀⠀{rs} {cy}\u{F313} {b}System{rs} \u{E621} {os_name}\ + \n {b}⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡜{cy}⢿⣿⣟⠀⠀⠀{b}⢀⡄⠀⠀⠀{rs} {cy}\u{E712} {b}Kernel{rs} \u{E621} {kernel_version}\ + \n {b}⠀⠀⠀⠉⠉⠉⠉{cy}⣩⣭⡭{b}⠉⠉⠉⠉⠉{cy}⠈⢿⣿⣆⠀{b}⢠⣿⣿⠂⠀⠀{rs} {cy}\u{F2DB} {b}CPU{rs} \u{E621} {cpu_name}\ + \n {cy}⠀⠀⠀⠀⠀⠀⣼⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⢻⡟{b}⣡⣿⣿⠃⠀⠀⠀{rs} {cy}\u{F4BC} {b}Topology{rs} \u{E621} {cpu_cores}\ + \n {cy}⢸⣿⣿⣿⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀{b}⣰⣿⣿⣿⣿⣿⣿⡇{rs} {cy}\u{E795} {b}Shell{rs} \u{E621} {shell}\ + \n {cy}⠀⠀⠀⢠⣿⣿⢋{b}⣼⣧⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⡟⠀⠀⠀⠀⠀⠀{rs} {cy}\u{F017} {b}Uptime{rs} \u{E621} {uptime}\ + \n {cy}⠀⠀⠠⣿⣿⠃⠀{b}⠹⣿⣷⡀{cy}⣀⣀⣀⣀⣀{b}⣚⣛⣋{cy}⣀⣀⣀⣀⠀⠀⠀{rs} {cy}\u{F2D2} {b}Desktop{rs} \u{E621} {desktop}\ + \n {cy}⠀⠀⠀⠘⠁⠀⠀⠀{b}⣽⣿⣷⡜{cy}⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⠀⠀⠀{rs} {cy}\u{F035B} {b}Memory{rs} \u{E621} {memory_usage}\ + \n {b}⠀⠀⠀⠀⠀⠀⢀⣾⣿⠟⣿⣿⡄⠀⠀⠀{cy}⠹⣿⣷⡀⠀⠀⠀⠀⠀⠀{rs} {cy}\u{F194E} {b}Storage (/){rs} \u{E621} {storage}\ + \n {b}⠀⠀⠀⠀⠀⠀⢺⣿⠋⠀⠈⢿⣿⣆⠀⠀⠀{cy}⠙⣿⡗⠀⠀⠀⠀⠀⠀{rs} {cy}\u{E22B} {b}Colors{rs} \u{E621} {colors}\n\n", b = c.blue, cy = c.cyan, rs = c.reset, user_info = user_info, os_name = os_name, kernel_version = kernel_version, + cpu_name = cpu_name, + cpu_cores = cpu_cores, shell = shell, uptime = uptime, desktop = desktop, @@ -385,39 +392,89 @@ fn print_system_info(fields: &Fields) -> Result<(), Error> { ) .ok(); } else { - // Custom logo is 9 lines from MICROFETCH_LOGO env var, one per info row. - // Lines beyond 9 are ignored; missing lines render as empty. + // Custom logo is 11 lines from MICROFETCH_LOGO env var, one per info row. + // Lines beyond 11 are ignored; missing lines render as empty. let mut lines = CUSTOM_LOGO.split('\n'); - let logo_rows: [&str; 9] = + let logo_rows: [&str; 11] = core::array::from_fn(|_| lines.next().unwrap_or("")); // Row format mirrors the default logo path exactly. - let rows: [(&str, &str, &str, &str, &str); 9] = [ + let rows: [(&str, &str, &str, &str, &str); 11] = [ ("", "", user_info.as_str(), " ", " ~"), - ("\u{F313} ", "System", os_name.as_str(), " ", ""), + ( + "\u{F313} ", + "System", + os_name.as_str(), + " \u{E621} ", + "", + ), ( "\u{E712} ", "Kernel", kernel_version.as_str(), - " ", + " \u{E621} ", + "", + ), + ( + "\u{F2DB} ", + "CPU", + cpu_name.as_str(), + " \u{E621} ", + "", + ), + ( + "\u{F4BC} ", + "Topology", + cpu_cores.as_str(), + " \u{E621} ", + "", + ), + ( + "\u{E795} ", + "Shell", + shell.as_str(), + " \u{E621} ", + "", + ), + ( + "\u{F017} ", + "Uptime", + uptime.as_str(), + " \u{E621} ", + "", + ), + ( + "\u{F2D2} ", + "Desktop", + desktop.as_str(), + " \u{E621} ", "", ), - ("\u{E795} ", "Shell", shell.as_str(), " ", ""), - ("\u{F017} ", "Uptime", uptime.as_str(), " ", ""), - ("\u{F2D2} ", "Desktop", desktop.as_str(), " ", ""), ( "\u{F035B} ", "Memory", memory_usage.as_str(), - " ", + " \u{E621} ", + "", + ), + ( + "\u{F194E} ", + "Storage (/)", + storage.as_str(), + " \u{E621} ", + "", + ), + ( + "\u{E22B} ", + "Colors", + colors.as_str(), + " \u{E621} ", "", ), - ("\u{F194E} ", "Storage (/)", storage.as_str(), " ", ""), - ("\u{E22B} ", "Colors", colors.as_str(), " ", ""), ]; core::fmt::write(&mut w, format_args!("\n")).ok(); - for i in 0..9 { + for i in 0..11 { let (icon, key, value, spacing, suffix) = rows[i]; if key.is_empty() { // Row 1 has no icon/key, just logo + user_info @@ -534,6 +591,8 @@ pub unsafe fn run(argc: i32, argv: *const *const u8) -> Result<(), Error> { user_info: system::get_username_and_hostname(&utsname), os_name: release::get_os_pretty_name()?, kernel_version: release::get_system_info(&utsname), + cpu_name: cpu::get_cpu_name(), + cpu_cores: cpu::get_cpu_cores()?, shell: system::get_shell(), desktop: desktop::get_desktop_info(), uptime: uptime::get_current()?, diff --git a/crates/lib/src/system.rs b/crates/lib/src/system.rs index b902dac..1db8510 100644 --- a/crates/lib/src/system.rs +++ b/crates/lib/src/system.rs @@ -149,7 +149,7 @@ fn round_f64(x: f64) -> f64 { } /// Write a u64 to string -fn write_u64(s: &mut String, mut n: u64) { +pub fn write_u64(s: &mut String, mut n: u64) { if n == 0 { s.push('0'); return; diff --git a/docs/README.md b/docs/README.md index c62aecf..6e08a06 100644 --- a/docs/README.md +++ b/docs/README.md @@ -328,7 +328,9 @@ person about current issues. To list a few, special thanks to: - [@sioodmy](https://github.com/sioodmy) - Being cute - [@mewoocat](https://github.com/mewoocat) - The awesome NixOS logo ASCII used in Microfetch -- [@uzaaft](https://github.com/uzaaft) - Helping me going faster +- [@uzaaft](https://github.com/uzaaft) - Helping me go faster +- [@amaanq](https://github.com/amaanq) - Helping me go faster, smaller, and + prettier. Also the new logo and CPU field. Additionally a big thank you to everyone who used, talked about or criticized Microfetch. I might have missed your name here, but you have my thanks. diff --git a/flake.lock b/flake.lock index e5578c1..b320b66 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1770562336, - "narHash": "sha256-ub1gpAONMFsT/GU2hV6ZWJjur8rJ6kKxdm9IlCT0j84=", + "lastModified": 1776877367, + "narHash": "sha256-EHq1/OX139R1RvBzOJ0aMRT3xnWyqtHBRUBuO1gFzjI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d6c71932130818840fc8fe9509cf50be8c64634f", + "rev": "0726a0ecb6d4e08f6adced58726b95db924cef57", "type": "github" }, "original": { @@ -18,7 +18,28 @@ }, "root": { "inputs": { - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1777346187, + "narHash": "sha256-oVxyGjpiIsrXhWTJVUOs38fZQkLjd0nZGOY9K7Kfot8=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "146e7bf7569b8288f24d41d806b9f584f7cfd5b5", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" } } }, diff --git a/flake.nix b/flake.nix index 90978a2..50d00b9 100644 --- a/flake.nix +++ b/flake.nix @@ -1,25 +1,31 @@ { - description = "A microscopic fetch script in Rust, for NixOS systems"; - inputs.nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable"; + description = "Microscopic fetch script in Rust, for NixOS systems"; + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; outputs = { self, nixpkgs, + rust-overlay, }: let systems = ["x86_64-linux" "aarch64-linux"]; forEachSystem = nixpkgs.lib.genAttrs systems; - pkgsForEach = nixpkgs.legacyPackages; + pkgsForEach = system: nixpkgs.legacyPackages.${system}.extend rust-overlay.overlays.default; in { packages = forEachSystem (system: let - pkgs = pkgsForEach.${system}; + pkgs = pkgsForEach system; in { default = self.packages.${system}.microfetch; microfetch = pkgs.callPackage ./nix/package.nix {}; - microfetch-mold = pkgs.callPackage ./nix/package.nix {useMold = true;}; }); devShells = forEachSystem (system: { - default = pkgsForEach.${system}.callPackage ./nix/shell.nix {}; + default = (pkgsForEach system).callPackage ./nix/shell.nix {}; }); }; } diff --git a/microfetch/Cargo.toml b/microfetch/Cargo.toml index 43d4f9c..b5532d1 100644 --- a/microfetch/Cargo.toml +++ b/microfetch/Cargo.toml @@ -8,10 +8,10 @@ rust-version.workspace = true license.workspace = true readme = "../docs/README.md" repository = "https://github.com/notashelf/microfetch" -publish = false +publish = true [dependencies] -hotpath = { optional = true, version = "0.14.0" } +hotpath = { optional = true, version = "0.16.1" } microfetch-alloc.workspace = true microfetch-asm.workspace = true microfetch-lib.workspace = true diff --git a/microfetch/src/main.rs b/microfetch/src/main.rs index fcefcc8..d1b39b0 100644 --- a/microfetch/src/main.rs +++ b/microfetch/src/main.rs @@ -1,14 +1,23 @@ #![no_std] #![no_main] +#![cfg_attr( + any( + target_arch = "sparc64", + target_arch = "sparc", + target_arch = "mips64", + target_arch = "mips" + ), + feature(asm_experimental_arch) +)] extern crate alloc; use core::{arch::naked_asm, panic::PanicInfo}; use microfetch_alloc::BumpAllocator; +use microfetch_asm::{entry_rust, sys_exit, sys_write}; // Re-export libc replacement functions from asm crate pub use microfetch_asm::{memcpy, memset, strlen}; -use microfetch_asm::{entry_rust, sys_exit, sys_write}; #[cfg(target_arch = "x86_64")] #[unsafe(no_mangle)] @@ -25,6 +34,23 @@ unsafe extern "C" fn _start() { ); } +#[cfg(target_arch = "x86")] +#[unsafe(no_mangle)] +#[unsafe(naked)] +unsafe extern "C" fn _start() { + naked_asm!( + "mov eax, esp", // save original sp (argc/argv) + "and esp, -16", // align stack to 16 bytes + "sub esp, 12", // leave room so that the one-arg push keeps alignment + "push eax", // arg: initial stack pointer + "call {entry_rust}", + "mov ebx, eax", // exit code -> first syscall arg + "mov eax, 1", // SYS_exit + "int 0x80", + entry_rust = sym entry_rust, + ); +} + #[cfg(target_arch = "aarch64")] #[unsafe(no_mangle)] #[unsafe(naked)] @@ -57,6 +83,218 @@ unsafe extern "C" fn _start() { ); } +#[cfg(target_arch = "loongarch64")] +#[unsafe(no_mangle)] +#[unsafe(naked)] +unsafe extern "C" fn _start() { + naked_asm!( + "or $a0, $sp, $zero", + "bstrins.d $sp, $zero, 3, 0", + "bl {entry_rust}", + "or $a0, $a0, $zero", + "li.w $a7, 93", + "syscall 0", + entry_rust = sym entry_rust, + ); +} + +#[cfg(target_arch = "s390x")] +#[unsafe(no_mangle)] +#[unsafe(naked)] +unsafe extern "C" fn _start() { + naked_asm!( + "lgr %r2, %r15", // save original sp (argc/argv) as arg + "aghi %r15, -160", // allocate s390x mandatory stack frame + "lghi %r0, -16", + "ngr %r15, %r0", // align stack to 16 bytes + "brasl %r14, {entry_rust}", + "lghi %r1, 1", // SYS_exit + "svc 0", + entry_rust = sym entry_rust, + ); +} + +// ELFv2 (ppc64le / new BE): entry point is code directly, no function +// descriptor needed. +#[cfg(all(target_arch = "powerpc64", target_abi = "elfv2"))] +#[unsafe(no_mangle)] +#[unsafe(naked)] +unsafe extern "C" fn _start() { + naked_asm!( + // Set up TOC (r2) for ELFv2 by computing from current PC + "bl 0f", + "0: mflr 12", + "addis 2, 12, .TOC. - 0b@ha", + "addi 2, 2, .TOC. - 0b@l", + // Save sp as first arg, set up stack frame + "mr 3, 1", + "clrrdi 1, 1, 4", + "stdu 1, -64(1)", + "bl {entry_rust}", + "nop", + "li 0, 1", + "sc", + entry_rust = sym entry_rust, + ); +} + +// ppc32 has no function descriptors; entry is direct code like ELFv2. +#[cfg(target_arch = "powerpc")] +#[unsafe(no_mangle)] +#[unsafe(naked)] +unsafe extern "C" fn _start() { + naked_asm!( + "mr 3, 1", // first arg = initial sp (argc/argv) + "clrrwi 1, 1, 4", // align sp to 16 bytes + "stwu 1, -64(1)", // reserve a frame with back-chain + "bl {entry_rust}", + "li 0, 1", // SYS_exit + "sc", + entry_rust = sym entry_rust, + ); +} + +// On ELFv1, the ELF entry point must be a function descriptor +// in .opd whose first word is the real code address. We emit both manually via +// `global_asm` so the kernel reads the descriptor and jumps into `_start_impl`. +#[cfg(all(target_arch = "powerpc64", target_abi = "elfv1"))] +core::arch::global_asm!( + ".section .opd, \"aw\"", + ".balign 8", + ".globl _start", + ".type _start, @object", + ".size _start, 24", + "_start:", + " .quad _start_impl", + " .quad .TOC.@tocbase", + " .quad 0", + ".previous", + ".text", + ".type _start_impl, @function", + "_start_impl:", + " mr 3, 1", + " clrrdi 1, 1, 4", + " stdu 1, -64(1)", + " bl entry_rust", + " nop", + " li 0, 1", + " sc", +); + +#[cfg(target_arch = "arm")] +#[unsafe(no_mangle)] +#[unsafe(naked)] +unsafe extern "C" fn _start() { + naked_asm!( + "mov r0, sp", // first arg = original sp (argc/argv) + "bic sp, sp, #7", // align sp to 8 bytes (AAPCS) + "bl {entry_rust}", + "mov r7, #1", // SYS_exit + "svc #0", + entry_rust = sym entry_rust, + ); +} + +#[cfg(target_arch = "riscv32")] +#[unsafe(no_mangle)] +#[unsafe(naked)] +unsafe extern "C" fn _start() { + naked_asm!( + "mv a0, sp", // first arg = original sp (argc/argv) + "andi sp, sp, -16", // align sp to 16 bytes + "call {entry_rust}", + "li a7, 93", // SYS_exit + "ecall", + entry_rust = sym entry_rust, + ); +} + +#[cfg(target_arch = "sparc64")] +#[unsafe(no_mangle)] +#[unsafe(naked)] +unsafe extern "C" fn _start() { + naked_asm!( + // SPARC v9: kernel sets %sp biased by -2047; argc is at + // %sp + 2047 + 128 (bias + 128-byte register save area). + // See glibc sysdeps/sparc/sparc64/start.S. + "mov %g0, %fp", + "add %sp, 2047+128, %o0", // first arg = &argc + "add %sp, 2047, %sp", // unbias + "sub %sp, 176, %sp", // reserve register save area + "and %sp, -16, %sp", // align + "sub %sp, 2047, %sp", // rebias + "call {entry_rust}", + "nop", // delay slot + "mov 1, %g1", // SYS_exit + "t 0x6d", + entry_rust = sym entry_rust, + ); +} + +#[cfg(target_arch = "mips64")] +#[unsafe(no_mangle)] +#[unsafe(naked)] +unsafe extern "C" fn _start() { + naked_asm!( + "move $a0, $sp", // first arg = original sp (argc/argv) + "daddiu $sp, $sp, -16",// reserve + keep 16-byte alignment (N64 ABI) + "jal {entry_rust}", + "nop", // delay slot + "move $a0, $v0", // exit code = entry_rust return value + "li $v0, 5058", // SYS_exit (5000 + 58) + "syscall", + entry_rust = sym entry_rust, + ); +} + +#[cfg(target_arch = "sparc")] +#[unsafe(no_mangle)] +#[unsafe(naked)] +unsafe extern "C" fn _start() { + naked_asm!( + "mov %g0, %fp", + "add %sp, 64, %o0", // first arg = &argc (past the 64-byte window save) + "and %sp, -16, %sp", // align sp to 16 bytes + "sub %sp, 64, %sp", // reserve window save area + "call {entry_rust}", + "nop", // delay slot + "mov 1, %g1", // SYS_exit (code already in %o0) + "ta 0x10", + entry_rust = sym entry_rust, + ); +} + +// SPARC V8 has no 1-byte CAS, so rustc emits calls to libatomic for byte +// atomics. We're single-threaded with panic=abort, so a plain load/store is +// already correct. +#[cfg(all(not(test), target_arch = "sparc"))] +#[unsafe(no_mangle)] +extern "C" fn __atomic_load_1(ptr: *const u8, _order: i32) -> u8 { + unsafe { core::ptr::read_volatile(ptr) } +} + +#[cfg(all(not(test), target_arch = "sparc"))] +#[unsafe(no_mangle)] +extern "C" fn __atomic_store_1(ptr: *mut u8, val: u8, _order: i32) { + unsafe { core::ptr::write_volatile(ptr, val) } +} + +#[cfg(target_arch = "mips")] +#[unsafe(no_mangle)] +#[unsafe(naked)] +unsafe extern "C" fn _start() { + naked_asm!( + "move $a0, $sp", // first arg = original sp (argc/argv) + "addiu $sp, $sp, -24", // reserve 16-byte O32 shadow space + align + "jal {entry_rust}", + "nop", // delay slot + "move $a0, $v0", // exit code = entry_rust return value + "li $v0, 4001", // SYS_exit (4000 + 1) + "syscall", + entry_rust = sym entry_rust, + ); +} + // Global allocator #[global_allocator] static ALLOCATOR: BumpAllocator = BumpAllocator::new(); @@ -110,3 +348,14 @@ const extern "C" fn rust_eh_personality() {} extern "C" fn _Unwind_Resume() -> ! { unsafe { sys_exit(1) } } + +// compiler_builtins emits `.ARM.exidx` entries that reference these even +// with panic=abort. libgcc/libunwind would normally resolve them; we're +// nostdlib, so we stub them. They're never called. +#[cfg(all(not(test), target_arch = "arm"))] +#[unsafe(no_mangle)] +extern "C" fn __aeabi_unwind_cpp_pr0() {} + +#[cfg(all(not(test), target_arch = "arm"))] +#[unsafe(no_mangle)] +extern "C" fn __aeabi_unwind_cpp_pr1() {} diff --git a/nix/package.nix b/nix/package.nix index 2658d73..1657fd7 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -1,13 +1,20 @@ { lib, - rustPlatform, + makeRustPlatform, + rust-bin, llvm, }: let pname = "microfetch"; toml = (lib.importTOML ../Cargo.toml).workspace.package; inherit (toml) version; + + toolchain = rust-bin.stable.latest; + rustWithToolchain = makeRustPlatform { + cargo = toolchain.minimal; + rustc = toolchain.minimal; + }; in - rustPlatform.buildRustPackage.override {inherit (llvm) stdenv;} { + rustWithToolchain.buildRustPackage.override {inherit (llvm) stdenv;} { inherit pname version; src = let fs = lib.fileset; @@ -16,9 +23,9 @@ in fs.toSource { root = s; fileset = fs.unions [ + (s + /.cargo) (s + /crates) (s + /microfetch) - (s + /.cargo) (s + /scripts/ld-wrapper) (s + /Cargo.lock) (s + /Cargo.toml)