| commit 97398da2580309352e2f85bc1e44fdf81f2fdba2 |
| Author: George Burgess IV <gbiv@google.com> |
| Date: Tue Jul 19 18:12:54 2022 -0700 |
| |
| . |
| |
| diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td |
| index 5f77073413fb..b3db0335c8f1 100644 |
| --- a/clang/include/clang/Basic/Attr.td |
| +++ b/clang/include/clang/Basic/Attr.td |
| @@ -4019,3 +4019,14 @@ def NoRandomizeLayout : InheritableAttr { |
| let LangOpts = [COnly]; |
| } |
| def : MutualExclusions<[RandomizeLayout, NoRandomizeLayout]>; |
| + |
| +def FunctionReturnThunks : InheritableAttr, |
| + TargetSpecificAttr<TargetAnyX86> { |
| + 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 |
| index dbb7f695a5a2..e431f3be5a72 100644 |
| --- a/clang/include/clang/Basic/AttrDocs.td |
| +++ b/clang/include/clang/Basic/AttrDocs.td |
| @@ -6586,3 +6586,25 @@ evaluate to NULL. |
| |
| }]; |
| } |
| + |
| +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 |
| index 8e89106993c2..a2c225cf2e19 100644 |
| --- a/clang/include/clang/Basic/CodeGenOptions.def |
| +++ b/clang/include/clang/Basic/CodeGenOptions.def |
| @@ -107,6 +107,7 @@ CODEGENOPT(CFProtectionReturn , 1, 0) ///< if -fcf-protection is |
| 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 |
| index 5fa681f53819..eb6535db6000 100644 |
| --- a/clang/include/clang/Driver/Options.td |
| +++ b/clang/include/clang/Driver/Options.td |
| @@ -1989,6 +1989,13 @@ def fcf_protection : Flag<["-"], "fcf-protection">, Group<f_Group>, Flags<[CoreO |
| HelpText<"Enable cf-protection in 'full' mode">; |
| def mibt_seal : Flag<["-"], "mibt-seal">, Group<m_Group>, Flags<[CoreOption, CC1Option]>, |
| HelpText<"Optimize fcf-protection=branch/full (requires LTO).">; |
| +def mfunction_return_EQ : Joined<["-"], "mfunction-return=">, |
| + Group<m_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<CodeGenOpts<"FunctionReturnThunks">, "Keep">; |
| |
| defm xray_instrument : BoolFOption<"xray-instrument", |
| LangOpts<"XRayInstrument">, DefaultFalse, |
| diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp |
| index 7d75d186969a..ecc7808c419a 100644 |
| --- a/clang/lib/CodeGen/CodeGenFunction.cpp |
| +++ b/clang/lib/CodeGen/CodeGenFunction.cpp |
| @@ -920,6 +920,20 @@ void CodeGenFunction::StartFunction(GlobalDecl GD, QualType RetTy, |
| if (D && D->hasAttr<NoProfileFunctionAttr>()) |
| Fn->addFnAttr(llvm::Attribute::NoProfile); |
| |
| + if (D) { |
| + // Function attributes take precedence over command line flags. |
| + if (auto *A = D->getAttr<FunctionReturnThunksAttr>()) { |
| + 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) { |
| // Add metadata for a kernel function. |
| EmitOpenCLKernelMetadata(FD, Fn); |
| diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp |
| index 43373083f8aa..11fe73fb9faf 100644 |
| --- a/clang/lib/Driver/ToolChains/Clang.cpp |
| +++ b/clang/lib/Driver/ToolChains/Clang.cpp |
| @@ -6307,6 +6307,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, |
| 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 |
| index 5d7916ace87e..dff5bc289308 100644 |
| --- a/clang/lib/Frontend/CompilerInvocation.cpp |
| +++ b/clang/lib/Frontend/CompilerInvocation.cpp |
| @@ -1485,6 +1485,9 @@ void CompilerInvocation::GenerateCodeGenArgs( |
| 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 @@ bool CompilerInvocation::ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args, |
| 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<llvm::FunctionReturnThunksKind>(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<unsigned>(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 |
| index bd4d9414365e..8d45fb4465ad 100644 |
| --- a/clang/lib/Sema/SemaDeclAttr.cpp |
| +++ b/clang/lib/Sema/SemaDeclAttr.cpp |
| @@ -7996,6 +7996,26 @@ static void handleZeroCallUsedRegsAttr(Sema &S, Decl *D, const ParsedAttr &AL) { |
| 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<FunctionReturnThunksAttr>(); |
| + 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<FunctionDecl>(D); |
| @@ -8862,6 +8882,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL, |
| 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 |
| index 000000000000..8b68cfe52afa |
| --- /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 |
| index 000000000000..9d58b7b2f857 |
| --- /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 |
| index 000000000000..fca010aefa55 |
| --- /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 |
| index 000000000000..9aefa0f8c0a8 |
| --- /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 |
| index b9499229c066..64e2bf619004 100644 |
| --- 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 |
| index 000000000000..e00eecc50ad9 |
| --- /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 |
| index 000000000000..c6fe88b821e3 |
| --- /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 |
| index d11283d73087..bdecba2675fa 100644 |
| --- a/llvm/docs/LangRef.rst |
| +++ b/llvm/docs/LangRef.rst |
| @@ -1678,6 +1678,10 @@ example: |
| 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 |
| index 5d96204ba42a..66d0acbce9ad 100644 |
| --- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h |
| +++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h |
| @@ -686,6 +686,7 @@ enum AttributeKindCodes { |
| 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 |
| index 7b955b40b0a8..ea4bf80205f8 100644 |
| --- a/llvm/include/llvm/IR/Attributes.td |
| +++ b/llvm/include/llvm/IR/Attributes.td |
| @@ -102,6 +102,10 @@ def DisableSanitizerInstrumentation: EnumAttr<"disable_sanitizer_instrumentation |
| /// 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 |
| index 71d0ddbfe05e..425d3a3d95d4 100644 |
| --- a/llvm/include/llvm/Support/CodeGen.h |
| +++ b/llvm/include/llvm/Support/CodeGen.h |
| @@ -103,6 +103,13 @@ namespace llvm { |
| 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 |
| index 6a7cd68d5a2f..a083cfa8d5db 100644 |
| --- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp |
| +++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp |
| @@ -1490,6 +1490,8 @@ static Attribute::AttrKind getAttrFromCode(uint64_t Code) { |
| 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 |
| index f5ccc80584bd..e2d6dd3cd341 100644 |
| --- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp |
| +++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp |
| @@ -632,6 +632,8 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) { |
| 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 |
| index fadd272049f4..312d347e2b2c 100644 |
| --- a/llvm/lib/Target/X86/CMakeLists.txt |
| +++ b/llvm/lib/Target/X86/CMakeLists.txt |
| @@ -74,6 +74,7 @@ set(sources |
| 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 |
| index 7344900f2e31..0ac916527495 100644 |
| --- a/llvm/lib/Target/X86/X86.h |
| +++ b/llvm/lib/Target/X86/X86.h |
| @@ -132,6 +132,9 @@ FunctionPass *createX86EvexToVexInsts(); |
| /// 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 <LineNumber, Discriminator> (with respect to eachother) |
| FunctionPass *createX86DiscriminateMemOpsPass(); |
| @@ -185,6 +188,7 @@ void initializeX86LowerAMXTypeLegacyPassPass(PassRegistry &); |
| 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 |
| index 000000000000..4b203229ba83 |
| --- /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<X86Subtarget>(); |
| + const bool Is64Bit = ST.getTargetTriple().getArch() == Triple::x86_64; |
| + const unsigned RetOpc = Is64Bit ? X86::RET64 : X86::RET32; |
| + SmallVector<MachineInstr *, 16> 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 |
| index 3818a4955821..776a0dded11f 100644 |
| --- a/llvm/lib/Target/X86/X86TargetMachine.cpp |
| +++ b/llvm/lib/Target/X86/X86TargetMachine.cpp |
| @@ -94,6 +94,7 @@ extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeX86Target() { |
| initializeX86OptimizeLEAPassPass(PR); |
| initializeX86PartialReductionPass(PR); |
| initializePseudoProbeInserterPass(PR); |
| + initializeX86ReturnThunksPass(PR); |
| } |
| |
| static std::unique_ptr<TargetLoweringObjectFile> createTLOF(const Triple &TT) { |
| @@ -569,6 +570,7 @@ void X86PassConfig::addPreEmitPass2() { |
| // 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 |
| index c4ef979790be..4e412aeb3317 100644 |
| --- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp |
| +++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp |
| @@ -927,6 +927,7 @@ Function *CodeExtractor::constructFunction(const ValueSet &inputs, |
| 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 |
| index ee582cab628d..78b54a5cf5c3 100644 |
| --- a/llvm/test/Bitcode/attributes.ll |
| +++ b/llvm/test/Bitcode/attributes.ll |
| @@ -532,6 +532,9 @@ define void @f86() nosanitize_bounds |
| 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 @@ define void @f86() nosanitize_bounds |
| ; 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 |
| index 43d0c0839940..cf0e0d401df2 100644 |
| --- 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 |
| index 000000000000..f40d971ed355 |
| --- /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 |
| index 000000000000..91c03e862182 |
| --- /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 |
| index 8552ebc348cb..cab34b96c233 100644 |
| --- a/llvm/test/CodeGen/X86/opt-pipeline.ll |
| +++ b/llvm/test/CodeGen/X86/opt-pipeline.ll |
| @@ -203,6 +203,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 |
| index 07d1b36e485b..e668013926bf 100644 |
| --- a/llvm/test/Transforms/Inline/attributes.ll |
| +++ b/llvm/test/Transforms/Inline/attributes.ll |
| @@ -600,6 +600,33 @@ define i32 @test_unsafe-fp-math3(i32 %i) "unsafe-fp-math"="true" { |
| ; 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 @@ define i32 @test_unsafe-fp-math3(i32 %i) "unsafe-fp-math"="true" { |
| ; 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 } |