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

via llvm-commits llvm-commits at lists.llvm.org
Wed Apr 29 06:19:13 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-debuginfo

Author: Orlando Cazalet-Hyams (OCHyams)

<details>
<summary>Changes</summary>

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 RFC 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.

---

Depends on:
https://github.com/llvm/llvm-project/pull/194846
https://github.com/llvm/llvm-project/pull/194852

---

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


5 Files Affected:

- (added) llvm/include/llvm/Transforms/Utils/DynamicDebugging.h (+64) 
- (modified) llvm/lib/Transforms/Utils/CMakeLists.txt (+1) 
- (added) llvm/lib/Transforms/Utils/DynamicDebugging.cpp (+167) 
- (modified) llvm/unittests/Transforms/Utils/CMakeLists.txt (+1) 
- (added) llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp (+567) 


``````````diff
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..ad943eb9ccb53
--- /dev/null
+++ b/llvm/lib/Transforms/Utils/DynamicDebugging.cpp
@@ -0,0 +1,167 @@
+//===- 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);
+        // 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.
+        // 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..6f29df9dada18
--- /dev/null
+++ b/llvm/unittests/Transforms/Utils/DynamicDebuggingTest.cpp
@@ -0,0 +1,567 @@
+//===- 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/IR/Verifier.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;
+    EXPE...
[truncated]

``````````

</details>


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


More information about the llvm-commits mailing list