Skip to content

Commit 97a58b0

Browse files
authored
[aarch64][x86][win] Add compiler support for MSVC's /funcoverride flag (Windows kernel loader replaceable functions) (#125320)
Adds support for MSVC's undocumented `/funcoverride` flag, which marks functions as being replaceable by the Windows kernel loader. This is used to allow functions to be upgraded depending on the capabilities of the current processor (e.g., the kernel can be built with the naive implementation of a function, but that function can be replaced at boot with one that uses SIMD instructions if the processor supports them). For each marked function we need to generate: * An undefined symbol named `<name>_$fo$`. * A defined symbol `<name>_$fo_default$` that points to the `.data` section (anywhere in the data section, it is assumed to be zero sized). * An `/ALTERNATENAME` linker directive that points from `<name>_$fo$` to `<name>_$fo_default$`. This is used by the MSVC linker to generate the appropriate metadata in the Dynamic Value Relocation Table. Marked function must never be inlined (otherwise those inline sites can't be replaced). Note that I've chosen to implement this in AsmPrinter as there was no way to create a `GlobalVariable` for `<name>_$fo$` that would result in a symbol being emitted (as nothing consumes it and it has no initializer). I tried to have `llvm.used` and `llvm.compiler.used` point to it, but this didn't help. Within LLVM I referred to this feature as "loader replaceable" as "function override" already has a different meaning to C++ developers... I also took the opportunity to extract the feature symbol generation code used by both AArch64 and X86 into a common function in AsmPrinter.
1 parent 8293955 commit 97a58b0

File tree

17 files changed

+270
-63
lines changed

17 files changed

+270
-63
lines changed

clang/include/clang/Basic/CodeGenOptions.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,9 @@ class CodeGenOptions : public CodeGenOptionsBase {
499499
/// The name of a file to use with \c .secure_log_unique directives.
500500
std::string AsSecureLogFile;
501501

502+
/// A list of functions that are replacable by the loader.
503+
std::vector<std::string> LoaderReplaceableFunctionNames;
504+
502505
public:
503506
// Define accessors/mutators for code generation options of enumeration type.
504507
#define CODEGENOPT(Name, Bits, Default)
@@ -571,6 +574,12 @@ class CodeGenOptions : public CodeGenOptionsBase {
571574
/// Reset all of the options that are not considered when building a
572575
/// module.
573576
void resetNonModularOptions(StringRef ModuleFormat);
577+
578+
// Is the given function name one of the functions that can be replaced by the
579+
// loader?
580+
bool isLoaderReplaceableFunctionName(StringRef FuncName) const {
581+
return llvm::is_contained(LoaderReplaceableFunctionNames, FuncName);
582+
}
574583
};
575584

576585
} // end namespace clang

clang/include/clang/Driver/Options.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7826,6 +7826,9 @@ def import_call_optimization : Flag<["-"], "import-call-optimization">,
78267826
"by the Windows kernel to enable import call optimization">,
78277827
MarshallingInfoFlag<CodeGenOpts<"ImportCallOptimization">>;
78287828

7829+
def replaceable_function: Joined<["-"], "loader-replaceable-function=">,
7830+
MarshallingInfoStringVector<CodeGenOpts<"LoaderReplaceableFunctionNames">>;
7831+
78297832
} // let Visibility = [CC1Option]
78307833

78317834
//===----------------------------------------------------------------------===//
@@ -9088,6 +9091,10 @@ def _SLASH_Gregcall : CLFlag<"Gregcall">,
90889091
def _SLASH_Gregcall4 : CLFlag<"Gregcall4">,
90899092
HelpText<"Set __regcall4 as a default calling convention to respect __regcall ABI v.4">;
90909093

9094+
def _SLASH_funcoverride : CLCompileJoined<"funcoverride:">,
9095+
HelpText<"Mark <function> as being replaceable by the Windows kernel loader">,
9096+
MetaVarName<"<function>">;
9097+
90919098
// GNU Driver aliases
90929099

90939100
def : Separate<["-"], "Xmicrosoft-visualc-tools-root">, Alias<_SLASH_vctoolsdir>;

clang/lib/CodeGen/CGCall.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2641,6 +2641,10 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
26412641
GetCPUAndFeaturesAttributes(CalleeInfo.getCalleeDecl(), FuncAttrs);
26422642
}
26432643

2644+
// Mark functions that are replaceable by the loader.
2645+
if (CodeGenOpts.isLoaderReplaceableFunctionName(Name))
2646+
FuncAttrs.addAttribute("loader-replaceable");
2647+
26442648
// Collect attributes from arguments and return values.
26452649
ClangToLLVMArgMapping IRFunctionArgs(getContext(), FI);
26462650

clang/lib/Driver/ToolChains/Clang.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8578,6 +8578,12 @@ void Clang::AddClangCLArgs(const ArgList &Args, types::ID InputType,
85788578
}
85798579
A->claim();
85808580
}
8581+
8582+
for (const auto &FuncOverride :
8583+
Args.getAllArgValues(options::OPT__SLASH_funcoverride)) {
8584+
CmdArgs.push_back(Args.MakeArgString(
8585+
Twine("-loader-replaceable-function=") + FuncOverride));
8586+
}
85818587
}
85828588

85838589
const char *Clang::getBaseInputName(const ArgList &Args,
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// RUN: %clang_cc1 -triple=x86_64-pc-windows-msvc -loader-replaceable-function=override_me -loader-replaceable-function="?override_me_cpp@@YAXXZ" -emit-llvm -o - %s | FileCheck %s
2+
3+
// CHECK: define dso_local void @override_me() #0
4+
extern "C" void override_me() {}
5+
6+
// CHECK: define dso_local void @"?override_me_cpp@@YAXXZ"() #0
7+
void override_me_cpp() {}
8+
9+
// CHECK: define dso_local void @dont_override_me() #1
10+
extern "C" void dont_override_me() {}
11+
12+
// CHECK: attributes #0 = {
13+
// CHECK-SAME: loader-replaceable
14+
15+
// CHECK: attributes #1 = {
16+
// CHECK-NOT: loader-replaceable
17+
// CHECK-SAME: }

clang/test/Driver/cl-options.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,4 +823,8 @@
823823
// RUN: %clang_cl /d2epilogunwind /c -### -- %s 2>&1 | FileCheck %s --check-prefix=EPILOGUNWIND
824824
// EPILOGUNWIND: -fwinx64-eh-unwindv2
825825

826+
// RUN: %clang_cl /funcoverride:override_me1 /funcoverride:override_me2 /c -### -- %s 2>&1 | FileCheck %s --check-prefix=FUNCOVERRIDE
827+
// FUNCOVERRIDE: -loader-replaceable-function=override_me1
828+
// FUNCOVERRIDE-SAME: -loader-replaceable-function=override_me2
829+
826830
void f(void) { }

llvm/include/llvm/CodeGen/AsmPrinter.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,17 @@ class AsmPrinter : public MachineFunctionPass {
812812
getCodeViewJumpTableInfo(int JTI, const MachineInstr *BranchInstr,
813813
const MCSymbol *BranchLabel) const;
814814

815+
//===------------------------------------------------------------------===//
816+
// COFF Helper Routines
817+
//===------------------------------------------------------------------===//
818+
819+
/// Emits symbols and data to allow functions marked with the
820+
/// loader-replaceable attribute to be replaceable.
821+
void emitCOFFReplaceableFunctionData(Module &M);
822+
823+
/// Emits the @feat.00 symbol indicating the features enabled in this module.
824+
void emitCOFFFeatureSymbol(Module &M);
825+
815826
//===------------------------------------------------------------------===//
816827
// Inline Asm Support
817828
//===------------------------------------------------------------------===//

llvm/include/llvm/IR/Attributes.td

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,7 @@ def NoJumpTables : StrBoolAttr<"no-jump-tables">;
400400
def NoInlineLineTables : StrBoolAttr<"no-inline-line-tables">;
401401
def ProfileSampleAccurate : StrBoolAttr<"profile-sample-accurate">;
402402
def UseSampleProfile : StrBoolAttr<"use-sample-profile">;
403+
def LoaderReplaceable : StrBoolAttr<"loader-replaceable">;
403404

404405
def DenormalFPMath : ComplexStrAttr<"denormal-fp-math", [FnAttr]>;
405406
def DenormalFPMathF32 : ComplexStrAttr<"denormal-fp-math-f32", [FnAttr]>;

llvm/include/llvm/IR/Mangler.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ class Triple;
2525
class Twine;
2626
class raw_ostream;
2727

28+
constexpr std::string_view HybridPatchableTargetSuffix = "$hp_target";
29+
2830
class Mangler {
2931
/// We need to give global values the same name every time they are mangled.
3032
/// This keeps track of the number we give to anonymous ones.

llvm/lib/Analysis/InlineCost.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3174,6 +3174,10 @@ std::optional<InlineResult> llvm::getAttributeBasedInliningDecision(
31743174
if (Call.isNoInline())
31753175
return InlineResult::failure("noinline call site attribute");
31763176

3177+
// Don't inline functions that are loader replaceable.
3178+
if (Callee->hasFnAttribute("loader-replaceable"))
3179+
return InlineResult::failure("loader replaceable function attribute");
3180+
31773181
return std::nullopt;
31783182
}
31793183

llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4723,3 +4723,109 @@ AsmPrinter::getCodeViewJumpTableInfo(int JTI, const MachineInstr *BranchInstr,
47234723
return std::make_tuple(Base, 0, BranchLabel,
47244724
codeview::JumpTableEntrySize::Int32);
47254725
}
4726+
4727+
void AsmPrinter::emitCOFFReplaceableFunctionData(Module &M) {
4728+
const Triple &TT = TM.getTargetTriple();
4729+
assert(TT.isOSBinFormatCOFF());
4730+
4731+
bool IsTargetArm64EC = TT.isWindowsArm64EC();
4732+
SmallVector<char> Buf;
4733+
SmallVector<MCSymbol *> FuncOverrideDefaultSymbols;
4734+
bool SwitchedToDirectiveSection = false;
4735+
for (const Function &F : M.functions()) {
4736+
if (F.hasFnAttribute("loader-replaceable")) {
4737+
if (!SwitchedToDirectiveSection) {
4738+
OutStreamer->switchSection(
4739+
OutContext.getObjectFileInfo()->getDrectveSection());
4740+
SwitchedToDirectiveSection = true;
4741+
}
4742+
4743+
StringRef Name = F.getName();
4744+
4745+
// For hybrid-patchable targets, strip the prefix so that we can mark
4746+
// the real function as replaceable.
4747+
if (IsTargetArm64EC && Name.ends_with(HybridPatchableTargetSuffix)) {
4748+
Name = Name.drop_back(HybridPatchableTargetSuffix.size());
4749+
}
4750+
4751+
MCSymbol *FuncOverrideSymbol =
4752+
MMI->getContext().getOrCreateSymbol(Name + "_$fo$");
4753+
OutStreamer->beginCOFFSymbolDef(FuncOverrideSymbol);
4754+
OutStreamer->emitCOFFSymbolStorageClass(COFF::IMAGE_SYM_CLASS_EXTERNAL);
4755+
OutStreamer->emitCOFFSymbolType(COFF::IMAGE_SYM_DTYPE_NULL);
4756+
OutStreamer->endCOFFSymbolDef();
4757+
4758+
MCSymbol *FuncOverrideDefaultSymbol =
4759+
MMI->getContext().getOrCreateSymbol(Name + "_$fo_default$");
4760+
OutStreamer->beginCOFFSymbolDef(FuncOverrideDefaultSymbol);
4761+
OutStreamer->emitCOFFSymbolStorageClass(COFF::IMAGE_SYM_CLASS_EXTERNAL);
4762+
OutStreamer->emitCOFFSymbolType(COFF::IMAGE_SYM_DTYPE_NULL);
4763+
OutStreamer->endCOFFSymbolDef();
4764+
FuncOverrideDefaultSymbols.push_back(FuncOverrideDefaultSymbol);
4765+
4766+
OutStreamer->emitBytes((Twine(" /ALTERNATENAME:") +
4767+
FuncOverrideSymbol->getName() + "=" +
4768+
FuncOverrideDefaultSymbol->getName())
4769+
.toStringRef(Buf));
4770+
Buf.clear();
4771+
}
4772+
}
4773+
4774+
if (SwitchedToDirectiveSection)
4775+
OutStreamer->popSection();
4776+
4777+
if (FuncOverrideDefaultSymbols.empty())
4778+
return;
4779+
4780+
// MSVC emits the symbols for the default variables pointing at the start of
4781+
// the .data section, but doesn't actually allocate any space for them. LLVM
4782+
// can't do this, so have all of the variables pointing at a single byte
4783+
// instead.
4784+
OutStreamer->switchSection(OutContext.getObjectFileInfo()->getDataSection());
4785+
for (MCSymbol *Symbol : FuncOverrideDefaultSymbols) {
4786+
OutStreamer->emitLabel(Symbol);
4787+
}
4788+
OutStreamer->emitZeros(1);
4789+
OutStreamer->popSection();
4790+
}
4791+
4792+
void AsmPrinter::emitCOFFFeatureSymbol(Module &M) {
4793+
const Triple &TT = TM.getTargetTriple();
4794+
assert(TT.isOSBinFormatCOFF());
4795+
4796+
// Emit an absolute @feat.00 symbol.
4797+
MCSymbol *S = MMI->getContext().getOrCreateSymbol(StringRef("@feat.00"));
4798+
OutStreamer->beginCOFFSymbolDef(S);
4799+
OutStreamer->emitCOFFSymbolStorageClass(COFF::IMAGE_SYM_CLASS_STATIC);
4800+
OutStreamer->emitCOFFSymbolType(COFF::IMAGE_SYM_DTYPE_NULL);
4801+
OutStreamer->endCOFFSymbolDef();
4802+
int64_t Feat00Value = 0;
4803+
4804+
if (TT.getArch() == Triple::x86) {
4805+
// According to the PE-COFF spec, the LSB of this value marks the object
4806+
// for "registered SEH". This means that all SEH handler entry points
4807+
// must be registered in .sxdata. Use of any unregistered handlers will
4808+
// cause the process to terminate immediately. LLVM does not know how to
4809+
// register any SEH handlers, so its object files should be safe.
4810+
Feat00Value |= COFF::Feat00Flags::SafeSEH;
4811+
}
4812+
4813+
if (M.getModuleFlag("cfguard")) {
4814+
// Object is CFG-aware.
4815+
Feat00Value |= COFF::Feat00Flags::GuardCF;
4816+
}
4817+
4818+
if (M.getModuleFlag("ehcontguard")) {
4819+
// Object also has EHCont.
4820+
Feat00Value |= COFF::Feat00Flags::GuardEHCont;
4821+
}
4822+
4823+
if (M.getModuleFlag("ms-kernel")) {
4824+
// Object is compiled with /kernel.
4825+
Feat00Value |= COFF::Feat00Flags::Kernel;
4826+
}
4827+
4828+
OutStreamer->emitSymbolAttribute(S, MCSA_Global);
4829+
OutStreamer->emitAssignment(
4830+
S, MCConstantExpr::create(Feat00Value, MMI->getContext()));
4831+
}

llvm/lib/Target/AArch64/AArch64Arm64ECCallLowering.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -815,15 +815,16 @@ bool AArch64Arm64ECCallLowering::runOnModule(Module &Mod) {
815815
}
816816

817817
if (!F.hasFnAttribute(Attribute::HybridPatchable) || F.isDeclaration() ||
818-
F.hasLocalLinkage() || F.getName().ends_with("$hp_target"))
818+
F.hasLocalLinkage() ||
819+
F.getName().ends_with(HybridPatchableTargetSuffix))
819820
continue;
820821

821822
// Rename hybrid patchable functions and change callers to use a global
822823
// alias instead.
823824
if (std::optional<std::string> MangledName =
824825
getArm64ECMangledFunctionName(F.getName().str())) {
825826
std::string OrigName(F.getName());
826-
F.setName(MangledName.value() + "$hp_target");
827+
F.setName(MangledName.value() + HybridPatchableTargetSuffix);
827828

828829
// The unmangled symbol is a weak alias to an undefined symbol with the
829830
// "EXP+" prefix. This undefined symbol is resolved by the linker by

llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -329,32 +329,8 @@ void AArch64AsmPrinter::emitStartOfAsmFile(Module &M) {
329329
const Triple &TT = TM.getTargetTriple();
330330

331331
if (TT.isOSBinFormatCOFF()) {
332-
// Emit an absolute @feat.00 symbol
333-
MCSymbol *S = MMI->getContext().getOrCreateSymbol(StringRef("@feat.00"));
334-
OutStreamer->beginCOFFSymbolDef(S);
335-
OutStreamer->emitCOFFSymbolStorageClass(COFF::IMAGE_SYM_CLASS_STATIC);
336-
OutStreamer->emitCOFFSymbolType(COFF::IMAGE_SYM_DTYPE_NULL);
337-
OutStreamer->endCOFFSymbolDef();
338-
int64_t Feat00Value = 0;
339-
340-
if (M.getModuleFlag("cfguard")) {
341-
// Object is CFG-aware.
342-
Feat00Value |= COFF::Feat00Flags::GuardCF;
343-
}
344-
345-
if (M.getModuleFlag("ehcontguard")) {
346-
// Object also has EHCont.
347-
Feat00Value |= COFF::Feat00Flags::GuardEHCont;
348-
}
349-
350-
if (M.getModuleFlag("ms-kernel")) {
351-
// Object is compiled with /kernel.
352-
Feat00Value |= COFF::Feat00Flags::Kernel;
353-
}
354-
355-
OutStreamer->emitSymbolAttribute(S, MCSA_Global);
356-
OutStreamer->emitAssignment(
357-
S, MCConstantExpr::create(Feat00Value, MMI->getContext()));
332+
emitCOFFFeatureSymbol(M);
333+
emitCOFFReplaceableFunctionData(M);
358334

359335
if (M.getModuleFlag("import-call-optimization"))
360336
EnableImportCallOptimization = true;

llvm/lib/Target/X86/X86AsmPrinter.cpp

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -910,41 +910,8 @@ void X86AsmPrinter::emitStartOfAsmFile(Module &M) {
910910
OutStreamer->switchSection(getObjFileLowering().getTextSection());
911911

912912
if (TT.isOSBinFormatCOFF()) {
913-
// Emit an absolute @feat.00 symbol.
914-
MCSymbol *S = MMI->getContext().getOrCreateSymbol(StringRef("@feat.00"));
915-
OutStreamer->beginCOFFSymbolDef(S);
916-
OutStreamer->emitCOFFSymbolStorageClass(COFF::IMAGE_SYM_CLASS_STATIC);
917-
OutStreamer->emitCOFFSymbolType(COFF::IMAGE_SYM_DTYPE_NULL);
918-
OutStreamer->endCOFFSymbolDef();
919-
int64_t Feat00Value = 0;
920-
921-
if (TT.getArch() == Triple::x86) {
922-
// According to the PE-COFF spec, the LSB of this value marks the object
923-
// for "registered SEH". This means that all SEH handler entry points
924-
// must be registered in .sxdata. Use of any unregistered handlers will
925-
// cause the process to terminate immediately. LLVM does not know how to
926-
// register any SEH handlers, so its object files should be safe.
927-
Feat00Value |= COFF::Feat00Flags::SafeSEH;
928-
}
929-
930-
if (M.getModuleFlag("cfguard")) {
931-
// Object is CFG-aware.
932-
Feat00Value |= COFF::Feat00Flags::GuardCF;
933-
}
934-
935-
if (M.getModuleFlag("ehcontguard")) {
936-
// Object also has EHCont.
937-
Feat00Value |= COFF::Feat00Flags::GuardEHCont;
938-
}
939-
940-
if (M.getModuleFlag("ms-kernel")) {
941-
// Object is compiled with /kernel.
942-
Feat00Value |= COFF::Feat00Flags::Kernel;
943-
}
944-
945-
OutStreamer->emitSymbolAttribute(S, MCSA_Global);
946-
OutStreamer->emitAssignment(
947-
S, MCConstantExpr::create(Feat00Value, MMI->getContext()));
913+
emitCOFFFeatureSymbol(M);
914+
emitCOFFReplaceableFunctionData(M);
948915
}
949916
OutStreamer->emitSyntaxDirective();
950917

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
; RUN: llc -mtriple=aarch64-pc-windows-msvc < %s | FileCheck %s
2+
3+
define dso_local i32 @override_me1() "loader-replaceable" {
4+
entry:
5+
ret i32 1
6+
}
7+
8+
define dso_local i32 @override_me2() "loader-replaceable" {
9+
entry:
10+
ret i32 2
11+
}
12+
13+
define dso_local i32 @dont_override_me() {
14+
entry:
15+
ret i32 3
16+
}
17+
18+
; CHECK: .section .drectve,"yni"
19+
; CHECK-NEXT: .def override_me1_$fo$;
20+
; CHECK-NEXT: .scl 2;
21+
; CHECK-NEXT: .type 0;
22+
; CHECK-NEXT: .endef
23+
; CHECK-NEXT: .def override_me1_$fo_default$;
24+
; CHECK-NEXT: .scl 2;
25+
; CHECK-NEXT: .type 0;
26+
; CHECK-NEXT: .endef
27+
; CHECK-NEXT: .ascii " /ALTERNATENAME:override_me1_$fo$=override_me1_$fo_default$"
28+
; CHECK-NEXT: .def override_me2_$fo$;
29+
; CHECK-NEXT: .scl 2;
30+
; CHECK-NEXT: .type 0;
31+
; CHECK-NEXT: .endef
32+
; CHECK-NEXT: .def override_me2_$fo_default$;
33+
; CHECK-NEXT: .scl 2;
34+
; CHECK-NEXT: .type 0;
35+
; CHECK-NEXT: .endef
36+
; CHECK-NEXT: .ascii " /ALTERNATENAME:override_me2_$fo$=override_me2_$fo_default$"
37+
; CHECK-NEXT: .data
38+
; CHECK-NEXT: override_me1_$fo_default$:
39+
; CHECK-NEXT: override_me2_$fo_default$:
40+
; CHECK-NEXT: .zero 1

0 commit comments

Comments
 (0)