Skip to content

Commit 2892ca4

Browse files
committed
Allow for unwinding out of foreign abi functions
We were not allowing for the possibility that we might unwind out of a foreign abi function. Now that we allow for some of these functions to unwind, generate the proper code so clean-ups are performed. This also allowed me to write a test for the functionality, which I have now done.
1 parent 099b499 commit 2892ca4

File tree

4 files changed

+142
-5
lines changed

4 files changed

+142
-5
lines changed

src/librustc_trans/trans/build.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,25 @@ pub fn Invoke(cx: Block,
126126
B(cx).invoke(fn_, args, then, catch, attributes)
127127
}
128128

129+
pub fn InvokeWithConv(cx: Block,
130+
fn_: ValueRef,
131+
args: &[ValueRef],
132+
then: BasicBlockRef,
133+
catch: BasicBlockRef,
134+
conv: CallConv,
135+
attributes: Option<AttrBuilder>)
136+
-> ValueRef {
137+
if cx.unreachable.get() {
138+
return C_null(Type::i8(cx.ccx()));
139+
}
140+
check_not_terminated(cx);
141+
terminate(cx, "InvokeWithConv");
142+
debug!("InvokeWithConv({} with arguments ({}))",
143+
cx.val_to_string(fn_),
144+
args.iter().map(|a| cx.val_to_string(*a)).collect::<Vec<String>>().connect(", "));
145+
B(cx).invoke_with_conv(fn_, args, then, catch, conv, attributes)
146+
}
147+
129148
pub fn Unreachable(cx: Block) {
130149
if cx.unreachable.get() {
131150
return

src/librustc_trans/trans/builder.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,40 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
184184
}
185185
}
186186

187+
pub fn invoke_with_conv(&self,
188+
llfn: ValueRef,
189+
args: &[ValueRef],
190+
then: BasicBlockRef,
191+
catch: BasicBlockRef,
192+
conv: CallConv,
193+
attributes: Option<AttrBuilder>)
194+
-> ValueRef {
195+
self.count_insn("invokewithconv");
196+
197+
debug!("Invoke {} with args ({})",
198+
self.ccx.tn().val_to_string(llfn),
199+
args.iter()
200+
.map(|&v| self.ccx.tn().val_to_string(v))
201+
.collect::<Vec<String>>()
202+
.connect(", "));
203+
204+
unsafe {
205+
let v = llvm::LLVMBuildInvoke(self.llbuilder,
206+
llfn,
207+
args.as_ptr(),
208+
args.len() as c_uint,
209+
then,
210+
catch,
211+
noname());
212+
match attributes {
213+
Some(a) => a.apply_callsite(v),
214+
None => {}
215+
}
216+
llvm::SetInstructionCallConv(v, conv);
217+
v
218+
}
219+
}
220+
187221
pub fn unreachable(&self) {
188222
self.count_insn("unreachable");
189223
unsafe {

src/librustc_trans/trans/foreign.rs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use trans::base::{llvm_linkage_by_name, push_ctxt};
1717
use trans::base;
1818
use trans::build::*;
1919
use trans::cabi;
20+
use trans::cleanup::CleanupMethods;
2021
use trans::common::*;
2122
use trans::machine;
2223
use trans::monomorphize;
@@ -366,11 +367,27 @@ pub fn trans_native_call<'blk, 'tcx>(bcx: Block<'blk, 'tcx>,
366367
arg_idx += 1;
367368
}
368369

369-
let llforeign_retval = CallWithConv(bcx,
370-
llfn,
371-
&llargs_foreign[],
372-
cc,
373-
Some(attrs));
370+
// Foreign functions *can* unwind, albeit rarely, so use invoke instead of call.
371+
// We shouldn't really be emitting an invoke all the time, LLVM can optimise them
372+
// out, but we should be able avoid it most of the time
373+
let (llforeign_retval, bcx) = if base::need_invoke(bcx) {
374+
let normal_bcx = bcx.fcx.new_temp_block("normal-return");
375+
let landing_pad = bcx.fcx.get_landing_pad();
376+
377+
let ret = InvokeWithConv(bcx, llfn, &llargs_foreign[],
378+
normal_bcx.llbb,
379+
landing_pad,
380+
cc,
381+
Some(attrs));
382+
(ret, normal_bcx)
383+
} else {
384+
let ret = CallWithConv(bcx,
385+
llfn,
386+
&llargs_foreign[],
387+
cc,
388+
Some(attrs));
389+
(ret, bcx)
390+
};
374391

375392
// If the function we just called does not use an outpointer,
376393
// store the result into the rust outpointer. Cast the outpointer

src/test/run-pass/ffi-unwinding.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![feature(unwinding_attributes)]
12+
13+
use std::io::Command;
14+
use std::os;
15+
use std::str;
16+
17+
struct Guard { name: &'static str }
18+
19+
impl Drop for Guard {
20+
fn drop(&mut self) {
21+
println!("Dropped Guard {}", self.name);
22+
}
23+
}
24+
25+
fn main() {
26+
let args = os::args();
27+
28+
if args.len() > 1 {
29+
if &args[1][] == "abort" {
30+
let _g = Guard { name: "abort" };
31+
32+
i_abort();
33+
34+
return;
35+
} else if &args[1][] == "unwind" {
36+
let _g = Guard { name: "unwind" };
37+
38+
i_unwind();
39+
40+
return;
41+
}
42+
}
43+
44+
let p = Command::new(&args[0][])
45+
.arg("abort")
46+
.spawn().unwrap().wait_with_output().unwrap();
47+
48+
assert!(!p.status.success());
49+
let mut lines = str::from_utf8(&p.output[]).unwrap().lines();
50+
51+
assert!(lines.next().is_none());
52+
53+
let p = Command::new(&args[0][])
54+
.arg("unwind")
55+
.spawn().unwrap().wait_with_output().unwrap();
56+
57+
assert!(!p.status.success());
58+
let mut lines = str::from_utf8(&p.output[]).unwrap().lines();
59+
60+
assert_eq!(&lines.next().unwrap()[], "Dropped Guard unwind");
61+
}
62+
63+
#[inline(never)]
64+
extern "C" fn i_abort() { panic!() }
65+
66+
#[inline(never)] #[can_unwind]
67+
extern "C" fn i_unwind() { panic!() }

0 commit comments

Comments
 (0)