feat(kernel): lazy user FPU: off until first FP insn (illegal-insn trap -> zeroed regs, retry), save on dirty trap, restore on switch, off for threads without + kernel; spawntest per-thread FP check

This commit is contained in:
2026-10-28 03:20:41 +01:00
parent 34a44b5928
commit c256f4ecf3
7 changed files with 302 additions and 1 deletions

View File

@@ -173,6 +173,57 @@ fn encrypted_home(fs: &mut Storage, home_fs: &mut Storage) -> Result<(), &'stati
Ok(())
}
/// Floating-point work that only comes out right if nobody else's
/// registers get in: a sum over `ROUNDS`, yielding (so switching threads)
/// all the way.
fn float_work(step: f64) -> u64 {
const ROUNDS: u32 = 2000;
let mut sum = 0.0f64;
let mut x = core::hint::black_box(step);
for _ in 0..ROUNDS {
sum += x * 0.5;
x += step;
syscall::yield_now();
}
sum.to_bits()
}
/// What `float_work(step)` gives when nothing interferes.
fn float_expected(step: f64) -> u64 {
let mut sum = 0.0f64;
let mut x = step;
for _ in 0..2000 {
sum += x * 0.5;
x += step;
}
sum.to_bits()
}
/// The other thread's answer, once it has one.
static FLOAT_RESULT: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
fn float_thread(_: &'static core::sync::atomic::AtomicU64) -> ! {
FLOAT_RESULT.store(float_work(0.25), core::sync::atomic::Ordering::Release);
syscall::exit_thread()
}
/// Two threads computing with floats at once each get their own registers
/// (the kernel saves and restores them on every switch).
fn floating_point_is_kept_per_thread() -> Result<(), &'static str> {
if !libkos::thread::spawn(&FLOAT_RESULT, float_thread) {
return Err("cannot start a thread");
}
let mine = float_work(1.75);
while FLOAT_RESULT.load(core::sync::atomic::Ordering::Acquire) == 0 {
syscall::yield_now();
}
let theirs = FLOAT_RESULT.load(core::sync::atomic::Ordering::Acquire);
if mine != float_expected(1.75) || theirs != float_expected(0.25) {
return Err("floating-point registers leaked between threads");
}
Ok(())
}
/// How many requests the `flaky` answering now has served.
fn flaky() -> Result<usize, &'static str> {
syscall::request(FLAKY, [flaky::op::WHO, 0, 0, 0]).map(|r| r[0]).map_err(|_| "flaky does not answer")
@@ -246,6 +297,9 @@ fn check() -> Result<(), &'static str> {
revoke_stops_a_running_app(died)?;
libkos::println!("spawntest: revoking stops a running app, allowing lets it run: ok");
floating_point_is_kept_per_thread()?;
libkos::println!("spawntest: floating point is kept per thread: ok");
restarted_after_crashes()?;
libkos::println!("spawntest: a crashed server is restarted, a crash loop backs off: ok");