[llvm] Reapply "[CFI] Create an external linkage alias instead of promoting i… (#220339) (PR #225173)

Mircea Trofin via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 21 17:59:56 PDT 2026


https://github.com/mtrofin updated https://github.com/llvm/llvm-project/pull/225173

>From 348c1104bd30f773a4a792c507dc69ef490fae8e Mon Sep 17 00:00:00 2001
From: Mircea Trofin <mtrofin at google.com>
Date: Mon, 21 Sep 2026 12:14:08 -0700
Subject: [PATCH] =?UTF-8?q?Reapply=20"[CFI]=20Create=20an=20external=20lin?=
 =?UTF-8?q?kage=20alias=20instead=20of=20promoting=20i=E2=80=A6=20(#220339?=
 =?UTF-8?q?)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 llvm/lib/Transforms/IPO/LowerTypeTests.cpp    | 35 +++++++++++
 .../Transforms/IPO/ThinLTOBitcodeWriter.cpp   | 55 ++++++++---------
 .../X86/cfi-import-internal-coroutine.ll      | 60 +++++++++++++++++++
 .../LowerTypeTests/promoted-internal.ll       | 30 ++++++++++
 .../cfi-icall-static-inline-asm.ll            |  5 +-
 .../cfi_dso_local_equivalent.ll               | 33 ++++++++++
 .../Transforms/ThinLTOBitcodeWriter/comdat.ll | 17 +++---
 .../ThinLTOBitcodeWriter/split-internal1.ll   |  3 +-
 .../ThinLTOBitcodeWriter/split-internal2.ll   |  5 +-
 .../split-vfunc-internal.ll                   |  6 +-
 10 files changed, 205 insertions(+), 44 deletions(-)
 create mode 100644 llvm/test/ThinLTO/X86/cfi-import-internal-coroutine.ll
 create mode 100644 llvm/test/Transforms/LowerTypeTests/promoted-internal.ll
 create mode 100644 llvm/test/Transforms/ThinLTOBitcodeWriter/cfi_dso_local_equivalent.ll

diff --git a/llvm/lib/Transforms/IPO/LowerTypeTests.cpp b/llvm/lib/Transforms/IPO/LowerTypeTests.cpp
index 216d5adc48ea8..d91f141241243 100644
--- a/llvm/lib/Transforms/IPO/LowerTypeTests.cpp
+++ b/llvm/lib/Transforms/IPO/LowerTypeTests.cpp
@@ -2490,6 +2490,38 @@ bool LowerTypeTestsModule::lower() {
       report_fatal_error(
           "unexpected call to llvm.icall.branch.funnel during import phase");
 
+    // For internal linkage cfiFunction defs/decls, we only needed the alias
+    // through the linker. We can replace those aliases with the aliased
+    // function here.
+    SmallVector<std::pair<Function *, std::string>> PromotedFuncs;
+    for (auto &A : llvm::make_early_inc_range(M.aliases())) {
+      if (A.hasLocalLinkage())
+        continue;
+      if (ImportSummary->cfiFunctionDefs().contains(A.getName()) ||
+          ImportSummary->cfiFunctionDecls().contains(A.getName())) {
+        if (auto *F = dyn_cast_or_null<Function>(A.getAliaseeObject())) {
+          if (F->hasExternalLinkage()) {
+            // The original internal linkage function was independently promoted
+            // by thinlink. While, pre-link, all static references to it
+            // (implicitly, module-internal) were replaced with references to
+            // the alias, thinlink might decide to promote it because (for
+            // example) it turns out to be a hot indirect call target in a
+            // different module.
+            // In that case, we need to remember its thinlink-promoted name
+            // because it's potentially referenced elsewhere, and make sure
+            // there's an alias to it.
+            PromotedFuncs.emplace_back(F, F->getName());
+          } else {
+            F->setLinkage(GlobalValue::ExternalLinkage);
+            F->setVisibility(GlobalValue::HiddenVisibility);
+          }
+          A.replaceAllUsesWith(F);
+          F->takeName(&A);
+          A.eraseFromParent();
+        }
+      }
+    }
+
     SmallVector<Function *, 8> Defs;
     SmallVector<Function *, 8> Decls;
     for (auto &F : M) {
@@ -2510,6 +2542,9 @@ bool LowerTypeTestsModule::lower() {
       for (auto *F : Decls)
         importFunction(F, /*isJumpTableCanonical*/ false);
     }
+    // Add an alias with the thinlink promotion name.
+    for (auto &[F, Name] : PromotedFuncs)
+      GlobalAlias::create(GlobalValue::LinkageTypes::ExternalLinkage, Name, F);
 
     return true;
   }
diff --git a/llvm/lib/Transforms/IPO/ThinLTOBitcodeWriter.cpp b/llvm/lib/Transforms/IPO/ThinLTOBitcodeWriter.cpp
index ee3b77cbfbcb8..51c28e352ff16 100644
--- a/llvm/lib/Transforms/IPO/ThinLTOBitcodeWriter.cpp
+++ b/llvm/lib/Transforms/IPO/ThinLTOBitcodeWriter.cpp
@@ -29,22 +29,10 @@ using namespace llvm;
 
 namespace {
 
-// Determine if a promotion alias should be created for a symbol name.
-static bool allowPromotionAlias(const std::string &Name) {
-  // Promotion aliases are used only in inline assembly. It's safe to
-  // simply skip unusual names. Subset of MCAsmInfo::isAcceptableChar().
-  for (const char &C : Name) {
-    if (isAlnum(C) || C == '_' || C == '.')
-      continue;
-    return false;
-  }
-  return true;
-}
-
 // Promote each local-linkage entity defined by ExportM and used by ImportM by
 // changing visibility and appending the given ModuleId.
 void promoteInternals(Module &ExportM, Module &ImportM, StringRef ModuleId,
-                      const SetVector<GlobalValue *> &PromoteExtra) {
+                      SetVector<GlobalValue *> *PromoteExtra = nullptr) {
   DenseMap<const Comdat *, Comdat *> RenamedComdats;
   for (auto &ExportGV : ExportM.global_values()) {
     if (!ExportGV.hasLocalLinkage())
@@ -52,7 +40,8 @@ void promoteInternals(Module &ExportM, Module &ImportM, StringRef ModuleId,
 
     auto Name = ExportGV.getName();
     GlobalValue *ImportGV = nullptr;
-    if (!PromoteExtra.count(&ExportGV)) {
+    const bool MustPromote = PromoteExtra && PromoteExtra->count(&ExportGV);
+    if (!MustPromote) {
       ImportGV = ImportM.getNamedValue(Name);
       if (!ImportGV)
         continue;
@@ -70,24 +59,32 @@ void promoteInternals(Module &ExportM, Module &ImportM, StringRef ModuleId,
       if (C->getName() == Name)
         RenamedComdats.try_emplace(C, ExportM.getOrInsertComdat(NewName));
 
-    ExportGV.setName(NewName);
-    ExportGV.setLinkage(GlobalValue::ExternalLinkage);
-    ExportGV.setVisibility(GlobalValue::HiddenVisibility);
-    // TODO: remove this reassign and instead create an alias.
-    ExportGV.reassignGUID();
+    // We must use the function's value type (FunctionType), not ptr - hence
+    // ExportGV.getValueType() rather than getType(). Otherwise, when an
+    // internal coroutine is imported into another module, IRMover sees a
+    // non-function value type for the unimported alias and materializes it as
+    // an external GlobalVariable rather than a Function declaration. That
+    // violates the verifier requirement that the coroutine argument of
+    // @llvm.coro.id must refer to a function. We could "pierce through" by
+    // stripping pointers, and cases other than coro do that, but this is
+    // cleaner.
+    auto *ExternalAlias = GlobalAlias::create(
+        ExportGV.getValueType(), ExportGV.getAddressSpace(),
+        GlobalValue::ExternalLinkage, NewName, &ExportGV, &ExportM);
+    ExternalAlias->setVisibility(GlobalValue::HiddenVisibility);
+    ExportGV.replaceUsesWithIf(
+        ExternalAlias, [](Use &U) { return !isa<GlobalAlias>(U.getUser()); });
+
+    if (MustPromote) {
+      PromoteExtra->remove(&ExportGV);
+      PromoteExtra->insert(ExternalAlias);
+    }
+
     if (ImportGV) {
       ImportGV->setName(NewName);
       ImportGV->setVisibility(GlobalValue::HiddenVisibility);
       ImportGV->reassignGUID();
     }
-
-    if (isa<Function>(&ExportGV) && allowPromotionAlias(OldName)) {
-      // Create a local alias with the original name to avoid breaking
-      // references from inline assembly.
-      std::string Alias =
-          ".lto_set_conditional " + OldName + "," + NewName + "\n";
-      ExportM.appendModuleInlineAsm(Alias);
-    }
   }
 
   if (!RenamedComdats.empty())
@@ -411,8 +408,8 @@ void splitAndWriteThinLTOBitcode(
   // match values from its first argument (the "exporting module") in
   // CfiFunctions. So we only need CfiFunctions for the second promotion (M ->
   // MergedM)
-  promoteInternals(*MergedM, M, ModuleId, {});
-  promoteInternals(M, *MergedM, ModuleId, CfiFunctions);
+  promoteInternals(*MergedM, M, ModuleId, nullptr);
+  promoteInternals(M, *MergedM, ModuleId, &CfiFunctions);
 
   // FIXME: Try to re-use PSI from the original module here.
   ProfileSummaryInfo PSI(M);
diff --git a/llvm/test/ThinLTO/X86/cfi-import-internal-coroutine.ll b/llvm/test/ThinLTO/X86/cfi-import-internal-coroutine.ll
new file mode 100644
index 0000000000000..622db2b6a8f6f
--- /dev/null
+++ b/llvm/test/ThinLTO/X86/cfi-import-internal-coroutine.ll
@@ -0,0 +1,60 @@
+; When ThinLTOBitcodeWriter promotes an internal function by creating an external
+; alias, the alias must use the function's value type (FunctionType), not ptr.
+; Otherwise, when an internal coroutine is imported into another module, IRMover
+; sees a non-function value type for the unimported alias and materializes it as
+; an external GlobalVariable rather than a Function declaration. That violates the
+; verifier requirement that the coroutine argument of @llvm.coro.id must refer to
+; a function.
+;
+; REQUIRES: x86-registered-target
+
+; RUN: rm -rf %t.dir && split-file %s %t.dir
+; RUN: opt -thinlto-bc -thinlto-split-lto-unit %t.dir/a.ll -o %t.dir/a.bc
+; RUN: opt -thinlto-bc -thinlto-split-lto-unit %t.dir/b.ll -o %t.dir/b.bc
+; RUN: llvm-lto2 run -save-temps %t.dir/a.bc %t.dir/b.bc -o %t.dir/out \
+; RUN:   -r=%t.dir/a.bc,get_vtable,plx \
+; RUN:   -r=%t.dir/a.bc,vtable.b66cc3a748330e63b2451fa89522eb06,l \
+; RUN:   -r=%t.dir/a.bc,coro.b66cc3a748330e63b2451fa89522eb06,plx \
+; RUN:   -r=%t.dir/a.bc,vtable.b66cc3a748330e63b2451fa89522eb06,plx \
+; RUN:   -r=%t.dir/a.bc,coro.b66cc3a748330e63b2451fa89522eb06,l \
+; RUN:   -r=%t.dir/b.bc,caller,plx
+; RUN: llvm-dis %t.dir/out.2.3.import.bc -o - | FileCheck %s --check-prefix=IMPORT
+
+; IMPORT: define available_externally hidden ptr @coro.llvm.{{[0-9]+}}()
+; IMPORT-NEXT: %id = call token @llvm.coro.id(i32 8, ptr null, ptr nonnull @coro.b66cc3a748330e63b2451fa89522eb06, ptr null)
+; IMPORT: declare ptr @coro.b66cc3a748330e63b2451fa89522eb06()
+
+;--- a.ll
+source_filename = "a.ll"
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+ at vtable = internal constant [1 x ptr] [ptr @coro], !type !0
+
+define internal ptr @coro() {
+  %id = call token @llvm.coro.id(i32 8, ptr null, ptr nonnull @coro, ptr null)
+  %hdl = call ptr @llvm.coro.begin(token %id, ptr null)
+  ret ptr %hdl
+}
+
+define hidden ptr @get_vtable() {
+  ret ptr @vtable
+}
+
+declare token @llvm.coro.id(i32, ptr readnone, ptr nocapture readonly, ptr)
+declare ptr @llvm.coro.begin(token, ptr writeonly)
+
+!0 = !{i64 0, !"_ZTS1A"}
+
+;--- b.ll
+source_filename = "b.ll"
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+; Indirect call with VP metadata targeting the GUID of a.ll:coro (1497692708810309344)
+define hidden ptr @caller(ptr %fp) {
+  %r = call ptr %fp(), !prof !0
+  ret ptr %r
+}
+
+!0 = !{!"VP", i32 0, i64 1, i64 1497692708810309344, i64 1}
diff --git a/llvm/test/Transforms/LowerTypeTests/promoted-internal.ll b/llvm/test/Transforms/LowerTypeTests/promoted-internal.ll
new file mode 100644
index 0000000000000..1654a089270b3
--- /dev/null
+++ b/llvm/test/Transforms/LowerTypeTests/promoted-internal.ll
@@ -0,0 +1,30 @@
+; RUN: split-file %s %t
+; RUN: opt -passes=lowertypetests -lowertypetests-summary-action=import \
+; RUN:  -lowertypetests-read-summary=%t/import.yaml %t/module.ll -S -o - | FileCheck %s
+
+; CHECK: @f.llvm.1234 = alias void (), ptr @f.5678.cfi
+; CHECK: define hidden void @f.5678.cfi()
+; CHECK: declare void @f.5678()
+
+;--- import.yaml
+---
+CfiFunctionDefs:
+- Name: f.5678
+  GUID: 4670599147315008938
+...
+
+;--- module.ll
+source_filename = "promoted-internal.ll"
+
+; f.5678 GUID: 4670599147315008938
+ at f.5678 = hidden alias void (), ptr @f.llvm.1234
+
+define void @f.llvm.1234() !type !0 !guid !{i64 1234} {
+  ret void
+}
+
+!cfi.functions = !{!1}
+!aliases = !{!2}
+!0 = !{i64 0, !"_ZTSFvE"}
+!1 = !{!"f.5678", i8 0, i64 4670599147315008938, !1}
+!2 = !{!"f", !"f.5678"}
diff --git a/llvm/test/Transforms/ThinLTOBitcodeWriter/cfi-icall-static-inline-asm.ll b/llvm/test/Transforms/ThinLTOBitcodeWriter/cfi-icall-static-inline-asm.ll
index 61141c71335bf..9d2ee425031ab 100644
--- a/llvm/test/Transforms/ThinLTOBitcodeWriter/cfi-icall-static-inline-asm.ll
+++ b/llvm/test/Transforms/ThinLTOBitcodeWriter/cfi-icall-static-inline-asm.ll
@@ -3,8 +3,7 @@
 
 target triple = "x86_64-unknown-linux-gnu"
 
-; CHECK: module asm
-; CHECK-NEXT: ".lto_set_conditional a,a.[[HASH:[0-9a-f]+]]"
+; CHECK: @a.[[HASH:[0-9a-f]+]] = hidden alias
 
 define void @b() {
   %f = alloca ptr, align 8
@@ -15,7 +14,7 @@ define void @b() {
   ret void
 }
 
-; CHECK: define{{.*}} @a.[[HASH]](){{.*}} !type
+; CHECK: define internal void @a() {{.*}}!type
 define internal void @a() !type !0 {
   ret void
 }
diff --git a/llvm/test/Transforms/ThinLTOBitcodeWriter/cfi_dso_local_equivalent.ll b/llvm/test/Transforms/ThinLTOBitcodeWriter/cfi_dso_local_equivalent.ll
new file mode 100644
index 0000000000000..b425ff0b50de7
--- /dev/null
+++ b/llvm/test/Transforms/ThinLTOBitcodeWriter/cfi_dso_local_equivalent.ll
@@ -0,0 +1,33 @@
+; REQUIRES: x86-registered-target
+
+; Verify that the dso_local_equivalent use of f becomes a dso_local_equivalent
+; use of the alias to f that is introduced for CFI.
+
+; RUN: opt -thinlto-bc -thinlto-split-lto-unit -o %t %s
+; RUN: llvm-modextract -b -n 0 -o - %t | llvm-dis | FileCheck --check-prefix=M0 %s
+; RUN: llvm-modextract -b -n 1 -o - %t | llvm-dis | FileCheck --check-prefix=M1 %s
+
+target triple = "x86_64-unknown-linux-gnu"
+
+; M0: @vtable.{{[0-9a-f]+}} = external hidden constant [1 x ptr]
+; M0: @f.{{[0-9a-f]+}} = hidden alias void (), ptr @f
+; M0: define internal void @f()
+; M0: define ptr @use()
+; M0-NEXT: ret ptr @vtable.{{[0-9a-f]+}}
+
+; M1: @vtable = internal constant [1 x ptr] [ptr dso_local_equivalent @f.{{[0-9a-f]+}}]
+; M1: @vtable.{{[0-9a-f]+}} = hidden alias [1 x ptr], ptr @vtable
+; M1: declare !guid !{{[0-9]+}} hidden void @f.{{[0-9a-f]+}}()
+ at vtable = internal constant [1 x ptr] [
+  ptr dso_local_equivalent @f
+], !type !0
+
+define internal void @f() {
+  ret void
+}
+
+define ptr @use() {
+  ret ptr @vtable
+}
+
+!0 = !{i32 0, !"typeid"}
diff --git a/llvm/test/Transforms/ThinLTOBitcodeWriter/comdat.ll b/llvm/test/Transforms/ThinLTOBitcodeWriter/comdat.ll
index f9545e2010c10..1b02fa0ff505c 100644
--- a/llvm/test/Transforms/ThinLTOBitcodeWriter/comdat.ll
+++ b/llvm/test/Transforms/ThinLTOBitcodeWriter/comdat.ll
@@ -25,9 +25,8 @@ $nt = comdat any
 ; MERGED-SAME: comdat(${{"?lwt[^ ]+}})
 @lwt_aliasee = private unnamed_addr global [1 x ptr] [ptr null], comdat($lwt), !type !0
 
-; MERGED: {{@"?lwt_nl[^ ]+}} = hidden unnamed_addr global
-; MERGED-SAME: comdat(${{"?lwt[^ ]+}})
-; THIN: {{@"?lwt_nl[^ ]+}} = external hidden
+; MERGED: @lwt_nl = internal unnamed_addr global i32 0, comdat(${{"?lwt[^ ]+}})
+; THIN: {{@"?lwt_nl\.[^ ]+}} = external hidden unnamed_addr global i32
 @lwt_nl = internal unnamed_addr global i32 0, comdat($lwt)
 
 ; MERGED: @nlwt_aliasee = private unnamed_addr global
@@ -47,12 +46,16 @@ $nt = comdat any
 ; THIN-SAME: comdat($nt)
 @nt_nl = internal unnamed_addr global i32 0, comdat($nt)
 
-; MERGED: {{@"?lwt[^ ]+}} = hidden unnamed_addr alias
-; THIN: {{@"?lwt[^ ]+}} = external hidden
+; MERGED: @lwt = internal unnamed_addr alias [1 x ptr], ptr @lwt_aliasee
+; MERGED: @nlwt_nl = internal unnamed_addr alias [1 x ptr], ptr @nlwt_aliasee
+; MERGED: {{@"?lwt_nl\.[^ ]+}} = hidden alias i32, ptr @lwt_nl
+; MERGED: {{@"?lwt\.[^ ]+}} = hidden alias [1 x ptr], ptr @lwt
+; MERGED: {{@"?nlwt_nl\.[^ ]+}} = hidden alias [1 x ptr], ptr @nlwt_nl
+
+; THIN: {{@"?lwt\.[^ ]+}} = external hidden global [1 x ptr]
 @lwt = internal unnamed_addr alias [1 x ptr], ptr @lwt_aliasee
 
-; MERGED: {{@"?nlwt_nl[^ ]+}} = hidden unnamed_addr alias
-; THIN: {{@"?nlwt_nl[^ ]+}} = external hidden
+; THIN: {{@"?nlwt_nl\.[^ ]+}} = external hidden global [1 x ptr]
 @nlwt_nl = internal unnamed_addr alias [1 x ptr], ptr @nlwt_aliasee
 
 ; The functions below exist just to make sure the globals are used.
diff --git a/llvm/test/Transforms/ThinLTOBitcodeWriter/split-internal1.ll b/llvm/test/Transforms/ThinLTOBitcodeWriter/split-internal1.ll
index 1a6cbb7e7e84b..89e069110fc00 100644
--- a/llvm/test/Transforms/ThinLTOBitcodeWriter/split-internal1.ll
+++ b/llvm/test/Transforms/ThinLTOBitcodeWriter/split-internal1.ll
@@ -13,7 +13,8 @@
 ; BCA1-NOT: <GLOBALVAL_SUMMARY_BLOCK
 
 ; M0: @g.581d7631532fa146ba4061179da39272 = external hidden global i8
-; M1: @g.581d7631532fa146ba4061179da39272 = hidden global i8 42, !type !0
+; M1: @g = internal global i8 42, !type !0
+; M1: @g.581d7631532fa146ba4061179da39272 = hidden alias i8, ptr @g
 @g = internal global i8 42, !type !0
 
 ; M0: define ptr @f()
diff --git a/llvm/test/Transforms/ThinLTOBitcodeWriter/split-internal2.ll b/llvm/test/Transforms/ThinLTOBitcodeWriter/split-internal2.ll
index 1a7b1aba66576..b5c4a2f88faf9 100644
--- a/llvm/test/Transforms/ThinLTOBitcodeWriter/split-internal2.ll
+++ b/llvm/test/Transforms/ThinLTOBitcodeWriter/split-internal2.ll
@@ -20,8 +20,9 @@ target triple = "x86_64-unknown-linux-gnu"
 ; M1: @g = global ptr @f.13757e0fb71915e385efa4dc9d1e08fd, !type !0
 @g = global ptr @f, !type !0
 
-; M0: define hidden void @f.13757e0fb71915e385efa4dc9d1e08fd()
-; M1: declare !guid !4 hidden void @f.13757e0fb71915e385efa4dc9d1e08fd()
+; M0: @f.13757e0fb71915e385efa4dc9d1e08fd = hidden alias void (), ptr @f
+; M0: define internal void @f()
+; M1: declare !guid !{{[0-9]+}} hidden void @f.13757e0fb71915e385efa4dc9d1e08fd()
 define internal void @f() {
   call void @f2()
   ret void
diff --git a/llvm/test/Transforms/ThinLTOBitcodeWriter/split-vfunc-internal.ll b/llvm/test/Transforms/ThinLTOBitcodeWriter/split-vfunc-internal.ll
index 75932fd84d83e..11ab5f8ea9905 100644
--- a/llvm/test/Transforms/ThinLTOBitcodeWriter/split-vfunc-internal.ll
+++ b/llvm/test/Transforms/ThinLTOBitcodeWriter/split-vfunc-internal.ll
@@ -10,12 +10,14 @@ define ptr @source() {
 }
 
 ; M0: @g.84f59439b469192440047efc8de357fb = external hidden constant [1 x ptr]
-; M1: @g.84f59439b469192440047efc8de357fb = hidden constant [1 x ptr] [ptr @ok.84f59439b469192440047efc8de357fb]
+; M1: @g = internal constant [1 x ptr] [ptr @ok.84f59439b469192440047efc8de357fb]
+; M1: @g.84f59439b469192440047efc8de357fb = hidden alias [1 x ptr], ptr @g
 @g = internal constant [1 x ptr] [
   ptr @ok
 ], !type !0
 
-; M0: define hidden i64 @ok.84f59439b469192440047efc8de357fb
+; M0: @ok.84f59439b469192440047efc8de357fb = hidden alias i64 (ptr), ptr @ok
+; M0: define internal i64 @ok(ptr %this)
 ; M1: define available_externally hidden i64 @ok.84f59439b469192440047efc8de357fb
 define internal i64 @ok(ptr %this) {
   ret i64 42



More information about the llvm-commits mailing list