Author: Andy Kaylor Date: 2025-05-20T10:52:15-07:00 New Revision: cbcfe667bbda2ba2862d873350309e29b4368880
URL: https://github.com/llvm/llvm-project/commit/cbcfe667bbda2ba2862d873350309e29b4368880 DIFF: https://github.com/llvm/llvm-project/commit/cbcfe667bbda2ba2862d873350309e29b4368880.diff LOG: [CIR] Upstream support for iterator-based range for loops (#140636) This change adds handling for C++ member operator calls, implicit no-op casts, and l-value call expressions. Together, these changes enable handling of range for loops based on iterators. Added: Modified: clang/lib/CIR/CodeGen/CIRGenCXXExpr.cpp clang/lib/CIR/CodeGen/CIRGenExpr.cpp clang/lib/CIR/CodeGen/CIRGenFunction.cpp clang/lib/CIR/CodeGen/CIRGenFunction.h clang/test/CIR/CodeGen/forrange.cpp Removed: ################################################################################ diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenCXXExpr.cpp index 906c212f0fa8a..33865728e4cdc 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXXExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCXXExpr.cpp @@ -98,9 +98,11 @@ RValue CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr( CallArgList rtlArgStorage; CallArgList *rtlArgs = nullptr; if (auto *oce = dyn_cast<CXXOperatorCallExpr>(ce)) { - cgm.errorNYI(oce->getSourceRange(), - "emitCXXMemberOrOperatorMemberCallExpr: operator call"); - return RValue::get(nullptr); + if (oce->isAssignmentOp()) { + cgm.errorNYI( + oce->getSourceRange(), + "emitCXXMemberOrOperatorMemberCallExpr: assignment operator"); + } } LValue thisPtr; @@ -169,6 +171,17 @@ RValue CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr( /*ImplicitParam=*/nullptr, QualType(), ce, rtlArgs); } +RValue +CIRGenFunction::emitCXXOperatorMemberCallExpr(const CXXOperatorCallExpr *e, + const CXXMethodDecl *md, + ReturnValueSlot returnValue) { + assert(md->isInstance() && + "Trying to emit a member call expr on a static method!"); + return emitCXXMemberOrOperatorMemberCallExpr( + e, md, returnValue, /*HasQualifier=*/false, /*Qualifier=*/nullptr, + /*IsArrow=*/false, e->getArg(0)); +} + RValue CIRGenFunction::emitCXXMemberOrOperatorCall( const CXXMethodDecl *md, const CIRGenCallee &callee, ReturnValueSlot returnValue, mlir::Value thisPtr, mlir::Value implicitParam, diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp index 3b0ade2c52d5b..c5fe3c1378624 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -743,6 +743,122 @@ CIRGenFunction::emitArraySubscriptExpr(const clang::ArraySubscriptExpr *e) { return lv; } +/// Casts are never lvalues unless that cast is to a reference type. If the cast +/// is to a reference, we can have the usual lvalue result, otherwise if a cast +/// is needed by the code generator in an lvalue context, then it must mean that +/// we need the address of an aggregate in order to access one of its members. +/// This can happen for all the reasons that casts are permitted with aggregate +/// result, including noop aggregate casts, and cast from scalar to union. +LValue CIRGenFunction::emitCastLValue(const CastExpr *e) { + switch (e->getCastKind()) { + case CK_ToVoid: + case CK_BitCast: + case CK_LValueToRValueBitCast: + case CK_ArrayToPointerDecay: + case CK_FunctionToPointerDecay: + case CK_NullToMemberPointer: + case CK_NullToPointer: + case CK_IntegralToPointer: + case CK_PointerToIntegral: + case CK_PointerToBoolean: + case CK_IntegralCast: + case CK_BooleanToSignedIntegral: + case CK_IntegralToBoolean: + case CK_IntegralToFloating: + case CK_FloatingToIntegral: + case CK_FloatingToBoolean: + case CK_FloatingCast: + case CK_FloatingRealToComplex: + case CK_FloatingComplexToReal: + case CK_FloatingComplexToBoolean: + case CK_FloatingComplexCast: + case CK_FloatingComplexToIntegralComplex: + case CK_IntegralRealToComplex: + case CK_IntegralComplexToReal: + case CK_IntegralComplexToBoolean: + case CK_IntegralComplexCast: + case CK_IntegralComplexToFloatingComplex: + case CK_DerivedToBaseMemberPointer: + case CK_BaseToDerivedMemberPointer: + case CK_MemberPointerToBoolean: + case CK_ReinterpretMemberPointer: + case CK_AnyPointerToBlockPointerCast: + case CK_ARCProduceObject: + case CK_ARCConsumeObject: + case CK_ARCReclaimReturnedObject: + case CK_ARCExtendBlockObject: + case CK_CopyAndAutoreleaseBlockObject: + case CK_IntToOCLSampler: + case CK_FloatingToFixedPoint: + case CK_FixedPointToFloating: + case CK_FixedPointCast: + case CK_FixedPointToBoolean: + case CK_FixedPointToIntegral: + case CK_IntegralToFixedPoint: + case CK_MatrixCast: + case CK_HLSLVectorTruncation: + case CK_HLSLArrayRValue: + case CK_HLSLElementwiseCast: + case CK_HLSLAggregateSplatCast: + llvm_unreachable("unexpected cast lvalue"); + + case CK_Dependent: + llvm_unreachable("dependent cast kind in IR gen!"); + + case CK_BuiltinFnToFnPtr: + llvm_unreachable("builtin functions are handled elsewhere"); + + // These are never l-values; just use the aggregate emission code. + case CK_NonAtomicToAtomic: + case CK_AtomicToNonAtomic: + case CK_Dynamic: + case CK_UncheckedDerivedToBase: + case CK_DerivedToBase: + case CK_ToUnion: + case CK_BaseToDerived: + case CK_LValueBitCast: + case CK_AddressSpaceConversion: + case CK_ObjCObjectLValueCast: + case CK_VectorSplat: + case CK_ConstructorConversion: + case CK_UserDefinedConversion: + case CK_CPointerToObjCPointerCast: + case CK_BlockPointerToObjCPointerCast: + case CK_LValueToRValue: { + cgm.errorNYI(e->getSourceRange(), + std::string("emitCastLValue for unhandled cast kind: ") + + e->getCastKindName()); + + return {}; + } + + case CK_NoOp: { + // CK_NoOp can model a qualification conversion, which can remove an array + // bound and change the IR type. + LValue lv = emitLValue(e->getSubExpr()); + // Propagate the volatile qualifier to LValue, if exists in e. + if (e->changesVolatileQualification()) + cgm.errorNYI(e->getSourceRange(), + "emitCastLValue: NoOp changes volatile qual"); + if (lv.isSimple()) { + Address v = lv.getAddress(); + if (v.isValid()) { + mlir::Type ty = convertTypeForMem(e->getType()); + if (v.getElementType() != ty) + cgm.errorNYI(e->getSourceRange(), + "emitCastLValue: NoOp needs bitcast"); + } + } + return lv; + } + + case CK_ZeroToOCLOpaqueType: + llvm_unreachable("NULL to OpenCL opaque type lvalue cast is not valid"); + } + + llvm_unreachable("Invalid cast kind"); +} + LValue CIRGenFunction::emitMemberExpr(const MemberExpr *e) { if (isa<VarDecl>(e->getMemberDecl())) { cgm.errorNYI(e->getSourceRange(), "emitMemberExpr: VarDecl"); @@ -785,6 +901,21 @@ LValue CIRGenFunction::emitMemberExpr(const MemberExpr *e) { llvm_unreachable("Unhandled member declaration!"); } +LValue CIRGenFunction::emitCallExprLValue(const CallExpr *e) { + RValue rv = emitCallExpr(e); + + if (!rv.isScalar()) { + cgm.errorNYI(e->getSourceRange(), "emitCallExprLValue: non-scalar return"); + return {}; + } + + assert(e->getCallReturnType(getContext())->isReferenceType() && + "Can't have a scalar return unless the return type is a " + "reference type!"); + + return makeNaturalAlignPointeeAddrLValue(rv.getScalarVal(), e->getType()); +} + LValue CIRGenFunction::emitBinaryOperatorLValue(const BinaryOperator *e) { // Comma expressions just emit their LHS then their RHS as an l-value. if (e->getOpcode() == BO_Comma) { @@ -983,10 +1114,14 @@ RValue CIRGenFunction::emitCallExpr(const clang::CallExpr *e, } if (const auto *operatorCall = dyn_cast<CXXOperatorCallExpr>(e)) { - if (isa_and_nonnull<CXXMethodDecl>(operatorCall->getCalleeDecl())) { - cgm.errorNYI(e->getSourceRange(), "call to member operator"); - return RValue::get(nullptr); - } + // If the callee decl is a CXXMethodDecl, we need to emit this as a C++ + // operator member call. + if (const CXXMethodDecl *md = + dyn_cast_or_null<CXXMethodDecl>(operatorCall->getCalleeDecl())) + return emitCXXOperatorMemberCallExpr(operatorCall, md, returnValue); + // A CXXOperatorCallExpr is created even for explicit object methods, but + // these should be treated like static function calls. Fall through to do + // that. } CIRGenCallee callee = emitCallee(e->getCallee()); diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 6bfad71f241dc..c3798de79d969 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -476,6 +476,18 @@ cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl gd, cir::FuncOp fn, return fn; } +/// Given a value of type T* that may not be to a complete object, construct +/// an l-vlaue withi the natural pointee alignment of T. +LValue CIRGenFunction::makeNaturalAlignPointeeAddrLValue(mlir::Value val, + QualType ty) { + // FIXME(cir): is it safe to assume Op->getResult(0) is valid? Perhaps + // assert on the result type first. + LValueBaseInfo baseInfo; + assert(!cir::MissingFeatures::opTBAA()); + CharUnits align = cgm.getNaturalTypeAlignment(ty, &baseInfo); + return makeAddrLValue(Address(val, align), ty, baseInfo); +} + clang::QualType CIRGenFunction::buildFunctionArgList(clang::GlobalDecl gd, FunctionArgList &args) { const auto *fd = cast<FunctionDecl>(gd.getDecl()); @@ -536,10 +548,20 @@ LValue CIRGenFunction::emitLValue(const Expr *e) { "CompoundAssignOperator with ComplexType"); return LValue(); } + case Expr::CallExprClass: + case Expr::CXXMemberCallExprClass: + case Expr::CXXOperatorCallExprClass: + case Expr::UserDefinedLiteralClass: + return emitCallExprLValue(cast<CallExpr>(e)); case Expr::ParenExprClass: return emitLValue(cast<ParenExpr>(e)->getSubExpr()); case Expr::DeclRefExprClass: return emitDeclRefLValue(cast<DeclRefExpr>(e)); + case Expr::CStyleCastExprClass: + case Expr::CXXStaticCastExprClass: + case Expr::CXXDynamicCastExprClass: + case Expr::ImplicitCastExprClass: + return emitCastLValue(cast<CastExpr>(e)); } } diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 369bae02f0a75..ce080f481da6b 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -282,6 +282,8 @@ class CIRGenFunction : public CIRGenTypeCache { // TODO: Add symbol table support } + LValue makeNaturalAlignPointeeAddrLValue(mlir::Value v, clang::QualType t); + /// Construct an address with the natural alignment of T. If a pointer to T /// is expected to be signed, the pointer passed to this function must have /// been signed, and the returned Address will have the pointer authentication @@ -515,6 +517,7 @@ class CIRGenFunction : public CIRGenTypeCache { AbstractCallee callee = AbstractCallee(), unsigned paramsToSkip = 0); RValue emitCallExpr(const clang::CallExpr *e, ReturnValueSlot returnValue = ReturnValueSlot()); + LValue emitCallExprLValue(const clang::CallExpr *e); CIRGenCallee emitCallee(const clang::Expr *e); template <typename T> @@ -527,6 +530,8 @@ class CIRGenFunction : public CIRGenTypeCache { mlir::Type condType, bool buildingTopLevelCase); + LValue emitCastLValue(const CastExpr *e); + LValue emitCompoundAssignmentLValue(const clang::CompoundAssignOperator *e); mlir::LogicalResult emitContinueStmt(const clang::ContinueStmt &s); @@ -549,6 +554,10 @@ class CIRGenFunction : public CIRGenTypeCache { clang::NestedNameSpecifier *qualifier, bool isArrow, const clang::Expr *base); + RValue emitCXXOperatorMemberCallExpr(const CXXOperatorCallExpr *e, + const CXXMethodDecl *md, + ReturnValueSlot returnValue); + mlir::LogicalResult emitDoStmt(const clang::DoStmt &s); /// Emit an expression as an initializer for an object (variable, field, etc.) diff --git a/clang/test/CIR/CodeGen/forrange.cpp b/clang/test/CIR/CodeGen/forrange.cpp index 80b936318334c..8a3570a35a325 100644 --- a/clang/test/CIR/CodeGen/forrange.cpp +++ b/clang/test/CIR/CodeGen/forrange.cpp @@ -47,3 +47,87 @@ void for_range() { // CIR: cir.yield // CIR: } // CIR: } + +struct C2 { + Element *begin(); + Element *end(); +}; + +void for_range2() { + C2 c; + for (Element &e : c) + ; +} + +// CIR: cir.func @_Z10for_range2v() +// CIR: %[[C_ADDR:.*]] = cir.alloca !rec_C2{{.*}} ["c"] +// CIR: cir.scope { +// CIR: %[[RANGE_ADDR:.*]] = cir.alloca !cir.ptr<!rec_C2>{{.*}} ["__range1", init, const] +// CIR: %[[BEGIN_ADDR:.*]] = cir.alloca !cir.ptr<!rec_Element>{{.*}} ["__begin1", init] +// CIR: %[[END_ADDR:.*]] = cir.alloca !cir.ptr<!rec_Element>{{.*}} ["__end1", init] +// CIR: %[[E_ADDR:.*]] = cir.alloca !cir.ptr<!rec_Element>{{.*}} ["e", init, const] +// CIR: cir.store %[[C_ADDR]], %[[RANGE_ADDR]] +// CIR: %[[C_REF:.*]] = cir.load %[[RANGE_ADDR]] +// CIR: %[[BEGIN:.*]] = cir.call @_ZN2C25beginEv(%[[C_REF]]) +// CIR: cir.store %[[BEGIN]], %[[BEGIN_ADDR]] +// CIR: %[[C_REF2:.*]] = cir.load %[[RANGE_ADDR]] +// CIR: %[[END:.*]] = cir.call @_ZN2C23endEv(%[[C_REF2]]) +// CIR: cir.store %[[END]], %[[END_ADDR]] +// CIR: cir.for : cond { +// CIR: %[[BEGIN:.*]] = cir.load %[[BEGIN_ADDR]] +// CIR: %[[END:.*]] = cir.load %[[END_ADDR]] +// CIR: %[[CMP:.*]] = cir.cmp(ne, %[[BEGIN]], %[[END]]) +// CIR: cir.condition(%[[CMP]]) +// CIR: } body { +// CIR: %[[E:.*]] = cir.load deref %[[BEGIN_ADDR]] +// CIR: cir.store %[[E]], %[[E_ADDR]] +// CIR: cir.yield +// CIR: } step { +// CIR: %[[BEGIN:.*]] = cir.load %[[BEGIN_ADDR]] +// CIR: %[[STEP:.*]] = cir.const #cir.int<1> +// CIR: %[[NEXT:.*]] = cir.ptr_stride(%[[BEGIN]] {{.*}}, %[[STEP]] {{.*}}) +// CIR: cir.store %[[NEXT]], %[[BEGIN_ADDR]] +// CIR: cir.yield +// CIR: } +// CIR: } + +// Iterator class definition +class Iterator { +public: + Element& operator*(); + Iterator& operator++(); + bool operator!=(const Iterator& other) const; +}; + +class C3 { +public: + Iterator begin(); + Iterator end(); +}; + +void for_range3() { + C3 c; + for (Element& e : c) + ; +} + +// CIR: cir.func @_Z10for_range3v() +// CIR: %[[C_ADDR:.*]] = cir.alloca !rec_C3{{.*}} ["c"] +// CIR: cir.scope { +// CIR: %[[RANGE_ADDR:.*]] = cir.alloca !cir.ptr<!rec_C3>{{.*}} ["__range1", init, const] +// CIR: %[[BEGIN_ADDR:.*]] = cir.alloca !rec_Iterator, !cir.ptr<!rec_Iterator>{{.*}} ["__begin1"] +// CIR: %[[END_ADDR:.*]] = cir.alloca !rec_Iterator, !cir.ptr<!rec_Iterator>{{.*}} ["__end1"] +// CIR: %[[E_ADDR:.*]] = cir.alloca !cir.ptr<!rec_Element>{{.*}} ["e", init, const] +// CIR: cir.store %[[C_ADDR]], %[[RANGE_ADDR]] +// CIR: cir.for : cond { +// CIR: %[[ITER_NE:.*]] = cir.call @_ZNK8IteratorneERKS_(%[[BEGIN_ADDR]], %[[END_ADDR]]) +// CIR: cir.condition(%[[ITER_NE]]) +// CIR: } body { +// CIR: %[[E:.*]] = cir.call @_ZN8IteratordeEv(%[[BEGIN_ADDR]]) +// CIR: cir.store %[[E]], %[[E_ADDR]] +// CIR: cir.yield +// CIR: } step { +// CIR: %[[ITER_NEXT:.*]] = cir.call @_ZN8IteratorppEv(%[[BEGIN_ADDR]]) +// CIR: cir.yield +// CIR: } +// CIR: } _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits