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:
parent
a37fe2de69
commit
f71e0daa29
18 changed files with 127 additions and 18 deletions
|
@ -537,6 +537,7 @@ impl CodegenCx<'b, 'tcx> {
|
||||||
ifn!("llvm.trap", fn() -> void);
|
ifn!("llvm.trap", fn() -> void);
|
||||||
ifn!("llvm.debugtrap", fn() -> void);
|
ifn!("llvm.debugtrap", fn() -> void);
|
||||||
ifn!("llvm.frameaddress", fn(t_i32) -> i8p);
|
ifn!("llvm.frameaddress", fn(t_i32) -> i8p);
|
||||||
|
ifn!("llvm.sideeffect", fn() -> void);
|
||||||
|
|
||||||
ifn!("llvm.powi.f32", fn(t_f32, t_i32) -> t_f32);
|
ifn!("llvm.powi.f32", fn(t_f32, t_i32) -> t_f32);
|
||||||
ifn!("llvm.powi.v2f32", fn(t_v2f32, t_i32) -> t_v2f32);
|
ifn!("llvm.powi.v2f32", fn(t_v2f32, t_i32) -> t_v2f32);
|
||||||
|
|
|
@ -124,6 +124,7 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> {
|
||||||
self.call(expect, &[args[0].immediate(), self.const_bool(false)], None)
|
self.call(expect, &[args[0].immediate(), self.const_bool(false)], None)
|
||||||
}
|
}
|
||||||
"try" => {
|
"try" => {
|
||||||
|
self.sideeffect();
|
||||||
try_intrinsic(self,
|
try_intrinsic(self,
|
||||||
args[0].immediate(),
|
args[0].immediate(),
|
||||||
args[1].immediate(),
|
args[1].immediate(),
|
||||||
|
@ -724,6 +725,11 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> {
|
||||||
self.call(expect, &[cond, self.const_bool(expected)], None)
|
self.call(expect, &[cond, self.const_bool(expected)], None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sideeffect(&mut self) {
|
||||||
|
let fnname = self.get_intrinsic(&("llvm.sideeffect"));
|
||||||
|
self.call(fnname, &[], None);
|
||||||
|
}
|
||||||
|
|
||||||
fn va_start(&mut self, va_list: &'ll Value) -> &'ll Value {
|
fn va_start(&mut self, va_list: &'ll Value) -> &'ll Value {
|
||||||
let intrinsic = self.cx().get_intrinsic("llvm.va_start");
|
let intrinsic = self.cx().get_intrinsic("llvm.va_start");
|
||||||
self.call(intrinsic, &[va_list], None)
|
self.call(intrinsic, &[va_list], None)
|
||||||
|
|
|
@ -148,6 +148,24 @@ impl<'a, 'tcx> TerminatorCodegenHelper<'a, 'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate sideeffect intrinsic if jumping to any of the targets can form
|
||||||
|
// a loop.
|
||||||
|
fn maybe_sideeffect<'b, 'tcx2: 'b, Bx: BuilderMethods<'b, 'tcx2>>(
|
||||||
|
&self,
|
||||||
|
mir: &'b mir::Body<'tcx>,
|
||||||
|
bx: &mut Bx,
|
||||||
|
targets: &[mir::BasicBlock],
|
||||||
|
) {
|
||||||
|
if targets.iter().any(|target| {
|
||||||
|
*target <= *self.bb
|
||||||
|
&& target
|
||||||
|
.start_location()
|
||||||
|
.is_predecessor_of(self.bb.start_location(), mir)
|
||||||
|
}) {
|
||||||
|
bx.sideeffect();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Codegen implementations for some terminator variants.
|
/// Codegen implementations for some terminator variants.
|
||||||
|
@ -196,6 +214,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||||
let lltrue = helper.llblock(self, targets[0]);
|
let lltrue = helper.llblock(self, targets[0]);
|
||||||
let llfalse = helper.llblock(self, targets[1]);
|
let llfalse = helper.llblock(self, targets[1]);
|
||||||
if switch_ty == bx.tcx().types.bool {
|
if switch_ty == bx.tcx().types.bool {
|
||||||
|
helper.maybe_sideeffect(self.mir, &mut bx, targets.as_slice());
|
||||||
// Don't generate trivial icmps when switching on bool
|
// Don't generate trivial icmps when switching on bool
|
||||||
if let [0] = values[..] {
|
if let [0] = values[..] {
|
||||||
bx.cond_br(discr.immediate(), llfalse, lltrue);
|
bx.cond_br(discr.immediate(), llfalse, lltrue);
|
||||||
|
@ -209,9 +228,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||||
);
|
);
|
||||||
let llval = bx.const_uint_big(switch_llty, values[0]);
|
let llval = bx.const_uint_big(switch_llty, values[0]);
|
||||||
let cmp = bx.icmp(IntPredicate::IntEQ, discr.immediate(), llval);
|
let cmp = bx.icmp(IntPredicate::IntEQ, discr.immediate(), llval);
|
||||||
|
helper.maybe_sideeffect(self.mir, &mut bx, targets.as_slice());
|
||||||
bx.cond_br(cmp, lltrue, llfalse);
|
bx.cond_br(cmp, lltrue, llfalse);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
helper.maybe_sideeffect(self.mir, &mut bx, targets.as_slice());
|
||||||
let (otherwise, targets) = targets.split_last().unwrap();
|
let (otherwise, targets) = targets.split_last().unwrap();
|
||||||
bx.switch(
|
bx.switch(
|
||||||
discr.immediate(),
|
discr.immediate(),
|
||||||
|
@ -310,6 +331,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||||
|
|
||||||
if let ty::InstanceDef::DropGlue(_, None) = drop_fn.def {
|
if let ty::InstanceDef::DropGlue(_, None) = drop_fn.def {
|
||||||
// we don't actually need to drop anything.
|
// we don't actually need to drop anything.
|
||||||
|
helper.maybe_sideeffect(self.mir, &mut bx, &[target]);
|
||||||
helper.funclet_br(self, &mut bx, target);
|
helper.funclet_br(self, &mut bx, target);
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -340,6 +362,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||||
FnType::of_instance(&bx, drop_fn))
|
FnType::of_instance(&bx, drop_fn))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
bx.sideeffect();
|
||||||
helper.do_call(self, &mut bx, fn_ty, drop_fn, args,
|
helper.do_call(self, &mut bx, fn_ty, drop_fn, args,
|
||||||
Some((ReturnDest::Nothing, target)),
|
Some((ReturnDest::Nothing, target)),
|
||||||
unwind);
|
unwind);
|
||||||
|
@ -375,6 +398,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||||
|
|
||||||
// Don't codegen the panic block if success if known.
|
// Don't codegen the panic block if success if known.
|
||||||
if const_cond == Some(expected) {
|
if const_cond == Some(expected) {
|
||||||
|
helper.maybe_sideeffect(self.mir, &mut bx, &[target]);
|
||||||
helper.funclet_br(self, &mut bx, target);
|
helper.funclet_br(self, &mut bx, target);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -385,6 +409,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||||
// Create the failure block and the conditional branch to it.
|
// Create the failure block and the conditional branch to it.
|
||||||
let lltarget = helper.llblock(self, target);
|
let lltarget = helper.llblock(self, target);
|
||||||
let panic_block = self.new_block("panic");
|
let panic_block = self.new_block("panic");
|
||||||
|
helper.maybe_sideeffect(self.mir, &mut bx, &[target]);
|
||||||
if expected {
|
if expected {
|
||||||
bx.cond_br(cond, lltarget, panic_block.llbb());
|
bx.cond_br(cond, lltarget, panic_block.llbb());
|
||||||
} else {
|
} else {
|
||||||
|
@ -437,6 +462,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||||
let fn_ty = FnType::of_instance(&bx, instance);
|
let fn_ty = FnType::of_instance(&bx, instance);
|
||||||
let llfn = bx.get_fn(instance);
|
let llfn = bx.get_fn(instance);
|
||||||
|
|
||||||
|
bx.sideeffect();
|
||||||
// Codegen the actual panic invoke/call.
|
// Codegen the actual panic invoke/call.
|
||||||
helper.do_call(self, &mut bx, fn_ty, llfn, &args, None, cleanup);
|
helper.do_call(self, &mut bx, fn_ty, llfn, &args, None, cleanup);
|
||||||
}
|
}
|
||||||
|
@ -488,6 +514,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||||
if let Some(destination_ref) = destination.as_ref() {
|
if let Some(destination_ref) = destination.as_ref() {
|
||||||
let &(ref dest, target) = destination_ref;
|
let &(ref dest, target) = destination_ref;
|
||||||
self.codegen_transmute(&mut bx, &args[0], dest);
|
self.codegen_transmute(&mut bx, &args[0], dest);
|
||||||
|
helper.maybe_sideeffect(self.mir, &mut bx, &[target]);
|
||||||
helper.funclet_br(self, &mut bx, target);
|
helper.funclet_br(self, &mut bx, target);
|
||||||
} else {
|
} else {
|
||||||
// If we are trying to transmute to an uninhabited type,
|
// If we are trying to transmute to an uninhabited type,
|
||||||
|
@ -518,6 +545,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||||
Some(ty::InstanceDef::DropGlue(_, None)) => {
|
Some(ty::InstanceDef::DropGlue(_, None)) => {
|
||||||
// Empty drop glue; a no-op.
|
// Empty drop glue; a no-op.
|
||||||
let &(_, target) = destination.as_ref().unwrap();
|
let &(_, target) = destination.as_ref().unwrap();
|
||||||
|
helper.maybe_sideeffect(self.mir, &mut bx, &[target]);
|
||||||
helper.funclet_br(self, &mut bx, target);
|
helper.funclet_br(self, &mut bx, target);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -554,6 +582,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||||
let fn_ty = FnType::of_instance(&bx, instance);
|
let fn_ty = FnType::of_instance(&bx, instance);
|
||||||
let llfn = bx.get_fn(instance);
|
let llfn = bx.get_fn(instance);
|
||||||
|
|
||||||
|
bx.sideeffect();
|
||||||
// Codegen the actual panic invoke/call.
|
// Codegen the actual panic invoke/call.
|
||||||
helper.do_call(
|
helper.do_call(
|
||||||
self,
|
self,
|
||||||
|
@ -566,7 +595,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// a NOP
|
// a NOP
|
||||||
helper.funclet_br(self, &mut bx, destination.as_ref().unwrap().1)
|
let target = destination.as_ref().unwrap().1;
|
||||||
|
helper.maybe_sideeffect(self.mir, &mut bx, &[target]);
|
||||||
|
helper.funclet_br(self, &mut bx, target);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -675,6 +706,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((_, target)) = *destination {
|
if let Some((_, target)) = *destination {
|
||||||
|
helper.maybe_sideeffect(self.mir, &mut bx, &[target]);
|
||||||
helper.funclet_br(self, &mut bx, target);
|
helper.funclet_br(self, &mut bx, target);
|
||||||
} else {
|
} else {
|
||||||
bx.unreachable();
|
bx.unreachable();
|
||||||
|
@ -786,6 +818,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||||
_ => span_bug!(span, "no llfn for call"),
|
_ => span_bug!(span, "no llfn for call"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bx.sideeffect();
|
||||||
helper.do_call(self, &mut bx, fn_ty, fn_ptr, &llargs,
|
helper.do_call(self, &mut bx, fn_ty, fn_ptr, &llargs,
|
||||||
destination.as_ref().map(|&(_, target)| (ret_dest, target)),
|
destination.as_ref().map(|&(_, target)| (ret_dest, target)),
|
||||||
cleanup);
|
cleanup);
|
||||||
|
@ -835,6 +868,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
mir::TerminatorKind::Goto { target } => {
|
mir::TerminatorKind::Goto { target } => {
|
||||||
|
helper.maybe_sideeffect(self.mir, &mut bx, &[target]);
|
||||||
helper.funclet_br(self, &mut bx, target);
|
helper.funclet_br(self, &mut bx, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -486,6 +486,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
||||||
};
|
};
|
||||||
let instance = ty::Instance::mono(bx.tcx(), def_id);
|
let instance = ty::Instance::mono(bx.tcx(), def_id);
|
||||||
let r = bx.cx().get_fn(instance);
|
let r = bx.cx().get_fn(instance);
|
||||||
|
bx.sideeffect();
|
||||||
let call = bx.call(r, &[llsize, llalign], None);
|
let call = bx.call(r, &[llsize, llalign], None);
|
||||||
let val = bx.pointercast(call, llty_ptr);
|
let val = bx.pointercast(call, llty_ptr);
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ pub trait IntrinsicCallMethods<'tcx>: BackendTypes {
|
||||||
fn abort(&mut self);
|
fn abort(&mut self);
|
||||||
fn assume(&mut self, val: Self::Value);
|
fn assume(&mut self, val: Self::Value);
|
||||||
fn expect(&mut self, cond: Self::Value, expected: bool) -> Self::Value;
|
fn expect(&mut self, cond: Self::Value, expected: bool) -> Self::Value;
|
||||||
|
fn sideeffect(&mut self);
|
||||||
/// Trait method used to inject `va_start` on the "spoofed" `VaListImpl` in
|
/// Trait method used to inject `va_start` on the "spoofed" `VaListImpl` in
|
||||||
/// Rust defined C-variadic functions.
|
/// Rust defined C-variadic functions.
|
||||||
fn va_start(&mut self, val: Self::Value) -> Self::Value;
|
fn va_start(&mut self, val: Self::Value) -> Self::Value;
|
||||||
|
|
|
@ -7,7 +7,8 @@
|
||||||
pub fn alloc_test(data: u32) {
|
pub fn alloc_test(data: u32) {
|
||||||
// CHECK-LABEL: @alloc_test
|
// CHECK-LABEL: @alloc_test
|
||||||
// CHECK-NEXT: start:
|
// CHECK-NEXT: start:
|
||||||
// CHECK-NEXT: ret void
|
// CHECK-NOT: alloc
|
||||||
|
// CHECK: ret void
|
||||||
let x = Box::new(data);
|
let x = Box::new(data);
|
||||||
drop(x);
|
drop(x);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ impl Drop for A {
|
||||||
pub fn a(a: Box<i32>) {
|
pub fn a(a: Box<i32>) {
|
||||||
// CHECK-LABEL: define void @a
|
// CHECK-LABEL: define void @a
|
||||||
// CHECK: call void @__rust_dealloc
|
// CHECK: call void @__rust_dealloc
|
||||||
// CHECK-NEXT: call void @foo
|
// CHECK: call void @foo
|
||||||
let _a = A;
|
let _a = A;
|
||||||
drop(a);
|
drop(a);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,11 @@
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub fn issue_34947(x: i32) -> i32 {
|
pub fn issue_34947(x: i32) -> i32 {
|
||||||
// CHECK: mul
|
// CHECK: mul
|
||||||
// CHECK-NEXT: mul
|
// CHECK-NOT: br label
|
||||||
// CHECK-NEXT: mul
|
// CHECK: mul
|
||||||
// CHECK-NEXT: ret
|
// CHECK-NOT: br label
|
||||||
|
// CHECK: mul
|
||||||
|
// CHECK-NOT: br label
|
||||||
|
// CHECK: ret
|
||||||
x.pow(5)
|
x.pow(5)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
// compile-flags: -O
|
||||||
// ignore-debug: the debug assertions get in the way
|
// ignore-debug: the debug assertions get in the way
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
// ignore-tidy-linelength
|
|
||||||
|
|
||||||
// compile-flags: -C no-prepopulate-passes
|
// compile-flags: -C no-prepopulate-passes
|
||||||
|
|
||||||
#![crate_type = "lib"]
|
#![crate_type = "lib"]
|
||||||
|
@ -53,7 +51,7 @@ pub fn naked_with_args_and_return(a: isize) -> isize {
|
||||||
#[naked]
|
#[naked]
|
||||||
pub fn naked_recursive() {
|
pub fn naked_recursive() {
|
||||||
// CHECK-NEXT: {{.+}}:
|
// CHECK-NEXT: {{.+}}:
|
||||||
// CHECK-NEXT: call void @naked_empty()
|
// CHECK: call void @naked_empty()
|
||||||
|
|
||||||
// FIXME(#39685) Avoid one block per call.
|
// FIXME(#39685) Avoid one block per call.
|
||||||
// CHECK-NEXT: br label %bb1
|
// CHECK-NEXT: br label %bb1
|
||||||
|
@ -61,19 +59,19 @@ pub fn naked_recursive() {
|
||||||
|
|
||||||
naked_empty();
|
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.
|
// FIXME(#39685) Avoid one block per call.
|
||||||
// CHECK-NEXT: br label %bb2
|
// CHECK-NEXT: br label %bb2
|
||||||
// CHECK: 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.
|
// FIXME(#39685) Avoid one block per call.
|
||||||
// CHECK-NEXT: br label %bb3
|
// CHECK-NEXT: br label %bb3
|
||||||
// CHECK: 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.
|
// FIXME(#39685) Avoid one block per call.
|
||||||
// CHECK-NEXT: br label %bb4
|
// CHECK-NEXT: br label %bb4
|
||||||
|
|
17
src/test/codegen/non-terminate/infinite-loop-1.rs
Normal file
17
src/test/codegen/non-terminate/infinite-loop-1.rs
Normal 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
|
||||||
|
}
|
19
src/test/codegen/non-terminate/infinite-loop-2.rs
Normal file
19
src/test/codegen/non-terminate/infinite-loop-2.rs
Normal 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
|
||||||
|
}
|
14
src/test/codegen/non-terminate/infinite-recursion.rs
Normal file
14
src/test/codegen/non-terminate/infinite-recursion.rs
Normal 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()
|
||||||
|
}
|
|
@ -14,6 +14,10 @@ pub fn helper(_: usize) {
|
||||||
// CHECK-LABEL: @repeat_take_collect
|
// CHECK-LABEL: @repeat_take_collect
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub fn repeat_take_collect() -> Vec<u8> {
|
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()
|
iter::repeat(42).take(100000).collect()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
// ignore-debug: the debug assertions get in the way
|
||||||
// compile-flags: -O
|
// compile-flags: -O
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub fn get_len() -> usize {
|
pub fn get_len() -> usize {
|
||||||
// CHECK-LABEL: @get_len
|
// CHECK-COUNT-1: {{^define}}
|
||||||
// CHECK-NOT: call
|
|
||||||
// CHECK-NOT: invoke
|
|
||||||
[1, 2, 3].iter().collect::<Vec<_>>().len()
|
[1, 2, 3].iter().collect::<Vec<_>>().len()
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,6 @@
|
||||||
pub fn sum_me() -> i32 {
|
pub fn sum_me() -> i32 {
|
||||||
// CHECK-LABEL: @sum_me
|
// CHECK-LABEL: @sum_me
|
||||||
// CHECK-NEXT: {{^.*:$}}
|
// CHECK-NEXT: {{^.*:$}}
|
||||||
// CHECK-NEXT: ret i32 6
|
// CHECK: ret i32 6
|
||||||
vec![1, 2, 3].iter().sum::<i32>()
|
vec![1, 2, 3].iter().sum::<i32>()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
all:
|
all:
|
||||||
$(RUSTC) foo.rs --emit llvm-ir -C codegen-units=2
|
$(RUSTC) foo.rs --emit llvm-ir -C codegen-units=2
|
||||||
if cat $(TMPDIR)/*.ll | $(CGREP) -e '\bcall\b'; then \
|
if cat $(TMPDIR)/*.ll | grep -v 'call void @llvm.sideeffect()' | $(CGREP) -e '\bcall\b'; then \
|
||||||
echo "found call instruction when one wasn't expected"; \
|
echo "found call instruction when one wasn't expected"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue