Skip to content

Commit dc48eeb

Browse files
committed
[aarch64][x86][win] Add support for MSVC's /funcoverride flag (Windows kernel loader replaceable functions)
1 parent c3c3262 commit dc48eeb

File tree

16 files changed

+253
-63
lines changed

16 files changed

+253
-63
lines changed

clang/include/clang/Basic/CodeGenOptions.h

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

492+
/// A list of functions that are replacable by the loader.
493+
std::vector<std::string> LoaderReplaceableFunctionNames;
494+
492495
public:
493496
// Define accessors/mutators for code generation options of enumeration type.
494497
#define CODEGENOPT(Name, Bits, Default)
@@ -561,6 +564,12 @@ class CodeGenOptions : public CodeGenOptionsBase {
561564
/// Reset all of the options that are not considered when building a
562565
/// module.
563566
void resetNonModularOptions(StringRef ModuleFormat);
567+
568+
// Is the given function name one of the functions that can be replaced by the
569+
// loader?
570+
bool isLoaderReplaceableFunctionName(StringRef FuncName) const {
571+
return llvm::is_contained(LoaderReplaceableFunctionNames, FuncName);
572+
}
564573
};
565574

566575
} // end namespace clang

clang/include/clang/Driver/Options.td

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

7595+
def replaceable_function: Joined<["-"], "loader-replaceable-function=">,
7596+
MarshallingInfoStringVector<CodeGenOpts<"LoaderReplaceableFunctionNames">>;
7597+
75957598
} // let Visibility = [CC1Option]
75967599

75977600
//===----------------------------------------------------------------------===//
@@ -8838,6 +8841,10 @@ def _SLASH_Gregcall : CLFlag<"Gregcall">,
88388841
def _SLASH_Gregcall4 : CLFlag<"Gregcall4">,
88398842
HelpText<"Set __regcall4 as a default calling convention to respect __regcall ABI v.4">;
88408843

8844+
def _SLASH_funcoverride : CLCompileJoined<"funcoverride:">,
8845+
HelpText<"Mark <function> as being replaceable by the Windows kernel loader">,
8846+
MetaVarName<"<function>">;
8847+
88418848
// GNU Driver aliases
88428849

88438850
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
@@ -2574,6 +2574,10 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
25742574
GetCPUAndFeaturesAttributes(CalleeInfo.getCalleeDecl(), FuncAttrs);
25752575
}
25762576

2577+
// Mark functions that are replaceable by the loader.
2578+
if (CodeGenOpts.isLoaderReplaceableFunctionName(Name))
2579+
FuncAttrs.addAttribute("loader-replaceable");
2580+
25772581
// Collect attributes from arguments and return values.
25782582
ClangToLLVMArgMapping IRFunctionArgs(getContext(), FI);
25792583

clang/lib/Driver/ToolChains/Clang.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8504,6 +8504,12 @@ void Clang::AddClangCLArgs(const ArgList &Args, types::ID InputType,
85048504
}
85058505
A->claim();
85068506
}
8507+
8508+
for (const auto &FuncOverride :
8509+
Args.getAllArgValues(options::OPT__SLASH_funcoverride)) {
8510+
CmdArgs.push_back(Args.MakeArgString(
8511+
Twine("-loader-replaceable-function=") + FuncOverride));
8512+
}
85078513
}
85088514

85098515
const char *Clang::getBaseInputName(const ArgList &Args,

clang/test/Driver/cl-options.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -813,4 +813,8 @@
813813
// RUN: %clang_cl -vctoolsdir "" /arm64EC /c -target x86_64-pc-windows-msvc -### -- %s 2>&1 | FileCheck --check-prefix=ARM64EC_OVERRIDE %s
814814
// ARM64EC_OVERRIDE: warning: /arm64EC has been overridden by specified target: x86_64-pc-windows-msvc; option ignored
815815

816+
// RUN: %clang_cl /funcoverride:override_me1 /funcoverride:override_me2 /c -### -- %s 2>&1 | FileCheck %s --check-prefix=FUNCOVERRIDE
817+
// FUNCOVERRIDE: -loader-replaceable-function=override_me1
818+
// FUNCOVERRIDE-SAME: -loader-replaceable-function=override_me2
819+
816820
void f(void) { }

llvm/include/llvm/CodeGen/AsmPrinter.h

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

799+
//===------------------------------------------------------------------===//
800+
// COFF Helper Routines
801+
//===------------------------------------------------------------------===//
802+
803+
/// Emits symbols and data to allow functions marked with the
804+
/// loader-replaceable attribute to be replaceable.
805+
void emitCOFFReplaceableFunctionData(Module &M);
806+
807+
/// Emits the @feat.00 symbol indicating the features enabled in this module.
808+
void emitCOFFFeatureSymbol(Module &M);
809+
799810
//===------------------------------------------------------------------===//
800811
// Inline Asm Support
801812
//===------------------------------------------------------------------===//

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
@@ -3078,6 +3078,10 @@ std::optional<InlineResult> llvm::getAttributeBasedInliningDecision(
30783078
if (Call.isNoInline())
30793079
return InlineResult::failure("noinline call site attribute");
30803080

3081+
// Don't inline functions that are loader replaceable.
3082+
if (Callee->hasFnAttribute("loader-replaceable"))
3083+
return InlineResult::failure("loader replaceable function attribute");
3084+
30813085
return std::nullopt;
30823086
}
30833087

llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4661,3 +4661,109 @@ AsmPrinter::getCodeViewJumpTableInfo(int JTI, const MachineInstr *BranchInstr,
46614661
return std::make_tuple(Base, 0, BranchLabel,
46624662
codeview::JumpTableEntrySize::Int32);
46634663
}
4664+
4665+
void AsmPrinter::emitCOFFReplaceableFunctionData(Module &M) {
4666+
const Triple &TT = TM.getTargetTriple();
4667+
assert(TT.isOSBinFormatCOFF());
4668+
4669+
bool IsTargetArm64EC = TT.isWindowsArm64EC();
4670+
SmallVector<char> Buf;
4671+
SmallVector<MCSymbol *> FuncOverrideDefaultSymbols;
4672+
bool SwitchedToDirectiveSection = false;
4673+
for (const Function &F : M.functions()) {
4674+
if (F.hasFnAttribute("loader-replaceable")) {
4675+
if (!SwitchedToDirectiveSection) {
4676+
OutStreamer->switchSection(
4677+
OutContext.getObjectFileInfo()->getDrectveSection());
4678+
SwitchedToDirectiveSection = true;
4679+
}
4680+
4681+
StringRef Name = F.getName();
4682+
4683+
// For hybrid-patchable targets, strip the prefix so that we can mark
4684+
// the real function as replaceable.
4685+
if (IsTargetArm64EC && Name.ends_with(HybridPatchableTargetSuffix)) {
4686+
Name = Name.substr(0, Name.size() - HybridPatchableTargetSuffix.size());
4687+
}
4688+
4689+
MCSymbol *FuncOverrideSymbol =
4690+
MMI->getContext().getOrCreateSymbol(Name + "_$fo$");
4691+
OutStreamer->beginCOFFSymbolDef(FuncOverrideSymbol);
4692+
OutStreamer->emitCOFFSymbolStorageClass(COFF::IMAGE_SYM_CLASS_EXTERNAL);
4693+
OutStreamer->emitCOFFSymbolType(COFF::IMAGE_SYM_DTYPE_NULL);
4694+
OutStreamer->endCOFFSymbolDef();
4695+
4696+
MCSymbol *FuncOverrideDefaultSymbol =
4697+
MMI->getContext().getOrCreateSymbol(Name + "_$fo_default$");
4698+
OutStreamer->beginCOFFSymbolDef(FuncOverrideDefaultSymbol);
4699+
OutStreamer->emitCOFFSymbolStorageClass(COFF::IMAGE_SYM_CLASS_EXTERNAL);
4700+
OutStreamer->emitCOFFSymbolType(COFF::IMAGE_SYM_DTYPE_NULL);
4701+
OutStreamer->endCOFFSymbolDef();
4702+
FuncOverrideDefaultSymbols.push_back(FuncOverrideDefaultSymbol);
4703+
4704+
OutStreamer->emitBytes((Twine(" /ALTERNATENAME:") +
4705+
FuncOverrideSymbol->getName() + "=" +
4706+
FuncOverrideDefaultSymbol->getName())
4707+
.toStringRef(Buf));
4708+
Buf.clear();
4709+
}
4710+
}
4711+
4712+
if (SwitchedToDirectiveSection)
4713+
OutStreamer->popSection();
4714+
4715+
if (FuncOverrideDefaultSymbols.empty())
4716+
return;
4717+
4718+
// MSVC emits the symbols for the default variables pointing at the start of
4719+
// the .data section, but doesn't actually allocate any space for them. LLVM
4720+
// can't do this, so have all of the variables pointing at a single byte
4721+
// instead.
4722+
OutStreamer->switchSection(OutContext.getObjectFileInfo()->getDataSection());
4723+
for (MCSymbol *Symbol : FuncOverrideDefaultSymbols) {
4724+
OutStreamer->emitLabel(Symbol);
4725+
}
4726+
OutStreamer->emitZeros(1);
4727+
OutStreamer->popSection();
4728+
}
4729+
4730+
void AsmPrinter::emitCOFFFeatureSymbol(Module &M) {
4731+
const Triple &TT = TM.getTargetTriple();
4732+
assert(TT.isOSBinFormatCOFF());
4733+
4734+
// Emit an absolute @feat.00 symbol.
4735+
MCSymbol *S = MMI->getContext().getOrCreateSymbol(StringRef("@feat.00"));
4736+
OutStreamer->beginCOFFSymbolDef(S);
4737+
OutStreamer->emitCOFFSymbolStorageClass(COFF::IMAGE_SYM_CLASS_STATIC);
4738+
OutStreamer->emitCOFFSymbolType(COFF::IMAGE_SYM_DTYPE_NULL);
4739+
OutStreamer->endCOFFSymbolDef();
4740+
int64_t Feat00Value = 0;
4741+
4742+
if (TT.getArch() == Triple::x86) {
4743+
// According to the PE-COFF spec, the LSB of this value marks the object
4744+
// for "registered SEH". This means that all SEH handler entry points
4745+
// must be registered in .sxdata. Use of any unregistered handlers will
4746+
// cause the process to terminate immediately. LLVM does not know how to
4747+
// register any SEH handlers, so its object files should be safe.
4748+
Feat00Value |= COFF::Feat00Flags::SafeSEH;
4749+
}
4750+
4751+
if (M.getModuleFlag("cfguard")) {
4752+
// Object is CFG-aware.
4753+
Feat00Value |= COFF::Feat00Flags::GuardCF;
4754+
}
4755+
4756+
if (M.getModuleFlag("ehcontguard")) {
4757+
// Object also has EHCont.
4758+
Feat00Value |= COFF::Feat00Flags::GuardEHCont;
4759+
}
4760+
4761+
if (M.getModuleFlag("ms-kernel")) {
4762+
// Object is compiled with /kernel.
4763+
Feat00Value |= COFF::Feat00Flags::Kernel;
4764+
}
4765+
4766+
OutStreamer->emitSymbolAttribute(S, MCSA_Global);
4767+
OutStreamer->emitAssignment(
4768+
S, MCConstantExpr::create(Feat00Value, MMI->getContext()));
4769+
}

llvm/lib/Target/AArch64/AArch64Arm64ECCallLowering.cpp

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

809809
for (Function &F : Mod) {
810810
if (!F.hasFnAttribute(Attribute::HybridPatchable) || F.isDeclaration() ||
811-
F.hasLocalLinkage() || F.getName().ends_with("$hp_target"))
811+
F.hasLocalLinkage() ||
812+
F.getName().ends_with(HybridPatchableTargetSuffix))
812813
continue;
813814

814815
// Rename hybrid patchable functions and change callers to use a global
815816
// alias instead.
816817
if (std::optional<std::string> MangledName =
817818
getArm64ECMangledFunctionName(F.getName().str())) {
818819
std::string OrigName(F.getName());
819-
F.setName(MangledName.value() + "$hp_target");
820+
F.setName(MangledName.value() + HybridPatchableTargetSuffix);
820821

821822
// The unmangled symbol is a weak alias to an undefined symbol with the
822823
// "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
@@ -314,32 +314,8 @@ void AArch64AsmPrinter::emitStartOfAsmFile(Module &M) {
314314
const Triple &TT = TM.getTargetTriple();
315315

316316
if (TT.isOSBinFormatCOFF()) {
317-
// Emit an absolute @feat.00 symbol
318-
MCSymbol *S = MMI->getContext().getOrCreateSymbol(StringRef("@feat.00"));
319-
OutStreamer->beginCOFFSymbolDef(S);
320-
OutStreamer->emitCOFFSymbolStorageClass(COFF::IMAGE_SYM_CLASS_STATIC);
321-
OutStreamer->emitCOFFSymbolType(COFF::IMAGE_SYM_DTYPE_NULL);
322-
OutStreamer->endCOFFSymbolDef();
323-
int64_t Feat00Value = 0;
324-
325-
if (M.getModuleFlag("cfguard")) {
326-
// Object is CFG-aware.
327-
Feat00Value |= COFF::Feat00Flags::GuardCF;
328-
}
329-
330-
if (M.getModuleFlag("ehcontguard")) {
331-
// Object also has EHCont.
332-
Feat00Value |= COFF::Feat00Flags::GuardEHCont;
333-
}
334-
335-
if (M.getModuleFlag("ms-kernel")) {
336-
// Object is compiled with /kernel.
337-
Feat00Value |= COFF::Feat00Flags::Kernel;
338-
}
339-
340-
OutStreamer->emitSymbolAttribute(S, MCSA_Global);
341-
OutStreamer->emitAssignment(
342-
S, MCConstantExpr::create(Feat00Value, MMI->getContext()));
317+
emitCOFFFeatureSymbol(M);
318+
emitCOFFReplaceableFunctionData(M);
343319

344320
if (M.getModuleFlag("import-call-optimization"))
345321
EnableImportCallOptimization = true;

llvm/lib/Target/X86/X86AsmPrinter.cpp

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

887887
if (TT.isOSBinFormatCOFF()) {
888-
// Emit an absolute @feat.00 symbol.
889-
MCSymbol *S = MMI->getContext().getOrCreateSymbol(StringRef("@feat.00"));
890-
OutStreamer->beginCOFFSymbolDef(S);
891-
OutStreamer->emitCOFFSymbolStorageClass(COFF::IMAGE_SYM_CLASS_STATIC);
892-
OutStreamer->emitCOFFSymbolType(COFF::IMAGE_SYM_DTYPE_NULL);
893-
OutStreamer->endCOFFSymbolDef();
894-
int64_t Feat00Value = 0;
895-
896-
if (TT.getArch() == Triple::x86) {
897-
// According to the PE-COFF spec, the LSB of this value marks the object
898-
// for "registered SEH". This means that all SEH handler entry points
899-
// must be registered in .sxdata. Use of any unregistered handlers will
900-
// cause the process to terminate immediately. LLVM does not know how to
901-
// register any SEH handlers, so its object files should be safe.
902-
Feat00Value |= COFF::Feat00Flags::SafeSEH;
903-
}
904-
905-
if (M.getModuleFlag("cfguard")) {
906-
// Object is CFG-aware.
907-
Feat00Value |= COFF::Feat00Flags::GuardCF;
908-
}
909-
910-
if (M.getModuleFlag("ehcontguard")) {
911-
// Object also has EHCont.
912-
Feat00Value |= COFF::Feat00Flags::GuardEHCont;
913-
}
914-
915-
if (M.getModuleFlag("ms-kernel")) {
916-
// Object is compiled with /kernel.
917-
Feat00Value |= COFF::Feat00Flags::Kernel;
918-
}
919-
920-
OutStreamer->emitSymbolAttribute(S, MCSA_Global);
921-
OutStreamer->emitAssignment(
922-
S, MCConstantExpr::create(Feat00Value, MMI->getContext()));
888+
emitCOFFFeatureSymbol(M);
889+
emitCOFFReplaceableFunctionData(M);
923890
}
924891
OutStreamer->emitSyntaxDirective();
925892

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)