Repository navigation
Delta division and rem_nanos panic on 64-bit but return values on 32-bit #1254
Description
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:
Lines 557 to 582 in 28924df
| /// 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.
Lines 408 to 424 in 28924df
| 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
Activity
WindDevil commentedon Sep 21, 2026
I think I can work on it.
ojeda commentedon Sep 27, 2026
Cc @fujita as well.
fujita commentedon Sep 28, 2026
Thanks for the report.
I think the most important thing is that 32-bit and 64-bit behave the same.
Division by zero should panic, the same as normal integer division in Rust. The same applies to
i64::MINdivided by-1.Divis an operator, so users expect it to behave like/oni64.rem_nanos()exists because 32-bit has nos64 % s64, so it should behave like%.The panic conditions should be documented in a
# Panicssection.ojeda commentedon Sep 28, 2026
Yeah, that sounds reasonable to me (by the way, the panics in the normal operator in this case are unconditional, i.e. even if overflow checks are disabled: https://doc.rust-lang.org/reference/expressions/operator-expr.html#r-expr.operator.int-overflow.div).
WindDevil commentedon Sep 28, 2026
Thanks for clarifying the expected behavior. I’ve recently put together a patch to address this issue. I’ll double-check that it handles both panic conditions consistently on 32-bit and 64-bit and that they’re documented in a # Panics section before sending it for review.