[clang] [CIR] Freeze maybe_undef call arguments (PR #234124)
Arseniy Obolenskiy via cfe-commits
cfe-commits at lists.llvm.org
Sat Oct 24 03:31:42 PDT 2026
https://github.com/aobolensk updated https://github.com/llvm/llvm-project/pull/234124
>From e8453854957f93fdf08999f1544eb4dacfe84e04 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Wed, 21 Oct 2026 11:12:04 +0200
Subject: [PATCH 1/2] [CIR] Freeze maybe_undef call arguments
Classic codegen freezes arguments passed to maybe_undef parameters, so do the same in CIR
---
clang/lib/CIR/CodeGen/CIRGenCall.cpp | 25 +++++++++++
clang/test/CIR/CodeGen/attr-maybeundef.cpp | 51 ++++++++++++++++++++++
2 files changed, 76 insertions(+)
create mode 100644 clang/test/CIR/CodeGen/attr-maybeundef.cpp
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 2d9113c278993..357b269f51492 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -1241,6 +1241,27 @@ CIRGenTypes::arrangeFreeFunctionType(CanQual<FunctionNoProtoType> fnpt) {
fnpt->getExtInfo(), RequiredArgs(0));
}
+/// Check if the argument of a function has maybe_undef attribute.
+static bool isArgumentMaybeUndef(const Decl *targetDecl,
+ unsigned numRequiredArgs, unsigned argNo) {
+ const auto *fd = dyn_cast_or_null<FunctionDecl>(targetDecl);
+ if (!fd)
+ return false;
+
+ // Assume variadic arguments do not have maybe_undef attribute.
+ if (argNo >= numRequiredArgs)
+ return false;
+
+ // Check if argument has maybe_undef attribute.
+ if (argNo < fd->getNumParams()) {
+ const ParmVarDecl *param = fd->getParamDecl(argNo);
+ if (param && param->hasAttr<MaybeUndefAttr>())
+ return true;
+ }
+
+ return false;
+}
+
RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo,
const CIRGenCallee &callee,
ReturnValueSlot returnValue,
@@ -1359,6 +1380,10 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo,
assert(!cir::MissingFeatures::lowerAggregateLoadStore());
cirCallArgs[argNo] = builder.createLoad(argLoc, src);
}
+
+ if (isArgumentMaybeUndef(targetDecl, funcInfo.getNumRequiredArgs(), argNo))
+ cirCallArgs[argNo] =
+ cir::FreezeOp::create(builder, loc, cirCallArgs[argNo]);
}
const CIRGenCallee &concreteCallee = callee.prepareConcreteCallee(*this);
diff --git a/clang/test/CIR/CodeGen/attr-maybeundef.cpp b/clang/test/CIR/CodeGen/attr-maybeundef.cpp
new file mode 100644
index 0000000000000..af2f0348617ab
--- /dev/null
+++ b/clang/test/CIR/CodeGen/attr-maybeundef.cpp
@@ -0,0 +1,51 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --check-prefixes=LLVM,LLVMCIR --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefixes=LLVM,OGCG --input-file=%t.ll %s
+
+#define __maybe_undef __attribute__((maybe_undef))
+
+struct S { int a, b; };
+void scalar(int a, int __maybe_undef b, int c);
+void variadic(int __maybe_undef x, ...);
+void record(S __maybe_undef s);
+
+void test() {
+ int x, y, z;
+ S s;
+ scalar(x, y, z);
+ variadic(x, y);
+ record(s);
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z4testv()
+// CIR: %[[X:.*]] = cir.load{{.*}} : !cir.ptr<!s32i>, !s32i
+// CIR: %[[Y:.*]] = cir.load{{.*}} : !cir.ptr<!s32i>, !s32i
+// CIR: %[[Z:.*]] = cir.load{{.*}} : !cir.ptr<!s32i>, !s32i
+// CIR: %[[FY:.*]] = cir.freeze %[[Y]] : !s32i
+// CIR-NEXT: cir.call @_Z6scalariii(%[[X]], %[[FY]], %[[Z]])
+// CIR: %[[X2:.*]] = cir.load{{.*}} : !cir.ptr<!s32i>, !s32i
+// CIR: %[[Y2:.*]] = cir.load{{.*}} : !cir.ptr<!s32i>, !s32i
+// CIR: %[[FX2:.*]] = cir.freeze %[[X2]] : !s32i
+// CIR-NEXT: cir.call @_Z8variadiciz(%[[FX2]], %[[Y2]])
+// CIR: %[[SV:.*]] = cir.load{{.*}} : !cir.ptr<!rec_S>, !rec_S
+// CIR: cir.freeze %[[SV]] : !rec_S
+// CIR: cir.call @_Z6record1S(
+
+// LLVM-LABEL: define{{.*}} void @_Z4testv()
+// LLVM: %[[X:.*]] = load i32
+// LLVM: %[[Y:.*]] = load i32
+// LLVM: %[[Z:.*]] = load i32
+// LLVM: %[[FY:.*]] = freeze i32 %[[Y]]
+// LLVM-NEXT: call void @_Z6scalariii(i32 noundef %[[X]], i32 noundef %[[FY]], i32 noundef %[[Z]])
+// LLVM: %[[X2:.*]] = load i32
+// LLVM: %[[Y2:.*]] = load i32
+// LLVM: %[[FX2:.*]] = freeze i32 %[[X2]]
+// LLVM-NEXT: call void (i32, ...) @_Z8variadiciz(i32 noundef %[[FX2]], i32 noundef %[[Y2]])
+// LLVMCIR: %[[SV:.*]] = load %struct.S
+// LLVMCIR: freeze %struct.S %[[SV]]
+// OGCG: %[[SV:.*]] = load i64
+// OGCG: freeze i64 %[[SV]]
+// LLVM: call void @_Z6record1S(i64
>From bb5fe89b7a6ca7865c5153e865d159257fc5c876 Mon Sep 17 00:00:00 2001
From: Arseniy Obolenskiy <arseniy.obolenskiy at amd.com>
Date: Sat, 24 Oct 2026 12:31:31 +0200
Subject: [PATCH 2/2] address comments
---
clang/include/clang/CodeGenUtils/CallUtils.h | 3 +
clang/lib/CIR/CodeGen/CIRGenCall.cpp | 24 +---
.../TargetLowering/CIRABIRewriteContext.cpp | 30 +++++
clang/lib/CodeGen/CGCall.cpp | 25 +---
clang/lib/CodeGenUtils/CallUtils.cpp | 21 ++++
clang/test/CIR/CodeGen/attr-maybeundef.cpp | 117 +++++++++++++++---
.../abi-lowering/maybe-undef-freeze.cir | 111 +++++++++++++++++
7 files changed, 269 insertions(+), 62 deletions(-)
create mode 100644 clang/test/CIR/Transforms/abi-lowering/maybe-undef-freeze.cir
diff --git a/clang/include/clang/CodeGenUtils/CallUtils.h b/clang/include/clang/CodeGenUtils/CallUtils.h
index 82f135f3be1e6..17110814d3459 100644
--- a/clang/include/clang/CodeGenUtils/CallUtils.h
+++ b/clang/include/clang/CodeGenUtils/CallUtils.h
@@ -55,6 +55,9 @@ CanQual<FunctionProtoType> getFormalType(const CXXMethodDecl *MD);
/// promise never reach a function's arguments or return value.
llvm::FPClassTest getNoFPClassTestMask(const LangOptions &LangOpts);
+bool isArgumentMaybeUndef(const Decl *TargetDecl, unsigned NumRequiredArgs,
+ unsigned ArgNo);
+
} // namespace clang::CodeGenUtils
#endif // LLVM_CLANG_CODEGENUTILS_CALLUTILS_H
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 357b269f51492..f2086854ee883 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -1241,27 +1241,6 @@ CIRGenTypes::arrangeFreeFunctionType(CanQual<FunctionNoProtoType> fnpt) {
fnpt->getExtInfo(), RequiredArgs(0));
}
-/// Check if the argument of a function has maybe_undef attribute.
-static bool isArgumentMaybeUndef(const Decl *targetDecl,
- unsigned numRequiredArgs, unsigned argNo) {
- const auto *fd = dyn_cast_or_null<FunctionDecl>(targetDecl);
- if (!fd)
- return false;
-
- // Assume variadic arguments do not have maybe_undef attribute.
- if (argNo >= numRequiredArgs)
- return false;
-
- // Check if argument has maybe_undef attribute.
- if (argNo < fd->getNumParams()) {
- const ParmVarDecl *param = fd->getParamDecl(argNo);
- if (param && param->hasAttr<MaybeUndefAttr>())
- return true;
- }
-
- return false;
-}
-
RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo,
const CIRGenCallee &callee,
ReturnValueSlot returnValue,
@@ -1381,7 +1360,8 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo,
cirCallArgs[argNo] = builder.createLoad(argLoc, src);
}
- if (isArgumentMaybeUndef(targetDecl, funcInfo.getNumRequiredArgs(), argNo))
+ if (CodeGenUtils::isArgumentMaybeUndef(
+ targetDecl, funcInfo.getNumRequiredArgs(), argNo))
cirCallArgs[argNo] =
cir::FreezeOp::create(builder, loc, cirCallArgs[argNo]);
}
diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp
index c7ca6151029eb..8a8f730b1c43d 100644
--- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp
@@ -17,6 +17,7 @@
#include "clang/CIR/Dialect/IR/CIRTypes.h"
#include "clang/CIR/MissingFeatures.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/ScopeExit.h"
#include "llvm/Support/MathExtras.h"
#include <algorithm>
#include <array>
@@ -975,6 +976,12 @@ static void eraseDeadRecordLoads(ArrayRef<cir::LoadOp> loads) {
load->erase();
}
+/// The old call, their only user, must already be erased.
+static void eraseDeadFreezes(ArrayRef<cir::FreezeOp> freezes) {
+ for (cir::FreezeOp freeze : freezes)
+ freeze->erase();
+}
+
/// The store that spills indirect parameter \p blockArg, if the parameter's
/// only use is that store. Null otherwise.
static cir::StoreOp maybeFindParamSpill(mlir::BlockArgument blockArg) {
@@ -1966,6 +1973,8 @@ CIRABIRewriteContext::rewriteCallSite(mlir::Operation *callOp,
// Indirect argument passes or copies from the address the load read from.
// The old call still uses them, so erase them only after it is gone.
SmallVector<cir::LoadOp> deadRecordLoads;
+ // Erased before deadRecordLoads, since a freeze keeps its load alive.
+ SmallVector<cir::FreezeOp> deadFreezes;
// Capture original arg types before building newArgs (an Indirect argument
// changes the wire argument from T to !cir.ptr<T>, so we save the
@@ -1992,6 +2001,25 @@ CIRABIRewriteContext::rewriteCallSite(mlir::Operation *callOp,
if (ac.kind == ArgKind::Ignore)
continue;
mlir::Value arg = argOperands[idx];
+
+ // Refreeze after the ABI rewrite, as classic CodeGen does, so padding is
+ // frozen too. Indirect freezes only the pointer, so keep non-load freezes.
+ auto freeze = arg.getDefiningOp<cir::FreezeOp>();
+ if (freeze && freeze->hasOneUse() &&
+ (ac.kind != ArgKind::Indirect ||
+ freeze.getInput().getDefiningOp<cir::LoadOp>())) {
+ arg = freeze.getInput();
+ deadFreezes.push_back(freeze);
+ } else {
+ freeze = {};
+ }
+ llvm::scope_exit refreeze([&, firstNewArg = newArgs.size()] {
+ if (!freeze)
+ return;
+ builder.setInsertionPoint(call);
+ for (mlir::Value &newArg : llvm::drop_begin(newArgs, firstNewArg))
+ newArg = cir::FreezeOp::create(builder, freeze.getLoc(), newArg);
+ });
if (cir::RecordType flatTy = getFlattenedCoercedType(ac)) {
// Direct + canFlatten: pass one scalar call argument per field of the
// ABI-coerced struct. A non-atomic record load is read in place or
@@ -2162,6 +2190,7 @@ CIRABIRewriteContext::rewriteCallSite(mlir::Operation *callOp,
origRetTy, origCallArgTypes, builder,
dl)))
return mlir::failure();
+ eraseDeadFreezes(deadFreezes);
eraseDeadRecordLoads(deadRecordLoads);
return mlir::success();
}
@@ -2228,6 +2257,7 @@ CIRABIRewriteContext::rewriteCallSite(mlir::Operation *callOp,
}
call->erase();
+ eraseDeadFreezes(deadFreezes);
eraseDeadRecordLoads(deadRecordLoads);
return mlir::success();
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 9b8b0b52b66cb..342bc8265b480 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -2679,27 +2679,6 @@ static bool DetermineNoUndef(QualType QTy, CodeGenTypes &Types,
return false;
}
-/// Check if the argument of a function has maybe_undef attribute.
-static bool IsArgumentMaybeUndef(const Decl *TargetDecl,
- unsigned NumRequiredArgs, unsigned ArgNo) {
- const auto *FD = dyn_cast_or_null<FunctionDecl>(TargetDecl);
- if (!FD)
- return false;
-
- // Assume variadic arguments do not have maybe_undef attribute.
- if (ArgNo >= NumRequiredArgs)
- return false;
-
- // Check if argument has maybe_undef attribute.
- if (ArgNo < FD->getNumParams()) {
- const ParmVarDecl *Param = FD->getParamDecl(ArgNo);
- if (Param && Param->hasAttr<MaybeUndefAttr>())
- return true;
- }
-
- return false;
-}
-
/// Test if it's legal to apply nofpclass for the given parameter type and it's
/// lowered IR type.
static bool canApplyNoFPClass(const ABIArgInfo &AI, QualType ParamType,
@@ -5828,8 +5807,8 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
unsigned FirstIRArg, NumIRArgs;
std::tie(FirstIRArg, NumIRArgs) = IRFunctionArgs.getIRArgs(ArgNo);
- bool ArgHasMaybeUndefAttr =
- IsArgumentMaybeUndef(TargetDecl, CallInfo.getNumRequiredArgs(), ArgNo);
+ bool ArgHasMaybeUndefAttr = CodeGenUtils::isArgumentMaybeUndef(
+ TargetDecl, CallInfo.getNumRequiredArgs(), ArgNo);
switch (ArgInfo.getKind()) {
case ABIArgInfo::InAlloca: {
diff --git a/clang/lib/CodeGenUtils/CallUtils.cpp b/clang/lib/CodeGenUtils/CallUtils.cpp
index 06747f04955e9..9844f60cd1ce5 100644
--- a/clang/lib/CodeGenUtils/CallUtils.cpp
+++ b/clang/lib/CodeGenUtils/CallUtils.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "clang/CodeGenUtils/CallUtils.h"
+#include "clang/AST/Attr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/Basic/TargetCXXABI.h"
@@ -27,6 +28,26 @@ llvm::FPClassTest getNoFPClassTestMask(const LangOptions &LangOpts) {
return Mask;
}
+bool isArgumentMaybeUndef(const Decl *TargetDecl, unsigned NumRequiredArgs,
+ unsigned ArgNo) {
+ const auto *FD = dyn_cast_or_null<FunctionDecl>(TargetDecl);
+ if (!FD)
+ return false;
+
+ // Assume variadic arguments do not have maybe_undef attribute.
+ if (ArgNo >= NumRequiredArgs)
+ return false;
+
+ // Check if argument has maybe_undef attribute.
+ if (ArgNo < FD->getNumParams()) {
+ const ParmVarDecl *Param = FD->getParamDecl(ArgNo);
+ if (Param && Param->hasAttr<MaybeUndefAttr>())
+ return true;
+ }
+
+ return false;
+}
+
EvaluationOrder getCallArgEvaluationOrder(const CallExpr *E) {
// Note that we allow this to override the order dictated by the calling
// convention on the MS ABI, which means that parameter destruction order is
diff --git a/clang/test/CIR/CodeGen/attr-maybeundef.cpp b/clang/test/CIR/CodeGen/attr-maybeundef.cpp
index af2f0348617ab..0d78e4b2f7627 100644
--- a/clang/test/CIR/CodeGen/attr-maybeundef.cpp
+++ b/clang/test/CIR/CodeGen/attr-maybeundef.cpp
@@ -1,26 +1,34 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
-// RUN: FileCheck --check-prefixes=LLVM,LLVMCIR --input-file=%t-cir.ll %s
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
-// RUN: FileCheck --check-prefixes=LLVM,OGCG --input-file=%t.ll %s
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
#define __maybe_undef __attribute__((maybe_undef))
-struct S { int a, b; };
+struct Pad { char c; int i; };
+struct Dbl { double a, b; };
+struct Big { int a[10]; };
+struct NT { NT(const NT &); int x; };
+
void scalar(int a, int __maybe_undef b, int c);
void variadic(int __maybe_undef x, ...);
-void record(S __maybe_undef s);
+void coerced(Pad __maybe_undef p);
+void flattened(Dbl __maybe_undef d);
+void byval(Big __maybe_undef b);
+void bitint(_BitInt(129) __maybe_undef x);
+void indirect(NT __maybe_undef n);
+void cfloat(_Complex float __maybe_undef c);
+void cdouble(_Complex double __maybe_undef c);
-void test() {
+void test_scalar() {
int x, y, z;
- S s;
scalar(x, y, z);
variadic(x, y);
- record(s);
}
-// CIR-LABEL: cir.func{{.*}} @_Z4testv()
+// CIR-LABEL: cir.func{{.*}} @_Z11test_scalarv()
// CIR: %[[X:.*]] = cir.load{{.*}} : !cir.ptr<!s32i>, !s32i
// CIR: %[[Y:.*]] = cir.load{{.*}} : !cir.ptr<!s32i>, !s32i
// CIR: %[[Z:.*]] = cir.load{{.*}} : !cir.ptr<!s32i>, !s32i
@@ -30,11 +38,8 @@ void test() {
// CIR: %[[Y2:.*]] = cir.load{{.*}} : !cir.ptr<!s32i>, !s32i
// CIR: %[[FX2:.*]] = cir.freeze %[[X2]] : !s32i
// CIR-NEXT: cir.call @_Z8variadiciz(%[[FX2]], %[[Y2]])
-// CIR: %[[SV:.*]] = cir.load{{.*}} : !cir.ptr<!rec_S>, !rec_S
-// CIR: cir.freeze %[[SV]] : !rec_S
-// CIR: cir.call @_Z6record1S(
-// LLVM-LABEL: define{{.*}} void @_Z4testv()
+// LLVM-LABEL: define{{.*}} void @_Z11test_scalarv()
// LLVM: %[[X:.*]] = load i32
// LLVM: %[[Y:.*]] = load i32
// LLVM: %[[Z:.*]] = load i32
@@ -44,8 +49,86 @@ void test() {
// LLVM: %[[Y2:.*]] = load i32
// LLVM: %[[FX2:.*]] = freeze i32 %[[X2]]
// LLVM-NEXT: call void (i32, ...) @_Z8variadiciz(i32 noundef %[[FX2]], i32 noundef %[[Y2]])
-// LLVMCIR: %[[SV:.*]] = load %struct.S
-// LLVMCIR: freeze %struct.S %[[SV]]
-// OGCG: %[[SV:.*]] = load i64
-// OGCG: freeze i64 %[[SV]]
-// LLVM: call void @_Z6record1S(i64
+
+// The coerced value is frozen, so the padding bytes are frozen too.
+void test_coerced(Pad p) { coerced(p); }
+
+// CIR-LABEL: cir.func{{.*}} @_Z12test_coerced3Pad(
+// CIR: %[[V:.*]] = cir.load{{.*}} : !cir.ptr<!u64i>, !u64i
+// CIR-NEXT: %[[F:.*]] = cir.freeze %[[V]] : !u64i
+// CIR-NEXT: cir.call @_Z7coerced3Pad(%[[F]])
+
+// LLVM-LABEL: define{{.*}} void @_Z12test_coerced3Pad(
+// LLVM: %[[V:.*]] = load i64
+// LLVM-NEXT: %[[F:.*]] = freeze i64 %[[V]]
+// LLVM-NEXT: call void @_Z7coerced3Pad(i64 %[[F]])
+
+void test_flattened(Dbl d) { flattened(d); }
+
+// CIR-LABEL: cir.func{{.*}} @_Z14test_flattened3Dbl(
+// CIR: %[[A:.*]] = cir.load{{.*}} : !cir.ptr<!cir.double>, !cir.double
+// CIR: %[[B:.*]] = cir.load{{.*}} : !cir.ptr<!cir.double>, !cir.double
+// CIR-NEXT: %[[FA:.*]] = cir.freeze %[[A]] : !cir.double
+// CIR-NEXT: %[[FB:.*]] = cir.freeze %[[B]] : !cir.double
+// CIR-NEXT: cir.call @_Z9flattened3Dbl(%[[FA]], %[[FB]])
+
+// LLVM-LABEL: define{{.*}} void @_Z14test_flattened3Dbl(
+// LLVM-DAG: %[[FA:.*]] = freeze double
+// LLVM-DAG: %[[FB:.*]] = freeze double
+// LLVM: call void @_Z9flattened3Dbl(double %[[FA]], double %[[FB]])
+
+// Like classic CodeGen, an indirect argument freezes the pointer.
+void test_byval(Big b) { byval(b); }
+
+// CIR-LABEL: cir.func{{.*}} @_Z10test_byval3Big(
+// CIR: %[[F:.*]] = cir.freeze %{{.*}} : !cir.ptr<!rec_Big>
+// CIR-NEXT: cir.call @_Z5byval3Big(%[[F]])
+
+// LLVM-LABEL: define{{.*}} void @_Z10test_byval3Big(
+// LLVM: %[[F:.*]] = freeze ptr %{{.*}}
+// LLVM-NEXT: call void @_Z5byval3Big(ptr noundef byval(%struct.Big) align 8 %[[F]])
+
+void test_bitint(_BitInt(129) x) { bitint(x); }
+
+// CIR-LABEL: cir.func{{.*}} @_Z11test_bitintDB129_(
+// CIR: %[[F:.*]] = cir.freeze %{{.*}} : !cir.ptr<
+// CIR-NEXT: cir.call @_Z6bitintDB129_(%[[F]])
+
+// LLVM-LABEL: define{{.*}} void @_Z11test_bitintDB129_(
+// LLVM: %[[F:.*]] = freeze ptr %{{.*}}
+// LLVM: call void @_Z6bitintDB129_(ptr noundef byval([24 x i8]) align 8 %[[F]])
+
+void test_indirect(NT &n) { indirect(n); }
+
+// CIR-LABEL: cir.func{{.*}} @_Z13test_indirectR2NT(
+// CIR: cir.call @_ZN2NTC1ERKS_(%[[TMP:[0-9]+]],
+// CIR-NEXT: %[[F:.*]] = cir.freeze %[[TMP]] : !cir.ptr<!rec_NT>
+// CIR-NEXT: cir.call @_Z8indirect2NT(%[[F]])
+
+// LLVM-LABEL: define{{.*}} void @_Z13test_indirectR2NT(
+// LLVM: call void @_ZN2NTC1ERKS_(ptr {{.*}} %[[TMP:[^,]+]],
+// LLVM-NEXT: %[[F:.*]] = freeze ptr %[[TMP]]
+// LLVM-NEXT: call void @_Z8indirect2NT(ptr {{.*}} %[[F]])
+
+void test_complex(_Complex float f, _Complex double d) {
+ cfloat(f);
+ cdouble(d);
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z12test_complexCfCd(
+// CIR: %[[V:.*]] = cir.load{{.*}} : !cir.ptr<!cir.vector<2 x !cir.float>>, !cir.vector<2 x !cir.float>
+// CIR-NEXT: %[[F:.*]] = cir.freeze %[[V]] : !cir.vector<2 x !cir.float>
+// CIR-NEXT: cir.call @_Z6cfloatCf(%[[F]])
+// CIR: %[[R:.*]] = cir.load{{.*}} : !cir.ptr<!cir.double>, !cir.double
+// CIR: %[[I:.*]] = cir.load{{.*}} : !cir.ptr<!cir.double>, !cir.double
+// CIR-NEXT: %[[FR:.*]] = cir.freeze %[[R]] : !cir.double
+// CIR-NEXT: %[[FI:.*]] = cir.freeze %[[I]] : !cir.double
+// CIR-NEXT: cir.call @_Z7cdoubleCd(%[[FR]], %[[FI]])
+
+// LLVM-LABEL: define{{.*}} void @_Z12test_complexCfCd(
+// LLVM: %[[V:.*]] = load <2 x float>
+// LLVM-NEXT: %[[F:.*]] = freeze <2 x float> %[[V]]
+// LLVM-NEXT: call void @_Z6cfloatCf(<2 x float> noundef %[[F]])
+// LLVM-DAG: %[[FR:.*]] = freeze double
+// LLVM-DAG: %[[FI:.*]] = freeze double
+// LLVM: call void @_Z7cdoubleCd(double noundef %[[FR]], double noundef %[[FI]])
diff --git a/clang/test/CIR/Transforms/abi-lowering/maybe-undef-freeze.cir b/clang/test/CIR/Transforms/abi-lowering/maybe-undef-freeze.cir
new file mode 100644
index 0000000000000..ba34dd48eb201
--- /dev/null
+++ b/clang/test/CIR/Transforms/abi-lowering/maybe-undef-freeze.cir
@@ -0,0 +1,111 @@
+// RUN: cir-opt %s -cir-call-conv-lowering="classification-attr=test_classify" \
+// RUN: | FileCheck %s
+
+!s32i = !cir.int<s, 32>
+!s64i = !cir.int<s, 64>
+!rec_Pair = !cir.struct<"Pair" {data !s32i, data !s32i}>
+
+#coerce_pair_to_i64 = {
+ return = { kind = "direct" },
+ args = [ { kind = "direct", coerced_type = !s64i } ]
+}
+
+#indirect_pair = {
+ return = { kind = "direct" },
+ args = [ { kind = "indirect", indirect_align = 4, byval = false } ]
+}
+
+#byval_pair = {
+ return = { kind = "direct" },
+ args = [ { kind = "indirect", indirect_align = 4, byval = true } ]
+}
+
+#caller_no_args = {
+ return = { kind = "direct" },
+ args = [ ]
+}
+
+module attributes {
+ dlti.dl_spec = #dlti.dl_spec<
+ #dlti.dl_entry<i32, dense<32>: vector<2xi64>>,
+ #dlti.dl_entry<i64, dense<64>: vector<2xi64>>>
+} {
+
+ cir.func private @coerced(!rec_Pair)
+ attributes { test_classify = #coerce_pair_to_i64 }
+ cir.func private @indirect(!rec_Pair)
+ attributes { test_classify = #indirect_pair }
+ cir.func private @byval(!rec_Pair)
+ attributes { test_classify = #byval_pair }
+
+ cir.func @freeze_coerced()
+ attributes { test_classify = #caller_no_args } {
+ %0 = cir.alloca "p" align(4) : !cir.ptr<!rec_Pair>
+ %1 = cir.load %0 : !cir.ptr<!rec_Pair>, !rec_Pair
+ %2 = cir.freeze %1 : !rec_Pair
+ cir.call @coerced(%2) : (!rec_Pair) -> ()
+ cir.return
+ }
+
+ // CHECK-LABEL: cir.func{{.*}} @freeze_coerced()
+ // CHECK: %[[P:.*]] = cir.alloca "p" {{.*}} : !cir.ptr<!rec_Pair>
+ // CHECK: %[[CAST:.*]] = cir.cast bitcast %[[P]] : !cir.ptr<!rec_Pair> -> !cir.ptr<!s64i>
+ // CHECK: %[[INT:.*]] = cir.load align(4) %[[CAST]] : !cir.ptr<!s64i>, !s64i
+ // CHECK-NEXT: %[[F:.*]] = cir.freeze %[[INT]] : !s64i
+ // CHECK-NEXT: cir.call @coerced(%[[F]]) : (!s64i) -> ()
+ // CHECK-NOT: cir.freeze
+
+ // The freeze no longer hides the load, so the call forwards its storage.
+ cir.func @freeze_indirect()
+ attributes { test_classify = #caller_no_args } {
+ %0 = cir.alloca "p" align(4) : !cir.ptr<!rec_Pair>
+ %1 = cir.load %0 : !cir.ptr<!rec_Pair>, !rec_Pair
+ %2 = cir.freeze %1 : !rec_Pair
+ cir.call @indirect(%2) : (!rec_Pair) -> ()
+ cir.return
+ }
+
+ // CHECK-LABEL: cir.func{{.*}} @freeze_indirect()
+ // CHECK: %[[P:.*]] = cir.alloca "p" {{.*}} : !cir.ptr<!rec_Pair>
+ // CHECK-NEXT: %[[F:.*]] = cir.freeze %[[P]] : !cir.ptr<!rec_Pair>
+ // CHECK-NEXT: cir.call @indirect(%[[F]]) : (!cir.ptr<!rec_Pair> {{.*}}) -> ()
+ // CHECK-NOT: cir.freeze
+
+ // A byval slot stored from a frozen non-load value keeps the value frozen.
+ cir.func @freeze_byval_value()
+ attributes { test_classify = #caller_no_args } {
+ %0 = cir.const #cir.poison : !rec_Pair
+ %1 = cir.freeze %0 : !rec_Pair
+ cir.call @byval(%1) : (!rec_Pair) -> ()
+ cir.return
+ }
+
+ // CHECK-LABEL: cir.func{{.*}} @freeze_byval_value()
+ // CHECK: %[[V:.*]] = cir.const #cir.poison : !rec_Pair
+ // CHECK-NEXT: %[[F:.*]] = cir.freeze %[[V]] : !rec_Pair
+ // CHECK: cir.store %[[F]], %[[SLOT:.*]] : !rec_Pair, !cir.ptr<!rec_Pair>
+ // CHECK-NEXT: cir.call @byval(%[[SLOT]])
+ // CHECK-NOT: cir.freeze
+
+ // A shared freeze stays put, since all its uses must see one value.
+ cir.func @freeze_shared()
+ attributes { test_classify = #caller_no_args } {
+ %0 = cir.alloca "p" align(4) : !cir.ptr<!rec_Pair>
+ %1 = cir.load %0 : !cir.ptr<!rec_Pair>, !rec_Pair
+ %2 = cir.freeze %1 : !rec_Pair
+ cir.call @coerced(%2) : (!rec_Pair) -> ()
+ cir.call @coerced(%2) : (!rec_Pair) -> ()
+ cir.return
+ }
+
+ // CHECK-LABEL: cir.func{{.*}} @freeze_shared()
+ // CHECK: %[[V:.*]] = cir.load {{.*}} : !cir.ptr<!rec_Pair>, !rec_Pair
+ // CHECK-NEXT: %[[F:.*]] = cir.freeze %[[V]] : !rec_Pair
+ // CHECK: cir.store {{.*}}%[[F]], %{{.*}} : !rec_Pair, !cir.ptr<!rec_Pair>
+ // CHECK: %[[A:.*]] = cir.load {{.*}} : !cir.ptr<!s64i>, !s64i
+ // CHECK-NEXT: cir.call @coerced(%[[A]]) : (!s64i) -> ()
+ // CHECK: cir.store {{.*}}%[[F]], %{{.*}} : !rec_Pair, !cir.ptr<!rec_Pair>
+ // CHECK: %[[B:.*]] = cir.load {{.*}} : !cir.ptr<!s64i>, !s64i
+ // CHECK-NEXT: cir.call @coerced(%[[B]]) : (!s64i) -> ()
+
+}
More information about the cfe-commits
mailing list