[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