Skip to content

Conversation

Prabhuk
Copy link
Contributor

@Prabhuk Prabhuk commented Sep 19, 2025

Make .callgraph section's layout efficient in space. Document the layout
of the section.

Copy link

github-actions bot commented Sep 19, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@llvmbot
Copy link
Member

llvmbot commented Sep 19, 2025

@llvm/pr-subscribers-backend-x86

Author: Prabhu Rajasekaran (Prabhuk)

Changes

Make .callgraph section's layout efficient in space. Document the layout
of the section.


Full diff: https://github.com/llvm/llvm-project/pull/159866.diff

6 Files Affected:

  • (added) llvm/docs/CallGraphSection.md (+26)
  • (modified) llvm/include/llvm/CodeGen/AsmPrinter.h (+17-17)
  • (modified) llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp (+42-47)
  • (modified) llvm/test/CodeGen/X86/call-graph-section-assembly.ll (+3-3)
  • (modified) llvm/test/CodeGen/X86/call-graph-section-tailcall.ll (+3-1)
  • (modified) llvm/test/CodeGen/X86/call-graph-section.ll (+3-3)
diff --git a/llvm/docs/CallGraphSection.md b/llvm/docs/CallGraphSection.md
new file mode 100644
index 0000000000000..eb673b8db0bd5
--- /dev/null
+++ b/llvm/docs/CallGraphSection.md
@@ -0,0 +1,26 @@
+# .callgraph Section Layout
+
+The `.callgraph` section is used to store call graph information for each function, which can be used for post-link analyses and optimizations. The section contains a series of records, with each record corresponding to a single function.
+
+## Per Function Record Layout
+
+Each record in the `.callgraph` section has the following binary layout:
+
+| Field                        | Type          | Size (bits) | Description                                                                                             |
+| ---------------------------- | ------------- | ----------- | ------------------------------------------------------------------------------------------------------- |
+| Format Version               | `uint32_t`    | 32          | The version of the record format. The current version is 0.                                             |
+| Function Entry PC            | `uintptr_t`   | 32/64       | The address of the function's entry point.                                                              |
+| Function Kind                | `uint8_t`     | 8           | An enum indicating the function's properties (e.g., if it's an indirect call target).                   |
+| Function Type ID             | `uint64_t`    | 64          | The type ID of the function. This field is **only** present if `Function Kind` is `INDIRECT_TARGET_KNOWN_TID`. |
+| Number of Indirect Callsites | `uint32_t`    | 32          | The number of indirect call sites within the function.                                                  |
+| Indirect Callsites Array     | `Callsite[]`  | Variable    | An array of `Callsite` records, with a length of `Number of Indirect Callsites`.                        |
+
+
+### Indirect Callsite Record Layout
+
+Each record in the `Indirect Callsites Array` has the following layout:
+
+| Field             | Type        | Size (bits) | Description                               |
+| ----------------- | ----------- | ----------- | ----------------------------------------- |
+| Type ID           | `uint64_t`  | 64          | The type ID of the indirect call target.  |
+| Callsite PC       | `uintptr_t` | 32/64       | The address of the indirect call site.    |
diff --git a/llvm/include/llvm/CodeGen/AsmPrinter.h b/llvm/include/llvm/CodeGen/AsmPrinter.h
index 71317619098ad..0dc900c2b4de2 100644
--- a/llvm/include/llvm/CodeGen/AsmPrinter.h
+++ b/llvm/include/llvm/CodeGen/AsmPrinter.h
@@ -192,31 +192,30 @@ class LLVM_ABI AsmPrinter : public MachineFunctionPass {
 
   /// Store symbols and type identifiers used to create callgraph section
   /// entries related to a function.
-  struct FunctionInfo {
+  struct FunctionCallGraphInfo {
     /// Numeric type identifier used in callgraph section for indirect calls
     /// and targets.
     using CGTypeId = uint64_t;
 
+    /// Map type identifiers to callsite labels. Labels are generated for each
+    /// indirect callsite in the function.
+    SmallVector<std::pair<CGTypeId, MCSymbol *>> IndirectCallsites;
+  };
+
     /// Enumeration of function kinds, and their mapping to function kind values
     /// stored in callgraph section entries.
     /// Must match the enum in llvm/tools/llvm-objdump/llvm-objdump.cpp.
-    enum class FunctionKind : uint64_t {
-      /// Function cannot be target to indirect calls.
-      NOT_INDIRECT_TARGET = 0,
+  enum class FunctionKind : uint8_t {
+    /// 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 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 generated for each
-    /// indirect callsite in the function.
-    SmallVector<std::pair<CGTypeId, MCSymbol *>> CallSiteLabels;
+    /// Function may be target to indirect calls and its type id is known.
+    INDIRECT_TARGET_KNOWN_TID = 2,
   };
-
-  enum CallGraphSectionFormatVersion : uint64_t {
+  enum CallGraphSectionFormatVersion : uint32_t {
     V_0 = 0,
   };
 
@@ -388,7 +387,7 @@ class LLVM_ABI AsmPrinter : public MachineFunctionPass {
   /// Generate and emit labels for callees of the indirect callsites which will
   /// be used to populate the .callgraph section.
   void emitIndirectCalleeLabels(
-      FunctionInfo &FuncInfo,
+      FunctionCallGraphInfo &FuncCGInfo,
       const MachineFunction::CallSiteInfoMap &CallSitesInfoMap,
       const MachineInstr &MI);
 
@@ -479,7 +478,8 @@ class LLVM_ABI AsmPrinter : public MachineFunctionPass {
   void emitKCFITrapEntry(const MachineFunction &MF, const MCSymbol *Symbol);
   virtual void emitKCFITypeId(const MachineFunction &MF);
 
-  void emitCallGraphSection(const MachineFunction &MF, FunctionInfo &FuncInfo);
+  void emitCallGraphSection(const MachineFunction &MF,
+                            FunctionCallGraphInfo &FuncCGInfo);
 
   void emitPseudoProbe(const MachineInstr &MI);
 
diff --git a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
index cd14a4f57f760..32b0aca23033d 100644
--- a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
@@ -1672,7 +1672,7 @@ static ConstantInt *extractNumericCGTypeId(const Function &F) {
 
 /// Emits .callgraph section.
 void AsmPrinter::emitCallGraphSection(const MachineFunction &MF,
-                                      FunctionInfo &FuncInfo) {
+                                      FunctionCallGraphInfo &FuncCGInfo) {
   if (!MF.getTarget().Options.EmitCallGraphSection)
     return;
 
@@ -1683,55 +1683,50 @@ void AsmPrinter::emitCallGraphSection(const MachineFunction &MF,
   OutStreamer->pushSection();
   OutStreamer->switchSection(FuncCGSection);
 
-  // Emit format version number.
-  OutStreamer->emitInt64(CallGraphSectionFormatVersion::V_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());
+  auto EmitFunctionKindAndTypeId = [&]() {
+    const Function &F = MF.getFunction();
+    // If this function has external linkage or has its address taken and
+    // it is not a callback, then anything could call it.
+    bool IsIndirectTarget = !F.hasLocalLinkage() ||
+                            F.hasAddressTaken(nullptr,
+                                              /*IgnoreCallbackUses=*/true,
+                                              /*IgnoreAssumeLikeCalls=*/true,
+                                              /*IgnoreLLVMUsed=*/false);
+    if (!IsIndirectTarget) {
+      OutStreamer->emitInt8(static_cast<uint8_t>(FunctionKind::NOT_INDIRECT_TARGET));
+      return;
+    }
 
-  // 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(
-        static_cast<uint64_t>(FunctionInfo::FunctionKind::NOT_INDIRECT_TARGET));
-  } else {
     if (const auto *TypeId = extractNumericCGTypeId(F)) {
-      OutStreamer->emitInt64(static_cast<uint64_t>(
-          FunctionInfo::FunctionKind::INDIRECT_TARGET_KNOWN_TID));
+      OutStreamer->emitInt8(static_cast<uint8_t>(FunctionKind::INDIRECT_TARGET_KNOWN_TID));
       OutStreamer->emitInt64(TypeId->getZExtValue());
-    } else {
-      OutStreamer->emitInt64(static_cast<uint64_t>(
-          FunctionInfo::FunctionKind::INDIRECT_TARGET_UNKNOWN_TID));
+      return;
     }
-  }
+    OutStreamer->emitInt8(static_cast<uint8_t>(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;
-  OutStreamer->emitInt64(CallSiteLabels.size());
-  for (const auto &[TypeId, Label] : CallSiteLabels) {
+  // Emit function's call graph information.
+  // 1) CallGraphSectionFormatVersion
+  // 2) Function entry PC.
+  // 3) FunctionKind: Whether the function is indirect target, and if so,
+  //    whether its type id is known.
+  // 4) FunctionTypeID if the function is indirect target, and its type id is
+  //    known.
+  // 5) Number of indirect callsites.
+  // 6) For each indirect callsite, its callsite PC and callee's expected type
+  // id.
+
+  OutStreamer->emitInt32(CallGraphSectionFormatVersion::V_0);
+  const MCSymbol *FunctionSymbol = getFunctionBegin();
+  OutStreamer->emitSymbolValue(FunctionSymbol, TM.getProgramPointerSize());
+  EmitFunctionKindAndTypeId();
+  const auto &IndirectCallsites = FuncCGInfo.IndirectCallsites;
+  OutStreamer->emitInt32(IndirectCallsites.size());
+  for (const auto &[TypeId, Label] : IndirectCallsites) {
     OutStreamer->emitInt64(TypeId);
     OutStreamer->emitSymbolValue(Label, TM.getProgramPointerSize());
   }
-  FuncInfo.CallSiteLabels.clear();
+  FuncCGInfo.IndirectCallsites.clear();
 
   OutStreamer->popSection();
 }
@@ -1867,7 +1862,7 @@ static StringRef getMIMnemonic(const MachineInstr &MI, MCStreamer &Streamer) {
 }
 
 void AsmPrinter::emitIndirectCalleeLabels(
-    FunctionInfo &FuncInfo,
+    FunctionCallGraphInfo &FuncCGInfo,
     const MachineFunction::CallSiteInfoMap &CallSitesInfoMap,
     const MachineInstr &MI) {
   // Only indirect calls have type identifiers set.
@@ -1879,7 +1874,7 @@ void AsmPrinter::emitIndirectCalleeLabels(
     MCSymbol *S = MF->getContext().createTempSymbol();
     OutStreamer->emitLabel(S);
     uint64_t CalleeTypeIdVal = CalleeTypeId->getZExtValue();
-    FuncInfo.CallSiteLabels.emplace_back(CalleeTypeIdVal, S);
+    FuncCGInfo.IndirectCallsites.emplace_back(CalleeTypeIdVal, S);
   }
 }
 
@@ -1929,7 +1924,7 @@ void AsmPrinter::emitFunctionBody() {
     MBBSectionRanges[MF->front().getSectionID()] =
         MBBSectionRange{CurrentFnBegin, nullptr};
 
-  FunctionInfo FuncInfo;
+  FunctionCallGraphInfo FuncCGInfo;
   const auto &CallSitesInfoMap = MF->getCallSitesInfo();
   for (auto &MBB : *MF) {
     // Print a label for the basic block.
@@ -2066,7 +2061,7 @@ void AsmPrinter::emitFunctionBody() {
         OutStreamer->emitLabel(createCallsiteEndSymbol(MBB));
 
       if (TM.Options.EmitCallGraphSection && MI.isCall())
-        emitIndirectCalleeLabels(FuncInfo, CallSitesInfoMap, MI);
+        emitIndirectCalleeLabels(FuncCGInfo, CallSitesInfoMap, MI);
 
       // If there is a post-instruction symbol, emit a label for it here.
       if (MCSymbol *S = MI.getPostInstrSymbol())
@@ -2248,7 +2243,7 @@ void AsmPrinter::emitFunctionBody() {
   emitStackSizeSection(*MF);
 
   // Emit section containing call graph metadata.
-  emitCallGraphSection(*MF, FuncInfo);
+  emitCallGraphSection(*MF, FuncCGInfo);
 
   // Emit .su file containing function stack size information.
   emitStackUsage(*MF);
diff --git a/llvm/test/CodeGen/X86/call-graph-section-assembly.ll b/llvm/test/CodeGen/X86/call-graph-section-assembly.ll
index 11362873fb151..1b318dca260dd 100644
--- a/llvm/test/CodeGen/X86/call-graph-section-assembly.ll
+++ b/llvm/test/CodeGen/X86/call-graph-section-assembly.ll
@@ -22,10 +22,10 @@ entry:
 
 ; CHECK: .section .callgraph,"o",@progbits,.text
 
-; CHECK-NEXT: .quad   0
+; CHECK-NEXT: .long   0
 ; CHECK-NEXT: .quad   [[LABEL_FUNC]]
-; CHECK-NEXT: .quad   1
-; CHECK-NEXT: .quad   3
+; CHECK-NEXT: .byte   1
+; CHECK-NEXT: .long   3
 !0 = !{!1}
 !1 = !{i64 0, !"_ZTSFvE.generalized"}
 ;; Test for MD5 hash of _ZTSFvE.generalized and the generated temporary callsite label.
diff --git a/llvm/test/CodeGen/X86/call-graph-section-tailcall.ll b/llvm/test/CodeGen/X86/call-graph-section-tailcall.ll
index fa14a98008b45..a83df51f702ab 100644
--- a/llvm/test/CodeGen/X86/call-graph-section-tailcall.ll
+++ b/llvm/test/CodeGen/X86/call-graph-section-tailcall.ll
@@ -29,6 +29,8 @@ declare !type !2 i32 @bar(i8 signext)
 
 !0 = !{i64 0, !"_ZTSFiPvcE.generalized"}
 !1 = !{!2}
-; CHECK-DAG: 5486bc59 814b8e30
+;; Verify that the type id 0x308e4b8159bc8654 is in section.
+; CHECK:      5486bc 59814b8e
+; CHECK-NEXT: 0x00000020 30000000 00000000 00000000 00000000
 !2 = !{i64 0, !"_ZTSFicE.generalized"}
 !3 = !{i64 0, !"_ZTSFiiE.generalized"}
diff --git a/llvm/test/CodeGen/X86/call-graph-section.ll b/llvm/test/CodeGen/X86/call-graph-section.ll
index 66d009cf1221d..37f1e8e2acf16 100644
--- a/llvm/test/CodeGen/X86/call-graph-section.ll
+++ b/llvm/test/CodeGen/X86/call-graph-section.ll
@@ -25,12 +25,12 @@ entry:
 
 ; CHECK: Hex dump of section '.callgraph':
 
-; CHECK-DAG: 2444f731 f5eecb3e
+; CHECK-DAG: 2444f7 31f5eecb 3e
 !0 = !{i64 0, !"_ZTSFvE.generalized"}
 !1 = !{!0}
-; CHECK-DAG: 5486bc59 814b8e30
+; CHECK-DAG: 5486bc 59814b8e 30
 !2 = !{i64 0, !"_ZTSFicE.generalized"}
 !3 = !{!2}
-; CHECK-DAG: 7ade6814 f897fd77
+; CHECK-DAG: 7ade68 14f897fd 77
 !4 = !{!5}
 !5 = !{i64 0, !"_ZTSFPvS_E.generalized"}

@Prabhuk Prabhuk requested review from frobtech and ilovepi September 22, 2025 22:15
Copy link
Contributor

@ilovepi ilovepi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need some more test cases, like when the fields are omitted, and when there are no callers of a function. A COMDAT example where the different TUs differ on the addr being taken may also be instructive/useful to have tests for.

Copy link
Contributor

@ilovepi ilovepi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably get another set of eyes on this, but I think this is in good shape now, and is consistent.

@Prabhuk Prabhuk merged commit 6fb87b2 into llvm:main Oct 10, 2025
10 checks passed
@Prabhuk
Copy link
Contributor Author

Prabhuk commented Oct 10, 2025

we should probably get another set of eyes on this, but I think this is in good shape now, and is consistent.

I've left a link of this PR in discourse RFC comments section requesting reviews.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants