Skip to content

Delta division and rem_nanos panic on 64-bit but return values on 32-bit #1254

Open

Description

@GeorgeAndrou

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:

linux/rust/kernel/time.rs

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.

linux/rust/kernel/time.rs

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

  1. WindDevil commented on Sep 21, 2026

    @WindDevil

    I think I can work on it.

  2. ojeda commented on Sep 27, 2026

    @ojeda
    Member

    Cc @fujita as well.

  3. fujita commented on Sep 28, 2026

    @fujita

    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::MIN divided by -1. Div is an operator, so users expect it to behave like / on i64. rem_nanos() exists because 32-bit has no s64 % s64, so it should behave like %.

    The panic conditions should be documented in a # Panics section.

  4. ojeda commented on Sep 28, 2026

    @ojeda
    Member

    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).

  5. WindDevil commented on Sep 28, 2026

    @WindDevil

    Thanks for the report.感谢您的报告。

    I think the most important thing is that 32-bit and 64-bit behave the same.我认为最重要的是 32 位和 64 位系统运行方式相同。

    Division by zero should panic, the same as normal integer division in Rust. The same applies to i64::MIN divided by -1. Div is an operator, so users expect it to behave like / on i64. rem_nanos() exists because 32-bit has no s64 % s64, so it should behave like %.除以零应该引发 panic,就像 Rust 中的普通整数除法一样。i64 i64::MIN 除以 -1 也同样如此。 Div 是一个运算符,因此用户期望它的行为与 i64 上的 / 类似。rem_nanos rem_nanos() 函数的存在是因为 32 位系统没有 s64 % s64 ,所以它的行为应该与 % 类似。

    The panic conditions should be documented in a # Panics section.恐慌情况应记录在 # Panics 部分。

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    • libRelated to the `rust/` library.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions