Disable panic in small bare metal systems

Hi,

New to the forum, and almost to rust as well.

I'm trying to get rid of the extra code added to detect panic situations, like a division by zero for instance.

There is an old thread about the same topic:

In that thread it is mentioned that it is most likely difficult to get rid of / disable panic handling all together. In can understand that but still I'm wondering; Is that still the case?

In very tiny embedded systems it would be better to just let the HW-exception for division by zero (as an example) "hit hard" and take proper actions. But that is just IMHO.
In my system I currently have 8 kBytes total code space for my rust plugin, and adding code for detecting division by zero and calling the panic-handler instead of letting the HW-exception take care of it adds almost 200 bytes. Further, there is no console or such, to print the panic info on.

Thanks.

You can build libcore with the panic_immediate_abort feature using -Zbuild-std. This will codegen every panic as an abort instruction without any panic message formatting. Something like cargo build -Zbuild-std=core -Zbuild-std-features=panic_immediate_abort --target <your_target> should work I think.

In LLVM division by zero is UB. This means that rustc has to explicitly check for 0 to avoid UB. There is no way around that unfortunately. Maybe LLVM could be convinced to fold if divisor == 0 { abort() } else { a / divisor } into a single a / divisor instruction? I am pretty sure LLVM won't do this by default as it changes an abort exception into a division by zero exception, which could change behavior.

4 Likes

I've investigated this myself, and there's no way to do a "processor division" without causing UB. Obviously, a div_unchecked can be written with no problems (Compiler Explorer):

pub unsafe fn div_unchecked(x: u32, y: u32) -> u32 {
    if y == 0 {
        std::hint::unreachable_unchecked();
    }
    x / y
}
example::div_unchecked:
        mov     eax, edi
        xor     edx, edx
        div     esi
        ret

But LLVM's optimizations will assume that this function will never be called with zero. As far as I know, the only way to do a real processor division is with inline assembly (Compiler Explorer), which produces the same assembly but with no UB:

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub fn div_processor(x: u32, y: u32) -> u32 {
    let result;
    unsafe {
        std::arch::asm!(
            "div {y:e}",
            y = in(reg) y,
            inlateout("ax") x => result,
            in("dx") 0u32,
            options(pure, nomem, nostack),
        );
    }
    result
}

(Repeat for other integer size and signedness combinations, perhaps with an extension trait.)

For a small program, it is sometimes possible to write it in such a way that the compiler can make sure that the panic code is never actually called and optimize it out.

There is a crate to help ensure that this is the case on a per-function level, https://crates.io/crates/no-panic , which will probably make it easier to convert a program that may panic to one that does not by doing it step by step.

In the specific case of divide by zero, this would require that the program is guaranteed to never divide by zero.

Thank you all for you replies. I will investigate the different alternatives you have given here.