diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index c0fccdd0af413..230ea7a378651 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -4,7 +4,7 @@ use rustc_codegen_ssa::traits::*; use rustc_hir::def_id::DefId; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::ty::{self, TyCtxt}; -use rustc_session::config::OptLevel; +use rustc_session::config::{FunctionReturn, OptLevel}; use rustc_span::symbol::sym; use rustc_target::spec::abi::Abi; use rustc_target::spec::{FramePointer, SanitizerSet, StackProbeType, StackProtector}; @@ -118,6 +118,15 @@ pub fn frame_pointer_type_attr<'ll>(cx: &CodegenCx<'ll, '_>) -> Option<&'ll Attr Some(llvm::CreateAttrStringValue(cx.llcx, "frame-pointer", attr_value)) } +fn function_return_attr<'ll>(cx: &CodegenCx<'ll, '_>) -> Option<&'ll Attribute> { + let function_return_attr = match cx.sess().opts.unstable_opts.function_return { + FunctionReturn::Keep => return None, + FunctionReturn::ThunkExtern => AttributeKind::FnRetThunkExtern, + }; + + Some(function_return_attr.create_attr(cx.llcx)) +} + /// Tell LLVM what instrument function to insert. #[inline] fn instrument_function_attr<'ll>(cx: &CodegenCx<'ll, '_>) -> SmallVec<[&'ll Attribute; 4]> { @@ -333,6 +342,7 @@ pub fn from_fn_attrs<'ll, 'tcx>( // FIXME: none of these functions interact with source level attributes. to_add.extend(frame_pointer_type_attr(cx)); + to_add.extend(function_return_attr(cx)); to_add.extend(instrument_function_attr(cx)); to_add.extend(nojumptables_attr(cx)); to_add.extend(probestack_attr(cx)); diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index a038b3af03dd6..786e2f1922d5b 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -200,6 +200,7 @@ pub enum AttributeKind { AllocatedPointer = 38, AllocAlign = 39, SanitizeSafeStack = 40, + FnRetThunkExtern = 41, } /// LLVMIntPredicate diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 594283168c98d..1765bf5cdd16c 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -4,11 +4,11 @@ use rustc_data_structures::profiling::TimePassesFormat; use rustc_errors::{emitter::HumanReadableErrorType, registry, ColorConfig}; use rustc_session::config::{ build_configuration, build_session_options, rustc_optgroups, BranchProtection, CFGuard, Cfg, - DebugInfo, DumpMonoStatsFormat, ErrorOutputType, ExternEntry, ExternLocation, Externs, Input, - InstrumentCoverage, InstrumentXRay, LinkSelfContained, LinkerPluginLto, LocationDetail, LtoCli, - MirSpanview, OomStrategy, Options, OutFileName, OutputType, OutputTypes, PAuthKey, PacRet, - Passes, Polonius, ProcMacroExecutionStrategy, Strip, SwitchWithOptPath, SymbolManglingVersion, - TraitSolver, WasiExecModel, + DebugInfo, DumpMonoStatsFormat, ErrorOutputType, ExternEntry, ExternLocation, Externs, + FunctionReturn, Input, InstrumentCoverage, InstrumentXRay, LinkSelfContained, LinkerPluginLto, + LocationDetail, LtoCli, MirSpanview, OomStrategy, Options, OutFileName, OutputType, + OutputTypes, PAuthKey, PacRet, Passes, Polonius, ProcMacroExecutionStrategy, Strip, + SwitchWithOptPath, SymbolManglingVersion, TraitSolver, WasiExecModel, }; use rustc_session::lint::Level; use rustc_session::search_paths::SearchPath; @@ -760,6 +760,7 @@ fn test_unstable_options_tracking_hash() { tracked!(flatten_format_args, false); tracked!(force_unstable_if_unmarked, true); tracked!(fuel, Some(("abc".to_string(), 99))); + tracked!(function_return, FunctionReturn::ThunkExtern); tracked!(function_sections, Some(false)); tracked!(human_readable_cgu_names, true); tracked!(incremental_ignore_spans, true); diff --git a/compiler/rustc_llvm/llvm-wrapper/LLVMWrapper.h b/compiler/rustc_llvm/llvm-wrapper/LLVMWrapper.h index 3f2bf2c9b444d..537778ea01c16 100644 --- a/compiler/rustc_llvm/llvm-wrapper/LLVMWrapper.h +++ b/compiler/rustc_llvm/llvm-wrapper/LLVMWrapper.h @@ -94,6 +94,7 @@ enum LLVMRustAttribute { AllocatedPointer = 38, AllocAlign = 39, SanitizeSafeStack = 40, + FnRetThunkExtern = 41, }; typedef struct OpaqueRustString *RustStringRef; diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index 4390486b0deb1..459038f47b92b 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -283,6 +283,8 @@ static Attribute::AttrKind fromRust(LLVMRustAttribute Kind) { return Attribute::AllocAlign; case SanitizeSafeStack: return Attribute::SafeStack; + case FnRetThunkExtern: + return Attribute::FnRetThunkExtern; } report_fatal_error("bad AttributeKind"); } diff --git a/compiler/rustc_session/messages.ftl b/compiler/rustc_session/messages.ftl index fa1b6f9f13d60..3a7959c332e61 100644 --- a/compiler/rustc_session/messages.ftl +++ b/compiler/rustc_session/messages.ftl @@ -26,6 +26,10 @@ session_file_is_not_writeable = output file {$file} is not writeable -- check it session_file_write_fail = failed to write `{$path}` due to error `{$err}` +session_function_return_requires_x86_or_x86_64 = `-Zfunction-return` (except `keep`) is only supported on x86 and x86_64 + +session_function_return_thunk_extern_requires_non_large_code_model = `-Zfunction-return=thunk-extern` is only supported on non-large code models + session_hexadecimal_float_literal_not_supported = hexadecimal float literal is not supported session_incompatible_linker_flavor = linker flavor `{$flavor}` is incompatible with the current target diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index a8ebab4ae333c..3880119e50ebd 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -3171,10 +3171,10 @@ impl PpMode { pub(crate) mod dep_tracking { use super::{ BranchProtection, CFGuard, CFProtection, CrateType, DebugInfo, DebugInfoCompression, - ErrorOutputType, InstrumentCoverage, InstrumentXRay, LinkerPluginLto, LocationDetail, - LtoCli, OomStrategy, OptLevel, OutFileName, OutputType, OutputTypes, Polonius, - RemapPathScopeComponents, ResolveDocLinks, SourceFileHashAlgorithm, SplitDwarfKind, - SwitchWithOptPath, SymbolManglingVersion, TraitSolver, TrimmedDefPaths, + ErrorOutputType, FunctionReturn, InstrumentCoverage, InstrumentXRay, LinkerPluginLto, + LocationDetail, LtoCli, OomStrategy, OptLevel, OutFileName, OutputType, OutputTypes, + Polonius, RemapPathScopeComponents, ResolveDocLinks, SourceFileHashAlgorithm, + SplitDwarfKind, SwitchWithOptPath, SymbolManglingVersion, TraitSolver, TrimmedDefPaths, }; use crate::lint; use crate::options::WasiExecModel; @@ -3280,6 +3280,7 @@ pub(crate) mod dep_tracking { LanguageIdentifier, TraitSolver, Polonius, + FunctionReturn, ); impl DepTrackingHash for (T1, T2) @@ -3445,3 +3446,14 @@ impl Polonius { matches!(self, Polonius::Next) } } + +/// The different settings that the `-Zfunction-return` flag can have. +#[derive(Clone, Copy, PartialEq, Hash, Debug, Default)] +pub enum FunctionReturn { + /// Keep the function return unmodified. + #[default] + Keep, + + /// Replace returns with jumps to thunk, without emitting the thunk. + ThunkExtern, +} diff --git a/compiler/rustc_session/src/errors.rs b/compiler/rustc_session/src/errors.rs index 31094e0d26669..70ee46ea902dc 100644 --- a/compiler/rustc_session/src/errors.rs +++ b/compiler/rustc_session/src/errors.rs @@ -436,3 +436,11 @@ pub struct IncompatibleLinkerFlavor { pub flavor: &'static str, pub compatible_list: String, } + +#[derive(Diagnostic)] +#[diag(session_function_return_requires_x86_or_x86_64)] +pub(crate) struct FunctionReturnRequiresX86OrX8664; + +#[derive(Diagnostic)] +#[diag(session_function_return_thunk_extern_requires_non_large_code_model)] +pub(crate) struct FunctionReturnThunkExternRequiresNonLargeCodeModel; diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index 30c8b9d67002c..84b6e4b0347f2 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -428,6 +428,7 @@ mod desc { "one of supported execution strategies (`same-thread`, or `cross-thread`)"; pub const parse_dump_solver_proof_tree: &str = "one of: `always`, `on-request`, `on-error`"; pub const parse_remap_path_scope: &str = "comma separated list of scopes: `macro`, `diagnostics`, `unsplit-debuginfo`, `split-debuginfo`, `split-debuginfo-path`, `object`, `all`"; + pub const parse_function_return: &str = "`keep` or `thunk-extern`"; } mod parse { @@ -1310,6 +1311,15 @@ mod parse { }; true } + + pub(crate) fn parse_function_return(slot: &mut FunctionReturn, v: Option<&str>) -> bool { + match v { + Some("keep") => *slot = FunctionReturn::Keep, + Some("thunk-extern") => *slot = FunctionReturn::ThunkExtern, + _ => return false, + } + true + } } options! { @@ -1554,6 +1564,8 @@ options! { "force all crates to be `rustc_private` unstable (default: no)"), fuel: Option<(String, u64)> = (None, parse_optimization_fuel, [TRACKED], "set the optimization fuel quota for a crate"), + function_return: FunctionReturn = (FunctionReturn::default(), parse_function_return, [TRACKED], + "replace returns with jumps to `__x86_return_thunk` (default: `keep`)"), function_sections: Option = (None, parse_opt_bool, [TRACKED], "whether each function should go in its own section"), future_incompat_test: bool = (false, parse_bool, [UNTRACKED], diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index f823b55615402..5c7023df61e6d 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -1,7 +1,7 @@ use crate::code_stats::CodeStats; pub use crate::code_stats::{DataTypeKind, FieldInfo, FieldKind, SizeKind, VariantInfo}; use crate::config::{ - self, CrateType, InstrumentCoverage, OptLevel, OutFileName, OutputType, + self, CrateType, FunctionReturn, InstrumentCoverage, OptLevel, OutFileName, OutputType, RemapPathScopeComponents, SwitchWithOptPath, }; use crate::config::{ErrorOutputType, Input}; @@ -1707,6 +1707,26 @@ fn validate_commandline_args_with_session_available(sess: &Session) { sess.emit_err(errors::IncompatibleLinkerFlavor { flavor, compatible_list }); } } + + if sess.opts.unstable_opts.function_return != FunctionReturn::default() { + if sess.target.arch != "x86" && sess.target.arch != "x86_64" { + sess.emit_err(errors::FunctionReturnRequiresX86OrX8664); + } + } + + // The code model check applies to `thunk` and `thunk-extern`, but not `thunk-inline`, so it is + // kept as a `match` to force a change if new ones are added, even if we currently only support + // `thunk-extern` like Clang. + match sess.opts.unstable_opts.function_return { + FunctionReturn::Keep => (), + FunctionReturn::ThunkExtern => { + // FIXME: In principle, the inherited base LLVM target code model could be large, + // but this only checks whether we were passed one explicitly (like Clang does). + if let Some(code_model) = sess.code_model() && code_model == CodeModel::Large { + sess.emit_err(errors::FunctionReturnThunkExternRequiresNonLargeCodeModel); + } + } + } } /// Holds data on the current incremental compilation session, if there is one. diff --git a/src/doc/unstable-book/src/compiler-flags/function-return.md b/src/doc/unstable-book/src/compiler-flags/function-return.md new file mode 100644 index 0000000000000..d044a6f68aaab --- /dev/null +++ b/src/doc/unstable-book/src/compiler-flags/function-return.md @@ -0,0 +1,25 @@ +# `function-return` + +The tracking issue for this feature is: https://github.com/rust-lang/rust/issues/116853. + +------------------------ + +Option `-Zfunction-return` controls how function returns are converted. + +It is equivalent to [Clang]'s and [GCC]'s `-mfunction-return`. The Linux kernel +uses it for RETHUNK builds. For details, see [LLVM commit 2240d72f15f3] ("[X86] +initial -mfunction-return=thunk-extern support") which introduces the feature. + +Supported values for this option are: + + - `keep`: do not convert function returns. + - `thunk-extern`: convert function returns (`ret`) to jumps (`jmp`) + to an external symbol called `__x86_return_thunk`. + +Like in Clang, GCC's values `thunk` and `thunk-inline` are not supported. + +Only x86 and non-large code models are supported. + +[Clang]: https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-mfunction-return +[GCC]: https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html#index-mfunction-return +[LLVM commit 2240d72f15f3]: https://github.com/llvm/llvm-project/commit/2240d72f15f3b7b9d9fb65450f9bf635fd310f6f diff --git a/tests/assembly/x86_64-function-return.rs b/tests/assembly/x86_64-function-return.rs new file mode 100644 index 0000000000000..39e363ce650c8 --- /dev/null +++ b/tests/assembly/x86_64-function-return.rs @@ -0,0 +1,28 @@ +// Test that the function return is (not) converted into a jump to the thunk +// when the `-Zfunction-return={keep,thunk-extern}` flag is (not) set. + +// revisions: unset keep thunk-extern keep-thunk-extern thunk-extern-keep +// assembly-output: emit-asm +// compile-flags: -O +// [keep] compile-flags: -Zfunction-return=keep +// [thunk-extern] compile-flags: -Zfunction-return=thunk-extern +// [keep-thunk-extern] compile-flags: -Zfunction-return=keep -Zfunction-return=thunk-extern +// [thunk-extern-keep] compile-flags: -Zfunction-return=thunk-extern -Zfunction-return=keep +// only-x86_64 + +#![crate_type = "lib"] + +// CHECK-LABEL: foo: +#[no_mangle] +pub unsafe fn foo() { + // unset: ret + // unset-NOT: jmp __x86_return_thunk + // keep: ret + // keep-NOT: jmp __x86_return_thunk + // thunk-extern: jmp __x86_return_thunk + // thunk-extern-NOT: ret + // keep-thunk-extern: jmp __x86_return_thunk + // keep-thunk-extern-NOT: ret + // thunk-extern-keep: ret + // thunk-extern-keep-NOT: jmp __x86_return_thunk +} diff --git a/tests/codegen/function-return.rs b/tests/codegen/function-return.rs new file mode 100644 index 0000000000000..d832d19ac3923 --- /dev/null +++ b/tests/codegen/function-return.rs @@ -0,0 +1,28 @@ +// Test that the `fn_ret_thunk_extern` function attribute is (not) emitted when +// the `-Zfunction-return={keep,thunk-extern}` flag is (not) set. + +// revisions: unset keep thunk-extern keep-thunk-extern thunk-extern-keep +// needs-llvm-components: x86 +// compile-flags: --target x86_64-unknown-linux-gnu +// [keep] compile-flags: -Zfunction-return=keep +// [thunk-extern] compile-flags: -Zfunction-return=thunk-extern +// [keep-thunk-extern] compile-flags: -Zfunction-return=keep -Zfunction-return=thunk-extern +// [thunk-extern-keep] compile-flags: -Zfunction-return=thunk-extern -Zfunction-return=keep + +#![crate_type = "lib"] +#![feature(no_core, lang_items)] +#![no_core] + +#[lang = "sized"] +trait Sized {} + +#[no_mangle] +pub fn foo() { + // CHECK: @foo() unnamed_addr #0 + + // unset-NOT: fn_ret_thunk_extern + // keep-NOT: fn_ret_thunk_extern + // thunk-extern: attributes #0 = { {{.*}}fn_ret_thunk_extern{{.*}} } + // keep-thunk-extern: attributes #0 = { {{.*}}fn_ret_thunk_extern{{.*}} } + // thunk-extern-keep-NOT: fn_ret_thunk_extern +} diff --git a/tests/ui/invalid-compile-flags/function-return/requires-x86-or-x86_64.aarch64.stderr b/tests/ui/invalid-compile-flags/function-return/requires-x86-or-x86_64.aarch64.stderr new file mode 100644 index 0000000000000..550f95538048d --- /dev/null +++ b/tests/ui/invalid-compile-flags/function-return/requires-x86-or-x86_64.aarch64.stderr @@ -0,0 +1,4 @@ +error: `-Zfunction-return` (except `keep`) is only supported on x86 and x86_64 + +error: aborting due to previous error + diff --git a/tests/ui/invalid-compile-flags/function-return/requires-x86-or-x86_64.rs b/tests/ui/invalid-compile-flags/function-return/requires-x86-or-x86_64.rs new file mode 100644 index 0000000000000..15a88ebdb118a --- /dev/null +++ b/tests/ui/invalid-compile-flags/function-return/requires-x86-or-x86_64.rs @@ -0,0 +1,20 @@ +// revisions: x86 x86_64 aarch64 + +// compile-flags: -Zfunction-return=thunk-extern + +//[x86] check-pass +//[x86] needs-llvm-components: x86 +//[x86] compile-flags: --target i686-unknown-linux-gnu + +//[x86_64] check-pass +//[x86_64] needs-llvm-components: x86 +//[x86_64] compile-flags: --target x86_64-unknown-linux-gnu + +//[aarch64] check-fail +//[aarch64] needs-llvm-components: aarch64 +//[aarch64] compile-flags: --target aarch64-unknown-linux-gnu +//[aarch64] error-pattern: `-Zfunction-return` (except `keep`) is only supported on x86 and x86_64 + +#![feature(no_core)] +#![no_core] +#![no_main] diff --git a/tests/ui/invalid-compile-flags/function-return/thunk-extern-requires-non-large-code-model.large.stderr b/tests/ui/invalid-compile-flags/function-return/thunk-extern-requires-non-large-code-model.large.stderr new file mode 100644 index 0000000000000..f0ca12cb8821d --- /dev/null +++ b/tests/ui/invalid-compile-flags/function-return/thunk-extern-requires-non-large-code-model.large.stderr @@ -0,0 +1,4 @@ +error: `-Zfunction-return=thunk-extern` is only supported on non-large code models + +error: aborting due to previous error + diff --git a/tests/ui/invalid-compile-flags/function-return/thunk-extern-requires-non-large-code-model.rs b/tests/ui/invalid-compile-flags/function-return/thunk-extern-requires-non-large-code-model.rs new file mode 100644 index 0000000000000..f925905de3613 --- /dev/null +++ b/tests/ui/invalid-compile-flags/function-return/thunk-extern-requires-non-large-code-model.rs @@ -0,0 +1,21 @@ +// revisions: small kernel medium large + +// needs-llvm-components: x86 +// compile-flags: --target x86_64-unknown-linux-gnu -Zfunction-return=thunk-extern + +//[small] check-pass +//[small] compile-flags: -Ccode-model=small + +//[kernel] check-pass +//[kernel] compile-flags: -Ccode-model=kernel + +//[medium] check-pass +//[medium] compile-flags: -Ccode-model=medium + +//[large] check-fail +//[large] compile-flags: -Ccode-model=large +//[large] error-pattern: `-Zfunction-return=thunk-extern` is only supported on non-large code models + +#![feature(no_core)] +#![no_core] +#![no_main]