diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -404,6 +404,12 @@ format string must correctly format the fixed parameter types of the function. Using the attribute this way emits a GCC compatibility diagnostic. +- Support was added for ``__attribute__((function_return("thunk-extern")))`` + to X86 to replace ``ret`` instructions with ``jmp __x86_return_thunk``. The + corresponding attribute to disable this, + ``__attribute__((function_return("keep")))`` was added. This is intended to + be used by the Linux kernel to mitigate RETBLEED. + Windows Support --------------- @@ -556,6 +562,9 @@ this instruction (see rdpruintrin.h). - Support ``-mstack-protector-guard-symbol=[SymbolName]`` to use the given symbol for addressing the stack protector guard. +- ``-mfunction-return=thunk-extern`` support was added to clang for x86. This + will be used by Linux kernel mitigations for RETBLEED. The corresponding flag + ``-mfunction-return=keep`` may be appended to disable the feature. DWARF Support in Clang ---------------------- diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -4036,3 +4036,14 @@ let LangOpts = [COnly]; } def : MutualExclusions<[RandomizeLayout, NoRandomizeLayout]>; + +def FunctionReturnThunks : InheritableAttr, + TargetSpecificAttr { + let Spellings = [GCC<"function_return">]; + let Args = [EnumArgument<"ThunkType", "Kind", + ["keep", "thunk-extern"], + ["Keep", "Extern"] + >]; + let Subjects = SubjectList<[Function]>; + let Documentation = [FunctionReturnThunksDocs]; +} diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -6606,6 +6606,27 @@ } return 0; } + }]; +} + +def FunctionReturnThunksDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +The attribute ``function_return`` can replace return instructions with jumps to +target-specific symbols. This attribute supports 2 possible values, +corresponding to the values supported by the ``-mfunction-return=`` command +line flag: +* ``__attribute__((function_return("keep")))`` to disable related transforms. + This is useful for undoing global setting from ``-mfunction-return=`` locally + for individual functions. +* ``__attribute__((function_return("thunk-extern")))`` to replace returns with + jumps, while NOT emitting the thunk. + +The values ``thunk`` and ``thunk-inline`` from GCC are not supported. + +The symbol used for ``thunk-extern`` is target specific: +* X86: ``__x86_return_thunk`` +As such, this function attribute is currently only supported on X86 targets. }]; } diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -107,6 +107,7 @@ CODEGENOPT(CFProtectionBranch , 1, 0) ///< if -fcf-protection is ///< set to full or branch. CODEGENOPT(IBTSeal, 1, 0) ///< set to optimize CFProtectionBranch. +CODEGENOPT(FunctionReturnThunks, 1, 0) ///< -mfunction-return={keep|thunk-extern} CODEGENOPT(XRayInstrumentFunctions , 1, 0) ///< Set when -fxray-instrument is ///< enabled. diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1998,6 +1998,13 @@ HelpText<"Enable cf-protection in 'full' mode">; def mibt_seal : Flag<["-"], "mibt-seal">, Group, Flags<[CoreOption, CC1Option]>, HelpText<"Optimize fcf-protection=branch/full (requires LTO).">; +def mfunction_return_EQ : Joined<["-"], "mfunction-return=">, + Group, Flags<[CoreOption, CC1Option]>, + HelpText<"Replace returns with jumps to ``__x86_return_thunk`` (x86 only, error otherwise)">, + Values<"keep,thunk-extern">, + NormalizedValues<["Keep", "Extern"]>, + NormalizedValuesScope<"llvm::FunctionReturnThunksKind">, + MarshallingInfoEnum, "Keep">; defm xray_instrument : BoolFOption<"xray-instrument", LangOpts<"XRayInstrument">, DefaultFalse, diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -898,6 +898,20 @@ if (D && D->hasAttr()) Fn->addFnAttr(llvm::Attribute::NoProfile); + if (D) { + // Function attributes take precedence over command line flags. + if (auto *A = D->getAttr()) { + switch (A->getThunkType()) { + case FunctionReturnThunksAttr::Kind::Keep: + break; + case FunctionReturnThunksAttr::Kind::Extern: + Fn->addFnAttr(llvm::Attribute::FnRetThunkExtern); + break; + } + } else if (CGM.getCodeGenOpts().FunctionReturnThunks) + Fn->addFnAttr(llvm::Attribute::FnRetThunkExtern); + } + if (FD && (getLangOpts().OpenCL || (getLangOpts().HIP && getLangOpts().CUDAIsDevice))) { // Add metadata for a kernel function. diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -6344,6 +6344,10 @@ if (IsUsingLTO) Args.AddLastArg(CmdArgs, options::OPT_mibt_seal); + if (Arg *A = Args.getLastArg(options::OPT_mfunction_return_EQ)) + CmdArgs.push_back( + Args.MakeArgString(Twine("-mfunction-return=") + A->getValue())); + // Forward -f options with positive and negative forms; we translate these by // hand. Do not propagate PGO options to the GPU-side compilations as the // profile info is for the host-side compilation only. diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -1485,6 +1485,9 @@ if (Opts.IBTSeal) GenerateArg(Args, OPT_mibt_seal, SA); + if (Opts.FunctionReturnThunks) + GenerateArg(Args, OPT_mfunction_return_EQ, "thunk-extern", SA); + for (const auto &F : Opts.LinkBitcodeFiles) { bool Builtint = F.LinkFlags == llvm::Linker::Flags::LinkOnlyNeeded && F.PropagateAttrs && F.Internalize; @@ -1825,6 +1828,27 @@ Diags.Report(diag::err_drv_invalid_value) << A->getAsString(Args) << Name; } + if (const Arg *A = Args.getLastArg(OPT_mfunction_return_EQ)) { + auto Val = llvm::StringSwitch(A->getValue()) + .Case("keep", llvm::FunctionReturnThunksKind::Keep) + .Case("thunk-extern", llvm::FunctionReturnThunksKind::Extern) + .Default(llvm::FunctionReturnThunksKind::Invalid); + // SystemZ might want to add support for "expolines." + if (!T.isX86()) + Diags.Report(diag::err_drv_argument_not_allowed_with) + << A->getSpelling() << T.getTriple(); + else if (Val == llvm::FunctionReturnThunksKind::Invalid) + Diags.Report(diag::err_drv_invalid_value) + << A->getAsString(Args) << A->getValue(); + else if (Val == llvm::FunctionReturnThunksKind::Extern && + Args.getLastArgValue(OPT_mcmodel_EQ).equals("large")) + Diags.Report(diag::err_drv_argument_not_allowed_with) + << A->getAsString(Args) + << Args.getLastArg(OPT_mcmodel_EQ)->getAsString(Args); + else + Opts.FunctionReturnThunks = static_cast(Val); + } + if (Opts.PrepareForLTO && Args.hasArg(OPT_mibt_seal)) Opts.IBTSeal = 1; diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -8000,6 +8000,26 @@ D->addAttr(ZeroCallUsedRegsAttr::Create(S.Context, Kind, AL)); } +static void handleFunctionReturnThunksAttr(Sema &S, Decl *D, + const ParsedAttr &AL) { + StringRef KindStr; + SourceLocation LiteralLoc; + if (!S.checkStringLiteralArgumentAttr(AL, 0, KindStr, &LiteralLoc)) + return; + + FunctionReturnThunksAttr::Kind Kind; + if (!FunctionReturnThunksAttr::ConvertStrToKind(KindStr, Kind)) { + S.Diag(LiteralLoc, diag::warn_attribute_type_not_supported) + << AL << KindStr; + return; + } + // FIXME: it would be good to better handle attribute merging rather than + // silently replacing the existing attribute, so long as it does not break + // the expected codegen tests. + D->dropAttr(); + D->addAttr(FunctionReturnThunksAttr::Create(S.Context, Kind, AL)); +} + static void handleSYCLKernelAttr(Sema &S, Decl *D, const ParsedAttr &AL) { // The 'sycl_kernel' attribute applies only to function templates. const auto *FD = cast(D); @@ -8866,6 +8886,9 @@ case ParsedAttr::AT_ZeroCallUsedRegs: handleZeroCallUsedRegsAttr(S, D, AL); break; + case ParsedAttr::AT_FunctionReturnThunks: + handleFunctionReturnThunksAttr(S, D, AL); + break; // Microsoft attributes: case ParsedAttr::AT_LayoutVersion: diff --git a/clang/test/CodeGen/attr-function-return.c b/clang/test/CodeGen/attr-function-return.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/attr-function-return.c @@ -0,0 +1,96 @@ +// RUN: %clang_cc1 -std=gnu2x -triple x86_64-linux-gnu %s -emit-llvm -o - \ +// RUN: | FileCheck %s --check-prefixes=CHECK,CHECK-NOM +// RUN: %clang_cc1 -std=gnu2x -triple x86_64-linux-gnu %s -emit-llvm -o - \ +// RUN: -mfunction-return=keep | FileCheck %s \ +// RUN: --check-prefixes=CHECK,CHECK-KEEP +// RUN: %clang_cc1 -std=gnu2x -triple x86_64-linux-gnu %s -emit-llvm -o - \ +// RUN: -mfunction-return=thunk-extern | FileCheck %s \ +// RUN: --check-prefixes=CHECK,CHECK-EXTERN + +#if !__has_attribute(function_return) +#error "missing attribute support for function_return" +#endif + +// CHECK: @keep() [[KEEP:#[0-9]+]] +__attribute__((function_return("keep"))) void keep(void) {} + +// CHECK: @keep2() [[KEEP:#[0-9]+]] +[[gnu::function_return("keep")]] void keep2(void) {} + +// CHECK: @thunk_extern() [[EXTERN:#[0-9]+]] +__attribute__((function_return("thunk-extern"))) void thunk_extern(void) {} + +// CHECK: @thunk_extern2() [[EXTERN:#[0-9]+]] +[[gnu::function_return("thunk-extern")]] void thunk_extern2(void) {} + +// CHECK: @double_thunk_keep() [[KEEP]] +// clang-format off +__attribute__((function_return("thunk-extern"))) +__attribute__((function_return("keep"))) +void double_thunk_keep(void) {} + +// CHECK: @double_thunk_keep2() [[KEEP]] +[[gnu::function_return("thunk-extern")]][[gnu::function_return("keep")]] +void double_thunk_keep2(void) {} + +// CHECK: @double_keep_thunk() [[EXTERN]] +__attribute__((function_return("keep"))) +__attribute__((function_return("thunk-extern"))) +void double_keep_thunk(void) {} + +// CHECK: @double_keep_thunk2() [[EXTERN]] +[[gnu::function_return("thunk-keep")]][[gnu::function_return("thunk-extern")]] +void double_keep_thunk2(void) {} + +// CHECK: @thunk_keep() [[KEEP]] +__attribute__((function_return("thunk-extern"), function_return("keep"))) +void thunk_keep(void) {} + +// CHECK: @thunk_keep2() [[KEEP]] +[[gnu::function_return("thunk-extern"), gnu::function_return("keep")]] +void thunk_keep2(void) {} + +// CHECK: @keep_thunk() [[EXTERN]] +__attribute__((function_return("keep"), function_return("thunk-extern"))) +void keep_thunk(void) {} + +// CHECK: @keep_thunk2() [[EXTERN]] +[[gnu::function_return("keep"), gnu::function_return("thunk-extern")]] +void keep_thunk2(void) {} +// clang-format on + +void undef(void); +// CHECK: @undef() [[KEEP]] +__attribute__((function_return("keep"))) void undef(void) {} + +void undef2(void); +// CHECK: @undef2() [[EXTERN]] +__attribute__((function_return("thunk-extern"))) void undef2(void) {} + +__attribute__((function_return("thunk-extern"))) void change_def(void); +// CHECK: @change_def() [[KEEP]] +__attribute__((function_return("keep"))) void change_def(void) {} + +__attribute__((function_return("keep"))) void change_def2(void); +// CHECK: @change_def2() [[EXTERN]] +__attribute__((function_return("thunk-extern"))) void change_def2(void) {} + +__attribute__((function_return("thunk-extern"))) void change_def3(void); +// CHECK: @change_def3() [[KEEP]] +[[gnu::function_return("keep")]] void change_def3(void) {} + +[[gnu::function_return("keep")]] void change_def4(void); +// CHECK: @change_def4() [[EXTERN]] +__attribute__((function_return("thunk-extern"))) void change_def4(void) {} + +// When there is no -mfunction-return= flag set (NOM) or it's set to keep, +// we don't emit anything into the IR for unattributed functions. + +// CHECK-NOM: @no_attrs() [[NOATTR:#[0-9]+]] +// CHECK-KEEP: @no_attrs() [[NOATTR:#[0-9]+]] +// CHECK-EXTERN: @no_attrs() [[EXTERN]] +void no_attrs(void) {} + +// CHECK-NOM-NOT: [[NOATTR]] = {{.*}}fn_ret_thunk_extern +// CHECK-KEEP-NOT: [[NOATTR]] = {{.*}}fn_ret_thunk_extern +// CHECK: [[EXTERN]] = {{.*}}fn_ret_thunk_extern diff --git a/clang/test/CodeGen/attr-function-return.cpp b/clang/test/CodeGen/attr-function-return.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/attr-function-return.cpp @@ -0,0 +1,52 @@ +// RUN: %clang_cc1 -triple x86_64-linux-gnu %s -emit-llvm -o - \ +// RUN: | FileCheck %s --check-prefixes=CHECK,CHECK-NOM +// RUN: %clang_cc1 -triple x86_64-linux-gnu %s -emit-llvm -o - \ +// RUN: -mfunction-return=keep | FileCheck %s \ +// RUN: --check-prefixes=CHECK,CHECK-KEEP +// RUN: %clang_cc1 -triple x86_64-linux-gnu %s -emit-llvm -o - \ +// RUN: -mfunction-return=thunk-extern | FileCheck %s \ +// RUN: --check-prefixes=CHECK,CHECK-EXTERN + +int foo(void) { + // CHECK: @"_ZZ3foovENK3$_0clEv"({{.*}}) [[NOATTR:#[0-9]+]] + return []() { + return 42; + }(); +} +int bar(void) { + // CHECK: @"_ZZ3barvENK3$_1clEv"({{.*}}) [[EXTERN:#[0-9]+]] + return []() __attribute__((function_return("thunk-extern"))) { + return 42; + } + (); +} +int baz(void) { + // CHECK: @"_ZZ3bazvENK3$_2clEv"({{.*}}) [[KEEP:#[0-9]+]] + return []() __attribute__((function_return("keep"))) { + return 42; + } + (); +} + +class Foo { +public: + // CHECK: @_ZN3Foo3fooEv({{.*}}) [[EXTERN]] + __attribute__((function_return("thunk-extern"))) int foo() { return 42; } +}; + +int quux() { + Foo my_foo; + return my_foo.foo(); +} + +// CHECK: @extern_c() [[EXTERN]] +extern "C" __attribute__((function_return("thunk-extern"))) void extern_c() {} +extern "C" { +// CHECK: @extern_c2() [[EXTERN]] +__attribute__((function_return("thunk-extern"))) void extern_c2() {} +} + +// CHECK-NOM-NOT: [[NOATTR]] = {{.*}}fn_ret_thunk_extern +// CHECK-KEEP-NOT: [[NOATTR]] = {{.*}}fn_ret_thunk_extern +// CHECK-KEEP-NOT: [[KEEP]] = {{.*}}fn_ret_thunk_extern +// CHECK-EXTERN: [[EXTERN]] = {{.*}}fn_ret_thunk_extern diff --git a/clang/test/Driver/mfunction-return.c b/clang/test/Driver/mfunction-return.c new file mode 100644 --- /dev/null +++ b/clang/test/Driver/mfunction-return.c @@ -0,0 +1,22 @@ +// RUN: %clang -mfunction-return= -### %s 2>&1 \ +// RUN: | FileCheck --check-prefix=CHECK-VALID %s +// RUN: not %clang -mfunction-return -### %s 2>&1 \ +// RUN: | FileCheck --check-prefix=CHECK-INVALID %s + +// RUN: %clang -mfunction-return=keep -### %s 2>&1 \ +// RUN: | FileCheck --check-prefix=CHECK-KEEP %s +// RUN: %clang -mfunction-return=thunk-extern -### %s 2>&1 \ +// RUN: | FileCheck --check-prefix=CHECK-EXTERN %s + +// RUN: %clang -mfunction-return=keep -mfunction-return=thunk-extern -### %s 2>&1 \ +// RUN: | FileCheck --check-prefix=CHECK-EXTERN %s +// RUN: %clang -mfunction-return=thunk-extern -mfunction-return=keep -### %s 2>&1 \ +// RUN: | FileCheck --check-prefix=CHECK-KEEP %s + +// CHECK-VALID: "-mfunction-return=" +// CHECK-INVALID: error: unknown argument: '-mfunction-return' + +// CHECK-KEEP: "-mfunction-return=keep" +// CHECK-KEEP-NOT: "-mfunction-return=thunk-extern" +// CHECK-EXTERN: "-mfunction-return=thunk-extern" +// CHECK-EXTERN-NOT: "-mfunction-return=keep" diff --git a/clang/test/Frontend/mfunction-return.c b/clang/test/Frontend/mfunction-return.c new file mode 100644 --- /dev/null +++ b/clang/test/Frontend/mfunction-return.c @@ -0,0 +1,20 @@ +// RUN: %clang_cc1 -mfunction-return=keep -triple x86_64-linux-gnu %s +// RUN: %clang_cc1 -mfunction-return=thunk-extern -triple x86_64-linux-gnu %s + +// RUN: not %clang_cc1 -mfunction-return=thunk -triple x86_64-linux-gnu %s 2>&1 \ +// RUN: | FileCheck --check-prefix=CHECK-THUNK %s +// RUN: not %clang_cc1 -mfunction-return=thunk-inline -triple x86_64-linux-gnu %s 2>&1 \ +// RUN: | FileCheck --check-prefix=CHECK-INLINE %s +// RUN: not %clang_cc1 -mfunction-return=invalid -triple x86_64-linux-gnu %s 2>&1 \ +// RUN: | FileCheck --check-prefix=CHECK-INVALID %s +// RUN: not %clang_cc1 -mfunction-return=thunk-extern -triple s390x-linux-gnu %s 2>&1 \ +// RUN: | FileCheck --check-prefix=CHECK-TARGET %s +// RUN: not %clang_cc1 -mfunction-return=thunk-extern -mcmodel=large \ +// RUN: -triple x86_64-linux-gnu %s 2>&1 \ +// RUN: | FileCheck --check-prefix=CHECK-LARGE %s + +// CHECK-THUNK: error: invalid value 'thunk' in '-mfunction-return=thunk' +// CHECK-INLINE: error: invalid value 'thunk-inline' in '-mfunction-return=thunk-inline' +// CHECK-INVALID: error: invalid value 'invalid' in '-mfunction-return=invalid' +// CHECK-TARGET: error: invalid argument '-mfunction-return=' not allowed with 's390x-unknown-linux-gnu' +// CHECK-LARGE: error: invalid argument '-mfunction-return=thunk-extern' not allowed with '-mcmodel=large' diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -69,6 +69,7 @@ // CHECK-NEXT: ExternalSourceSymbol ((SubjectMatchRule_record, SubjectMatchRule_enum, SubjectMatchRule_enum_constant, SubjectMatchRule_field, SubjectMatchRule_function, SubjectMatchRule_namespace, SubjectMatchRule_objc_category, SubjectMatchRule_objc_implementation, SubjectMatchRule_objc_interface, SubjectMatchRule_objc_method, SubjectMatchRule_objc_property, SubjectMatchRule_objc_protocol, SubjectMatchRule_record, SubjectMatchRule_type_alias, SubjectMatchRule_variable)) // CHECK-NEXT: FlagEnum (SubjectMatchRule_enum) // CHECK-NEXT: Flatten (SubjectMatchRule_function) +// CHECK-NEXT: FunctionReturnThunks (SubjectMatchRule_function) // CHECK-NEXT: GNUInline (SubjectMatchRule_function) // CHECK-NEXT: HIPManaged (SubjectMatchRule_variable) // CHECK-NEXT: Hot (SubjectMatchRule_function) diff --git a/clang/test/Sema/attr-function-return-unsupported-target.c b/clang/test/Sema/attr-function-return-unsupported-target.c new file mode 100644 --- /dev/null +++ b/clang/test/Sema/attr-function-return-unsupported-target.c @@ -0,0 +1,16 @@ +// RUN: %clang_cc1 -triple s390x-linux-gnu -fsyntax-only -verify %s + +// expected-warning@+1 {{unknown attribute 'function_return' ignored}} +__attribute__((function_return("keep"))) void x(void) {} + +// expected-warning@+1 {{unknown attribute 'function_return' ignored}} +__attribute__((function_return("thunk"))) void y(void) {} + +// expected-warning@+1 {{unknown attribute 'function_return' ignored}} +__attribute__((function_return("thunk-inline"))) void z(void) {} + +// expected-warning@+1 {{unknown attribute 'function_return' ignored}} +__attribute__((function_return("thunk-extern"))) void w(void) {} + +// expected-warning@+1 {{unknown attribute 'function_return' ignored}} +__attribute__((function_return("invalid"))) void v(void) {} diff --git a/clang/test/Sema/attr-function-return.c b/clang/test/Sema/attr-function-return.c new file mode 100644 --- /dev/null +++ b/clang/test/Sema/attr-function-return.c @@ -0,0 +1,23 @@ +// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsyntax-only -verify %s + +__attribute__((function_return("keep"))) void x(void) {} + +// expected-warning@+1 {{'function_return' attribute argument not supported: thunk}} +__attribute__((function_return("thunk"))) void y(void) {} + +// expected-warning@+1 {{'function_return' attribute argument not supported: thunk-inline}} +__attribute__((function_return("thunk-inline"))) void z(void) {} + +__attribute__((function_return("thunk-extern"))) void w(void) {} + +// expected-warning@+1 {{'function_return' attribute argument not supported: invalid}} +__attribute__((function_return("invalid"))) void v(void) {} + +// expected-error@+1 {{'function_return' attribute requires a string}} +__attribute__((function_return(5))) void a(void) {} + +// expected-error@+1 {{'function_return' attribute takes one argument}} +__attribute__((function_return)) void b(void) {} + +// expected-warning@+1 {{'function_return' attribute only applies to functions}} +__attribute__((function_return)) int c; diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst --- a/llvm/docs/LangRef.rst +++ b/llvm/docs/LangRef.rst @@ -1678,6 +1678,10 @@ Front ends can provide optional ``srcloc`` metadata nodes on call sites of such callees to attach information about where in the source language such a call came from. A string value can be provided as a note. +``fn_ret_thunk_extern`` + This attribute tells the code generator that returns from functions should + be replaced with jumps to externally-defined architecture-specific symbols. + For X86, this symbol's identifier is ``__x86_return_thunk``. ``"frame-pointer"`` This attribute tells the code generator whether the function should keep the frame pointer. The code generator may emit the frame pointer diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h --- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h +++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h @@ -688,6 +688,7 @@ ATTR_KIND_ALLOCATED_POINTER = 81, ATTR_KIND_ALLOC_KIND = 82, ATTR_KIND_PRESPLIT_COROUTINE = 83, + ATTR_KIND_FNRETTHUNK_EXTERN = 84, }; enum ComdatSelectionKindCodes { diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td --- a/llvm/include/llvm/IR/Attributes.td +++ b/llvm/include/llvm/IR/Attributes.td @@ -102,6 +102,10 @@ /// Provide pointer element type to intrinsic. def ElementType : TypeAttr<"elementtype", [ParamAttr]>; +/// Whether to keep return instructions, or replace with a jump to an external +/// symbol. +def FnRetThunkExtern : EnumAttr<"fn_ret_thunk_extern", [FnAttr]>; + /// Function may only access memory that is inaccessible from IR. def InaccessibleMemOnly : EnumAttr<"inaccessiblememonly", [FnAttr]>; diff --git a/llvm/include/llvm/Support/CodeGen.h b/llvm/include/llvm/Support/CodeGen.h --- a/llvm/include/llvm/Support/CodeGen.h +++ b/llvm/include/llvm/Support/CodeGen.h @@ -103,6 +103,13 @@ Async = 2, ///< "Asynchronous" unwind tables (instr precise) Default = 2, }; + + enum class FunctionReturnThunksKind : unsigned int { + Keep = 0, ///< No function return thunk. + Extern = 1, ///< Replace returns with jump to thunk, don't emit thunk. + Invalid = 2, ///< Not used. + }; + } // namespace llvm #endif diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp --- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -1855,6 +1855,8 @@ return Attribute::DisableSanitizerInstrumentation; case bitc::ATTR_KIND_ELEMENTTYPE: return Attribute::ElementType; + case bitc::ATTR_KIND_FNRETTHUNK_EXTERN: + return Attribute::FnRetThunkExtern; case bitc::ATTR_KIND_INACCESSIBLEMEM_ONLY: return Attribute::InaccessibleMemOnly; case bitc::ATTR_KIND_INACCESSIBLEMEM_OR_ARGMEMONLY: diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp --- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -634,6 +634,8 @@ return bitc::ATTR_KIND_COLD; case Attribute::DisableSanitizerInstrumentation: return bitc::ATTR_KIND_DISABLE_SANITIZER_INSTRUMENTATION; + case Attribute::FnRetThunkExtern: + return bitc::ATTR_KIND_FNRETTHUNK_EXTERN; case Attribute::Hot: return bitc::ATTR_KIND_HOT; case Attribute::ElementType: diff --git a/llvm/lib/Target/X86/CMakeLists.txt b/llvm/lib/Target/X86/CMakeLists.txt --- a/llvm/lib/Target/X86/CMakeLists.txt +++ b/llvm/lib/Target/X86/CMakeLists.txt @@ -75,6 +75,7 @@ X86PartialReduction.cpp X86RegisterBankInfo.cpp X86RegisterInfo.cpp + X86ReturnThunks.cpp X86SelectionDAGInfo.cpp X86ShuffleDecodeConstantPool.cpp X86SpeculativeLoadHardening.cpp diff --git a/llvm/lib/Target/X86/X86.h b/llvm/lib/Target/X86/X86.h --- a/llvm/lib/Target/X86/X86.h +++ b/llvm/lib/Target/X86/X86.h @@ -132,6 +132,9 @@ /// This pass creates the thunks for the retpoline feature. FunctionPass *createX86IndirectThunksPass(); +/// This pass replaces ret instructions with jmp's to __x86_return thunk. +FunctionPass *createX86ReturnThunksPass(); + /// This pass ensures instructions featuring a memory operand /// have distinctive (with respect to eachother) FunctionPass *createX86DiscriminateMemOpsPass(); @@ -185,6 +188,7 @@ void initializeX86PreAMXConfigPassPass(PassRegistry &); void initializeX86LowerTileCopyPass(PassRegistry &); void initializeX86LowerAMXIntrinsicsLegacyPassPass(PassRegistry &); +void initializeX86ReturnThunksPass(PassRegistry &); namespace X86AS { enum : unsigned { diff --git a/llvm/lib/Target/X86/X86ReturnThunks.cpp b/llvm/lib/Target/X86/X86ReturnThunks.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/Target/X86/X86ReturnThunks.cpp @@ -0,0 +1,92 @@ +//==- X86ReturnThunks.cpp - Replace rets with thunks or inline thunks --=// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// \file +/// +/// Pass that replaces ret instructions with a jmp to __x86_return_thunk. +/// +/// This corresponds to -mfunction-return=thunk-extern or +/// __attribute__((function_return("thunk-extern"). +/// +/// This pass is a minimal implementation necessary to help mitigate +/// RetBleed for the Linux kernel. +/// +/// Should support for thunk or thunk-inline be necessary in the future, then +/// this pass should be combined with x86-retpoline-thunks which already has +/// machinery to emit thunks. Until then, YAGNI. +/// +/// This pass is very similar to x86-lvi-ret. +/// +//===----------------------------------------------------------------------===// + +#include "X86.h" +#include "X86InstrInfo.h" +#include "X86Subtarget.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Triple.h" +#include "llvm/CodeGen/MachineBasicBlock.h" +#include "llvm/CodeGen/MachineFunction.h" +#include "llvm/CodeGen/MachineFunctionPass.h" +#include "llvm/CodeGen/MachineInstr.h" +#include "llvm/CodeGen/MachineInstrBuilder.h" +#include "llvm/MC/MCInstrDesc.h" +#include "llvm/Support/Debug.h" + +using namespace llvm; + +#define PASS_KEY "x86-return-thunks" +#define DEBUG_TYPE PASS_KEY + +struct X86ReturnThunks final : public MachineFunctionPass { + static char ID; + X86ReturnThunks() : MachineFunctionPass(ID) {} + StringRef getPassName() const override { return "X86 Return Thunks"; } + bool runOnMachineFunction(MachineFunction &MF) override; +}; + +char X86ReturnThunks::ID = 0; + +bool X86ReturnThunks::runOnMachineFunction(MachineFunction &MF) { + LLVM_DEBUG(dbgs() << getPassName() << "\n"); + + bool Modified = false; + + if (!MF.getFunction().hasFnAttribute(llvm::Attribute::FnRetThunkExtern)) + return Modified; + + StringRef ThunkName = "__x86_return_thunk"; + if (MF.getFunction().getName() == ThunkName) + return Modified; + + const auto &ST = MF.getSubtarget(); + const bool Is64Bit = ST.getTargetTriple().getArch() == Triple::x86_64; + const unsigned RetOpc = Is64Bit ? X86::RET64 : X86::RET32; + SmallVector Rets; + + for (MachineBasicBlock &MBB : MF) + for (MachineInstr &Term : MBB.terminators()) + if (Term.getOpcode() == RetOpc) + Rets.push_back(&Term); + + const MCInstrDesc &JMP = ST.getInstrInfo()->get(X86::TAILJMPd); + + for (MachineInstr *Ret : Rets) { + BuildMI(Ret->getParent(), Ret->getDebugLoc(), JMP) + .addExternalSymbol(ThunkName.data()); + Ret->eraseFromParent(); + Modified = true; + } + + return Modified; +} + +INITIALIZE_PASS(X86ReturnThunks, PASS_KEY, "X86 Return Thunks", false, false) + +FunctionPass *llvm::createX86ReturnThunksPass() { + return new X86ReturnThunks(); +} diff --git a/llvm/lib/Target/X86/X86TargetMachine.cpp b/llvm/lib/Target/X86/X86TargetMachine.cpp --- a/llvm/lib/Target/X86/X86TargetMachine.cpp +++ b/llvm/lib/Target/X86/X86TargetMachine.cpp @@ -100,6 +100,7 @@ initializeX86OptimizeLEAPassPass(PR); initializeX86PartialReductionPass(PR); initializePseudoProbeInserterPass(PR); + initializeX86ReturnThunksPass(PR); } static std::unique_ptr createTLOF(const Triple &TT) { @@ -575,6 +576,7 @@ // hand inspection of the codegen output. addPass(createX86SpeculativeExecutionSideEffectSuppression()); addPass(createX86IndirectThunksPass()); + addPass(createX86ReturnThunksPass()); // Insert extra int3 instructions after trailing call instructions to avoid // issues in the unwinder. diff --git a/llvm/lib/Transforms/Utils/CodeExtractor.cpp b/llvm/lib/Transforms/Utils/CodeExtractor.cpp --- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp +++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp @@ -927,6 +927,7 @@ case Attribute::AlwaysInline: case Attribute::Cold: case Attribute::DisableSanitizerInstrumentation: + case Attribute::FnRetThunkExtern: case Attribute::Hot: case Attribute::NoRecurse: case Attribute::InlineHint: diff --git a/llvm/test/Bitcode/attributes.ll b/llvm/test/Bitcode/attributes.ll --- a/llvm/test/Bitcode/attributes.ll +++ b/llvm/test/Bitcode/attributes.ll @@ -532,6 +532,9 @@ ret void; } +; CHECK: define void @f87() [[FNRETTHUNKEXTERN:#[0-9]+]] +define void @f87() fn_ret_thunk_extern { ret void } + ; CHECK: attributes #0 = { noreturn } ; CHECK: attributes #1 = { nounwind } ; CHECK: attributes #2 = { readnone } @@ -585,4 +588,5 @@ ; CHECK: attributes #50 = { disable_sanitizer_instrumentation } ; CHECK: attributes #51 = { uwtable(sync) } ; CHECK: attributes #52 = { nosanitize_bounds } +; CHECK: attributes [[FNRETTHUNKEXTERN]] = { fn_ret_thunk_extern } ; CHECK: attributes #[[NOBUILTIN]] = { nobuiltin } diff --git a/llvm/test/CodeGen/X86/O0-pipeline.ll b/llvm/test/CodeGen/X86/O0-pipeline.ll --- a/llvm/test/CodeGen/X86/O0-pipeline.ll +++ b/llvm/test/CodeGen/X86/O0-pipeline.ll @@ -71,8 +71,9 @@ ; CHECK-NEXT: Live DEBUG_VALUE analysis ; CHECK-NEXT: X86 Speculative Execution Side Effect Suppression ; CHECK-NEXT: X86 Indirect Thunks +; CHECK-NEXT: X86 Return Thunks ; CHECK-NEXT: Check CFA info and insert CFI instructions if needed -; CHECK-NEXT: X86 Load Value Injection (LVI) Ret-Hardening +; CHECK-NEXT: X86 Load Value Injection (LVI) Ret-Hardening ; CHECK-NEXT: Pseudo Probe Inserter ; CHECK-NEXT: Lazy Machine Block Frequency Analysis ; CHECK-NEXT: Machine Optimization Remark Emitter diff --git a/llvm/test/CodeGen/X86/attr-function-return.ll b/llvm/test/CodeGen/X86/attr-function-return.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/X86/attr-function-return.ll @@ -0,0 +1,11 @@ +; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py +; RUN: llc --mtriple=i386-linux-gnu %s -o - -verify-machineinstrs \ +; RUN: | FileCheck %s +; RUN: llc --mtriple=x86_64-linux-gnu %s -o - -verify-machineinstrs \ +; RUN: | FileCheck %s +define void @x() fn_ret_thunk_extern { +; CHECK-LABEL: x: +; CHECK: # %bb.0: +; CHECK-NEXT: jmp __x86_return_thunk + ret void +} diff --git a/llvm/test/CodeGen/X86/attr-function-return.mir b/llvm/test/CodeGen/X86/attr-function-return.mir new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/X86/attr-function-return.mir @@ -0,0 +1,62 @@ +# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py +# RUN: llc --mtriple=x86_64-linux-gnu -run-pass=x86-return-thunks \ +# RUN: -verify-machineinstrs %s -o - | FileCheck %s +--- | + ; ModuleID = 'y.ll' + source_filename = "y.ll" + target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + + define void @x() #0 { + ret void + } + + attributes #0 = { fn_ret_thunk_extern } + +... +--- +name: x +alignment: 16 +exposesReturnsTwice: false +legalized: false +regBankSelected: false +selected: false +failedISel: false +tracksRegLiveness: true +hasWinCFI: false +failsVerification: false +tracksDebugUserValues: true +registers: [] +liveins: [] +frameInfo: + isFrameAddressTaken: false + isReturnAddressTaken: false + hasStackMap: false + hasPatchPoint: false + stackSize: 0 + offsetAdjustment: 0 + maxAlignment: 1 + adjustsStack: false + hasCalls: false + stackProtector: '' + maxCallFrameSize: 0 + cvBytesOfCalleeSavedRegisters: 0 + hasOpaqueSPAdjustment: false + hasVAStart: false + hasMustTailInVarArgFunc: false + hasTailCall: false + localFrameSize: 0 + savePoint: '' + restorePoint: '' +fixedStack: [] +stack: [] +callSites: [] +debugValueSubstitutions: [] +constants: [] +machineFunctionInfo: {} +body: | + bb.0 (%ir-block.0): + ; CHECK-LABEL: name: x + ; CHECK: TAILJMPd &__x86_return_thunk, implicit $esp, implicit $ssp + RET64 + +... diff --git a/llvm/test/CodeGen/X86/opt-pipeline.ll b/llvm/test/CodeGen/X86/opt-pipeline.ll --- a/llvm/test/CodeGen/X86/opt-pipeline.ll +++ b/llvm/test/CodeGen/X86/opt-pipeline.ll @@ -204,6 +204,7 @@ ; CHECK-NEXT: Live DEBUG_VALUE analysis ; CHECK-NEXT: X86 Speculative Execution Side Effect Suppression ; CHECK-NEXT: X86 Indirect Thunks +; CHECK-NEXT: X86 Return Thunks ; CHECK-NEXT: Check CFA info and insert CFI instructions if needed ; CHECK-NEXT: X86 Load Value Injection (LVI) Ret-Hardening ; CHECK-NEXT: Pseudo Probe Inserter diff --git a/llvm/test/Transforms/Inline/attributes.ll b/llvm/test/Transforms/Inline/attributes.ll --- a/llvm/test/Transforms/Inline/attributes.ll +++ b/llvm/test/Transforms/Inline/attributes.ll @@ -600,6 +600,33 @@ ; CHECK-NEXT: ret i32 } +; Test that fn_ret_thunk_extern has no CompatRule; inlining is permitted. +; Test that fn_ret_thunk_extern has no MergeRule; fn_ret_thunk_extern is not +; propagated or dropped on the caller after inlining. +define i32 @thunk_extern_callee() fn_ret_thunk_extern { +; CHECK: @thunk_extern_callee() [[FNRETTHUNK_EXTERN:#[0-9]+]] + ret i32 42 +} + +define i32 @thunk_keep_caller() { +; CHECK: @thunk_keep_caller() { +; CHECK-NEXT: ret i32 42 + %1 = call i32 @thunk_extern_callee() + ret i32 %1 +} + +define i32 @thunk_keep_callee() { +; CHECK: @thunk_keep_callee() { + ret i32 42 +} + +define i32 @thunk_extern_caller() fn_ret_thunk_extern { +; CHECK: @thunk_extern_caller() [[FNRETTHUNK_EXTERN]] +; CHECK-NEXT: ret i32 42 + %1 = call i32 @thunk_keep_callee() + ret i32 %1 +} + ; CHECK: attributes [[SLH]] = { speculative_load_hardening } ; CHECK: attributes [[FPMAD_FALSE]] = { "less-precise-fpmad"="false" } ; CHECK: attributes [[FPMAD_TRUE]] = { "less-precise-fpmad"="true" } @@ -614,3 +641,4 @@ ; CHECK: attributes [[NO_SIGNED_ZEROS_FPMATH_TRUE]] = { "no-signed-zeros-fp-math"="true" } ; CHECK: attributes [[UNSAFE_FPMATH_FALSE]] = { "unsafe-fp-math"="false" } ; CHECK: attributes [[UNSAFE_FPMATH_TRUE]] = { "unsafe-fp-math"="true" } +; CHECK: attributes [[FNRETTHUNK_EXTERN]] = { fn_ret_thunk_extern }