While studying the kernel crate I noticed that Delta::rem_nanos() and the Div impl for Delta behave differently depending on CONFIG_64BIT.
Both use native Rust % and / under CONFIG_64BIT, and the C helpers div_s64_rem() / div64_s64() otherwise. rem_nanos() shows the pattern:
|
/// Return `self % dividend` where `dividend` is in nanoseconds. |
|
/// |
|
/// The kernel doesn't have any emulation for `s64 % s64` on 32 bit platforms, so this is |
|
/// limited to 32 bit dividends. |
|
#[inline] |
|
pub fn rem_nanos(self, dividend: i32) -> Self { |
|
#[cfg(CONFIG_64BIT)] |
|
{ |
|
Self { |
|
value: self.as_nanos() % i64::from(dividend), |
|
} |
|
} |
|
|
|
#[cfg(not(CONFIG_64BIT))] |
|
{ |
|
let mut rem = 0; |
|
|
|
// SAFETY: `rem` is in the stack, so we can always provide a valid pointer to it. |
|
unsafe { bindings::div_s64_rem(self.as_nanos(), dividend, &mut rem) }; |
|
|
|
Self { |
|
value: i64::from(rem), |
|
} |
|
} |
|
} |
|
} |
Rust's % and / panic on a zero divisor, and on i64::MIN with a divisor of -1. The C helpers do not, so the same safe call is fatal on
one architecture and returns a value on the other. For Div the panic at least follows from the operator the caller wrote. rem_nanos() is an
ordinary method call, so nothing at the call site suggests potential panicking at runtime.
Tested at 28924df2a08f on x86-64 and ARMv7, calling these from a module init.
On ARMv7:
| Call |
Result |
Delta::from_nanos(10).rem_nanos(0) |
returns 10 |
Delta::from_nanos(i64::MIN).rem_nanos(-1) |
returns 0 |
Delta::from_nanos(10) / Delta::from_nanos(0) |
returns 0 |
Delta::from_nanos(i64::MIN) / Delta::from_nanos(-1) |
returns i64::MIN |
Behaviour also depends on the magnitude of the dividend, since do_div() takes different internal paths. Delta::from_nanos(1_i64 << 32).rem_nanos(0) on ARMv7:
[ 0.836144] rust_repro: reproducing Delta::rem_nanos zero-divisor path
[ 0.836262] Division by zero in kernel.
[ 0.836595] CPU: 0 UID: 0 PID: 1 Comm: swapper/0 Not tainted 7.3.0-rc2-00006-g28924df2a08f-dirty #6 VOLUNTARY
[ 0.836694] Hardware name: Generic DT based system
[ 0.836818] Call trace:
[ 0.837331] unwind_backtrace from show_stack+0x10/0x14
[ 0.837903] show_stack from dump_stack_lvl+0x34/0x70
[ 0.837976] dump_stack_lvl from Ldiv0_64+0x8/0x18
[ 0.838002] Ldiv0_64 from div_s64_rem+0xa4/0xbc
[ 0.838024] div_s64_rem from __rust_repro_init+0x64/0xc8
[ 0.838189] rust_repro: Delta::rem_nanos zero-divisor returned 0
The diagnostic ("Division by zero in kernel." plus a stack trace) is printed and the call still returns. The same call on x86-64 is fatal:
[ 13.643086] rust_repro: reproducing Delta::rem_nanos zero-divisor path
[ 13.643091] rust_kernel: panicked at rust/kernel/time.rs:566:24:
[ 13.643091] attempt to calculate the remainder with a divisor of zero
[ 13.643163] ------------[ cut here ]------------
[ 13.643166] kernel BUG at rust/helpers/bug.c:7!
...
[ 13.677145] _RNvNtNtCsi1DYhJShKqC_4core9panicking11panic_const23panic_const_rem_by_zero+0x21/0x30
[ 13.678434] _RNvCs4ivlYrUDNg4_10rust_repro24reproduce_delta_rem_zero+0x37/0x40 [rust_repro]
...
[ 13.710176] Kernel panic - not syncing: Fatal exception
Delta::from_nanos(1_i64 << 32) / Delta::from_nanos(0) behaves the same way: a diagnostic and a return of 0 on ARMv7, panic_const_div_by_zero at time.rs:415 and a fatal panic on x86-64.
|
impl ops::Div for Delta { |
|
type Output = i64; |
|
|
|
#[inline] |
|
fn div(self, rhs: Self) -> Self::Output { |
|
#[cfg(CONFIG_64BIT)] |
|
{ |
|
self.value / rhs.value |
|
} |
|
|
|
#[cfg(not(CONFIG_64BIT))] |
|
{ |
|
// SAFETY: This function is always safe to call regardless of the input values |
|
unsafe { bindings::div64_s64(self.value, rhs.value) } |
|
} |
|
} |
|
} |
Worth noting: the Div impl carries // SAFETY: This function is always safe to call regardless of the input values. That holds for memory safety, but it sits directly above the 32-bit branch that produces the kernel diagnostic. Also, rem_nanos() documents a portability constraint ("limited to 32 bit dividends") but not these cases, and has no # Panics section.
I don't have a view on which way this should be resolved — making 32-bit panic as 64-bit does, or defining non-panicking behaviour for both — so I'm raising it rather than sending a patch. Either way the resulting semantics would be worth documenting, since these are public kernel crate functions and the current docs say nothing about either condition.
Cc @metaspace @ojeda
While studying the
kernelcrate I noticed thatDelta::rem_nanos()and theDivimpl forDeltabehave differently depending onCONFIG_64BIT.Both use native Rust
%and/underCONFIG_64BIT, and the C helpersdiv_s64_rem()/div64_s64()otherwise.rem_nanos()shows the pattern:linux/rust/kernel/time.rs
Lines 557 to 582 in 28924df
Rust's
%and/panic on a zero divisor, and oni64::MINwith a divisor of-1. The C helpers do not, so the same safe call is fatal onone architecture and returns a value on the other. For
Divthe panic at least follows from the operator the caller wrote.rem_nanos()is anordinary method call, so nothing at the call site suggests potential panicking at runtime.
Tested at
28924df2a08fon x86-64 and ARMv7, calling these from a moduleinit.On ARMv7:
Delta::from_nanos(10).rem_nanos(0)Delta::from_nanos(i64::MIN).rem_nanos(-1)Delta::from_nanos(10) / Delta::from_nanos(0)Delta::from_nanos(i64::MIN) / Delta::from_nanos(-1)i64::MINBehaviour also depends on the magnitude of the dividend, since
do_div()takes different internal paths.Delta::from_nanos(1_i64 << 32).rem_nanos(0)on ARMv7:The diagnostic ("Division by zero in kernel." plus a stack trace) is printed and the call still returns. The same call on x86-64 is fatal:
Delta::from_nanos(1_i64 << 32) / Delta::from_nanos(0)behaves the same way: a diagnostic and a return of 0 on ARMv7,panic_const_div_by_zeroattime.rs:415and a fatal panic on x86-64.linux/rust/kernel/time.rs
Lines 408 to 424 in 28924df
Worth noting: the
Divimpl carries// SAFETY: This function is always safe to call regardless of the input values. That holds for memory safety, but it sits directly above the 32-bit branch that produces the kernel diagnostic. Also,rem_nanos()documents a portability constraint ("limited to 32 bit dividends") but not these cases, and has no# Panicssection.I don't have a view on which way this should be resolved — making 32-bit panic as 64-bit does, or defining non-panicking behaviour for both — so I'm raising it rather than sending a patch. Either way the resulting semantics would be worth documenting, since these are public
kernelcrate functions and the current docs say nothing about either condition.Cc @metaspace @ojeda