https://github.com/andykaylor created https://github.com/llvm/llvm-project/pull/175996
This change upstreams the code to generate CIR for bool casts and reinterpret casts involving member pointer types and the code to lower these casts for the Itanium C++ ABI. >From d53e6dfa53ef31579a2087610f3c2559e84ab49a Mon Sep 17 00:00:00 2001 From: Andy Kaylor <[email protected]> Date: Tue, 13 Jan 2026 16:15:28 -0800 Subject: [PATCH] [CIR] Upstream handling for member pointer bool and reinterpret casts This change upstreams the code to generate CIR for bool casts and reinterpret casts involving member pointer types and the code to lower these casts for the Itanium C++ ABI. --- clang/include/clang/CIR/Dialect/IR/CIROps.td | 1 + clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp | 5 ++ clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 11 ++- .../CIR/Dialect/Transforms/CXXABILowering.cpp | 42 ++++++++++- .../Transforms/TargetLowering/CIRCXXABI.h | 18 +++++ .../TargetLowering/LowerItaniumCXXABI.cpp | 61 ++++++++++++++++ .../CodeGen/pointer-to-data-member-cast.cpp | 55 ++++++++++++++ .../CodeGen/pointer-to-member-func-cast.cpp | 71 +++++++++++++++++++ clang/test/CIR/IR/invalid-cast.cir | 27 +++++++ 9 files changed, 287 insertions(+), 4 deletions(-) create mode 100644 clang/test/CIR/CodeGen/pointer-to-member-func-cast.cpp create mode 100644 clang/test/CIR/IR/invalid-cast.cir diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index ceb9899a00ac4..8352f99380845 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -241,6 +241,7 @@ def CIR_CastOp : CIR_Op<"cast", [ // The input and output types should match the cast kind. let hasVerifier = 1; let hasFolder = 1; + let hasCXXABILowering = 1; let extraLLVMLoweringPatternDecl = [{ mlir::Type convertTy(mlir::Type ty) const; diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp index 2a4e6d9b89ee4..8a3af1ffcdbdf 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp @@ -2243,6 +2243,11 @@ mlir::Value ScalarExprEmitter::VisitCastExpr(CastExpr *ce) { return builder.getNullDataMemberPtr(ty, cgf.getLoc(subExpr->getExprLoc())); } + case CK_ReinterpretMemberPointer: { + mlir::Value src = Visit(subExpr); + return builder.createBitcast(cgf.getLoc(subExpr->getExprLoc()), src, + cgf.convertType(destTy)); + } case CK_BaseToDerivedMemberPointer: case CK_DerivedToBaseMemberPointer: { mlir::Value src = Visit(subExpr); diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index 6c4607abb40e7..1be7fe06d8be3 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -657,9 +657,16 @@ LogicalResult cir::CastOp::verify() { << "requires floating point !cir.complex type for result"; return success(); } - default: - llvm_unreachable("Unknown CastOp kind?"); + case cir::CastKind::member_ptr_to_bool: { + if (!mlir::isa<cir::DataMemberType, cir::MethodType>(srcType)) + return emitOpError() + << "requires !cir.data_member or !cir.method type for source"; + if (!mlir::isa<cir::BoolType>(resType)) + return emitOpError() << "requires !cir.bool type for result"; + return success(); + } } + llvm_unreachable("Unknown CastOp kind?"); } static bool isIntOrBoolCast(cir::CastOp op) { diff --git a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp index 145f8574893f4..2a928a556b762 100644 --- a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp +++ b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp @@ -57,8 +57,8 @@ class CIRGenericCXXABILoweringPattern : public mlir::ConversionPattern { matchAndRewrite(mlir::Operation *op, llvm::ArrayRef<mlir::Value> operands, mlir::ConversionPatternRewriter &rewriter) const override { // Do not match on operations that have dedicated ABI lowering rewrite rules - if (llvm::isa<cir::AllocaOp, cir::BaseDataMemberOp, cir::ConstantOp, - cir::CmpOp, cir::DerivedDataMemberOp, cir::FuncOp, + if (llvm::isa<cir::AllocaOp, cir::BaseDataMemberOp, cir::CastOp, cir::CmpOp, + cir::ConstantOp, cir::DerivedDataMemberOp, cir::FuncOp, cir::GetRuntimeMemberOp, cir::GlobalOp>(op)) return mlir::failure(); @@ -130,6 +130,44 @@ mlir::LogicalResult CIRAllocaOpABILowering::matchAndRewrite( return mlir::success(); } +mlir::LogicalResult CIRCastOpABILowering::matchAndRewrite( + cir::CastOp op, OpAdaptor adaptor, + mlir::ConversionPatternRewriter &rewriter) const { + mlir::Type srcTy = op.getSrc().getType(); + assert((mlir::isa<cir::DataMemberType, cir::MethodType>(srcTy)) && + "input to bitcast in ABI lowering must be a data member or method"); + + switch (op.getKind()) { + case cir::CastKind::bitcast: { + mlir::Type destTy = getTypeConverter()->convertType(op.getType()); + mlir::Value loweredResult; + if (mlir::isa<cir::DataMemberType>(srcTy)) + loweredResult = lowerModule->getCXXABI().lowerDataMemberBitcast( + op, destTy, adaptor.getSrc(), rewriter); + else + loweredResult = lowerModule->getCXXABI().lowerMethodBitcast( + op, destTy, adaptor.getSrc(), rewriter); + rewriter.replaceOp(op, loweredResult); + return mlir::success(); + } + case cir::CastKind::member_ptr_to_bool: { + mlir::Value loweredResult; + if (mlir::isa<cir::MethodType>(srcTy)) + loweredResult = lowerModule->getCXXABI().lowerMethodToBoolCast( + op, adaptor.getSrc(), rewriter); + else + loweredResult = lowerModule->getCXXABI().lowerDataMemberToBoolCast( + op, adaptor.getSrc(), rewriter); + rewriter.replaceOp(op, loweredResult); + return mlir::success(); + } + default: + break; + } + + return mlir::failure(); +} + mlir::LogicalResult CIRConstantOpABILowering::matchAndRewrite( cir::ConstantOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h index 0dedfa7221f5f..a7ff9f848fb7a 100644 --- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h +++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRCXXABI.h @@ -81,6 +81,24 @@ class CIRCXXABI { virtual mlir::Value lowerDataMemberCmp(cir::CmpOp op, mlir::Value loweredLhs, mlir::Value loweredRhs, mlir::OpBuilder &builder) const = 0; + + virtual mlir::Value + lowerDataMemberBitcast(cir::CastOp op, mlir::Type loweredDstTy, + mlir::Value loweredSrc, + mlir::OpBuilder &builder) const = 0; + + virtual mlir::Value + lowerDataMemberToBoolCast(cir::CastOp op, mlir::Value loweredSrc, + mlir::OpBuilder &builder) const = 0; + + virtual mlir::Value lowerMethodBitcast(cir::CastOp op, + mlir::Type loweredDstTy, + mlir::Value loweredSrc, + mlir::OpBuilder &builder) const = 0; + + virtual mlir::Value lowerMethodToBoolCast(cir::CastOp op, + mlir::Value loweredSrc, + mlir::OpBuilder &builder) const = 0; }; /// Creates an Itanium-family ABI. diff --git a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp index d944fa3294684..82f17340264c4 100644 --- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp +++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp @@ -67,6 +67,21 @@ class LowerItaniumCXXABI : public CIRCXXABI { mlir::Value lowerDataMemberCmp(cir::CmpOp op, mlir::Value loweredLhs, mlir::Value loweredRhs, mlir::OpBuilder &builder) const override; + + mlir::Value lowerDataMemberBitcast(cir::CastOp op, mlir::Type loweredDstTy, + mlir::Value loweredSrc, + mlir::OpBuilder &builder) const override; + + mlir::Value + lowerDataMemberToBoolCast(cir::CastOp op, mlir::Value loweredSrc, + mlir::OpBuilder &builder) const override; + + mlir::Value lowerMethodBitcast(cir::CastOp op, mlir::Type loweredDstTy, + mlir::Value loweredSrc, + mlir::OpBuilder &builder) const override; + + mlir::Value lowerMethodToBoolCast(cir::CastOp op, mlir::Value loweredSrc, + mlir::OpBuilder &builder) const override; }; } // namespace @@ -249,4 +264,50 @@ LowerItaniumCXXABI::lowerDataMemberCmp(cir::CmpOp op, mlir::Value loweredLhs, loweredRhs); } +mlir::Value LowerItaniumCXXABI::lowerDataMemberBitcast( + cir::CastOp op, mlir::Type loweredDstTy, mlir::Value loweredSrc, + mlir::OpBuilder &builder) const { + if (loweredSrc.getType() == loweredDstTy) + return loweredSrc; + + return cir::CastOp::create(builder, op.getLoc(), loweredDstTy, + cir::CastKind::bitcast, loweredSrc); +} + +mlir::Value LowerItaniumCXXABI::lowerDataMemberToBoolCast( + cir::CastOp op, mlir::Value loweredSrc, mlir::OpBuilder &builder) const { + // Itanium C++ ABI 2.3: + // A NULL pointer is represented as -1. + auto nullAttr = cir::IntAttr::get(getPtrDiffCIRTy(lm), -1); + auto nullValue = cir::ConstantOp::create(builder, op.getLoc(), nullAttr); + return cir::CmpOp::create(builder, op.getLoc(), cir::CmpOpKind::ne, + loweredSrc, nullValue); +} + +mlir::Value +LowerItaniumCXXABI::lowerMethodBitcast(cir::CastOp op, mlir::Type loweredDstTy, + mlir::Value loweredSrc, + mlir::OpBuilder &builder) const { + if (loweredSrc.getType() == loweredDstTy) + return loweredSrc; + + return loweredSrc; +} + +mlir::Value LowerItaniumCXXABI::lowerMethodToBoolCast( + cir::CastOp op, mlir::Value loweredSrc, mlir::OpBuilder &builder) const { + // Itanium C++ ABI 2.3.2: + // + // In the standard representation, a null member function pointer is + // represented with ptr set to a null pointer. The value of adj is + // unspecified for null member function pointers. + cir::IntType ptrdiffCIRTy = getPtrDiffCIRTy(lm); + mlir::Value ptrdiffZero = cir::ConstantOp::create( + builder, op.getLoc(), cir::IntAttr::get(ptrdiffCIRTy, 0)); + mlir::Value ptrField = cir::ExtractMemberOp::create( + builder, op.getLoc(), ptrdiffCIRTy, loweredSrc, 0); + return cir::CmpOp::create(builder, op.getLoc(), cir::CmpOpKind::ne, ptrField, + ptrdiffZero); +} + } // namespace cir diff --git a/clang/test/CIR/CodeGen/pointer-to-data-member-cast.cpp b/clang/test/CIR/CodeGen/pointer-to-data-member-cast.cpp index 91cf4b1ae5386..d3aca69c3914c 100644 --- a/clang/test/CIR/CodeGen/pointer-to-data-member-cast.cpp +++ b/clang/test/CIR/CodeGen/pointer-to-data-member-cast.cpp @@ -133,3 +133,58 @@ auto derived_to_base_zero_offset(int Derived::*ptr) -> int Base1::* { // OGCG-NEXT: store i64 %{{.*}}, ptr %[[PTR_ADDR]] // OGCG-NEXT: %[[RET:.*]] = load i64, ptr %[[PTR_ADDR]] // OGCG-NEXT: ret i64 %[[RET]] + +struct Foo { + int a; +}; + +struct Bar { + int a; +}; + +bool to_bool(int Foo::*x) { + return x; +} + +// CIR-BEFORE: cir.func {{.*}} @_Z7to_boolM3Fooi +// CIR-BEFORE: %[[X:.*]] = cir.load{{.*}} %{{.*}} : !cir.ptr<!cir.data_member<!s32i in !rec_Foo>>, !cir.data_member<!s32i in !rec_Foo> +// CIR-BEFORE: %{{.*}} = cir.cast member_ptr_to_bool %[[X]] : !cir.data_member<!s32i in !rec_Foo> -> !cir.bool + +// CIR-AFTER: cir.func {{.*}} @_Z7to_boolM3Fooi +// CIR-AFTER: %[[NULL_VAL:.*]] = cir.const #cir.int<-1> : !s64i +// CIR-AFTER: %[[BOOL_VAL:.*]] = cir.cmp(ne, %{{.*}}, %[[NULL_VAL]]) : !s64i, !cir.bool + +// LLVM: define {{.*}} i1 @_Z7to_boolM3Fooi +// LLVM: %[[X:.*]] = load i64, ptr %{{.*}} +// LLVM: %[[IS_NULL:.*]] = icmp ne i64 %[[X]], -1 + +// OGCG: define {{.*}} i1 @_Z7to_boolM3Fooi +// OGCG: %[[X:.*]] = load i64, ptr %{{.*}} +// OGCG: %[[IS_NULL:.*]] = icmp ne i64 %[[X]], -1 + +auto bitcast(int Foo::*x) { + return reinterpret_cast<int Bar::*>(x); +} + +// CIR-BEFORE: cir.func {{.*}} @_Z7bitcastM3Fooi +// CIR-BEFORE: %[[X:.*]] = cir.load{{.*}} %{{.*}} : !cir.ptr<!cir.data_member<!s32i in !rec_Foo>>, !cir.data_member<!s32i in !rec_Foo> +// CIR-BEFORE: %{{.*}} = cir.cast bitcast %[[X]] : !cir.data_member<!s32i in !rec_Foo> -> !cir.data_member<!s32i in !rec_Bar> + +// CIR-AFTER: cir.func {{.*}} @_Z7bitcastM3Fooi(%[[ARG0:.*]]: !s64i +// CIR-AFTER: %[[X_ADDR:.*]] = cir.alloca !s64i, !cir.ptr<!s64i>, ["x", init] {alignment = 8 : i64} +// CIR-AFTER: %[[RET_ADDR:.*]] = cir.alloca !s64i, !cir.ptr<!s64i>, ["__retval"] {alignment = 8 : i64} +// CIR-AFTER: cir.store %[[ARG0]], %[[X_ADDR]] +// CIR-AFTER: %[[X:.*]] = cir.load{{.*}} %[[X_ADDR]] +// CIR-AFTER: cir.store %[[X]], %[[RET_ADDR]] +// CIR-AFTER: %[[RET:.*]] = cir.load %[[RET_ADDR]] +// CIR-AFTER: cir.return %[[RET]] : !s64i + +// LLVM: define {{.*}} i64 @_Z7bitcastM3Fooi +// LLVM: %[[X:.*]] = load i64, ptr %{{.*}} +// LLVM: store i64 %[[X]], ptr %[[RET_ADDR:.*]] +// LLVM: %[[RET:.*]] = load i64, ptr %[[RET_ADDR:.*]] +// LLVM: ret i64 %[[RET]] + +// OGCG: define {{.*}} i64 @_Z7bitcastM3Fooi +// OGCG: %[[X:.*]] = load i64, ptr %{{.*}} +// OGCG: ret i64 %[[X]] diff --git a/clang/test/CIR/CodeGen/pointer-to-member-func-cast.cpp b/clang/test/CIR/CodeGen/pointer-to-member-func-cast.cpp new file mode 100644 index 0000000000000..4f18a6a6a9540 --- /dev/null +++ b/clang/test/CIR/CodeGen/pointer-to-member-func-cast.cpp @@ -0,0 +1,71 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 -fclangir -emit-cir -mmlir -mlir-print-ir-before=cir-cxxabi-lowering %s -o %t.cir 2> %t-before.cir +// RUN: FileCheck --check-prefix=CIR-BEFORE --input-file=%t-before.cir %s +// RUN: FileCheck --check-prefix=CIR-AFTER --input-file=%t.cir %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --input-file=%t-cir.ll --check-prefix=LLVM %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++17 -emit-llvm %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll --check-prefix=OGCG %s + +struct Foo { + void m1(int); + virtual void m2(int); + virtual void m3(int); +}; + +struct Bar { + void m4(); +}; + +bool memfunc_to_bool(void (Foo::*func)(int)) { + return func; +} + +// CIR-BEFORE: cir.func {{.*}} @_Z15memfunc_to_boolM3FooFviE +// CIR-BEFORE: %{{.*}} = cir.cast member_ptr_to_bool %{{.*}} : !cir.method<!cir.func<(!s32i)> in !rec_Foo> -> !cir.bool + +// CIR-AFTER: cir.func {{.*}} @_Z15memfunc_to_boolM3FooFviE +// CIR-AFTER: %[[FUNC:.*]] = cir.load{{.*}} %{{.*}} : !cir.ptr<!rec_anon_struct>, !rec_anon_struct +// CIR-AFTER: %[[NULL_VAL:.*]] = cir.const #cir.int<0> : !s64i +// CIR-AFTER: %[[FUNC_PTR:.*]] = cir.extract_member %[[FUNC]][0] : !rec_anon_struct -> !s64i +// CIR-AFTER: %[[BOOL_VAL:.*]] = cir.cmp(ne, %[[FUNC_PTR]], %[[NULL_VAL]]) : !s64i, !cir.bool + +// LLVM: define {{.*}} i1 @_Z15memfunc_to_boolM3FooFviE +// LLVM: %[[FUNC:.*]] = load { i64, i64 }, ptr %{{.*}} +// LLVM: %[[FUNC_PTR:.*]] = extractvalue { i64, i64 } %[[FUNC]], 0 +// LLVM: %{{.*}} = icmp ne i64 %[[FUNC_PTR]], 0 + +// Note: OGCG uses an extra temporary for the function argument because it +// composes it from coerced arguments. We'll do that in CIR too after +// calling convention lowering is implemented. + +// OGCG: define {{.*}} i1 @_Z15memfunc_to_boolM3FooFviE +// OGCG: %[[FUNC_TMP:.*]] = load { i64, i64 }, ptr %{{.*}} +// OGCG: store { i64, i64 } %[[FUNC_TMP]], ptr %[[FUNC_ADDR:.*]] +// OGCG: %[[FUNC:.*]] = load { i64, i64 }, ptr %[[FUNC_ADDR]] +// OGCG: %[[FUNC_PTR:.*]] = extractvalue { i64, i64 } %[[FUNC]], 0 +// OGCG: %{{.*}} = icmp ne i64 %[[FUNC_PTR]], 0 + +auto memfunc_reinterpret(void (Foo::*func)(int)) -> void (Bar::*)() { + return reinterpret_cast<void (Bar::*)()>(func); +} + +// CIR-BEFORE: cir.func {{.*}} @_Z19memfunc_reinterpretM3FooFviE +// CIR-BEFORE: %{{.*}} = cir.cast bitcast %{{.*}} : !cir.method<!cir.func<(!s32i)> in !rec_Foo> -> !cir.method<!cir.func<()> in !rec_Bar> + +// CIR-AFTER: cir.func {{.*}} @_Z19memfunc_reinterpretM3FooFviE +// CIR-AFTER: %[[FUNC:.*]] = cir.load{{.*}} %{{.*}} : !cir.ptr<!rec_anon_struct>, !rec_anon_struct +// CIR-AFTER: cir.store %[[FUNC]], %[[RET_ADDR:.*]] : !rec_anon_struct, !cir.ptr<!rec_anon_struct> +// CIR-AFTER: %[[RET:.*]] = cir.load{{.*}} %[[RET_ADDR]] : !cir.ptr<!rec_anon_struct>, !rec_anon_struct +// CIR-AFTER: cir.return %[[RET]] : !rec_anon_struct + +// LLVM: define {{.*}} { i64, i64 } @_Z19memfunc_reinterpretM3FooFviE +// LLVM: %[[FUNC:.*]] = load { i64, i64 }, ptr %{{.*}} +// LLVM: store { i64, i64 } %[[FUNC]], ptr %[[RET_ADDR:.*]] +// LLVM: %[[RET:.*]] = load { i64, i64 }, ptr %[[RET_ADDR]] +// LLVM: ret { i64, i64 } %[[RET]] + +// OGCG: define {{.*}} { i64, i64 } @_Z19memfunc_reinterpretM3FooFviE +// OGCG: %[[FUNC:.*]] = load { i64, i64 }, ptr %{{.*}} +// OGCG: store { i64, i64 } %[[FUNC]], ptr %[[RET_ADDR:.*]] +// OGCG: %[[RET:.*]] = load { i64, i64 }, ptr %[[RET_ADDR]] +// OGCG: ret { i64, i64 } %[[RET]] diff --git a/clang/test/CIR/IR/invalid-cast.cir b/clang/test/CIR/IR/invalid-cast.cir new file mode 100644 index 0000000000000..321934215e84d --- /dev/null +++ b/clang/test/CIR/IR/invalid-cast.cir @@ -0,0 +1,27 @@ +// RUN: cir-opt %s -verify-diagnostics -split-input-file + +// ----- + +!s32i = !cir.int<s, 32> +!rec_Foo = !cir.record<struct "Foo" {!s32i}> + +module { + cir.func no_inline dso_local @_Z7to_boolM3Fooi(%arg0: !cir.data_member<!s32i in !rec_Foo>) -> !s32i { + // expected-error@+1 {{requires !cir.bool type for result}} + %0 = cir.cast member_ptr_to_bool %arg0 : !cir.data_member<!s32i in !rec_Foo> -> !s32i + cir.return %0 : !s32i + } +} + +// ----- + +!s32i = !cir.int<s, 32> +!rec_Foo = !cir.record<struct "Foo" {!s32i}> + +module { + cir.func no_inline dso_local @_Z7to_boolM3Fooi(%arg0: !s32i) -> !cir.bool { + // expected-error@+1 {{requires !cir.data_member or !cir.method type for source}} + %0 = cir.cast member_ptr_to_bool %arg0 : !s32i -> !cir.bool + cir.return %0 : !cir.bool + } +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
