Implement fixed_point module to replace the usage of fixed crate
This commit is contained in:
parent
d2b4664078
commit
2b18c893a8
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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<const FRAC_BITS: u32>(u32);
|
||||
|
||||
impl<const FRAC_BITS: u32> FixedU32<FRAC_BITS> {
|
||||
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<Self> {
|
||||
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<const FRAC_BITS: u32> Add for FixedU32<FRAC_BITS> {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 + rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const FRAC_BITS: u32> Sub for FixedU32<FRAC_BITS> {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
Self(self.0 - rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const FRAC_BITS: u32> Mul for FixedU32<FRAC_BITS> {
|
||||
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<const FRAC_BITS: u32> Div for FixedU32<FRAC_BITS> {
|
||||
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<const FRAC_BITS: u32> fmt::Display for FixedU32<FRAC_BITS> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue