Skip to content

Commit 1410d56

Browse files
committed
Auto merge of #45920 - sunfishcode:trap-on-unreachable, r=Zoxc
Enable TrapUnreachable in LLVM. This patch enables LLVM's TrapUnreachable flag, which tells it to translate `unreachable` instructions into hardware trap instructions, rather than allowing control flow to "fall through" into whatever code happens to follow it in memory. This follows up on #28728 (comment). For example, for @zackw's testcase [here](#42009 (comment)), the output function contains a `ud2` instead of no code, so it won't "fall through" into whatever happens to be next in memory. (I'm also working on the problem of LLVM optimizing away infinite loops, but the patch here is useful independently.) I tested this patch on a few different codebases, and the code size increase ranged from 0.0% to 0.1%.
2 parents 8385fc0 + ac48348 commit 1410d56

File tree

7 files changed

+33
-5
lines changed

7 files changed

+33
-5
lines changed

src/librustc_back/target/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,10 @@ pub struct TargetOptions {
435435

436436
/// Default number of codegen units to use in debug mode
437437
pub default_codegen_units: Option<u64>,
438+
439+
/// Whether to generate trap instructions in places where optimization would
440+
/// otherwise produce control flow that falls through into unrelated memory.
441+
pub trap_unreachable: bool,
438442
}
439443

440444
impl Default for TargetOptions {
@@ -498,6 +502,7 @@ impl Default for TargetOptions {
498502
stack_probes: false,
499503
min_global_align: None,
500504
default_codegen_units: None,
505+
trap_unreachable: true,
501506
}
502507
}
503508
}
@@ -739,6 +744,7 @@ impl Target {
739744
key!(stack_probes, bool);
740745
key!(min_global_align, Option<u64>);
741746
key!(default_codegen_units, Option<u64>);
747+
key!(trap_unreachable, bool);
742748

743749
if let Some(array) = obj.find("abi-blacklist").and_then(Json::as_array) {
744750
for name in array.iter().filter_map(|abi| abi.as_string()) {
@@ -932,6 +938,7 @@ impl ToJson for Target {
932938
target_option_val!(stack_probes);
933939
target_option_val!(min_global_align);
934940
target_option_val!(default_codegen_units);
941+
target_option_val!(trap_unreachable);
935942

936943
if default.abi_blacklist != self.options.abi_blacklist {
937944
d.insert("abi-blacklist".to_string(), self.options.abi_blacklist.iter()

src/librustc_llvm/ffi.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1605,7 +1605,8 @@ extern "C" {
16051605
UseSoftFP: bool,
16061606
PositionIndependentExecutable: bool,
16071607
FunctionSections: bool,
1608-
DataSections: bool)
1608+
DataSections: bool,
1609+
TrapUnreachable: bool)
16091610
-> TargetMachineRef;
16101611
pub fn LLVMRustDisposeTargetMachine(T: TargetMachineRef);
16111612
pub fn LLVMRustAddAnalysisPasses(T: TargetMachineRef, PM: PassManagerRef, M: ModuleRef);

src/librustc_trans/back/write.rs

+2
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ pub fn target_machine_factory(sess: &Session)
196196
let cpu = CString::new(cpu.as_bytes()).unwrap();
197197
let features = CString::new(target_feature(sess).as_bytes()).unwrap();
198198
let is_pie_binary = is_pie_binary(sess);
199+
let trap_unreachable = sess.target.target.options.trap_unreachable;
199200

200201
Arc::new(move || {
201202
let tm = unsafe {
@@ -208,6 +209,7 @@ pub fn target_machine_factory(sess: &Session)
208209
is_pie_binary,
209210
ffunction_sections,
210211
fdata_sections,
212+
trap_unreachable,
211213
)
212214
};
213215

src/rustllvm/PassWrapper.cpp

+9-1
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ extern "C" LLVMTargetMachineRef LLVMRustCreateTargetMachine(
366366
LLVMRustCodeModel RustCM, LLVMRustRelocMode RustReloc,
367367
LLVMRustCodeGenOptLevel RustOptLevel, bool UseSoftFloat,
368368
bool PositionIndependentExecutable, bool FunctionSections,
369-
bool DataSections) {
369+
bool DataSections, bool TrapUnreachable) {
370370

371371
auto CM = fromRust(RustCM);
372372
auto OptLevel = fromRust(RustOptLevel);
@@ -398,6 +398,14 @@ extern "C" LLVMTargetMachineRef LLVMRustCreateTargetMachine(
398398
Options.DataSections = DataSections;
399399
Options.FunctionSections = FunctionSections;
400400

401+
if (TrapUnreachable) {
402+
// Tell LLVM to translate `unreachable` into an explicit trap instruction.
403+
// This limits the extent of possible undefined behavior in some cases, as
404+
// it prevents control flow from "falling through" into whatever code
405+
// happens to be laid out next in memory.
406+
Options.TrapUnreachable = true;
407+
}
408+
401409
TargetMachine *TM = TheTarget->createTargetMachine(
402410
Trip.getTriple(), RealCPU, Feature, Options, RM, CM, OptLevel);
403411
return wrap(TM);

src/test/run-make/intrinsic-unreachable/exit-ret.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,15 @@
1111
#![feature(asm)]
1212
#![crate_type="lib"]
1313

14-
pub fn exit(n: usize) {
14+
#[deny(unreachable_code)]
15+
pub fn exit(n: usize) -> i32 {
1516
unsafe {
1617
// Pretend this asm is an exit() syscall.
1718
asm!("" :: "r"(n) :: "volatile");
1819
// Can't actually reach this point, but rustc doesn't know that.
1920
}
21+
// This return value is just here to generate some extra code for a return
22+
// value, making it easier for the test script to detect whether the
23+
// compiler deleted it.
24+
42
2025
}

src/test/run-make/intrinsic-unreachable/exit-unreachable.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,15 @@
1313

1414
use std::intrinsics;
1515

16-
pub fn exit(n: usize) -> ! {
16+
#[allow(unreachable_code)]
17+
pub fn exit(n: usize) -> i32 {
1718
unsafe {
1819
// Pretend this asm is an exit() syscall.
1920
asm!("" :: "r"(n) :: "volatile");
2021
intrinsics::unreachable()
2122
}
23+
// This return value is just here to generate some extra code for a return
24+
// value, making it easier for the test script to detect whether the
25+
// compiler deleted it.
26+
42
2227
}

0 commit comments

Comments
 (0)