diff --git a/crates/asm/src/lib.rs b/crates/asm/src/lib.rs index b16d7b2..3b27962 100644 --- a/crates/asm/src/lib.rs +++ b/crates/asm/src/lib.rs @@ -20,6 +20,8 @@ feature(asm_experimental_arch) )] +#[cfg(not(target_os = "macos"))] use core::ffi::c_void; + // Per-arch syscall implementations live in their own module files. // macOS is matched first; there `target_arch` is `aarch64`, but the // implementation is libSystem-backed rather than raw `svc` traps. @@ -52,6 +54,23 @@ pub use arch::{ macos_uptime_secs, }; +#[cfg(not(target_os = "macos"))] +const WORD: usize = size_of::(); + +/// Whether an unaligned `usize` load is a single instruction here. SPARC and +/// MIPS trap on one, and riscv64 hardware may emulate it in the trap handler, +/// so those copy in words only when source and destination share alignment. +#[cfg(not(target_os = "macos"))] +const UNALIGNED_WORDS: bool = cfg!(any( + target_arch = "x86_64", + target_arch = "x86", + target_arch = "aarch64", + target_arch = "powerpc64", + target_arch = "powerpc", + target_arch = "s390x", + target_arch = "loongarch64", +)); + /// Copies `n` bytes from `src` to `dest`. /// /// # Safety @@ -62,14 +81,35 @@ pub use arch::{ // clash at link time, so the freestanding implementations are Linux-only. #[cfg(not(target_os = "macos"))] #[unsafe(no_mangle)] +#[allow(clippy::cast_ptr_alignment)] pub unsafe extern "C" fn memcpy( - dest: *mut u8, - src: *const u8, + dest: *mut c_void, + src: *const c_void, n: usize, -) -> *mut u8 { - for i in 0..n { - unsafe { - *dest.add(i) = *src.add(i); +) -> *mut c_void { + let dest_bytes = dest.cast::(); + let src_bytes = src.cast::(); + let mut i = 0; + unsafe { + if UNALIGNED_WORDS { + while n - i >= WORD { + let word = src_bytes.add(i).cast::().read_unaligned(); + dest_bytes.add(i).cast::().write_unaligned(word); + i += WORD; + } + } else if dest_bytes as usize % WORD == src_bytes as usize % WORD { + while i < n && !(dest_bytes.add(i) as usize).is_multiple_of(WORD) { + *dest_bytes.add(i) = *src_bytes.add(i); + i += 1; + } + while n - i >= WORD { + *dest_bytes.add(i).cast::() = *src_bytes.add(i).cast::(); + i += WORD; + } + } + while i < n { + *dest_bytes.add(i) = *src_bytes.add(i); + i += 1; } } dest @@ -83,10 +123,36 @@ pub unsafe extern "C" fn memcpy( /// The value in `c` is treated as unsigned (lower 8 bits used). #[cfg(not(target_os = "macos"))] #[unsafe(no_mangle)] -pub unsafe extern "C" fn memset(s: *mut u8, c: i32, n: usize) -> *mut u8 { - for i in 0..n { - unsafe { - *s.add(i) = u8::try_from(c).unwrap_or(0); +#[allow(clippy::cast_ptr_alignment)] +pub unsafe extern "C" fn memset( + s: *mut c_void, + c: i32, + n: usize, +) -> *mut c_void { + let bytes = s.cast::(); + let value = c.to_le_bytes()[0]; + let mut i = 0; + unsafe { + if UNALIGNED_WORDS { + let word = usize::from_ne_bytes([value; WORD]); + while n - i >= WORD { + bytes.add(i).cast::().write_unaligned(word); + i += WORD; + } + } else { + while i < n && !(bytes.add(i) as usize).is_multiple_of(WORD) { + *bytes.add(i) = value; + i += 1; + } + let word = usize::from_ne_bytes([value; WORD]); + while n - i >= WORD { + *bytes.add(i).cast::() = word; + i += WORD; + } + } + while i < n { + *bytes.add(i) = value; + i += 1; } } s @@ -99,10 +165,16 @@ pub unsafe extern "C" fn memset(s: *mut u8, c: i32, n: usize) -> *mut u8 { /// `s1` and `s2` must be valid pointers to memory of at least `n` bytes. #[cfg(not(target_os = "macos"))] #[unsafe(no_mangle)] -pub unsafe extern "C" fn bcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 { +pub unsafe extern "C" fn bcmp( + s1: *const c_void, + s2: *const c_void, + n: usize, +) -> i32 { + let bytes1 = s1.cast::(); + let bytes2 = s2.cast::(); for i in 0..n { - let a = unsafe { *s1.add(i) }; - let b = unsafe { *s2.add(i) }; + let a = unsafe { *bytes1.add(i) }; + let b = unsafe { *bytes2.add(i) }; if a != b { return i32::from(a) - i32::from(b); } @@ -117,7 +189,11 @@ pub unsafe extern "C" fn bcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 { /// `s1` and `s2` must be valid pointers to memory of at least `n` bytes. #[cfg(not(target_os = "macos"))] #[unsafe(no_mangle)] -pub unsafe extern "C" fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 { +pub unsafe extern "C" fn memcmp( + s1: *const c_void, + s2: *const c_void, + n: usize, +) -> i32 { unsafe { bcmp(s1, s2, n) } } @@ -128,7 +204,7 @@ pub unsafe extern "C" fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> i32 { /// `s` must be a valid pointer to a null-terminated string. #[cfg(not(target_os = "macos"))] #[unsafe(no_mangle)] -pub const unsafe extern "C" fn strlen(s: *const u8) -> usize { +pub const unsafe extern "C" fn strlen(s: *const i8) -> usize { let mut len = 0; while unsafe { *s.add(len) } != 0 { len += 1; @@ -619,3 +695,39 @@ pub unsafe fn sys_sched_getaffinity( pub unsafe fn sys_exit(code: i32) -> ! { unsafe { arch::sys_exit(code) } } + +// The freestanding memory symbols exist everywhere but macOS, where +// libSystem supplies them instead. +#[cfg(all(test, not(target_os = "macos")))] +mod tests { + use super::*; + + #[test] + fn runtime_memory_symbols_match_c_semantics() { + let src = [1u8, 2, 3, 4]; + let mut dest = [0u8; 4]; + + unsafe { + memcpy(dest.as_mut_ptr().cast(), src.as_ptr().cast(), src.len()); + } + assert_eq!(dest, src); + assert_eq!( + unsafe { memcmp(dest.as_ptr().cast(), src.as_ptr().cast(), 4) }, + 0 + ); + + unsafe { + memset(dest.as_mut_ptr().cast(), -1, dest.len()); + } + assert_eq!(dest, [u8::MAX; 4]); + assert_ne!( + unsafe { bcmp(dest.as_ptr().cast(), src.as_ptr().cast(), 4) }, + 0 + ); + } + + #[test] + fn runtime_strlen_uses_c_char_pointer() { + assert_eq!(unsafe { strlen(c"microfetch".as_ptr().cast()) }, 10); + } +} diff --git a/crates/benchmarks/benches/microfetch.rs b/crates/benchmarks/benches/microfetch.rs index 7755786..a44bf68 100644 --- a/crates/benchmarks/benches/microfetch.rs +++ b/crates/benchmarks/benches/microfetch.rs @@ -1,3 +1,5 @@ +use std::{hint::black_box, mem::MaybeUninit}; + use criterion::{Criterion, criterion_group, criterion_main}; use microfetch_lib::{ StackWriter, @@ -16,65 +18,90 @@ fn main_benchmark(c: &mut Criterion) { c.bench_function("user_info", |b| { b.iter(|| { - let mut buf = [0u8; 256]; + let mut buf = [MaybeUninit::uninit(); 256]; let mut w = StackWriter::new(&mut buf); system::write_username_and_hostname(&mut w, &colors, &utsname); + black_box(w.written()); }); }); c.bench_function("os_name", |b| { b.iter(|| { - let mut buf = [0u8; 256]; + let mut buf = [MaybeUninit::uninit(); 256]; let mut w = StackWriter::new(&mut buf); let _ = release::write_os_pretty_name(&mut w); + black_box(w.written()); }); }); c.bench_function("kernel_version", |b| { b.iter(|| { - let mut buf = [0u8; 256]; + let mut buf = [MaybeUninit::uninit(); 256]; let mut w = StackWriter::new(&mut buf); release::write_system_info(&mut w, &utsname); + black_box(w.written()); + }); + }); + c.bench_function("cpu_name", |b| { + b.iter(|| { + let mut buf = [MaybeUninit::uninit(); 256]; + let mut w = StackWriter::new(&mut buf); + cpu::write_cpu_name(&mut w); + black_box(w.written()); + }); + }); + c.bench_function("cpu_cores", |b| { + b.iter(|| { + let mut buf = [MaybeUninit::uninit(); 256]; + let mut w = StackWriter::new(&mut buf); + let _ = cpu::write_cpu_cores(&mut w); + black_box(w.written()); }); }); c.bench_function("shell", |b| { b.iter(|| { - let mut buf = [0u8; 256]; + let mut buf = [MaybeUninit::uninit(); 256]; let mut w = StackWriter::new(&mut buf); system::write_shell(&mut w); + black_box(w.written()); }); }); c.bench_function("desktop", |b| { b.iter(|| { - let mut buf = [0u8; 256]; + let mut buf = [MaybeUninit::uninit(); 256]; let mut w = StackWriter::new(&mut buf); desktop::write_desktop_info(&mut w); + black_box(w.written()); }); }); c.bench_function("uptime", |b| { b.iter(|| { - let mut buf = [0u8; 256]; + let mut buf = [MaybeUninit::uninit(); 256]; let mut w = StackWriter::new(&mut buf); let _ = uptime::write_uptime(&mut w); + black_box(w.written()); }); }); c.bench_function("memory_usage", |b| { b.iter(|| { - let mut buf = [0u8; 256]; + let mut buf = [MaybeUninit::uninit(); 256]; let mut w = StackWriter::new(&mut buf); let _ = system::write_memory_usage(&mut w, &colors); + black_box(w.written()); }); }); c.bench_function("storage", |b| { b.iter(|| { - let mut buf = [0u8; 256]; + let mut buf = [MaybeUninit::uninit(); 256]; let mut w = StackWriter::new(&mut buf); let _ = system::write_root_disk_usage(&mut w, &colors); + black_box(w.written()); }); }); c.bench_function("colors", |b| { b.iter(|| { - let mut buf = [0u8; 256]; + let mut buf = [MaybeUninit::uninit(); 256]; let mut w = StackWriter::new(&mut buf); colors::write_dots(&mut w, &colors); + black_box(w.written()); }); }); } diff --git a/crates/lib/build.rs b/crates/lib/build.rs new file mode 100644 index 0000000..f6bdf9e --- /dev/null +++ b/crates/lib/build.rs @@ -0,0 +1,43 @@ +// Pack logo rows into a marker-delimited stream: +// 1 = blue, 2 = cyan, 0 = row separator. +fn main() { + let rows: &[&[(u8, &str)]] = &[ + &[(0, "⠀⠀⠀⠀⠀⠀⢼⣿⣄⠀⠀⠀"), (1, "⠹⣿⣷⡀⠀⣠⣿⡧⠀⠀⠀⠀⠀⠀")], + &[(0, "⠀⠀⠀⠀⠀⠀⠈⢿⣿⣆⠀⠀⠀"), (1, "⠘⣿⣿⣴⣿⡿⠁⠀⠀⠀⠀⠀⠀")], + &[(0, "⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡜"), (1, "⢿⣿⣟⠀⠀⠀"), (0, "⢀⡄⠀⠀⠀")], + &[ + (0, "⠀⠀⠀⠉⠉⠉⠉"), + (1, "⣩⣭⡭"), + (0, "⠉⠉⠉⠉⠉"), + (1, "⠈⢿⣿⣆⠀"), + (0, "⢠⣿⣿⠂⠀⠀"), + ], + &[(1, "⠀⠀⠀⠀⠀⠀⣼⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⢻⡟"), (0, "⣡⣿⣿⠃⠀⠀⠀")], + &[(1, "⢸⣿⣿⣿⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀"), (0, "⣰⣿⣿⣿⣿⣿⣿⡇")], + &[(1, "⠀⠀⠀⢠⣿⣿⢋"), (0, "⣼⣧⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⡟⠀⠀⠀⠀⠀⠀")], + &[ + (1, "⠀⠀⠠⣿⣿⠃⠀"), + (0, "⠹⣿⣷⡀"), + (1, "⣀⣀⣀⣀⣀"), + (0, "⣚⣛⣋"), + (1, "⣀⣀⣀⣀⠀⠀⠀"), + ], + &[(1, "⠀⠀⠀⠘⠁⠀⠀⠀"), (0, "⣽⣿⣷⡜"), (1, "⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⠀⠀⠀")], + &[(0, "⠀⠀⠀⠀⠀⠀⢀⣾⣿⠟⣿⣿⡄⠀⠀⠀"), (1, "⠹⣿⣷⡀⠀⠀⠀⠀⠀⠀")], + &[(0, "⠀⠀⠀⠀⠀⠀⢺⣿⠋⠀⠈⢿⣿⣆⠀⠀⠀"), (1, "⠙⣿⡗⠀⠀⠀⠀⠀⠀")], + ]; + + let mut out = Vec::new(); + for (i, row) in rows.iter().enumerate() { + for &(color, text) in *row { + out.push(color + 1); + out.extend_from_slice(text.as_bytes()); + } + if i < rows.len() - 1 { + out.push(0); + } + } + + let out_dir = std::env::var("OUT_DIR").unwrap(); + std::fs::write(format!("{out_dir}/logo.bin"), &out).unwrap(); +} diff --git a/crates/lib/src/colors.rs b/crates/lib/src/colors.rs index 5e76298..ead3acd 100644 --- a/crates/lib/src/colors.rs +++ b/crates/lib/src/colors.rs @@ -38,48 +38,23 @@ impl Colors { } } -use core::sync::atomic::{AtomicBool, Ordering}; - -// Check if NO_COLOR is set (only once, lazily) -// Only presence matters; value is irrelevant per the NO_COLOR spec -static NO_COLOR_CHECKED: AtomicBool = AtomicBool::new(false); -static NO_COLOR_SET: AtomicBool = AtomicBool::new(false); - /// Checks if `NO_COLOR` environment variable is set. pub(crate) fn is_no_color() -> bool { - // Fast path: already checked - if NO_COLOR_CHECKED.load(Ordering::Acquire) { - return NO_COLOR_SET.load(Ordering::Relaxed); - } - - // Slow path: check environment - let is_set = crate::env_exists("NO_COLOR"); - NO_COLOR_SET.store(is_set, Ordering::Relaxed); - NO_COLOR_CHECKED.store(true, Ordering::Release); - is_set + crate::env_exists("NO_COLOR") } #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn write_dots(w: &mut StackWriter, colors: &Colors) { - const GLYPH: &str = "●"; - - w.push_str(colors.blue); - w.push_str(GLYPH); - w.push_str(" "); - w.push_str(colors.cyan); - w.push_str(GLYPH); - w.push_str(" "); - w.push_str(colors.green); - w.push_str(GLYPH); - w.push_str(" "); - w.push_str(colors.yellow); - w.push_str(GLYPH); - w.push_str(" "); - w.push_str(colors.red); - w.push_str(GLYPH); - w.push_str(" "); - w.push_str(colors.magenta); - w.push_str(GLYPH); - w.push_str(" "); + for c in [ + colors.blue, + colors.cyan, + colors.green, + colors.yellow, + colors.red, + colors.magenta, + ] { + w.push_str(c); + w.push_str("● "); + } w.push_str(colors.reset); } diff --git a/crates/lib/src/cpu.rs b/crates/lib/src/cpu.rs index 1c8b6a7..08460a0 100644 --- a/crates/lib/src/cpu.rs +++ b/crates/lib/src/cpu.rs @@ -210,43 +210,75 @@ fn format_cpufreq_path(buf: &mut [u8; 64], cpu: u32) -> usize { i + SUFFIX.len() } -/// Read CPU frequency in MHz. Tries sysfs first, then cpuinfo fields. +/// Read one CPU's `cpuinfo_max_freq`, in kHz. #[cfg(target_os = "linux")] -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; +fn read_cpu_max_khz(cpu: u32) -> Option { 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; + let n = format_cpufreq_path(&mut path, cpu); + // SAFETY: the path is a constant prefix, decimal digits, and a constant + // suffix, so it is ASCII by construction. + let p = unsafe { core::str::from_utf8_unchecked(&path[..n]) }; + let mut buf = [0u8; 32]; + let m = read_file_fast(p, &mut buf).ok()?; + let mut khz = 0u32; + for &b in &buf[..m] { + if b.is_ascii_digit() { + khz = khz * 10 + u32::from(b - b'0'); } } + (khz > 0).then_some(khz) +} + +/// Highest CPU id the kernel lists in `/sys/devices/system/cpu/present`, +/// which is an ascending cpulist. +// https://github.com/torvalds/linux/blob/v6.19/drivers/base/cpu.c#L273-L286 +#[cfg(target_os = "linux")] +fn highest_present_cpu() -> Option { + let mut buf = [0u8; 64]; + let n = read_file_fast("/sys/devices/system/cpu/present", &mut buf).ok()?; + let data = &buf[..n]; + let end = data.iter().rposition(u8::is_ascii_digit)? + 1; + let start = data[..end] + .iter() + .rposition(|b| !b.is_ascii_digit()) + .map_or(0, |i| i + 1); + let mut cpu = 0u32; + for &b in &data[start..end] { + cpu = cpu * 10 + u32::from(b - b'0'); + } + Some(cpu) +} + +/// Read CPU frequency in MHz. Tries sysfs first, then cpuinfo data. +#[cfg(target_os = "linux")] +fn get_cpu_freq_mhz(cpuinfo: &[u8]) -> Option { + let last_cpu = highest_present_cpu().unwrap_or(63); + let first_khz = read_cpu_max_khz(0); + let last_khz = if last_cpu == 0 { + first_khz + } else { + read_cpu_max_khz(last_cpu) + }; + + let max_khz = match (first_khz, last_khz) { + (Some(first), Some(last)) if first == last => first, + (first, last) => { + let mut max_khz = first.unwrap_or(0).max(last.unwrap_or(0)); + for cpu in 1..last_cpu { + let Some(khz) = read_cpu_max_khz(cpu) else { + break; + }; + if khz > max_khz { + max_khz = khz; + } + } + max_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", @@ -256,7 +288,7 @@ fn get_cpu_freq_mhz() -> Option { // BogoMIPS on MIPS is calibrated to the clock frequency (unlike x86). b"BogoMIPS", ] { - if let Some(val) = extract_field(data, key) { + if let Some(val) = extract_field(cpuinfo, key) { // Parse integer part of the MHz value (e.g. "5200.00" -> 5200) let mut mhz = 0u32; for &b in val.as_bytes() { @@ -271,7 +303,7 @@ fn get_cpu_freq_mhz() -> Option { // Octeon presets loops_per_jiffy to clock_rate/HZ, so its BogoMIPS is // exactly 2x the core clock, unlike the 1:1 of other MIPS. // https://github.com/torvalds/linux/blob/v6.19/arch/mips/cavium-octeon/csrc-octeon.c#L40 - if *key == b"BogoMIPS" && data.windows(6).any(|w| w == b"Octeon") { + if *key == b"BogoMIPS" && cpuinfo.windows(6).any(|w| w == b"Octeon") { mhz /= 2; } @@ -282,7 +314,7 @@ fn get_cpu_freq_mhz() -> Option { } // SPARC exposes its clock as `Cpu0ClkTck : `, // which signifies ticks per second in hex. - if let Some(val) = extract_field(data, b"Cpu0ClkTck") { + if let Some(val) = extract_field(cpuinfo, b"Cpu0ClkTck") { let mut hz = 0u64; let mut seen = false; for &b in val.as_bytes() { @@ -313,7 +345,7 @@ fn get_cpu_freq_mhz() -> Option { /// Appends CPU frequency if available. #[cfg(target_os = "linux")] fn write_model_name(w: &mut StackWriter) { - let mut buf = [0u8; 2048]; + let mut buf = [0u8; 1024]; let Ok(n) = read_file_fast("/proc/cpuinfo", &mut buf) else { return; }; @@ -324,7 +356,7 @@ fn write_model_name(w: &mut StackWriter) { return; } - let mhz = get_cpu_freq_mhz(); + let mhz = get_cpu_freq_mhz(data); if let Some(name) = name { // x86 `model name` already ends in `@ GHz`, which hides the // ` CPU` that trim() would otherwise strip, so re-trim after cutting. diff --git a/crates/lib/src/lib.rs b/crates/lib/src/lib.rs index 40e69e8..0d446a7 100644 --- a/crates/lib/src/lib.rs +++ b/crates/lib/src/lib.rs @@ -200,20 +200,20 @@ impl UtsName { /// Minimal, stack-allocated writer. pub struct StackWriter<'a> { - buf: &'a mut [u8], + buf: &'a mut [MaybeUninit], pos: usize, } impl<'a> StackWriter<'a> { #[inline] - pub const fn new(buf: &'a mut [u8]) -> Self { + pub const fn new(buf: &'a mut [MaybeUninit]) -> Self { Self { buf, pos: 0 } } #[inline] #[must_use] pub fn written(&self) -> &[u8] { - &self.buf[..self.pos] + unsafe { core::slice::from_raw_parts(self.buf.as_ptr().cast(), self.pos) } } #[inline] @@ -221,17 +221,23 @@ impl<'a> StackWriter<'a> { self.push_bytes(s.as_bytes()); } - #[inline] + #[inline(never)] pub fn push_bytes(&mut self, bytes: &[u8]) { let n = bytes.len().min(self.buf.len() - self.pos); - self.buf[self.pos..self.pos + n].copy_from_slice(&bytes[..n]); + unsafe { + core::ptr::copy_nonoverlapping( + bytes.as_ptr(), + self.buf.as_mut_ptr().add(self.pos).cast(), + n, + ); + } self.pos += n; } #[inline] pub fn push_byte(&mut self, b: u8) { if self.pos < self.buf.len() { - self.buf[self.pos] = b; + self.buf[self.pos].write(b); self.pos += 1; } } @@ -272,192 +278,104 @@ const CUSTOM_LOGO: &str = match option_env!("MICROFETCH_LOGO") { None => "", }; -/// Write the default two-tone NixOS braille logo for one row. -/// Color assignments derived from flood-fill decomposition of the two lambda -/// shapes. -#[allow(clippy::too_many_lines)] -fn write_logo(w: &mut StackWriter, c: &colors::Colors, row: usize) { - let (b, cy) = (c.blue, c.cyan); - match row { - 0 => { - w.push_str(b); - w.push_str("⠀⠀⠀⠀⠀⠀⢼⣿⣄⠀⠀⠀"); - w.push_str(cy); - w.push_str("⠹⣿⣷⡀⠀⣠⣿⡧⠀⠀⠀⠀⠀⠀"); - }, - 1 => { - w.push_str(b); - w.push_str("⠀⠀⠀⠀⠀⠀⠈⢿⣿⣆⠀⠀⠀"); - w.push_str(cy); - w.push_str("⠘⣿⣿⣴⣿⡿⠁⠀⠀⠀⠀⠀⠀"); - }, - 2 => { - w.push_str(b); - w.push_str("⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡜"); - w.push_str(cy); - w.push_str("⢿⣿⣟⠀⠀⠀"); - w.push_str(b); - w.push_str("⢀⡄⠀⠀⠀"); - }, - 3 => { - w.push_str(b); - w.push_str("⠀⠀⠀⠉⠉⠉⠉"); - w.push_str(cy); - w.push_str("⣩⣭⡭"); - w.push_str(b); - w.push_str("⠉⠉⠉⠉⠉"); - w.push_str(cy); - w.push_str("⠈⢿⣿⣆⠀"); - w.push_str(b); - w.push_str("⢠⣿⣿⠂⠀⠀"); - }, - 4 => { - w.push_str(cy); - w.push_str("⠀⠀⠀⠀⠀⠀⣼⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⢻⡟"); - w.push_str(b); - w.push_str("⣡⣿⣿⠃⠀⠀⠀"); - }, - 5 => { - w.push_str(cy); - w.push_str("⢸⣿⣿⣿⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀"); - w.push_str(b); - w.push_str("⣰⣿⣿⣿⣿⣿⣿⡇"); - }, - 6 => { - w.push_str(cy); - w.push_str("⠀⠀⠀⢠⣿⣿⢋"); - w.push_str(b); - w.push_str("⣼⣧⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⡟⠀⠀⠀⠀⠀⠀"); - }, - 7 => { - w.push_str(cy); - w.push_str("⠀⠀⠠⣿⣿⠃⠀"); - w.push_str(b); - w.push_str("⠹⣿⣷⡀"); - w.push_str(cy); - w.push_str("⣀⣀⣀⣀⣀"); - w.push_str(b); - w.push_str("⣚⣛⣋"); - w.push_str(cy); - w.push_str("⣀⣀⣀⣀⠀⠀⠀"); - }, - 8 => { - w.push_str(cy); - w.push_str("⠀⠀⠀⠘⠁⠀⠀⠀"); - w.push_str(b); - w.push_str("⣽⣿⣷⡜"); - w.push_str(cy); - w.push_str("⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⠀⠀⠀"); - }, - 9 => { - w.push_str(b); - w.push_str("⠀⠀⠀⠀⠀⠀⢀⣾⣿⠟⣿⣿⡄⠀⠀⠀"); - w.push_str(cy); - w.push_str("⠹⣿⣷⡀⠀⠀⠀⠀⠀⠀"); - }, - _ => { - w.push_str(b); - w.push_str("⠀⠀⠀⠀⠀⠀⢺⣿⠋⠀⠈⢿⣿⣆⠀⠀⠀"); - w.push_str(cy); - w.push_str("⠙⣿⡗⠀⠀⠀⠀⠀⠀"); - }, - } - w.push_str(c.reset); -} +/// Packed logo rows. `0` separates rows; `1` and `2` select colors. +const LOGO: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/logo.bin")); + +/// Write one row from a packed color-marked byte stream. +#[inline(never)] +fn write_styled_row( + w: &mut StackWriter, + c: &colors::Colors, + stream: &mut &[u8], +) { + let colors = [c.blue, c.cyan, c.reset]; + let data = *stream; + let row_end = data.iter().position(|&b| b == 0).unwrap_or(data.len()); + let row_data = &data[..row_end]; + *stream = data.get(row_end + 1..).unwrap_or(&[]); -// Info row labels -struct RowLabel { - icon: &'static str, - key: &'static str, - spacing: &'static str, + let mut i = 0; + while i < row_data.len() { + if let marker @ 1..=3 = row_data[i] { + w.push_str(colors[(marker - 1) as usize]); + i += 1; + } else { + let chunk_start = i; + while i < row_data.len() && row_data[i] > 3 { + i += 1; + } + w.push_bytes(&row_data[chunk_start..i]); + } + } } -const ROW_LABELS: [Option; 11] = [ - None, // row 0: user@host - Some(RowLabel { - icon: "\u{F313} ", - key: "System", - spacing: " \u{E621} ", - }), - Some(RowLabel { - icon: "\u{E712} ", - key: "Kernel", - spacing: " \u{E621} ", - }), - Some(RowLabel { - icon: "\u{F2DB} ", - key: "CPU", - spacing: " \u{E621} ", - }), - Some(RowLabel { - icon: "\u{F4BC} ", - key: "Topology", - spacing: " \u{E621} ", - }), - Some(RowLabel { - icon: "\u{E795} ", - key: "Shell", - spacing: " \u{E621} ", - }), - Some(RowLabel { - icon: "\u{F017} ", - key: "Uptime", - spacing: " \u{E621} ", - }), - Some(RowLabel { - icon: "\u{F2D2} ", - key: "Desktop", - spacing: " \u{E621} ", - }), - Some(RowLabel { - icon: "\u{F035B} ", - key: "Memory", - spacing: " \u{E621} ", - }), - Some(RowLabel { - icon: "\u{F194E} ", - key: "Storage (/)", - spacing: " \u{E621} ", - }), - Some(RowLabel { - icon: "\u{E22B} ", - key: "Colors", - spacing: " \u{E621} ", - }), -]; +const ROW_COUNT: usize = 11; +const ROW_LABELS: &[u8] = concat!( + "\x02\u{F313} \x01System\x03 \u{E621} \0", + "\x02\u{E712} \x01Kernel\x03 \u{E621} \0", + "\x02\u{F2DB} \x01CPU\x03 \u{E621} \0", + "\x02\u{F4BC} \x01Topology\x03 \u{E621} \0", + "\x02\u{E795} \x01Shell\x03 \u{E621} \0", + "\x02\u{F017} \x01Uptime\x03 \u{E621} \0", + "\x02\u{F2D2} \x01Desktop\x03 \u{E621} \0", + "\x02\u{F035B} \x01Memory\x03 \u{E621} \0", + "\x02\u{F194E} \x01Storage (/)\x03 \u{E621} \0", + "\x02\u{E22B} \x01Colors\x03 \u{E621} \0", +) +.as_bytes(); /// Write one row: logo + label + value. -#[inline] +#[inline(never)] #[allow(clippy::ref_option)] fn write_row( w: &mut StackWriter, c: &colors::Colors, row: usize, - custom_logo: &str, - use_custom: bool, - label: &Option, - write_value: impl FnOnce(&mut StackWriter), - suffix: &str, + custom_logo: Option<&str>, + logo: &mut &[u8], + labels: &mut &[u8], + utsname: &UtsName, ) { w.push_str(" "); - if use_custom { + if let Some(custom_logo) = custom_logo { w.push_str(c.cyan); w.push_str(custom_logo); w.push_str(c.reset); } else { - write_logo(w, c, row); + write_styled_row(w, c, logo); + w.push_str(c.reset); } w.push_str(" "); - if let Some(l) = label { - w.push_str(c.cyan); - w.push_str(l.icon); - w.push_str(c.blue); - w.push_str(l.key); - w.push_str(c.reset); - w.push_str(l.spacing); + if row != 0 { + write_styled_row(w, c, labels); + } + match row { + 0 => { + system::write_username_and_hostname(w, c, utsname); + w.push_str(" ~"); + w.push_str(c.reset); + }, + 1 => { + let _ = release::write_os_pretty_name(w); + }, + 2 => release::write_system_info(w, utsname), + 3 => cpu::write_cpu_name(w), + 4 => { + let _ = cpu::write_cpu_cores(w); + }, + 5 => system::write_shell(w), + 6 => { + let _ = uptime::write_uptime(w); + }, + 7 => desktop::write_desktop_info(w), + 8 => { + let _ = system::write_memory_usage(w, c); + }, + 9 => { + let _ = system::write_root_disk_usage(w, c); + }, + _ => colors::write_dots(w, c), } - write_value(w); - w.push_str(suffix); w.push_byte(b'\n'); } @@ -522,7 +440,7 @@ pub unsafe fn run(argc: i32, argv: *const *const u8) -> Result<(), Error> { let no_color = colors::is_no_color(); let c = colors::Colors::new(no_color); - let mut buf = [0u8; 2560]; + let mut buf = [MaybeUninit::uninit(); 2560]; let mut w = StackWriter::new(&mut buf); // Custom logo is 11 lines from MICROFETCH_LOGO env var, one per info row. @@ -536,104 +454,23 @@ pub unsafe fn run(argc: i32, argv: *const *const u8) -> Result<(), Error> { } else { &[""; 11] // unused, we use LOGO pairs below }; + let mut logo = LOGO; + let mut labels = ROW_LABELS; w.push_byte(b'\n'); - macro_rules! row { - ($idx:expr, $write_value:expr, $suffix:expr) => { - write_row( - &mut w, - &c, - $idx, - if use_custom { logo_lines[$idx] } else { "" }, - use_custom, - &ROW_LABELS[$idx], - $write_value, - $suffix, - ); - }; + for row in 0..ROW_COUNT { + write_row( + &mut w, + &c, + row, + use_custom.then_some(logo_lines[row]), + &mut logo, + &mut labels, + &utsname, + ); } - row!( - 0, - |w: &mut StackWriter| { - system::write_username_and_hostname(w, &c, &utsname); - w.push_str(" ~"); - w.push_str(c.reset); - }, - "" - ); - row!( - 1, - |w: &mut StackWriter| { - let _ = release::write_os_pretty_name(w); - }, - "" - ); - row!( - 2, - |w: &mut StackWriter| { - release::write_system_info(w, &utsname); - }, - "" - ); - row!( - 3, - |w: &mut StackWriter| { - cpu::write_cpu_name(w); - }, - "" - ); - row!( - 4, - |w: &mut StackWriter| { - let _ = cpu::write_cpu_cores(w); - }, - "" - ); - row!( - 5, - |w: &mut StackWriter| { - system::write_shell(w); - }, - "" - ); - row!( - 6, - |w: &mut StackWriter| { - let _ = uptime::write_uptime(w); - }, - "" - ); - row!( - 7, - |w: &mut StackWriter| { - desktop::write_desktop_info(w); - }, - "" - ); - row!( - 8, - |w: &mut StackWriter| { - let _ = system::write_memory_usage(w, &c); - }, - "" - ); - row!( - 9, - |w: &mut StackWriter| { - let _ = system::write_root_disk_usage(w, &c); - }, - "" - ); - row!( - 10, - |w: &mut StackWriter| { - colors::write_dots(w, &c); - }, - "" - ); - w.push_byte(b'\n'); // Single syscall for the entire output. @@ -651,3 +488,28 @@ pub unsafe fn run(argc: i32, argv: *const *const u8) -> Result<(), Error> { Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn stack_writer_truncates_at_capacity() { + let mut buf = [MaybeUninit::uninit(); 4]; + let mut writer = StackWriter::new(&mut buf); + writer.push_bytes(b"abc"); + writer.push_byte(b'd'); + writer.push_str("overflow"); + assert_eq!(writer.written(), b"abcd"); + } + + #[test] + fn styled_rows_advance_the_stream() { + let mut buf = [MaybeUninit::uninit(); 3]; + let mut writer = StackWriter::new(&mut buf); + let mut rows = &b"\x02a\x01b\x03c\0tail"[..]; + write_styled_row(&mut writer, &colors::Colors::new(true), &mut rows); + assert_eq!(writer.written(), b"abc"); + assert_eq!(rows, b"tail"); + } +} diff --git a/microfetch/build.rs b/microfetch/build.rs index 8c63739..658c09e 100644 --- a/microfetch/build.rs +++ b/microfetch/build.rs @@ -14,10 +14,16 @@ fn main() { println!("cargo:rustc-link-arg-bin=microfetch=-nostartfiles"); // Fully static, no dynamic linker, no .interp/.dynsym/.dynamic overhead println!("cargo:rustc-link-arg-bin=microfetch=-static"); + // Clang ignores Rust's PIE selection flag for fully static links. + println!( + "cargo:rustc-link-arg-bin=microfetch=-Wno-unused-command-line-argument" + ); // Remove unreferenced input sections println!("cargo:rustc-link-arg-bin=microfetch=-Wl,--gc-sections"); // Strip all symbol table entries - println!("cargo:rustc-link-arg-bin=microfetch=-Wl,--strip-all"); + if std::env::var("DEBUG").as_deref() != Ok("true") { + println!("cargo:rustc-link-arg-bin=microfetch=-Wl,--strip-all"); + } // Omit the .note.gnu.build-id section println!("cargo:rustc-link-arg-bin=microfetch=-Wl,--build-id=none"); // Disable RELRO (removes relro_padding)