Skip to content

Commit b27a120

Browse files
committed
[CodeGen][ARM64EC] Add support for hybrid_patchable attribute.
1 parent 3fae555 commit b27a120

File tree

10 files changed

+484
-11
lines changed

10 files changed

+484
-11
lines changed

llvm/include/llvm/Bitcode/LLVMBitCodes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,7 @@ enum AttributeKindCodes {
757757
ATTR_KIND_RANGE = 92,
758758
ATTR_KIND_SANITIZE_NUMERICAL_STABILITY = 93,
759759
ATTR_KIND_INITIALIZES = 94,
760+
ATTR_KIND_HYBRID_PATCHABLE = 95,
760761
};
761762

762763
enum ComdatSelectionKindCodes {

llvm/include/llvm/CodeGen/AsmPrinter.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -892,14 +892,14 @@ class AsmPrinter : public MachineFunctionPass {
892892
virtual void emitModuleCommandLines(Module &M);
893893

894894
GCMetadataPrinter *getOrCreateGCPrinter(GCStrategy &S);
895-
virtual void emitGlobalAlias(const Module &M, const GlobalAlias &GA);
896895
void emitGlobalIFunc(Module &M, const GlobalIFunc &GI);
897896

898897
private:
899898
/// This method decides whether the specified basic block requires a label.
900899
bool shouldEmitLabelForBasicBlock(const MachineBasicBlock &MBB) const;
901900

902901
protected:
902+
virtual void emitGlobalAlias(const Module &M, const GlobalAlias &GA);
903903
virtual bool shouldEmitWeakSwiftAsyncExtendedFramePointerFlags() const {
904904
return false;
905905
}

llvm/include/llvm/IR/Attributes.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ def ElementType : TypeAttr<"elementtype", [ParamAttr]>;
112112
/// symbol.
113113
def FnRetThunkExtern : EnumAttr<"fn_ret_thunk_extern", [FnAttr]>;
114114

115+
/// Function has a hybrid patchable thunk.
116+
def HybridPatchable : EnumAttr<"hybrid_patchable", [FnAttr]>;
117+
115118
/// Pass structure in an alloca.
116119
def InAlloca : TypeAttr<"inalloca", [ParamAttr]>;
117120

llvm/lib/Bitcode/Writer/BitcodeWriter.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,8 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) {
727727
return bitc::ATTR_KIND_HOT;
728728
case Attribute::ElementType:
729729
return bitc::ATTR_KIND_ELEMENTTYPE;
730+
case Attribute::HybridPatchable:
731+
return bitc::ATTR_KIND_HYBRID_PATCHABLE;
730732
case Attribute::InlineHint:
731733
return bitc::ATTR_KIND_INLINE_HINT;
732734
case Attribute::InReg:

llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2859,8 +2859,8 @@ bool AsmPrinter::emitSpecialLLVMGlobal(const GlobalVariable *GV) {
28592859
auto *Arr = cast<ConstantArray>(GV->getInitializer());
28602860
for (auto &U : Arr->operands()) {
28612861
auto *C = cast<Constant>(U);
2862-
auto *Src = cast<Function>(C->getOperand(0)->stripPointerCasts());
2863-
auto *Dst = cast<Function>(C->getOperand(1)->stripPointerCasts());
2862+
auto *Src = cast<GlobalValue>(C->getOperand(0)->stripPointerCasts());
2863+
auto *Dst = cast<GlobalValue>(C->getOperand(1)->stripPointerCasts());
28642864
int Kind = cast<ConstantInt>(C->getOperand(2))->getZExtValue();
28652865

28662866
if (Src->hasDLLImportStorageClass()) {

llvm/lib/Target/AArch64/AArch64Arm64ECCallLowering.cpp

Lines changed: 131 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "llvm/ADT/SmallVector.h"
2222
#include "llvm/ADT/Statistic.h"
2323
#include "llvm/IR/CallingConv.h"
24+
#include "llvm/IR/GlobalAlias.h"
2425
#include "llvm/IR/IRBuilder.h"
2526
#include "llvm/IR/Instruction.h"
2627
#include "llvm/IR/Mangler.h"
@@ -70,15 +71,21 @@ class AArch64Arm64ECCallLowering : public ModulePass {
7071
Function *buildEntryThunk(Function *F);
7172
void lowerCall(CallBase *CB);
7273
Function *buildGuestExitThunk(Function *F);
73-
bool processFunction(Function &F, SetVector<Function *> &DirectCalledFns);
74+
Function *buildPatchableThunk(GlobalAlias *UnmangledAlias,
75+
GlobalAlias *MangledAlias);
76+
bool processFunction(Function &F, SetVector<GlobalValue *> &DirectCalledFns,
77+
DenseMap<GlobalAlias *, GlobalAlias *> &FnsMap);
7478
bool runOnModule(Module &M) override;
7579

7680
private:
7781
int cfguard_module_flag = 0;
7882
FunctionType *GuardFnType = nullptr;
7983
PointerType *GuardFnPtrType = nullptr;
84+
FunctionType *DispatchFnType = nullptr;
85+
PointerType *DispatchFnPtrType = nullptr;
8086
Constant *GuardFnCFGlobal = nullptr;
8187
Constant *GuardFnGlobal = nullptr;
88+
Constant *DispatchFnGlobal = nullptr;
8289
Module *M = nullptr;
8390

8491
Type *PtrTy;
@@ -672,6 +679,66 @@ Function *AArch64Arm64ECCallLowering::buildGuestExitThunk(Function *F) {
672679
return GuestExit;
673680
}
674681

682+
Function *
683+
AArch64Arm64ECCallLowering::buildPatchableThunk(GlobalAlias *UnmangledAlias,
684+
GlobalAlias *MangledAlias) {
685+
llvm::raw_null_ostream NullThunkName;
686+
FunctionType *Arm64Ty, *X64Ty;
687+
Function *F = cast<Function>(MangledAlias->getAliasee());
688+
SmallVector<ThunkArgTranslation> ArgTranslations;
689+
getThunkType(F->getFunctionType(), F->getAttributes(),
690+
Arm64ECThunkType::GuestExit, NullThunkName, Arm64Ty, X64Ty,
691+
ArgTranslations);
692+
std::string ThunkName(MangledAlias->getName());
693+
if (ThunkName[0] == '?' && ThunkName.find("@") != std::string::npos) {
694+
ThunkName.insert(ThunkName.find("@"), "$hybpatch_thunk");
695+
} else {
696+
ThunkName.append("$hybpatch_thunk");
697+
}
698+
699+
Function *GuestExit =
700+
Function::Create(Arm64Ty, GlobalValue::WeakODRLinkage, 0, ThunkName, M);
701+
GuestExit->setComdat(M->getOrInsertComdat(ThunkName));
702+
GuestExit->setSection(".wowthk$aa");
703+
BasicBlock *BB = BasicBlock::Create(M->getContext(), "", GuestExit);
704+
IRBuilder<> B(BB);
705+
706+
// Load the global symbol as a pointer to the check function.
707+
LoadInst *DispatchLoad = B.CreateLoad(DispatchFnPtrType, DispatchFnGlobal);
708+
709+
// Create new dispatch call instruction.
710+
Function *ExitThunk =
711+
buildExitThunk(F->getFunctionType(), F->getAttributes());
712+
CallInst *Dispatch =
713+
B.CreateCall(DispatchFnType, DispatchLoad,
714+
{UnmangledAlias, ExitThunk, UnmangledAlias->getAliasee()});
715+
716+
// Ensure that the first arguments are passed in the correct registers.
717+
Dispatch->setCallingConv(CallingConv::CFGuard_Check);
718+
719+
Value *DispatchRetVal = B.CreateBitCast(Dispatch, PtrTy);
720+
SmallVector<Value *> Args;
721+
for (Argument &Arg : GuestExit->args())
722+
Args.push_back(&Arg);
723+
CallInst *Call = B.CreateCall(Arm64Ty, DispatchRetVal, Args);
724+
Call->setTailCallKind(llvm::CallInst::TCK_MustTail);
725+
726+
if (Call->getType()->isVoidTy())
727+
B.CreateRetVoid();
728+
else
729+
B.CreateRet(Call);
730+
731+
auto SRetAttr = F->getAttributes().getParamAttr(0, Attribute::StructRet);
732+
auto InRegAttr = F->getAttributes().getParamAttr(0, Attribute::InReg);
733+
if (SRetAttr.isValid() && !InRegAttr.isValid()) {
734+
GuestExit->addParamAttr(0, SRetAttr);
735+
Call->addParamAttr(0, SRetAttr);
736+
}
737+
738+
MangledAlias->setAliasee(GuestExit);
739+
return GuestExit;
740+
}
741+
675742
// Lower an indirect call with inline code.
676743
void AArch64Arm64ECCallLowering::lowerCall(CallBase *CB) {
677744
assert(Triple(CB->getModule()->getTargetTriple()).isOSWindows() &&
@@ -727,17 +794,57 @@ bool AArch64Arm64ECCallLowering::runOnModule(Module &Mod) {
727794

728795
GuardFnType = FunctionType::get(PtrTy, {PtrTy, PtrTy}, false);
729796
GuardFnPtrType = PointerType::get(GuardFnType, 0);
797+
DispatchFnType = FunctionType::get(PtrTy, {PtrTy, PtrTy, PtrTy}, false);
798+
DispatchFnPtrType = PointerType::get(DispatchFnType, 0);
730799
GuardFnCFGlobal =
731800
M->getOrInsertGlobal("__os_arm64x_check_icall_cfg", GuardFnPtrType);
732801
GuardFnGlobal =
733802
M->getOrInsertGlobal("__os_arm64x_check_icall", GuardFnPtrType);
803+
DispatchFnGlobal =
804+
M->getOrInsertGlobal("__os_arm64x_dispatch_call", DispatchFnPtrType);
805+
806+
DenseMap<GlobalAlias *, GlobalAlias *> FnsMap;
807+
SetVector<GlobalAlias *> PatchableFns;
734808

735-
SetVector<Function *> DirectCalledFns;
809+
for (Function &F : Mod) {
810+
if (!F.hasFnAttribute(Attribute::HybridPatchable) || F.isDeclaration() ||
811+
F.hasLocalLinkage() || F.getName().ends_with("$hp_target"))
812+
continue;
813+
814+
// Rename hybrid patchable functions and change callers to use a global
815+
// alias instead.
816+
if (std::optional<std::string> MangledName =
817+
getArm64ECMangledFunctionName(F.getName().str())) {
818+
std::string OrigName(F.getName());
819+
F.setName(MangledName.value() + "$hp_target");
820+
821+
// The unmangled symbol is a weak alias to an undefined symbol with the
822+
// "EXP+" prefix. This undefined symbol is resolved by the linker by
823+
// creating an x86 thunk that jumps back to the actual EC target. Since we
824+
// can't represent that in IR, we create an alias to the target instead.
825+
// The "EXP+" symbol is set as metadata, which is then used by
826+
// emitGlobalAlias to emit the right alias.
827+
auto *A =
828+
GlobalAlias::create(GlobalValue::LinkOnceODRLinkage, OrigName, &F);
829+
F.replaceAllUsesWith(A);
830+
F.setMetadata("arm64ec_exp_name",
831+
MDNode::get(M->getContext(),
832+
MDString::get(M->getContext(),
833+
"EXP+" + MangledName.value())));
834+
A->setAliasee(&F);
835+
836+
FnsMap[A] = GlobalAlias::create(GlobalValue::LinkOnceODRLinkage,
837+
MangledName.value(), &F);
838+
PatchableFns.insert(A);
839+
}
840+
}
841+
842+
SetVector<GlobalValue *> DirectCalledFns;
736843
for (Function &F : Mod)
737844
if (!F.isDeclaration() &&
738845
F.getCallingConv() != CallingConv::ARM64EC_Thunk_Native &&
739846
F.getCallingConv() != CallingConv::ARM64EC_Thunk_X64)
740-
processFunction(F, DirectCalledFns);
847+
processFunction(F, DirectCalledFns, FnsMap);
741848

742849
struct ThunkInfo {
743850
Constant *Src;
@@ -755,14 +862,20 @@ bool AArch64Arm64ECCallLowering::runOnModule(Module &Mod) {
755862
{&F, buildEntryThunk(&F), Arm64ECThunkType::Entry});
756863
}
757864
}
758-
for (Function *F : DirectCalledFns) {
865+
for (GlobalValue *O : DirectCalledFns) {
866+
auto GA = dyn_cast<GlobalAlias>(O);
867+
auto F = dyn_cast<Function>(GA ? GA->getAliasee() : O);
759868
ThunkMapping.push_back(
760-
{F, buildExitThunk(F->getFunctionType(), F->getAttributes()),
869+
{O, buildExitThunk(F->getFunctionType(), F->getAttributes()),
761870
Arm64ECThunkType::Exit});
762-
if (!F->hasDLLImportStorageClass())
871+
if (!GA && !F->hasDLLImportStorageClass())
763872
ThunkMapping.push_back(
764873
{buildGuestExitThunk(F), F, Arm64ECThunkType::GuestExit});
765874
}
875+
for (GlobalAlias *A : PatchableFns) {
876+
Function *Thunk = buildPatchableThunk(A, FnsMap[A]);
877+
ThunkMapping.push_back({Thunk, A, Arm64ECThunkType::GuestExit});
878+
}
766879

767880
if (!ThunkMapping.empty()) {
768881
SmallVector<Constant *> ThunkMappingArrayElems;
@@ -785,7 +898,8 @@ bool AArch64Arm64ECCallLowering::runOnModule(Module &Mod) {
785898
}
786899

787900
bool AArch64Arm64ECCallLowering::processFunction(
788-
Function &F, SetVector<Function *> &DirectCalledFns) {
901+
Function &F, SetVector<GlobalValue *> &DirectCalledFns,
902+
DenseMap<GlobalAlias *, GlobalAlias *> &FnsMap) {
789903
SmallVector<CallBase *, 8> IndirectCalls;
790904

791905
// For ARM64EC targets, a function definition's name is mangled differently
@@ -837,6 +951,16 @@ bool AArch64Arm64ECCallLowering::processFunction(
837951
continue;
838952
}
839953

954+
// Use mangled global alias for direct calls to patchable functions.
955+
if (GlobalAlias *A = dyn_cast<GlobalAlias>(CB->getCalledOperand())) {
956+
auto I = FnsMap.find(A);
957+
if (I != FnsMap.end()) {
958+
CB->setCalledOperand(I->second);
959+
DirectCalledFns.insert(I->first);
960+
continue;
961+
}
962+
}
963+
840964
IndirectCalls.push_back(CB);
841965
++Arm64ECCallsLowered;
842966
}

llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ class AArch64AsmPrinter : public AsmPrinter {
201201
void PrintDebugValueComment(const MachineInstr *MI, raw_ostream &OS);
202202

203203
void emitFunctionBodyEnd() override;
204+
void emitGlobalAlias(const Module &M, const GlobalAlias &GA) override;
204205

205206
MCSymbol *GetCPISymbol(unsigned CPID) const override;
206207
void emitEndOfAsmFile(Module &M) override;
@@ -1263,6 +1264,32 @@ void AArch64AsmPrinter::emitFunctionEntryLabel() {
12631264
}
12641265
}
12651266

1267+
void AArch64AsmPrinter::emitGlobalAlias(const Module &M,
1268+
const GlobalAlias &GA) {
1269+
if (auto F = dyn_cast_or_null<Function>(GA.getAliasee())) {
1270+
// Global aliases must point to a definition, but unmangled patchable
1271+
// symbols are special and need to point to an undefined symbol with "EXP+"
1272+
// prefix. Such undefined symbol is resolved by the linker by creating
1273+
// x86 thunk that jumps back to the actual EC target.
1274+
if (MDNode *Node = F->getMetadata("arm64ec_exp_name")) {
1275+
StringRef ExpStr = cast<MDString>(Node->getOperand(0))->getString();
1276+
MCSymbol *ExpSym = MMI->getContext().getOrCreateSymbol(ExpStr);
1277+
MCSymbol *Sym = MMI->getContext().getOrCreateSymbol(GA.getName());
1278+
OutStreamer->beginCOFFSymbolDef(Sym);
1279+
OutStreamer->emitCOFFSymbolStorageClass(COFF::IMAGE_SYM_CLASS_EXTERNAL);
1280+
OutStreamer->emitCOFFSymbolType(COFF::IMAGE_SYM_DTYPE_FUNCTION
1281+
<< COFF::SCT_COMPLEX_TYPE_SHIFT);
1282+
OutStreamer->endCOFFSymbolDef();
1283+
OutStreamer->emitSymbolAttribute(Sym, MCSA_Weak);
1284+
OutStreamer->emitAssignment(
1285+
Sym, MCSymbolRefExpr::create(ExpSym, MCSymbolRefExpr::VK_None,
1286+
MMI->getContext()));
1287+
return;
1288+
}
1289+
}
1290+
AsmPrinter::emitGlobalAlias(M, GA);
1291+
}
1292+
12661293
/// Small jump tables contain an unsigned byte or half, representing the offset
12671294
/// from the lowest-addressed possible destination to the desired basic
12681295
/// block. Since all instructions are 4-byte aligned, this is further compressed

llvm/lib/Target/AArch64/AArch64CallingConvention.td

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ def CC_AArch64_Win64_CFGuard_Check : CallingConv<[
333333

334334
let Entry = 1 in
335335
def CC_AArch64_Arm64EC_CFGuard_Check : CallingConv<[
336-
CCIfType<[i64], CCAssignToReg<[X11, X10]>>
336+
CCIfType<[i64], CCAssignToReg<[X11, X10, X9]>>
337337
]>;
338338

339339
let Entry = 1 in

llvm/lib/Transforms/Utils/CodeExtractor.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,7 @@ Function *CodeExtractor::constructFunction(const ValueSet &inputs,
932932
case Attribute::DisableSanitizerInstrumentation:
933933
case Attribute::FnRetThunkExtern:
934934
case Attribute::Hot:
935+
case Attribute::HybridPatchable:
935936
case Attribute::NoRecurse:
936937
case Attribute::InlineHint:
937938
case Attribute::MinSize:

0 commit comments

Comments
 (0)