Skip to content

Make mxcsr::write and mxcsr::update unsafe - #627

Merged
Freax13 merged 2 commits into
rust-osdev:nextfrom
Garfield1002:fix/mxcsr-write-unsafe
Oct 29, 2026
Merged

Freax13 merged 2 commits into
rust-osdev:nextfrom
Garfield1002:fix/mxcsr-write-unsafe

Conversation

@Garfield1002

@Garfield1002 Garfield1002 commented Oct 26, 2026 •

Copy link
Copy Markdown

registers::mxcsr::write and registers::mxcsr::update are safe functions, and any MxCsr value can be built in safe code, for example MxCsr::default() | MxCsr::ROUNDING_CONTROL_POSITIVE. Loading a value with a non-default rounding mode, unmasked exceptions, DAZ or FTZ breaks an assumption the compiler relies on. Safe code can then observe optimized and unoptimized evaluation of the same expression disagree, or take an unexpected #XM on an unmasked exception. This PR makes both functions unsafe and documents the requirement.

Why this cannot be a safe function

Rust assumes that floating-point code runs in the default floating-point environment. core states this explicitly for the equivalent intrinsic, core::arch::x86_64::_mm_setcsr:

Note that modifying the masking flags, rounding mode, or denormals-are-zero mode flags leads to immediate Undefined Behavior: Rust assumes that these are always in their default state and will optimize accordingly. This even applies when the register is altered and later reset to its original value without any floating-point operations appearing in the source code between those operations (since floating-point operations appearing earlier or later can be reordered).

The primitive docs for f64 likewise define arithmetic as "rounded according to the roundTiesToEven direction defined in IEEE 754-2008". The compiler uses this when it evaluates floating-point operations at compile time.

The inline-assembly rules point the same way. asm.rules.preserved-registers treats only the MXCSR exception flags (PE, UE, OE, ZE, DE, IE) as status flags. The control bits are not something an asm! block may leave changed.

Repro

write's body, taking the raw bits (0x1F80 | (0b10 << 13) is MxCsr::default() | MxCsr::ROUNDING_CONTROL_POSITIVE):

use std::arch::asm;
use std::hint::black_box;

#[inline]
pub fn write(mxcsr: u32) {
    unsafe { asm!("ldmxcsr [{}]", in(reg) &mxcsr, options(nostack, readonly)); }
}

fn third(x: f64) -> f64 { x / 3.0 }

fn main() {
    // Safe code only from here.
    write(0x1F80 | (0b10 << 13));
    let folded = third(1.0);             // may be evaluated at compile time
    let runtime = third(black_box(1.0)); // evaluated on the CPU under the new mode
    println!("folded  = {:.20} ({:#x})", folded, folded.to_bits());
    println!("runtime = {:.20} ({:#x})", runtime, runtime.to_bits());
    println!("same expression, same input, equal: {}", folded == runtime);
}

rustc 1.99.0 (b940084d7 2026-09-28), -C opt-level=3, x86_64 Linux:

folded  = 0.33333333333333331483 (0x3fd5555555555555)
runtime = 0.33333333333333337034 (0x3fd5555555555556)
same expression, same input, equal: false

Rust assumes the default floating-point environment. MxCsr values with
unmasked exceptions, a non-default rounding mode, DAZ or FTZ can be built
in safe code, and loading one makes optimized code disagree with code
evaluated at run time (or trap on an unmasked exception). core documents
changing these bits as immediate undefined behavior (_mm_setcsr), so the
functions that load them cannot be safe.

@Freax13 Freax13 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your contribution!

LGTM, thanks!

Some more reading on this:

@Freax13
Freax13 enabled auto-merge October 29, 2026 18:50
rustdoc is pointing out that write could refer to a function and a
macro (`core::write!`). Clarify that we mean the function.
@Freax13
Freax13 merged commit 2c5980e into rust-osdev:next Oct 29, 2026
15 of 16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants