4

Here's the code sample where I ran into the problem:

pub fn div(x: u32, y: u32) -> u32 {
    x / y
}
pub fn safe_div(x: u32, y: std::num::NonZeroU32) -> u32 {
    x / y.get() // an unchecked division expected
}

Godbolt's rustc 1.47.0 -O generates the same assembly for both functions:

example::div:
        push    rax
        test    esi, esi
        je      .LBB0_2
        mov     eax, edi
        xor     edx, edx
        div     esi
        pop     rcx
        ret
.LBB0_2:
        lea     rdi, [rip + str.0]
        lea     rdx, [rip + .L__unnamed_1]
        mov     esi, 25
        call    qword ptr [rip + core::panicking::panic@GOTPCREL]
        ud2

example::safe_div:
        push    rax
        test    esi, esi
        je      .LBB1_2
        mov     eax, edi
        xor     edx, edx
        div     esi
        pop     rcx
        ret
.LBB1_2:
        lea     rdi, [rip + str.0]
        lea     rdx, [rip + .L__unnamed_2]
        mov     esi, 25
        call    qword ptr [rip + core::panicking::panic@GOTPCREL]
        ud2

However, it is statically known that checking NonZeroU32::get's result against zero is pointless. Can I somehow make the optimizer believe it (maybe with creating new structs for this) in an unsafeless way?

Related GitHub issue #49572

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
passing_through
  • 1,778
  • 12
  • 24
  • This simply isn't an optimization the compiler guarantees. On nightly, you can use `std::intrinsics::unchecked_div()` to skip checking for a division by zero. Please measure first to see if it's really worth it. – Sven Marnach Nov 16 '20 at 10:16
  • Does this answer your question? [Can I disable checking for zero division every time the division happens?](https://stackoverflow.com/questions/42544491/can-i-disable-checking-for-zero-division-every-time-the-division-happens) – Sven Marnach Nov 16 '20 at 10:16
  • 3
    @SvenMarnach actually, I was looking for a "safe" (unlike the suggested question) general way to prove an invariant to the optimizer (unlike the suggested question). Division is just an example to avoid clutter. – passing_through Nov 16 '20 at 10:22
  • You generally don't get many guarantees about what exactly the optimizer is going to do, even if the compiler "knows" some invariant. My recommendation is not to bother, unless you have measured, can prove that the generated code is significantly slower than it could be, and the difference actually matters. – Sven Marnach Nov 16 '20 at 10:27
  • 3
    I don't agree with the duplicate, it's clearly a workaround but it makes sense to answer this question saying this optimization is not guarantee and so to use the linked answer for a unsafe way. Also, I would maybe search/create an issue about that on github – Stargateur Nov 16 '20 at 10:44
  • 1
    Also, even if the optimization is not _guaranteed_, it's a reasonable question whether it's possible to (safely) obtain it in practice. Most performance questions are based on current state of things rather than set-in-stone guarantees. – user4815162342 Nov 16 '20 at 11:30

1 Answers1

2

After I saw your question, I added the needed impls to std, so now the nightly release (and the upcoming 1.51 stable release) supports this!

Godbolt for

pub fn div(x: u32, y: u32) -> u32 {
    x / y
}

pub fn safe_div(x: u32, y: std::num::NonZeroU32) -> u32 {
    x / y
}

pub fn safe_rem(x: u32, y: std::num::NonZeroU32) -> u32 {
    x % y
}

Produces the expected assembly:

example::div:
        push    rax
        test    esi, esi
        je      .LBB0_2
        mov     eax, edi
        xor     edx, edx
        div     esi
        pop     rcx
        ret
.LBB0_2:
        lea     rdi, [rip + str.0]
        lea     rdx, [rip + .L__unnamed_1]
        mov     esi, 25
        call    qword ptr [rip + core::panicking::panic@GOTPCREL]
        ud2

example::safe_div:
        mov     eax, edi
        xor     edx, edx
        div     esi
        ret

example::safe_rem:
        mov     eax, edi
        xor     edx, edx
        div     esi
        mov     eax, edx
        ret
Ohad
  • 2,752
  • 17
  • 15