Integer Overflow Checking in LLVM


#1

I’ve seen this thread:

About this interesting blog post:
http://blog.regehr.org/archives/1384

Is something like this in Rustc (or is Rustc going to have it)?

This is my translation of the C code from the blog post (I’ve used get_unchecked as in C, and in foo2() I’ve added two extra casts as in C):

#[inline(never)]
fn foo(x: i32, y: i32) -> i32 {
    x + y
}

#[inline(never)]
fn foo2(x: i32, y: i32) -> i32 {
    (x as i16) as i32 + (y as i16) as i32
}

#[inline(never)]
fn foo3(x: i32, y: i32) -> i32 {
    ((x as i64) + (y as i64)) as i32
}

#[inline(never)]
fn foo4(x: i32, y: i32) -> i32 {
    (x >> 1) + (y >> 1)
}

#[inline(never)]
fn foo5(x: i32, y: i32) -> i32 {
    let mask: i32 = (!(3u32 << 30)) as i32;
    (x & mask) + (y & mask)
}

#[inline(never)]
fn foo6(x: i32, y: i32) -> i32 {
    let mask: i32 = (3u32 << 30) as i32;
    (x | mask) + (y | mask)
}

#[inline(never)]
fn foo7(x: i32, y: i32) -> i32 {
    let mask: i32 = (1u32 << 31) as i32;
    (x | mask) + (y & !mask)
}

#[inline(never)]
fn foo8(x: i32) -> i32 {
    use std::i32::MAX;
    if x < MAX { x + 1 } else { MAX }
}

#[inline(never)]
fn foo9(x: i32, y: i32) -> i32 {
    if x < 10 && x > -10 && y < 10 && y > -10 {
        return x + y;
    }
    return 0;
}

#[inline(never)]
fn foo10(a: &mut [i8]) {
    for i in 0i32 .. 10i32 {
        unsafe {
            *a.get_unchecked_mut(i as usize) = 0;
        }
    }
}

#[inline(never)]
fn foo11(a: &[u32], i: i32) -> u32 {
    unsafe {
        a.get_unchecked((i + 3) as usize) +
        a.get_unchecked((i + 5) as usize) +
        a.get_unchecked((i + 2) as usize)
    }
}

fn main() {
    let x: i32 = std::env::args().nth(1).unwrap().parse().unwrap();
    let y: i32 = std::env::args().nth(0).unwrap().parse().unwrap();

    println!("{}", foo(x, y));
    println!("{}", foo2(x, y));
    println!("{}", foo3(x, y));
    println!("{}", foo4(x, y));
    println!("{}", foo5(x, y));
    println!("{}", foo6(x, y));
    println!("{}", foo7(x, y));
    println!("{}", foo8(x));
    println!("{}", foo9(x, y));

    let x8 = x as i8;
    let y8 = y as i8;
    let mut arr1 = [x8, y8, x8, y8, x8, y8, x8, y8, x8, y8];
    foo10(&mut arr1);
    println!("{:?}", arr1);

    let ux = x as u32;
    let uy = y as u32;
    let arr2 = [ux, uy, ux, uy, ux, uy];
    println!("{}", foo11(&arr2, x));
}

This is the asm, using “-Z force-overflow-checks=on -C opt-level=3 -C target-cpu=native --emit asm” (if I use just -O the asm is the same), using rustc 1.9.0-nightly 2016-04-02, on a 64 bit system. I have assumed int = i32, unsigned = u32, long = i64, char = i8:

_ZN4temp3foo17hcd7dc3780f71cf13E:
    subq    $40, %rsp
    addl    %edx, %ecx
    jo    .LBB0_2
    movl    %ecx, %eax
    addq    $40, %rsp
    retq
.LBB0_2:
    leaq    panic_loc5965(%rip), %rcx
    callq    _ZN4core9panicking5panic17h188c6c6a0fe5463cE
    ud2


_ZN4temp4foo217h36cd5306e44d9490E:
    movswl    %cx, %ecx
    movswl    %dx, %eax
    addl    %ecx, %eax
    retq


_ZN4temp4foo317h2d6d816224352628E:
    leal    (%rcx,%rdx), %eax
    retq


_ZN4temp4foo417h10e6771f01c6ce47E:
    sarl    %ecx
    sarl    %edx
    leal    (%rdx,%rcx), %eax
    retq


_ZN4temp4foo517h16ae48d90b753b2aE:
    andl    $1073741823, %ecx
    andl    $1073741823, %edx
    leal    (%rdx,%rcx), %eax
    retq


_ZN4temp4foo617hdc0b94050cc4ab8eE:
    orl    $-1073741824, %ecx
    orl    $-1073741824, %edx
    leal    (%rdx,%rcx), %eax
    retq


_ZN4temp4foo717hc3e817990764820dE:
    orl    $-2147483648, %ecx
    andl    $2147483647, %edx
    leal    (%rdx,%rcx), %eax
    retq


_ZN4temp4foo817hefdf0c9012716395E:
    subq    $40, %rsp
    movl    $2147483647, %eax
    cmpl    $2147483647, %ecx
    je    .LBB7_3
    incl    %ecx
    jo    .LBB7_4
    movl    %ecx, %eax
.LBB7_3:
    addq    $40, %rsp
    retq
.LBB7_4:
    leaq    panic_loc5978(%rip), %rcx
    callq    _ZN4core9panicking5panic17h188c6c6a0fe5463cE
    ud2


_ZN4temp4foo917h1b8e21683e5607dcE:
    subq    $40, %rsp
    leal    9(%rcx), %r8d
    xorl    %eax, %eax
    cmpl    $18, %r8d
    ja    .LBB8_4
    leal    9(%rdx), %r8d
    cmpl    $18, %r8d
    ja    .LBB8_4
    addl    %edx, %ecx
    jo    .LBB8_5
    movl    %ecx, %eax
.LBB8_4:
    addq    $40, %rsp
    retq
.LBB8_5:
    leaq    panic_loc5981(%rip), %rcx
    callq    _ZN4core9panicking5panic17h188c6c6a0fe5463cE
    ud2


_ZN4temp5foo1017h5bde45ebfe6d6fcdE:
    subq    $40, %rsp
    xorl    %eax, %eax
    .align    16, 0x90
.LBB9_1:
    movl    %eax, %edx
    incl    %edx
    jo    .LBB9_4
    cltq
    movb    $0, (%rcx,%rax)
    movl    %edx, %eax
    cmpl    $10, %edx
    jl    .LBB9_1
    addq    $40, %rsp
    retq
.LBB9_4:
    leaq    panic_loc7761(%rip), %rcx
    callq    _ZN4core9panicking5panic17h188c6c6a0fe5463cE
    ud2


_ZN4temp5foo1117hedfa86a790c1c8caE:
    subq    $40, %rsp
    movl    %edx, %r8d
    addl    $3, %r8d
    jo    .LBB10_7
    movl    %edx, %eax
    addl    $5, %eax
    jo    .LBB10_9
    movslq    %r8d, %r8
    movslq    %eax, %r9
    movl    (%rcx,%r8,4), %eax
    addl    (%rcx,%r9,4), %eax
    jb    .LBB10_8
    addl    $2, %edx
    jo    .LBB10_6
    movslq    %edx, %rdx
    addl    (%rcx,%rdx,4), %eax
    jb    .LBB10_8
    addq    $40, %rsp
    retq
.LBB10_8:
    leaq    panic_loc7761(%rip), %rcx
    callq    _ZN4core9panicking5panic17h188c6c6a0fe5463cE
    ud2
.LBB10_7:
    leaq    panic_loc7932(%rip), %rcx
    callq    _ZN4core9panicking5panic17h188c6c6a0fe5463cE
    ud2
.LBB10_9:
    leaq    panic_loc7934(%rip), %rcx
    callq    _ZN4core9panicking5panic17h188c6c6a0fe5463cE
    ud2
.LBB10_6:
    leaq    panic_loc7936(%rip), %rcx
    callq    _ZN4core9panicking5panic17h188c6c6a0fe5463cE
    ud2

Compared to the blog post, the asm of foo() is longer. The asm of the C foo() is:

foo:                                    # @foo
        addl    %esi, %edi
        jo      .LBB0_1
        movl    %edi, %eax
        retq
.LBB0_1:
        ud2

foo11() has five panics instead of three because the C code is compiled with -fsanitize=signed-integer-overflow -fsanitize-trap=signed-integer-overflow, so it ignores the unsigned overflows. If you add -fsanitize=unsigned-integer-overflow -fsanitize-trap=unsigned-integer-overflow to the C compilation flags, you get five traps in the C code too.


#2

The differences between the foo() from the blog post and Rust’s foo():

  • The call to core::panicking::panic is the panic infrastructure, which gives you nice panic messages, unwinding, etc. as opposed to just crashing the program like ud2.

  • subq $40, %rsp etc. is stack manipulation; LLVM implements an optimization called shrink-wrapping which mitigates this, but it doesn’t work with unwinding at the moment (see https://llvm.org/bugs/show_bug.cgi?id=25614).