Auto merge of #59546 - sfanxiang:interminable-ub, r=nagisa
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
**UPDATE:** [Mentoring instructions here](https://github.com/rust-lang/rust/pull/59546#issuecomment-515072429) to unstall this PR
This commit is contained in:
commit
58b54911fa
9 changed files with 107 additions and 1 deletions
|
|
@ -1467,6 +1467,9 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
|
|||
"which mangling version to use for symbol names"),
|
||||
binary_dep_depinfo: bool = (false, parse_bool, [TRACKED],
|
||||
"include artifacts (sysroot, crate dependencies) used during compilation in dep-info"),
|
||||
insert_sideeffect: bool = (false, parse_bool, [TRACKED],
|
||||
"fix undefined behavior when a thread doesn't eventually make progress \
|
||||
(such as entering an empty infinite loop) by inserting llvm.sideeffect"),
|
||||
}
|
||||
|
||||
pub fn default_lib_output() -> CrateType {
|
||||
|
|
|
|||
|
|
@ -537,6 +537,7 @@ impl CodegenCx<'b, 'tcx> {
|
|||
ifn!("llvm.trap", fn() -> void);
|
||||
ifn!("llvm.debugtrap", fn() -> void);
|
||||
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.v2f32", fn(t_v2f32, t_i32) -> t_v2f32);
|
||||
|
|
|
|||
|
|
@ -724,6 +724,13 @@ impl IntrinsicCallMethods<'tcx> for Builder<'a, 'll, 'tcx> {
|
|||
self.call(expect, &[cond, self.const_bool(expected)], None)
|
||||
}
|
||||
|
||||
fn sideeffect(&mut self) {
|
||||
if self.tcx.sess.opts.debugging_opts.insert_sideeffect {
|
||||
let fnname = self.get_intrinsic(&("llvm.sideeffect"));
|
||||
self.call(fnname, &[], None);
|
||||
}
|
||||
}
|
||||
|
||||
fn va_start(&mut self, va_list: &'ll Value) -> &'ll Value {
|
||||
let intrinsic = self.cx().get_intrinsic("llvm.va_start");
|
||||
self.call(intrinsic, &[va_list], None)
|
||||
|
|
@ -810,6 +817,7 @@ fn codegen_msvc_try(
|
|||
) {
|
||||
let llfn = get_rust_try_fn(bx, &mut |mut bx| {
|
||||
bx.set_personality_fn(bx.eh_personality());
|
||||
bx.sideeffect();
|
||||
|
||||
let mut normal = bx.build_sibling_block("normal");
|
||||
let mut catchswitch = bx.build_sibling_block("catchswitch");
|
||||
|
|
@ -933,6 +941,8 @@ fn codegen_gnu_try(
|
|||
// expected to be `*mut *mut u8` for this to actually work, but that's
|
||||
// managed by the standard library.
|
||||
|
||||
bx.sideeffect();
|
||||
|
||||
let mut then = bx.build_sibling_block("then");
|
||||
let mut catch = bx.build_sibling_block("catch");
|
||||
|
||||
|
|
|
|||
|
|
@ -149,6 +149,26 @@ 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 bx.tcx().sess.opts.debugging_opts.insert_sideeffect {
|
||||
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.
|
||||
|
|
@ -197,6 +217,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
let lltrue = helper.llblock(self, targets[0]);
|
||||
let llfalse = helper.llblock(self, targets[1]);
|
||||
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
|
||||
if let [0] = values[..] {
|
||||
bx.cond_br(discr.immediate(), llfalse, lltrue);
|
||||
|
|
@ -210,9 +231,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
);
|
||||
let llval = bx.const_uint_big(switch_llty, values[0]);
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
helper.maybe_sideeffect(self.mir, &mut bx, targets.as_slice());
|
||||
let (otherwise, targets) = targets.split_last().unwrap();
|
||||
bx.switch(
|
||||
discr.immediate(),
|
||||
|
|
@ -308,6 +331,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
|
||||
if let ty::InstanceDef::DropGlue(_, None) = drop_fn.def {
|
||||
// we don't actually need to drop anything.
|
||||
helper.maybe_sideeffect(self.mir, &mut bx, &[target]);
|
||||
helper.funclet_br(self, &mut bx, target);
|
||||
return
|
||||
}
|
||||
|
|
@ -338,6 +362,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
FnType::of_instance(&bx, drop_fn))
|
||||
}
|
||||
};
|
||||
helper.maybe_sideeffect(self.mir, &mut bx, &[target]);
|
||||
helper.do_call(self, &mut bx, fn_ty, drop_fn, args,
|
||||
Some((ReturnDest::Nothing, target)),
|
||||
unwind);
|
||||
|
|
@ -373,6 +398,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
|
||||
// Don't codegen the panic block if success if known.
|
||||
if const_cond == Some(expected) {
|
||||
helper.maybe_sideeffect(self.mir, &mut bx, &[target]);
|
||||
helper.funclet_br(self, &mut bx, target);
|
||||
return;
|
||||
}
|
||||
|
|
@ -383,6 +409,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
// Create the failure block and the conditional branch to it.
|
||||
let lltarget = helper.llblock(self, target);
|
||||
let panic_block = self.new_block("panic");
|
||||
helper.maybe_sideeffect(self.mir, &mut bx, &[target]);
|
||||
if expected {
|
||||
bx.cond_br(cond, lltarget, panic_block.llbb());
|
||||
} else {
|
||||
|
|
@ -486,6 +513,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
if let Some(destination_ref) = destination.as_ref() {
|
||||
let &(ref dest, target) = destination_ref;
|
||||
self.codegen_transmute(&mut bx, &args[0], dest);
|
||||
helper.maybe_sideeffect(self.mir, &mut bx, &[target]);
|
||||
helper.funclet_br(self, &mut bx, target);
|
||||
} else {
|
||||
// If we are trying to transmute to an uninhabited type,
|
||||
|
|
@ -513,6 +541,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
Some(ty::InstanceDef::DropGlue(_, None)) => {
|
||||
// Empty drop glue; a no-op.
|
||||
let &(_, target) = destination.as_ref().unwrap();
|
||||
helper.maybe_sideeffect(self.mir, &mut bx, &[target]);
|
||||
helper.funclet_br(self, &mut bx, target);
|
||||
return;
|
||||
}
|
||||
|
|
@ -549,6 +578,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
let fn_ty = FnType::of_instance(&bx, instance);
|
||||
let llfn = bx.get_fn(instance);
|
||||
|
||||
if let Some((_, target)) = destination.as_ref() {
|
||||
helper.maybe_sideeffect(self.mir, &mut bx, &[*target]);
|
||||
}
|
||||
// Codegen the actual panic invoke/call.
|
||||
helper.do_call(
|
||||
self,
|
||||
|
|
@ -561,7 +593,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
);
|
||||
} else {
|
||||
// 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;
|
||||
}
|
||||
|
|
@ -670,6 +704,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
}
|
||||
|
||||
if let Some((_, target)) = *destination {
|
||||
helper.maybe_sideeffect(self.mir, &mut bx, &[target]);
|
||||
helper.funclet_br(self, &mut bx, target);
|
||||
} else {
|
||||
bx.unreachable();
|
||||
|
|
@ -762,6 +797,9 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
_ => span_bug!(span, "no llfn for call"),
|
||||
};
|
||||
|
||||
if let Some((_, target)) = destination.as_ref() {
|
||||
helper.maybe_sideeffect(self.mir, &mut bx, &[*target]);
|
||||
}
|
||||
helper.do_call(self, &mut bx, fn_ty, fn_ptr, &llargs,
|
||||
destination.as_ref().map(|&(_, target)| (ret_dest, target)),
|
||||
cleanup);
|
||||
|
|
@ -811,6 +849,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
|
|||
}
|
||||
|
||||
mir::TerminatorKind::Goto { target } => {
|
||||
helper.maybe_sideeffect(self.mir, &mut bx, &[target]);
|
||||
helper.funclet_br(self, &mut bx, target);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -200,6 +200,8 @@ pub fn codegen_mir<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
|
|||
bx.set_personality_fn(cx.eh_personality());
|
||||
}
|
||||
|
||||
bx.sideeffect();
|
||||
|
||||
let cleanup_kinds = analyze::cleanup_kinds(&mir);
|
||||
// Allocate a `Block` for every basic block, except
|
||||
// the start block, if nothing loops back to it.
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ pub trait IntrinsicCallMethods<'tcx>: BackendTypes {
|
|||
fn abort(&mut self);
|
||||
fn assume(&mut self, val: 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
|
||||
/// Rust defined C-variadic functions.
|
||||
fn va_start(&mut self, val: Self::Value) -> Self::Value;
|
||||
|
|
|
|||
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 -Z insert-sideeffect
|
||||
|
||||
#![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 -Z insert-sideeffect
|
||||
|
||||
#![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 -Z insert-sideeffect
|
||||
|
||||
#![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()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue