Add llvm.sideeffect to potential infinite loops and recursions

LLVM assumes that a thread will eventually cause side effect. This is
not true in Rust if a loop or recursion does nothing in its body,
causing undefined behavior even in common cases like `loop {}`.
Inserting llvm.sideeffect fixes the undefined behavior.

As a micro-optimization, only insert llvm.sideeffect when jumping back
in blocks or calling a function.

A patch for LLVM is expected to allow empty non-terminate code by
default and fix this issue from LLVM side.

https://github.com/rust-lang/rust/issues/28728
This commit is contained in:
Xiang Fan 2019-06-06 08:39:20 +08:00
parent a37fe2de69
commit f71e0daa29
18 changed files with 127 additions and 18 deletions

View file

@ -7,7 +7,8 @@
pub fn alloc_test(data: u32) {
// CHECK-LABEL: @alloc_test
// CHECK-NEXT: start:
// CHECK-NEXT: ret void
// CHECK-NOT: alloc
// CHECK: ret void
let x = Box::new(data);
drop(x);
}

View file

@ -17,7 +17,7 @@ impl Drop for A {
pub fn a(a: Box<i32>) {
// CHECK-LABEL: define void @a
// CHECK: call void @__rust_dealloc
// CHECK-NEXT: call void @foo
// CHECK: call void @foo
let _a = A;
drop(a);
}

View file

@ -6,8 +6,11 @@
#[no_mangle]
pub fn issue_34947(x: i32) -> i32 {
// CHECK: mul
// CHECK-NEXT: mul
// CHECK-NEXT: mul
// CHECK-NEXT: ret
// CHECK-NOT: br label
// CHECK: mul
// CHECK-NOT: br label
// CHECK: mul
// CHECK-NOT: br label
// CHECK: ret
x.pow(5)
}

View file

@ -1,3 +1,9 @@
// ignore-test
// FIXME:
// LLVM can't optimize some loops with a large number of iterations because of
// @llvm.sideeffect() (see also #59546)
// compile-flags: -O
// ignore-debug: the debug assertions get in the way

View file

@ -1,5 +1,3 @@
// ignore-tidy-linelength
// compile-flags: -C no-prepopulate-passes
#![crate_type = "lib"]
@ -53,7 +51,7 @@ pub fn naked_with_args_and_return(a: isize) -> isize {
#[naked]
pub fn naked_recursive() {
// CHECK-NEXT: {{.+}}:
// CHECK-NEXT: call void @naked_empty()
// CHECK: call void @naked_empty()
// FIXME(#39685) Avoid one block per call.
// CHECK-NEXT: br label %bb1
@ -61,19 +59,19 @@ pub fn naked_recursive() {
naked_empty();
// CHECK-NEXT: %{{[0-9]+}} = call i{{[0-9]+}} @naked_with_return()
// CHECK: %{{[0-9]+}} = call i{{[0-9]+}} @naked_with_return()
// FIXME(#39685) Avoid one block per call.
// CHECK-NEXT: br label %bb2
// CHECK: bb2:
// CHECK-NEXT: %{{[0-9]+}} = call i{{[0-9]+}} @naked_with_args_and_return(i{{[0-9]+}} %{{[0-9]+}})
// CHECK: %{{[0-9]+}} = call i{{[0-9]+}} @naked_with_args_and_return(i{{[0-9]+}} %{{[0-9]+}})
// FIXME(#39685) Avoid one block per call.
// CHECK-NEXT: br label %bb3
// CHECK: bb3:
// CHECK-NEXT: call void @naked_with_args(i{{[0-9]+}} %{{[0-9]+}})
// CHECK: call void @naked_with_args(i{{[0-9]+}} %{{[0-9]+}})
// FIXME(#39685) Avoid one block per call.
// CHECK-NEXT: br label %bb4

View file

@ -0,0 +1,17 @@
// compile-flags: -C opt-level=3
#![crate_type = "lib"]
fn infinite_loop() -> u8 {
loop {}
}
// CHECK-LABEL: @test
#[no_mangle]
fn test() -> u8 {
// CHECK-NOT: unreachable
// CHECK: br label %{{.+}}
// CHECK-NOT: unreachable
let x = infinite_loop();
x
}

View file

@ -0,0 +1,19 @@
// compile-flags: -C opt-level=3
#![crate_type = "lib"]
fn infinite_loop() -> u8 {
let i = 2;
while i > 1 {}
1
}
// CHECK-LABEL: @test
#[no_mangle]
fn test() -> u8 {
// CHECK-NOT: unreachable
// CHECK: br label %{{.+}}
// CHECK-NOT: unreachable
let x = infinite_loop();
x
}

View file

@ -0,0 +1,14 @@
// compile-flags: -C opt-level=3
#![crate_type = "lib"]
#![allow(unconditional_recursion)]
// CHECK-LABEL: @infinite_recursion
#[no_mangle]
fn infinite_recursion() -> u8 {
// CHECK-NOT: ret i8 undef
// CHECK: br label %{{.+}}
// CHECK-NOT: ret i8 undef
infinite_recursion()
}

View file

@ -14,6 +14,10 @@ pub fn helper(_: usize) {
// CHECK-LABEL: @repeat_take_collect
#[no_mangle]
pub fn repeat_take_collect() -> Vec<u8> {
// CHECK: call void @llvm.memset.p0i8.[[USIZE]](i8* {{(nonnull )?}}align 1 %{{[0-9]+}}, i8 42, [[USIZE]] 100000, i1 false)
// FIXME: At the time of writing LLVM transforms this loop into a single
// `store` and then a `memset` with size = 99999. The correct check should be:
// call void @llvm.memset.p0i8.[[USIZE]](i8* {{(nonnull )?}}align 1 %{{[a-z0-9.]+}}, i8 42, [[USIZE]] 100000, i1 false)
// CHECK: call void @llvm.memset.p0i8.[[USIZE]](i8* {{(nonnull )?}}align 1 %{{[a-z0-9.]+}}, i8 42, [[USIZE]] 99999, i1 false)
iter::repeat(42).take(100000).collect()
}

View file

@ -1,3 +1,9 @@
// ignore-test
// FIXME:
// LLVM can't optimize some loops with unknown number of iterations because of
// @llvm.sideeffect() (see also #59546)
// ignore-debug: the debug assertions get in the way
// compile-flags: -O

View file

@ -5,8 +5,6 @@
#[no_mangle]
pub fn get_len() -> usize {
// CHECK-LABEL: @get_len
// CHECK-NOT: call
// CHECK-NOT: invoke
// CHECK-COUNT-1: {{^define}}
[1, 2, 3].iter().collect::<Vec<_>>().len()
}

View file

@ -8,6 +8,6 @@
pub fn sum_me() -> i32 {
// CHECK-LABEL: @sum_me
// CHECK-NEXT: {{^.*:$}}
// CHECK-NEXT: ret i32 6
// CHECK: ret i32 6
vec![1, 2, 3].iter().sum::<i32>()
}