Skip to content

Commit cbcfe66

Browse files
authored
[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.
1 parent 0931874 commit cbcfe66

File tree

5 files changed

+270
-7
lines changed

5 files changed

+270
-7
lines changed

clang/lib/CIR/CodeGen/CIRGenCXXExpr.cpp

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,11 @@ RValue CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr(
9898
CallArgList rtlArgStorage;
9999
CallArgList *rtlArgs = nullptr;
100100
if (auto *oce = dyn_cast<CXXOperatorCallExpr>(ce)) {
101-
cgm.errorNYI(oce->getSourceRange(),
102-
"emitCXXMemberOrOperatorMemberCallExpr: operator call");
103-
return RValue::get(nullptr);
101+
if (oce->isAssignmentOp()) {
102+
cgm.errorNYI(
103+
oce->getSourceRange(),
104+
"emitCXXMemberOrOperatorMemberCallExpr: assignment operator");
105+
}
104106
}
105107

106108
LValue thisPtr;
@@ -169,6 +171,17 @@ RValue CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr(
169171
/*ImplicitParam=*/nullptr, QualType(), ce, rtlArgs);
170172
}
171173

174+
RValue
175+
CIRGenFunction::emitCXXOperatorMemberCallExpr(const CXXOperatorCallExpr *e,
176+
const CXXMethodDecl *md,
177+
ReturnValueSlot returnValue) {
178+
assert(md->isInstance() &&
179+
"Trying to emit a member call expr on a static method!");
180+
return emitCXXMemberOrOperatorMemberCallExpr(
181+
e, md, returnValue, /*HasQualifier=*/false, /*Qualifier=*/nullptr,
182+
/*IsArrow=*/false, e->getArg(0));
183+
}
184+
172185
RValue CIRGenFunction::emitCXXMemberOrOperatorCall(
173186
const CXXMethodDecl *md, const CIRGenCallee &callee,
174187
ReturnValueSlot returnValue, mlir::Value thisPtr, mlir::Value implicitParam,

clang/lib/CIR/CodeGen/CIRGenExpr.cpp

Lines changed: 139 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,122 @@ CIRGenFunction::emitArraySubscriptExpr(const clang::ArraySubscriptExpr *e) {
743743
return lv;
744744
}
745745

746+
/// Casts are never lvalues unless that cast is to a reference type. If the cast
747+
/// is to a reference, we can have the usual lvalue result, otherwise if a cast
748+
/// is needed by the code generator in an lvalue context, then it must mean that
749+
/// we need the address of an aggregate in order to access one of its members.
750+
/// This can happen for all the reasons that casts are permitted with aggregate
751+
/// result, including noop aggregate casts, and cast from scalar to union.
752+
LValue CIRGenFunction::emitCastLValue(const CastExpr *e) {
753+
switch (e->getCastKind()) {
754+
case CK_ToVoid:
755+
case CK_BitCast:
756+
case CK_LValueToRValueBitCast:
757+
case CK_ArrayToPointerDecay:
758+
case CK_FunctionToPointerDecay:
759+
case CK_NullToMemberPointer:
760+
case CK_NullToPointer:
761+
case CK_IntegralToPointer:
762+
case CK_PointerToIntegral:
763+
case CK_PointerToBoolean:
764+
case CK_IntegralCast:
765+
case CK_BooleanToSignedIntegral:
766+
case CK_IntegralToBoolean:
767+
case CK_IntegralToFloating:
768+
case CK_FloatingToIntegral:
769+
case CK_FloatingToBoolean:
770+
case CK_FloatingCast:
771+
case CK_FloatingRealToComplex:
772+
case CK_FloatingComplexToReal:
773+
case CK_FloatingComplexToBoolean:
774+
case CK_FloatingComplexCast:
775+
case CK_FloatingComplexToIntegralComplex:
776+
case CK_IntegralRealToComplex:
777+
case CK_IntegralComplexToReal:
778+
case CK_IntegralComplexToBoolean:
779+
case CK_IntegralComplexCast:
780+
case CK_IntegralComplexToFloatingComplex:
781+
case CK_DerivedToBaseMemberPointer:
782+
case CK_BaseToDerivedMemberPointer:
783+
case CK_MemberPointerToBoolean:
784+
case CK_ReinterpretMemberPointer:
785+
case CK_AnyPointerToBlockPointerCast:
786+
case CK_ARCProduceObject:
787+
case CK_ARCConsumeObject:
788+
case CK_ARCReclaimReturnedObject:
789+
case CK_ARCExtendBlockObject:
790+
case CK_CopyAndAutoreleaseBlockObject:
791+
case CK_IntToOCLSampler:
792+
case CK_FloatingToFixedPoint:
793+
case CK_FixedPointToFloating:
794+
case CK_FixedPointCast:
795+
case CK_FixedPointToBoolean:
796+
case CK_FixedPointToIntegral:
797+
case CK_IntegralToFixedPoint:
798+
case CK_MatrixCast:
799+
case CK_HLSLVectorTruncation:
800+
case CK_HLSLArrayRValue:
801+
case CK_HLSLElementwiseCast:
802+
case CK_HLSLAggregateSplatCast:
803+
llvm_unreachable("unexpected cast lvalue");
804+
805+
case CK_Dependent:
806+
llvm_unreachable("dependent cast kind in IR gen!");
807+
808+
case CK_BuiltinFnToFnPtr:
809+
llvm_unreachable("builtin functions are handled elsewhere");
810+
811+
// These are never l-values; just use the aggregate emission code.
812+
case CK_NonAtomicToAtomic:
813+
case CK_AtomicToNonAtomic:
814+
case CK_Dynamic:
815+
case CK_UncheckedDerivedToBase:
816+
case CK_DerivedToBase:
817+
case CK_ToUnion:
818+
case CK_BaseToDerived:
819+
case CK_LValueBitCast:
820+
case CK_AddressSpaceConversion:
821+
case CK_ObjCObjectLValueCast:
822+
case CK_VectorSplat:
823+
case CK_ConstructorConversion:
824+
case CK_UserDefinedConversion:
825+
case CK_CPointerToObjCPointerCast:
826+
case CK_BlockPointerToObjCPointerCast:
827+
case CK_LValueToRValue: {
828+
cgm.errorNYI(e->getSourceRange(),
829+
std::string("emitCastLValue for unhandled cast kind: ") +
830+
e->getCastKindName());
831+
832+
return {};
833+
}
834+
835+
case CK_NoOp: {
836+
// CK_NoOp can model a qualification conversion, which can remove an array
837+
// bound and change the IR type.
838+
LValue lv = emitLValue(e->getSubExpr());
839+
// Propagate the volatile qualifier to LValue, if exists in e.
840+
if (e->changesVolatileQualification())
841+
cgm.errorNYI(e->getSourceRange(),
842+
"emitCastLValue: NoOp changes volatile qual");
843+
if (lv.isSimple()) {
844+
Address v = lv.getAddress();
845+
if (v.isValid()) {
846+
mlir::Type ty = convertTypeForMem(e->getType());
847+
if (v.getElementType() != ty)
848+
cgm.errorNYI(e->getSourceRange(),
849+
"emitCastLValue: NoOp needs bitcast");
850+
}
851+
}
852+
return lv;
853+
}
854+
855+
case CK_ZeroToOCLOpaqueType:
856+
llvm_unreachable("NULL to OpenCL opaque type lvalue cast is not valid");
857+
}
858+
859+
llvm_unreachable("Invalid cast kind");
860+
}
861+
746862
LValue CIRGenFunction::emitMemberExpr(const MemberExpr *e) {
747863
if (isa<VarDecl>(e->getMemberDecl())) {
748864
cgm.errorNYI(e->getSourceRange(), "emitMemberExpr: VarDecl");
@@ -785,6 +901,21 @@ LValue CIRGenFunction::emitMemberExpr(const MemberExpr *e) {
785901
llvm_unreachable("Unhandled member declaration!");
786902
}
787903

904+
LValue CIRGenFunction::emitCallExprLValue(const CallExpr *e) {
905+
RValue rv = emitCallExpr(e);
906+
907+
if (!rv.isScalar()) {
908+
cgm.errorNYI(e->getSourceRange(), "emitCallExprLValue: non-scalar return");
909+
return {};
910+
}
911+
912+
assert(e->getCallReturnType(getContext())->isReferenceType() &&
913+
"Can't have a scalar return unless the return type is a "
914+
"reference type!");
915+
916+
return makeNaturalAlignPointeeAddrLValue(rv.getScalarVal(), e->getType());
917+
}
918+
788919
LValue CIRGenFunction::emitBinaryOperatorLValue(const BinaryOperator *e) {
789920
// Comma expressions just emit their LHS then their RHS as an l-value.
790921
if (e->getOpcode() == BO_Comma) {
@@ -983,10 +1114,14 @@ RValue CIRGenFunction::emitCallExpr(const clang::CallExpr *e,
9831114
}
9841115

9851116
if (const auto *operatorCall = dyn_cast<CXXOperatorCallExpr>(e)) {
986-
if (isa_and_nonnull<CXXMethodDecl>(operatorCall->getCalleeDecl())) {
987-
cgm.errorNYI(e->getSourceRange(), "call to member operator");
988-
return RValue::get(nullptr);
989-
}
1117+
// If the callee decl is a CXXMethodDecl, we need to emit this as a C++
1118+
// operator member call.
1119+
if (const CXXMethodDecl *md =
1120+
dyn_cast_or_null<CXXMethodDecl>(operatorCall->getCalleeDecl()))
1121+
return emitCXXOperatorMemberCallExpr(operatorCall, md, returnValue);
1122+
// A CXXOperatorCallExpr is created even for explicit object methods, but
1123+
// these should be treated like static function calls. Fall through to do
1124+
// that.
9901125
}
9911126

9921127
CIRGenCallee callee = emitCallee(e->getCallee());

clang/lib/CIR/CodeGen/CIRGenFunction.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,18 @@ cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl gd, cir::FuncOp fn,
476476
return fn;
477477
}
478478

479+
/// Given a value of type T* that may not be to a complete object, construct
480+
/// an l-vlaue withi the natural pointee alignment of T.
481+
LValue CIRGenFunction::makeNaturalAlignPointeeAddrLValue(mlir::Value val,
482+
QualType ty) {
483+
// FIXME(cir): is it safe to assume Op->getResult(0) is valid? Perhaps
484+
// assert on the result type first.
485+
LValueBaseInfo baseInfo;
486+
assert(!cir::MissingFeatures::opTBAA());
487+
CharUnits align = cgm.getNaturalTypeAlignment(ty, &baseInfo);
488+
return makeAddrLValue(Address(val, align), ty, baseInfo);
489+
}
490+
479491
clang::QualType CIRGenFunction::buildFunctionArgList(clang::GlobalDecl gd,
480492
FunctionArgList &args) {
481493
const auto *fd = cast<FunctionDecl>(gd.getDecl());
@@ -536,10 +548,20 @@ LValue CIRGenFunction::emitLValue(const Expr *e) {
536548
"CompoundAssignOperator with ComplexType");
537549
return LValue();
538550
}
551+
case Expr::CallExprClass:
552+
case Expr::CXXMemberCallExprClass:
553+
case Expr::CXXOperatorCallExprClass:
554+
case Expr::UserDefinedLiteralClass:
555+
return emitCallExprLValue(cast<CallExpr>(e));
539556
case Expr::ParenExprClass:
540557
return emitLValue(cast<ParenExpr>(e)->getSubExpr());
541558
case Expr::DeclRefExprClass:
542559
return emitDeclRefLValue(cast<DeclRefExpr>(e));
560+
case Expr::CStyleCastExprClass:
561+
case Expr::CXXStaticCastExprClass:
562+
case Expr::CXXDynamicCastExprClass:
563+
case Expr::ImplicitCastExprClass:
564+
return emitCastLValue(cast<CastExpr>(e));
543565
}
544566
}
545567

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,8 @@ class CIRGenFunction : public CIRGenTypeCache {
282282
// TODO: Add symbol table support
283283
}
284284

285+
LValue makeNaturalAlignPointeeAddrLValue(mlir::Value v, clang::QualType t);
286+
285287
/// Construct an address with the natural alignment of T. If a pointer to T
286288
/// is expected to be signed, the pointer passed to this function must have
287289
/// been signed, and the returned Address will have the pointer authentication
@@ -515,6 +517,7 @@ class CIRGenFunction : public CIRGenTypeCache {
515517
AbstractCallee callee = AbstractCallee(), unsigned paramsToSkip = 0);
516518
RValue emitCallExpr(const clang::CallExpr *e,
517519
ReturnValueSlot returnValue = ReturnValueSlot());
520+
LValue emitCallExprLValue(const clang::CallExpr *e);
518521
CIRGenCallee emitCallee(const clang::Expr *e);
519522

520523
template <typename T>
@@ -527,6 +530,8 @@ class CIRGenFunction : public CIRGenTypeCache {
527530
mlir::Type condType,
528531
bool buildingTopLevelCase);
529532

533+
LValue emitCastLValue(const CastExpr *e);
534+
530535
LValue emitCompoundAssignmentLValue(const clang::CompoundAssignOperator *e);
531536

532537
mlir::LogicalResult emitContinueStmt(const clang::ContinueStmt &s);
@@ -549,6 +554,10 @@ class CIRGenFunction : public CIRGenTypeCache {
549554
clang::NestedNameSpecifier *qualifier, bool isArrow,
550555
const clang::Expr *base);
551556

557+
RValue emitCXXOperatorMemberCallExpr(const CXXOperatorCallExpr *e,
558+
const CXXMethodDecl *md,
559+
ReturnValueSlot returnValue);
560+
552561
mlir::LogicalResult emitDoStmt(const clang::DoStmt &s);
553562

554563
/// Emit an expression as an initializer for an object (variable, field, etc.)

clang/test/CIR/CodeGen/forrange.cpp

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,87 @@ void for_range() {
4747
// CIR: cir.yield
4848
// CIR: }
4949
// CIR: }
50+
51+
struct C2 {
52+
Element *begin();
53+
Element *end();
54+
};
55+
56+
void for_range2() {
57+
C2 c;
58+
for (Element &e : c)
59+
;
60+
}
61+
62+
// CIR: cir.func @_Z10for_range2v()
63+
// CIR: %[[C_ADDR:.*]] = cir.alloca !rec_C2{{.*}} ["c"]
64+
// CIR: cir.scope {
65+
// CIR: %[[RANGE_ADDR:.*]] = cir.alloca !cir.ptr<!rec_C2>{{.*}} ["__range1", init, const]
66+
// CIR: %[[BEGIN_ADDR:.*]] = cir.alloca !cir.ptr<!rec_Element>{{.*}} ["__begin1", init]
67+
// CIR: %[[END_ADDR:.*]] = cir.alloca !cir.ptr<!rec_Element>{{.*}} ["__end1", init]
68+
// CIR: %[[E_ADDR:.*]] = cir.alloca !cir.ptr<!rec_Element>{{.*}} ["e", init, const]
69+
// CIR: cir.store %[[C_ADDR]], %[[RANGE_ADDR]]
70+
// CIR: %[[C_REF:.*]] = cir.load %[[RANGE_ADDR]]
71+
// CIR: %[[BEGIN:.*]] = cir.call @_ZN2C25beginEv(%[[C_REF]])
72+
// CIR: cir.store %[[BEGIN]], %[[BEGIN_ADDR]]
73+
// CIR: %[[C_REF2:.*]] = cir.load %[[RANGE_ADDR]]
74+
// CIR: %[[END:.*]] = cir.call @_ZN2C23endEv(%[[C_REF2]])
75+
// CIR: cir.store %[[END]], %[[END_ADDR]]
76+
// CIR: cir.for : cond {
77+
// CIR: %[[BEGIN:.*]] = cir.load %[[BEGIN_ADDR]]
78+
// CIR: %[[END:.*]] = cir.load %[[END_ADDR]]
79+
// CIR: %[[CMP:.*]] = cir.cmp(ne, %[[BEGIN]], %[[END]])
80+
// CIR: cir.condition(%[[CMP]])
81+
// CIR: } body {
82+
// CIR: %[[E:.*]] = cir.load deref %[[BEGIN_ADDR]]
83+
// CIR: cir.store %[[E]], %[[E_ADDR]]
84+
// CIR: cir.yield
85+
// CIR: } step {
86+
// CIR: %[[BEGIN:.*]] = cir.load %[[BEGIN_ADDR]]
87+
// CIR: %[[STEP:.*]] = cir.const #cir.int<1>
88+
// CIR: %[[NEXT:.*]] = cir.ptr_stride(%[[BEGIN]] {{.*}}, %[[STEP]] {{.*}})
89+
// CIR: cir.store %[[NEXT]], %[[BEGIN_ADDR]]
90+
// CIR: cir.yield
91+
// CIR: }
92+
// CIR: }
93+
94+
// Iterator class definition
95+
class Iterator {
96+
public:
97+
Element& operator*();
98+
Iterator& operator++();
99+
bool operator!=(const Iterator& other) const;
100+
};
101+
102+
class C3 {
103+
public:
104+
Iterator begin();
105+
Iterator end();
106+
};
107+
108+
void for_range3() {
109+
C3 c;
110+
for (Element& e : c)
111+
;
112+
}
113+
114+
// CIR: cir.func @_Z10for_range3v()
115+
// CIR: %[[C_ADDR:.*]] = cir.alloca !rec_C3{{.*}} ["c"]
116+
// CIR: cir.scope {
117+
// CIR: %[[RANGE_ADDR:.*]] = cir.alloca !cir.ptr<!rec_C3>{{.*}} ["__range1", init, const]
118+
// CIR: %[[BEGIN_ADDR:.*]] = cir.alloca !rec_Iterator, !cir.ptr<!rec_Iterator>{{.*}} ["__begin1"]
119+
// CIR: %[[END_ADDR:.*]] = cir.alloca !rec_Iterator, !cir.ptr<!rec_Iterator>{{.*}} ["__end1"]
120+
// CIR: %[[E_ADDR:.*]] = cir.alloca !cir.ptr<!rec_Element>{{.*}} ["e", init, const]
121+
// CIR: cir.store %[[C_ADDR]], %[[RANGE_ADDR]]
122+
// CIR: cir.for : cond {
123+
// CIR: %[[ITER_NE:.*]] = cir.call @_ZNK8IteratorneERKS_(%[[BEGIN_ADDR]], %[[END_ADDR]])
124+
// CIR: cir.condition(%[[ITER_NE]])
125+
// CIR: } body {
126+
// CIR: %[[E:.*]] = cir.call @_ZN8IteratordeEv(%[[BEGIN_ADDR]])
127+
// CIR: cir.store %[[E]], %[[E_ADDR]]
128+
// CIR: cir.yield
129+
// CIR: } step {
130+
// CIR: %[[ITER_NEXT:.*]] = cir.call @_ZN8IteratorppEv(%[[BEGIN_ADDR]])
131+
// CIR: cir.yield
132+
// CIR: }
133+
// CIR: }

0 commit comments

Comments
 (0)