[clang] [CIR] Update uses of no-prototype GetGlobalOp (PR #193868)

Andy Kaylor via cfe-commits cfe-commits at lists.llvm.org
Fri Apr 24 10:46:18 PDT 2026


https://github.com/andykaylor updated https://github.com/llvm/llvm-project/pull/193868

>From 2c3b921c0606b0c8e3c3a1c9eb33ff7b40c5ac96 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Thu, 23 Apr 2026 16:47:45 -0700
Subject: [PATCH 1/2] [CIR] Update uses of no-prototype GetGlobalOp

When a no-prototype function is replaced by a proper definition, we update
uses of the previous function, mostly cir::GetGlobalOp operations. If
the result of the GetGlobalOp was being used in a store, this was
leading to verifier errors because the type being stored no longer
matched the expected type. This change fixes that by introducing a
bitcast when the GetGlobalOp is updated. It also introduces a new
cast folder to eliminate cast chains that are circular after this
new bitcast is inserted.

Assisted-by: Cursor / claude-4.7-opus-high
---
 clang/lib/CIR/CodeGen/CIRGenModule.cpp        | 20 ++++++--
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       | 47 ++++++++++++++-----
 clang/test/CIR/CodeGen/no-proto-then-def.c    | 28 +++++++++++
 clang/test/CIR/CodeGen/no-prototype.c         |  3 +-
 .../cast-bitcast-funcptr-roundtrip-fold.cir   | 16 +++++++
 5 files changed, 97 insertions(+), 17 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/no-proto-then-def.c
 create mode 100644 clang/test/CIR/Transforms/cast-bitcast-funcptr-roundtrip-fold.cir

diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index e3c8dd270d964..7cf682072eea9 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -1816,9 +1816,23 @@ void CIRGenModule::replaceUsesOfNonProtoTypeWithRealFunction(
       noProtoCallOp.erase();
     } else if (auto getGlobalOp =
                    mlir::dyn_cast<cir::GetGlobalOp>(use.getUser())) {
-      // Replace type
-      getGlobalOp.getAddr().setType(
-          cir::PointerType::get(newFn.getFunctionType()));
+      // The GetGlobal was emitted with the no-proto FuncType. Uses of this
+      // operation (cir.store, cir.cast) were built for that pointer type. When
+      // we re-type the result to the real FuncType, we need to add a bit the
+      // old pointer type so those uses are still valid. This can lead to
+      // some redundant bitcast chains, but those will be cleaned up by the
+      // canonicalizer.
+      mlir::Value res = getGlobalOp.getAddr();
+      const mlir::Type oldResTy = res.getType();
+      const auto newPtrTy = cir::PointerType::get(newFn.getFunctionType());
+      res.setType(newPtrTy);
+      if (oldResTy != newPtrTy) {
+        builder.setInsertionPointAfter(getGlobalOp.getOperation());
+        mlir::Value castRes =
+            cir::CastOp::create(builder, getGlobalOp.getLoc(), oldResTy,
+                                cir::CastKind::bitcast, res);
+        res.replaceAllUsesExcept(castRes, castRes.getDefiningOp());
+      }
     } else if (mlir::isa<cir::GlobalOp>(use.getUser())) {
       // Function addresses in global initializers use GlobalViewAttrs typed to
       // the initializer context (e.g. struct field type), not the FuncOp type,
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 35a31b0dbda63..f4546fe6db692 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -744,6 +744,11 @@ static bool isIntOrBoolCast(cir::CastOp op) {
          kind == cir::CastKind::int_to_bool || kind == cir::CastKind::integral;
 }
 
+static bool isCirFunctionPointerType(mlir::Type ty) {
+  const auto ptrTy = mlir::dyn_cast<cir::PointerType>(ty);
+  return ptrTy && mlir::isa<cir::FuncType>(ptrTy.getPointee());
+}
+
 static Value tryFoldCastChain(cir::CastOp op) {
   cir::CastOp head = op, tail = op;
 
@@ -754,21 +759,37 @@ static Value tryFoldCastChain(cir::CastOp op) {
     op = head.getSrc().getDefiningOp<cir::CastOp>();
   }
 
-  if (head == tail)
+  if (head != tail) {
+    // if bool_to_int -> ...  -> int_to_bool: take the bool
+    // as we had it was before all casts
+    if (head.getKind() == cir::CastKind::bool_to_int &&
+        tail.getKind() == cir::CastKind::int_to_bool)
+      return head.getSrc();
+
+    // if int_to_bool -> ...  -> int_to_bool: take the result
+    // of the first one, as no other casts (and ext casts as well)
+    // don't change the first result
+    if (head.getKind() == cir::CastKind::int_to_bool &&
+        tail.getKind() == cir::CastKind::int_to_bool)
+      return head.getResult();
+
     return {};
+  }
 
-  // if bool_to_int -> ...  -> int_to_bool: take the bool
-  // as we had it was before all casts
-  if (head.getKind() == cir::CastKind::bool_to_int &&
-      tail.getKind() == cir::CastKind::int_to_bool)
-    return head.getSrc();
-
-  // if int_to_bool -> ...  -> int_to_bool: take the result
-  // of the first one, as no other casts (and ext casts as well)
-  // don't change the first result
-  if (head.getKind() == cir::CastKind::int_to_bool &&
-      tail.getKind() == cir::CastKind::int_to_bool)
-    return head.getResult();
+  // Bitcast round-trip on function pointers: T0 -> T1 -> T0 (e.g. no-proto
+  // redeclaration vs. actual prototype). Restrict to function pointers so
+  // other pointer bitcast chains are unchanged.
+  if (tail.getKind() == cir::CastKind::bitcast) {
+    auto *inner = tail.getSrc().getDefiningOp();
+    if (inner && isCirFunctionPointerType(tail.getType())) {
+      auto innerCast = mlir::dyn_cast<cir::CastOp>(inner);
+      if (innerCast && innerCast.getKind() == cir::CastKind::bitcast &&
+          innerCast.getSrc().getType() == tail.getType() &&
+          innerCast.getType() == tail.getSrc().getType()) {
+        return innerCast.getSrc();
+      }
+    }
+  }
 
   return {};
 }
diff --git a/clang/test/CIR/CodeGen/no-proto-then-def.c b/clang/test/CIR/CodeGen/no-proto-then-def.c
new file mode 100644
index 0000000000000..c142c4f0a16f5
--- /dev/null
+++ b/clang/test/CIR/CodeGen/no-proto-then-def.c
@@ -0,0 +1,28 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir -Wno-deprecated-non-prototype %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm -Wno-deprecated-non-prototype %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -Wno-deprecated-non-prototype %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM
+
+extern int default_proc();
+
+int test_proc_ptr(int (*proc)()) {
+  if (!proc)
+    proc = default_proc;
+  return 0;
+}
+
+int default_proc(int a) { return a; }
+
+// Address of a no-proto decl is taken before the real definition is emitted; the
+// final FuncOp must be bitcast back to the no-proto function pointer type for
+// stores and other uses that were typed from the earlier declaration.
+// CIR: cir.func{{.*}}@test_proc_ptr
+// CIR:   cir.get_global @default_proc : !cir.ptr<!cir.func<(!s32i) -> !s32i>>
+// CIR:   cir.cast bitcast %{{.*}} : !cir.ptr<!cir.func<(!s32i) -> !s32i>> -> !cir.ptr<!cir.func<(...) -> !s32i>>
+
+// LLVM: define{{.*}} @test_proc_ptr
+// LLVM:   store ptr @default_proc, ptr
+// LLVM:   define{{.*}} @default_proc
+// LLVM:   ret i32
diff --git a/clang/test/CIR/CodeGen/no-prototype.c b/clang/test/CIR/CodeGen/no-prototype.c
index 826f7033d7fd1..1a3885ac1cf7e 100644
--- a/clang/test/CIR/CodeGen/no-prototype.c
+++ b/clang/test/CIR/CodeGen/no-prototype.c
@@ -77,7 +77,8 @@ int noProto5();
 int test5(int x) {
   return noProto5();
   // CHECK:  [[GGO:%.*]] = cir.get_global @noProto5 : !cir.ptr<!cir.func<(!s32i) -> !s32i>>
-  // CHECK:  [[CAST:%.*]] = cir.cast bitcast [[GGO]] : !cir.ptr<!cir.func<(!s32i) -> !s32i>> -> !cir.ptr<!cir.func<() -> !s32i>>
+  // CHECK:  [[TO_NOPROTO:%.*]] = cir.cast bitcast [[GGO]] : !cir.ptr<!cir.func<(!s32i) -> !s32i>> -> !cir.ptr<!cir.func<(...) -> !s32i>>
+  // CHECK:  [[CAST:%.*]] = cir.cast bitcast [[TO_NOPROTO]] : !cir.ptr<!cir.func<(...) -> !s32i>> -> !cir.ptr<!cir.func<() -> !s32i>>
   // CHECK:  {{%.*}} = cir.call [[CAST]]() : (!cir.ptr<!cir.func<() -> !s32i>>) -> !s32i
 }
 int noProto5(int x) { return x; }
diff --git a/clang/test/CIR/Transforms/cast-bitcast-funcptr-roundtrip-fold.cir b/clang/test/CIR/Transforms/cast-bitcast-funcptr-roundtrip-fold.cir
new file mode 100644
index 0000000000000..a8979f6adfe71
--- /dev/null
+++ b/clang/test/CIR/Transforms/cast-bitcast-funcptr-roundtrip-fold.cir
@@ -0,0 +1,16 @@
+// RUN: cir-opt %s -cir-canonicalize -o - | FileCheck %s
+
+!s32i = !cir.int<s, 32>
+!fnp = !cir.func<(!s32i) -> !s32i>
+!fnnp = !cir.func<(...) -> !s32i>
+!pfnp = !cir.ptr<!fnp>
+!pfnnp = !cir.ptr<!fnnp>
+
+// Two function-pointer bitcasts that round-trip fold to the original value.
+// CHECK-LABEL: cir.func @round_trip
+// CHECK-NEXT:    cir.return %arg0
+cir.func @round_trip(%arg0: !pfnp) -> !pfnp {
+  %0 = cir.cast bitcast %arg0 : !pfnp -> !pfnnp
+  %1 = cir.cast bitcast %0 : !pfnnp -> !pfnp
+  cir.return %1 : !pfnp
+}

>From 96cebb3791dbb957a0b631ea9aa12bc59f37a576 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Fri, 24 Apr 2026 10:45:39 -0700
Subject: [PATCH 2/2] Adressed review feedback

---
 clang/lib/CIR/CodeGen/CIRGenModule.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 7cf682072eea9..709f4e9129faf 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -1825,8 +1825,8 @@ void CIRGenModule::replaceUsesOfNonProtoTypeWithRealFunction(
       mlir::Value res = getGlobalOp.getAddr();
       const mlir::Type oldResTy = res.getType();
       const auto newPtrTy = cir::PointerType::get(newFn.getFunctionType());
-      res.setType(newPtrTy);
       if (oldResTy != newPtrTy) {
+        res.setType(newPtrTy);
         builder.setInsertionPointAfter(getGlobalOp.getOperation());
         mlir::Value castRes =
             cir::CastOp::create(builder, getGlobalOp.getLoc(), oldResTy,



More information about the cfe-commits mailing list