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 <[email protected]> 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 <[email protected]> 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) -> () + +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
