Skip to content

Compute GUIDs once and store in metadata#184065

Merged
orodley merged 33 commits into
llvm:mainfrom
orodley:guid
May 27, 2026
Merged

Compute GUIDs once and store in metadata#184065
orodley merged 33 commits into
llvm:mainfrom
orodley:guid

Conversation

@orodley
Copy link
Copy Markdown
Contributor

@orodley orodley commented Mar 2, 2026

This allows us to keep GUIDs consistent across compilation phases which may change the name or linkage type.

See https://discourse.llvm.org/t/rfc-keep-globalvalue-guids-stable/84801

@orodley orodley requested review from mtrofin and teresajohnson March 2, 2026 05:53
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 2, 2026

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

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 2, 2026

🐧 Linux x64 Test Results

  • 204607 tests passed
  • 6540 tests skipped

✅ The build succeeded and all tests passed.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 2, 2026

🪟 Windows x64 Test Results

  • 136652 tests passed
  • 4665 tests skipped

✅ The build succeeded and all tests passed.

Copy link
Copy Markdown
Member

@mtrofin mtrofin left a comment

Choose a reason for hiding this comment

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

overall lgtm, just nits.

Comment thread llvm/include/llvm/IR/Module.h Outdated
Comment thread llvm/include/llvm/IR/Module.h Outdated
Comment thread llvm/lib/Bitcode/Reader/BitcodeReader.cpp
Comment thread llvm/lib/IR/Globals.cpp Outdated
Comment thread llvm/lib/Transforms/Utils/AssignGUID.cpp Outdated
@orodley orodley marked this pull request as ready for review March 9, 2026 00:06
@llvmbot llvmbot added backend:X86 PGO Profile Guided Optimizations LTO Link time optimization (regular/full LTO or ThinLTO) llvm:ir llvm:analysis Includes value tracking, cost tables and constant folding llvm:transforms labels Mar 9, 2026
@llvmbot
Copy link
Copy Markdown
Member

llvmbot commented Mar 9, 2026

@llvm/pr-subscribers-clang-codegen
@llvm/pr-subscribers-llvm-ir
@llvm/pr-subscribers-llvm-transforms
@llvm/pr-subscribers-pgo

@llvm/pr-subscribers-backend-x86

Author: Owen Rodley (orodley)

Changes

This allows us to keep GUIDs consistent across compilation phases which may change the name or linkage type.

See https://discourse.llvm.org/t/rfc-keep-globalvalue-guids-stable/84801


Patch is 117.67 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/184065.diff

87 Files Affected:

  • (modified) llvm/include/llvm/Analysis/CtxProfAnalysis.h (+5-31)
  • (modified) llvm/include/llvm/Bitcode/BitcodeReader.h (+3-1)
  • (modified) llvm/include/llvm/Bitcode/LLVMBitCodes.h (+3)
  • (modified) llvm/include/llvm/IR/FixedMetadataKinds.def (+1)
  • (modified) llvm/include/llvm/IR/GlobalValue.h (+28-5)
  • (modified) llvm/include/llvm/IR/Module.h (+16)
  • (modified) llvm/include/llvm/IR/ModuleSummaryIndex.h (+11-5)
  • (modified) llvm/include/llvm/LTO/LTO.h (+17)
  • (added) llvm/include/llvm/Transforms/Utils/AssignGUID.h (+42)
  • (modified) llvm/lib/Analysis/CtxProfAnalysis.cpp (+4-40)
  • (modified) llvm/lib/AsmParser/LLParser.cpp (+9-3)
  • (modified) llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp (+1)
  • (modified) llvm/lib/Bitcode/Reader/BitcodeReader.cpp (+49-17)
  • (modified) llvm/lib/Bitcode/Writer/BitcodeWriter.cpp (+44-3)
  • (modified) llvm/lib/IR/Globals.cpp (+42)
  • (modified) llvm/lib/LTO/LTO.cpp (+30-21)
  • (modified) llvm/lib/LTO/LTOBackend.cpp (+7-2)
  • (modified) llvm/lib/Passes/PassBuilder.cpp (+1)
  • (modified) llvm/lib/Passes/PassBuilderPipelines.cpp (+6-3)
  • (modified) llvm/lib/Transforms/IPO/FunctionImport.cpp (+39-21)
  • (modified) llvm/lib/Transforms/IPO/ThinLTOBitcodeWriter.cpp (+2)
  • (modified) llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp (+7)
  • (modified) llvm/lib/Transforms/Instrumentation/PGOCtxProfFlattening.cpp (+2-2)
  • (modified) llvm/lib/Transforms/Instrumentation/PGOCtxProfLowering.cpp (+1-2)
  • (modified) llvm/lib/Transforms/Scalar/JumpTableToSwitch.cpp (+2-3)
  • (added) llvm/lib/Transforms/Utils/AssignGUID.cpp (+31)
  • (modified) llvm/lib/Transforms/Utils/CMakeLists.txt (+1)
  • (modified) llvm/lib/Transforms/Utils/CallPromotionUtils.cpp (+2-3)
  • (modified) llvm/lib/Transforms/Utils/CloneModule.cpp (+5-1)
  • (modified) llvm/lib/Transforms/Utils/FunctionImportUtils.cpp (+17-2)
  • (modified) llvm/lib/Transforms/Utils/InlineFunction.cpp (+2-2)
  • (modified) llvm/test/Assembler/index-value-order.ll (+6-3)
  • (modified) llvm/test/Bitcode/thinlto-alias.ll (+1)
  • (modified) llvm/test/Bitcode/thinlto-function-summary-callgraph-partial-sample-profile-summary.ll (+3-2)
  • (modified) llvm/test/Bitcode/thinlto-function-summary-callgraph-pgo.ll (+1)
  • (modified) llvm/test/Bitcode/thinlto-function-summary-callgraph-profile-summary.ll (+3-2)
  • (modified) llvm/test/Bitcode/thinlto-function-summary-callgraph-sample-profile-summary.ll (+3-2)
  • (modified) llvm/test/Bitcode/thinlto-function-summary-callgraph.ll (+1)
  • (modified) llvm/test/Bitcode/thinlto-function-summary-refgraph.ll (+1)
  • (modified) llvm/test/Bitcode/thinlto-function-summary.ll (+1)
  • (modified) llvm/test/CodeGen/X86/fat-lto-section.ll (+1-1)
  • (modified) llvm/test/Linker/funcimport2.ll (+1-1)
  • (modified) llvm/test/Other/new-pm-O0-defaults.ll (+2-1)
  • (modified) llvm/test/Other/new-pm-defaults.ll (+2-1)
  • (modified) llvm/test/Other/new-pm-thinlto-prelink-defaults.ll (+2-1)
  • (modified) llvm/test/Other/new-pm-thinlto-prelink-pgo-defaults.ll (+2-1)
  • (modified) llvm/test/Other/new-pm-thinlto-prelink-samplepgo-defaults.ll (+1)
  • (modified) llvm/test/ThinLTO/AArch64/aarch64_inline.ll (+1-1)
  • (modified) llvm/test/ThinLTO/X86/ctor-dtor-alias.ll (+2-2)
  • (modified) llvm/test/ThinLTO/X86/ctor-dtor-alias2.ll (+2-2)
  • (modified) llvm/test/ThinLTO/X86/deadstrip.ll (+4-4)
  • (modified) llvm/test/ThinLTO/X86/distributed_import.ll (+2-2)
  • (modified) llvm/test/ThinLTO/X86/funcattrs-prop-exported-internal.ll (+2-2)
  • (modified) llvm/test/ThinLTO/X86/funcattrs-prop-unknown.ll (+3-3)
  • (modified) llvm/test/ThinLTO/X86/globals-import.ll (+2-2)
  • (modified) llvm/test/ThinLTO/X86/hidden-escaped-symbols-alt.ll (+1-1)
  • (modified) llvm/test/ThinLTO/X86/hidden-escaped-symbols.ll (+1-1)
  • (modified) llvm/test/ThinLTO/X86/import-ro-constant.ll (+1-1)
  • (modified) llvm/test/ThinLTO/X86/index-const-prop-alias.ll (+1-1)
  • (modified) llvm/test/ThinLTO/X86/index-const-prop.ll (+3-3)
  • (modified) llvm/test/ThinLTO/X86/linkonce_resolution_comdat.ll (+8-8)
  • (modified) llvm/test/ThinLTO/X86/memprof-dups.ll (+1-1)
  • (modified) llvm/test/ThinLTO/X86/memprof_callee_type_mismatch.ll (+1-1)
  • (modified) llvm/test/ThinLTO/X86/memprof_imported_internal.ll (+1-1)
  • (modified) llvm/test/ThinLTO/X86/memprof_imported_internal2.ll (+1-1)
  • (modified) llvm/test/ThinLTO/X86/prevailing_weak_globals_import.ll (+1-1)
  • (modified) llvm/test/ThinLTO/X86/visibility-elf.ll (+3-3)
  • (modified) llvm/test/ThinLTO/X86/visibility-macho.ll (+2-2)
  • (modified) llvm/test/ThinLTO/X86/windows-vftable.ll (+2-2)
  • (modified) llvm/test/ThinLTO/X86/writeonly.ll (+2-2)
  • (added) llvm/test/Transforms/AssignGUID/assign_guid.ll (+18)
  • (modified) llvm/test/Transforms/EmbedBitcode/embed-wpd.ll (+1-1)
  • (modified) llvm/test/Transforms/EmbedBitcode/embed.ll (+7-6)
  • (modified) llvm/test/Transforms/FunctionImport/funcimport-debug-retained-nodes.ll (+2-1)
  • (modified) llvm/test/Transforms/FunctionImport/funcimport.ll (+12-12)
  • (modified) llvm/test/Transforms/LowerTypeTests/cfi-icall-alias.ll (+1-1)
  • (modified) llvm/test/Transforms/PGOProfile/thinlto_indirect_call_promotion.ll (+2-2)
  • (modified) llvm/test/Transforms/PhaseOrdering/speculative-devirt-then-inliner.ll (+1-1)
  • (modified) llvm/test/Transforms/SampleProfile/ctxsplit.ll (+4-4)
  • (modified) llvm/test/Transforms/ThinLTOBitcodeWriter/split-internal2.ll (+3-3)
  • (modified) llvm/test/Transforms/ThinLTOBitcodeWriter/split-vfunc.ll (+7-7)
  • (modified) llvm/test/Transforms/ThinLTOBitcodeWriter/unsplittable.ll (+1-1)
  • (modified) llvm/test/Transforms/WholeProgramDevirt/branch-funnel-profile.ll (+22-22)
  • (modified) llvm/test/Transforms/WholeProgramDevirt/export-single-impl.ll (+2-2)
  • (modified) llvm/test/Transforms/WholeProgramDevirt/export-vcp.ll (+3-3)
  • (modified) llvm/tools/llvm-link/llvm-link.cpp (+3-1)
  • (modified) llvm/tools/opt/NewPMDriver.cpp (+8)
diff --git a/llvm/include/llvm/Analysis/CtxProfAnalysis.h b/llvm/include/llvm/Analysis/CtxProfAnalysis.h
index 5c9823b0f6cc1..90b1d3fc0a688 100644
--- a/llvm/include/llvm/Analysis/CtxProfAnalysis.h
+++ b/llvm/include/llvm/Analysis/CtxProfAnalysis.h
@@ -47,9 +47,6 @@ class PGOContextualProfile {
   // we'll need when we maintain the profiles during IPO transformations.
   std::map<GlobalValue::GUID, FunctionInfo> FuncInfo;
 
-  /// Get the GUID of this Function if it's defined in this module.
-  LLVM_ABI GlobalValue::GUID getDefinedFunctionGUID(const Function &F) const;
-
   // This is meant to be constructed from CtxProfAnalysis, which will also set
   // its state piecemeal.
   PGOContextualProfile() = default;
@@ -68,9 +65,7 @@ class PGOContextualProfile {
 
   LLVM_ABI bool isInSpecializedModule() const;
 
-  bool isFunctionKnown(const Function &F) const {
-    return getDefinedFunctionGUID(F) != 0;
-  }
+  bool isFunctionKnown(const Function &F) const { return !F.isDeclaration(); }
 
   StringRef getFunctionName(GlobalValue::GUID GUID) const {
     auto It = FuncInfo.find(GUID);
@@ -81,22 +76,22 @@ class PGOContextualProfile {
 
   uint32_t getNumCounters(const Function &F) const {
     assert(isFunctionKnown(F));
-    return FuncInfo.find(getDefinedFunctionGUID(F))->second.NextCounterIndex;
+    return FuncInfo.find(F.getGUID())->second.NextCounterIndex;
   }
 
   uint32_t getNumCallsites(const Function &F) const {
     assert(isFunctionKnown(F));
-    return FuncInfo.find(getDefinedFunctionGUID(F))->second.NextCallsiteIndex;
+    return FuncInfo.find(F.getGUID())->second.NextCallsiteIndex;
   }
 
   uint32_t allocateNextCounterIndex(const Function &F) {
     assert(isFunctionKnown(F));
-    return FuncInfo.find(getDefinedFunctionGUID(F))->second.NextCounterIndex++;
+    return FuncInfo.find(F.getGUID())->second.NextCounterIndex++;
   }
 
   uint32_t allocateNextCallsiteIndex(const Function &F) {
     assert(isFunctionKnown(F));
-    return FuncInfo.find(getDefinedFunctionGUID(F))->second.NextCallsiteIndex++;
+    return FuncInfo.find(F.getGUID())->second.NextCallsiteIndex++;
   }
 
   using ConstVisitor = function_ref<void(const PGOCtxProfContext &)>;
@@ -188,26 +183,5 @@ class ProfileAnnotator {
   LLVM_ABI ~ProfileAnnotator();
 };
 
-/// Assign a GUID to functions as metadata. GUID calculation takes linkage into
-/// account, which may change especially through and after thinlto. By
-/// pre-computing and assigning as metadata, this mechanism is resilient to such
-/// changes (as well as name changes e.g. suffix ".llvm." additions).
-
-// FIXME(mtrofin): we can generalize this mechanism to calculate a GUID early in
-// the pass pipeline, associate it with any Global Value, and then use it for
-// PGO and ThinLTO.
-// At that point, this should be moved elsewhere.
-class AssignGUIDPass : public PassInfoMixin<AssignGUIDPass> {
-public:
-  explicit AssignGUIDPass() = default;
-
-  /// Assign a GUID *if* one is not already assign, as a function metadata named
-  /// `GUIDMetadataName`.
-  LLVM_ABI PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM);
-  LLVM_ABI static const char *GUIDMetadataName;
-  // This should become GlobalValue::getGUID
-  LLVM_ABI static uint64_t getGUID(const Function &F);
-};
-
 } // namespace llvm
 #endif // LLVM_ANALYSIS_CTXPROFANALYSIS_H
diff --git a/llvm/include/llvm/Bitcode/BitcodeReader.h b/llvm/include/llvm/Bitcode/BitcodeReader.h
index 772ca82019278..b7cab51857f0b 100644
--- a/llvm/include/llvm/Bitcode/BitcodeReader.h
+++ b/llvm/include/llvm/Bitcode/BitcodeReader.h
@@ -17,6 +17,7 @@
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Bitstream/BitCodeEnums.h"
 #include "llvm/IR/GlobalValue.h"
+#include "llvm/IR/ModuleSummaryIndex.h"
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/Endian.h"
 #include "llvm/Support/Error.h"
@@ -165,7 +166,8 @@ struct ParserCallbacks {
     /// into CombinedIndex.
     LLVM_ABI Error
     readSummary(ModuleSummaryIndex &CombinedIndex, StringRef ModulePath,
-                std::function<bool(GlobalValue::GUID)> IsPrevailing = nullptr);
+                std::function<bool(StringRef)> IsPrevailing = nullptr,
+                std::function<void(ValueInfo)> OnValueInfo = nullptr);
   };
 
   struct BitcodeFileContents {
diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
index bcf596a0d79b2..a3afad599186a 100644
--- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h
+++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
@@ -120,6 +120,9 @@ enum ModuleCodes {
 
   // IFUNC: [ifunc value type, addrspace, resolver val#, linkage, visibility]
   MODULE_CODE_IFUNC = 18,
+
+  // GUIDLIST: [n x i64]
+  MODULE_CODE_GUIDLIST = 19,
 };
 
 /// PARAMATTR blocks have code for defining a parameter attribute set.
diff --git a/llvm/include/llvm/IR/FixedMetadataKinds.def b/llvm/include/llvm/IR/FixedMetadataKinds.def
index 0d79677d7079e..ced586aaa503e 100644
--- a/llvm/include/llvm/IR/FixedMetadataKinds.def
+++ b/llvm/include/llvm/IR/FixedMetadataKinds.def
@@ -60,3 +60,4 @@ LLVM_FIXED_MD_KIND(MD_alloc_token, "alloc_token", 45)
 LLVM_FIXED_MD_KIND(MD_implicit_ref, "implicit.ref", 46)
 LLVM_FIXED_MD_KIND(MD_nofpclass, "nofpclass", 47)
 LLVM_FIXED_MD_KIND(MD_call_target, "call_target", 48)
+LLVM_FIXED_MD_KIND(MD_unique_id, "guid", 49)
diff --git a/llvm/include/llvm/IR/GlobalValue.h b/llvm/include/llvm/IR/GlobalValue.h
index 83e695cdd27d9..acc836e1974d4 100644
--- a/llvm/include/llvm/IR/GlobalValue.h
+++ b/llvm/include/llvm/IR/GlobalValue.h
@@ -589,17 +589,40 @@ class GlobalValue : public Constant {
   /// used as the key for a global lookup (e.g. profile or ThinLTO).
   LLVM_ABI std::string getGlobalIdentifier() const;
 
+  /// Assign a GUID to this value based on its current name and linkage.
+  /// This GUID will remain the same even if those change. This method is
+  /// idempotent -- if a GUID has already been assigned, calling it again
+  /// will do nothing.
+  ///
+  /// This is private (exposed only to \c AssignGUIDPass), as users don't need
+  /// to call it. GUIDs are assigned only by \c AssignGUIDPass. The pass
+  /// pipeline should be set up such that GUIDs are always available when
+  /// needed. If not, the GUID assignment pass should be moved (or run again)
+  /// such that they are.
+  void assignGUID();
+
+  // assignGUID needs to be accessible from AssignGUIDPass, which is called
+  // early in the pipeline to make GUIDs available to later passes. But we'd
+  // rather not expose it publicly, as no-one else should call it.
+  friend class AssignGUIDPass;
+
 public:
   /// Return a 64-bit global unique ID constructed from the name of a global
   /// symbol. Since this call doesn't supply the linkage or defining filename,
   /// the GUID computation will assume that the global has external linkage.
   LLVM_ABI static GUID getGUIDAssumingExternalLinkage(StringRef GlobalName);
 
-  /// Return a 64-bit global unique ID constructed from global value name
-  /// (i.e. returned by getGlobalIdentifier()).
-  GUID getGUID() const {
-    return getGUIDAssumingExternalLinkage(getGlobalIdentifier());
-  }
+  /// Return a 64-bit global unique ID for this value. It is based on the
+  /// "original" name and linkage of this value (i.e. whenever its GUID was
+  /// assigned). This might not match the current name and linkage.
+  GUID getGUID() const;
+
+  /// Return the GUID for this value if it has been assigned, nullopt
+  /// otherwise. This should only need to be used in some exceptional
+  /// situations. If possible whatever code needs access to a GUID should
+  /// be set up to run after AssignGUIDPass, in which case it will always
+  /// be available.
+  std::optional<GUID> getGUIDIfAssigned() const;
 
   /// @name Materialization
   /// Materialization is used to construct functions only as they're needed.
diff --git a/llvm/include/llvm/IR/Module.h b/llvm/include/llvm/IR/Module.h
index 7156a83c9f3cc..b4a13128dd9f7 100644
--- a/llvm/include/llvm/IR/Module.h
+++ b/llvm/include/llvm/IR/Module.h
@@ -32,6 +32,7 @@
 #include "llvm/Support/CBindingWrapping.h"
 #include "llvm/Support/CodeGen.h"
 #include "llvm/Support/Compiler.h"
+#include "llvm/Support/Debug.h"
 #include "llvm/TargetParser/Triple.h"
 #include <cstddef>
 #include <cstdint>
@@ -576,7 +577,22 @@ class LLVM_ABI Module {
   // Use global_size() to get the total number of global variables.
   // Use globals() to get the range of all global variables.
 
+  std::optional<GlobalValue::GUID> getGUID(const Value* V) const {
+    const auto It = ValueToGUIDMap.find(V);
+    if (It == ValueToGUIDMap.end()) {
+      return {};
+    }
+
+    return It->getSecond();
+  }
+
+  void insertGUID(const Value* V, GlobalValue::GUID GUID) {
+    ValueToGUIDMap[V] = GUID;
+  }
+
 private:
+  DenseMap<const Value*, GlobalValue::GUID> ValueToGUIDMap;
+
 /// @}
 /// @name Direct access to the globals list, functions list, and symbol table
 /// @{
diff --git a/llvm/include/llvm/IR/ModuleSummaryIndex.h b/llvm/include/llvm/IR/ModuleSummaryIndex.h
index b0b5ef9908a5e..07e019cbe48c4 100644
--- a/llvm/include/llvm/IR/ModuleSummaryIndex.h
+++ b/llvm/include/llvm/IR/ModuleSummaryIndex.h
@@ -29,6 +29,7 @@
 #include "llvm/IR/Module.h"
 #include "llvm/Support/Allocator.h"
 #include "llvm/Support/Compiler.h"
+#include "llvm/Support/Debug.h"
 #include "llvm/Support/InterleavedRange.h"
 #include "llvm/Support/ScaledNumber.h"
 #include "llvm/Support/StringSaver.h"
@@ -599,8 +600,8 @@ class GlobalValueSummary {
   bool wasPromoted() const { return Flags.Promoted; }
 
   void promote() {
-    assert(GlobalValue::isLocalLinkage(linkage()) &&
-           "unexpected (re-)promotion of non-local symbol");
+    assert(!GlobalValue::isExternalLinkage(linkage()) &&
+           "unexpected (re-)promotion of external symbol");
     assert(!Flags.Promoted);
     Flags.Promoted = true;
     Flags.Linkage = GlobalValue::LinkageTypes::ExternalLinkage;
@@ -1763,14 +1764,19 @@ class ModuleSummaryIndex {
     return ValueInfo(HaveGVs, VP);
   }
 
-  /// Return a ValueInfo for \p GV and mark it as belonging to GV.
-  ValueInfo getOrInsertValueInfo(const GlobalValue *GV) {
+  /// Return a ValueInfo for \p GV with GUID \p GUID and mark it as belonging to GV.
+  ValueInfo getOrInsertValueInfo(const GlobalValue *GV, GlobalValue::GUID GUID) {
     assert(HaveGVs);
-    auto VP = getOrInsertValuePtr(GV->getGUID());
+    auto VP = getOrInsertValuePtr(GUID);
     VP->second.U.GV = GV;
     return ValueInfo(HaveGVs, VP);
   }
 
+  /// Return a ValueInfo for \p GV and mark it as belonging to GV.
+  ValueInfo getOrInsertValueInfo(const GlobalValue *GV) {
+    return getOrInsertValueInfo(GV, GV->getGUID());
+  }
+
   /// Return the GUID for \p OriginalId in the OidGuidMap.
   GlobalValue::GUID getGUIDFromOriginalID(GlobalValue::GUID OriginalID) const {
     const auto I = OidGuidMap.find(OriginalID);
diff --git a/llvm/include/llvm/LTO/LTO.h b/llvm/include/llvm/LTO/LTO.h
index 38349b604b6b3..32a47b3c87e1d 100644
--- a/llvm/include/llvm/LTO/LTO.h
+++ b/llvm/include/llvm/LTO/LTO.h
@@ -572,6 +572,23 @@ class LTO {
     /// LTO backend.
     unsigned Partition = Unknown;
 
+    // FIXME: make private, and also make GlobalResolution a class, it has been
+    // becoming more than just a data bag.
+    GlobalValue::GUID GUID = 0;
+
+    void setGUID(GlobalValue::GUID G) {
+      assert(G);
+      assert(!GUID || GUID == G);
+      GUID = G;
+    }
+
+    GlobalValue::GUID getGUID() const {
+      return GUID ? GUID
+                  : GlobalValue::getGUIDAssumingExternalLinkage(
+                        GlobalValue::getGlobalIdentifier(
+                            IRName, GlobalValue::LinkageTypes::ExternalLinkage,
+                            ""));
+    }
     /// Special partition numbers.
     enum : unsigned {
       /// A partition number has not yet been assigned to this global.
diff --git a/llvm/include/llvm/Transforms/Utils/AssignGUID.h b/llvm/include/llvm/Transforms/Utils/AssignGUID.h
new file mode 100644
index 0000000000000..0abd450d5a522
--- /dev/null
+++ b/llvm/include/llvm/Transforms/Utils/AssignGUID.h
@@ -0,0 +1,42 @@
+//===-- AssignGUID.h - Unique identifier assignment pass --------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file provides a pass which assigns a a GUID (globally unique identifier)
+// to every GlobalValue in the module, according to its current name, linkage,
+// and originating file. This way we have a consistent identifier even when
+// these inputs to the GUID change (for instance, after externalising a global
+// in ThinLTO).
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TRANSFORMS_UTILS_ASSIGNGUID_H
+#define LLVM_TRANSFORMS_UTILS_ASSIGNGUID_H
+
+#include "llvm/IR/Module.h"
+#include "llvm/IR/PassManager.h"
+#include "llvm/Support/Debug.h"
+
+namespace llvm {
+
+class AssignGUIDPass : public PassInfoMixin<AssignGUIDPass> {
+public:
+  AssignGUIDPass() = default;
+
+  static void runOnModule(Module &M);
+
+  PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) {
+    AssignGUIDPass::runOnModule(M);
+    return PreservedAnalyses::all();
+  }
+
+  static bool isRequired() { return true; }
+};
+
+} // end namespace llvm
+
+#endif // LLVM_TRANSFORMS_UTILS_ASSIGNGUID_H
\ No newline at end of file
diff --git a/llvm/lib/Analysis/CtxProfAnalysis.cpp b/llvm/lib/Analysis/CtxProfAnalysis.cpp
index c4abec02e765a..d4c00e4090220 100644
--- a/llvm/lib/Analysis/CtxProfAnalysis.cpp
+++ b/llvm/lib/Analysis/CtxProfAnalysis.cpp
@@ -50,9 +50,7 @@ static cl::opt<bool> ForceIsInSpecializedModule(
     "ctx-profile-force-is-specialized", cl::init(false),
     cl::desc("Treat the given module as-if it were containing the "
              "post-thinlink module containing the root"));
-
-const char *AssignGUIDPass::GUIDMetadataName = "guid";
-
+ 
 class ProfileAnnotatorImpl final {
   friend class ProfileAnnotator;
   class BBInfo;
@@ -420,33 +418,6 @@ bool ProfileAnnotator::getOutgoingBranchWeights(
   return MaxCount > 0;
 }
 
-PreservedAnalyses AssignGUIDPass::run(Module &M, ModuleAnalysisManager &MAM) {
-  for (auto &F : M.functions()) {
-    if (F.isDeclaration())
-      continue;
-    if (F.getMetadata(GUIDMetadataName))
-      continue;
-    const GlobalValue::GUID GUID = F.getGUID();
-    F.setMetadata(GUIDMetadataName,
-                  MDNode::get(M.getContext(),
-                              {ConstantAsMetadata::get(ConstantInt::get(
-                                  Type::getInt64Ty(M.getContext()), GUID))}));
-  }
-  return PreservedAnalyses::none();
-}
-
-GlobalValue::GUID AssignGUIDPass::getGUID(const Function &F) {
-  if (F.isDeclaration()) {
-    assert(GlobalValue::isExternalLinkage(F.getLinkage()));
-    return F.getGUID();
-  }
-  auto *MD = F.getMetadata(GUIDMetadataName);
-  assert(MD && "guid not found for defined function");
-  return cast<ConstantInt>(cast<ConstantAsMetadata>(MD->getOperand(0))
-                               ->getValue()
-                               ->stripPointerCasts())
-      ->getZExtValue();
-}
 AnalysisKey CtxProfAnalysis::Key;
 
 CtxProfAnalysis::CtxProfAnalysis(std::optional<StringRef> Profile)
@@ -515,7 +486,7 @@ PGOContextualProfile CtxProfAnalysis::run(Module &M,
   for (const auto &F : M) {
     if (F.isDeclaration())
       continue;
-    auto GUID = AssignGUIDPass::getGUID(F);
+    auto GUID = F.getGUID();
     assert(GUID && "guid not found for defined function");
     const auto &Entry = F.begin();
     uint32_t MaxCounters = 0; // we expect at least a counter.
@@ -549,13 +520,6 @@ PGOContextualProfile CtxProfAnalysis::run(Module &M,
   return Result;
 }
 
-GlobalValue::GUID
-PGOContextualProfile::getDefinedFunctionGUID(const Function &F) const {
-  if (auto It = FuncInfo.find(AssignGUIDPass::getGUID(F)); It != FuncInfo.end())
-    return It->first;
-  return 0;
-}
-
 CtxProfAnalysisPrinterPass::CtxProfAnalysisPrinterPass(raw_ostream &OS)
     : OS(OS), Mode(PrintLevel) {}
 
@@ -671,7 +635,7 @@ bool PGOContextualProfile::isInSpecializedModule() const {
 
 void PGOContextualProfile::update(Visitor V, const Function &F) {
   assert(isFunctionKnown(F));
-  GlobalValue::GUID G = getDefinedFunctionGUID(F);
+  GlobalValue::GUID G = F.getGUID();
   for (auto *Node = FuncInfo.find(G)->second.Index.Next; Node;
        Node = Node->Next)
     V(*reinterpret_cast<PGOCtxProfContext *>(Node));
@@ -682,7 +646,7 @@ void PGOContextualProfile::visit(ConstVisitor V, const Function *F) const {
     return preorderVisit<const PGOCtxProfContext::CallTargetMapTy,
                          const PGOCtxProfContext>(Profiles.Contexts, V);
   assert(isFunctionKnown(*F));
-  GlobalValue::GUID G = getDefinedFunctionGUID(*F);
+  GlobalValue::GUID G = F->getGUID();
   for (const auto *Node = FuncInfo.find(G)->second.Index.Next; Node;
        Node = Node->Next)
     V(*reinterpret_cast<const PGOCtxProfContext *>(Node));
diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp
index 551b88d8b435b..fcfa2ec2be342 100644
--- a/llvm/lib/AsmParser/LLParser.cpp
+++ b/llvm/lib/AsmParser/LLParser.cpp
@@ -1064,8 +1064,9 @@ bool LLParser::skipModuleSummaryEntry() {
   // support is in place we will look for the tokens corresponding to the
   // expected tags.
   if (Lex.getKind() != lltok::kw_gv && Lex.getKind() != lltok::kw_module &&
-      Lex.getKind() != lltok::kw_typeid && Lex.getKind() != lltok::kw_flags &&
-      Lex.getKind() != lltok::kw_blockcount)
+      Lex.getKind() != lltok::kw_typeid &&
+      Lex.getKind() != lltok::kw_typeidCompatibleVTable &&
+      Lex.getKind() != lltok::kw_flags && Lex.getKind() != lltok::kw_blockcount)
     return tokError(
         "Expected 'gv', 'module', 'typeid', 'flags' or 'blockcount' at the "
         "start of summary entry");
@@ -9847,7 +9848,12 @@ bool LLParser::addGlobalValueToIndex(
       if (!GV)
         return error(Loc, "Reference to undefined global \"" + Name + "\"");
 
-      VI = Index->getOrInsertValueInfo(GV);
+      // Be a little lenient here, to accomodate older files without GUIDs
+      // already computed and assigned as metadata.
+      auto MaybeGUID = GV->getGUIDIfAssigned();
+      GUID = MaybeGUID ? *MaybeGUID : GlobalValue::getGUIDAssumingExternalLinkage(GV->getName());
+
+      VI = Index->getOrInsertValueInfo(GV, GUID);
     } else {
       assert(
           (!GlobalValue::isLocalLinkage(Linkage) || !SourceFileName.empty()) &&
diff --git a/llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp b/llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp
index 911ec7501eb8b..6574ab7a93c58 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp
@@ -142,6 +142,7 @@ GetCodeName(unsigned CodeID, unsigned BlockID,
       STRINGIFY_CODE(MODULE_CODE, METADATA_VALUES_UNUSED)
       STRINGIFY_CODE(MODULE_CODE, SOURCE_FILENAME)
       STRINGIFY_CODE(MODULE_CODE, HASH)
+      STRINGIFY_CODE(MODULE_CODE, GUIDLIST)
     }
   case bitc::IDENTIFICATION_BLOCK_ID:
     switch (CodeID) {
diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
index a781a43112f8c..0b6ce590f00af 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
@@ -696,6 +696,9 @@ class BitcodeReader : public BitcodeReaderBase, public GVMaterializer {
 
   std::optional<ValueTypeCallbackTy> ValueTypeCallback;
 
+  /// A list of GUIDs defined by this module. Indexed by ValueID.
+  std::vector<GlobalValue::GUID> GUIDList;
+
 public:
   BitcodeReader(BitstreamCursor Stream, StringRef Strtab,
                 StringRef ProducerIdentification, LLVMContext &Context);
@@ -974,9 +977,10 @@ class ModuleSummaryIndexBitcodeReader : public BitcodeReaderBase {
 
   /// Callback to ask whether a symbol is the prevailing copy when invoked
   /// during combined index building.
-  std::function<bool(GlobalValue::GUID)> IsPrevailing;
+  std::function<bool(StringRef)> IsPrevailing = nullptr;
+  std::function<void(ValueInfo)> OnValueInfo = nullptr;
 
-  /// Saves the stack ids from the STACK_IDS record to consult when adding stack
+  /// Saves the stack ids from the STACK_IDS record to consult ...
[truncated]

@llvmbot
Copy link
Copy Markdown
Member

llvmbot commented Mar 9, 2026

@llvm/pr-subscribers-llvm-analysis

Author: Owen Rodley (orodley)

Changes

This allows us to keep GUIDs consistent across compilation phases which may change the name or linkage type.

See https://discourse.llvm.org/t/rfc-keep-globalvalue-guids-stable/84801


Patch is 117.67 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/184065.diff

87 Files Affected:

  • (modified) llvm/include/llvm/Analysis/CtxProfAnalysis.h (+5-31)
  • (modified) llvm/include/llvm/Bitcode/BitcodeReader.h (+3-1)
  • (modified) llvm/include/llvm/Bitcode/LLVMBitCodes.h (+3)
  • (modified) llvm/include/llvm/IR/FixedMetadataKinds.def (+1)
  • (modified) llvm/include/llvm/IR/GlobalValue.h (+28-5)
  • (modified) llvm/include/llvm/IR/Module.h (+16)
  • (modified) llvm/include/llvm/IR/ModuleSummaryIndex.h (+11-5)
  • (modified) llvm/include/llvm/LTO/LTO.h (+17)
  • (added) llvm/include/llvm/Transforms/Utils/AssignGUID.h (+42)
  • (modified) llvm/lib/Analysis/CtxProfAnalysis.cpp (+4-40)
  • (modified) llvm/lib/AsmParser/LLParser.cpp (+9-3)
  • (modified) llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp (+1)
  • (modified) llvm/lib/Bitcode/Reader/BitcodeReader.cpp (+49-17)
  • (modified) llvm/lib/Bitcode/Writer/BitcodeWriter.cpp (+44-3)
  • (modified) llvm/lib/IR/Globals.cpp (+42)
  • (modified) llvm/lib/LTO/LTO.cpp (+30-21)
  • (modified) llvm/lib/LTO/LTOBackend.cpp (+7-2)
  • (modified) llvm/lib/Passes/PassBuilder.cpp (+1)
  • (modified) llvm/lib/Passes/PassBuilderPipelines.cpp (+6-3)
  • (modified) llvm/lib/Transforms/IPO/FunctionImport.cpp (+39-21)
  • (modified) llvm/lib/Transforms/IPO/ThinLTOBitcodeWriter.cpp (+2)
  • (modified) llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp (+7)
  • (modified) llvm/lib/Transforms/Instrumentation/PGOCtxProfFlattening.cpp (+2-2)
  • (modified) llvm/lib/Transforms/Instrumentation/PGOCtxProfLowering.cpp (+1-2)
  • (modified) llvm/lib/Transforms/Scalar/JumpTableToSwitch.cpp (+2-3)
  • (added) llvm/lib/Transforms/Utils/AssignGUID.cpp (+31)
  • (modified) llvm/lib/Transforms/Utils/CMakeLists.txt (+1)
  • (modified) llvm/lib/Transforms/Utils/CallPromotionUtils.cpp (+2-3)
  • (modified) llvm/lib/Transforms/Utils/CloneModule.cpp (+5-1)
  • (modified) llvm/lib/Transforms/Utils/FunctionImportUtils.cpp (+17-2)
  • (modified) llvm/lib/Transforms/Utils/InlineFunction.cpp (+2-2)
  • (modified) llvm/test/Assembler/index-value-order.ll (+6-3)
  • (modified) llvm/test/Bitcode/thinlto-alias.ll (+1)
  • (modified) llvm/test/Bitcode/thinlto-function-summary-callgraph-partial-sample-profile-summary.ll (+3-2)
  • (modified) llvm/test/Bitcode/thinlto-function-summary-callgraph-pgo.ll (+1)
  • (modified) llvm/test/Bitcode/thinlto-function-summary-callgraph-profile-summary.ll (+3-2)
  • (modified) llvm/test/Bitcode/thinlto-function-summary-callgraph-sample-profile-summary.ll (+3-2)
  • (modified) llvm/test/Bitcode/thinlto-function-summary-callgraph.ll (+1)
  • (modified) llvm/test/Bitcode/thinlto-function-summary-refgraph.ll (+1)
  • (modified) llvm/test/Bitcode/thinlto-function-summary.ll (+1)
  • (modified) llvm/test/CodeGen/X86/fat-lto-section.ll (+1-1)
  • (modified) llvm/test/Linker/funcimport2.ll (+1-1)
  • (modified) llvm/test/Other/new-pm-O0-defaults.ll (+2-1)
  • (modified) llvm/test/Other/new-pm-defaults.ll (+2-1)
  • (modified) llvm/test/Other/new-pm-thinlto-prelink-defaults.ll (+2-1)
  • (modified) llvm/test/Other/new-pm-thinlto-prelink-pgo-defaults.ll (+2-1)
  • (modified) llvm/test/Other/new-pm-thinlto-prelink-samplepgo-defaults.ll (+1)
  • (modified) llvm/test/ThinLTO/AArch64/aarch64_inline.ll (+1-1)
  • (modified) llvm/test/ThinLTO/X86/ctor-dtor-alias.ll (+2-2)
  • (modified) llvm/test/ThinLTO/X86/ctor-dtor-alias2.ll (+2-2)
  • (modified) llvm/test/ThinLTO/X86/deadstrip.ll (+4-4)
  • (modified) llvm/test/ThinLTO/X86/distributed_import.ll (+2-2)
  • (modified) llvm/test/ThinLTO/X86/funcattrs-prop-exported-internal.ll (+2-2)
  • (modified) llvm/test/ThinLTO/X86/funcattrs-prop-unknown.ll (+3-3)
  • (modified) llvm/test/ThinLTO/X86/globals-import.ll (+2-2)
  • (modified) llvm/test/ThinLTO/X86/hidden-escaped-symbols-alt.ll (+1-1)
  • (modified) llvm/test/ThinLTO/X86/hidden-escaped-symbols.ll (+1-1)
  • (modified) llvm/test/ThinLTO/X86/import-ro-constant.ll (+1-1)
  • (modified) llvm/test/ThinLTO/X86/index-const-prop-alias.ll (+1-1)
  • (modified) llvm/test/ThinLTO/X86/index-const-prop.ll (+3-3)
  • (modified) llvm/test/ThinLTO/X86/linkonce_resolution_comdat.ll (+8-8)
  • (modified) llvm/test/ThinLTO/X86/memprof-dups.ll (+1-1)
  • (modified) llvm/test/ThinLTO/X86/memprof_callee_type_mismatch.ll (+1-1)
  • (modified) llvm/test/ThinLTO/X86/memprof_imported_internal.ll (+1-1)
  • (modified) llvm/test/ThinLTO/X86/memprof_imported_internal2.ll (+1-1)
  • (modified) llvm/test/ThinLTO/X86/prevailing_weak_globals_import.ll (+1-1)
  • (modified) llvm/test/ThinLTO/X86/visibility-elf.ll (+3-3)
  • (modified) llvm/test/ThinLTO/X86/visibility-macho.ll (+2-2)
  • (modified) llvm/test/ThinLTO/X86/windows-vftable.ll (+2-2)
  • (modified) llvm/test/ThinLTO/X86/writeonly.ll (+2-2)
  • (added) llvm/test/Transforms/AssignGUID/assign_guid.ll (+18)
  • (modified) llvm/test/Transforms/EmbedBitcode/embed-wpd.ll (+1-1)
  • (modified) llvm/test/Transforms/EmbedBitcode/embed.ll (+7-6)
  • (modified) llvm/test/Transforms/FunctionImport/funcimport-debug-retained-nodes.ll (+2-1)
  • (modified) llvm/test/Transforms/FunctionImport/funcimport.ll (+12-12)
  • (modified) llvm/test/Transforms/LowerTypeTests/cfi-icall-alias.ll (+1-1)
  • (modified) llvm/test/Transforms/PGOProfile/thinlto_indirect_call_promotion.ll (+2-2)
  • (modified) llvm/test/Transforms/PhaseOrdering/speculative-devirt-then-inliner.ll (+1-1)
  • (modified) llvm/test/Transforms/SampleProfile/ctxsplit.ll (+4-4)
  • (modified) llvm/test/Transforms/ThinLTOBitcodeWriter/split-internal2.ll (+3-3)
  • (modified) llvm/test/Transforms/ThinLTOBitcodeWriter/split-vfunc.ll (+7-7)
  • (modified) llvm/test/Transforms/ThinLTOBitcodeWriter/unsplittable.ll (+1-1)
  • (modified) llvm/test/Transforms/WholeProgramDevirt/branch-funnel-profile.ll (+22-22)
  • (modified) llvm/test/Transforms/WholeProgramDevirt/export-single-impl.ll (+2-2)
  • (modified) llvm/test/Transforms/WholeProgramDevirt/export-vcp.ll (+3-3)
  • (modified) llvm/tools/llvm-link/llvm-link.cpp (+3-1)
  • (modified) llvm/tools/opt/NewPMDriver.cpp (+8)
diff --git a/llvm/include/llvm/Analysis/CtxProfAnalysis.h b/llvm/include/llvm/Analysis/CtxProfAnalysis.h
index 5c9823b0f6cc1..90b1d3fc0a688 100644
--- a/llvm/include/llvm/Analysis/CtxProfAnalysis.h
+++ b/llvm/include/llvm/Analysis/CtxProfAnalysis.h
@@ -47,9 +47,6 @@ class PGOContextualProfile {
   // we'll need when we maintain the profiles during IPO transformations.
   std::map<GlobalValue::GUID, FunctionInfo> FuncInfo;
 
-  /// Get the GUID of this Function if it's defined in this module.
-  LLVM_ABI GlobalValue::GUID getDefinedFunctionGUID(const Function &F) const;
-
   // This is meant to be constructed from CtxProfAnalysis, which will also set
   // its state piecemeal.
   PGOContextualProfile() = default;
@@ -68,9 +65,7 @@ class PGOContextualProfile {
 
   LLVM_ABI bool isInSpecializedModule() const;
 
-  bool isFunctionKnown(const Function &F) const {
-    return getDefinedFunctionGUID(F) != 0;
-  }
+  bool isFunctionKnown(const Function &F) const { return !F.isDeclaration(); }
 
   StringRef getFunctionName(GlobalValue::GUID GUID) const {
     auto It = FuncInfo.find(GUID);
@@ -81,22 +76,22 @@ class PGOContextualProfile {
 
   uint32_t getNumCounters(const Function &F) const {
     assert(isFunctionKnown(F));
-    return FuncInfo.find(getDefinedFunctionGUID(F))->second.NextCounterIndex;
+    return FuncInfo.find(F.getGUID())->second.NextCounterIndex;
   }
 
   uint32_t getNumCallsites(const Function &F) const {
     assert(isFunctionKnown(F));
-    return FuncInfo.find(getDefinedFunctionGUID(F))->second.NextCallsiteIndex;
+    return FuncInfo.find(F.getGUID())->second.NextCallsiteIndex;
   }
 
   uint32_t allocateNextCounterIndex(const Function &F) {
     assert(isFunctionKnown(F));
-    return FuncInfo.find(getDefinedFunctionGUID(F))->second.NextCounterIndex++;
+    return FuncInfo.find(F.getGUID())->second.NextCounterIndex++;
   }
 
   uint32_t allocateNextCallsiteIndex(const Function &F) {
     assert(isFunctionKnown(F));
-    return FuncInfo.find(getDefinedFunctionGUID(F))->second.NextCallsiteIndex++;
+    return FuncInfo.find(F.getGUID())->second.NextCallsiteIndex++;
   }
 
   using ConstVisitor = function_ref<void(const PGOCtxProfContext &)>;
@@ -188,26 +183,5 @@ class ProfileAnnotator {
   LLVM_ABI ~ProfileAnnotator();
 };
 
-/// Assign a GUID to functions as metadata. GUID calculation takes linkage into
-/// account, which may change especially through and after thinlto. By
-/// pre-computing and assigning as metadata, this mechanism is resilient to such
-/// changes (as well as name changes e.g. suffix ".llvm." additions).
-
-// FIXME(mtrofin): we can generalize this mechanism to calculate a GUID early in
-// the pass pipeline, associate it with any Global Value, and then use it for
-// PGO and ThinLTO.
-// At that point, this should be moved elsewhere.
-class AssignGUIDPass : public PassInfoMixin<AssignGUIDPass> {
-public:
-  explicit AssignGUIDPass() = default;
-
-  /// Assign a GUID *if* one is not already assign, as a function metadata named
-  /// `GUIDMetadataName`.
-  LLVM_ABI PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM);
-  LLVM_ABI static const char *GUIDMetadataName;
-  // This should become GlobalValue::getGUID
-  LLVM_ABI static uint64_t getGUID(const Function &F);
-};
-
 } // namespace llvm
 #endif // LLVM_ANALYSIS_CTXPROFANALYSIS_H
diff --git a/llvm/include/llvm/Bitcode/BitcodeReader.h b/llvm/include/llvm/Bitcode/BitcodeReader.h
index 772ca82019278..b7cab51857f0b 100644
--- a/llvm/include/llvm/Bitcode/BitcodeReader.h
+++ b/llvm/include/llvm/Bitcode/BitcodeReader.h
@@ -17,6 +17,7 @@
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Bitstream/BitCodeEnums.h"
 #include "llvm/IR/GlobalValue.h"
+#include "llvm/IR/ModuleSummaryIndex.h"
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/Endian.h"
 #include "llvm/Support/Error.h"
@@ -165,7 +166,8 @@ struct ParserCallbacks {
     /// into CombinedIndex.
     LLVM_ABI Error
     readSummary(ModuleSummaryIndex &CombinedIndex, StringRef ModulePath,
-                std::function<bool(GlobalValue::GUID)> IsPrevailing = nullptr);
+                std::function<bool(StringRef)> IsPrevailing = nullptr,
+                std::function<void(ValueInfo)> OnValueInfo = nullptr);
   };
 
   struct BitcodeFileContents {
diff --git a/llvm/include/llvm/Bitcode/LLVMBitCodes.h b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
index bcf596a0d79b2..a3afad599186a 100644
--- a/llvm/include/llvm/Bitcode/LLVMBitCodes.h
+++ b/llvm/include/llvm/Bitcode/LLVMBitCodes.h
@@ -120,6 +120,9 @@ enum ModuleCodes {
 
   // IFUNC: [ifunc value type, addrspace, resolver val#, linkage, visibility]
   MODULE_CODE_IFUNC = 18,
+
+  // GUIDLIST: [n x i64]
+  MODULE_CODE_GUIDLIST = 19,
 };
 
 /// PARAMATTR blocks have code for defining a parameter attribute set.
diff --git a/llvm/include/llvm/IR/FixedMetadataKinds.def b/llvm/include/llvm/IR/FixedMetadataKinds.def
index 0d79677d7079e..ced586aaa503e 100644
--- a/llvm/include/llvm/IR/FixedMetadataKinds.def
+++ b/llvm/include/llvm/IR/FixedMetadataKinds.def
@@ -60,3 +60,4 @@ LLVM_FIXED_MD_KIND(MD_alloc_token, "alloc_token", 45)
 LLVM_FIXED_MD_KIND(MD_implicit_ref, "implicit.ref", 46)
 LLVM_FIXED_MD_KIND(MD_nofpclass, "nofpclass", 47)
 LLVM_FIXED_MD_KIND(MD_call_target, "call_target", 48)
+LLVM_FIXED_MD_KIND(MD_unique_id, "guid", 49)
diff --git a/llvm/include/llvm/IR/GlobalValue.h b/llvm/include/llvm/IR/GlobalValue.h
index 83e695cdd27d9..acc836e1974d4 100644
--- a/llvm/include/llvm/IR/GlobalValue.h
+++ b/llvm/include/llvm/IR/GlobalValue.h
@@ -589,17 +589,40 @@ class GlobalValue : public Constant {
   /// used as the key for a global lookup (e.g. profile or ThinLTO).
   LLVM_ABI std::string getGlobalIdentifier() const;
 
+  /// Assign a GUID to this value based on its current name and linkage.
+  /// This GUID will remain the same even if those change. This method is
+  /// idempotent -- if a GUID has already been assigned, calling it again
+  /// will do nothing.
+  ///
+  /// This is private (exposed only to \c AssignGUIDPass), as users don't need
+  /// to call it. GUIDs are assigned only by \c AssignGUIDPass. The pass
+  /// pipeline should be set up such that GUIDs are always available when
+  /// needed. If not, the GUID assignment pass should be moved (or run again)
+  /// such that they are.
+  void assignGUID();
+
+  // assignGUID needs to be accessible from AssignGUIDPass, which is called
+  // early in the pipeline to make GUIDs available to later passes. But we'd
+  // rather not expose it publicly, as no-one else should call it.
+  friend class AssignGUIDPass;
+
 public:
   /// Return a 64-bit global unique ID constructed from the name of a global
   /// symbol. Since this call doesn't supply the linkage or defining filename,
   /// the GUID computation will assume that the global has external linkage.
   LLVM_ABI static GUID getGUIDAssumingExternalLinkage(StringRef GlobalName);
 
-  /// Return a 64-bit global unique ID constructed from global value name
-  /// (i.e. returned by getGlobalIdentifier()).
-  GUID getGUID() const {
-    return getGUIDAssumingExternalLinkage(getGlobalIdentifier());
-  }
+  /// Return a 64-bit global unique ID for this value. It is based on the
+  /// "original" name and linkage of this value (i.e. whenever its GUID was
+  /// assigned). This might not match the current name and linkage.
+  GUID getGUID() const;
+
+  /// Return the GUID for this value if it has been assigned, nullopt
+  /// otherwise. This should only need to be used in some exceptional
+  /// situations. If possible whatever code needs access to a GUID should
+  /// be set up to run after AssignGUIDPass, in which case it will always
+  /// be available.
+  std::optional<GUID> getGUIDIfAssigned() const;
 
   /// @name Materialization
   /// Materialization is used to construct functions only as they're needed.
diff --git a/llvm/include/llvm/IR/Module.h b/llvm/include/llvm/IR/Module.h
index 7156a83c9f3cc..b4a13128dd9f7 100644
--- a/llvm/include/llvm/IR/Module.h
+++ b/llvm/include/llvm/IR/Module.h
@@ -32,6 +32,7 @@
 #include "llvm/Support/CBindingWrapping.h"
 #include "llvm/Support/CodeGen.h"
 #include "llvm/Support/Compiler.h"
+#include "llvm/Support/Debug.h"
 #include "llvm/TargetParser/Triple.h"
 #include <cstddef>
 #include <cstdint>
@@ -576,7 +577,22 @@ class LLVM_ABI Module {
   // Use global_size() to get the total number of global variables.
   // Use globals() to get the range of all global variables.
 
+  std::optional<GlobalValue::GUID> getGUID(const Value* V) const {
+    const auto It = ValueToGUIDMap.find(V);
+    if (It == ValueToGUIDMap.end()) {
+      return {};
+    }
+
+    return It->getSecond();
+  }
+
+  void insertGUID(const Value* V, GlobalValue::GUID GUID) {
+    ValueToGUIDMap[V] = GUID;
+  }
+
 private:
+  DenseMap<const Value*, GlobalValue::GUID> ValueToGUIDMap;
+
 /// @}
 /// @name Direct access to the globals list, functions list, and symbol table
 /// @{
diff --git a/llvm/include/llvm/IR/ModuleSummaryIndex.h b/llvm/include/llvm/IR/ModuleSummaryIndex.h
index b0b5ef9908a5e..07e019cbe48c4 100644
--- a/llvm/include/llvm/IR/ModuleSummaryIndex.h
+++ b/llvm/include/llvm/IR/ModuleSummaryIndex.h
@@ -29,6 +29,7 @@
 #include "llvm/IR/Module.h"
 #include "llvm/Support/Allocator.h"
 #include "llvm/Support/Compiler.h"
+#include "llvm/Support/Debug.h"
 #include "llvm/Support/InterleavedRange.h"
 #include "llvm/Support/ScaledNumber.h"
 #include "llvm/Support/StringSaver.h"
@@ -599,8 +600,8 @@ class GlobalValueSummary {
   bool wasPromoted() const { return Flags.Promoted; }
 
   void promote() {
-    assert(GlobalValue::isLocalLinkage(linkage()) &&
-           "unexpected (re-)promotion of non-local symbol");
+    assert(!GlobalValue::isExternalLinkage(linkage()) &&
+           "unexpected (re-)promotion of external symbol");
     assert(!Flags.Promoted);
     Flags.Promoted = true;
     Flags.Linkage = GlobalValue::LinkageTypes::ExternalLinkage;
@@ -1763,14 +1764,19 @@ class ModuleSummaryIndex {
     return ValueInfo(HaveGVs, VP);
   }
 
-  /// Return a ValueInfo for \p GV and mark it as belonging to GV.
-  ValueInfo getOrInsertValueInfo(const GlobalValue *GV) {
+  /// Return a ValueInfo for \p GV with GUID \p GUID and mark it as belonging to GV.
+  ValueInfo getOrInsertValueInfo(const GlobalValue *GV, GlobalValue::GUID GUID) {
     assert(HaveGVs);
-    auto VP = getOrInsertValuePtr(GV->getGUID());
+    auto VP = getOrInsertValuePtr(GUID);
     VP->second.U.GV = GV;
     return ValueInfo(HaveGVs, VP);
   }
 
+  /// Return a ValueInfo for \p GV and mark it as belonging to GV.
+  ValueInfo getOrInsertValueInfo(const GlobalValue *GV) {
+    return getOrInsertValueInfo(GV, GV->getGUID());
+  }
+
   /// Return the GUID for \p OriginalId in the OidGuidMap.
   GlobalValue::GUID getGUIDFromOriginalID(GlobalValue::GUID OriginalID) const {
     const auto I = OidGuidMap.find(OriginalID);
diff --git a/llvm/include/llvm/LTO/LTO.h b/llvm/include/llvm/LTO/LTO.h
index 38349b604b6b3..32a47b3c87e1d 100644
--- a/llvm/include/llvm/LTO/LTO.h
+++ b/llvm/include/llvm/LTO/LTO.h
@@ -572,6 +572,23 @@ class LTO {
     /// LTO backend.
     unsigned Partition = Unknown;
 
+    // FIXME: make private, and also make GlobalResolution a class, it has been
+    // becoming more than just a data bag.
+    GlobalValue::GUID GUID = 0;
+
+    void setGUID(GlobalValue::GUID G) {
+      assert(G);
+      assert(!GUID || GUID == G);
+      GUID = G;
+    }
+
+    GlobalValue::GUID getGUID() const {
+      return GUID ? GUID
+                  : GlobalValue::getGUIDAssumingExternalLinkage(
+                        GlobalValue::getGlobalIdentifier(
+                            IRName, GlobalValue::LinkageTypes::ExternalLinkage,
+                            ""));
+    }
     /// Special partition numbers.
     enum : unsigned {
       /// A partition number has not yet been assigned to this global.
diff --git a/llvm/include/llvm/Transforms/Utils/AssignGUID.h b/llvm/include/llvm/Transforms/Utils/AssignGUID.h
new file mode 100644
index 0000000000000..0abd450d5a522
--- /dev/null
+++ b/llvm/include/llvm/Transforms/Utils/AssignGUID.h
@@ -0,0 +1,42 @@
+//===-- AssignGUID.h - Unique identifier assignment pass --------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file provides a pass which assigns a a GUID (globally unique identifier)
+// to every GlobalValue in the module, according to its current name, linkage,
+// and originating file. This way we have a consistent identifier even when
+// these inputs to the GUID change (for instance, after externalising a global
+// in ThinLTO).
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TRANSFORMS_UTILS_ASSIGNGUID_H
+#define LLVM_TRANSFORMS_UTILS_ASSIGNGUID_H
+
+#include "llvm/IR/Module.h"
+#include "llvm/IR/PassManager.h"
+#include "llvm/Support/Debug.h"
+
+namespace llvm {
+
+class AssignGUIDPass : public PassInfoMixin<AssignGUIDPass> {
+public:
+  AssignGUIDPass() = default;
+
+  static void runOnModule(Module &M);
+
+  PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM) {
+    AssignGUIDPass::runOnModule(M);
+    return PreservedAnalyses::all();
+  }
+
+  static bool isRequired() { return true; }
+};
+
+} // end namespace llvm
+
+#endif // LLVM_TRANSFORMS_UTILS_ASSIGNGUID_H
\ No newline at end of file
diff --git a/llvm/lib/Analysis/CtxProfAnalysis.cpp b/llvm/lib/Analysis/CtxProfAnalysis.cpp
index c4abec02e765a..d4c00e4090220 100644
--- a/llvm/lib/Analysis/CtxProfAnalysis.cpp
+++ b/llvm/lib/Analysis/CtxProfAnalysis.cpp
@@ -50,9 +50,7 @@ static cl::opt<bool> ForceIsInSpecializedModule(
     "ctx-profile-force-is-specialized", cl::init(false),
     cl::desc("Treat the given module as-if it were containing the "
              "post-thinlink module containing the root"));
-
-const char *AssignGUIDPass::GUIDMetadataName = "guid";
-
+ 
 class ProfileAnnotatorImpl final {
   friend class ProfileAnnotator;
   class BBInfo;
@@ -420,33 +418,6 @@ bool ProfileAnnotator::getOutgoingBranchWeights(
   return MaxCount > 0;
 }
 
-PreservedAnalyses AssignGUIDPass::run(Module &M, ModuleAnalysisManager &MAM) {
-  for (auto &F : M.functions()) {
-    if (F.isDeclaration())
-      continue;
-    if (F.getMetadata(GUIDMetadataName))
-      continue;
-    const GlobalValue::GUID GUID = F.getGUID();
-    F.setMetadata(GUIDMetadataName,
-                  MDNode::get(M.getContext(),
-                              {ConstantAsMetadata::get(ConstantInt::get(
-                                  Type::getInt64Ty(M.getContext()), GUID))}));
-  }
-  return PreservedAnalyses::none();
-}
-
-GlobalValue::GUID AssignGUIDPass::getGUID(const Function &F) {
-  if (F.isDeclaration()) {
-    assert(GlobalValue::isExternalLinkage(F.getLinkage()));
-    return F.getGUID();
-  }
-  auto *MD = F.getMetadata(GUIDMetadataName);
-  assert(MD && "guid not found for defined function");
-  return cast<ConstantInt>(cast<ConstantAsMetadata>(MD->getOperand(0))
-                               ->getValue()
-                               ->stripPointerCasts())
-      ->getZExtValue();
-}
 AnalysisKey CtxProfAnalysis::Key;
 
 CtxProfAnalysis::CtxProfAnalysis(std::optional<StringRef> Profile)
@@ -515,7 +486,7 @@ PGOContextualProfile CtxProfAnalysis::run(Module &M,
   for (const auto &F : M) {
     if (F.isDeclaration())
       continue;
-    auto GUID = AssignGUIDPass::getGUID(F);
+    auto GUID = F.getGUID();
     assert(GUID && "guid not found for defined function");
     const auto &Entry = F.begin();
     uint32_t MaxCounters = 0; // we expect at least a counter.
@@ -549,13 +520,6 @@ PGOContextualProfile CtxProfAnalysis::run(Module &M,
   return Result;
 }
 
-GlobalValue::GUID
-PGOContextualProfile::getDefinedFunctionGUID(const Function &F) const {
-  if (auto It = FuncInfo.find(AssignGUIDPass::getGUID(F)); It != FuncInfo.end())
-    return It->first;
-  return 0;
-}
-
 CtxProfAnalysisPrinterPass::CtxProfAnalysisPrinterPass(raw_ostream &OS)
     : OS(OS), Mode(PrintLevel) {}
 
@@ -671,7 +635,7 @@ bool PGOContextualProfile::isInSpecializedModule() const {
 
 void PGOContextualProfile::update(Visitor V, const Function &F) {
   assert(isFunctionKnown(F));
-  GlobalValue::GUID G = getDefinedFunctionGUID(F);
+  GlobalValue::GUID G = F.getGUID();
   for (auto *Node = FuncInfo.find(G)->second.Index.Next; Node;
        Node = Node->Next)
     V(*reinterpret_cast<PGOCtxProfContext *>(Node));
@@ -682,7 +646,7 @@ void PGOContextualProfile::visit(ConstVisitor V, const Function *F) const {
     return preorderVisit<const PGOCtxProfContext::CallTargetMapTy,
                          const PGOCtxProfContext>(Profiles.Contexts, V);
   assert(isFunctionKnown(*F));
-  GlobalValue::GUID G = getDefinedFunctionGUID(*F);
+  GlobalValue::GUID G = F->getGUID();
   for (const auto *Node = FuncInfo.find(G)->second.Index.Next; Node;
        Node = Node->Next)
     V(*reinterpret_cast<const PGOCtxProfContext *>(Node));
diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp
index 551b88d8b435b..fcfa2ec2be342 100644
--- a/llvm/lib/AsmParser/LLParser.cpp
+++ b/llvm/lib/AsmParser/LLParser.cpp
@@ -1064,8 +1064,9 @@ bool LLParser::skipModuleSummaryEntry() {
   // support is in place we will look for the tokens corresponding to the
   // expected tags.
   if (Lex.getKind() != lltok::kw_gv && Lex.getKind() != lltok::kw_module &&
-      Lex.getKind() != lltok::kw_typeid && Lex.getKind() != lltok::kw_flags &&
-      Lex.getKind() != lltok::kw_blockcount)
+      Lex.getKind() != lltok::kw_typeid &&
+      Lex.getKind() != lltok::kw_typeidCompatibleVTable &&
+      Lex.getKind() != lltok::kw_flags && Lex.getKind() != lltok::kw_blockcount)
     return tokError(
         "Expected 'gv', 'module', 'typeid', 'flags' or 'blockcount' at the "
         "start of summary entry");
@@ -9847,7 +9848,12 @@ bool LLParser::addGlobalValueToIndex(
       if (!GV)
         return error(Loc, "Reference to undefined global \"" + Name + "\"");
 
-      VI = Index->getOrInsertValueInfo(GV);
+      // Be a little lenient here, to accomodate older files without GUIDs
+      // already computed and assigned as metadata.
+      auto MaybeGUID = GV->getGUIDIfAssigned();
+      GUID = MaybeGUID ? *MaybeGUID : GlobalValue::getGUIDAssumingExternalLinkage(GV->getName());
+
+      VI = Index->getOrInsertValueInfo(GV, GUID);
     } else {
       assert(
           (!GlobalValue::isLocalLinkage(Linkage) || !SourceFileName.empty()) &&
diff --git a/llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp b/llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp
index 911ec7501eb8b..6574ab7a93c58 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeAnalyzer.cpp
@@ -142,6 +142,7 @@ GetCodeName(unsigned CodeID, unsigned BlockID,
       STRINGIFY_CODE(MODULE_CODE, METADATA_VALUES_UNUSED)
       STRINGIFY_CODE(MODULE_CODE, SOURCE_FILENAME)
       STRINGIFY_CODE(MODULE_CODE, HASH)
+      STRINGIFY_CODE(MODULE_CODE, GUIDLIST)
     }
   case bitc::IDENTIFICATION_BLOCK_ID:
     switch (CodeID) {
diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
index a781a43112f8c..0b6ce590f00af 100644
--- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
+++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp
@@ -696,6 +696,9 @@ class BitcodeReader : public BitcodeReaderBase, public GVMaterializer {
 
   std::optional<ValueTypeCallbackTy> ValueTypeCallback;
 
+  /// A list of GUIDs defined by this module. Indexed by ValueID.
+  std::vector<GlobalValue::GUID> GUIDList;
+
 public:
   BitcodeReader(BitstreamCursor Stream, StringRef Strtab,
                 StringRef ProducerIdentification, LLVMContext &Context);
@@ -974,9 +977,10 @@ class ModuleSummaryIndexBitcodeReader : public BitcodeReaderBase {
 
   /// Callback to ask whether a symbol is the prevailing copy when invoked
   /// during combined index building.
-  std::function<bool(GlobalValue::GUID)> IsPrevailing;
+  std::function<bool(StringRef)> IsPrevailing = nullptr;
+  std::function<void(ValueInfo)> OnValueInfo = nullptr;
 
-  /// Saves the stack ids from the STACK_IDS record to consult when adding stack
+  /// Saves the stack ids from the STACK_IDS record to consult ...
[truncated]

Copy link
Copy Markdown
Contributor

@teresajohnson teresajohnson left a comment

Choose a reason for hiding this comment

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

Sorry for the slow review. A bunch of misc mostly minor questions and comments.

Comment thread llvm/include/llvm/IR/Module.h Outdated
Comment thread llvm/include/llvm/IR/ModuleSummaryIndex.h Outdated
Comment thread llvm/include/llvm/LTO/LTO.h Outdated
Comment thread llvm/lib/AsmParser/LLParser.cpp
Comment thread llvm/lib/Bitcode/Reader/BitcodeReader.cpp
Comment thread llvm/lib/LTO/LTO.cpp
Comment thread llvm/lib/LTO/LTO.cpp
Comment thread llvm/lib/Transforms/IPO/FunctionImport.cpp Outdated
Comment thread llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp Outdated
Comment thread llvm/tools/opt/NewPMDriver.cpp
@llvmbot llvmbot added the clang:codegen IR generation bugs: mangling, exceptions, etc. label Mar 30, 2026
@mtrofin
Copy link
Copy Markdown
Member

mtrofin commented May 27, 2026

Hello! This commit appears to be breaking our downstream toolchain support for LTO, particularly constant merging. I'll try to come up with a reproducer, but it's difficult because we use a proprietary linker. However, I wanted to give you a heads-up.

If it helps: do you also have a custom pass pipeline? If the issue is an assert about missing guid metadata, you may just need to run the AssignGUID pass earlier on in your pass pipeline (see also the referenced RFC)

@nikic
Copy link
Copy Markdown
Contributor

nikic commented May 28, 2026

It looks like this causes a compile-time regression, esp. for ThinLTO: https://llvm-compile-time-tracker.com/compare.php?from=e0ef143823ce8f733c50fae675f05d0df0f702e5&to=7938535a54b51a4ca84821b7766d68049c9aa895&stat=instructions:u Can you please investigate what the cause of the issue is? This doesn't seem like a change that should impact compile-time if implemented correctly.

@john-brawn-arm
Copy link
Copy Markdown
Contributor

I'm also seeing this cause global merging to no longer occur.

tmp.c

int var1 = 0;
int var2 = 1;
int var3 = 2;

__attribute__((noinline)) void fn() {
  var1 += var2 + var3;
}

int main(int argc, char **argv) {
  var1 = *argv[0];
  var2 = *argv[1];
  var3 = *argv[2];
  fn();
  return var1;
}

Compiled with clang --target=arm-none-eabi -mcpu=cortex-m55 -mfloat-abi=hard -O3 tmp.c -flto -c the IR is

; ModuleID = 'new.o'            
source_filename = "tmp.c"      
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "thumbv8.1m.main-unknown-none-eabihf" 
                                                     
@var1 = dso_local local_unnamed_addr global i32 0, align 4, !guid !0
@var2 = dso_local local_unnamed_addr global i32 1, align 4, !guid !1
@var3 = dso_local local_unnamed_addr global i32 2, align 4, !guid !2
                                                     
; Function Attrs: mustprogress nofree noinline norecurse nosync nounwind willreturn memory(readwrite, argmem: none, inaccessiblemem: none, target_mem: none)
define dso_local void @fn() local_unnamed_addr #0 !guid !13 {
  %1 = load i32, ptr @var2, align 4, !tbaa !9
  %2 = load i32, ptr @var3, align 4, !tbaa !9
  %3 = add nsw i32 %2, %1        
  %4 = load i32, ptr @var1, align 4, !tbaa !9
  %5 = add nsw i32 %3, %4                 
  store i32 %5, ptr @var1, align 4, !tbaa !9
  ret void              
}                                                                                                          

; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(readwrite, inaccessiblemem: none, target_mem: none)
define dso_local i32 @main(i32 noundef %0, ptr noundef readonly captures(none) %1) local_unnamed_addr #1 !guid !14 {
  %3 = load ptr, ptr %1, align 4, !tbaa !15
  %4 = load i8, ptr %3, align 1, !tbaa !18
  %5 = zext i8 %4 to i32
  store i32 %5, ptr @var1, align 4, !tbaa !9
  %6 = getelementptr inbounds nuw i8, ptr %1, i32 4
  %7 = load ptr, ptr %6, align 4, !tbaa !15
  %8 = load i8, ptr %7, align 1, !tbaa !18
  %9 = zext i8 %8 to i32
  store i32 %9, ptr @var2, align 4, !tbaa !9
  %10 = getelementptr inbounds nuw i8, ptr %1, i32 8
  %11 = load ptr, ptr %10, align 4, !tbaa !15
  %12 = load i8, ptr %11, align 1, !tbaa !18
  %13 = zext i8 %12 to i32
  store i32 %13, ptr @var3, align 4, !tbaa !9
  tail call void @fn()
  %14 = load i32, ptr @var1, align 4, !tbaa !9
  ret i32 %14
}

attributes #0 = { mustprogress nofree noinline norecurse nosync nounwind willreturn memory(readwrite, argmem: none, inaccessiblemem: none, target_mem: none) "no-trapping-math"="true" "stack-protector-buffer-size"="
8" "target-cpu"="cortex-m55" "target-features"="+armv8.1-m.main,+dsp,+fp-armv8d16,+fp-armv8d16sp,+fp16,+fp64,+fullfp16,+hwdiv,+lob,+mve,+mve.fp,+ras,+thumb-mode,+vfp2,+vfp2sp,+vfp3d16,+vfp3d16sp,+vfp4d16,+vfp4d16sp
,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-fp-armv8,-fp-armv8sp,-fp16fml,-hwdiv-arm,-i8mm,-neon,-pacbti,-sb,-sha2,-vfp3,-vfp3sp,-vfp4,-vfp4sp" }
attributes #1 = { mustprogress nofree norecurse nosync nounwind willreturn memory(readwrite, inaccessiblemem: none, target_mem: none) "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="cortex
-m55" "target-features"="+armv8.1-m.main,+dsp,+fp-armv8d16,+fp-armv8d16sp,+fp16,+fp64,+fullfp16,+hwdiv,+lob,+mve,+mve.fp,+ras,+thumb-mode,+vfp2,+vfp2sp,+vfp3d16,+vfp3d16sp,+vfp4d16,+vfp4d16sp,-aes,-bf16,-cdecp0,-cd
ecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-fp-armv8,-fp-armv8sp,-fp16fml,-hwdiv-arm,-i8mm,-neon,-pacbti,-sb,-sha2,-vfp3,-vfp3sp,-vfp4,-vfp4sp" }

!llvm.module.flags = !{!3, !4, !5, !6, !7}
!llvm.ident = !{!8}
!llvm.errno.tbaa = !{!9}

!0 = !{i64 -7857281302475341212}
!1 = !{i64 -3534809055184556418}
!2 = !{i64 6020659679014300478}
!3 = !{i32 1, !"min_enum_size", i32 4}
!4 = !{i32 4, !"arm-eabi-fp-denormal", i32 1}
!5 = !{i32 8, !"arm-eabi-fp-number-model", i32 3}
!6 = !{i32 1, !"ThinLTO", i32 0}
!7 = !{i32 1, !"EnableSplitLTOUnit", i32 1}
!8 = !{!"clang version 23.0.0git"}
!9 = !{!10, !10, i64 0}
!10 = !{!"int", !11, i64 0}
!11 = !{!"omnipotent char", !12, i64 0}
!12 = !{!"Simple C/C++ TBAA"}
!13 = !{i64 6064007760896105805}
!14 = !{i64 -2624081020897602054}
!15 = !{!16, !16, i64 0}
!16 = !{!"p1 omnipotent char", !17, i64 0}
!17 = !{!"any pointer", !11, i64 0}
!18 = !{!11, !11, i64 0}

Running llvm-lto tmp.o -filetype=asm -o tmp.s -exported-symbol main -O3 the output for fn is

fn:                         
        .fnstart                              
        movw    r0, :lower16:var2
        movw    r1, :lower16:var3
        movt    r0, :upper16:var2
        movw    r2, :lower16:var1
        movt    r1, :upper16:var3
        ldr     r0, [r0]    
        ldr     r1, [r1]                         
        movt    r2, :upper16:var1
        ldr     r3, [r2]
        add     r0, r1
        add     r0, r3 
        str     r0, [r2]
        bx      lr          
.Lfunc_end0:                                     
        .size   fn, .Lfunc_end0-fn
        .cantunwind
        .fnend

without the guid metadata the output for fn is

fn:                    
        .fnstart
        movw    r0, :lower16:.L_MergedGlobals
        movw    r2, :lower16:var1                                                                          
        movt    r0, :upper16:.L_MergedGlobals
        ldrd    r0, r1, [r0]
        movt    r2, :upper16:var1
        ldr     r3, [r2]
        add     r0, r1             
        add     r0, r3
        str     r0, [r2]
        bx      lr     
.Lfunc_end0:             
        .size   fn, .Lfunc_end0-fn
        .cantunwind                      
        .fnend

@john-brawn-arm
Copy link
Copy Markdown
Contributor

Looks like this is probably due to this bit in GlobalMergeImpl::run in llvm/lib/CodeGen/GlobalMerge.cpp

    // Don't merge globals with metadata other than !dbg, as this is essentially                                                                                                                                      
    // equivalent to adding metadata to an existing global, which is not                                                                                                                                              
    // necessarily a correct transformation depending on the specific metadata's                                                                                                                                      
    // semantics. We will later use copyMetadata() to copy metadata from                                                                                                                                              
    // component globals to the combined global, which only knows how to do this                                                                                                                                      
    // correctly for !dbg (and !type, but by this point LowerTypeTests will have                                                                                                                                      
    // already run).                                                                                                                                                                                                  
    if (GV.hasMetadataOtherThanDebugLoc())
      continue;

@mtrofin
Copy link
Copy Markdown
Member

mtrofin commented May 28, 2026

Looking at both today (and thanks @john-brawn-arm for the pointer!)

@google-yfyang
Copy link
Copy Markdown
Contributor

google-yfyang commented May 28, 2026

This is causing quite a few clang crashes with internal google code that are built with fdo and thinlto enabled. We don't have a reproducer yet that can be shared externally but here is the modified crash stack trace when running with clang-checked.

F0000 00:00:1780000217.029498 1750196 logging.cc:52] assert.h assertion failed atllvm/lib/Transforms/IPO/MemProfContextDisambiguation.cpp:5902 in bool llvm::MemProfContextDisambiguation::applyImport(Module &): GVSummary && GVSummary->modulePath() == SrcModule
*** Check failure stack trace: ***
    @     0x56372f6cfda4  absl::log_internal::LogMessage::SendToLog()
    @     0x56372f6cfd65  absl::log_internal::LogMessage::Flush()
    @     0x56372f8b1264  __assert_fail
    @     0x56372ded8b33  llvm::MemProfContextDisambiguation::applyImport()
    @     0x56372dee1e68  llvm::MemProfContextDisambiguation::run()
    @     0x56372f327c39  llvm::PassManager<>::run()
    @     0x56372971e24b  llvm::lto::opt()
    @     0x563729720dd4  llvm::lto::thinBackend()::$_1::operator()()
    @     0x563729720c71  llvm::lto::thinBackend()
    @     0x56372944f5b6  clang::emitBackendOutput()
    @     0x5637290f2a67  clang::CodeGenAction::ExecuteAction()
    @     0x563729f80aea  clang::FrontendAction::Execute()
    @     0x563729ef152d  clang::CompilerInstance::ExecuteAction()
    @     0x5637290ec68e  clang::ExecuteCompilerInvocation()
    @     0x5637290df858  cc1_main()
    @     0x5637290dc959  ExecuteCC1Tool()
    @     0x5637290df10c  llvm::function_ref<>::callback_fn<>()
    @     0x563729cafbbe  llvm::function_ref<>::callback_fn<>()
    @     0x56372f50a41c  llvm::CrashRecoveryContext::RunSafely()
    @     0x563729caf1a4  clang::driver::CC1Command::Execute()
    @     0x563729c65993  clang::driver::Compilation::ExecuteCommand()
    @     0x563729c65c1f  clang::driver::Compilation::ExecuteJobs()
    @     0x563729c884c0  clang::driver::Driver::ExecuteCompilation()
    @     0x5637290dbf30  clang_main()
    @     0x5637290da27a  main
    @     0x7fa5e8bb4f12  __libc_start_main
    @     0x5637290da1aa  _start
PLEASE submit a bug report to http://go/clang-crash-bug and include the crash backtrace, preprocessed source, and associated run script.

@mtrofin
Copy link
Copy Markdown
Member

mtrofin commented May 28, 2026

It looks like this causes a compile-time regression, esp. for ThinLTO: https://llvm-compile-time-tracker.com/compare.php?from=e0ef143823ce8f733c50fae675f05d0df0f702e5&to=7938535a54b51a4ca84821b7766d68049c9aa895&stat=instructions:u Can you please investigate what the cause of the issue is? This doesn't seem like a change that should impact compile-time if implemented correctly.

I was able to repro for the file causing the largest relative regression - libclamav_entconv (1.67% more instructions retired), in the ReleaseThinLTO config.

Turns out it has lots of globals, and just 7, not particularly large, functions. The result is a large number of global values to write to the global summary, and also lots of guids. The function bodies aren't particularly large, though, so the extra bitcode writing because of the guids is detectable.

mtrofin added a commit that referenced this pull request May 29, 2026
Follow up from PR #184065. Since globals or constants would now have
their GUID pre-calculated and affixed with metadata, we need to tolerate
its presence when merging. To that effect, we rename (and change
accordingly) `GlobalObject::hasMetadataOtherThanDebugLoc` to
`...AndGuid`.

In the case of Constants, they are merged into one of them, which then
keeps its guid, while the rest are erased. In the case of
GlobalVariables, a new one is created, and we will give it its own guid.
@b-sumner
Copy link
Copy Markdown

This patch has resulted in the following appearing for me at link time:

ptr @_Z4set1Pi
ld.lld: warning: ignoring invalid debug info in ld-temp.o
function declaration may only have a unique !dbg attachment

@boomanaiden154
Copy link
Copy Markdown
Contributor

This patch has resulted in the following appearing for me at link time:

This needs a reproducer.

@b-sumner
Copy link
Copy Markdown

This patch has resulted in the following appearing for me at link time:

ptr @_Z4set1Pi ld.lld: warning: ignoring invalid debug info in ld-temp.o function declaration may only have a unique !dbg attachment

We had not applied #200323 yet, so this warning may already be gone.

@nikic
Copy link
Copy Markdown
Contributor

nikic commented May 29, 2026

It looks like this causes a compile-time regression, esp. for ThinLTO: https://llvm-compile-time-tracker.com/compare.php?from=e0ef143823ce8f733c50fae675f05d0df0f702e5&to=7938535a54b51a4ca84821b7766d68049c9aa895&stat=instructions:u Can you please investigate what the cause of the issue is? This doesn't seem like a change that should impact compile-time if implemented correctly.

I was able to repro for the file causing the largest relative regression - libclamav_entconv (1.67% more instructions retired), in the ReleaseThinLTO config.

Turns out it has lots of globals, and just 7, not particularly large, functions. The result is a large number of global values to write to the global summary, and also lots of guids. The function bodies aren't particularly large, though, so the extra bitcode writing because of the guids is detectable.

I don't think this is the whole story. This causes a ~0.3% regression during the thin link of clang (and llvm-tblgen / clang-tblgen), and those are certainly not workloads dominated by globals. This seems like a pretty across the board regression for ThinLTO.

@mtrofin
Copy link
Copy Markdown
Member

mtrofin commented May 29, 2026

It looks like this causes a compile-time regression, esp. for ThinLTO: https://llvm-compile-time-tracker.com/compare.php?from=e0ef143823ce8f733c50fae675f05d0df0f702e5&to=7938535a54b51a4ca84821b7766d68049c9aa895&stat=instructions:u Can you please investigate what the cause of the issue is? This doesn't seem like a change that should impact compile-time if implemented correctly.

I was able to repro for the file causing the largest relative regression - libclamav_entconv (1.67% more instructions retired), in the ReleaseThinLTO config.
Turns out it has lots of globals, and just 7, not particularly large, functions. The result is a large number of global values to write to the global summary, and also lots of guids. The function bodies aren't particularly large, though, so the extra bitcode writing because of the guids is detectable.

I don't think this is the whole story. This causes a ~0.3% regression during the thin link of clang (and llvm-tblgen / clang-tblgen), and those are certainly not workloads dominated by globals. This seems like a pretty across the board regression for ThinLTO.

It would be visible during ThinTLO mainly, since that's when we'd write summaries which now contain an extra table, but I agree that confirming that's all we're seeing is a good idea. How do the clang builds get measured by the compile time tracker, perf stat or it runs everything (the whole ninja build) in valgrind? (one thing I noticed in the ninja traces is that one job has more threads, and some instruction count overhead might be just from synchronization maybe - so I'd want to do a bunch of repeated measurements and see what the noise is)

@boomanaiden154
Copy link
Copy Markdown
Contributor

It would be visible during ThinTLO mainly, since that's when we'd write summaries which now contain an extra table, but I agree that confirming that's all we're seeing is a good idea. How do the clang builds get measured by the compile time tracker, perf stat or it runs everything (the whole ninja build) in valgrind? (one thing I noticed in the ninja traces is that one job has more threads, and some instruction count overhead might be just from synchronization maybe - so I'd want to do a bunch of repeated measurements and see what the noise is)

CTT I believe uses perf stat -e instructions:u. CTT shouldn't be running any jobs with multiple threads, other than maybe lld which I don't think should matter here. That regression is definitely above the noise floor for CTT.

@vitalybuka
Copy link
Copy Markdown
Contributor

How this could work? Madata is unreliable, CFI requires reliable GUIDS.
cc @pcc

@vitalybuka
Copy link
Copy Markdown
Contributor

vitalybuka commented May 29, 2026

We should revert and follow up with issues.

BTW. It would be easier to deal with regressions if it was landed as set of smaller patches according to https://llvm.org/docs/CodeReview.html#code-reviews-speed-and-reciprocity.

@mtrofin
Copy link
Copy Markdown
Member

mtrofin commented May 29, 2026

We should revert and follow up with issues.

BTW. It would be easier to deal with regressions if it was landed as set of smaller patches according to https://llvm.org/docs/CodeReview.html#code-reviews-speed-and-reciprocity.

There have already been some preparatory patches. The bulk of the patch is test changes due to tests assuming stuff like the last character in a declaration is { (which now changes because there's a !guid, too).

@mtrofin
Copy link
Copy Markdown
Member

mtrofin commented May 29, 2026

How this could work? Madata is unreliable, CFI requires reliable GUIDS. cc @pcc

The RFC (linked in the patch) details this more, this patch actually makes GUIDs and their presence by globals more reliable rather than less. Before, with the on-the-fly name/linkage based computation, a lot of complexity was required to handle things like linkage changes and name suffixes (see the RFC)

mtrofin added a commit to mtrofin/llvm-project that referenced this pull request May 30, 2026
LowerTypeTests and the ModuleSummaryIndex mapped CFI function declarations
and definitions based purely on the string name. If an internal function
was promoted and renamed during ThinLTO, its name-derived GUID would change,
leading to mismatches, resulting in runtime traps.

This patch saves the GUIDs in the CFI tables. LowerTypeTests doesn't need to
perform name-based lookups, relying on this GUID instead.
zengshzh pushed a commit to zengshzh/llvm-project that referenced this pull request May 30, 2026
This allows us to keep GUIDs consistent across compilation phases which
may change the name or linkage type.

See https://discourse.llvm.org/t/rfc-keep-globalvalue-guids-stable/84801

This is a large change since the addition of metadata breaks many tests.
The test changes are mostly just trivial changes to checks to get them
passing.
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Jun 1, 2026
LLVM 23: Run AssignGUIDPass in some places

llvm/llvm-project#184065 introduced the new pass.

I'm not 100% confident that this is the correct patch, but it makes all the previously failing `run-make` "lto" tests pass on my machine.

I added the pass to the `if (NeedThinLTOBufferPasses)` section to match the `addRequiredLTOPreLinkPasses` changes from the LLVM PR, and to the `LLVMRustModuleSerialize` section based on the stack trace of an LLVM assertion.

The IR may now contain an additional `!guid` attribute, requiring some codegen test expectations to be updated. I mostly followed the pattern from the LLVM PR, and removed trailing `{`s. (For both, tests that were failing locally, as well as potentially problematic expectations identified via grep, in case the test wasn't being run locally).

@rustbot label llvm-main
rust-timer added a commit to rust-lang/rust that referenced this pull request Jun 2, 2026
Rollup merge of #157055 - TimNN:guid, r=cuviper

LLVM 23: Run AssignGUIDPass in some places

llvm/llvm-project#184065 introduced the new pass.

I'm not 100% confident that this is the correct patch, but it makes all the previously failing `run-make` "lto" tests pass on my machine.

I added the pass to the `if (NeedThinLTOBufferPasses)` section to match the `addRequiredLTOPreLinkPasses` changes from the LLVM PR, and to the `LLVMRustModuleSerialize` section based on the stack trace of an LLVM assertion.

The IR may now contain an additional `!guid` attribute, requiring some codegen test expectations to be updated. I mostly followed the pattern from the LLVM PR, and removed trailing `{`s. (For both, tests that were failing locally, as well as potentially problematic expectations identified via grep, in case the test wasn't being run locally).

@rustbot label llvm-main
pull Bot pushed a commit to xtqqczze/rust-lang-miri that referenced this pull request Jun 2, 2026
LLVM 23: Run AssignGUIDPass in some places

llvm/llvm-project#184065 introduced the new pass.

I'm not 100% confident that this is the correct patch, but it makes all the previously failing `run-make` "lto" tests pass on my machine.

I added the pass to the `if (NeedThinLTOBufferPasses)` section to match the `addRequiredLTOPreLinkPasses` changes from the LLVM PR, and to the `LLVMRustModuleSerialize` section based on the stack trace of an LLVM assertion.

The IR may now contain an additional `!guid` attribute, requiring some codegen test expectations to be updated. I mostly followed the pattern from the LLVM PR, and removed trailing `{`s. (For both, tests that were failing locally, as well as potentially problematic expectations identified via grep, in case the test wasn't being run locally).

@rustbot label llvm-main
@zmodem
Copy link
Copy Markdown
Contributor

zmodem commented Jun 2, 2026

We're hitting CFI failures after this (https://crbug.com/519012435). I see Vitaly already asked for it to be reverted. Can we revert back to green?

mtrofin added a commit that referenced this pull request Jun 2, 2026
…201194)

There is a relation between CFI and ThinLTO GUIDs that still needs to be
disentangled first. Note that we leave the `MD_unique_id` in
`FixedMetadataKinds.def` to avoid needing to re-number it later. Plus
the metadata string ("guid") itself is used by ctxprof.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend:X86 clang:codegen IR generation bugs: mangling, exceptions, etc. llvm:analysis Includes value tracking, cost tables and constant folding llvm:ir llvm:transforms LTO Link time optimization (regular/full LTO or ThinLTO) PGO Profile Guided Optimizations

Projects

None yet

Development

Successfully merging this pull request may close these issues.