From 2b18c893a8663557d1f7fc47c16a40b408fd42dc Mon Sep 17 00:00:00 2001 From: Chen Chengjun Date: Mon, 13 Oct 2025 01:57:51 +0000 Subject: [PATCH] Implement fixed_point module to replace the usage of fixed crate --- Cargo.lock | 35 --- kernel/Cargo.toml | 3 - kernel/libs/aster-util/src/fixed_point.rs | 295 ++++++++++++++++++++++ kernel/libs/aster-util/src/lib.rs | 1 + kernel/src/sched/stats/loadavg.rs | 19 +- 5 files changed, 306 insertions(+), 47 deletions(-) create mode 100644 kernel/libs/aster-util/src/fixed_point.rs diff --git a/Cargo.lock b/Cargo.lock index 30d93884c..414d15e1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -220,7 +220,6 @@ dependencies = [ "controlled", "core2", "cpio-decoder", - "fixed", "getset", "hashbrown 0.14.5", "id-alloc", @@ -380,12 +379,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" -[[package]] -name = "az" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" - [[package]] name = "bit_field" version = "0.10.2" @@ -560,12 +553,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" -[[package]] -name = "crunchy" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" - [[package]] name = "ctor" version = "0.1.25" @@ -728,18 +715,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784a4df722dc6267a04af36895398f59d21d07dce47232adf31ec0ff2fa45e67" -[[package]] -name = "fixed" -version = "1.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707070ccf8c4173548210893a0186e29c266901b71ed20cd9e2ca0193dfe95c3" -dependencies = [ - "az", - "bytemuck", - "half", - "typenum", -] - [[package]] name = "fnv" version = "1.0.7" @@ -831,16 +806,6 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -[[package]] -name = "half" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" -dependencies = [ - "cfg-if", - "crunchy", -] - [[package]] name = "hash32" version = "0.2.1" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 8d12720d8..2356874a9 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -63,9 +63,6 @@ inherit-methods-macro = { git = "https://github.com/asterinas/inherit-methods-ma getset = "0.1.2" takeable = "0.2.2" cfg-if = "1.0" -# Fixed point numbers -# TODO: fork this crate to rewrite all the (unnecessary) unsafe usage -fixed = "1.28.0" [target.x86_64-unknown-none.dependencies] tdx-guest = { version = "0.2.1", optional = true } diff --git a/kernel/libs/aster-util/src/fixed_point.rs b/kernel/libs/aster-util/src/fixed_point.rs new file mode 100644 index 000000000..3dda9d557 --- /dev/null +++ b/kernel/libs/aster-util/src/fixed_point.rs @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! A lightweight fixed-point number implementation optimized for kernel use. +//! +//! This crate provides a minimal, safe fixed-point arithmetic implementation +//! designed specifically for kernel development where zero `unsafe` code usage +//! throughout the entire implementation. + +use core::{ + fmt, + ops::{Add, Div, Mul, Sub}, +}; + +/// A generic fixed-point number with `FRAC_BITS` fractional bits. +/// +/// This type represents a non-negative real number using a `u32` for storage, +/// with the lower `FRAC_BITS` representing the fractional part. +/// +/// **Standard arithmetic operations can overflow and wrap around.** This follows +/// Rust's default integer overflow behavior. +/// +/// For safer arithmetic that prevents overflow, use the `saturating_*` methods: +/// - [`saturating_add`](Self::saturating_add) +/// - [`saturating_sub`](Self::saturating_sub) +/// - [`saturating_mul`](Self::saturating_mul) +/// - [`saturating_div`](Self::saturating_div) +/// +/// # Examples +/// +/// ```rust +/// use fixed_point::FixedU32; +/// +/// type FixedU32_8 = FixedU32<8>; +/// let max_val = FixedU32_8::from_raw(u32::MAX); +/// let one = FixedU32_8::saturating_from_num(1); +/// +/// // Standard operations can overflow. +/// let wrapped = max_val + one; +/// +/// // Saturating operations prevent overflow. +/// let saturated = max_val.saturating_add(one); +/// assert_eq!(saturated, max_val); // Stays at maximum. +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct FixedU32(u32); + +impl FixedU32 { + const FRAC_SCALE: u32 = { + // Do not remove or rewrite the const expression below. + // It implicitly prevents users from giving invalid values of `FRAC_BITS` greater + // than 31 because doing so would cause integer overflow during const evaluation. + 1 << FRAC_BITS + }; + + pub const ZERO: Self = Self(0); + pub const ONE: Self = Self(Self::FRAC_SCALE); + const MAX_INT: u32 = u32::MAX >> FRAC_BITS; + + /// Creates a fixed-point number from an integer. + /// + /// If the value is too large to be represented, it will saturate + /// at the maximum representable value. + pub const fn saturating_from_num(val: u32) -> Self { + if val > Self::MAX_INT { + Self(u32::MAX) + } else { + Self(val << FRAC_BITS) + } + } + + /// Creates a fixed-point number from raw bits. + pub const fn from_raw(raw: u32) -> Self { + Self(raw) + } + + /// Gets the raw underlying value. + #[cfg(ktest)] + const fn raw(self) -> u32 { + self.0 + } + + /// Adds two fixed-point numbers, saturating on overflow. + pub const fn saturating_add(self, other: Self) -> Self { + Self(self.0.saturating_add(other.0)) + } + + /// Subtracts two fixed-point numbers, saturating on underflow. + pub const fn saturating_sub(self, other: Self) -> Self { + Self(self.0.saturating_sub(other.0)) + } + + /// Multiplies two fixed-point numbers, saturating on overflow. + pub const fn saturating_mul(self, other: Self) -> Self { + let result = (self.0 as u64 * other.0 as u64) >> FRAC_BITS; + Self(if result > u32::MAX as u64 { + u32::MAX + } else { + result as u32 + }) + } + + /// Divides two fixed-point numbers, saturating on overflow. + /// + /// Returns `None` if division by zero is attempted. + pub const fn saturating_div(self, other: Self) -> Option { + if other.0 == 0 { + return None; + } + + let result = ((self.0 as u64) << FRAC_BITS) / other.0 as u64; + Some(Self(if result > u32::MAX as u64 { + u32::MAX + } else { + result as u32 + })) + } +} + +impl Add for FixedU32 { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0) + } +} + +impl Sub for FixedU32 { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Self(self.0 - rhs.0) + } +} + +impl Mul for FixedU32 { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + let result = (self.0 as u64 * rhs.0 as u64) >> FRAC_BITS; + debug_assert!( + result <= u32::MAX as u64, + "attempt to multiply with overflow" + ); + Self(result as u32) + } +} + +impl Div for FixedU32 { + type Output = Self; + + fn div(self, rhs: Self) -> Self::Output { + let result = ((self.0 as u64) << FRAC_BITS) / rhs.0 as u64; + debug_assert!(result <= u32::MAX as u64, "attempt to divide with overflow"); + Self(result as u32) + } +} + +impl fmt::Display for FixedU32 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}.{:>03}", + self.0 >> FRAC_BITS, + ((self.0 % Self::FRAC_SCALE) as u64) * 1000 / Self::FRAC_SCALE as u64 + )?; + Ok(()) + } +} + +#[cfg(ktest)] +mod tests { + extern crate alloc; + + use alloc::format; + + use ostd::prelude::*; + + use super::*; + + type FixedU32_8 = FixedU32<8>; + type FixedU32_16 = FixedU32<16>; + + #[ktest] + fn creation_methods() { + // Test `saturating_from_num` with normal values + let normal = FixedU32_8::saturating_from_num(42); + assert_eq!(normal.raw(), 42 << 8); + + // Test `saturating_from_num` with overflow. + let max_int = u32::MAX >> 8; // Maximum integer for FixedU32_8 + let at_limit = FixedU32_8::saturating_from_num(max_int); + let over_limit = FixedU32_8::saturating_from_num(max_int + 1); + + assert_eq!(at_limit.raw(), max_int << 8); + assert_eq!(over_limit.raw(), u32::MAX); // Should saturate + + // Test `from_raw` + let half = FixedU32_8::from_raw(128); // 0.5 in 8.8 format + assert_eq!(half.raw(), 128); + } + + #[ktest] + fn basic_arithmetic_methods() { + let a = FixedU32_8::saturating_from_num(3); // 3.0 + let b = FixedU32_8::saturating_from_num(2); // 2.0 + + // Test method-based arithmetic + let sum = a + b; + assert_eq!(sum.raw(), 5 << 8); + + let diff = a - b; + assert_eq!(diff.raw(), 1 << 8); + + let prod = a * b; + assert_eq!(prod.raw(), 6 << 8); + + let quotient = a / b; + assert_eq!(quotient.raw(), 384); + } + + #[ktest] + fn saturating_arithmetic() { + let max_val = FixedU32_8::from_raw(u32::MAX); + let zero = FixedU32_8::ZERO; + let one = FixedU32_8::saturating_from_num(1); + + let result = max_val.saturating_add(one); + assert_eq!(result.raw(), u32::MAX); + + let result = zero.saturating_sub(one); + assert_eq!(result.raw(), 0); + + let large = FixedU32_8::from_raw(u32::MAX / 2); + let result = large.saturating_mul(FixedU32_8::saturating_from_num(3)); + assert_eq!(result.raw(), u32::MAX); + + let result = max_val.saturating_div(FixedU32_8::from_raw(1)).unwrap(); + assert_eq!(result.raw(), u32::MAX); + } + + #[ktest] + #[should_panic(expected = "attempt to divide by zero")] + fn division_by_zero() { + let a = FixedU32_8::saturating_from_num(5); + let zero = FixedU32_8::ZERO; + + let _result = a / zero; + } + + #[ktest] + fn display_formatting() { + // Test integer display + let integer = FixedU32_8::saturating_from_num(42); + let display_str = format!("{}", integer); + assert_eq!(display_str, "42.000"); + + // Test fractional display + let fractional = FixedU32_8::from_raw(384); // 1.5 + let display_str = format!("{}", fractional); + assert_eq!(display_str, "1.500"); + + // Test zero + let zero = FixedU32_8::ZERO; + let display_str = format!("{}", zero); + assert_eq!(display_str, "0.000"); + + // Test with different precision + let high_precision = FixedU32_16::from_raw(98304); // 1.5 in 16.16 format + let display_str = format!("{}", high_precision); + assert_eq!(display_str, "1.500"); + } + + #[ktest] + #[expect(clippy::eq_op)] + fn edge_cases() { + let zero = FixedU32_8::ZERO; + let one = FixedU32_8::saturating_from_num(1); + let val = FixedU32_8::saturating_from_num(42); + + // Zero multiplication + assert_eq!(zero * val, zero); + assert_eq!(val * zero, zero); + + // One multiplication (identity) + assert_eq!(one * val, val); + assert_eq!(val * one, val); + + // Self subtraction + assert_eq!(val - val, zero); + + // Self division + let result = val.div(val); + assert_eq!(result.raw(), one.raw()); + } +} diff --git a/kernel/libs/aster-util/src/lib.rs b/kernel/libs/aster-util/src/lib.rs index 1aa0104a5..fe7691799 100644 --- a/kernel/libs/aster-util/src/lib.rs +++ b/kernel/libs/aster-util/src/lib.rs @@ -9,6 +9,7 @@ extern crate alloc; pub mod coeff; pub mod dup; +pub mod fixed_point; pub mod mem_obj_slice; pub mod per_cpu_counter; pub mod printer; diff --git a/kernel/src/sched/stats/loadavg.rs b/kernel/src/sched/stats/loadavg.rs index 6ce8ce8c1..3e8862022 100644 --- a/kernel/src/sched/stats/loadavg.rs +++ b/kernel/src/sched/stats/loadavg.rs @@ -6,6 +6,7 @@ use core::sync::atomic::{AtomicU64, Ordering::Relaxed}; +use aster_util::fixed_point::FixedU32; use ostd::{ sync::RwLock, timer::{self, TIMER_FREQ}, @@ -14,28 +15,28 @@ use ostd::{ /// Fixed-point representation of the load average. /// /// This is an equivalent of an u32 with 21 bits for the integer part and 11 bits for the fractional part. -pub type FixedPoint = fixed::types::U21F11; +pub type LoadAvgFixed = FixedU32<11>; /// 5 sec intervals const LOAD_FREQ: u64 = 5 * TIMER_FREQ + 1; /// 1/exp(5sec/1min) as fixed-point -const EXP_1: FixedPoint = FixedPoint::from_bits(1884); +const EXP_1: LoadAvgFixed = LoadAvgFixed::from_raw(1884); /// 1/exp(5sec/5min) -const EXP_5: FixedPoint = FixedPoint::from_bits(2014); +const EXP_5: LoadAvgFixed = LoadAvgFixed::from_raw(2014); /// 1/exp(5sec/15min) -const EXP_15: FixedPoint = FixedPoint::from_bits(2037); +const EXP_15: LoadAvgFixed = LoadAvgFixed::from_raw(2037); /// Load average of all CPU cores. /// /// The load average is calculated as an exponential moving average of the load /// over the last 1, 5, and 15 minutes. -static LOAD_AVG: RwLock<[FixedPoint; 3]> = RwLock::new([FixedPoint::ZERO; 3]); +static LOAD_AVG: RwLock<[LoadAvgFixed; 3]> = RwLock::new([LoadAvgFixed::ZERO; 3]); /// Next time the load average will be updated (in jiffies). static LOAD_AVG_NEXT_UPDATE: AtomicU64 = AtomicU64::new(0); /// Returns the calculated load average of the system. -pub fn get_loadavg() -> [FixedPoint; 3] { +pub fn get_loadavg() -> [LoadAvgFixed; 3] { *LOAD_AVG.read() } @@ -59,7 +60,7 @@ where LOAD_AVG_NEXT_UPDATE.store(jiffies + LOAD_FREQ, Relaxed); // Get the fixed-point representation of the load - let new_load = FixedPoint::from_num(get_load()); + let new_load = LoadAvgFixed::saturating_from_num(get_load()); let mut load = LOAD_AVG.write(); @@ -69,6 +70,6 @@ where load[2] = calc_loadavg(load[2], EXP_15, new_load); } -fn calc_loadavg(old_load: FixedPoint, exp: FixedPoint, new_load: FixedPoint) -> FixedPoint { - old_load * exp + new_load * (FixedPoint::ONE - exp) +fn calc_loadavg(old_load: LoadAvgFixed, exp: LoadAvgFixed, new_load: LoadAvgFixed) -> LoadAvgFixed { + old_load * exp + new_load * (LoadAvgFixed::ONE - exp) }