[clang] [llvm] [aarch64][x86][win] Add compiler support for MSVC's /funcoverride flag (Windows kernel loader replaceable functions) (PR #125320)
Daniel Paoliello via llvm-commits
llvm-commits at lists.llvm.org
Fri Jan 31 17:10:03 PST 2025
https://github.com/dpaoliello updated https://github.com/llvm/llvm-project/pull/125320
>From 061d2521e8db70c591e9c0d8f2e6348120cb55c1 Mon Sep 17 00:00:00 2001
From: "Daniel Paoliello (HE/HIM)" <danpao at microsoft.com>
Date: Fri, 31 Jan 2025 16:47:23 -0800
Subject: [PATCH] [aarch64][x86][win] Add support for MSVC's /funcoverride flag
(Windows kernel loader replaceable functions)
---
clang/include/clang/Basic/CodeGenOptions.h | 13 +++
clang/include/clang/Driver/Options.td | 7 ++
clang/lib/CodeGen/CGCall.cpp | 4 +
clang/lib/Driver/ToolChains/Clang.cpp | 6 ++
.../CodeGen/loader-replaceable-function.c | 14 +++
clang/test/Driver/cl-options.c | 4 +
llvm/include/llvm/CodeGen/AsmPrinter.h | 11 ++
llvm/include/llvm/IR/Attributes.td | 1 +
llvm/include/llvm/IR/Mangler.h | 2 +
llvm/lib/Analysis/InlineCost.cpp | 4 +
llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp | 100 ++++++++++++++++++
.../AArch64/AArch64Arm64ECCallLowering.cpp | 5 +-
llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp | 28 +----
llvm/lib/Target/X86/X86AsmPrinter.cpp | 37 +------
.../win-loader-replaceable-function.ll | 40 +++++++
.../X86/win-loader-replaceable-function.ll | 40 +++++++
llvm/test/Transforms/Inline/attributes.ll | 12 +++
17 files changed, 265 insertions(+), 63 deletions(-)
create mode 100644 clang/test/CodeGen/loader-replaceable-function.c
create mode 100644 llvm/test/CodeGen/AArch64/win-loader-replaceable-function.ll
create mode 100644 llvm/test/CodeGen/X86/win-loader-replaceable-function.ll
diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h
index c531c656f42b7e..579f50043369cb 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -489,6 +489,9 @@ class CodeGenOptions : public CodeGenOptionsBase {
/// The name of a file to use with \c .secure_log_unique directives.
std::string AsSecureLogFile;
+ /// A list of functions that are replacable by the loader.
+ std::vector<std::string> LoaderReplaceableFunctionNames;
+
public:
// Define accessors/mutators for code generation options of enumeration type.
#define CODEGENOPT(Name, Bits, Default)
@@ -561,6 +564,16 @@ class CodeGenOptions : public CodeGenOptionsBase {
/// Reset all of the options that are not considered when building a
/// module.
void resetNonModularOptions(StringRef ModuleFormat);
+
+ // Is the given function name one of the functions that can be replaced by the
+ // loader?
+ bool isLoaderReplaceableFunctionName(StringRef FuncName) const {
+ return std::any_of(LoaderReplaceableFunctionNames.begin(),
+ LoaderReplaceableFunctionNames.end(),
+ [&](const std::string &ReplaceableName) {
+ return FuncName == ReplaceableName;
+ });
+ }
};
} // end namespace clang
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index d8123cc39fdc95..8dcf0558519185 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -7592,6 +7592,9 @@ def import_call_optimization : Flag<["-"], "import-call-optimization">,
"by the Windows kernel to enable import call optimization">,
MarshallingInfoFlag<CodeGenOpts<"ImportCallOptimization">>;
+def replaceable_function: Joined<["-"], "loader-replaceable-function=">,
+ MarshallingInfoStringVector<CodeGenOpts<"LoaderReplaceableFunctionNames">>;
+
} // let Visibility = [CC1Option]
//===----------------------------------------------------------------------===//
@@ -8838,6 +8841,10 @@ def _SLASH_Gregcall : CLFlag<"Gregcall">,
def _SLASH_Gregcall4 : CLFlag<"Gregcall4">,
HelpText<"Set __regcall4 as a default calling convention to respect __regcall ABI v.4">;
+def _SLASH_funcoverride : CLCompileJoined<"funcoverride:">,
+ HelpText<"Mark <function> as being replaceable by the Windows kernel loader">,
+ MetaVarName<"<function>">;
+
// GNU Driver aliases
def : Separate<["-"], "Xmicrosoft-visualc-tools-root">, Alias<_SLASH_vctoolsdir>;
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 2dce86410db857..f8cc8108352852 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -2574,6 +2574,10 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
GetCPUAndFeaturesAttributes(CalleeInfo.getCalleeDecl(), FuncAttrs);
}
+ // Mark functions that are replaceable by the loader.
+ if (CodeGenOpts.isLoaderReplaceableFunctionName(Name))
+ FuncAttrs.addAttribute("loader-replaceable");
+
// Collect attributes from arguments and return values.
ClangToLLVMArgMapping IRFunctionArgs(getContext(), FI);
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 9b5132c5625faa..466bb42e557d41 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -8504,6 +8504,12 @@ void Clang::AddClangCLArgs(const ArgList &Args, types::ID InputType,
}
A->claim();
}
+
+ for (const auto &FuncOverride :
+ Args.getAllArgValues(options::OPT__SLASH_funcoverride)) {
+ CmdArgs.push_back(Args.MakeArgString(
+ Twine("-loader-replaceable-function=") + FuncOverride));
+ }
}
const char *Clang::getBaseInputName(const ArgList &Args,
diff --git a/clang/test/CodeGen/loader-replaceable-function.c b/clang/test/CodeGen/loader-replaceable-function.c
new file mode 100644
index 00000000000000..16cda854b1932a
--- /dev/null
+++ b/clang/test/CodeGen/loader-replaceable-function.c
@@ -0,0 +1,14 @@
+// RUN: %clang_cc1 -loader-replaceable-function=override_me -emit-llvm -std=c11 -o - %s | FileCheck %s
+
+// CHECK: define dso_local void @override_me() #0
+void override_me() {}
+
+// CHECK: define dso_local void @dont_override_me() #1
+void dont_override_me() {}
+
+// CHECK: attributes #0 = {
+// CHECK-SAME: loader-replaceable
+
+// CHECK: attributes #1 = {
+// CHECK-NOT: loader-replaceable
+// CHECK-SAME: }
diff --git a/clang/test/Driver/cl-options.c b/clang/test/Driver/cl-options.c
index 29a0fcbc17ac60..136abee8113d88 100644
--- a/clang/test/Driver/cl-options.c
+++ b/clang/test/Driver/cl-options.c
@@ -813,4 +813,8 @@
// RUN: %clang_cl -vctoolsdir "" /arm64EC /c -target x86_64-pc-windows-msvc -### -- %s 2>&1 | FileCheck --check-prefix=ARM64EC_OVERRIDE %s
// ARM64EC_OVERRIDE: warning: /arm64EC has been overridden by specified target: x86_64-pc-windows-msvc; option ignored
+// RUN: %clang_cl /funcoverride:override_me1 /funcoverride:override_me2 /c -### -- %s 2>&1 | FileCheck %s --check-prefix=FUNCOVERRIDE
+// FUNCOVERRIDE: -loader-replaceable-function=override_me1
+// FUNCOVERRIDE-SAME: -loader-replaceable-function=override_me2
+
void f(void) { }
diff --git a/llvm/include/llvm/CodeGen/AsmPrinter.h b/llvm/include/llvm/CodeGen/AsmPrinter.h
index 3da63af5ba5716..e1cdf6ad520741 100644
--- a/llvm/include/llvm/CodeGen/AsmPrinter.h
+++ b/llvm/include/llvm/CodeGen/AsmPrinter.h
@@ -796,6 +796,17 @@ class AsmPrinter : public MachineFunctionPass {
getCodeViewJumpTableInfo(int JTI, const MachineInstr *BranchInstr,
const MCSymbol *BranchLabel) const;
+ //===------------------------------------------------------------------===//
+ // COFF Helper Routines
+ //===------------------------------------------------------------------===//
+
+ /// Emits symbols and data to allow functions marked with the
+ /// loader-replaceable attribute to be replaceable.
+ void emitCOFFReplaceableFunctionData(Module &M);
+
+ /// Emits the @feat.00 symbol indicating the features enabled in this module.
+ void emitCOFFFeatureSymbol(Module &M);
+
//===------------------------------------------------------------------===//
// Inline Asm Support
//===------------------------------------------------------------------===//
diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td
index 70b9a2c488d3e9..1a35fc6632eea1 100644
--- a/llvm/include/llvm/IR/Attributes.td
+++ b/llvm/include/llvm/IR/Attributes.td
@@ -400,6 +400,7 @@ def NoJumpTables : StrBoolAttr<"no-jump-tables">;
def NoInlineLineTables : StrBoolAttr<"no-inline-line-tables">;
def ProfileSampleAccurate : StrBoolAttr<"profile-sample-accurate">;
def UseSampleProfile : StrBoolAttr<"use-sample-profile">;
+def LoaderReplaceable : StrBoolAttr<"loader-replaceable">;
def DenormalFPMath : ComplexStrAttr<"denormal-fp-math", [FnAttr]>;
def DenormalFPMathF32 : ComplexStrAttr<"denormal-fp-math-f32", [FnAttr]>;
diff --git a/llvm/include/llvm/IR/Mangler.h b/llvm/include/llvm/IR/Mangler.h
index 6c8ebf5f072f28..edbd0a5efb5dcc 100644
--- a/llvm/include/llvm/IR/Mangler.h
+++ b/llvm/include/llvm/IR/Mangler.h
@@ -25,6 +25,8 @@ class Triple;
class Twine;
class raw_ostream;
+constexpr std::string_view HybridPatchableTargetSuffix = "$hp_target";
+
class Mangler {
/// We need to give global values the same name every time they are mangled.
/// This keeps track of the number we give to anonymous ones.
diff --git a/llvm/lib/Analysis/InlineCost.cpp b/llvm/lib/Analysis/InlineCost.cpp
index 8fa150f7d690e7..af913ffef3141a 100644
--- a/llvm/lib/Analysis/InlineCost.cpp
+++ b/llvm/lib/Analysis/InlineCost.cpp
@@ -3078,6 +3078,10 @@ std::optional<InlineResult> llvm::getAttributeBasedInliningDecision(
if (Call.isNoInline())
return InlineResult::failure("noinline call site attribute");
+ // Don't inline functions that are loader replaceable.
+ if (Callee->hasFnAttribute("loader-replaceable"))
+ return InlineResult::failure("loader replaceable function attribute");
+
return std::nullopt;
}
diff --git a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
index 44b10c3ef99726..0e558f8a4e88e9 100644
--- a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
@@ -4661,3 +4661,103 @@ AsmPrinter::getCodeViewJumpTableInfo(int JTI, const MachineInstr *BranchInstr,
return std::make_tuple(Base, 0, BranchLabel,
codeview::JumpTableEntrySize::Int32);
}
+
+void AsmPrinter::emitCOFFReplaceableFunctionData(Module &M) {
+ const Triple &TT = TM.getTargetTriple();
+ assert(TT.isOSBinFormatCOFF());
+
+ bool IsTargetArm64EC = TT.isWindowsArm64EC();
+ SmallVector<char> Buf;
+ SmallVector<MCSymbol *> FuncOverrideDefaultSymbols;
+ OutStreamer->switchSection(
+ OutContext.getObjectFileInfo()->getDrectveSection());
+ for (const auto &F : M.functions()) {
+ if (F.hasFnAttribute("loader-replaceable")) {
+ auto Name = F.getName();
+
+ // For hybrid-patchable targets, strip the prefix so that we can mark
+ // the real function as replaceable.
+ if (IsTargetArm64EC && Name.ends_with(HybridPatchableTargetSuffix)) {
+ Name = Name.substr(0, Name.size() - HybridPatchableTargetSuffix.size());
+ }
+
+ llvm::Twine FuncOverrideName = Name + "_$fo$";
+ auto FuncOverrideSymbol =
+ MMI->getContext().getOrCreateSymbol(FuncOverrideName);
+ OutStreamer->beginCOFFSymbolDef(FuncOverrideSymbol);
+ OutStreamer->emitCOFFSymbolStorageClass(COFF::IMAGE_SYM_CLASS_EXTERNAL);
+ OutStreamer->emitCOFFSymbolType(COFF::IMAGE_SYM_DTYPE_NULL);
+ OutStreamer->endCOFFSymbolDef();
+
+ llvm::Twine FuncOverrideDefaultName = Name + "_$fo_default$";
+ auto FuncOverrideDefaultSymbol =
+ MMI->getContext().getOrCreateSymbol(FuncOverrideDefaultName);
+ OutStreamer->beginCOFFSymbolDef(FuncOverrideDefaultSymbol);
+ OutStreamer->emitCOFFSymbolStorageClass(COFF::IMAGE_SYM_CLASS_EXTERNAL);
+ OutStreamer->emitCOFFSymbolType(COFF::IMAGE_SYM_DTYPE_NULL);
+ OutStreamer->endCOFFSymbolDef();
+ FuncOverrideDefaultSymbols.push_back(FuncOverrideDefaultSymbol);
+
+ OutStreamer->emitBytes((Twine(" /ALTERNATENAME:") + FuncOverrideName +
+ "=" + FuncOverrideDefaultName)
+ .toStringRef(Buf));
+ Buf.clear();
+ }
+ }
+ OutStreamer->popSection();
+
+ if (FuncOverrideDefaultSymbols.empty())
+ return;
+
+ // MSVC emits the symbols for the default variables pointing at the start of
+ // the .data section, but doesn't actually allocate any space for them. LLVM
+ // can't do this, so have all of the variables pointing at a single byte
+ // instead.
+ OutStreamer->switchSection(OutContext.getObjectFileInfo()->getDataSection());
+ for (auto Symbol : FuncOverrideDefaultSymbols) {
+ OutStreamer->emitLabel(Symbol);
+ }
+ OutStreamer->emitZeros(1);
+ OutStreamer->popSection();
+}
+
+void AsmPrinter::emitCOFFFeatureSymbol(Module &M) {
+ const Triple &TT = TM.getTargetTriple();
+ assert(TT.isOSBinFormatCOFF());
+
+ // Emit an absolute @feat.00 symbol.
+ MCSymbol *S = MMI->getContext().getOrCreateSymbol(StringRef("@feat.00"));
+ OutStreamer->beginCOFFSymbolDef(S);
+ OutStreamer->emitCOFFSymbolStorageClass(COFF::IMAGE_SYM_CLASS_STATIC);
+ OutStreamer->emitCOFFSymbolType(COFF::IMAGE_SYM_DTYPE_NULL);
+ OutStreamer->endCOFFSymbolDef();
+ int64_t Feat00Value = 0;
+
+ if (TT.getArch() == Triple::x86) {
+ // According to the PE-COFF spec, the LSB of this value marks the object
+ // for "registered SEH". This means that all SEH handler entry points
+ // must be registered in .sxdata. Use of any unregistered handlers will
+ // cause the process to terminate immediately. LLVM does not know how to
+ // register any SEH handlers, so its object files should be safe.
+ Feat00Value |= COFF::Feat00Flags::SafeSEH;
+ }
+
+ if (M.getModuleFlag("cfguard")) {
+ // Object is CFG-aware.
+ Feat00Value |= COFF::Feat00Flags::GuardCF;
+ }
+
+ if (M.getModuleFlag("ehcontguard")) {
+ // Object also has EHCont.
+ Feat00Value |= COFF::Feat00Flags::GuardEHCont;
+ }
+
+ if (M.getModuleFlag("ms-kernel")) {
+ // Object is compiled with /kernel.
+ Feat00Value |= COFF::Feat00Flags::Kernel;
+ }
+
+ OutStreamer->emitSymbolAttribute(S, MCSA_Global);
+ OutStreamer->emitAssignment(
+ S, MCConstantExpr::create(Feat00Value, MMI->getContext()));
+}
diff --git a/llvm/lib/Target/AArch64/AArch64Arm64ECCallLowering.cpp b/llvm/lib/Target/AArch64/AArch64Arm64ECCallLowering.cpp
index abd2df301880c4..b63b798f86a331 100644
--- a/llvm/lib/Target/AArch64/AArch64Arm64ECCallLowering.cpp
+++ b/llvm/lib/Target/AArch64/AArch64Arm64ECCallLowering.cpp
@@ -808,7 +808,8 @@ bool AArch64Arm64ECCallLowering::runOnModule(Module &Mod) {
for (Function &F : Mod) {
if (!F.hasFnAttribute(Attribute::HybridPatchable) || F.isDeclaration() ||
- F.hasLocalLinkage() || F.getName().ends_with("$hp_target"))
+ F.hasLocalLinkage() ||
+ F.getName().ends_with(HybridPatchableTargetSuffix))
continue;
// Rename hybrid patchable functions and change callers to use a global
@@ -816,7 +817,7 @@ bool AArch64Arm64ECCallLowering::runOnModule(Module &Mod) {
if (std::optional<std::string> MangledName =
getArm64ECMangledFunctionName(F.getName().str())) {
std::string OrigName(F.getName());
- F.setName(MangledName.value() + "$hp_target");
+ F.setName(MangledName.value() + HybridPatchableTargetSuffix);
// The unmangled symbol is a weak alias to an undefined symbol with the
// "EXP+" prefix. This undefined symbol is resolved by the linker by
diff --git a/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp b/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp
index f1f25b65fc53fa..03d21e6f80a535 100644
--- a/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp
+++ b/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp
@@ -314,32 +314,8 @@ void AArch64AsmPrinter::emitStartOfAsmFile(Module &M) {
const Triple &TT = TM.getTargetTriple();
if (TT.isOSBinFormatCOFF()) {
- // Emit an absolute @feat.00 symbol
- MCSymbol *S = MMI->getContext().getOrCreateSymbol(StringRef("@feat.00"));
- OutStreamer->beginCOFFSymbolDef(S);
- OutStreamer->emitCOFFSymbolStorageClass(COFF::IMAGE_SYM_CLASS_STATIC);
- OutStreamer->emitCOFFSymbolType(COFF::IMAGE_SYM_DTYPE_NULL);
- OutStreamer->endCOFFSymbolDef();
- int64_t Feat00Value = 0;
-
- if (M.getModuleFlag("cfguard")) {
- // Object is CFG-aware.
- Feat00Value |= COFF::Feat00Flags::GuardCF;
- }
-
- if (M.getModuleFlag("ehcontguard")) {
- // Object also has EHCont.
- Feat00Value |= COFF::Feat00Flags::GuardEHCont;
- }
-
- if (M.getModuleFlag("ms-kernel")) {
- // Object is compiled with /kernel.
- Feat00Value |= COFF::Feat00Flags::Kernel;
- }
-
- OutStreamer->emitSymbolAttribute(S, MCSA_Global);
- OutStreamer->emitAssignment(
- S, MCConstantExpr::create(Feat00Value, MMI->getContext()));
+ emitCOFFFeatureSymbol(M);
+ emitCOFFReplaceableFunctionData(M);
if (M.getModuleFlag("import-call-optimization"))
EnableImportCallOptimization = true;
diff --git a/llvm/lib/Target/X86/X86AsmPrinter.cpp b/llvm/lib/Target/X86/X86AsmPrinter.cpp
index f01e47b41cf5e4..d85e4df0a08b27 100644
--- a/llvm/lib/Target/X86/X86AsmPrinter.cpp
+++ b/llvm/lib/Target/X86/X86AsmPrinter.cpp
@@ -885,41 +885,8 @@ void X86AsmPrinter::emitStartOfAsmFile(Module &M) {
OutStreamer->switchSection(getObjFileLowering().getTextSection());
if (TT.isOSBinFormatCOFF()) {
- // Emit an absolute @feat.00 symbol.
- MCSymbol *S = MMI->getContext().getOrCreateSymbol(StringRef("@feat.00"));
- OutStreamer->beginCOFFSymbolDef(S);
- OutStreamer->emitCOFFSymbolStorageClass(COFF::IMAGE_SYM_CLASS_STATIC);
- OutStreamer->emitCOFFSymbolType(COFF::IMAGE_SYM_DTYPE_NULL);
- OutStreamer->endCOFFSymbolDef();
- int64_t Feat00Value = 0;
-
- if (TT.getArch() == Triple::x86) {
- // According to the PE-COFF spec, the LSB of this value marks the object
- // for "registered SEH". This means that all SEH handler entry points
- // must be registered in .sxdata. Use of any unregistered handlers will
- // cause the process to terminate immediately. LLVM does not know how to
- // register any SEH handlers, so its object files should be safe.
- Feat00Value |= COFF::Feat00Flags::SafeSEH;
- }
-
- if (M.getModuleFlag("cfguard")) {
- // Object is CFG-aware.
- Feat00Value |= COFF::Feat00Flags::GuardCF;
- }
-
- if (M.getModuleFlag("ehcontguard")) {
- // Object also has EHCont.
- Feat00Value |= COFF::Feat00Flags::GuardEHCont;
- }
-
- if (M.getModuleFlag("ms-kernel")) {
- // Object is compiled with /kernel.
- Feat00Value |= COFF::Feat00Flags::Kernel;
- }
-
- OutStreamer->emitSymbolAttribute(S, MCSA_Global);
- OutStreamer->emitAssignment(
- S, MCConstantExpr::create(Feat00Value, MMI->getContext()));
+ emitCOFFFeatureSymbol(M);
+ emitCOFFReplaceableFunctionData(M);
}
OutStreamer->emitSyntaxDirective();
diff --git a/llvm/test/CodeGen/AArch64/win-loader-replaceable-function.ll b/llvm/test/CodeGen/AArch64/win-loader-replaceable-function.ll
new file mode 100644
index 00000000000000..745a67ae41b460
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/win-loader-replaceable-function.ll
@@ -0,0 +1,40 @@
+; RUN: llc -mtriple=aarch64-pc-windows-msvc < %s | FileCheck %s
+
+define dso_local i32 @override_me1() "loader-replaceable" {
+entry:
+ ret i32 1
+}
+
+define dso_local i32 @override_me2() "loader-replaceable" {
+entry:
+ ret i32 2
+}
+
+define dso_local i32 @dont_override_me() {
+entry:
+ ret i32 3
+}
+
+; CHECK: .section .drectve,"yni"
+; CHECK-NEXT: .def override_me1_$fo$;
+; CHECK-NEXT: .scl 2;
+; CHECK-NEXT: .type 0;
+; CHECK-NEXT: .endef
+; CHECK-NEXT: .def override_me1_$fo_default$;
+; CHECK-NEXT: .scl 2;
+; CHECK-NEXT: .type 0;
+; CHECK-NEXT: .endef
+; CHECK-NEXT: .ascii " /ALTERNATENAME:override_me1_$fo$=override_me1_$fo_default$"
+; CHECK-NEXT: .def override_me2_$fo$;
+; CHECK-NEXT: .scl 2;
+; CHECK-NEXT: .type 0;
+; CHECK-NEXT: .endef
+; CHECK-NEXT: .def override_me2_$fo_default$;
+; CHECK-NEXT: .scl 2;
+; CHECK-NEXT: .type 0;
+; CHECK-NEXT: .endef
+; CHECK-NEXT: .ascii " /ALTERNATENAME:override_me2_$fo$=override_me2_$fo_default$"
+; CHECK-NEXT: .data
+; CHECK-NEXT: override_me1_$fo_default$:
+; CHECK-NEXT: override_me2_$fo_default$:
+; CHECK-NEXT: .zero 1
diff --git a/llvm/test/CodeGen/X86/win-loader-replaceable-function.ll b/llvm/test/CodeGen/X86/win-loader-replaceable-function.ll
new file mode 100644
index 00000000000000..69212d3d56da8e
--- /dev/null
+++ b/llvm/test/CodeGen/X86/win-loader-replaceable-function.ll
@@ -0,0 +1,40 @@
+; RUN: llc -mtriple=x86_64-pc-windows-msvc < %s | FileCheck %s
+
+define dso_local i32 @override_me1() "loader-replaceable" {
+entry:
+ ret i32 1
+}
+
+define dso_local i32 @override_me2() "loader-replaceable" {
+entry:
+ ret i32 2
+}
+
+define dso_local i32 @dont_override_me() {
+entry:
+ ret i32 3
+}
+
+; CHECK: .section .drectve,"yni"
+; CHECK-NEXT: .def override_me1_$fo$;
+; CHECK-NEXT: .scl 2;
+; CHECK-NEXT: .type 0;
+; CHECK-NEXT: .endef
+; CHECK-NEXT: .def override_me1_$fo_default$;
+; CHECK-NEXT: .scl 2;
+; CHECK-NEXT: .type 0;
+; CHECK-NEXT: .endef
+; CHECK-NEXT: .ascii " /ALTERNATENAME:override_me1_$fo$=override_me1_$fo_default$"
+; CHECK-NEXT: .def override_me2_$fo$;
+; CHECK-NEXT: .scl 2;
+; CHECK-NEXT: .type 0;
+; CHECK-NEXT: .endef
+; CHECK-NEXT: .def override_me2_$fo_default$;
+; CHECK-NEXT: .scl 2;
+; CHECK-NEXT: .type 0;
+; CHECK-NEXT: .endef
+; CHECK-NEXT: .ascii " /ALTERNATENAME:override_me2_$fo$=override_me2_$fo_default$"
+; CHECK-NEXT: .data
+; CHECK-NEXT: override_me1_$fo_default$:
+; CHECK-NEXT: override_me2_$fo_default$:
+; CHECK-NEXT: .zero 1
diff --git a/llvm/test/Transforms/Inline/attributes.ll b/llvm/test/Transforms/Inline/attributes.ll
index 6595f54bda9804..42b1a3a29aec43 100644
--- a/llvm/test/Transforms/Inline/attributes.ll
+++ b/llvm/test/Transforms/Inline/attributes.ll
@@ -627,6 +627,18 @@ define i32 @thunk_extern_caller() fn_ret_thunk_extern {
ret i32 %1
}
+; Test that loader replaceable functions never get inlined.
+define i32 @loader_replaceable_callee(i32 %i) "loader-replaceable" {
+ ret i32 %i
+}
+
+define i32 @loader_replaceable_caller() {
+; CHECK: @loader_replaceable_caller() {
+; CHECK-NEXT: call i32 @loader_replaceable_callee()
+ %1 = call i32 @loader_replaceable_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" }
More information about the llvm-commits
mailing list