diff options
author | prabhukr <prabhukr@google.com> | 2024-12-10 14:56:01 -0800 |
---|---|---|
committer | prabhukr <prabhukr@google.com> | 2024-12-10 14:56:01 -0800 |
commit | fec6493ee9ecc5fe5af191912f25ec4aa443430b (patch) | |
tree | 93696b7c0b702dd495c041a3df284fb85d7cb511 | |
parent | 581f755a2a22910da8a9a160c4ea5efeb43a40b4 (diff) | |
parent | a72a703a7c6de19300167bc67cb5b9b26a43bfb3 (diff) | |
download | llvm-users/Prabhuk/sprclang-document-callgraphsection-option.zip llvm-users/Prabhuk/sprclang-document-callgraphsection-option.tar.gz llvm-users/Prabhuk/sprclang-document-callgraphsection-option.tar.bz2 |
[𝘀𝗽𝗿] initial versionusers/Prabhuk/sprclang-document-callgraphsection-option
Created using spr 1.3.6-beta.1
46 files changed, 1345 insertions, 29 deletions
diff --git a/clang/docs/CallGraphSection.rst b/clang/docs/CallGraphSection.rst new file mode 100644 index 0000000..d5db507 --- /dev/null +++ b/clang/docs/CallGraphSection.rst @@ -0,0 +1,251 @@ +================== +Call Graph Section +================== + +Introduction +============ + +With ``-fcall-graph-section``, the compiler will create a call graph section +in the object file. It will include type identifiers for indirect calls and +targets. This information can be used to map indirect calls to their receivers +with matching types. A complete and high-precision call graph can be +reconstructed by complementing this information with disassembly +(see ``llvm-objdump --call-graph-info``). + +Semantics +========= + +A coarse-grained, type-agnostic call graph may allow indirect calls to target +any function in the program. This approach ensures completeness since no +indirect call edge is missing. However, it is generally poor in precision +due to having unneeded edges. + +A call graph section provides type identifiers for indirect calls and targets. +This information can be used to restrict the receivers of an indirect target to +indirect calls with matching type. Consequently, the precision for indirect +call edges are improved while maintaining the completeness. + +The ``llvm-objdump`` utility provides a ``--call-graph-info`` option to extract +full call graph information by parsing the content of the call graph section +and disassembling the program for complementary information, e.g., direct +calls. + +Section layout +============== + +A call graph section consists of zero or more call graph entries. +Each entry contains information on a function and its indirect calls. + +An entry of a call graph section has the following layout in the binary: + ++---------------------+-----------------------------------------------------------------------+ +| Element | Content | ++=====================+=======================================================================+ +| FormatVersionNumber | Format version number. | ++---------------------+-----------------------------------------------------------------------+ +| FunctionEntryPc | Function entry address. | ++---------------------+-----------------------------------+-----------------------------------+ +| | A flag whether the function is an | - 0: not an indirect target | +| FunctionKind | indirect target, and if so, | - 1: indirect target, unknown id | +| | whether its type id is known. | - 2: indirect target, known id | ++---------------------+-----------------------------------+-----------------------------------+ +| FunctionTypeId | Type id for the indirect target. Present only when FunctionKind is 2. | ++---------------------+-----------------------------------------------------------------------+ +| CallSiteCount | Number of type id to indirect call site mappings that follow. | ++---------------------+-----------------------------------------------------------------------+ +| CallSiteList | List of type id and indirect call site pc pairs. | ++---------------------+-----------------------------------------------------------------------+ + +Each element in an entry (including each element of the contained lists and +pairs) occupies 64-bit space. + +The format version number is repeated per entry to support concatenation of +call graph sections with different format versions by the linker. + +As of now, the only supported format version is described above and has version +number 0. + +Type identifiers +================ + +The type for an indirect call or target is the function signature. +The mapping from a type to an identifier is an ABI detail. +In the current experimental implementation, an identifier of type T is +computed as follows: + + - Obtain the generalized mangled name for “typeinfo name for T”. + - Compute MD5 hash of the name as a string. + - Reinterpret the first 8 bytes of the hash as a little-endian 64-bit integer. + +To avoid mismatched pointer types, generalizations are applied. +Pointers in return and argument types are treated as equivalent as long as the +qualifiers for the type they point to match. +For example, ``char*``, ``char**``, and ``int*`` are considered equivalent +types. However, ``char*`` and ``const char*`` are considered separate types. + +Missing type identifiers +======================== + +For functions, two cases need to be considered. First, if the compiler cannot +deduce a type id for an indirect target, it will be listed as an indirect target +without a type id. Second, if an object without a call graph section gets +linked, the final call graph section will lack information on functions from +the object. For completeness, these functions need to be taken as receiver to +any indirect call regardless of their type id. +``llvm-objdump --call-graph-info`` lists these functions as indirect targets +with `UNKNOWN` type id. + +For indirect calls, current implementation guarantees a type id for each +compiled call. However, if an object without a call graph section gets linked, +no type id will be present for its indirect calls. For completeness, these calls +need to be taken to target any indirect target regardless of their type id. For +indirect calls, ``llvm-objdump --call-graph-info`` prints 1) a complete list of +indirect calls, 2) type id to indirect call mappings. The difference of these +lists allow to deduce the indirect calls with missing type ids. + +TODO: measure and report the ratio of missed type ids + +Performance +=========== + +A call graph section does not affect the executable code and does not occupy +memory during process execution. Therefore, there is no performance overhead. + +The scheme has not yet been optimized for binary size. + +TODO: measure and report the increase in the binary size + +Example +======= + +For example, consider the following C++ code: + +.. code-block:: cpp + + namespace { + // Not an indirect target + void foo() {} + } + + // Indirect target 1 + void bar() {} + + // Indirect target 2 + int baz(char a, float *b) { + return 0; + } + + // Indirect target 3 + int main() { + char a; + float b; + void (*fp_bar)() = bar; + int (*fp_baz1)(char, float*) = baz; + int (*fp_baz2)(char, float*) = baz; + + // Indirect call site 1 + fp_bar(); + + // Indirect call site 2 + fp_baz1(a, &b); + + // Indirect call site 3: shares the type id with indirect call site 2 + fp_baz2(a, &b); + + // Direct call sites + foo(); + bar(); + baz(a, &b); + + return 0; + } + +Following will compile it with a call graph section created in the binary: + +.. code-block:: bash + + $ clang -fcall-graph-section example.cpp + +During the construction of the call graph section, the type identifiers are +computed as follows: + ++---------------+-----------------------+----------------------------+----------------------------+ +| Function name | Generalized signature | Mangled name (itanium ABI) | Numeric type id (md5 hash) | ++===============+=======================+============================+============================+ +| bar | void () | _ZTSFvvE.generalized | f85c699bb8ef20a2 | ++---------------+-----------------------+----------------------------+----------------------------+ +| baz | int (char, void*) | _ZTSFicPvE.generalized | e3804d2a7f2b03fe | ++---------------+-----------------------+----------------------------+----------------------------+ +| main | int () | _ZTSFivE.generalized | a9494def81a01dc | ++---------------+-----------------------+----------------------------+----------------------------+ + +The call graph section will have the following content: + ++---------------+-----------------+--------------+----------------+---------------+--------------------------------------+ +| FormatVersion | FunctionEntryPc | FunctionKind | FunctionTypeId | CallSiteCount | CallSiteList | ++===============+=================+==============+================+===============+======================================+ +| 0 | EntryPc(foo) | 0 | (empty) | 0 | (empty) | ++---------------+-----------------+--------------+----------------+---------------+--------------------------------------+ +| 0 | EntryPc(bar) | 2 | TypeId(bar) | 0 | (empty) | ++---------------+-----------------+--------------+----------------+---------------+--------------------------------------+ +| 0 | EntryPc(baz) | 2 | TypeId(baz) | 0 | (empty) | ++---------------+-----------------+--------------+----------------+---------------+--------------------------------------+ +| 0 | EntryPc(main) | 2 | TypeId(main) | 3 | * TypeId(bar), CallSitePc(fp_bar()) | +| | | | | | * TypeId(baz), CallSitePc(fp_baz1()) | +| | | | | | * TypeId(baz), CallSitePc(fp_baz2()) | ++---------------+-----------------+--------------+----------------+---------------+--------------------------------------+ + + +The ``llvm-objdump`` utility can parse the call graph section and disassemble +the program to provide complete call graph information. This includes any +additional call sites from the binary: + +.. code-block:: bash + + $ llvm-objdump --call-graph-info a.out + + # Comments are not a part of the llvm-objdump's output but inserted for clarifications. + + a.out: file format elf64-x86-64 + # These warnings are due to the functions and the indirect calls coming from linked objects. + llvm-objdump: warning: 'a.out': callgraph section does not have type ids for 3 indirect calls + llvm-objdump: warning: 'a.out': callgraph section does not have information for 10 functions + + # Unknown targets are the 10 functions the warnings mention. + INDIRECT TARGET TYPES (TYPEID [FUNC_ADDR,]) + UNKNOWN 401000 401100 401234 401050 401090 4010d0 4011d0 401020 401060 401230 + a9494def81a01dc 401150 # main() + f85c699bb8ef20a2 401120 # bar() + e3804d2a7f2b03fe 401130 # baz() + + # Notice that the call sites share the same type id as target functions + INDIRECT CALL TYPES (TYPEID [CALL_SITE_ADDR,]) + f85c699bb8ef20a2 401181 # Indirect call site 1 (fp_bar()) + e3804d2a7f2b03fe 401191 4011a1 # Indirect call site 2 and 3 (fp_baz1() and fp_baz2()) + + INDIRECT CALL SITES (CALLER_ADDR [CALL_SITE_ADDR,]) + 401000 401012 # _init + 401150 401181 401191 4011a1 # main calls fp_bar(), fp_baz1(), fp_baz2() + 4011d0 401215 # __libc_csu_init + 401020 40104a # _start + + DIRECT CALL SITES (CALLER_ADDR [(CALL_SITE_ADDR, TARGET_ADDR),]) + 4010d0 4010e2 401060 # __do_global_dtors_aux + 401150 4011a6 401110 4011ab 401120 4011ba 401130 # main calls foo(), bar(), baz() + 4011d0 4011fd 401000 # __libc_csu_init + + FUNCTIONS (FUNC_ENTRY_ADDR, SYM_NAME) + 401000 _init + 401100 frame_dummy + 401234 _fini + 401050 _dl_relocate_static_pie + 401090 register_tm_clones + 4010d0 __do_global_dtors_aux + 401110 _ZN12_GLOBAL__N_13fooEv # (anonymous namespace)::foo() + 401150 main # main + 4011d0 __libc_csu_init + 401020 _start + 401060 deregister_tm_clones + 401120 _Z3barv # bar() + 401130 _Z3bazcPf # baz(char, float*) + 401230 __libc_csu_fini diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def index 4cf22c4..a3ee5ace 100644 --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -78,6 +78,8 @@ CODEGENOPT(EnableNoundefAttrs, 1, 0) ///< Enable emitting `noundef` attributes o CODEGENOPT(DebugPassManager, 1, 0) ///< Prints debug information for the new ///< pass manager. CODEGENOPT(DisableRedZone , 1, 0) ///< Set when -mno-red-zone is enabled. +CODEGENOPT(CallGraphSection, 1, 0) ///< Emit a call graph section into the + ///< object file. CODEGENOPT(EmitCallSiteInfo, 1, 0) ///< Emit call site info only in the case of ///< '-g' + 'O>0' level. CODEGENOPT(IndirectTlsSegRefs, 1, 0) ///< Set when -mno-tls-direct-seg-refs diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index f2f9c20..77c78ea 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -4291,6 +4291,10 @@ defm data_sections : BoolFOption<"data-sections", PosFlag<SetTrue, [], [ClangOption, CC1Option], "Place each data in its own section">, NegFlag<SetFalse>>; +defm call_graph_section : BoolFOption<"call-graph-section", + CodeGenOpts<"CallGraphSection">, DefaultFalse, + PosFlag<SetTrue, [], [CC1Option], "Emit a call graph section">, + NegFlag<SetFalse>>; defm stack_size_section : BoolFOption<"stack-size-section", CodeGenOpts<"StackSizeSection">, DefaultFalse, PosFlag<SetTrue, [], [ClangOption, CC1Option], diff --git a/clang/lib/CodeGen/BackendUtil.cpp b/clang/lib/CodeGen/BackendUtil.cpp index bf9b04f..4b81d22 100644 --- a/clang/lib/CodeGen/BackendUtil.cpp +++ b/clang/lib/CodeGen/BackendUtil.cpp @@ -455,6 +455,7 @@ static bool initTargetOptions(DiagnosticsEngine &Diags, Options.StackUsageOutput = CodeGenOpts.StackUsageOutput; Options.EmitAddrsig = CodeGenOpts.Addrsig; Options.ForceDwarfFrameSection = CodeGenOpts.ForceDwarfFrameSection; + Options.EmitCallGraphSection = CodeGenOpts.CallGraphSection; Options.EmitCallSiteInfo = CodeGenOpts.EmitCallSiteInfo; Options.EnableAIXExtendedAltivecABI = LangOpts.EnableAIXExtendedAltivecABI; Options.XRayFunctionIndex = CodeGenOpts.XRayFunctionIndex; diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp index 35d495d..e59dc3f 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -25,6 +25,7 @@ #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" +#include "clang/AST/Type.h" #include "clang/Basic/CodeGenOptions.h" #include "clang/Basic/TargetInfo.h" #include "clang/CodeGen/CGFunctionInfo.h" @@ -37,6 +38,7 @@ #include "llvm/IR/CallingConv.h" #include "llvm/IR/DataLayout.h" #include "llvm/IR/InlineAsm.h" +#include "llvm/IR/Instruction.h" #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Intrinsics.h" #include "llvm/IR/Type.h" @@ -5077,6 +5079,11 @@ static unsigned getMaxVectorWidth(const llvm::Type *Ty) { return MaxVectorWidth; } +static bool isCXXDeclType(const FunctionDecl *FD) { + return isa<CXXConstructorDecl>(FD) || isa<CXXMethodDecl>(FD) || + isa<CXXDestructorDecl>(FD); +} + RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo, const CGCallee &Callee, ReturnValueSlot ReturnValue, @@ -5765,6 +5772,25 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo, AllocAlignAttrEmitter AllocAlignAttrEmitter(*this, TargetDecl, CallArgs); Attrs = AllocAlignAttrEmitter.TryEmitAsCallSiteAttribute(Attrs); + if (CGM.getCodeGenOpts().CallGraphSection) { + assert((TargetDecl && TargetDecl->getFunctionType() || + Callee.getAbstractInfo().getCalleeFunctionProtoType()) && + "cannot find callsite type"); + QualType CST; + if (TargetDecl && TargetDecl->getFunctionType()) + CST = QualType(TargetDecl->getFunctionType(), 0); + else if (const auto *FPT = + Callee.getAbstractInfo().getCalleeFunctionProtoType()) + CST = QualType(FPT, 0); + + if (!CST.isNull()) { + auto *TypeIdMD = CGM.CreateMetadataIdentifierGeneralized(CST); + auto *TypeIdMDVal = + llvm::MetadataAsValue::get(getLLVMContext(), TypeIdMD); + BundleList.emplace_back("type", TypeIdMDVal); + } + } + // Emit the actual call/invoke instruction. llvm::CallBase *CI; if (!InvokeDest) { @@ -5779,8 +5805,18 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo, CI->getCalledFunction()->getName().starts_with("_Z4sqrt")) { SetSqrtFPAccuracy(CI); } - if (callOrInvoke) + if (callOrInvoke) { *callOrInvoke = CI; + if (CGM.getCodeGenOpts().CallGraphSection) { + // Set type identifier metadata of indirect calls for call graph section. + if (const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(TargetDecl)) { + // Type id metadata is set only for C/C++ contexts. + if (isCXXDeclType(FD)) { + CGM.CreateFunctionTypeMetadataForIcall(FD->getType(), *callOrInvoke); + } + } + } + } // If this is within a function that has the guard(nocf) attribute and is an // indirect call, add the "guard_nocf" attribute to this call to indicate that diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index 232cac2..b066b18 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -6172,7 +6172,6 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType, } if (CallOrInvoke) *CallOrInvoke = LocalCallOrInvoke; - return Call; } diff --git a/clang/lib/CodeGen/CGObjCMac.cpp b/clang/lib/CodeGen/CGObjCMac.cpp index 7b85dcc..248fb2e 100644 --- a/clang/lib/CodeGen/CGObjCMac.cpp +++ b/clang/lib/CodeGen/CGObjCMac.cpp @@ -2214,9 +2214,8 @@ CGObjCCommonMac::EmitMessageSend(CodeGen::CodeGenFunction &CGF, llvm::CallBase *CallSite; CGCallee Callee = CGCallee::forDirect(BitcastFn); - RValue rvalue = CGF.EmitCall(MSI.CallInfo, Callee, Return, ActualArgs, - &CallSite); - + RValue rvalue = + CGF.EmitCall(MSI.CallInfo, Callee, Return, ActualArgs, &CallSite); // Mark the call as noreturn if the method is marked noreturn and the // receiver cannot be null. if (Method && Method->hasAttr<NoReturnAttr>() && !ReceiverCanBeNull) { diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index b854eeb..56a831b 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -2622,8 +2622,9 @@ void CodeGenModule::SetLLVMFunctionAttributesForDefinition(const Decl *D, // In the cross-dso CFI mode with canonical jump tables, we want !type // attributes on definitions only. - if (CodeGenOpts.SanitizeCfiCrossDso && - CodeGenOpts.SanitizeCfiCanonicalJumpTables) { + if ((CodeGenOpts.SanitizeCfiCrossDso && + CodeGenOpts.SanitizeCfiCanonicalJumpTables) || + CodeGenOpts.CallGraphSection) { if (auto *FD = dyn_cast<FunctionDecl>(D)) { // Skip available_externally functions. They won't be codegen'ed in the // current module anyway. @@ -2813,7 +2814,17 @@ static void setLinkageForGV(llvm::GlobalValue *GV, const NamedDecl *ND) { void CodeGenModule::CreateFunctionTypeMetadataForIcall(const FunctionDecl *FD, llvm::Function *F) { - // Only if we are checking indirect calls. + bool EmittedMDIdGeneralized = false; + if (CodeGenOpts.CallGraphSection && + (!F->hasLocalLinkage() || + F->getFunction().hasAddressTaken(nullptr, /*IgnoreCallbackUses=*/true, + /*IgnoreAssumeLikeCalls=*/true, + /*IgnoreLLVMUsed=*/false))) { + F->addTypeMetadata(0, CreateMetadataIdentifierGeneralized(FD->getType())); + EmittedMDIdGeneralized = true; + } + + // Add additional metadata only if we are checking indirect calls with CFI. if (!LangOpts.Sanitize.has(SanitizerKind::CFIICall)) return; @@ -2824,7 +2835,9 @@ void CodeGenModule::CreateFunctionTypeMetadataForIcall(const FunctionDecl *FD, llvm::Metadata *MD = CreateMetadataIdentifierForType(FD->getType()); F->addTypeMetadata(0, MD); - F->addTypeMetadata(0, CreateMetadataIdentifierGeneralized(FD->getType())); + // Add the generalized identifier if not added already. + if (!EmittedMDIdGeneralized) + F->addTypeMetadata(0, CreateMetadataIdentifierGeneralized(FD->getType())); // Emit a hash-based bit set entry for cross-DSO calls. if (CodeGenOpts.SanitizeCfiCrossDso) @@ -2832,6 +2845,17 @@ void CodeGenModule::CreateFunctionTypeMetadataForIcall(const FunctionDecl *FD, F->addTypeMetadata(0, llvm::ConstantAsMetadata::get(CrossDsoTypeId)); } +void CodeGenModule::CreateFunctionTypeMetadataForIcall(const QualType &QT, + llvm::CallBase *CB) { + // Only if needed for call graph section and only for indirect calls. + if (!CodeGenOpts.CallGraphSection || !CB || !CB->isIndirectCall()) + return; + + auto *MD = CreateMetadataIdentifierGeneralized(QT); + auto *MDN = llvm::MDNode::get(getLLVMContext(), MD); + CB->setMetadata(llvm::LLVMContext::MD_type, MDN); +} + void CodeGenModule::setKCFIType(const FunctionDecl *FD, llvm::Function *F) { llvm::LLVMContext &Ctx = F->getContext(); llvm::MDBuilder MDB(Ctx); @@ -2959,7 +2983,8 @@ void CodeGenModule::SetFunctionAttributes(GlobalDecl GD, llvm::Function *F, // are non-canonical then we need type metadata in order to produce the local // jump table. if (!CodeGenOpts.SanitizeCfiCrossDso || - !CodeGenOpts.SanitizeCfiCanonicalJumpTables) + !CodeGenOpts.SanitizeCfiCanonicalJumpTables || + CodeGenOpts.CallGraphSection) CreateFunctionTypeMetadataForIcall(FD, F); if (LangOpts.Sanitize.has(SanitizerKind::KCFI)) diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h index 741b0f1..bbaf5b1 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -1572,6 +1572,10 @@ public: void CreateFunctionTypeMetadataForIcall(const FunctionDecl *FD, llvm::Function *F); + /// Create and attach type metadata to the given call. + void CreateFunctionTypeMetadataForIcall(const QualType &QT, + llvm::CallBase *CB); + /// Set type metadata to the given function. void setKCFIType(const FunctionDecl *FD, llvm::Function *F); diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index d3eec9f..d749f0f 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -6609,6 +6609,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, CmdArgs.push_back(A->getValue()); } + if (Args.hasFlag(options::OPT_fcall_graph_section, + options::OPT_fno_call_graph_section, false)) + CmdArgs.push_back("-fcall-graph-section"); + Args.addOptInFlag(CmdArgs, options::OPT_fstack_size_section, options::OPT_fno_stack_size_section); diff --git a/clang/test/CodeGen/call-graph-section-1.cpp b/clang/test/CodeGen/call-graph-section-1.cpp new file mode 100644 index 0000000..5bc11fd --- /dev/null +++ b/clang/test/CodeGen/call-graph-section-1.cpp @@ -0,0 +1,110 @@ +// Tests that we assign appropriate identifiers to indirect calls and targets +// specifically for C++ class and instance methods. + +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fcall-graph-section \ +// RUN: -emit-llvm -o %t %s +// RUN: FileCheck --check-prefix=FT %s < %t +// RUN: FileCheck --check-prefix=CST %s < %t + +//////////////////////////////////////////////////////////////////////////////// +// Class definitions (check for indirect target metadata) + +class Cls1 { +public: + // FT-DAG: define {{.*}} ptr @_ZN4Cls18receiverEPcPf({{.*}} !type [[F_TCLS1RECEIVER:![0-9]+]] + static int *receiver(char *a, float *b) { return 0; } +}; + +class Cls2 { +public: + int *(*fp)(char *, float *); + + // FT-DAG: define {{.*}} i32 @_ZN4Cls22f1Ecfd({{.*}} !type [[F_TCLS2F1:![0-9]+]] + int f1(char a, float b, double c) { return 0; } + + // FT-DAG: define {{.*}} ptr @_ZN4Cls22f2EPcPfPd({{.*}} !type [[F_TCLS2F2:![0-9]+]] + int *f2(char *a, float *b, double *c) { return 0; } + + // FT-DAG: define {{.*}} void @_ZN4Cls22f3E4Cls1({{.*}} !type [[F_TCLS2F3F4:![0-9]+]] + void f3(Cls1 a) {} + + // FT-DAG: define {{.*}} void @_ZN4Cls22f4E4Cls1({{.*}} !type [[F_TCLS2F3F4]] + void f4(const Cls1 a) {} + + // FT-DAG: define {{.*}} void @_ZN4Cls22f5EP4Cls1({{.*}} !type [[F_TCLS2F5:![0-9]+]] + void f5(Cls1 *a) {} + + // FT-DAG: define {{.*}} void @_ZN4Cls22f6EPK4Cls1({{.*}} !type [[F_TCLS2F6:![0-9]+]] + void f6(const Cls1 *a) {} + + // FT-DAG: define {{.*}} void @_ZN4Cls22f7ER4Cls1({{.*}} !type [[F_TCLS2F7:![0-9]+]] + void f7(Cls1 &a) {} + + // FT-DAG: define {{.*}} void @_ZN4Cls22f8ERK4Cls1({{.*}} !type [[F_TCLS2F8:![0-9]+]] + void f8(const Cls1 &a) {} + + // FT-DAG: define {{.*}} void @_ZNK4Cls22f9Ev({{.*}} !type [[F_TCLS2F9:![0-9]+]] + void f9() const {} +}; + +// FT-DAG: [[F_TCLS1RECEIVER]] = !{i64 0, !"_ZTSFPvS_S_E.generalized"} +// FT-DAG: [[F_TCLS2F2]] = !{i64 0, !"_ZTSFPvS_S_S_E.generalized"} +// FT-DAG: [[F_TCLS2F1]] = !{i64 0, !"_ZTSFicfdE.generalized"} +// FT-DAG: [[F_TCLS2F3F4]] = !{i64 0, !"_ZTSFv4Cls1E.generalized"} +// FT-DAG: [[F_TCLS2F5]] = !{i64 0, !"_ZTSFvPvE.generalized"} +// FT-DAG: [[F_TCLS2F6]] = !{i64 0, !"_ZTSFvPKvE.generalized"} +// FT-DAG: [[F_TCLS2F7]] = !{i64 0, !"_ZTSFvR4Cls1E.generalized"} +// FT-DAG: [[F_TCLS2F8]] = !{i64 0, !"_ZTSFvRK4Cls1E.generalized"} +// FT-DAG: [[F_TCLS2F9]] = !{i64 0, !"_ZTSKFvvE.generalized"} + +//////////////////////////////////////////////////////////////////////////////// +// Callsites (check for indirect callsite operand bundles) + +// CST-LABEL: define {{.*}} @_Z3foov +void foo() { + Cls2 ObjCls2; + ObjCls2.fp = &Cls1::receiver; + + // CST: call noundef ptr %{{.*}} [ "type"(metadata !"_ZTSFPvS_S_E.generalized") ] + ObjCls2.fp(0, 0); + + auto fp_f1 = &Cls2::f1; + auto fp_f2 = &Cls2::f2; + auto fp_f3 = &Cls2::f3; + auto fp_f4 = &Cls2::f4; + auto fp_f5 = &Cls2::f5; + auto fp_f6 = &Cls2::f6; + auto fp_f7 = &Cls2::f7; + auto fp_f8 = &Cls2::f8; + auto fp_f9 = &Cls2::f9; + + Cls2 *ObjCls2Ptr = &ObjCls2; + Cls1 Cls1Param; + + // CST: call noundef i32 %{{.*}} [ "type"(metadata !"_ZTSFicfdE.generalized") ] + (ObjCls2Ptr->*fp_f1)(0, 0, 0); + + // CST: call noundef ptr %{{.*}} [ "type"(metadata !"_ZTSFPvS_S_S_E.generalized") ] + (ObjCls2Ptr->*fp_f2)(0, 0, 0); + + // CST: call void %{{.*}} [ "type"(metadata !"_ZTSFv4Cls1E.generalized") ] + (ObjCls2Ptr->*fp_f3)(Cls1Param); + + // CST: call void %{{.*}} [ "type"(metadata !"_ZTSFv4Cls1E.generalized") ] + (ObjCls2Ptr->*fp_f4)(Cls1Param); + + // CST: call void %{{.*}} [ "type"(metadata !"_ZTSFvPvE.generalized") ] + (ObjCls2Ptr->*fp_f5)(&Cls1Param); + + // CST: call void %{{.*}} [ "type"(metadata !"_ZTSFvPKvE.generalized") ] + (ObjCls2Ptr->*fp_f6)(&Cls1Param); + + // CST: call void %{{.*}} [ "type"(metadata !"_ZTSFvR4Cls1E.generalized") ] + (ObjCls2Ptr->*fp_f7)(Cls1Param); + + // CST: call void %{{.*}} [ "type"(metadata !"_ZTSFvRK4Cls1E.generalized") ] + (ObjCls2Ptr->*fp_f8)(Cls1Param); + + // CST: call void %{{.*}} [ "type"(metadata !"_ZTSKFvvE.generalized") ] + (ObjCls2Ptr->*fp_f9)(); +} diff --git a/clang/test/CodeGen/call-graph-section-2.cpp b/clang/test/CodeGen/call-graph-section-2.cpp new file mode 100644 index 0000000..1a5e69e --- /dev/null +++ b/clang/test/CodeGen/call-graph-section-2.cpp @@ -0,0 +1,95 @@ +// Tests that we assign appropriate identifiers to indirect calls and targets +// specifically for C++ templates. + +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fcall-graph-section \ +// RUN: -emit-llvm -o %t %s +// RUN: FileCheck --check-prefix=FT %s < %t +// RUN: FileCheck --check-prefix=CST %s < %t +// RUN: FileCheck --check-prefix=CHECK %s < %t + +//////////////////////////////////////////////////////////////////////////////// +// Class definitions and template classes (check for indirect target metadata) + +class Cls1 {}; + +// Cls2 is instantiated with T=Cls1 in foo(). Following checks are for this +// instantiation. +template <class T> +class Cls2 { +public: + // FT: define {{.*}} void @_ZN4Cls2I4Cls1E2f1Ev({{.*}} !type [[F_TCLS2F1:![0-9]+]] + void f1() {} + + // FT: define {{.*}} void @_ZN4Cls2I4Cls1E2f2ES0_({{.*}} !type [[F_TCLS2F2:![0-9]+]] + void f2(T a) {} + + // FT: define {{.*}} void @_ZN4Cls2I4Cls1E2f3EPS0_({{.*}} !type [[F_TCLS2F3:![0-9]+]] + void f3(T *a) {} + + // FT: define {{.*}} void @_ZN4Cls2I4Cls1E2f4EPKS0_({{.*}} !type [[F_TCLS2F4:![0-9]+]] + void f4(const T *a) {} + + // FT: define {{.*}} void @_ZN4Cls2I4Cls1E2f5ERS0_({{.*}} !type [[F_TCLS2F5:![0-9]+]] + void f5(T &a) {} + + // FT: define {{.*}} void @_ZN4Cls2I4Cls1E2f6ERKS0_({{.*}} !type [[F_TCLS2F6:![0-9]+]] + void f6(const T &a) {} + + // Mixed type function pointer member + T *(*fp)(T a, T *b, const T *c, T &d, const T &e); +}; + +// FT-DAG: [[F_TCLS2F1]] = !{i64 0, !"_ZTSFvvE.generalized"} +// FT-DAG: [[F_TCLS2F2]] = !{i64 0, !"_ZTSFv4Cls1E.generalized"} +// FT-DAG: [[F_TCLS2F3]] = !{i64 0, !"_ZTSFvPvE.generalized"} +// FT-DAG: [[F_TCLS2F4]] = !{i64 0, !"_ZTSFvPKvE.generalized"} +// FT-DAG: [[F_TCLS2F5]] = !{i64 0, !"_ZTSFvR4Cls1E.generalized"} +// FT-DAG: [[F_TCLS2F6]] = !{i64 0, !"_ZTSFvRK4Cls1E.generalized"} + +//////////////////////////////////////////////////////////////////////////////// +// Callsites (check for indirect callsite operand bundles) + +template <class T> +T *T_func(T a, T *b, const T *c, T &d, const T &e) { return b; } + +// CST-LABEL: define {{.*}} @_Z3foov +void foo() { + // Methods for Cls2<Cls1> is checked above within the template description. + Cls2<Cls1> Obj; + + // CHECK-DAG: define {{.*}} @_Z6T_funcI4Cls1EPT_S1_S2_PKS1_RS1_RS3_({{.*}} !type [[F_TFUNC_CLS1:![0-9]+]] + // CHECK-DAG: [[F_TFUNC_CLS1]] = !{i64 0, !"_ZTSFPv4Cls1S_PKvRS0_RKS0_E.generalized"} + Obj.fp = T_func<Cls1>; + Cls1 Cls1Obj; + + // CST: call noundef ptr %{{.*}} [ "type"(metadata !"_ZTSFPv4Cls1S_PKvRS0_RKS0_E.generalized") ] + Obj.fp(Cls1Obj, &Cls1Obj, &Cls1Obj, Cls1Obj, Cls1Obj); + + // Make indirect calls to Cls2's member methods + auto fp_f1 = &Cls2<Cls1>::f1; + auto fp_f2 = &Cls2<Cls1>::f2; + auto fp_f3 = &Cls2<Cls1>::f3; + auto fp_f4 = &Cls2<Cls1>::f4; + auto fp_f5 = &Cls2<Cls1>::f5; + auto fp_f6 = &Cls2<Cls1>::f6; + + auto *Obj2Ptr = &Obj; + + // CST: call void %{{.*}} [ "type"(metadata !"_ZTSFvvE.generalized") ] + (Obj2Ptr->*fp_f1)(); + + // CST: call void %{{.*}} [ "type"(metadata !"_ZTSFv4Cls1E.generalized") ] + (Obj2Ptr->*fp_f2)(Cls1Obj); + + // CST: call void %{{.*}} [ "type"(metadata !"_ZTSFvPvE.generalized") ] + (Obj2Ptr->*fp_f3)(&Cls1Obj); + + // CST: call void %{{.*}} [ "type"(metadata !"_ZTSFvPKvE.generalized") ] + (Obj2Ptr->*fp_f4)(&Cls1Obj); + + // CST: call void %{{.*}} [ "type"(metadata !"_ZTSFvR4Cls1E.generalized") ] + (Obj2Ptr->*fp_f5)(Cls1Obj); + + // CST: call void %{{.*}} [ "type"(metadata !"_ZTSFvRK4Cls1E.generalized") ] + (Obj2Ptr->*fp_f6)(Cls1Obj); +} diff --git a/clang/test/CodeGen/call-graph-section-3.cpp b/clang/test/CodeGen/call-graph-section-3.cpp new file mode 100644 index 0000000..997e0f9 --- /dev/null +++ b/clang/test/CodeGen/call-graph-section-3.cpp @@ -0,0 +1,52 @@ +// Tests that we assign appropriate identifiers to indirect calls and targets +// specifically for virtual methods. + +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fcall-graph-section \ +// RUN: -emit-llvm -o %t %s +// RUN: FileCheck --check-prefix=FT %s < %t +// RUN: FileCheck --check-prefix=CST %s < %t + +//////////////////////////////////////////////////////////////////////////////// +// Class definitions (check for indirect target metadata) + +class Base { +public: + // FT-DAG: define {{.*}} @_ZN4Base2vfEPc({{.*}} !type [[F_TVF:![0-9]+]] + virtual int vf(char *a) { return 0; }; +}; + +class Derived : public Base { +public: + // FT-DAG: define {{.*}} @_ZN7Derived2vfEPc({{.*}} !type [[F_TVF]] + int vf(char *a) override { return 1; }; +}; + +// FT-DAG: [[F_TVF]] = !{i64 0, !"_ZTSFiPvE.generalized"} + +//////////////////////////////////////////////////////////////////////////////// +// Callsites (check for indirect callsite operand bundles) + +// CST-LABEL: define {{.*}} @_Z3foov +void foo() { + auto B = Base(); + auto D = Derived(); + + Base *Bptr = &B; + Base *BptrToD = &D; + Derived *Dptr = &D; + + auto FpBaseVf = &Base::vf; + auto FpDerivedVf = &Derived::vf; + + // CST: call noundef i32 %{{.*}} [ "type"(metadata !"_ZTSFiPvE.generalized") ] + (Bptr->*FpBaseVf)(0); + + // CST: call noundef i32 %{{.*}} [ "type"(metadata !"_ZTSFiPvE.generalized") ] + (BptrToD->*FpBaseVf)(0); + + // CST: call noundef i32 %{{.*}} [ "type"(metadata !"_ZTSFiPvE.generalized") ] + (Dptr->*FpBaseVf)(0); + + // CST: call noundef i32 %{{.*}} [ "type"(metadata !"_ZTSFiPvE.generalized") ] + (Dptr->*FpDerivedVf)(0); +} diff --git a/clang/test/CodeGen/call-graph-section.c b/clang/test/CodeGen/call-graph-section.c new file mode 100644 index 0000000..d971d1f --- /dev/null +++ b/clang/test/CodeGen/call-graph-section.c @@ -0,0 +1,85 @@ +// Tests that we assign appropriate identifiers to indirect calls and targets. + +// RUN: %clang_cc1 -triple x86_64-unknown-linux -fcall-graph-section \ +// RUN: -emit-llvm -o - %s | FileCheck --check-prefixes=CHECK,ITANIUM %s + +// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc -fcall-graph-section \ +// RUN: -emit-llvm -o - %s | FileCheck --check-prefixes=CHECK,MS %s + +// CHECK-DAG: define {{(dso_local)?}} void @foo({{.*}} !type [[F_TVOID:![0-9]+]] +void foo() { +} + +// CHECK-DAG: define {{(dso_local)?}} void @bar({{.*}} !type [[F_TVOID]] +void bar() { + void (*fp)() = foo; + // ITANIUM: call {{.*}} [ "type"(metadata !"_ZTSFvE.generalized") ] + // MS: call {{.*}} [ "type"(metadata !"?6AX@Z.generalized") ] + fp(); +} + +// CHECK-DAG: define {{(dso_local)?}} i32 @baz({{.*}} !type [[F_TPRIMITIVE:![0-9]+]] +int baz(char a, float b, double c) { + return 1; +} + +// CHECK-DAG: define {{(dso_local)?}} ptr @qux({{.*}} !type [[F_TPTR:![0-9]+]] +int *qux(char *a, float *b, double *c) { + return 0; +} + +// CHECK-DAG: define {{(dso_local)?}} void @corge({{.*}} !type [[F_TVOID]] +void corge() { + int (*fp_baz)(char, float, double) = baz; + // ITANIUM: call i32 {{.*}} [ "type"(metadata !"_ZTSFicfdE.generalized") ] + // MS: call i32 {{.*}} [ "type"(metadata !"?6AHDMN@Z.generalized") ] + fp_baz('a', .0f, .0); + + int *(*fp_qux)(char *, float *, double *) = qux; + // ITANIUM: call ptr {{.*}} [ "type"(metadata !"_ZTSFPvS_S_S_E.generalized") ] + // MS: call ptr {{.*}} [ "type"(metadata !"?6APEAXPEAX00@Z.generalized") ] + fp_qux(0, 0, 0); +} + +struct st1 { + int *(*fp)(char *, float *, double *); +}; + +struct st2 { + struct st1 m; +}; + +// CHECK-DAG: define {{(dso_local)?}} void @stparam({{.*}} !type [[F_TSTRUCT:![0-9]+]] +void stparam(struct st2 a, struct st2 *b) {} + +// CHECK-DAG: define {{(dso_local)?}} void @stf({{.*}} !type [[F_TVOID]] +void stf() { + struct st1 St1; + St1.fp = qux; + // ITANIUM: call ptr {{.*}} [ "type"(metadata !"_ZTSFPvS_S_S_E.generalized") ] + // MS: call ptr {{.*}} [ "type"(metadata !"?6APEAXPEAX00@Z.generalized") ] + St1.fp(0, 0, 0); + + struct st2 St2; + St2.m.fp = qux; + // ITANIUM: call ptr {{.*}} [ "type"(metadata !"_ZTSFPvS_S_S_E.generalized") ] + // MS: call ptr {{.*}} [ "type"(metadata !"?6APEAXPEAX00@Z.generalized") ] + St2.m.fp(0, 0, 0); + + // ITANIUM: call void {{.*}} [ "type"(metadata !"_ZTSFv3st2PvE.generalized") ] + // MS: call void {{.*}} [ "type"(metadata !"?6AXUst2@@PEAX@Z.generalized") ] + void (*fp_stparam)(struct st2, struct st2 *) = stparam; + fp_stparam(St2, &St2); +} + +// ITANIUM-DAG: [[F_TVOID]] = !{i64 0, !"_ZTSFvE.generalized"} +// MS-DAG: [[F_TVOID]] = !{i64 0, !"?6AX@Z.generalized"} + +// ITANIUM-DAG: [[F_TPRIMITIVE]] = !{i64 0, !"_ZTSFicfdE.generalized"} +// MS-DAG: [[F_TPRIMITIVE]] = !{i64 0, !"?6AHDMN@Z.generalized"} + +// ITANIUM-DAG: [[F_TPTR]] = !{i64 0, !"_ZTSFPvS_S_S_E.generalized"} +// MS-DAG: [[F_TPTR]] = !{i64 0, !"?6APEAXPEAX00@Z.generalized"} + +// ITANIUM-DAG: [[F_TSTRUCT]] = !{i64 0, !"_ZTSFv3st2PvE.generalized"} +// MS-DAG: [[F_TSTRUCT]] = !{i64 0, !"?6AXUst2@@PEAX@Z.generalized"}
\ No newline at end of file diff --git a/clang/test/Driver/call-graph-section.c b/clang/test/Driver/call-graph-section.c new file mode 100644 index 0000000..5832aa6 --- /dev/null +++ b/clang/test/Driver/call-graph-section.c @@ -0,0 +1,5 @@ +// RUN: %clang -### -S -fcall-graph-section %s 2>&1 | FileCheck --check-prefix=CALL-GRAPH-SECTION %s +// RUN: %clang -### -S -fcall-graph-section -fno-call-graph-section %s 2>&1 | FileCheck --check-prefix=NO-CALL-GRAPH-SECTION %s + +// CALL-GRAPH-SECTION: "-fcall-graph-section" +// NO-CALL-GRAPH-SECTION-NOT: "-fcall-graph-section" diff --git a/llvm/include/llvm/CodeGen/AsmPrinter.h b/llvm/include/llvm/CodeGen/AsmPrinter.h index c9a88d7..76bbcaa 100644 --- a/llvm/include/llvm/CodeGen/AsmPrinter.h +++ b/llvm/include/llvm/CodeGen/AsmPrinter.h @@ -173,6 +173,32 @@ private: /// Emit comments in assembly output if this is true. bool VerboseAsm; + /// Store symbols and type identifiers used to create call graph section + /// entries related to a function. + struct FunctionInfo { + /// Numeric type identifier used in call graph section for indirect calls + /// and targets. + using CGTypeId = uint64_t; + + /// Enumeration of function kinds, and their mapping to function kind values + /// stored in call graph section entries. + /// Must match the enum in llvm/tools/llvm-objdump/llvm-objdump.cpp. + enum FunctionKind { + /// Function cannot be target to indirect calls. + NOT_INDIRECT_TARGET = 0, + + /// Function may be target to indirect calls but its type id is unknown. + INDIRECT_TARGET_UNKNOWN_TID = 1, + + /// Function may be target to indirect calls and its type id is known. + INDIRECT_TARGET_KNOWN_TID = 2, + }; + + /// Map type identifiers to callsite labels. Labels are only for indirect + /// calls and inclusive of all indirect calls of the function. + SmallVector<std::pair<CGTypeId, MCSymbol *>> CallSiteLabels; + }; + /// Output stream for the stack usage file (i.e., .su file). std::unique_ptr<raw_fd_ostream> StackUsageStream; @@ -414,6 +440,8 @@ public: void emitKCFITrapEntry(const MachineFunction &MF, const MCSymbol *Symbol); virtual void emitKCFITypeId(const MachineFunction &MF); + void emitCallGraphSection(const MachineFunction &MF, FunctionInfo &FuncInfo); + void emitPseudoProbe(const MachineInstr &MI); void emitRemarksSection(remarks::RemarkStreamer &RS); diff --git a/llvm/include/llvm/CodeGen/CommandFlags.h b/llvm/include/llvm/CodeGen/CommandFlags.h index d5448d7..eb93192 100644 --- a/llvm/include/llvm/CodeGen/CommandFlags.h +++ b/llvm/include/llvm/CodeGen/CommandFlags.h @@ -132,6 +132,8 @@ bool getEnableStackSizeSection(); bool getEnableAddrsig(); +bool getEnableCallGraphSection(); + bool getEmitCallSiteInfo(); bool getEnableMachineFunctionSplitter(); diff --git a/llvm/include/llvm/CodeGen/MIRYamlMapping.h b/llvm/include/llvm/CodeGen/MIRYamlMapping.h index 09a6ca9..fe35c82 100644 --- a/llvm/include/llvm/CodeGen/MIRYamlMapping.h +++ b/llvm/include/llvm/CodeGen/MIRYamlMapping.h @@ -483,6 +483,10 @@ struct CallSiteInfo { MachineInstrLoc CallLocation; std::vector<ArgRegPair> ArgForwardingRegs; + /// Numeric callee type identifier used for call graph section. + using TypeIdTy = std::optional<uint64_t>; + TypeIdTy TypeId; + bool operator==(const CallSiteInfo &Other) const { return CallLocation.BlockNum == Other.CallLocation.BlockNum && CallLocation.Offset == Other.CallLocation.Offset; @@ -511,6 +515,7 @@ template <> struct MappingTraits<CallSiteInfo> { YamlIO.mapRequired("offset", CSInfo.CallLocation.Offset); YamlIO.mapOptional("fwdArgRegs", CSInfo.ArgForwardingRegs, std::vector<CallSiteInfo::ArgRegPair>()); + YamlIO.mapOptional("typeId", CSInfo.TypeId); } static const bool flow = true; diff --git a/llvm/include/llvm/CodeGen/MachineFunction.h b/llvm/include/llvm/CodeGen/MachineFunction.h index bb75d5a..28b73c5 100644 --- a/llvm/include/llvm/CodeGen/MachineFunction.h +++ b/llvm/include/llvm/CodeGen/MachineFunction.h @@ -26,11 +26,14 @@ #include "llvm/CodeGen/MachineBasicBlock.h" #include "llvm/CodeGen/MachineInstr.h" #include "llvm/CodeGen/MachineMemOperand.h" +#include "llvm/IR/Constants.h" #include "llvm/IR/EHPersonalities.h" +#include "llvm/IR/Instructions.h" #include "llvm/Support/Allocator.h" #include "llvm/Support/ArrayRecycler.h" #include "llvm/Support/AtomicOrdering.h" #include "llvm/Support/Compiler.h" +#include "llvm/Support/MD5.h" #include "llvm/Support/Recycler.h" #include "llvm/Target/TargetOptions.h" #include <bitset> @@ -486,6 +489,40 @@ public: struct CallSiteInfo { /// Vector of call argument and its forwarding register. SmallVector<ArgRegPair, 1> ArgRegPairs; + + /// Callee type id. + ConstantInt *TypeId = nullptr; + + CallSiteInfo() = default; + + /// Extracts the numeric type id from the CallBase's type operand bundle, + /// and sets TypeId. This is used as type id for the indirect call in the + /// call graph section. + CallSiteInfo(const CallBase &CB) { + // Call graph section needs numeric type id only for indirect calls. + if (!CB.isIndirectCall()) + return; + + std::optional<OperandBundleUse> Opt = + CB.getOperandBundle(LLVMContext::OB_type); + // Return if the operand bundle for call graph section cannot be found. + if (!Opt) + return; + + // Get generalized type id string + auto OB = *Opt; + assert(OB.Inputs.size() == 1 && "invalid input size"); + auto *OBVal = OB.Inputs.front().get(); + auto *TypeIdMD = cast<MetadataAsValue>(OBVal)->getMetadata(); + auto *TypeIdStr = cast<MDString>(TypeIdMD); + assert(TypeIdStr->getString().ends_with(".generalized") && + "invalid type identifier"); + + // Compute numeric type id from generalized type id string + uint64_t TypeIdVal = MD5Hash(TypeIdStr->getString()); + IntegerType *Int64Ty = Type::getInt64Ty(CB.getContext()); + TypeId = ConstantInt::get(Int64Ty, TypeIdVal, /*IsSigned=*/false); + } }; private: diff --git a/llvm/include/llvm/IR/LLVMContext.h b/llvm/include/llvm/IR/LLVMContext.h index 6d4a59b..0190f56 100644 --- a/llvm/include/llvm/IR/LLVMContext.h +++ b/llvm/include/llvm/IR/LLVMContext.h @@ -96,6 +96,7 @@ public: OB_ptrauth = 7, // "ptrauth" OB_kcfi = 8, // "kcfi" OB_convergencectrl = 9, // "convergencectrl" + OB_type = 10, // "type" }; /// getMDKindID - Return a unique non-zero ID for the specified metadata kind. diff --git a/llvm/include/llvm/MC/MCObjectFileInfo.h b/llvm/include/llvm/MC/MCObjectFileInfo.h index e2a2c84..cdf7550 100644 --- a/llvm/include/llvm/MC/MCObjectFileInfo.h +++ b/llvm/include/llvm/MC/MCObjectFileInfo.h @@ -68,6 +68,9 @@ protected: /// Language Specific Data Area information is emitted to. MCSection *LSDASection = nullptr; + /// Section containing metadata on call graph. + MCSection *CallGraphSection = nullptr; + /// If exception handling is supported by the target and the target can /// support a compact representation of the CIE and FDE, this is the section /// to emit them into. @@ -355,6 +358,8 @@ public: MCSection *getFaultMapSection() const { return FaultMapSection; } MCSection *getRemarksSection() const { return RemarksSection; } + MCSection *getCallGraphSection(const MCSection &TextSec) const; + MCSection *getStackSizesSection(const MCSection &TextSec) const; MCSection *getBBAddrMapSection(const MCSection &TextSec) const; diff --git a/llvm/include/llvm/Target/TargetOptions.h b/llvm/include/llvm/Target/TargetOptions.h index 88f2538..68270d5 100644 --- a/llvm/include/llvm/Target/TargetOptions.h +++ b/llvm/include/llvm/Target/TargetOptions.h @@ -146,10 +146,11 @@ namespace llvm { EmulatedTLS(false), EnableTLSDESC(false), EnableIPRA(false), EmitStackSizeSection(false), EnableMachineOutliner(false), EnableMachineFunctionSplitter(false), SupportsDefaultOutlining(false), - EmitAddrsig(false), BBAddrMap(false), EmitCallSiteInfo(false), - SupportsDebugEntryValues(false), EnableDebugEntryValues(false), - ValueTrackingVariableLocations(false), ForceDwarfFrameSection(false), - XRayFunctionIndex(true), DebugStrictDwarf(false), Hotpatch(false), + EmitAddrsig(false), BBAddrMap(false), EmitCallGraphSection(false), + EmitCallSiteInfo(false), SupportsDebugEntryValues(false), + EnableDebugEntryValues(false), ValueTrackingVariableLocations(false), + ForceDwarfFrameSection(false), XRayFunctionIndex(true), + DebugStrictDwarf(false), Hotpatch(false), PPCGenScalarMASSEntries(false), JMCInstrument(false), EnableCFIFixup(false), MisExpect(false), XCOFFReadOnlyPointers(false), VerifyArgABICompliance(true), @@ -329,6 +330,9 @@ namespace llvm { /// to selectively generate basic block sections. std::shared_ptr<MemoryBuffer> BBSectionsFuncListBuf; + /// Emit section containing call graph metadata. + unsigned EmitCallGraphSection : 1; + /// The flag enables call site info production. It is used only for debug /// info, and it is restricted only to optimized code. This can be used for /// something else, so that should be controlled in the frontend. diff --git a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp index 030d0e1..f439783 100644 --- a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp +++ b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp @@ -1629,6 +1629,102 @@ void AsmPrinter::emitStackUsage(const MachineFunction &MF) { *StackUsageStream << "static\n"; } +/// Extracts a generalized numeric type identifier of a Function's type from +/// type metadata. Returns null if metadata cannot be found. +static ConstantInt *extractNumericCGTypeId(const Function &F) { + SmallVector<MDNode *, 2> Types; + F.getMetadata(LLVMContext::MD_type, Types); + MDString *MDGeneralizedTypeId = nullptr; + for (const auto &Type : Types) { + if (Type->getNumOperands() == 2 && isa<MDString>(Type->getOperand(1))) { + auto *TMDS = cast<MDString>(Type->getOperand(1)); + if (TMDS->getString().ends_with("generalized")) { + MDGeneralizedTypeId = TMDS; + break; + } + } + } + + if (!MDGeneralizedTypeId) + return nullptr; + + uint64_t TypeIdVal = llvm::MD5Hash(MDGeneralizedTypeId->getString()); + Type *Int64Ty = Type::getInt64Ty(F.getContext()); + return cast<ConstantInt>(ConstantInt::get(Int64Ty, TypeIdVal)); +} + +/// Emits call graph section. +void AsmPrinter::emitCallGraphSection(const MachineFunction &MF, + FunctionInfo &FuncInfo) { + if (!MF.getTarget().Options.EmitCallGraphSection) + return; + + // Switch to the call graph section for the function + MCSection *FuncCGSection = + getObjFileLowering().getCallGraphSection(*getCurrentSection()); + assert(FuncCGSection && "null call graph section"); + OutStreamer->pushSection(); + OutStreamer->switchSection(FuncCGSection); + + // Emit format version number. + OutStreamer->emitInt64(0); + + // Emit function's self information, which is composed of: + // 1) FunctionEntryPc + // 2) FunctionKind: Whether the function is indirect target, and if so, + // whether its type id is known. + // 3) FunctionTypeId: Emit only when the function is an indirect target + // and its type id is known. + + // Emit function entry pc. + const MCSymbol *FunctionSymbol = getFunctionBegin(); + OutStreamer->emitSymbolValue(FunctionSymbol, TM.getProgramPointerSize()); + + // If this function has external linkage or has its address taken and + // it is not a callback, then anything could call it. + const Function &F = MF.getFunction(); + bool IsIndirectTarget = + !F.hasLocalLinkage() || F.hasAddressTaken(nullptr, + /*IgnoreCallbackUses=*/true, + /*IgnoreAssumeLikeCalls=*/true, + /*IgnoreLLVMUsed=*/false); + + // FIXME: FunctionKind takes a few values but emitted as a 64-bit value. + // Can be optimized to occupy 2 bits instead. + // Emit function kind, and type id if available. + if (!IsIndirectTarget) { + OutStreamer->emitInt64(FunctionInfo::FunctionKind::NOT_INDIRECT_TARGET); + } else { + const auto *TypeId = extractNumericCGTypeId(F); + if (TypeId) { + OutStreamer->emitInt64( + FunctionInfo::FunctionKind::INDIRECT_TARGET_KNOWN_TID); + OutStreamer->emitInt64(TypeId->getZExtValue()); + } else { + OutStreamer->emitInt64( + FunctionInfo::FunctionKind::INDIRECT_TARGET_UNKNOWN_TID); + } + } + + // Emit callsite labels, where each element is a pair of type id and + // indirect callsite pc. + const auto &CallSiteLabels = FuncInfo.CallSiteLabels; + + // Emit the count of pairs. + OutStreamer->emitInt64(CallSiteLabels.size()); + + // Emit the type id and call site label pairs. + for (const std::pair<uint64_t, MCSymbol *> &El : CallSiteLabels) { + auto TypeId = El.first; + const auto &Label = El.second; + OutStreamer->emitInt64(TypeId); + OutStreamer->emitSymbolValue(Label, TM.getProgramPointerSize()); + } + FuncInfo.CallSiteLabels.clear(); + + OutStreamer->popSection(); +} + void AsmPrinter::emitPCSectionsLabel(const MachineFunction &MF, const MDNode &MD) { MCSymbol *S = MF.getContext().createTempSymbol("pcsection"); @@ -1805,6 +1901,8 @@ void AsmPrinter::emitFunctionBody() { MBBSectionRanges[MF->front().getSectionID()] = MBBSectionRange{CurrentFnBegin, nullptr}; + FunctionInfo FuncInfo; + const auto &CallSitesInfoMap = MF->getCallSitesInfo(); for (auto &MBB : *MF) { // Print a label for the basic block. emitBasicBlockStart(MBB); @@ -1935,6 +2033,26 @@ void AsmPrinter::emitFunctionBody() { break; } + // FIXME: Some indirect calls can get lowered to jump instructions, + // resulting in emitting labels for them. The extra information can + // be neglected while disassembling but still takes space in the binary. + if (TM.Options.EmitCallGraphSection && MI.isCall()) { + // Only indirect calls have type identifiers set. + const auto &CallSiteInfo = CallSitesInfoMap.find(&MI); + if (CallSiteInfo != CallSitesInfoMap.end()) { + if (auto *TypeId = CallSiteInfo->second.TypeId) { + // Emit label. + MCSymbol *S = MF->getContext().createTempSymbol(); + OutStreamer->emitLabel(S); + + // Get type id value. + uint64_t TypeIdVal = TypeId->getZExtValue(); + + // Add to function's callsite labels. + FuncInfo.CallSiteLabels.emplace_back(TypeIdVal, S); + } + } + } // If there is a post-instruction symbol, emit a label for it here. if (MCSymbol *S = MI.getPostInstrSymbol()) OutStreamer->emitLabel(S); @@ -2112,6 +2230,9 @@ void AsmPrinter::emitFunctionBody() { // Emit section containing stack size metadata. emitStackSizeSection(*MF); + // Emit section containing call graph metadata. + emitCallGraphSection(*MF, FuncInfo); + // Emit .su file containing function stack size information. emitStackUsage(*MF); @@ -2697,6 +2818,7 @@ void AsmPrinter::SetupMachineFunction(MachineFunction &MF) { F.hasFnAttribute("xray-instruction-threshold") || needFuncLabels(MF, *this) || NeedsLocalForSize || MF.getTarget().Options.EmitStackSizeSection || + MF.getTarget().Options.EmitCallGraphSection || MF.getTarget().Options.BBAddrMap) { CurrentFnBegin = createTempSymbol("func_begin"); if (NeedsLocalForSize) diff --git a/llvm/lib/CodeGen/CommandFlags.cpp b/llvm/lib/CodeGen/CommandFlags.cpp index d180cfc..c2a7a0d 100644 --- a/llvm/lib/CodeGen/CommandFlags.cpp +++ b/llvm/lib/CodeGen/CommandFlags.cpp @@ -101,6 +101,7 @@ CGOPT(EABI, EABIVersion) CGOPT(DebuggerKind, DebuggerTuningOpt) CGOPT(bool, EnableStackSizeSection) CGOPT(bool, EnableAddrsig) +CGOPT(bool, EnableCallGraphSection) CGOPT(bool, EmitCallSiteInfo) CGOPT(bool, EnableMachineFunctionSplitter) CGOPT(bool, EnableDebugEntryValues) @@ -460,6 +461,11 @@ codegen::RegisterCodeGenFlags::RegisterCodeGenFlags() { cl::init(false)); CGBINDOPT(EnableAddrsig); + static cl::opt<bool> EnableCallGraphSection( + "call-graph-section", cl::desc("Emit a call graph section"), + cl::init(false)); + CGBINDOPT(EnableCallGraphSection); + static cl::opt<bool> EmitCallSiteInfo( "emit-call-site-info", cl::desc( @@ -587,6 +593,7 @@ codegen::InitTargetOptionsFromCodeGenFlags(const Triple &TheTriple) { Options.EmitStackSizeSection = getEnableStackSizeSection(); Options.EnableMachineFunctionSplitter = getEnableMachineFunctionSplitter(); Options.EmitAddrsig = getEnableAddrsig(); + Options.EmitCallGraphSection = getEnableCallGraphSection(); Options.EmitCallSiteInfo = getEmitCallSiteInfo(); Options.EnableDebugEntryValues = getEnableDebugEntryValues(); Options.ForceDwarfFrameSection = getForceDwarfFrameSection(); diff --git a/llvm/lib/CodeGen/MIRParser/MIRParser.cpp b/llvm/lib/CodeGen/MIRParser/MIRParser.cpp index e2543f8..0913154 100644 --- a/llvm/lib/CodeGen/MIRParser/MIRParser.cpp +++ b/llvm/lib/CodeGen/MIRParser/MIRParser.cpp @@ -488,12 +488,18 @@ bool MIRParserImpl::initializeCallSiteInfo( return error(Error, ArgRegPair.Reg.SourceRange); CSInfo.ArgRegPairs.emplace_back(Reg, ArgRegPair.ArgNo); } + if (YamlCSInfo.TypeId.has_value()) { + IntegerType *Int64Ty = Type::getInt64Ty(Context); + CSInfo.TypeId = ConstantInt::get(Int64Ty, YamlCSInfo.TypeId.value(), + /*isSigned=*/false); + } - if (TM.Options.EmitCallSiteInfo) + if (TM.Options.EmitCallSiteInfo || TM.Options.EmitCallGraphSection) MF.addCallSiteInfo(&*CallI, std::move(CSInfo)); } - if (YamlMF.CallSitesInfo.size() && !TM.Options.EmitCallSiteInfo) + if (YamlMF.CallSitesInfo.size() && + !(TM.Options.EmitCallSiteInfo || TM.Options.EmitCallGraphSection)) return error(Twine("Call site info provided but not used")); return false; } diff --git a/llvm/lib/CodeGen/MIRPrinter.cpp b/llvm/lib/CodeGen/MIRPrinter.cpp index 658bbe0..f5f48b8 100644 --- a/llvm/lib/CodeGen/MIRPrinter.cpp +++ b/llvm/lib/CodeGen/MIRPrinter.cpp @@ -571,6 +571,9 @@ void MIRPrinter::convertCallSiteObjects(yaml::MachineFunction &YMF, printRegMIR(ArgReg.Reg, YmlArgReg.Reg, TRI); YmlCS.ArgForwardingRegs.emplace_back(YmlArgReg); } + // Get type id. + if (CSInfo.second.TypeId) + YmlCS.TypeId = CSInfo.second.TypeId->getZExtValue(); YMF.CallSitesInfo.push_back(YmlCS); } diff --git a/llvm/lib/CodeGen/MachineFunction.cpp b/llvm/lib/CodeGen/MachineFunction.cpp index a293a77..c5e76994 100644 --- a/llvm/lib/CodeGen/MachineFunction.cpp +++ b/llvm/lib/CodeGen/MachineFunction.cpp @@ -913,7 +913,7 @@ MachineFunction::getCallSiteInfo(const MachineInstr *MI) { assert(MI->isCandidateForCallSiteEntry() && "Call site info refers only to call (MI) candidates"); - if (!Target.Options.EmitCallSiteInfo) + if (!Target.Options.EmitCallSiteInfo && !Target.Options.EmitCallGraphSection) return CallSitesInfo.end(); return CallSitesInfo.find(MI); } diff --git a/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGSDNodes.cpp b/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGSDNodes.cpp index 31939ae..250f98c 100644 --- a/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGSDNodes.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/ScheduleDAGSDNodes.cpp @@ -889,7 +889,8 @@ EmitSchedule(MachineBasicBlock::iterator &InsertPos) { } if (MI->isCandidateForCallSiteEntry() && - DAG->getTarget().Options.EmitCallSiteInfo) { + (DAG->getTarget().Options.EmitCallSiteInfo || + DAG->getTarget().Options.EmitCallGraphSection)) { MF.addCallSiteInfo(MI, DAG->getCallSiteInfo(Node)); } diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp index 9d729d4..dd6b77c 100644 --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp @@ -3326,7 +3326,7 @@ void SelectionDAGBuilder::visitInvoke(const InvokeInst &I) { {LLVMContext::OB_deopt, LLVMContext::OB_gc_transition, LLVMContext::OB_gc_live, LLVMContext::OB_funclet, LLVMContext::OB_cfguardtarget, LLVMContext::OB_ptrauth, - LLVMContext::OB_clang_arc_attachedcall}) && + LLVMContext::OB_clang_arc_attachedcall, LLVMContext::OB_type}) && "Cannot lower invokes with arbitrary operand bundles yet!"); const Value *Callee(I.getCalledOperand()); @@ -3415,8 +3415,9 @@ void SelectionDAGBuilder::visitCallBr(const CallBrInst &I) { // Deopt bundles are lowered in LowerCallSiteWithDeoptBundle, and we don't // have to do anything here to lower funclet bundles. - assert(!I.hasOperandBundlesOtherThan( - {LLVMContext::OB_deopt, LLVMContext::OB_funclet}) && + assert(!I.hasOperandBundlesOtherThan({LLVMContext::OB_deopt, + LLVMContext::OB_funclet, + LLVMContext::OB_type}) && "Cannot lower callbrs with arbitrary operand bundles yet!"); assert(I.isInlineAsm() && "Only know how to handle inlineasm callbr"); @@ -9599,7 +9600,7 @@ void SelectionDAGBuilder::visitCall(const CallInst &I) { {LLVMContext::OB_deopt, LLVMContext::OB_funclet, LLVMContext::OB_cfguardtarget, LLVMContext::OB_preallocated, LLVMContext::OB_clang_arc_attachedcall, LLVMContext::OB_kcfi, - LLVMContext::OB_convergencectrl}) && + LLVMContext::OB_convergencectrl, LLVMContext::OB_type}) && "Cannot lower calls with arbitrary operand bundles!"); SDValue Callee = getValue(I.getCalledOperand()); diff --git a/llvm/lib/IR/LLVMContext.cpp b/llvm/lib/IR/LLVMContext.cpp index e078527..20b3008 100644 --- a/llvm/lib/IR/LLVMContext.cpp +++ b/llvm/lib/IR/LLVMContext.cpp @@ -97,6 +97,11 @@ LLVMContext::LLVMContext() : pImpl(new LLVMContextImpl(*this)) { "convergencectrl operand bundle id drifted!"); (void)ConvergenceCtrlEntry; + auto *TypeEntry = pImpl->getOrInsertBundleTag("type"); + assert(TypeEntry->second == LLVMContext::OB_type && + "type operand bundle id drifted!"); + (void)TypeEntry; + SyncScope::ID SingleThreadSSID = pImpl->getOrInsertSyncScopeID("singlethread"); assert(SingleThreadSSID == SyncScope::SingleThread && diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp index 5c0ccf7..b72672e 100644 --- a/llvm/lib/IR/Verifier.cpp +++ b/llvm/lib/IR/Verifier.cpp @@ -3707,14 +3707,14 @@ void Verifier::visitCallBase(CallBase &Call) { if (Intrinsic::ID ID = (Intrinsic::ID)F->getIntrinsicID()) visitIntrinsicCall(ID, Call); - // Verify that a callsite has at most one "deopt", at most one "funclet", at - // most one "gc-transition", at most one "cfguardtarget", at most one - // "preallocated" operand bundle, and at most one "ptrauth" operand bundle. + // Verify that a callsite has at most one operand bundle for each of the + // following: "deopt", "funclet", "gc-transition", "cfguardtarget", "type", + // "preallocated", and "ptrauth". bool FoundDeoptBundle = false, FoundFuncletBundle = false, FoundGCTransitionBundle = false, FoundCFGuardTargetBundle = false, FoundPreallocatedBundle = false, FoundGCLiveBundle = false, FoundPtrauthBundle = false, FoundKCFIBundle = false, - FoundAttachedCallBundle = false; + FoundAttachedCallBundle = false, FoundTypeBundle = false; for (unsigned i = 0, e = Call.getNumOperandBundles(); i < e; ++i) { OperandBundleUse BU = Call.getOperandBundleAt(i); uint32_t Tag = BU.getTagID(); @@ -3777,6 +3777,9 @@ void Verifier::visitCallBase(CallBase &Call) { "Multiple \"clang.arc.attachedcall\" operand bundles", Call); FoundAttachedCallBundle = true; verifyAttachedCallBundle(Call, BU); + } else if (Tag == LLVMContext::OB_type) { + Check(!FoundTypeBundle, "Multiple \"type\" operand bundles", Call); + FoundTypeBundle = true; } } diff --git a/llvm/lib/MC/MCObjectFileInfo.cpp b/llvm/lib/MC/MCObjectFileInfo.cpp index f37e138..eb019f2 100644 --- a/llvm/lib/MC/MCObjectFileInfo.cpp +++ b/llvm/lib/MC/MCObjectFileInfo.cpp @@ -536,6 +536,8 @@ void MCObjectFileInfo::initELFMCObjectFileInfo(const Triple &T, bool Large) { EHFrameSection = Ctx->getELFSection(".eh_frame", EHSectionType, EHSectionFlags); + CallGraphSection = Ctx->getELFSection(".callgraph", ELF::SHT_PROGBITS, 0); + StackSizesSection = Ctx->getELFSection(".stack_sizes", ELF::SHT_PROGBITS, 0); PseudoProbeSection = Ctx->getELFSection(".pseudo_probe", DebugSecType, 0); @@ -1077,6 +1079,24 @@ MCSection *MCObjectFileInfo::getDwarfComdatSection(const char *Name, } MCSection * +MCObjectFileInfo::getCallGraphSection(const MCSection &TextSec) const { + if (Ctx->getObjectFileType() != MCContext::IsELF) + return CallGraphSection; + + const MCSectionELF &ElfSec = static_cast<const MCSectionELF &>(TextSec); + unsigned Flags = ELF::SHF_LINK_ORDER; + StringRef GroupName; + if (const MCSymbol *Group = ElfSec.getGroup()) { + GroupName = Group->getName(); + Flags |= ELF::SHF_GROUP; + } + + return Ctx->getELFSection(".callgraph", ELF::SHT_PROGBITS, Flags, 0, + GroupName, true, ElfSec.getUniqueID(), + cast<MCSymbolELF>(TextSec.getBeginSymbol())); +} + +MCSection * MCObjectFileInfo::getStackSizesSection(const MCSection &TextSec) const { if ((Ctx->getObjectFileType() != MCContext::IsELF) || Ctx->getTargetTriple().isPS4()) diff --git a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp index ad1d123..0c5a838 100644 --- a/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp +++ b/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp @@ -8739,6 +8739,7 @@ AArch64TargetLowering::LowerCall(CallLoweringInfo &CLI, bool &IsTailCall = CLI.IsTailCall; CallingConv::ID &CallConv = CLI.CallConv; bool IsVarArg = CLI.IsVarArg; + const auto *CB = CLI.CB; MachineFunction &MF = DAG.getMachineFunction(); MachineFunction::CallSiteInfo CSInfo; @@ -8778,6 +8779,10 @@ AArch64TargetLowering::LowerCall(CallLoweringInfo &CLI, *DAG.getContext()); RetCCInfo.AnalyzeCallResult(Ins, RetCC); + // Set type id for call site info. + if (MF.getTarget().Options.EmitCallGraphSection && CB && CB->isIndirectCall()) + CSInfo = MachineFunction::CallSiteInfo(*CB); + // Check callee args/returns for SVE registers and set calling convention // accordingly. if (CallConv == CallingConv::C || CallConv == CallingConv::Fast) { diff --git a/llvm/lib/Target/ARM/ARMISelLowering.cpp b/llvm/lib/Target/ARM/ARMISelLowering.cpp index 84b37ae..ec8e600 100644 --- a/llvm/lib/Target/ARM/ARMISelLowering.cpp +++ b/llvm/lib/Target/ARM/ARMISelLowering.cpp @@ -2431,6 +2431,7 @@ ARMTargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI, CallingConv::ID CallConv = CLI.CallConv; bool doesNotRet = CLI.DoesNotReturn; bool isVarArg = CLI.IsVarArg; + const auto *CB = CLI.CB; MachineFunction &MF = DAG.getMachineFunction(); ARMFunctionInfo *AFI = MF.getInfo<ARMFunctionInfo>(); @@ -2454,6 +2455,10 @@ ARMTargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI, !Subtarget->noBTIAtReturnTwice()) GuardWithBTI = AFI->branchTargetEnforcement(); + // Set type id for call site info. + if (MF.getTarget().Options.EmitCallGraphSection && CB && CB->isIndirectCall()) + CSInfo = MachineFunction::CallSiteInfo(*CB); + // Determine whether this is a non-secure function call. if (CLI.CB && CLI.CB->getAttributes().hasFnAttr("cmse_nonsecure_call")) isCmseNSCall = true; diff --git a/llvm/lib/Target/Mips/MipsISelLowering.cpp b/llvm/lib/Target/Mips/MipsISelLowering.cpp index d903481..922441c 100644 --- a/llvm/lib/Target/Mips/MipsISelLowering.cpp +++ b/llvm/lib/Target/Mips/MipsISelLowering.cpp @@ -3250,6 +3250,7 @@ MipsTargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI, bool &IsTailCall = CLI.IsTailCall; CallingConv::ID CallConv = CLI.CallConv; bool IsVarArg = CLI.IsVarArg; + const auto *CB = CLI.CB; MachineFunction &MF = DAG.getMachineFunction(); MachineFrameInfo &MFI = MF.getFrameInfo(); @@ -3306,8 +3307,11 @@ MipsTargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI, // Get a count of how many bytes are to be pushed on the stack. unsigned StackSize = CCInfo.getStackSize(); - // Call site info for function parameters tracking. + // Call site info for function parameters tracking and call base type info. MachineFunction::CallSiteInfo CSInfo; + // Set type id for call site info. + if (MF.getTarget().Options.EmitCallGraphSection && CB && CB->isIndirectCall()) + CSInfo = MachineFunction::CallSiteInfo(*CB); // Check if it's really possible to do a tail call. Restrict it to functions // that are part of this compilation unit. diff --git a/llvm/lib/Target/X86/X86FastISel.cpp b/llvm/lib/Target/X86/X86FastISel.cpp index 039b892..da2e1f6c 100644 --- a/llvm/lib/Target/X86/X86FastISel.cpp +++ b/llvm/lib/Target/X86/X86FastISel.cpp @@ -3631,6 +3631,12 @@ bool X86FastISel::fastLowerCall(CallLoweringInfo &CLI) { CLI.NumResultRegs = RVLocs.size(); CLI.Call = MIB; + // Add call site info for call graph section. + if (TM.Options.EmitCallGraphSection && CB && CB->isIndirectCall()) { + MachineFunction::CallSiteInfo CSInfo(*CB); + MF->addCallSiteInfo(CLI.Call, std::move(CSInfo)); + } + return true; } @@ -4026,6 +4032,8 @@ bool X86FastISel::tryToFoldLoadIntoMI(MachineInstr *MI, unsigned OpNo, MO.setReg(IndexReg); } + if (MI->isCall()) + FuncInfo.MF->moveCallSiteInfo(MI, Result); Result->addMemOperand(*FuncInfo.MF, createMachineMemOperandFor(LI)); Result->cloneInstrSymbols(*FuncInfo.MF, *MI); MachineBasicBlock::iterator I(MI); diff --git a/llvm/lib/Target/X86/X86ISelLoweringCall.cpp b/llvm/lib/Target/X86/X86ISelLoweringCall.cpp index 8a764de..4dbea3b 100644 --- a/llvm/lib/Target/X86/X86ISelLoweringCall.cpp +++ b/llvm/lib/Target/X86/X86ISelLoweringCall.cpp @@ -2027,6 +2027,10 @@ X86TargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI, if (CallConv == CallingConv::X86_INTR) report_fatal_error("X86 interrupts may not be called directly"); + // Set type id for call site info. + if (MF.getTarget().Options.EmitCallGraphSection && CB && CB->isIndirectCall()) + CSInfo = MachineFunction::CallSiteInfo(*CB); + // Analyze operands of the call, assigning locations to each operand. SmallVector<CCValAssign, 16> ArgLocs; CCState CCInfo(CallConv, isVarArg, MF, ArgLocs, *DAG.getContext()); diff --git a/llvm/test/Bitcode/operand-bundles-bc-analyzer.ll b/llvm/test/Bitcode/operand-bundles-bc-analyzer.ll index d860104..ca3f3a9 100644 --- a/llvm/test/Bitcode/operand-bundles-bc-analyzer.ll +++ b/llvm/test/Bitcode/operand-bundles-bc-analyzer.ll @@ -13,6 +13,7 @@ ; CHECK-NEXT: <OPERAND_BUNDLE_TAG ; CHECK-NEXT: <OPERAND_BUNDLE_TAG ; CHECK-NEXT: <OPERAND_BUNDLE_TAG +; CHECK-NEXT: <OPERAND_BUNDLE_TAG ; CHECK-NEXT: </OPERAND_BUNDLE_TAGS_BLOCK ; CHECK: <FUNCTION_BLOCK @@ -25,9 +26,9 @@ declare void @callee0() -define void @f0(i32* %ptr) { +define void @f0(ptr %ptr) { entry: - %l = load i32, i32* %ptr + %l = load i32, ptr %ptr %x = add i32 42, 1 call void @callee0() [ "foo"(i32 42, i64 100, i32 %x), "bar"(float 0.000000e+00, i64 100, i32 %l) ] ret void diff --git a/llvm/test/CodeGen/AArch64/call-site-info-typeid.ll b/llvm/test/CodeGen/AArch64/call-site-info-typeid.ll new file mode 100644 index 0000000..f3b98c2 --- /dev/null +++ b/llvm/test/CodeGen/AArch64/call-site-info-typeid.ll @@ -0,0 +1,29 @@ +;; Tests that call site type ids can be extracted and set from type operand +;; bundles. + +;; Verify the exact typeId value to ensure it is not garbage but the value +;; computed as the type id from the type operand bundle. +; RUN: llc --call-graph-section -mtriple aarch64-linux-gnu < %s -stop-before=finalize-isel -o - | FileCheck %s + +define dso_local void @foo(i8 signext %a) !type !3 { +entry: + ret void +} + +; CHECK: name: main +define dso_local i32 @main() !type !4 { +entry: + %retval = alloca i32, align 4 + %fp = alloca ptr, align 8 + store i32 0, ptr %retval, align 4 + store ptr @foo, ptr %fp, align 8 + %0 = load ptr, ptr %fp, align 8 + ; CHECK: callSites: + ; CHECK-NEXT: - { bb: {{.*}}, offset: {{.*}}, fwdArgRegs: [], typeId: + ; CHECK-NEXT: 7854600665770582568 } + call void %0(i8 signext 97) [ "type"(metadata !"_ZTSFvcE.generalized") ] + ret i32 0 +} + +!3 = !{i64 0, !"_ZTSFvcE.generalized"} +!4 = !{i64 0, !"_ZTSFiE.generalized"} diff --git a/llvm/test/CodeGen/ARM/call-site-info-typeid.ll b/llvm/test/CodeGen/ARM/call-site-info-typeid.ll new file mode 100644 index 0000000..9feeef9 --- /dev/null +++ b/llvm/test/CodeGen/ARM/call-site-info-typeid.ll @@ -0,0 +1,29 @@ +;; Tests that call site type ids can be extracted and set from type operand +;; bundles. + +;; Verify the exact typeId value to ensure it is not garbage but the value +;; computed as the type id from the type operand bundle. +; RUN: llc --call-graph-section -mtriple arm-linux-gnu < %s -stop-before=finalize-isel -o - | FileCheck %s + +define dso_local void @foo(i8 signext %a) !type !3 { +entry: + ret void +} + +; CHECK: name: main +define dso_local i32 @main() !type !4 { +entry: + %retval = alloca i32, align 4 + %fp = alloca ptr, align 8 + store i32 0, ptr %retval, align 4 + store ptr @foo, ptr %fp, align 8 + %0 = load ptr, ptr %fp, align 8 + ; CHECK: callSites: + ; CHECK-NEXT: - { bb: {{.*}}, offset: {{.*}}, fwdArgRegs: [], typeId: + ; CHECK-NEXT: 7854600665770582568 } + call void %0(i8 signext 97) [ "type"(metadata !"_ZTSFvcE.generalized") ] + ret i32 0 +} + +!3 = !{i64 0, !"_ZTSFvcE.generalized"} +!4 = !{i64 0, !"_ZTSFiE.generalized"} diff --git a/llvm/test/CodeGen/MIR/X86/call-site-info-typeid.ll b/llvm/test/CodeGen/MIR/X86/call-site-info-typeid.ll new file mode 100644 index 0000000..a8b2de8 --- /dev/null +++ b/llvm/test/CodeGen/MIR/X86/call-site-info-typeid.ll @@ -0,0 +1,102 @@ +;; Test MIR printer and parser for type id field in call site info. Test that +;; it works well with/without --emit-call-site-info. + +;; Multiplex --call-graph-section and -emit-call-site-info as both utilize +;; CallSiteInfo and callSites. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Test printer and parser with --call-graph-section only. + +;; Test printer. +;; Verify that fwdArgRegs is not set, typeId is set. +;; Verify the exact typeId value to ensure it is not garbage but the value +;; computed as the type id from the type operand bundle. +; RUN: llc --call-graph-section %s -stop-before=finalize-isel -o %t1.mir +; RUN: cat %t1.mir | FileCheck %s --check-prefix=PRINTER_CGS +; PRINTER_CGS: name: main +; PRINTER_CGS: callSites: +; PRINTER_CGS-NEXT: - { bb: {{.*}}, offset: {{.*}}, fwdArgRegs: [], typeId: +; PRINTER_CGS-NEXT: 7854600665770582568 } + + +;; Test parser. +;; Verify that we get the same result. +; RUN: llc --call-graph-section %t1.mir -run-pass=finalize-isel -o - \ +; RUN: | FileCheck %s --check-prefix=PARSER_CGS +; PARSER_CGS: name: main +; PARSER_CGS: callSites: +; PARSER_CGS-NEXT: - { bb: {{.*}}, offset: {{.*}}, fwdArgRegs: [], typeId: +; PARSER_CGS-NEXT: 7854600665770582568 } + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Test printer and parser with -emit-call-site-info only. + +;; Test printer. +;; Verify that fwdArgRegs is set, typeId is not set. +; RUN: llc -emit-call-site-info %s -stop-before=finalize-isel -o %t2.mir +; RUN: cat %t2.mir | FileCheck %s --check-prefix=PRINTER_CSI +; PRINTER_CSI: name: main +; PRINTER_CSI: callSites: +; PRINTER_CSI-NEXT: - { bb: {{.*}}, offset: {{.*}}, fwdArgRegs: +; PRINTER_CSI-NEXT: { arg: 0, reg: '$edi' } +; PRINTER_CSI-NOT: typeId: + + +;; Test parser. +;; Verify that we get the same result. +; RUN: llc -emit-call-site-info %t2.mir -run-pass=finalize-isel -o - \ +; RUN: | FileCheck %s --check-prefix=PARSER_CSI +; PARSER_CSI: name: main +; PARSER_CSI: callSites: +; PARSER_CSI-NEXT: - { bb: {{.*}}, offset: {{.*}}, fwdArgRegs: +; PARSER_CSI-NEXT: { arg: 0, reg: '$edi' } +; PARSER_CSI-NOT: typeId: + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Test printer and parser with both -emit-call-site-info and --call-graph-section. + +;; Test printer. +;; Verify both fwdArgRegs and typeId are set. +;; Verify the exact typeId value to ensure it is not garbage but the value +;; computed as the type id from the type operand bundle. +; RUN: llc --call-graph-section -emit-call-site-info %s -stop-before=finalize-isel -o %t2.mir +; RUN: cat %t2.mir | FileCheck %s --check-prefix=PRINTER_CGS_CSI +; PRINTER_CGS_CSI: name: main +; PRINTER_CGS_CSI: callSites: +; PRINTER_CGS_CSI-NEXT: - { bb: {{.*}}, offset: {{.*}}, fwdArgRegs: +; PRINTER_CGS_CSI-NEXT: { arg: 0, reg: '$edi' }, typeId: +; PRINTER_CGS_CSI-NEXT: 7854600665770582568 } + + +;; Test parser. +;; Verify that we get the same result. +; RUN: llc --call-graph-section -emit-call-site-info %t2.mir -run-pass=finalize-isel -o - \ +; RUN: | FileCheck %s --check-prefix=PARSER_CGS_CSI +; PARSER_CGS_CSI: name: main +; PARSER_CGS_CSI: callSites: +; PARSER_CGS_CSI-NEXT: - { bb: {{.*}}, offset: {{.*}}, fwdArgRegs: +; PARSER_CGS_CSI-NEXT: { arg: 0, reg: '$edi' }, typeId: +; PARSER_CGS_CSI-NEXT: 7854600665770582568 } + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local void @foo(i8 signext %a) !type !3 { +entry: + ret void +} + +; Function Attrs: noinline nounwind optnone uwtable +define dso_local i32 @main() !type !4 { +entry: + %retval = alloca i32, align 4 + %fp = alloca ptr, align 8 + store i32 0, ptr %retval, align 4 + store ptr @foo, ptr %fp, align 8 + %0 = load ptr, ptr %fp, align 8 + call void %0(i8 signext 97) [ "type"(metadata !"_ZTSFvcE.generalized") ] + ret i32 0 +} + +!3 = !{i64 0, !"_ZTSFvcE.generalized"} +!4 = !{i64 0, !"_ZTSFiE.generalized"} diff --git a/llvm/test/CodeGen/MIR/X86/call-site-info-typeid.mir b/llvm/test/CodeGen/MIR/X86/call-site-info-typeid.mir new file mode 100644 index 0000000..dd21246 --- /dev/null +++ b/llvm/test/CodeGen/MIR/X86/call-site-info-typeid.mir @@ -0,0 +1,63 @@ +# Test MIR printer and parser for type id field in callSites. It is used +# for propogating call site type identifiers to emit in the call graph section. + +# RUN: llc --call-graph-section %s -run-pass=none -o - | FileCheck %s +# CHECK: name: main +# CHECK: callSites: +# CHECK-NEXT: - { bb: {{.*}}, offset: {{.*}}, fwdArgRegs: [], typeId: +# CHECK-NEXT: 123456789 } + +--- | + define dso_local void @foo(i8 signext %a) { + entry: + ret void + } + + define dso_local i32 @main() { + entry: + %retval = alloca i32, align 4 + %fp = alloca ptr, align 8 + store i32 0, ptr %retval, align 4 + store ptr @foo, ptr %fp, align 8 + %0 = load ptr, ptr %fp, align 8 + call void %0(i8 signext 97) + ret i32 0 + } + +... +--- +name: foo +tracksRegLiveness: true +body: | + bb.0.entry: + RET 0 + +... +--- +name: main +tracksRegLiveness: true +stack: + - { id: 0, name: retval, type: default, offset: 0, size: 4, alignment: 4, + stack-id: default, callee-saved-register: '', callee-saved-restored: true, + debug-info-variable: '', debug-info-expression: '', debug-info-location: '' } + - { id: 1, name: fp, type: default, offset: 0, size: 8, alignment: 8, + stack-id: default, callee-saved-register: '', callee-saved-restored: true, + debug-info-variable: '', debug-info-expression: '', debug-info-location: '' } +callSites: + - { bb: 0, offset: 6, fwdArgRegs: [], typeId: + 123456789 } +body: | + bb.0.entry: + MOV32mi %stack.0.retval, 1, $noreg, 0, $noreg, 0 :: (store (s32) into %ir.retval) + MOV64mi32 %stack.1.fp, 1, $noreg, 0, $noreg, @foo :: (store (s64) into %ir.fp) + %0:gr64 = MOV32ri64 @foo + ADJCALLSTACKDOWN64 0, 0, 0, implicit-def dead $rsp, implicit-def dead $eflags, implicit-def dead $ssp, implicit $rsp, implicit $ssp + %1:gr32 = MOV32ri 97 + $edi = COPY %1 + CALL64r killed %0, csr_64, implicit $rsp, implicit $ssp, implicit $edi, implicit-def $rsp, implicit-def $ssp + ADJCALLSTACKUP64 0, 0, implicit-def dead $rsp, implicit-def dead $eflags, implicit-def dead $ssp, implicit $rsp, implicit $ssp + %2:gr32 = MOV32r0 implicit-def dead $eflags + $eax = COPY %2 + RET 0, $eax + +... diff --git a/llvm/test/CodeGen/Mips/call-site-info-typeid.ll b/llvm/test/CodeGen/Mips/call-site-info-typeid.ll new file mode 100644 index 0000000..91de6a8 --- /dev/null +++ b/llvm/test/CodeGen/Mips/call-site-info-typeid.ll @@ -0,0 +1,29 @@ +;; Tests that call site type ids can be extracted and set from type operand +;; bundles. + +;; Verify the exact typeId value to ensure it is not garbage but the value +;; computed as the type id from the type operand bundle. +; RUN: llc --call-graph-section -mtriple=mips-linux-gnu < %s -stop-before=finalize-isel -o - | FileCheck %s + +define dso_local void @foo(i8 signext %a) !type !3 { +entry: + ret void +} + +; CHECK: name: main +define dso_local i32 @main() !type !4 { +entry: + %retval = alloca i32, align 4 + %fp = alloca ptr, align 8 + store i32 0, ptr %retval, align 4 + store ptr @foo, ptr %fp, align 8 + %0 = load ptr, ptr %fp, align 8 + ; CHECK: callSites: + ; CHECK-NEXT: - { bb: {{.*}}, offset: {{.*}}, fwdArgRegs: [], typeId: + ; CHECK-NEXT: 7854600665770582568 } + call void %0(i8 signext 97) [ "type"(metadata !"_ZTSFvcE.generalized") ] + ret i32 0 +} + +!3 = !{i64 0, !"_ZTSFvcE.generalized"} +!4 = !{i64 0, !"_ZTSFiE.generalized"} diff --git a/llvm/test/CodeGen/X86/call-site-info-typeid.ll b/llvm/test/CodeGen/X86/call-site-info-typeid.ll new file mode 100644 index 0000000..6fbdce4 --- /dev/null +++ b/llvm/test/CodeGen/X86/call-site-info-typeid.ll @@ -0,0 +1,29 @@ +;; Tests that call site type ids can be extracted and set from type operand +;; bundles. + +;; Verify the exact typeId value to ensure it is not garbage but the value +;; computed as the type id from the type operand bundle. +; RUN: llc --call-graph-section -mtriple=x86_64-unknown-linux < %s -stop-before=finalize-isel -o - | FileCheck %s + +define dso_local void @foo(i8 signext %a) !type !3 { +entry: + ret void +} + +; CHECK: name: main +define dso_local i32 @main() !type !4 { +entry: + %retval = alloca i32, align 4 + %fp = alloca ptr, align 8 + store i32 0, ptr %retval, align 4 + store ptr @foo, ptr %fp, align 8 + %0 = load ptr, ptr %fp, align 8 + ; CHECK: callSites: + ; CHECK-NEXT: - { bb: {{.*}}, offset: {{.*}}, fwdArgRegs: [], typeId: + ; CHECK-NEXT: 7854600665770582568 } + call void %0(i8 signext 97) [ "type"(metadata !"_ZTSFvcE.generalized") ] + ret i32 0 +} + +!3 = !{i64 0, !"_ZTSFvcE.generalized"} +!4 = !{i64 0, !"_ZTSFiE.generalized"} diff --git a/llvm/test/CodeGen/call-graph-section.ll b/llvm/test/CodeGen/call-graph-section.ll new file mode 100644 index 0000000..eb10161 --- /dev/null +++ b/llvm/test/CodeGen/call-graph-section.ll @@ -0,0 +1,73 @@ +; Tests that we store the type identifiers in .callgraph section of the binary. + +; RUN: llc --call-graph-section -filetype=obj -o - < %s | \ +; RUN: llvm-readelf -x .callgraph - | FileCheck %s + +target triple = "x86_64-unknown-linux-gnu" + +define dso_local void @foo() #0 !type !4 { +entry: + ret void +} + +define dso_local i32 @bar(i8 signext %a) #0 !type !5 { +entry: + %a.addr = alloca i8, align 1 + store i8 %a, i8* %a.addr, align 1 + ret i32 0 +} + +define dso_local i32* @baz(i8* %a) #0 !type !6 { +entry: + %a.addr = alloca i8*, align 8 + store i8* %a, i8** %a.addr, align 8 + ret i32* null +} + +define dso_local i32 @main() #0 !type !7 { +entry: + %retval = alloca i32, align 4 + %fp_foo = alloca void (...)*, align 8 + %a = alloca i8, align 1 + %fp_bar = alloca i32 (i8)*, align 8 + %fp_baz = alloca i32* (i8*)*, align 8 + store i32 0, i32* %retval, align 4 + store void (...)* bitcast (void ()* @foo to void (...)*), void (...)** %fp_foo, align 8 + %0 = load void (...)*, void (...)** %fp_foo, align 8 + call void (...) %0() [ "type"(metadata !"_ZTSFvE.generalized") ] + store i32 (i8)* @bar, i32 (i8)** %fp_bar, align 8 + %1 = load i32 (i8)*, i32 (i8)** %fp_bar, align 8 + %2 = load i8, i8* %a, align 1 + %call = call i32 %1(i8 signext %2) [ "type"(metadata !"_ZTSFicE.generalized") ] + store i32* (i8*)* @baz, i32* (i8*)** %fp_baz, align 8 + %3 = load i32* (i8*)*, i32* (i8*)** %fp_baz, align 8 + %call1 = call i32* %3(i8* %a) [ "type"(metadata !"_ZTSFPvS_E.generalized") ] + call void @foo() [ "type"(metadata !"_ZTSFvE.generalized") ] + %4 = load i8, i8* %a, align 1 + %call2 = call i32 @bar(i8 signext %4) [ "type"(metadata !"_ZTSFicE.generalized") ] + %call3 = call i32* @baz(i8* %a) [ "type"(metadata !"_ZTSFPvS_E.generalized") ] + ret i32 0 +} + +attributes #0 = { noinline nounwind optnone uwtable "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" } + +!llvm.module.flags = !{!0, !1, !2} +!llvm.ident = !{!3} + +; Check that the numeric type id (md5 hash) for the below type ids are emitted +; to the callgraph section. + +; CHECK: Hex dump of section '.callgraph': + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{i32 7, !"uwtable", i32 1} +!2 = !{i32 7, !"frame-pointer", i32 2} +!3 = !{!"clang version 13.0.0 (git@github.com:llvm/llvm-project.git 6d35f403b91c2f2c604e23763f699d580370ca96)"} +; CHECK-DAG: 2444f731 f5eecb3e +!4 = !{i64 0, !"_ZTSFvE.generalized"} +; CHECK-DAG: 5486bc59 814b8e30 +!5 = !{i64 0, !"_ZTSFicE.generalized"} +; CHECK-DAG: 7ade6814 f897fd77 +!6 = !{i64 0, !"_ZTSFPvS_E.generalized"} +; CHECK-DAG: caaf769a 600968fa +!7 = !{i64 0, !"_ZTSFiE.generalized"} diff --git a/llvm/test/Verifier/operand-bundles.ll b/llvm/test/Verifier/operand-bundles.ll index db85b6a..575cafa 100644 --- a/llvm/test/Verifier/operand-bundles.ll +++ b/llvm/test/Verifier/operand-bundles.ll @@ -105,4 +105,17 @@ declare ptr @objc_retainAutoreleasedReturnValue(ptr) declare ptr @objc_unsafeClaimAutoreleasedReturnValue(ptr) declare void @llvm.assume(i1) +define void @f_type(ptr %ptr) { +; CHECK: Multiple "type" operand bundles +; CHECK-NEXT: call void @g() [ "type"(metadata !"_ZTSFvE.generalized"), "type"(metadata !"_ZTSFvE.generalized") ] +; CHECK-NOT: call void @g() [ "type"(metadata !"_ZTSFvE.generalized") ] + + entry: + %l = load i32, ptr %ptr, align 4 + call void @g() [ "type"(metadata !"_ZTSFvE.generalized"), "type"(metadata !"_ZTSFvE.generalized") ] + call void @g() [ "type"(metadata !"_ZTSFvE.generalized") ] + %x = add i32 42, 1 + ret void +} + attributes #0 = { noreturn } |