Skip to content

Commit 61a02cd

Browse files
committed
add support for explicit tail calls (musttail)
1 parent 3aeb91f commit 61a02cd

5 files changed

Lines changed: 69 additions & 45 deletions

File tree

Cargo.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,18 +56,18 @@ dependencies = [
5656

5757
[[package]]
5858
name = "gccjit"
59-
version = "3.4.0"
59+
version = "3.5.0"
6060
source = "registry+https://github.com/rust-lang/crates.io-index"
61-
checksum = "8b2c6ee720b5459292678267e9ffed8229b11e0e27fc5ecf7618634dc6272fa4"
61+
checksum = "796be22e4854830de9e785ddbd814b275a80fe6a975598be355c239e5b4eabe1"
6262
dependencies = [
6363
"gccjit_sys",
6464
]
6565

6666
[[package]]
6767
name = "gccjit_sys"
68-
version = "1.4.0"
68+
version = "1.5.0"
6969
source = "registry+https://github.com/rust-lang/crates.io-index"
70-
checksum = "0cea2ed05e093fd90bd21fa03a7d09e07f62103ce94de21859f048c9a6c18e42"
70+
checksum = "3db558fc13a541c478ef791d2a88cdb276a9373f46fb2e8aa30734d19037ffa6"
7171
dependencies = [
7272
"libc",
7373
]

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ default = ["master"]
2020
[dependencies]
2121
object = { version = "0.37.0", default-features = false, features = ["std", "read"] }
2222
tempfile = "3.20"
23-
gccjit = { version = "3.4.0", features = ["dlopen"] }
23+
gccjit = { version = "3.5.0", features = ["dlopen"] }
2424
#gccjit = { git = "https://github.com/rust-lang/gccjit.rs", branch = "error-dlopen", features = ["dlopen"] }
2525

2626
# Local copy.

src/builder.rs

Lines changed: 64 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ use rustc_target::spec::{HasTargetSpec, HasX86AbiOpt, Target, X86Abi};
3636
use crate::abi::FnAbiGccExt;
3737
use crate::common::{SignType, TypeReflection, type_is_pointer};
3838
use crate::context::CodegenCx;
39-
use crate::errors;
4039
use crate::intrinsic::llvm;
4140
use crate::type_of::LayoutGccExt;
4241

@@ -312,14 +311,48 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> {
312311
self.block.get_function()
313312
}
314313

314+
/// Shared implementation of `call` and `tail_call`. For tail call it is important that this
315+
/// returns a bare call, and not the result assigned to a local, or the result of `add_eval`.
316+
fn build_call(
317+
&mut self,
318+
typ: Type<'gcc>,
319+
fn_abi: Option<&FnAbi<'tcx, Ty<'tcx>>>,
320+
func: RValue<'gcc>,
321+
args: &[RValue<'gcc>],
322+
funclet: Option<&Funclet>,
323+
must_tail: bool,
324+
) -> RValue<'gcc> {
325+
// FIXME(antoyo): remove when having a proper API.
326+
let gcc_func = unsafe { std::mem::transmute::<RValue<'gcc>, Function<'gcc>>(func) };
327+
let call = if self.functions.borrow().values().any(|value| *value == gcc_func) {
328+
// FIXME(antoyo): remove when the API supports a different type for functions.
329+
let func: Function<'gcc> = self.cx.rvalue_as_function(func);
330+
self.function_call(func, args, funclet, must_tail)
331+
} else {
332+
// If it's a not function that was defined, it's a function pointer.
333+
self.function_ptr_call(typ, fn_abi, func, args, funclet, must_tail)
334+
};
335+
if let Some(_fn_abi) = fn_abi {
336+
// FIXME(bjorn3): Apply function attributes
337+
}
338+
call
339+
}
340+
315341
pub fn function_call(
316342
&mut self,
317343
func: Function<'gcc>,
318344
args: &[RValue<'gcc>],
319345
_funclet: Option<&Funclet>,
346+
must_tail: bool,
320347
) -> RValue<'gcc> {
321348
let args = self.check_call("call", func, args);
322349

350+
let call = self.cx.context.new_call(self.location, func, &args);
351+
if must_tail {
352+
// Return the bare tail call, don't assign or `add_eval` it yet.
353+
return call;
354+
}
355+
323356
// gccjit requires to use the result of functions, even when it's not used.
324357
// That's why we assign the result to a local or call add_eval().
325358
let return_type = func.get_return_type();
@@ -331,15 +364,10 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> {
331364
return_type,
332365
format!("returnValue{}", self.next_value_counter()),
333366
);
334-
self.block.add_assignment(
335-
self.location,
336-
result,
337-
self.cx.context.new_call(self.location, func, &args),
338-
);
367+
self.block.add_assignment(self.location, result, call);
339368
result.to_rvalue()
340369
} else {
341-
self.block
342-
.add_eval(self.location, self.cx.context.new_call(self.location, func, &args));
370+
self.block.add_eval(self.location, call);
343371
// Return dummy value when not having return value.
344372
self.context.new_rvalue_zero(self.isize_type)
345373
}
@@ -352,6 +380,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> {
352380
mut func_ptr: RValue<'gcc>,
353381
args: &[RValue<'gcc>],
354382
_funclet: Option<&Funclet>,
383+
must_tail: bool,
355384
) -> RValue<'gcc> {
356385
let func_ptr_type = {
357386
let func_ptr_type = func_ptr.get_type();
@@ -376,6 +405,12 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> {
376405
let args_adjusted = args.len() != previous_arg_count;
377406
let args = self.check_ptr_call("call", func_ptr, &args, &on_stack_param_indices);
378407

408+
if must_tail {
409+
// Return the bare tail call, don't assign or `add_eval` it yet.
410+
let call = self.cx.context.new_call_through_ptr(self.location, func_ptr, &args);
411+
return call;
412+
}
413+
379414
// gccjit requires to use the result of functions, even when it's not used.
380415
// That's why we assign the result to a local or call add_eval().
381416
let return_type = gcc_func.get_return_type();
@@ -1798,34 +1833,34 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> {
17981833
funclet: Option<&Funclet>,
17991834
_instance: Option<Instance<'tcx>>,
18001835
) -> RValue<'gcc> {
1801-
// FIXME(antoyo): remove when having a proper API.
1802-
let gcc_func = unsafe { std::mem::transmute::<RValue<'gcc>, Function<'gcc>>(func) };
1803-
let call = if self.functions.borrow().values().any(|value| *value == gcc_func) {
1804-
// FIXME(antoyo): remove when the API supports a different type for functions.
1805-
let func: Function<'gcc> = self.cx.rvalue_as_function(func);
1806-
self.function_call(func, args, funclet)
1807-
} else {
1808-
// If it's a not function that was defined, it's a function pointer.
1809-
self.function_ptr_call(typ, fn_abi, func, args, funclet)
1810-
};
1811-
if let Some(_fn_abi) = fn_abi {
1812-
// FIXME(bjorn3): Apply function attributes
1813-
}
1814-
call
1836+
self.build_call(typ, fn_abi, func, args, funclet, false)
18151837
}
18161838

18171839
fn tail_call(
18181840
&mut self,
1819-
_llty: Self::Type,
1841+
llty: Self::Type,
18201842
_fn_attrs: Option<&CodegenFnAttrs>,
1821-
_fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
1822-
_llfn: Self::Value,
1823-
_args: &[Self::Value],
1824-
_funclet: Option<&Self::Funclet>,
1843+
fn_abi: &FnAbi<'tcx, Ty<'tcx>>,
1844+
llfn: Self::Value,
1845+
args: &[Self::Value],
1846+
funclet: Option<&Self::Funclet>,
18251847
_instance: Option<Instance<'tcx>>,
18261848
) {
1827-
// FIXME: implement support for explicit tail calls like rustc_codegen_llvm.
1828-
self.tcx.dcx().emit_fatal(errors::ExplicitTailCallsUnsupported);
1849+
// `emit_call` returns a bare call for here, it has not been assigned or passed to add_eval.
1850+
let call = self.build_call(llty, Some(fn_abi), llfn, args, funclet, true);
1851+
call.set_require_tail_call(true);
1852+
1853+
let return_type = self.current_func().get_return_type();
1854+
let void_type = self.context.new_type::<()>();
1855+
1856+
if return_type == void_type {
1857+
// For a void return the call is emitted as its own statement, immediately
1858+
// followed by a void return, so the tail call sits in tail position.
1859+
self.llbb().add_eval(self.location, call);
1860+
self.ret_void();
1861+
} else {
1862+
self.ret(call)
1863+
}
18291864
}
18301865

18311866
fn zext(&mut self, value: RValue<'gcc>, dest_typ: Type<'gcc>) -> RValue<'gcc> {

src/errors.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,6 @@ pub(crate) struct LtoBitcodeFromRlib {
2020
pub gcc_err: String,
2121
}
2222

23-
#[derive(Diagnostic)]
24-
#[diag("explicit tail calls with the 'become' keyword are not implemented in the GCC backend")]
25-
pub(crate) struct ExplicitTailCallsUnsupported;
26-
2723
#[derive(Diagnostic)]
2824
#[diag("asm contains a NUL byte")]
2925
pub(crate) struct NulBytesInAsm {

tests/failing-ui-tests.txt

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,6 @@ tests/ui/linking/no-gc-encapsulation-symbols.rs
7171
tests/ui/panics/unwind-force-no-unwind-tables.rs
7272
tests/ui/attributes/fn-align-dyn.rs
7373
tests/ui/linkage-attr/raw-dylib/elf/glibc-x86_64.rs
74-
tests/ui/explicit-tail-calls/recursion-etc.rs
75-
tests/ui/explicit-tail-calls/indexer.rs
76-
tests/ui/explicit-tail-calls/drop-order.rs
77-
tests/ui/explicit-tail-calls/become-cast-return.rs
78-
tests/ui/explicit-tail-calls/become-indirect-return.rs
7974
tests/ui/panics/panic-abort-backtrace-without-debuginfo.rs
8075
tests/ui/sanitizer/kcfi-c-variadic.rs
8176
tests/ui/sanitizer/kcfi/fn-trait-objects.rs
@@ -97,7 +92,6 @@ tests/ui/eii/linking/track_caller_cross_crate.rs
9792
tests/ui/asm/x86_64/global_asm_escape.rs
9893
tests/ui/lto/all-crates.rs
9994
tests/ui/eii/default/call_default_panics.rs
100-
tests/ui/explicit-tail-calls/indirect.rs
10195
tests/ui/traits/inheritance/self-in-supertype.rs
10296
tests/ui/fmt/fmt_debug/shallow.rs
10397
tests/ui/eii/eii_impl_with_contract.rs
@@ -109,5 +103,4 @@ tests/ui/eii/static/default.rs
109103
tests/ui/eii/static/default_cross_crate.rs
110104
tests/ui/eii/static/default_explicit.rs
111105
tests/ui/eii/static/default_cross_crate_explicit.rs
112-
tests/ui/explicit-tail-calls/default-trait-method.rs
113106
tests/ui/explicit-tail-calls/tailcc-no-signature-restriction.rs

0 commit comments

Comments
 (0)