[llvm] [dyndbg][LLVM] Implement dynamic debugging support (PR #194854)

Orlando Cazalet-Hyams via llvm-commits llvm-commits at lists.llvm.org
Mon Jul 6 06:03:18 PDT 2026


https://github.com/OCHyams updated https://github.com/llvm/llvm-project/pull/194854

>From 6cab7a15e3c89b9ab3becb02b28d788308167fe7 Mon Sep 17 00:00:00 2001
From: Orlando Cazalet-Hyams <orlando.hyams at sony.com>
Date: Fri, 24 Apr 2026 10:28:03 +0100
Subject: [PATCH 1/6] [dyndbg][LLVM] Implement dynamic debugging support

This patch adds support to LLVM to prepare a module for dynamic
debugging. `prepareForDynamicDebugging` modifies the input module (intended to
be the "optimized" module) and returns a modified clone (intended to be the
"unoptimized" module).

The (to be) optimized module holds global data referred to by both modules, and
all calls in the (to be) unoptimized module are to the optimized module
functions. To facilitate this the optimized module is modified, adding
external-linkage aliases for local symbols.

See https://discourse.llvm.org/t/90113 for more detail (including a diagram).

In later patches Clang will use this utility to implement ahead of time dynamic
debugging (compiling both optimized and unoptimized modules fully), though
additional modes may be supported in the future.
---
 .../llvm/Transforms/Utils/DynamicDebugging.h  |  64 +++
 llvm/lib/Transforms/Utils/CMakeLists.txt      |   1 +
 .../lib/Transforms/Utils/DynamicDebugging.cpp | 164 ++++++
 .../unittests/Transforms/Utils/CMakeLists.txt |   1 +
 .../Transforms/Utils/DynamicDebuggingTest.cpp | 524 ++++++++++++++++++
 5 files changed, 754 insertions(+)
 create mode 100644 llvm/include/llvm/Transforms/Utils/DynamicDebugging.h
 create mode 100644 llvm/lib/Transforms/Utils/DynamicDebugging.cpp
 create mode 100644 llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp

diff --git a/llvm/include/llvm/Transforms/Utils/DynamicDebugging.h b/llvm/include/llvm/Transforms/Utils/DynamicDebugging.h
new file mode 100644
index 0000000000000..c2860d2502bf7
--- /dev/null
+++ b/llvm/include/llvm/Transforms/Utils/DynamicDebugging.h
@@ -0,0 +1,64 @@
+//===- DynamicDebugging.h - Dynamic Debugging utils ------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TRANSFORMS_UTILS_DYNAMIC_DEBUGGING_H
+#define LLVM_TRANSFORMS_UTILS_DYNAMIC_DEBUGGING_H
+
+#include "llvm/ADT/StringRef.h"
+#include <memory>
+
+namespace llvm {
+class Module;
+
+/// Modify \p M to prepare it for dynamic debugging before running
+/// optimizations. Return a clone of the module which references global
+/// values in \p M. The names of the cloned function definitions in this
+//  module are prefixed with “__dyndbg.”.
+///
+/// \p M requires debug info. Note any instrumentation in \p M will be
+/// cloned into the returned module.
+///
+/// Aliases with external linkage are created for GlobalValues in \p M that
+/// have local (non-weak) linkage and are not in a COMDAT. These names of these
+/// aliases are appended with \p PromotionSuffix which should be unique to the
+/// translation unit. If it's not unique then linking may result in multiple
+/// definition errors.
+///
+/// Input pseudo-code:
+/// +----------------------------------------------+
+/// | internal global g                            |
+/// | internal function loc() { return g }         |
+/// | exported function ext() { return loc() }     |
+/// +----------------------------------------------+
+///
+/// M becomes:
+/// +----------------------------------------------+
+/// | internal global g                            |
+/// | internal function loc() { return g }         |
+/// | exported function ext() { return loc() }     |<---+
+/// | exported loc.promo = alias of loc            |<--+|
+/// | exported g.promo = alias of g                |<-+||
+/// +----------------------------------------------+  |||
+///                                                   |||
+/// Returned Module:                                  |||
+/// +----------------------------------------------+  |||
+/// | external global g.promo                      |--+||
+/// | external function loc.promo()                |---+|
+/// | external function ext()                      |----+
+/// | exported function __dyndbg.loc.promo() {     |
+/// |   return g.promo                             |
+/// | }                                            |
+/// | exported function __dyndbg.ext() {           |
+/// |   return return loc.promo()                  |
+/// | }                                            |
+/// +----------------------------------------------+
+std::unique_ptr<Module> prepareForDynamicDebugging(Module *M,
+                                                   StringRef PromotionSuffix);
+} // namespace llvm
+
+#endif
diff --git a/llvm/lib/Transforms/Utils/CMakeLists.txt b/llvm/lib/Transforms/Utils/CMakeLists.txt
index 2b5f5cf344e60..13ff92773e8aa 100644
--- a/llvm/lib/Transforms/Utils/CMakeLists.txt
+++ b/llvm/lib/Transforms/Utils/CMakeLists.txt
@@ -23,6 +23,7 @@ add_llvm_component_library(LLVMTransformUtils
   DebugSSAUpdater.cpp
   DeclareRuntimeLibcalls.cpp
   DemoteRegToStack.cpp
+  DynamicDebugging.cpp
   DXILUpgrade.cpp
   EntryExitInstrumenter.cpp
   EscapeEnumerator.cpp
diff --git a/llvm/lib/Transforms/Utils/DynamicDebugging.cpp b/llvm/lib/Transforms/Utils/DynamicDebugging.cpp
new file mode 100644
index 0000000000000..4f5a9fb8bcae6
--- /dev/null
+++ b/llvm/lib/Transforms/Utils/DynamicDebugging.cpp
@@ -0,0 +1,164 @@
+//===- DynamicDebugging.cpp - Dynamic Debugging utils --------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Transforms/Utils/DynamicDebugging.h"
+#include "llvm/IR/Module.h"
+#include "llvm/Transforms/Utils/Cloning.h"
+#include "llvm/Transforms/Utils/ModuleUtils.h"
+#include "llvm/Transforms/Utils/ValueMapper.h"
+
+using namespace llvm;
+
+std::unique_ptr<Module>
+llvm::prepareForDynamicDebugging(Module *M, StringRef PromotionSuffix) {
+  using namespace llvm;
+  assert(M->getNamedMetadata("llvm.dbg.cu") &&
+         "Expected module with debug info");
+
+  auto ShouldPromoteGlobal = [](const GlobalValue &GV) {
+    if (!GV.hasLocalLinkage())
+      return false;
+
+    // Local symbols in a comdat shouldn't be promoted either.
+    // This can happen with (at least) __cxx_global_var_init (which is local
+    // and may initialize an ODR-weak global variable).
+    if (GV.hasComdat())
+      return false;
+
+    return true;
+  };
+
+  // Clone functions definitions only - CloneModule will clone data definitions
+  // as declarations. We rename these and explicitly set their linkage later.
+  auto ShouldCloneDefinition = [](const GlobalValue *GV) {
+    return isa<Function>(GV);
+  };
+  ValueToValueMapTy VMap;
+  std::unique_ptr<Module> UnoptM = CloneModule(*M, VMap, ShouldCloneDefinition);
+
+  // Insert declarations into Inner that point to Outer, apply attributes to
+  // Outer functions.
+  DenseMap<Function *, Function *> OuterDefToInnerDecl;
+  for (Function &OuterDef : M->functions()) {
+    if (OuterDef.isDeclaration())
+      continue;
+
+    // Find the Inner version of Outer's function.
+    Function *InnerDef = cast<Function>(VMap[&OuterDef]);
+
+    // Apply some attributes to both Inner and Outer defs.
+    {
+      // Unoptimized module wants no inlining at all.
+      InnerDef->addFnAttr(Attribute::NoInline);
+      InnerDef->removeFnAttr(Attribute::AlwaysInline);
+
+      // Apply optnone, remove clashing attributes.
+      InnerDef->addFnAttr(Attribute::OptimizeNone);
+      InnerDef->removeFnAttr(Attribute::OptimizeForSize);
+      InnerDef->removeFnAttr(Attribute::MinSize);
+
+      // Add attributes to the outer-object functions to ensure they're
+      // always patchable. TODO: Add patch bytes size/value for other targets.
+      if (M->getTargetTriple().isX86_64()) {
+        OuterDef.addFnAttr("tail-pad-to-size", "5");
+        OuterDef.addFnAttr("tail-pad-value", "144"); // 0x90
+        OuterDef.addFnAttr("no-func-spec");
+      }
+    }
+
+    // Apply COMDAT grouping to the clone if OuterDef is in one.
+    if (OuterDef.hasComdat()) {
+      std::string NewComdat =
+          Twine("__dyndbg." + OuterDef.getComdat()->getName()).str();
+      Comdat *C = M->getOrInsertComdat(NewComdat);
+      C->setSelectionKind(OuterDef.getComdat()->getSelectionKind());
+      InnerDef->setComdat(C);
+    }
+
+    // Rename Inner's copy and set appropriate linkage depending on whether
+    // it'll get promoted in Outer or not.
+    if (ShouldPromoteGlobal(OuterDef)) {
+      InnerDef->setName("__dyndbg." + InnerDef->getName() + PromotionSuffix);
+      InnerDef->setLinkage(GlobalValue::ExternalLinkage);
+      InnerDef->setVisibility(GlobalValue::HiddenVisibility);
+    } else {
+      InnerDef->setName("__dyndbg." + InnerDef->getName());
+      InnerDef->setLinkage(OuterDef.getLinkage());
+      InnerDef->setVisibility(OuterDef.getVisibility());
+    }
+
+    // Create Inner's external reference to Outer's version.
+    Function *InnerDeclOfOuterDef = Function::Create(
+        cast<FunctionType>(OuterDef.getValueType()), OuterDef.getLinkage(),
+        OuterDef.getAddressSpace(), OuterDef.getName(), UnoptM.get());
+    InnerDeclOfOuterDef->copyAttributesFrom(&OuterDef);
+    // Re-set linkage and visibility after copyAttributesFrom.
+    InnerDeclOfOuterDef->setLinkage(GlobalValue::ExternalLinkage);
+    InnerDeclOfOuterDef->setPersonalityFn(nullptr);
+
+    // Replace Inner uses of function with that external reference.
+    InnerDef->replaceAllUsesWith(InnerDeclOfOuterDef);
+
+    VMap[&OuterDef] = InnerDeclOfOuterDef;
+  }
+
+  // Add Outer aliases for globals with internal linkage, adding
+  // ".dyndbg.<TU-unique-hash>" suffix. Update Inner's external references to
+  // these promoted functions to use their new names.
+  SmallVector<GlobalValue *> GlobalsToPreserve;
+  for (GlobalValue &GV : M->global_values()) {
+    // If the global is used but may be discarded after optimizations
+    // (e.g. inlining) then ensure it's marked as compiler-used to prevent
+    // that. It may be referenced from the inner module.
+    if (GV.isDiscardableIfUnused()) {
+      if (GV.getNumUses()) {
+        GlobalsToPreserve.push_back(&GV);
+      } else {
+        // No uses, so the inner module doesn't need a reference nor do we need
+        // to produce an alias.
+        // Remove the inner module reference.
+        auto GVAndUnoptPair = VMap.find(&GV);
+        assert(GVAndUnoptPair != VMap.end() && "Unmapped global?");
+        // Delete the external reference - VMap shold only contain mappings to
+        // those declarations now.
+        assert(cast<GlobalValue>(GVAndUnoptPair->second)->isDeclaration() &&
+               "expected only declarations in VMap now");
+        cast<GlobalValue>(GVAndUnoptPair->second)->eraseFromParent();
+        GVAndUnoptPair->second = nullptr;
+        // Nothing else to do for this global.
+        continue;
+      }
+    }
+
+    if (!ShouldPromoteGlobal(GV))
+      continue;
+
+    // We need external aliases with a mangled name and hidden visability.
+    auto *Alias = GlobalAlias::create(GlobalValue::ExternalLinkage,
+                                      GV.getName() + PromotionSuffix, &GV);
+    Alias->setVisibility(GlobalValue::HiddenVisibility);
+
+    // Update the Inner external reference that corresponds to the promoted
+    // Outer global (created just now as an alias in opt) to reference the new
+    // alias.
+    GlobalValue *UnoptGV = cast<GlobalValue>(VMap[&GV]);
+    UnoptGV->setName(Alias->getName());
+    UnoptGV->setVisibility(GlobalValue::HiddenVisibility);
+    assert(UnoptGV->getLinkage() == GlobalValue::ExternalLinkage &&
+           "Expected ExternalLinkage from CloneModule or inserted decl");
+  }
+
+  // Preserve functions that may be discarded after optimizing away call sites
+  // (e.g. ODR-weak). Another desirable effect of this is that it prevents
+  // GlobalOpt promoting the alias. If the function-preservation mechanism
+  // changes in the future GlobalOpt alias promotion must be handled another
+  // way.
+  appendToCompilerUsed(*M, GlobalsToPreserve);
+
+  return UnoptM;
+}
\ No newline at end of file
diff --git a/llvm/unittests/Transforms/Utils/CMakeLists.txt b/llvm/unittests/Transforms/Utils/CMakeLists.txt
index 7d649f25c830a..f4e4c817114c1 100644
--- a/llvm/unittests/Transforms/Utils/CMakeLists.txt
+++ b/llvm/unittests/Transforms/Utils/CMakeLists.txt
@@ -20,6 +20,7 @@ add_llvm_unittest(UtilsTests
   CodeMoverUtilsTest.cpp
   DebugifyTest.cpp
   DebugSSAUpdaterTest.cpp
+  DynamicDebuggingTest.cpp
   FunctionComparatorTest.cpp
   IntegerDivisionTest.cpp
   LocalTest.cpp
diff --git a/llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp b/llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp
new file mode 100644
index 0000000000000..f95a674c54ccf
--- /dev/null
+++ b/llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp
@@ -0,0 +1,524 @@
+//===- DynamicDebuggingTest.cpp - Unit tests for dynamic debugging utils -===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Transforms/Utils/DynamicDebugging.h"
+#include "llvm/AsmParser/Parser.h"
+#include "llvm/IR/Attributes.h"
+#include "llvm/IR/GlobalVariable.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/IR/Module.h"
+#include "llvm/Support/SourceMgr.h"
+#include "llvm/Transforms/IPO/Attributor.h"
+#include "llvm/Transforms/Utils/Cloning.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+
+namespace {
+
+TEST(DynamicDebugging, UnoptimizedModuleAttributes) {
+  // Test that `prepareForDynamicDebugging` removes alwaysinline and minsize
+  // attributes from cloned functions in the unoptimized module, adding
+  // noinline and optnone.
+  StringRef IR = R"(
+    define dso_local void @f() #0 !dbg !4 {
+      ret void
+    }
+
+    attributes #0 = { alwaysinline minsize }
+
+    !llvm.dbg.cu = !{!0}
+    !llvm.module.flags = !{!2, !3}
+
+    !0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 23.0.0git", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+    !1 = !DIFile(filename: "test.c", directory: "/")
+    !2 = !{i32 7, !"Dwarf Version", i32 5}
+    !3 = !{i32 2, !"Debug Info Version", i32 3}
+    !4 = distinct !DISubprogram(name: "f", scope: !5, file: !5, line: 1, type: !6, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
+    !5 = !DIFile(filename: "test.c", directory: "/")
+    !6 = !DISubroutineType(types: !7)
+    !7 = !{null}
+  )";
+
+  LLVMContext Context;
+  SMDiagnostic Error;
+
+  std::unique_ptr<Module> OptModule = parseAssemblyString(IR, Error, Context);
+  ASSERT_TRUE(OptModule != nullptr) << "Failed to parse IR\n";
+  ASSERT_TRUE(OptModule->getFunction("f"));
+  ASSERT_TRUE(
+      OptModule->getFunction("f")->hasFnAttribute(Attribute::AlwaysInline));
+  ASSERT_TRUE(OptModule->getFunction("f")->hasFnAttribute(Attribute::MinSize));
+
+  std::unique_ptr<Module> UnoptModule =
+      prepareForDynamicDebugging(OptModule.get(), "unused");
+
+  // Check that optnone and noinline are added to the unoptimized function
+  // clone and that the clashing attributes minsize and alwaysinline are
+  // removed.
+  Function *F = UnoptModule->getFunction("__dyndbg.f");
+  ASSERT_TRUE(F && !F->isDeclaration());
+  EXPECT_FALSE(F->hasFnAttribute(Attribute::MinSize));
+  EXPECT_FALSE(F->hasFnAttribute(Attribute::AlwaysInline));
+  EXPECT_TRUE(F->hasFnAttribute(Attribute::OptimizeNone));
+  EXPECT_TRUE(F->hasFnAttribute(Attribute::NoInline));
+
+  // Check the declarations (references to OptModule's functions) are unchanged.
+  F = UnoptModule->getFunction("f");
+  ASSERT_TRUE(F && F->isDeclaration());
+  EXPECT_TRUE(F->hasFnAttribute(Attribute::MinSize));
+  EXPECT_TRUE(F->hasFnAttribute(Attribute::AlwaysInline));
+  EXPECT_FALSE(F->hasFnAttribute(Attribute::OptimizeNone));
+  EXPECT_FALSE(F->hasFnAttribute(Attribute::NoInline));
+
+  // The originl function in OptModule should be unchanged.
+  F = OptModule->getFunction("f");
+  ASSERT_TRUE(F && !F->isDeclaration());
+  EXPECT_TRUE(F->hasFnAttribute(Attribute::MinSize));
+  EXPECT_TRUE(F->hasFnAttribute(Attribute::AlwaysInline));
+  EXPECT_FALSE(F->hasFnAttribute(Attribute::OptimizeNone));
+  EXPECT_FALSE(F->hasFnAttribute(Attribute::NoInline));
+}
+
+TEST(DynamicDebugging, OptimizedModuleAttributes) {
+  // Test that `prepareForDynamicDebugging` adds necessary attributes to
+  // functions in the (to be) optimized module.
+  // Note: The test requires a triple as support for targets other than x86_64
+  // hasn't been added yet for the tail padding attributes.
+  StringRef IR = R"(
+    target triple = "x86_64-unknown-linux"
+
+    define dso_local void @f() !dbg !4 {
+      ret void
+    }
+
+    !llvm.dbg.cu = !{!0}
+    !llvm.module.flags = !{!2, !3}
+
+    !0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 23.0.0git", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+    !1 = !DIFile(filename: "test.c", directory: "/")
+    !2 = !{i32 7, !"Dwarf Version", i32 5}
+    !3 = !{i32 2, !"Debug Info Version", i32 3}
+    !4 = distinct !DISubprogram(name: "f", scope: !5, file: !5, line: 1, type: !6, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
+    !5 = !DIFile(filename: "test.c", directory: "/")
+    !6 = !DISubroutineType(types: !7)
+    !7 = !{null}
+  )";
+
+  LLVMContext Context;
+  SMDiagnostic Error;
+
+  std::unique_ptr<Module> M = parseAssemblyString(IR, Error, Context);
+  ASSERT_TRUE(M != nullptr) << "Failed to parse IR\n";
+
+  // We're only interested in M (drop returned unoptimzed module).
+  prepareForDynamicDebugging(M.get(), "unused");
+  Function *F = M->getFunction("f");
+  ASSERT_TRUE(F);
+
+  EXPECT_TRUE(F->hasFnAttribute("no-func-spec"));
+  EXPECT_EQ(F->getFnAttributeAsParsedInteger("tail-pad-to-size"), 5u);
+  EXPECT_EQ(F->getFnAttributeAsParsedInteger("tail-pad-value"), 144u);
+}
+
+TEST(DynamicDebugging, FunctionLinkage) {
+  // Test functions get expected linkage and names in the unoptimized and
+  // optimized modules. Each optimized module function should have a
+  // corresponding unoptimized version, and ones with internal linkage should be
+  // given external aliases.
+  StringRef IR = R"(
+    ; Mark as used - unused internal or weak symbols aren't promoted.
+    @llvm.compiler.used = appending global [1 x ptr] [ptr @f], section "llvm.metadata"
+
+    define dso_local void @f() !dbg !4 {
+      ret void
+    }
+
+    !llvm.dbg.cu = !{!0}
+    !llvm.module.flags = !{!2, !3}
+
+    !0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 23.0.0git", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+    !1 = !DIFile(filename: "test.cpp", directory: "/")
+    !2 = !{i32 7, !"Dwarf Version", i32 5}
+    !3 = !{i32 2, !"Debug Info Version", i32 3}
+    !4 = distinct !DISubprogram(name: "f", scope: !1, file: !1, line: 1, type: !5, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+    !5 = !DISubroutineType(types: !6)
+    !6 = !{null}
+  )";
+
+  LLVMContext Context;
+  SMDiagnostic Error;
+
+  std::unique_ptr<Module> M = parseAssemblyString(IR, Error, Context);
+  ASSERT_TRUE(M != nullptr) << "Failed to parse IR\n";
+
+  // Input:        define f
+  // Opt module:   define f, alias f.promoted
+  // Unopt module:         declare f.promoted, define __dyndbg.f.promoted
+  auto ExpectPromoted = [&M](GlobalValue::LinkageTypes Linkage,
+                             GlobalValue::VisibilityTypes Visibility,
+                             int Line) {
+#define FAIL_MSG "ExpectPromoted called from " << Line;
+    std::unique_ptr<Module> OptModule = CloneModule(*M);
+    Function *F = OptModule->getFunction("f");
+    ASSERT_TRUE(F && !F->isDeclaration()) << FAIL_MSG;
+    F->setLinkage(Linkage);
+    F->setVisibility(Visibility);
+
+    std::unique_ptr<Module> UnoptModule =
+        prepareForDynamicDebugging(OptModule.get(), ".promoted");
+
+    // Optimized module function linkage and visibility should not change.
+    EXPECT_EQ(F->getLinkage(), Linkage) << FAIL_MSG;
+    EXPECT_EQ(F->getVisibility(), Visibility) << FAIL_MSG;
+
+    // External alias added to optimized module.
+    GlobalAlias *A = OptModule->getNamedAlias("f.promoted");
+    ASSERT_TRUE(A) << FAIL_MSG;
+    EXPECT_EQ(A->getAliasee(), F) << FAIL_MSG;
+    EXPECT_EQ(A->getLinkage(), GlobalValue::ExternalLinkage) << FAIL_MSG;
+    EXPECT_EQ(A->getVisibility(), GlobalValue::HiddenVisibility) << FAIL_MSG;
+
+    // Unoptimized module's reference to the optimzed module's function.
+    F = UnoptModule->getFunction("f.promoted");
+    ASSERT_TRUE(F) << FAIL_MSG;
+    EXPECT_TRUE(F->isDeclaration()) << FAIL_MSG;
+    EXPECT_EQ(F->getLinkage(), GlobalValue::ExternalLinkage) << FAIL_MSG;
+    EXPECT_EQ(F->getVisibility(), GlobalValue::HiddenVisibility) << FAIL_MSG;
+
+    // Unoptimized module's copy of the function.
+    F = UnoptModule->getFunction("__dyndbg.f.promoted");
+    ASSERT_TRUE(F) << FAIL_MSG;
+    EXPECT_FALSE(F->isDeclaration()) << FAIL_MSG;
+    EXPECT_EQ(F->getLinkage(), GlobalValue::ExternalLinkage) << FAIL_MSG;
+    EXPECT_EQ(F->getVisibility(), GlobalValue::HiddenVisibility) << FAIL_MSG;
+#undef FAIL_MSG
+  };
+
+  // Input:         define f
+  // Opt module:    define f
+  // Unopt module: declare f, define __dyndbg.f
+  auto ExpectNotPromoted = [&M](GlobalValue::LinkageTypes Linkage,
+                                GlobalValue::VisibilityTypes Visibility,
+                                int Line) {
+#define FAIL_MSG "ExpectNotPromoted called from " << Line;
+    std::unique_ptr<Module> OptModule = CloneModule(*M);
+    Function *F = OptModule->getFunction("f");
+    ASSERT_TRUE(F && !F->isDeclaration()) << FAIL_MSG;
+    F->setLinkage(Linkage);
+    F->setVisibility(Visibility);
+
+    std::unique_ptr<Module> UnoptModule =
+        prepareForDynamicDebugging(OptModule.get(), ".promoted");
+
+    // Optimized module function linkage and visibility should not change.
+    EXPECT_EQ(F->getLinkage(), Linkage) << FAIL_MSG;
+    EXPECT_EQ(F->getVisibility(), Visibility) << FAIL_MSG;
+
+    // External alias not added to optimized module.
+    EXPECT_EQ(OptModule->alias_size(), 0u) << FAIL_MSG;
+
+    // Unoptimized module's reference to the optimzed module's function.
+    F = UnoptModule->getFunction("f");
+    ASSERT_TRUE(F) << FAIL_MSG;
+    EXPECT_TRUE(F->isDeclaration()) << FAIL_MSG;
+    EXPECT_EQ(F->getLinkage(), GlobalValue::ExternalLinkage) << FAIL_MSG;
+    EXPECT_EQ(F->getVisibility(), Visibility) << FAIL_MSG;
+
+    // Unoptimized module's copy of the function.
+    F = UnoptModule->getFunction("__dyndbg.f");
+    ASSERT_TRUE(F) << FAIL_MSG;
+    EXPECT_FALSE(F->isDeclaration()) << FAIL_MSG;
+    EXPECT_EQ(F->getLinkage(), Linkage) << FAIL_MSG;
+    EXPECT_EQ(F->getVisibility(), Visibility) << FAIL_MSG;
+#undef FAIL_MSG
+  };
+
+  ExpectPromoted(GlobalValue::InternalLinkage, GlobalValue::DefaultVisibility,
+                 __LINE__);
+  ExpectPromoted(GlobalValue::PrivateLinkage, GlobalValue::DefaultVisibility,
+                 __LINE__);
+
+  ExpectNotPromoted(GlobalValue::ExternalLinkage,
+                    GlobalValue::DefaultVisibility, __LINE__);
+  ExpectNotPromoted(GlobalValue::ExternalLinkage, GlobalValue::HiddenVisibility,
+                    __LINE__);
+  ExpectNotPromoted(GlobalValue::ExternalLinkage,
+                    GlobalValue::ProtectedVisibility, __LINE__);
+
+  ExpectNotPromoted(GlobalValue::AvailableExternallyLinkage,
+                    GlobalValue::DefaultVisibility, __LINE__);
+  ExpectNotPromoted(GlobalValue::AvailableExternallyLinkage,
+                    GlobalValue::HiddenVisibility, __LINE__);
+  ExpectNotPromoted(GlobalValue::AvailableExternallyLinkage,
+                    GlobalValue::ProtectedVisibility, __LINE__);
+
+  ExpectNotPromoted(GlobalValue::LinkOnceAnyLinkage,
+                    GlobalValue::DefaultVisibility, __LINE__);
+  ExpectNotPromoted(GlobalValue::LinkOnceAnyLinkage,
+                    GlobalValue::HiddenVisibility, __LINE__);
+  ExpectNotPromoted(GlobalValue::LinkOnceAnyLinkage,
+                    GlobalValue::ProtectedVisibility, __LINE__);
+
+  ExpectNotPromoted(GlobalValue::WeakAnyLinkage, GlobalValue::DefaultVisibility,
+                    __LINE__);
+  ExpectNotPromoted(GlobalValue::WeakAnyLinkage, GlobalValue::HiddenVisibility,
+                    __LINE__);
+  ExpectNotPromoted(GlobalValue::WeakAnyLinkage,
+                    GlobalValue::ProtectedVisibility, __LINE__);
+
+  ExpectNotPromoted(GlobalValue::WeakODRLinkage, GlobalValue::DefaultVisibility,
+                    __LINE__);
+  ExpectNotPromoted(GlobalValue::WeakODRLinkage, GlobalValue::HiddenVisibility,
+                    __LINE__);
+  ExpectNotPromoted(GlobalValue::WeakODRLinkage,
+                    GlobalValue::ProtectedVisibility, __LINE__);
+
+  ExpectNotPromoted(GlobalValue::ExternalWeakLinkage,
+                    GlobalValue::DefaultVisibility, __LINE__);
+  ExpectNotPromoted(GlobalValue::ExternalWeakLinkage,
+                    GlobalValue::HiddenVisibility, __LINE__);
+  ExpectNotPromoted(GlobalValue::ExternalWeakLinkage,
+                    GlobalValue::ProtectedVisibility, __LINE__);
+
+  // Add a comadt group.
+  Comdat *Comdat = M->getOrInsertComdat("c");
+  Comdat->setSelectionKind(Comdat::Any);
+  Function *F = M->getFunction("f");
+  ASSERT_TRUE(F);
+  F->setComdat(Comdat);
+  // These linkage types should no longer be promtoed.
+  ExpectNotPromoted(GlobalValue::InternalLinkage,
+                    GlobalValue::DefaultVisibility, __LINE__);
+  ExpectNotPromoted(GlobalValue::PrivateLinkage, GlobalValue::DefaultVisibility,
+                    __LINE__);
+}
+
+TEST(DynamicDebugging, GlobalVariableLinkage) {
+  // Test global vairables get expected linkage and names in the unoptimized and
+  // optimized modules. Each optimized module global should be referenced from
+  // the unoptimized version, and ones with internal linkage should be
+  // given external aliases to facilitate this.
+  StringRef IR = R"(
+    ; Mark as used - unused internal or weak symbols aren't promoted.
+    @llvm.compiler.used = appending global [1 x ptr] [ptr @g], section "llvm.metadata"
+
+    @g = dso_local global i32 1, align 4
+
+    !llvm.dbg.cu = !{!0}
+    !llvm.module.flags = !{!2, !3}
+
+    !0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 23.0.0git", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+    !1 = !DIFile(filename: "test.cpp", directory: "/")
+    !2 = !{i32 7, !"Dwarf Version", i32 5}
+    !3 = !{i32 2, !"Debug Info Version", i32 3}
+  )";
+
+  LLVMContext Context;
+  SMDiagnostic Error;
+
+  std::unique_ptr<Module> M = parseAssemblyString(IR, Error, Context);
+  ASSERT_TRUE(M != nullptr) << "Failed to parse IR\n";
+
+  // If 'g' is a definition, add 'g.promoted' alias which is referenced from
+  // the unoptimized module. Declarations don't get promoted (no alias added).
+  auto ExpectPromoted = [&M](GlobalValue::LinkageTypes Linkage,
+                             GlobalValue::VisibilityTypes Visibility,
+                             int Line) {
+#define FAIL_MSG "ExpectPromoted called from " << Line;
+    std::unique_ptr<Module> OptModule = CloneModule(*M);
+    GlobalVariable *G = OptModule->getGlobalVariable("g");
+    ASSERT_TRUE(G) << FAIL_MSG;
+    G->setLinkage(Linkage);
+    G->setVisibility(Visibility);
+
+    std::unique_ptr<Module> UnoptModule =
+        prepareForDynamicDebugging(OptModule.get(), ".promoted");
+
+    // Optimized module function linkage and visibility should not change.
+    EXPECT_EQ(G->getLinkage(), Linkage) << FAIL_MSG;
+    EXPECT_EQ(G->getVisibility(), Visibility) << FAIL_MSG;
+
+    // External alias added to optimized module.
+    GlobalAlias *A = OptModule->getNamedAlias("g.promoted");
+    ASSERT_TRUE(A) << FAIL_MSG;
+    EXPECT_EQ(A->getAliasee(), G) << FAIL_MSG;
+    EXPECT_EQ(A->getLinkage(), GlobalValue::ExternalLinkage) << FAIL_MSG;
+    EXPECT_EQ(A->getVisibility(), GlobalValue::HiddenVisibility) << FAIL_MSG;
+
+    // Unoptimized module's reference to the optimzed module's function.
+    G = UnoptModule->getGlobalVariable("g.promoted");
+    ASSERT_TRUE(G) << FAIL_MSG;
+    EXPECT_TRUE(G->isDeclaration()) << FAIL_MSG;
+    EXPECT_EQ(G->getLinkage(), GlobalValue::ExternalLinkage) << FAIL_MSG;
+    EXPECT_EQ(G->getVisibility(), GlobalValue::HiddenVisibility) << FAIL_MSG;
+
+    // The unoptimized module doesn't contain any global definitions.
+    for (GlobalVariable &GV : UnoptModule->globals())
+      EXPECT_TRUE(GV.isDeclaration());
+
+#undef FAIL_MSG
+  };
+
+  // Add reference to 'g' in the unoptimized module.
+  auto ExpectNotPromoted = [&M](GlobalValue::LinkageTypes Linkage,
+                                GlobalValue::VisibilityTypes Visibility,
+                                int Line) {
+#define FAIL_MSG "ExpectPromoted called from " << Line;
+    std::unique_ptr<Module> OptModule = CloneModule(*M);
+    GlobalVariable *G = OptModule->getGlobalVariable("g");
+    ASSERT_TRUE(G) << FAIL_MSG;
+    G->setLinkage(Linkage);
+    G->setVisibility(Visibility);
+
+    std::unique_ptr<Module> UnoptModule =
+        prepareForDynamicDebugging(OptModule.get(), ".promoted");
+
+    // Optimized module function linkage and visibility should not change.
+    EXPECT_EQ(G->getLinkage(), Linkage) << FAIL_MSG;
+    EXPECT_EQ(G->getVisibility(), Visibility) << FAIL_MSG;
+
+    // External alias not added to optimized module.
+    EXPECT_EQ(OptModule->alias_size(), 0u) << FAIL_MSG;
+
+    // Unoptimized module's reference to the optimzed module's function.
+    G = UnoptModule->getGlobalVariable("g");
+    ASSERT_TRUE(G) << FAIL_MSG;
+    EXPECT_TRUE(G->isDeclaration()) << FAIL_MSG;
+    EXPECT_EQ(G->getLinkage(), GlobalValue::ExternalLinkage) << FAIL_MSG;
+    EXPECT_EQ(G->getVisibility(), Visibility) << FAIL_MSG;
+
+    // The unoptimized module doesn't contain any global definitions.
+    for (GlobalVariable &GV : UnoptModule->globals())
+      EXPECT_TRUE(GV.isDeclaration());
+
+#undef FAIL_MSG
+  };
+
+  ExpectPromoted(GlobalValue::InternalLinkage, GlobalValue::DefaultVisibility,
+                 __LINE__);
+  ExpectPromoted(GlobalValue::PrivateLinkage, GlobalValue::DefaultVisibility,
+                 __LINE__);
+
+  ExpectPromoted(GlobalValue::InternalLinkage, GlobalValue::DefaultVisibility,
+                 __LINE__);
+  ExpectPromoted(GlobalValue::PrivateLinkage, GlobalValue::DefaultVisibility,
+                 __LINE__);
+
+  ExpectNotPromoted(GlobalValue::ExternalLinkage,
+                    GlobalValue::DefaultVisibility, __LINE__);
+  ExpectNotPromoted(GlobalValue::ExternalLinkage, GlobalValue::HiddenVisibility,
+                    __LINE__);
+  ExpectNotPromoted(GlobalValue::ExternalLinkage,
+                    GlobalValue::ProtectedVisibility, __LINE__);
+
+  ExpectNotPromoted(GlobalValue::AvailableExternallyLinkage,
+                    GlobalValue::DefaultVisibility, __LINE__);
+  ExpectNotPromoted(GlobalValue::AvailableExternallyLinkage,
+                    GlobalValue::HiddenVisibility, __LINE__);
+  ExpectNotPromoted(GlobalValue::AvailableExternallyLinkage,
+                    GlobalValue::ProtectedVisibility, __LINE__);
+
+  ExpectNotPromoted(GlobalValue::LinkOnceAnyLinkage,
+                    GlobalValue::DefaultVisibility, __LINE__);
+  ExpectNotPromoted(GlobalValue::LinkOnceAnyLinkage,
+                    GlobalValue::HiddenVisibility, __LINE__);
+  ExpectNotPromoted(GlobalValue::LinkOnceAnyLinkage,
+                    GlobalValue::ProtectedVisibility, __LINE__);
+
+  ExpectNotPromoted(GlobalValue::WeakAnyLinkage, GlobalValue::DefaultVisibility,
+                    __LINE__);
+  ExpectNotPromoted(GlobalValue::WeakAnyLinkage, GlobalValue::HiddenVisibility,
+                    __LINE__);
+  ExpectNotPromoted(GlobalValue::WeakAnyLinkage,
+                    GlobalValue::ProtectedVisibility, __LINE__);
+
+  ExpectNotPromoted(GlobalValue::WeakODRLinkage, GlobalValue::DefaultVisibility,
+                    __LINE__);
+  ExpectNotPromoted(GlobalValue::WeakODRLinkage, GlobalValue::HiddenVisibility,
+                    __LINE__);
+  ExpectNotPromoted(GlobalValue::WeakODRLinkage,
+                    GlobalValue::ProtectedVisibility, __LINE__);
+
+  ExpectNotPromoted(GlobalValue::ExternalWeakLinkage,
+                    GlobalValue::DefaultVisibility, __LINE__);
+  ExpectNotPromoted(GlobalValue::ExternalWeakLinkage,
+                    GlobalValue::HiddenVisibility, __LINE__);
+  ExpectNotPromoted(GlobalValue::ExternalWeakLinkage,
+                    GlobalValue::ProtectedVisibility, __LINE__);
+
+  ExpectNotPromoted(GlobalValue::CommonLinkage, GlobalValue::DefaultVisibility,
+                    __LINE__);
+  ExpectNotPromoted(GlobalValue::CommonLinkage, GlobalValue::HiddenVisibility,
+                    __LINE__);
+  ExpectNotPromoted(GlobalValue::CommonLinkage,
+                    GlobalValue::ProtectedVisibility, __LINE__);
+
+  // Add a comadt group.
+  Comdat *Comdat = M->getOrInsertComdat("c");
+  Comdat->setSelectionKind(Comdat::Any);
+  GlobalVariable *G = M->getGlobalVariable("g");
+  ASSERT_TRUE(G);
+  G->setComdat(Comdat);
+  // These linkage types should no longer be promtoed.
+  ExpectNotPromoted(GlobalValue::InternalLinkage,
+                    GlobalValue::DefaultVisibility, __LINE__);
+  ExpectNotPromoted(GlobalValue::PrivateLinkage, GlobalValue::DefaultVisibility,
+                    __LINE__);
+}
+
+TEST(DynamicDebugging, DiscardableGlobal) {
+  // Test discardable symbols are added to the @llvm.compiler.used global,
+  // which prevents them being discarded (including by globalopt replacing) them
+  // with their external linkage aliases.
+  StringRef IR = R"(
+    @not_discardable_used = dso_local global i32 1, align 4
+    @not_discardable_unused = dso_local global i32 1, align 4
+
+    @discardable_used = linkonce_odr dso_local global i32 2, align 4
+    @discardable_unused = linkonce_odr dso_local global i32 2, align 4
+
+    define internal void @use() {
+      %1 = load i32, ptr @not_discardable_used, align 4
+      %2 = load i32, ptr @discardable_used, align 4
+      ret void
+    }
+
+    !llvm.dbg.cu = !{!0}
+    !llvm.module.flags = !{!2, !3}
+
+    !0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 23.0.0git", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+    !1 = !DIFile(filename: "test.cpp", directory: "/")
+    !2 = !{i32 7, !"Dwarf Version", i32 5}
+    !3 = !{i32 2, !"Debug Info Version", i32 3}
+  )";
+
+  LLVMContext Context;
+  SMDiagnostic Error;
+  std::unique_ptr<Module> M = parseAssemblyString(IR, Error, Context);
+  ASSERT_TRUE(M != nullptr) << Error.getMessage();
+
+  // We're only interested in M (drop returned unoptimzed module).
+  prepareForDynamicDebugging(M.get(), ".whatever");
+  auto *CompilerUsedGV = M->getGlobalVariable("llvm.compiler.used");
+  ASSERT_TRUE(CompilerUsedGV);
+  ASSERT_TRUE(CompilerUsedGV->hasInitializer());
+  ASSERT_TRUE(isa<ConstantArray>(CompilerUsedGV->getInitializer()));
+
+  ConstantArray *CompilerUsedArr =
+      cast<ConstantArray>(CompilerUsedGV->getInitializer());
+
+  GlobalVariable *DiscardableUsed = M->getGlobalVariable("discardable_used");
+  ASSERT_TRUE(DiscardableUsed);
+
+  // Expect to see @llvm.compiler.used = appending global [1 x ptr] [ptr @discardable_used].
+  EXPECT_EQ(CompilerUsedArr->getNumOperands(), 1u);
+  EXPECT_TRUE(llvm::find(CompilerUsedArr->operands(), DiscardableUsed));
+}
+
+} // namespace
\ No newline at end of file

>From 278c62534becef872c8a53d57b1b634580c94021 Mon Sep 17 00:00:00 2001
From: Orlando Cazalet-Hyams <orlando.hyams at sony.com>
Date: Wed, 29 Apr 2026 13:30:22 +0100
Subject: [PATCH 2/6] llvm dyndbg: name unnamed values

---
 .../lib/Transforms/Utils/DynamicDebugging.cpp |  3 ++
 .../Transforms/Utils/DynamicDebuggingTest.cpp | 45 ++++++++++++++++++-
 2 files changed, 47 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/Utils/DynamicDebugging.cpp b/llvm/lib/Transforms/Utils/DynamicDebugging.cpp
index 4f5a9fb8bcae6..ad943eb9ccb53 100644
--- a/llvm/lib/Transforms/Utils/DynamicDebugging.cpp
+++ b/llvm/lib/Transforms/Utils/DynamicDebugging.cpp
@@ -118,6 +118,9 @@ llvm::prepareForDynamicDebugging(Module *M, StringRef PromotionSuffix) {
     if (GV.isDiscardableIfUnused()) {
       if (GV.getNumUses()) {
         GlobalsToPreserve.push_back(&GV);
+        // Name unnamed globals. Compiler-used expects named globals only.
+        if (GV.getName().empty())
+          GV.setName("__unnamed");
       } else {
         // No uses, so the inner module doesn't need a reference nor do we need
         // to produce an alias.
diff --git a/llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp b/llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp
index f95a674c54ccf..6f29df9dada18 100644
--- a/llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp
+++ b/llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp
@@ -12,6 +12,7 @@
 #include "llvm/IR/GlobalVariable.h"
 #include "llvm/IR/LLVMContext.h"
 #include "llvm/IR/Module.h"
+#include "llvm/IR/Verifier.h"
 #include "llvm/Support/SourceMgr.h"
 #include "llvm/Transforms/IPO/Attributor.h"
 #include "llvm/Transforms/Utils/Cloning.h"
@@ -521,4 +522,46 @@ TEST(DynamicDebugging, DiscardableGlobal) {
   EXPECT_TRUE(llvm::find(CompilerUsedArr->operands(), DiscardableUsed));
 }
 
-} // namespace
\ No newline at end of file
+TEST(DynamicDebugging, UnnamedGlobal) {
+  StringRef IR = R"(
+    @0 = internal global i32 1, align 4
+    @1 = internal global i32 1, align 4
+
+    define internal void @use() {
+      %1 = load i32, ptr @0, align 4
+      %2 = load i32, ptr @1, align 4
+      ret void
+    }
+
+    !llvm.dbg.cu = !{!0}
+    !llvm.module.flags = !{!2, !3}
+
+    !0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 23.0.0git", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None)
+    !1 = !DIFile(filename: "test.cpp", directory: "/")
+    !2 = !{i32 7, !"Dwarf Version", i32 5}
+    !3 = !{i32 2, !"Debug Info Version", i32 3}
+  )";
+
+  LLVMContext Context;
+  SMDiagnostic Error;
+  std::unique_ptr<Module> M = parseAssemblyString(IR, Error, Context);
+  ASSERT_TRUE(M != nullptr) << Error.getMessage();
+  for (GlobalVariable &GV : M->globals())
+    EXPECT_FALSE(GV.hasName());
+
+  std::unique_ptr<Module> UnoptM =
+      prepareForDynamicDebugging(M.get(), ".promo");
+  EXPECT_FALSE(verifyModule(*M));
+  EXPECT_FALSE(verifyModule(*UnoptM));
+
+  EXPECT_TRUE(M->getGlobalVariable("__unnamed", /*AllowInternal=*/true));
+  EXPECT_TRUE(M->getGlobalVariable("__unnamed.1", /*AllowInternal=*/true));
+
+  EXPECT_TRUE(M->getNamedAlias("__unnamed.promo"));
+  EXPECT_TRUE(M->getNamedAlias("__unnamed.1.promo"));
+
+  EXPECT_TRUE(UnoptM->getNamedGlobal("__unnamed.promo"));
+  EXPECT_TRUE(UnoptM->getNamedGlobal("__unnamed.1.promo"));
+}
+
+} // namespace

>From 1dc77aad34d0a76449bf5988631712c75b5b3928 Mon Sep 17 00:00:00 2001
From: Orlando Cazalet-Hyams <orlando.hyams at sony.com>
Date: Mon, 6 Jul 2026 12:41:56 +0100
Subject: [PATCH 3/6] fmt

---
 llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp b/llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp
index 6f29df9dada18..03139c259c838 100644
--- a/llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp
+++ b/llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp
@@ -517,7 +517,8 @@ TEST(DynamicDebugging, DiscardableGlobal) {
   GlobalVariable *DiscardableUsed = M->getGlobalVariable("discardable_used");
   ASSERT_TRUE(DiscardableUsed);
 
-  // Expect to see @llvm.compiler.used = appending global [1 x ptr] [ptr @discardable_used].
+  // Expect to see:
+  // @llvm.compiler.used = appending global [1 x ptr] [ptr @discardable_used].
   EXPECT_EQ(CompilerUsedArr->getNumOperands(), 1u);
   EXPECT_TRUE(llvm::find(CompilerUsedArr->operands(), DiscardableUsed));
 }

>From 8366cf87896e28f0e3057976bb098f6e40c1a119 Mon Sep 17 00:00:00 2001
From: Orlando Cazalet-Hyams <orlando.hyams at sony.com>
Date: Mon, 6 Jul 2026 12:57:46 +0100
Subject: [PATCH 4/6] replace atribute no-func-spec with NoIPA

---
 llvm/lib/Transforms/Utils/DynamicDebugging.cpp           | 9 +++++++--
 llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp | 2 +-
 2 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/DynamicDebugging.cpp b/llvm/lib/Transforms/Utils/DynamicDebugging.cpp
index ad943eb9ccb53..9694d8db904d3 100644
--- a/llvm/lib/Transforms/Utils/DynamicDebugging.cpp
+++ b/llvm/lib/Transforms/Utils/DynamicDebugging.cpp
@@ -63,11 +63,16 @@ llvm::prepareForDynamicDebugging(Module *M, StringRef PromotionSuffix) {
       InnerDef->removeFnAttr(Attribute::MinSize);
 
       // Add attributes to the outer-object functions to ensure they're
-      // always patchable. TODO: Add patch bytes size/value for other targets.
+      // always patchable.
+      //
+      // The debugger patches outer (optimized) functions to redirect to inner
+      // (unoptimized) functions. Block interprocedural analysis to ensure
+      // the two function implementations share the same interface.
+      OuterDef.addFnAttr(Attribute::NoIPA);
+      // TODO: Add patch bytes size/value for other targets.
       if (M->getTargetTriple().isX86_64()) {
         OuterDef.addFnAttr("tail-pad-to-size", "5");
         OuterDef.addFnAttr("tail-pad-value", "144"); // 0x90
-        OuterDef.addFnAttr("no-func-spec");
       }
     }
 
diff --git a/llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp b/llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp
index 03139c259c838..f82d58765f620 100644
--- a/llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp
+++ b/llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp
@@ -122,7 +122,7 @@ TEST(DynamicDebugging, OptimizedModuleAttributes) {
   Function *F = M->getFunction("f");
   ASSERT_TRUE(F);
 
-  EXPECT_TRUE(F->hasFnAttribute("no-func-spec"));
+  EXPECT_TRUE(F->hasFnAttribute(Attribute::NoIPA));
   EXPECT_EQ(F->getFnAttributeAsParsedInteger("tail-pad-to-size"), 5u);
   EXPECT_EQ(F->getFnAttributeAsParsedInteger("tail-pad-value"), 144u);
 }

>From 583c9e3ac9a36ccdc68c3db7da523665dc88934e Mon Sep 17 00:00:00 2001
From: Orlando Cazalet-Hyams <orlando.hyams at sony.com>
Date: Mon, 6 Jul 2026 13:06:01 +0100
Subject: [PATCH 5/6] block outlining

---
 llvm/lib/Transforms/Utils/DynamicDebugging.cpp           | 4 ++++
 llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp | 1 +
 2 files changed, 5 insertions(+)

diff --git a/llvm/lib/Transforms/Utils/DynamicDebugging.cpp b/llvm/lib/Transforms/Utils/DynamicDebugging.cpp
index 9694d8db904d3..004d6e25cbd8d 100644
--- a/llvm/lib/Transforms/Utils/DynamicDebugging.cpp
+++ b/llvm/lib/Transforms/Utils/DynamicDebugging.cpp
@@ -69,6 +69,10 @@ llvm::prepareForDynamicDebugging(Module *M, StringRef PromotionSuffix) {
       // (unoptimized) functions. Block interprocedural analysis to ensure
       // the two function implementations share the same interface.
       OuterDef.addFnAttr(Attribute::NoIPA);
+      // Outlining creates specialized functions in the outer (optimized)
+      // module without an inner (unoptimized) equivalent, meaning the debugger
+      // can't switch to an unoptimized version, so block outlining.
+      OuterDef.addFnAttr(Attribute::NoOutline);
       // TODO: Add patch bytes size/value for other targets.
       if (M->getTargetTriple().isX86_64()) {
         OuterDef.addFnAttr("tail-pad-to-size", "5");
diff --git a/llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp b/llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp
index f82d58765f620..1a5ea98d85d46 100644
--- a/llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp
+++ b/llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp
@@ -122,6 +122,7 @@ TEST(DynamicDebugging, OptimizedModuleAttributes) {
   Function *F = M->getFunction("f");
   ASSERT_TRUE(F);
 
+  EXPECT_TRUE(F->hasFnAttribute(Attribute::NoOutline));
   EXPECT_TRUE(F->hasFnAttribute(Attribute::NoIPA));
   EXPECT_EQ(F->getFnAttributeAsParsedInteger("tail-pad-to-size"), 5u);
   EXPECT_EQ(F->getFnAttributeAsParsedInteger("tail-pad-value"), 144u);

>From 2a56b1c20befc334ed1733796849a90c0a0001e3 Mon Sep 17 00:00:00 2001
From: Orlando Cazalet-Hyams <orlando.hyams at sony.com>
Date: Mon, 6 Jul 2026 14:02:59 +0100
Subject: [PATCH 6/6] LLVM_ABI

---
 llvm/include/llvm/Transforms/Utils/DynamicDebugging.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/llvm/include/llvm/Transforms/Utils/DynamicDebugging.h b/llvm/include/llvm/Transforms/Utils/DynamicDebugging.h
index c2860d2502bf7..4132ec206ca52 100644
--- a/llvm/include/llvm/Transforms/Utils/DynamicDebugging.h
+++ b/llvm/include/llvm/Transforms/Utils/DynamicDebugging.h
@@ -57,8 +57,8 @@ class Module;
 /// |   return return loc.promo()                  |
 /// | }                                            |
 /// +----------------------------------------------+
-std::unique_ptr<Module> prepareForDynamicDebugging(Module *M,
-                                                   StringRef PromotionSuffix);
+LLVM_ABI std::unique_ptr<Module>
+prepareForDynamicDebugging(Module *M, StringRef PromotionSuffix);
 } // namespace llvm
 
 #endif



More information about the llvm-commits mailing list