[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