From 61a02cd8e08d30a49de652b138f5f2effb80aad2 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 16 Jul 2026 21:48:09 +0200 Subject: [PATCH 1/2] add support for explicit tail calls (`musttail`) --- Cargo.lock | 8 ++-- Cargo.toml | 2 +- src/builder.rs | 93 ++++++++++++++++++++++++++------------ src/errors.rs | 4 -- tests/failing-ui-tests.txt | 7 --- 5 files changed, 69 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3786a94b241..87f95e11ec6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,18 +56,18 @@ dependencies = [ [[package]] name = "gccjit" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2c6ee720b5459292678267e9ffed8229b11e0e27fc5ecf7618634dc6272fa4" +checksum = "796be22e4854830de9e785ddbd814b275a80fe6a975598be355c239e5b4eabe1" dependencies = [ "gccjit_sys", ] [[package]] name = "gccjit_sys" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cea2ed05e093fd90bd21fa03a7d09e07f62103ce94de21859f048c9a6c18e42" +checksum = "3db558fc13a541c478ef791d2a88cdb276a9373f46fb2e8aa30734d19037ffa6" dependencies = [ "libc", ] diff --git a/Cargo.toml b/Cargo.toml index aba88456801..86b7b56acdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ default = ["master"] [dependencies] object = { version = "0.37.0", default-features = false, features = ["std", "read"] } tempfile = "3.20" -gccjit = { version = "3.4.0", features = ["dlopen"] } +gccjit = { version = "3.5.0", features = ["dlopen"] } #gccjit = { git = "https://github.com/rust-lang/gccjit.rs", branch = "error-dlopen", features = ["dlopen"] } # Local copy. diff --git a/src/builder.rs b/src/builder.rs index 9eca9eeb620..67c6616b519 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -36,7 +36,6 @@ use rustc_target::spec::{HasTargetSpec, HasX86AbiOpt, Target, X86Abi}; use crate::abi::FnAbiGccExt; use crate::common::{SignType, TypeReflection, type_is_pointer}; use crate::context::CodegenCx; -use crate::errors; use crate::intrinsic::llvm; use crate::type_of::LayoutGccExt; @@ -312,14 +311,48 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { self.block.get_function() } + /// Shared implementation of `call` and `tail_call`. For tail call it is important that this + /// returns a bare call, and not the result assigned to a local, or the result of `add_eval`. + fn build_call( + &mut self, + typ: Type<'gcc>, + fn_abi: Option<&FnAbi<'tcx, Ty<'tcx>>>, + func: RValue<'gcc>, + args: &[RValue<'gcc>], + funclet: Option<&Funclet>, + must_tail: bool, + ) -> RValue<'gcc> { + // FIXME(antoyo): remove when having a proper API. + let gcc_func = unsafe { std::mem::transmute::, Function<'gcc>>(func) }; + let call = if self.functions.borrow().values().any(|value| *value == gcc_func) { + // FIXME(antoyo): remove when the API supports a different type for functions. + let func: Function<'gcc> = self.cx.rvalue_as_function(func); + self.function_call(func, args, funclet, must_tail) + } else { + // If it's a not function that was defined, it's a function pointer. + self.function_ptr_call(typ, fn_abi, func, args, funclet, must_tail) + }; + if let Some(_fn_abi) = fn_abi { + // FIXME(bjorn3): Apply function attributes + } + call + } + pub fn function_call( &mut self, func: Function<'gcc>, args: &[RValue<'gcc>], _funclet: Option<&Funclet>, + must_tail: bool, ) -> RValue<'gcc> { let args = self.check_call("call", func, args); + let call = self.cx.context.new_call(self.location, func, &args); + if must_tail { + // Return the bare tail call, don't assign or `add_eval` it yet. + return call; + } + // gccjit requires to use the result of functions, even when it's not used. // That's why we assign the result to a local or call add_eval(). let return_type = func.get_return_type(); @@ -331,15 +364,10 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { return_type, format!("returnValue{}", self.next_value_counter()), ); - self.block.add_assignment( - self.location, - result, - self.cx.context.new_call(self.location, func, &args), - ); + self.block.add_assignment(self.location, result, call); result.to_rvalue() } else { - self.block - .add_eval(self.location, self.cx.context.new_call(self.location, func, &args)); + self.block.add_eval(self.location, call); // Return dummy value when not having return value. self.context.new_rvalue_zero(self.isize_type) } @@ -352,6 +380,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { mut func_ptr: RValue<'gcc>, args: &[RValue<'gcc>], _funclet: Option<&Funclet>, + must_tail: bool, ) -> RValue<'gcc> { let func_ptr_type = { let func_ptr_type = func_ptr.get_type(); @@ -376,6 +405,12 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { let args_adjusted = args.len() != previous_arg_count; let args = self.check_ptr_call("call", func_ptr, &args, &on_stack_param_indices); + if must_tail { + // Return the bare tail call, don't assign or `add_eval` it yet. + let call = self.cx.context.new_call_through_ptr(self.location, func_ptr, &args); + return call; + } + // gccjit requires to use the result of functions, even when it's not used. // That's why we assign the result to a local or call add_eval(). let return_type = gcc_func.get_return_type(); @@ -1798,34 +1833,34 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { funclet: Option<&Funclet>, _instance: Option>, ) -> RValue<'gcc> { - // FIXME(antoyo): remove when having a proper API. - let gcc_func = unsafe { std::mem::transmute::, Function<'gcc>>(func) }; - let call = if self.functions.borrow().values().any(|value| *value == gcc_func) { - // FIXME(antoyo): remove when the API supports a different type for functions. - let func: Function<'gcc> = self.cx.rvalue_as_function(func); - self.function_call(func, args, funclet) - } else { - // If it's a not function that was defined, it's a function pointer. - self.function_ptr_call(typ, fn_abi, func, args, funclet) - }; - if let Some(_fn_abi) = fn_abi { - // FIXME(bjorn3): Apply function attributes - } - call + self.build_call(typ, fn_abi, func, args, funclet, false) } fn tail_call( &mut self, - _llty: Self::Type, + llty: Self::Type, _fn_attrs: Option<&CodegenFnAttrs>, - _fn_abi: &FnAbi<'tcx, Ty<'tcx>>, - _llfn: Self::Value, - _args: &[Self::Value], - _funclet: Option<&Self::Funclet>, + fn_abi: &FnAbi<'tcx, Ty<'tcx>>, + llfn: Self::Value, + args: &[Self::Value], + funclet: Option<&Self::Funclet>, _instance: Option>, ) { - // FIXME: implement support for explicit tail calls like rustc_codegen_llvm. - self.tcx.dcx().emit_fatal(errors::ExplicitTailCallsUnsupported); + // `emit_call` returns a bare call for here, it has not been assigned or passed to add_eval. + let call = self.build_call(llty, Some(fn_abi), llfn, args, funclet, true); + call.set_require_tail_call(true); + + let return_type = self.current_func().get_return_type(); + let void_type = self.context.new_type::<()>(); + + if return_type == void_type { + // For a void return the call is emitted as its own statement, immediately + // followed by a void return, so the tail call sits in tail position. + self.llbb().add_eval(self.location, call); + self.ret_void(); + } else { + self.ret(call) + } } fn zext(&mut self, value: RValue<'gcc>, dest_typ: Type<'gcc>) -> RValue<'gcc> { diff --git a/src/errors.rs b/src/errors.rs index de633d3bdde..67723ebd2f3 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -20,10 +20,6 @@ pub(crate) struct LtoBitcodeFromRlib { pub gcc_err: String, } -#[derive(Diagnostic)] -#[diag("explicit tail calls with the 'become' keyword are not implemented in the GCC backend")] -pub(crate) struct ExplicitTailCallsUnsupported; - #[derive(Diagnostic)] #[diag("asm contains a NUL byte")] pub(crate) struct NulBytesInAsm { diff --git a/tests/failing-ui-tests.txt b/tests/failing-ui-tests.txt index bf274f2c781..84bfbc19c0a 100644 --- a/tests/failing-ui-tests.txt +++ b/tests/failing-ui-tests.txt @@ -71,11 +71,6 @@ tests/ui/linking/no-gc-encapsulation-symbols.rs tests/ui/panics/unwind-force-no-unwind-tables.rs tests/ui/attributes/fn-align-dyn.rs tests/ui/linkage-attr/raw-dylib/elf/glibc-x86_64.rs -tests/ui/explicit-tail-calls/recursion-etc.rs -tests/ui/explicit-tail-calls/indexer.rs -tests/ui/explicit-tail-calls/drop-order.rs -tests/ui/explicit-tail-calls/become-cast-return.rs -tests/ui/explicit-tail-calls/become-indirect-return.rs tests/ui/panics/panic-abort-backtrace-without-debuginfo.rs tests/ui/sanitizer/kcfi-c-variadic.rs tests/ui/sanitizer/kcfi/fn-trait-objects.rs @@ -97,7 +92,6 @@ tests/ui/eii/linking/track_caller_cross_crate.rs tests/ui/asm/x86_64/global_asm_escape.rs tests/ui/lto/all-crates.rs tests/ui/eii/default/call_default_panics.rs -tests/ui/explicit-tail-calls/indirect.rs tests/ui/traits/inheritance/self-in-supertype.rs tests/ui/fmt/fmt_debug/shallow.rs tests/ui/eii/eii_impl_with_contract.rs @@ -109,5 +103,4 @@ tests/ui/eii/static/default.rs tests/ui/eii/static/default_cross_crate.rs tests/ui/eii/static/default_explicit.rs tests/ui/eii/static/default_cross_crate_explicit.rs -tests/ui/explicit-tail-calls/default-trait-method.rs tests/ui/explicit-tail-calls/tailcc-no-signature-restriction.rs From 6b64c4da36d489741528eaf7c471b0f5c2b87f8f Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 16 Jul 2026 22:36:57 +0200 Subject: [PATCH 2/2] let's see --- src/builder.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 67c6616b519..a6c3593c577 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -435,20 +435,6 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { self.block.add_assignment(self.location, result, return_value); result.to_rvalue() } else { - #[cfg(not(feature = "master"))] - if gcc_func.get_param_count() == 0 { - // FIXME(antoyo): As a temporary workaround for unsupported LLVM intrinsics. - self.block.add_eval( - self.location, - self.cx.context.new_call_through_ptr(self.location, func_ptr, &[]), - ); - } else { - self.block.add_eval( - self.location, - self.cx.context.new_call_through_ptr(self.location, func_ptr, &args), - ); - } - #[cfg(feature = "master")] self.block.add_eval( self.location, self.cx.context.new_call_through_ptr(self.location, func_ptr, &args),