https://github.com/cjacek created https://github.com/llvm/llvm-project/pull/99478
This adds support for `hybrid_patchable` on top of LLVM part from #92965 (so it depends on #92965 and this PR is meant only for the second commit). For the most part, it just adds LLVM attribute whenever C/C++ attribute is specified. I added a warning when it's used on static function. Due to the way it works (emitting a reference to an undefined symbol that linker substitutes with a generated thunk), it can't work on static functions. MSVC just silently ignores it; using it like that seems like a non-obvious mistake to me, so I followed MSVC by allowing it too, but emitting an additional warning. The patch does nothing special for function inlining, which matches my experimentation with MSVC. `hybrid_patchable` functions may be specified on inline functions. Depending on optimizations taken, when they are actually inlined, it has no effect, but when they are not inlined, it works as expected. >From b27a1205f893204aca939042f7c17dc7b9196cd4 Mon Sep 17 00:00:00 2001 From: Jacek Caban <ja...@codeweavers.com> Date: Fri, 3 May 2024 00:24:39 +0200 Subject: [PATCH 1/2] [CodeGen][ARM64EC] Add support for hybrid_patchable attribute. --- llvm/include/llvm/Bitcode/LLVMBitCodes.h | 1 + llvm/include/llvm/CodeGen/AsmPrinter.h | 2 +- llvm/include/llvm/IR/Attributes.td | 3 + llvm/lib/Bitcode/Writer/BitcodeWriter.cpp | 2 + llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp | 4 +- .../AArch64/AArch64Arm64ECCallLowering.cpp | 138 +++++++- llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp | 27 ++ .../AArch64/AArch64CallingConvention.td | 2 +- llvm/lib/Transforms/Utils/CodeExtractor.cpp | 1 + .../AArch64/arm64ec-hybrid-patchable.ll | 315 ++++++++++++++++++ 10 files changed, 484 insertions(+), 11 deletions(-) create mode 100644 llvm/test/CodeGen/AArch64/arm64ec-hybrid-patchable.ll diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h index 184bbe32df695..fb88f2fe75adb 100644 --- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h +++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h @@ -757,6 +757,7 @@ enum AttributeKindCodes { ATTR_KIND_RANGE = 92, ATTR_KIND_SANITIZE_NUMERICAL_STABILITY = 93, ATTR_KIND_INITIALIZES = 94, + ATTR_KIND_HYBRID_PATCHABLE = 95, }; enum ComdatSelectionKindCodes { diff --git a/llvm/include/llvm/CodeGen/AsmPrinter.h b/llvm/include/llvm/CodeGen/AsmPrinter.h index dc00bd57d655d..1c4e9e9111441 100644 --- a/llvm/include/llvm/CodeGen/AsmPrinter.h +++ b/llvm/include/llvm/CodeGen/AsmPrinter.h @@ -892,7 +892,6 @@ class AsmPrinter : public MachineFunctionPass { virtual void emitModuleCommandLines(Module &M); GCMetadataPrinter *getOrCreateGCPrinter(GCStrategy &S); - virtual void emitGlobalAlias(const Module &M, const GlobalAlias &GA); void emitGlobalIFunc(Module &M, const GlobalIFunc &GI); private: @@ -900,6 +899,7 @@ class AsmPrinter : public MachineFunctionPass { bool shouldEmitLabelForBasicBlock(const MachineBasicBlock &MBB) const; protected: + virtual void emitGlobalAlias(const Module &M, const GlobalAlias &GA); virtual bool shouldEmitWeakSwiftAsyncExtendedFramePointerFlags() const { return false; } diff --git a/llvm/include/llvm/IR/Attributes.td b/llvm/include/llvm/IR/Attributes.td index 0457f0c388d26..e1bd193891c1e 100644 --- a/llvm/include/llvm/IR/Attributes.td +++ b/llvm/include/llvm/IR/Attributes.td @@ -112,6 +112,9 @@ def ElementType : TypeAttr<"elementtype", [ParamAttr]>; /// symbol. def FnRetThunkExtern : EnumAttr<"fn_ret_thunk_extern", [FnAttr]>; +/// Function has a hybrid patchable thunk. +def HybridPatchable : EnumAttr<"hybrid_patchable", [FnAttr]>; + /// Pass structure in an alloca. def InAlloca : TypeAttr<"inalloca", [ParamAttr]>; diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp index b3ebe70e8c52f..324dcbca8137e 100644 --- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -727,6 +727,8 @@ static uint64_t getAttrKindEncoding(Attribute::AttrKind Kind) { return bitc::ATTR_KIND_HOT; case Attribute::ElementType: return bitc::ATTR_KIND_ELEMENTTYPE; + case Attribute::HybridPatchable: + return bitc::ATTR_KIND_HYBRID_PATCHABLE; case Attribute::InlineHint: return bitc::ATTR_KIND_INLINE_HINT; case Attribute::InReg: diff --git a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp index 1f59ec545b4f7..b46a6d348413b 100644 --- a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp +++ b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp @@ -2859,8 +2859,8 @@ bool AsmPrinter::emitSpecialLLVMGlobal(const GlobalVariable *GV) { auto *Arr = cast<ConstantArray>(GV->getInitializer()); for (auto &U : Arr->operands()) { auto *C = cast<Constant>(U); - auto *Src = cast<Function>(C->getOperand(0)->stripPointerCasts()); - auto *Dst = cast<Function>(C->getOperand(1)->stripPointerCasts()); + auto *Src = cast<GlobalValue>(C->getOperand(0)->stripPointerCasts()); + auto *Dst = cast<GlobalValue>(C->getOperand(1)->stripPointerCasts()); int Kind = cast<ConstantInt>(C->getOperand(2))->getZExtValue(); if (Src->hasDLLImportStorageClass()) { diff --git a/llvm/lib/Target/AArch64/AArch64Arm64ECCallLowering.cpp b/llvm/lib/Target/AArch64/AArch64Arm64ECCallLowering.cpp index 4ff52eb252d20..310b152ef9817 100644 --- a/llvm/lib/Target/AArch64/AArch64Arm64ECCallLowering.cpp +++ b/llvm/lib/Target/AArch64/AArch64Arm64ECCallLowering.cpp @@ -21,6 +21,7 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/Statistic.h" #include "llvm/IR/CallingConv.h" +#include "llvm/IR/GlobalAlias.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Mangler.h" @@ -70,15 +71,21 @@ class AArch64Arm64ECCallLowering : public ModulePass { Function *buildEntryThunk(Function *F); void lowerCall(CallBase *CB); Function *buildGuestExitThunk(Function *F); - bool processFunction(Function &F, SetVector<Function *> &DirectCalledFns); + Function *buildPatchableThunk(GlobalAlias *UnmangledAlias, + GlobalAlias *MangledAlias); + bool processFunction(Function &F, SetVector<GlobalValue *> &DirectCalledFns, + DenseMap<GlobalAlias *, GlobalAlias *> &FnsMap); bool runOnModule(Module &M) override; private: int cfguard_module_flag = 0; FunctionType *GuardFnType = nullptr; PointerType *GuardFnPtrType = nullptr; + FunctionType *DispatchFnType = nullptr; + PointerType *DispatchFnPtrType = nullptr; Constant *GuardFnCFGlobal = nullptr; Constant *GuardFnGlobal = nullptr; + Constant *DispatchFnGlobal = nullptr; Module *M = nullptr; Type *PtrTy; @@ -672,6 +679,66 @@ Function *AArch64Arm64ECCallLowering::buildGuestExitThunk(Function *F) { return GuestExit; } +Function * +AArch64Arm64ECCallLowering::buildPatchableThunk(GlobalAlias *UnmangledAlias, + GlobalAlias *MangledAlias) { + llvm::raw_null_ostream NullThunkName; + FunctionType *Arm64Ty, *X64Ty; + Function *F = cast<Function>(MangledAlias->getAliasee()); + SmallVector<ThunkArgTranslation> ArgTranslations; + getThunkType(F->getFunctionType(), F->getAttributes(), + Arm64ECThunkType::GuestExit, NullThunkName, Arm64Ty, X64Ty, + ArgTranslations); + std::string ThunkName(MangledAlias->getName()); + if (ThunkName[0] == '?' && ThunkName.find("@") != std::string::npos) { + ThunkName.insert(ThunkName.find("@"), "$hybpatch_thunk"); + } else { + ThunkName.append("$hybpatch_thunk"); + } + + Function *GuestExit = + Function::Create(Arm64Ty, GlobalValue::WeakODRLinkage, 0, ThunkName, M); + GuestExit->setComdat(M->getOrInsertComdat(ThunkName)); + GuestExit->setSection(".wowthk$aa"); + BasicBlock *BB = BasicBlock::Create(M->getContext(), "", GuestExit); + IRBuilder<> B(BB); + + // Load the global symbol as a pointer to the check function. + LoadInst *DispatchLoad = B.CreateLoad(DispatchFnPtrType, DispatchFnGlobal); + + // Create new dispatch call instruction. + Function *ExitThunk = + buildExitThunk(F->getFunctionType(), F->getAttributes()); + CallInst *Dispatch = + B.CreateCall(DispatchFnType, DispatchLoad, + {UnmangledAlias, ExitThunk, UnmangledAlias->getAliasee()}); + + // Ensure that the first arguments are passed in the correct registers. + Dispatch->setCallingConv(CallingConv::CFGuard_Check); + + Value *DispatchRetVal = B.CreateBitCast(Dispatch, PtrTy); + SmallVector<Value *> Args; + for (Argument &Arg : GuestExit->args()) + Args.push_back(&Arg); + CallInst *Call = B.CreateCall(Arm64Ty, DispatchRetVal, Args); + Call->setTailCallKind(llvm::CallInst::TCK_MustTail); + + if (Call->getType()->isVoidTy()) + B.CreateRetVoid(); + else + B.CreateRet(Call); + + auto SRetAttr = F->getAttributes().getParamAttr(0, Attribute::StructRet); + auto InRegAttr = F->getAttributes().getParamAttr(0, Attribute::InReg); + if (SRetAttr.isValid() && !InRegAttr.isValid()) { + GuestExit->addParamAttr(0, SRetAttr); + Call->addParamAttr(0, SRetAttr); + } + + MangledAlias->setAliasee(GuestExit); + return GuestExit; +} + // Lower an indirect call with inline code. void AArch64Arm64ECCallLowering::lowerCall(CallBase *CB) { assert(Triple(CB->getModule()->getTargetTriple()).isOSWindows() && @@ -727,17 +794,57 @@ bool AArch64Arm64ECCallLowering::runOnModule(Module &Mod) { GuardFnType = FunctionType::get(PtrTy, {PtrTy, PtrTy}, false); GuardFnPtrType = PointerType::get(GuardFnType, 0); + DispatchFnType = FunctionType::get(PtrTy, {PtrTy, PtrTy, PtrTy}, false); + DispatchFnPtrType = PointerType::get(DispatchFnType, 0); GuardFnCFGlobal = M->getOrInsertGlobal("__os_arm64x_check_icall_cfg", GuardFnPtrType); GuardFnGlobal = M->getOrInsertGlobal("__os_arm64x_check_icall", GuardFnPtrType); + DispatchFnGlobal = + M->getOrInsertGlobal("__os_arm64x_dispatch_call", DispatchFnPtrType); + + DenseMap<GlobalAlias *, GlobalAlias *> FnsMap; + SetVector<GlobalAlias *> PatchableFns; - SetVector<Function *> DirectCalledFns; + for (Function &F : Mod) { + if (!F.hasFnAttribute(Attribute::HybridPatchable) || F.isDeclaration() || + F.hasLocalLinkage() || F.getName().ends_with("$hp_target")) + continue; + + // Rename hybrid patchable functions and change callers to use a global + // alias instead. + if (std::optional<std::string> MangledName = + getArm64ECMangledFunctionName(F.getName().str())) { + std::string OrigName(F.getName()); + F.setName(MangledName.value() + "$hp_target"); + + // The unmangled symbol is a weak alias to an undefined symbol with the + // "EXP+" prefix. This undefined symbol is resolved by the linker by + // creating an x86 thunk that jumps back to the actual EC target. Since we + // can't represent that in IR, we create an alias to the target instead. + // The "EXP+" symbol is set as metadata, which is then used by + // emitGlobalAlias to emit the right alias. + auto *A = + GlobalAlias::create(GlobalValue::LinkOnceODRLinkage, OrigName, &F); + F.replaceAllUsesWith(A); + F.setMetadata("arm64ec_exp_name", + MDNode::get(M->getContext(), + MDString::get(M->getContext(), + "EXP+" + MangledName.value()))); + A->setAliasee(&F); + + FnsMap[A] = GlobalAlias::create(GlobalValue::LinkOnceODRLinkage, + MangledName.value(), &F); + PatchableFns.insert(A); + } + } + + SetVector<GlobalValue *> DirectCalledFns; for (Function &F : Mod) if (!F.isDeclaration() && F.getCallingConv() != CallingConv::ARM64EC_Thunk_Native && F.getCallingConv() != CallingConv::ARM64EC_Thunk_X64) - processFunction(F, DirectCalledFns); + processFunction(F, DirectCalledFns, FnsMap); struct ThunkInfo { Constant *Src; @@ -755,14 +862,20 @@ bool AArch64Arm64ECCallLowering::runOnModule(Module &Mod) { {&F, buildEntryThunk(&F), Arm64ECThunkType::Entry}); } } - for (Function *F : DirectCalledFns) { + for (GlobalValue *O : DirectCalledFns) { + auto GA = dyn_cast<GlobalAlias>(O); + auto F = dyn_cast<Function>(GA ? GA->getAliasee() : O); ThunkMapping.push_back( - {F, buildExitThunk(F->getFunctionType(), F->getAttributes()), + {O, buildExitThunk(F->getFunctionType(), F->getAttributes()), Arm64ECThunkType::Exit}); - if (!F->hasDLLImportStorageClass()) + if (!GA && !F->hasDLLImportStorageClass()) ThunkMapping.push_back( {buildGuestExitThunk(F), F, Arm64ECThunkType::GuestExit}); } + for (GlobalAlias *A : PatchableFns) { + Function *Thunk = buildPatchableThunk(A, FnsMap[A]); + ThunkMapping.push_back({Thunk, A, Arm64ECThunkType::GuestExit}); + } if (!ThunkMapping.empty()) { SmallVector<Constant *> ThunkMappingArrayElems; @@ -785,7 +898,8 @@ bool AArch64Arm64ECCallLowering::runOnModule(Module &Mod) { } bool AArch64Arm64ECCallLowering::processFunction( - Function &F, SetVector<Function *> &DirectCalledFns) { + Function &F, SetVector<GlobalValue *> &DirectCalledFns, + DenseMap<GlobalAlias *, GlobalAlias *> &FnsMap) { SmallVector<CallBase *, 8> IndirectCalls; // For ARM64EC targets, a function definition's name is mangled differently @@ -837,6 +951,16 @@ bool AArch64Arm64ECCallLowering::processFunction( continue; } + // Use mangled global alias for direct calls to patchable functions. + if (GlobalAlias *A = dyn_cast<GlobalAlias>(CB->getCalledOperand())) { + auto I = FnsMap.find(A); + if (I != FnsMap.end()) { + CB->setCalledOperand(I->second); + DirectCalledFns.insert(I->first); + continue; + } + } + IndirectCalls.push_back(CB); ++Arm64ECCallsLowered; } diff --git a/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp b/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp index 6f6b551993b6d..63358c1568a35 100644 --- a/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp +++ b/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp @@ -201,6 +201,7 @@ class AArch64AsmPrinter : public AsmPrinter { void PrintDebugValueComment(const MachineInstr *MI, raw_ostream &OS); void emitFunctionBodyEnd() override; + void emitGlobalAlias(const Module &M, const GlobalAlias &GA) override; MCSymbol *GetCPISymbol(unsigned CPID) const override; void emitEndOfAsmFile(Module &M) override; @@ -1263,6 +1264,32 @@ void AArch64AsmPrinter::emitFunctionEntryLabel() { } } +void AArch64AsmPrinter::emitGlobalAlias(const Module &M, + const GlobalAlias &GA) { + if (auto F = dyn_cast_or_null<Function>(GA.getAliasee())) { + // Global aliases must point to a definition, but unmangled patchable + // symbols are special and need to point to an undefined symbol with "EXP+" + // prefix. Such undefined symbol is resolved by the linker by creating + // x86 thunk that jumps back to the actual EC target. + if (MDNode *Node = F->getMetadata("arm64ec_exp_name")) { + StringRef ExpStr = cast<MDString>(Node->getOperand(0))->getString(); + MCSymbol *ExpSym = MMI->getContext().getOrCreateSymbol(ExpStr); + MCSymbol *Sym = MMI->getContext().getOrCreateSymbol(GA.getName()); + OutStreamer->beginCOFFSymbolDef(Sym); + OutStreamer->emitCOFFSymbolStorageClass(COFF::IMAGE_SYM_CLASS_EXTERNAL); + OutStreamer->emitCOFFSymbolType(COFF::IMAGE_SYM_DTYPE_FUNCTION + << COFF::SCT_COMPLEX_TYPE_SHIFT); + OutStreamer->endCOFFSymbolDef(); + OutStreamer->emitSymbolAttribute(Sym, MCSA_Weak); + OutStreamer->emitAssignment( + Sym, MCSymbolRefExpr::create(ExpSym, MCSymbolRefExpr::VK_None, + MMI->getContext())); + return; + } + } + AsmPrinter::emitGlobalAlias(M, GA); +} + /// Small jump tables contain an unsigned byte or half, representing the offset /// from the lowest-addressed possible destination to the desired basic /// block. Since all instructions are 4-byte aligned, this is further compressed diff --git a/llvm/lib/Target/AArch64/AArch64CallingConvention.td b/llvm/lib/Target/AArch64/AArch64CallingConvention.td index 2f7e226fd09b2..6f885f4588c4b 100644 --- a/llvm/lib/Target/AArch64/AArch64CallingConvention.td +++ b/llvm/lib/Target/AArch64/AArch64CallingConvention.td @@ -333,7 +333,7 @@ def CC_AArch64_Win64_CFGuard_Check : CallingConv<[ let Entry = 1 in def CC_AArch64_Arm64EC_CFGuard_Check : CallingConv<[ - CCIfType<[i64], CCAssignToReg<[X11, X10]>> + CCIfType<[i64], CCAssignToReg<[X11, X10, X9]>> ]>; let Entry = 1 in diff --git a/llvm/lib/Transforms/Utils/CodeExtractor.cpp b/llvm/lib/Transforms/Utils/CodeExtractor.cpp index a05e955943f7c..5bca5cf8ff91f 100644 --- a/llvm/lib/Transforms/Utils/CodeExtractor.cpp +++ b/llvm/lib/Transforms/Utils/CodeExtractor.cpp @@ -932,6 +932,7 @@ Function *CodeExtractor::constructFunction(const ValueSet &inputs, case Attribute::DisableSanitizerInstrumentation: case Attribute::FnRetThunkExtern: case Attribute::Hot: + case Attribute::HybridPatchable: case Attribute::NoRecurse: case Attribute::InlineHint: case Attribute::MinSize: diff --git a/llvm/test/CodeGen/AArch64/arm64ec-hybrid-patchable.ll b/llvm/test/CodeGen/AArch64/arm64ec-hybrid-patchable.ll new file mode 100644 index 0000000000000..e5387d40b9c64 --- /dev/null +++ b/llvm/test/CodeGen/AArch64/arm64ec-hybrid-patchable.ll @@ -0,0 +1,315 @@ +; RUN: llc -mtriple=arm64ec-pc-windows-msvc < %s | FileCheck %s +; RUN: llc -mtriple=arm64ec-pc-windows-msvc -filetype=obj -o %t.o < %s +; RUN: llvm-objdump -t %t.o | FileCheck --check-prefix=SYM %s + +define dso_local ptr @func() hybrid_patchable nounwind { +; SYM: [ 8](sec 4)(fl 0x00)(ty 20)(scl 2) (nx 0) 0x00000000 #func$hp_target +; CHECK-LABEL: .def "#func$hp_target"; +; CHECK: .section .text,"xr",discard,"#func$hp_target" +; CHECK-NEXT: .globl "#func$hp_target" // -- Begin function #func$hp_target +; CHECK-NEXT: .p2align 2 +; CHECK-NEXT: "#func$hp_target": // @"#func$hp_target" +; CHECK-NEXT: // %bb.0: +; CHECK-NEXT: adrp x0, func +; CHECK-NEXT: add x0, x0, :lo12:func +; CHECK-NEXT: ret + ret ptr @func +} + +define void @has_varargs(...) hybrid_patchable nounwind { +; SYM: [11](sec 5)(fl 0x00)(ty 20)(scl 2) (nx 0) 0x00000000 #has_varargs$hp_target +; CHECK-LABEL: .def "#has_varargs$hp_target"; +; CHECK: .section .text,"xr",discard,"#has_varargs$hp_target" +; CHECK-NEXT: .globl "#has_varargs$hp_target" // -- Begin function #has_varargs$hp_target +; CHECK-NEXT: .p2align 2 +; CHECK-NEXT: "#has_varargs$hp_target": // @"#has_varargs$hp_target" +; CHECK-NEXT: // %bb.0: +; CHECK-NEXT: sub sp, sp, #32 +; CHECK-NEXT: stp x0, x1, [x4, #-32] +; CHECK-NEXT: stp x2, x3, [x4, #-16] +; CHECK-NEXT: add sp, sp, #32 +; CHECK-NEXT: ret + ret void +} + +define void @has_sret(ptr sret([100 x i8])) hybrid_patchable nounwind { +; SYM: [14](sec 6)(fl 0x00)(ty 20)(scl 2) (nx 0) 0x00000000 #has_sret$hp_target +; CHECK-LABEL: .def "#has_sret$hp_target"; +; CHECK: .section .text,"xr",discard,"#has_sret$hp_target" +; CHECK-NEXT: .globl "#has_sret$hp_target" // -- Begin function #has_sret$hp_target +; CHECK-NEXT: .p2align 2 +; CHECK-NEXT: "#has_sret$hp_target": // @"#has_sret$hp_target" +; CHECK-NEXT: // %bb.0: +; CHECK-NEXT: ret + ret void +} + +define dllexport void @exp() hybrid_patchable nounwind { +; CHECK-LABEL: .def "#exp$hp_target"; +; CHECK: .section .text,"xr",discard,"#exp$hp_target" +; CHECK-NEXT: .globl "#exp$hp_target" // -- Begin function #exp$hp_target +; CHECK-NEXT: .p2align 2 +; CHECK-NEXT: "#exp$hp_target": // @"#exp$hp_target" +; CHECK-NEXT: // %bb.0: +; CHECK-NEXT: ret + ret void +} + +; hybrid_patchable attribute is ignored on internal functions +define internal i32 @static_func() hybrid_patchable nounwind { +; CHECK-LABEL: .def static_func; +; CHECK: static_func: // @static_func +; CHECK-NEXT: // %bb.0: +; CHECK-NEXT: mov w0, #2 // =0x2 +; CHECK-NEXT: ret + ret i32 2 +} + +define dso_local void @caller() nounwind { +; CHECK-LABEL: .def "#caller"; +; CHECK: .section .text,"xr",discard,"#caller" +; CHECK-NEXT: .globl "#caller" // -- Begin function #caller +; CHECK-NEXT: .p2align 2 +; CHECK-NEXT: "#caller": // @"#caller" +; CHECK-NEXT: .weak_anti_dep caller +; CHECK-NEXT: .set caller, "#caller"{{$}} +; CHECK-NEXT: // %bb.0: +; CHECK-NEXT: str x30, [sp, #-16]! // 8-byte Folded Spill +; CHECK-NEXT: bl "#func" +; CHECK-NEXT: bl static_func +; CHECK-NEXT: adrp x8, __os_arm64x_check_icall +; CHECK-NEXT: adrp x11, func +; CHECK-NEXT: add x11, x11, :lo12:func +; CHECK-NEXT: ldr x8, [x8, :lo12:__os_arm64x_check_icall] +; CHECK-NEXT: adrp x10, ($iexit_thunk$cdecl$v$v) +; CHECK-NEXT: add x10, x10, :lo12:($iexit_thunk$cdecl$v$v) +; CHECK-NEXT: str x11, [sp, #8] +; CHECK-NEXT: blr x8 +; CHECK-NEXT: blr x11 +; CHECK-NEXT: ldr x30, [sp], #16 // 8-byte Folded Reload +; CHECK-NEXT: ret + %1 = call i32 @func() + %2 = call i32 @static_func() + %3 = alloca ptr, align 8 + store ptr @func, ptr %3, align 8 + %4 = load ptr, ptr %3, align 8 + call void %4() + ret void +} + +; CHECK-LABEL: def "#func$hybpatch_thunk"; +; CHECK: .section .wowthk$aa,"xr",discard,"#func$hybpatch_thunk" +; CHECK-NEXT: .globl "#func$hybpatch_thunk" // -- Begin function #func$hybpatch_thunk +; CHECK-NEXT: .p2align 2 +; CHECK-NEXT: "#func$hybpatch_thunk": // @"#func$hybpatch_thunk" +; CHECK-NEXT: .seh_proc "#func$hybpatch_thunk" +; CHECK-NEXT: // %bb.0: +; CHECK-NEXT: str x30, [sp, #-16]! // 8-byte Folded Spill +; CHECK-NEXT: .seh_save_reg_x x30, 16 +; CHECK-NEXT: .seh_endprologue +; CHECK-NEXT: adrp x8, __os_arm64x_dispatch_call +; CHECK-NEXT: adrp x11, func +; CHECK-NEXT: add x11, x11, :lo12:func +; CHECK-NEXT: ldr x8, [x8, :lo12:__os_arm64x_dispatch_call] +; CHECK-NEXT: adrp x10, ($iexit_thunk$cdecl$i8$v) +; CHECK-NEXT: add x10, x10, :lo12:($iexit_thunk$cdecl$i8$v) +; CHECK-NEXT: adrp x9, "#func$hp_target" +; CHECK-NEXT: add x9, x9, :lo12:"#func$hp_target" +; CHECK-NEXT: blr x8 +; CHECK-NEXT: .seh_startepilogue +; CHECK-NEXT: ldr x30, [sp], #16 // 8-byte Folded Reload +; CHECK-NEXT: .seh_save_reg_x x30, 16 +; CHECK-NEXT: .seh_endepilogue +; CHECK-NEXT: br x11 +; CHECK-NEXT: .seh_endfunclet +; CHECK-NEXT: .seh_endproc + +; CHECK-LABEL: .def "#has_varargs$hybpatch_thunk"; +; CHECK: .section .wowthk$aa,"xr",discard,"#has_varargs$hybpatch_thunk" +; CHECK-NEXT: .globl "#has_varargs$hybpatch_thunk" // -- Begin function #has_varargs$hybpatch_thunk +; CHECK-NEXT: .p2align 2 +; CHECK-NEXT:"#has_varargs$hybpatch_thunk": // @"#has_varargs$hybpatch_thunk" +; CHECK-NEXT:.seh_proc "#has_varargs$hybpatch_thunk" +; CHECK-NEXT:// %bb.0: +; CHECK-NEXT: str x30, [sp, #-16]! // 8-byte Folded Spill +; CHECK-NEXT: .seh_save_reg_x x30, 16 +; CHECK-NEXT: .seh_endprologue +; CHECK-NEXT: adrp x8, __os_arm64x_dispatch_call +; CHECK-NEXT: adrp x11, has_varargs +; CHECK-NEXT: add x11, x11, :lo12:has_varargs +; CHECK-NEXT: ldr x8, [x8, :lo12:__os_arm64x_dispatch_call] +; CHECK-NEXT: adrp x10, ($iexit_thunk$cdecl$v$varargs) +; CHECK-NEXT: add x10, x10, :lo12:($iexit_thunk$cdecl$v$varargs) +; CHECK-NEXT: adrp x9, "#has_varargs$hp_target" +; CHECK-NEXT: add x9, x9, :lo12:"#has_varargs$hp_target" +; CHECK-NEXT: blr x8 +; CHECK-NEXT: .seh_startepilogue +; CHECK-NEXT: ldr x30, [sp], #16 // 8-byte Folded Reload +; CHECK-NEXT: .seh_save_reg_x x30, 16 +; CHECK-NEXT: .seh_endepilogue +; CHECK-NEXT: br x11 +; CHECK-NEXT: .seh_endfunclet +; CHECK-NEXT: .seh_endproc + +; CHECK-LABEL: .def "#has_sret$hybpatch_thunk"; +; CHECK: .section .wowthk$aa,"xr",discard,"#has_sret$hybpatch_thunk" +; CHECK-NEXT: .globl "#has_sret$hybpatch_thunk" // -- Begin function #has_sret$hybpatch_thunk +; CHECK-NEXT: .p2align 2 +; CHECK-NEXT: "#has_sret$hybpatch_thunk": // @"#has_sret$hybpatch_thunk" +; CHECK-NEXT: .seh_proc "#has_sret$hybpatch_thunk" +; CHECK-NEXT: // %bb.0: +; CHECK-NEXT: str x30, [sp, #-16]! // 8-byte Folded Spill +; CHECK-NEXT: .seh_save_reg_x x30, 16 +; CHECK-NEXT: .seh_endprologue +; CHECK-NEXT: adrp x9, __os_arm64x_dispatch_call +; CHECK-NEXT: adrp x11, has_sret +; CHECK-NEXT: add x11, x11, :lo12:has_sret +; CHECK-NEXT: ldr x12, [x9, :lo12:__os_arm64x_dispatch_call] +; CHECK-NEXT: adrp x10, ($iexit_thunk$cdecl$m100$v) +; CHECK-NEXT: add x10, x10, :lo12:($iexit_thunk$cdecl$m100$v) +; CHECK-NEXT: adrp x9, "#has_sret$hp_target" +; CHECK-NEXT: add x9, x9, :lo12:"#has_sret$hp_target" +; CHECK-NEXT: blr x12 +; CHECK-NEXT: .seh_startepilogue +; CHECK-NEXT: ldr x30, [sp], #16 // 8-byte Folded Reload +; CHECK-NEXT: .seh_save_reg_x x30, 16 +; CHECK-NEXT: .seh_endepilogue +; CHECK-NEXT: br x11 +; CHECK-NEXT: .seh_endfunclet +; CHECK-NEXT: .seh_endproc + +; CHECK-LABEL: .def "#exp$hybpatch_thunk"; +; CHECK: .section .wowthk$aa,"xr",discard,"#exp$hybpatch_thunk" +; CHECK-NEXT: .globl "#exp$hybpatch_thunk" // -- Begin function #exp$hybpatch_thunk +; CHECK-NEXT: .p2align 2 +; CHECK-NEXT: "#exp$hybpatch_thunk": // @"#exp$hybpatch_thunk" +; CHECK-NEXT: .seh_proc "#exp$hybpatch_thunk" +; CHECK-NEXT: // %bb.0: +; CHECK-NEXT: str x30, [sp, #-16]! // 8-byte Folded Spill +; CHECK-NEXT: .seh_save_reg_x x30, 16 +; CHECK-NEXT: .seh_endprologue +; CHECK-NEXT: adrp x8, __os_arm64x_dispatch_call +; CHECK-NEXT: adrp x11, exp +; CHECK-NEXT: add x11, x11, :lo12:exp +; CHECK-NEXT: ldr x8, [x8, :lo12:__os_arm64x_dispatch_call] +; CHECK-NEXT: adrp x10, ($iexit_thunk$cdecl$v$v) +; CHECK-NEXT: add x10, x10, :lo12:($iexit_thunk$cdecl$v$v) +; CHECK-NEXT: adrp x9, "#exp$hp_target" +; CHECK-NEXT: add x9, x9, :lo12:"#exp$hp_target" +; CHECK-NEXT: blr x8 +; CHECK-NEXT: .seh_startepilogue +; CHECK-NEXT: ldr x30, [sp], #16 // 8-byte Folded Reload +; CHECK-NEXT: .seh_save_reg_x x30, 16 +; CHECK-NEXT: .seh_endepilogue +; CHECK-NEXT: br x11 +; CHECK-NEXT: .seh_endfunclet +; CHECK-NEXT: .seh_endproc + +; Verify the hybrid bitmap +; CHECK-LABEL: .section .hybmp$x,"yi" +; CHECK-NEXT: .symidx "#func$hp_target" +; CHECK-NEXT: .symidx $ientry_thunk$cdecl$i8$v +; CHECK-NEXT: .word 1 +; CHECK-NEXT: .symidx "#has_varargs$hp_target" +; CHECK-NEXT: .symidx $ientry_thunk$cdecl$v$varargs +; CHECK-NEXT: .word 1 +; CHECK-NEXT: .symidx "#has_sret$hp_target" +; CHECK-NEXT: .symidx $ientry_thunk$cdecl$m100$v +; CHECK-NEXT: .word 1 +; CHECK-NEXT: .symidx "#exp$hp_target" +; CHECK-NEXT: .symidx $ientry_thunk$cdecl$v$v +; CHECK-NEXT: .word 1 +; CHECK-NEXT: .symidx "#caller" +; CHECK-NEXT: .symidx $ientry_thunk$cdecl$v$v +; CHECK-NEXT: .word 1 +; CHECK-NEXT: .symidx func +; CHECK-NEXT: .symidx $iexit_thunk$cdecl$i8$v +; CHECK-NEXT: .word 4 +; CHECK-NEXT: .symidx "#func$hybpatch_thunk" +; CHECK-NEXT: .symidx func +; CHECK-NEXT: .word 0 +; CHECK-NEXT: .symidx "#has_varargs$hybpatch_thunk" +; CHECK-NEXT: .symidx has_varargs +; CHECK-NEXT: .word 0 +; CHECK-NEXT: .symidx "#has_sret$hybpatch_thunk" +; CHECK-NEXT: .symidx has_sret +; CHECK-NEXT: .word 0 +; CHECK-NEXT: .symidx "#exp$hybpatch_thunk" +; CHECK-NEXT: .symidx exp +; CHECK-NEXT: .word 0 +; CHECK-NEXT: .section .drectve,"yni" +; CHECK-NEXT: .ascii " /EXPORT:\"#exp$hp_target,EXPORTAS,exp$hp_target\"" + +; CHECK-NEXT: .def func; +; CHECK-NEXT: .scl 2; +; CHECK-NEXT: .type 32; +; CHECK-NEXT: .endef +; CHECK-NEXT: .weak func +; CHECK-NEXT: .set func, "EXP+#func"{{$}} +; CHECK-NEXT: .weak "#func" +; CHECK-NEXT: .def "#func"; +; CHECK-NEXT: .scl 2; +; CHECK-NEXT: .type 32; +; CHECK-NEXT: .endef +; CHECK-NEXT: .set "#func", "#func$hybpatch_thunk"{{$}} +; CHECK-NEXT: .def has_varargs; +; CHECK-NEXT: .scl 2; +; CHECK-NEXT: .type 32; +; CHECK-NEXT: .endef +; CHECK-NEXT: .weak has_varargs +; CHECK-NEXT: .set has_varargs, "EXP+#has_varargs" +; CHECK-NEXT: .weak "#has_varargs" +; CHECK-NEXT: .def "#has_varargs"; +; CHECK-NEXT: .scl 2; +; CHECK-NEXT: .type 32; +; CHECK-NEXT: .endef +; CHECK-NEXT: .set "#has_varargs", "#has_varargs$hybpatch_thunk" +; CHECK-NEXT: .def has_sret; +; CHECK-NEXT: .scl 2; +; CHECK-NEXT: .type 32; +; CHECK-NEXT: .endef +; CHECK-NEXT: .weak has_sret +; CHECK-NEXT: .set has_sret, "EXP+#has_sret" +; CHECK-NEXT: .weak "#has_sret" +; CHECK-NEXT: .def "#has_sret"; +; CHECK-NEXT: .scl 2; +; CHECK-NEXT: .type 32; +; CHECK-NEXT: .endef +; CHECK-NEXT: .set "#has_sret", "#has_sret$hybpatch_thunk" +; CHECK-NEXT: .def exp; +; CHECK-NEXT: .scl 2; +; CHECK-NEXT: .type 32; +; CHECK-NEXT: .endef +; CHECK-NEXT: .weak exp +; CHECK-NEXT: .set exp, "EXP+#exp" +; CHECK-NEXT: .weak "#exp" +; CHECK-NEXT: .def "#exp"; +; CHECK-NEXT: .scl 2; +; CHECK-NEXT: .type 32; +; CHECK-NEXT: .endef +; CHECK-NEXT: .set "#exp", "#exp$hybpatch_thunk" + +; SYM: [53](sec 15)(fl 0x00)(ty 20)(scl 2) (nx 0) 0x00000000 #func$hybpatch_thunk +; SYM: [58](sec 16)(fl 0x00)(ty 20)(scl 2) (nx 0) 0x00000000 #has_varargs$hybpatch_thunk +; SYM: [68](sec 18)(fl 0x00)(ty 20)(scl 2) (nx 0) 0x00000000 #has_sret$hybpatch_thunk +; SYM: [78](sec 20)(fl 0x00)(ty 20)(scl 2) (nx 0) 0x00000000 #exp$hybpatch_thunk +; SYM: [110](sec 0)(fl 0x00)(ty 0)(scl 69) (nx 1) 0x00000000 func +; SYM-NEXT: AUX indx 112 srch 3 +; SYM-NEXT: [112](sec 0)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000 EXP+#func +; SYM: [116](sec 0)(fl 0x00)(ty 0)(scl 69) (nx 1) 0x00000000 #func +; SYM-NEXT: AUX indx 53 srch 3 +; SYM: [122](sec 0)(fl 0x00)(ty 0)(scl 69) (nx 1) 0x00000000 has_varargs +; SYM-NEXT: AUX indx 124 srch 3 +; SYM-NEXT: [124](sec 0)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000 EXP+#has_varargs +; SYM-NEXT: [125](sec 0)(fl 0x00)(ty 0)(scl 69) (nx 1) 0x00000000 has_sret +; SYM-NEXT: AUX indx 127 srch 3 +; SYM-NEXT: [127](sec 0)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000 EXP+#has_sret +; SYM-NEXT: [128](sec 0)(fl 0x00)(ty 0)(scl 69) (nx 1) 0x00000000 exp +; SYM-NEXT: AUX indx 130 srch 3 +; SYM-NEXT: [130](sec 0)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000 EXP+#exp +; SYM-NEXT: [131](sec 0)(fl 0x00)(ty 0)(scl 69) (nx 1) 0x00000000 #has_varargs +; SYM-NEXT: AUX indx 58 srch 3 +; SYM-NEXT: [133](sec 0)(fl 0x00)(ty 0)(scl 69) (nx 1) 0x00000000 #has_sret +; SYM-NEXT: AUX indx 68 srch 3 +; SYM-NEXT: [135](sec 0)(fl 0x00)(ty 0)(scl 69) (nx 1) 0x00000000 #exp +; SYM-NEXT: AUX indx 78 srch 3 >From 1ac6b1bcc927f22a39311f6c85e60ddcb1de8949 Mon Sep 17 00:00:00 2001 From: Jacek Caban <ja...@codeweavers.com> Date: Fri, 3 May 2024 00:27:20 +0200 Subject: [PATCH 2/2] [clang][ARM64EC] Add support for hybrid_patchable attribute. --- clang/include/clang/Basic/Attr.td | 9 ++++++ clang/include/clang/Basic/AttrDocs.td | 10 +++++++ .../clang/Basic/DiagnosticSemaKinds.td | 3 ++ clang/lib/AST/TypePrinter.cpp | 3 ++ clang/lib/CodeGen/CodeGenFunction.cpp | 3 ++ clang/lib/Sema/SemaDecl.cpp | 7 +++++ clang/lib/Sema/SemaDeclAttr.cpp | 3 ++ clang/test/CodeGen/arm64ec-hybrid-patchable.c | 29 +++++++++++++++++++ ...a-attribute-supported-attributes-list.test | 1 + 9 files changed, 68 insertions(+) create mode 100644 clang/test/CodeGen/arm64ec-hybrid-patchable.c diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 1293d0ddbc117..2b6d421729322 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -477,6 +477,9 @@ def TargetELF : TargetSpec { def TargetELFOrMachO : TargetSpec { let ObjectFormats = ["ELF", "MachO"]; } +def TargetArm64EC : TargetSpec { + let CustomCode = [{ Target.getTriple().isWindowsArm64EC() }]; +} def TargetSupportsInitPriority : TargetSpec { let CustomCode = [{ !Target.getTriple().isOSzOS() }]; @@ -4027,6 +4030,12 @@ def SelectAny : InheritableAttr { let SimpleHandler = 1; } +def HybridPatchable : DeclOrTypeAttr, TargetSpecificAttr<TargetArm64EC> { + let Spellings = [Declspec<"hybrid_patchable">, GCC<"hybrid_patchable">]; + let Subjects = SubjectList<[Function]>; + let Documentation = [HybridPatchableDocs]; +} + def Thread : Attr { let Spellings = [Declspec<"thread">]; let LangOpts = [MicrosoftExt]; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 09cf4f80bd999..b5944382ba7a4 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -5984,6 +5984,16 @@ For more information see or `msvc documentation <https://docs.microsoft.com/pl-pl/cpp/cpp/selectany>`_. }]; } +def HybridPatchableDocs : Documentation { + let Category = DocCatDecl; + let Content = [{ +The ``hybrid_patchable`` attribute declares an ARM64EC function with an additional +x86-64 thunk, which may be patched in runtime. + +For more information see +`ARM64EC ABI documentation <https://learn.microsoft.com/en-us/windows/arm/arm64ec-abi>`_. +}]; } + def WebAssemblyExportNameDocs : Documentation { let Category = DocCatFunction; let Content = [{ diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index de3d94155a9a0..60d9f3d0ec022 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -3671,6 +3671,9 @@ def err_attribute_weak_static : Error< "weak declaration cannot have internal linkage">; def err_attribute_selectany_non_extern_data : Error< "'selectany' can only be applied to data items with external linkage">; +def warn_attribute_hybrid_patchable_non_extern : Warning< + "'hybrid_patchable' is ignored on functions without external linkage">, + InGroup<IgnoredAttributes>; def err_declspec_thread_on_thread_variable : Error< "'__declspec(thread)' applied to variable that already has a " "thread-local storage specifier">; diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp index ffec3ef9d2269..c2c3807399a8a 100644 --- a/clang/lib/AST/TypePrinter.cpp +++ b/clang/lib/AST/TypePrinter.cpp @@ -1984,6 +1984,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T, case attr::MSABI: OS << "ms_abi"; break; case attr::SysVABI: OS << "sysv_abi"; break; case attr::RegCall: OS << "regcall"; break; + case attr::HybridPatchable: + OS << "hybrid_patchable"; + break; case attr::Pcs: { OS << "pcs("; QualType t = T->getEquivalentType(); diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp index ea4635c039cb2..df2f493b3aa4a 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -977,6 +977,9 @@ void CodeGenFunction::StartFunction(GlobalDecl GD, QualType RetTy, if (D && D->hasAttr<NoProfileFunctionAttr>()) Fn->addFnAttr(llvm::Attribute::NoProfile); + if (D && D->hasAttr<HybridPatchableAttr>()) + Fn->addFnAttr(llvm::Attribute::HybridPatchable); + if (D) { // Function attributes take precedence over command line flags. if (auto *A = D->getAttr<FunctionReturnThunksAttr>()) { diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 1f2fde12c9d24..0843071ed7ec1 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -6886,6 +6886,13 @@ static void checkAttributesAfterMerging(Sema &S, NamedDecl &ND) { } } + if (HybridPatchableAttr *Attr = ND.getAttr<HybridPatchableAttr>()) { + if (!ND.isExternallyVisible()) { + S.Diag(Attr->getLocation(), + diag::warn_attribute_hybrid_patchable_non_extern); + ND.dropAttr<SelectAnyAttr>(); + } + } if (const InheritableAttr *Attr = getDLLAttr(&ND)) { auto *VD = dyn_cast<VarDecl>(&ND); bool IsAnonymousNS = false; diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 20f46c003a464..edaeef4bdb2de 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -7033,6 +7033,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL, case ParsedAttr::AT_MSConstexpr: handleMSConstexprAttr(S, D, AL); break; + case ParsedAttr::AT_HybridPatchable: + handleSimpleAttribute<HybridPatchableAttr>(S, D, AL); + break; // HLSL attributes: case ParsedAttr::AT_HLSLNumThreads: diff --git a/clang/test/CodeGen/arm64ec-hybrid-patchable.c b/clang/test/CodeGen/arm64ec-hybrid-patchable.c new file mode 100644 index 0000000000000..30e5c7cdbb102 --- /dev/null +++ b/clang/test/CodeGen/arm64ec-hybrid-patchable.c @@ -0,0 +1,29 @@ +// REQUIRES: aarch64-registered-target +// RUN: %clang_cc1 -triple arm64ec-pc-windows -fms-extensions -emit-llvm -o - %s -verify | FileCheck %s + +// CHECK: ; Function Attrs: hybrid_patchable noinline nounwind optnone +// CHECK-NEXT: define dso_local i32 @func() #0 { +int __attribute__((hybrid_patchable)) func(void) { return 1; } + +// CHECK: ; Function Attrs: hybrid_patchable noinline nounwind optnone +// CHECK-NEXT: define dso_local i32 @func2() #0 { +int __declspec(hybrid_patchable) func2(void) { return 2; } + +// CHECK: ; Function Attrs: hybrid_patchable noinline nounwind optnone +// CHECK-NEXT: define dso_local i32 @func3() #0 { +int __declspec(hybrid_patchable) func3(void); +int func3(void) { return 3; } + +// CHECK: ; Function Attrs: hybrid_patchable noinline nounwind optnone +// CHECK-NEXT: define internal void @static_func() #0 { +// expected-warning@+1 {{'hybrid_patchable' is ignored on functions without external linkage}} +static void __declspec(hybrid_patchable) static_func(void) {} + +// CHECK: ; Function Attrs: hybrid_patchable noinline nounwind optnone +// CHECK-NEXT: define linkonce_odr dso_local i32 @func4() #0 comdat { +int inline __declspec(hybrid_patchable) func4(void) { return 4; } + +void caller(void) { + static_func(); + func4(); +} diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test index 33f9c2f51363c..e082db698ef0c 100644 --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -83,6 +83,7 @@ // CHECK-NEXT: HIPManaged (SubjectMatchRule_variable) // CHECK-NEXT: HLSLResourceClass (SubjectMatchRule_record_not_is_union) // CHECK-NEXT: Hot (SubjectMatchRule_function) +// CHECK-NEXT: HybridPatchable (SubjectMatchRule_function) // CHECK-NEXT: IBAction (SubjectMatchRule_objc_method_is_instance) // CHECK-NEXT: IFunc (SubjectMatchRule_function) // CHECK-NEXT: InitPriority (SubjectMatchRule_variable) _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits