Skip to content

Commit 32180cf

Browse files
authored
[CIR] Upstream support for operator assign (#145979)
This adds support for assignment operators, including implicit operator definitions.
1 parent 570b952 commit 32180cf

File tree

7 files changed

+199
-40
lines changed

7 files changed

+199
-40
lines changed

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ struct MissingFeatures {
172172
static bool alignCXXRecordDecl() { return false; }
173173
static bool armComputeVolatileBitfields() { return false; }
174174
static bool asmLabelAttr() { return false; }
175+
static bool assignMemcpyizer() { return false; }
175176
static bool astVarDeclInterface() { return false; }
176177
static bool attributeBuiltin() { return false; }
177178
static bool attributeNoBuiltin() { return false; }

clang/lib/CIR/CodeGen/CIRGenCXXExpr.cpp

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -85,23 +85,24 @@ RValue CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr(
8585
return RValue::get(nullptr);
8686
}
8787

88-
bool trivialForCodegen =
89-
md->isTrivial() || (md->isDefaulted() && md->getParent()->isUnion());
90-
bool trivialAssignment =
91-
trivialForCodegen &&
92-
(md->isCopyAssignmentOperator() || md->isMoveAssignmentOperator()) &&
93-
!md->getParent()->mayInsertExtraPadding();
94-
(void)trivialAssignment;
88+
// Note on trivial assignment
89+
// --------------------------
90+
// Classic codegen avoids generating the trivial copy/move assignment operator
91+
// when it isn't necessary, choosing instead to just produce IR with an
92+
// equivalent effect. We have chosen not to do that in CIR, instead emitting
93+
// trivial copy/move assignment operators and allowing later transformations
94+
// to optimize them away if appropriate.
9595

9696
// C++17 demands that we evaluate the RHS of a (possibly-compound) assignment
9797
// operator before the LHS.
9898
CallArgList rtlArgStorage;
9999
CallArgList *rtlArgs = nullptr;
100100
if (auto *oce = dyn_cast<CXXOperatorCallExpr>(ce)) {
101101
if (oce->isAssignmentOp()) {
102-
cgm.errorNYI(
103-
oce->getSourceRange(),
104-
"emitCXXMemberOrOperatorMemberCallExpr: assignment operator");
102+
rtlArgs = &rtlArgStorage;
103+
emitCallArgs(*rtlArgs, md->getType()->castAs<FunctionProtoType>(),
104+
drop_begin(ce->arguments(), 1), ce->getDirectCallee(),
105+
/*ParamsToSkip*/ 0);
105106
}
106107
}
107108

@@ -121,19 +122,9 @@ RValue CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr(
121122
return RValue::get(nullptr);
122123
}
123124

124-
if (trivialForCodegen) {
125-
if (isa<CXXDestructorDecl>(md))
126-
return RValue::get(nullptr);
127-
128-
if (trivialAssignment) {
129-
cgm.errorNYI(ce->getSourceRange(),
130-
"emitCXXMemberOrOperatorMemberCallExpr: trivial assignment");
131-
return RValue::get(nullptr);
132-
}
133-
134-
assert(md->getParent()->mayInsertExtraPadding() &&
135-
"unknown trivial member function");
136-
}
125+
if ((md->isTrivial() || (md->isDefaulted() && md->getParent()->isUnion())) &&
126+
isa<CXXDestructorDecl>(md))
127+
return RValue::get(nullptr);
137128

138129
// Compute the function type we're calling
139130
const CXXMethodDecl *calleeDecl = md;

clang/lib/CIR/CodeGen/CIRGenClass.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,31 @@ void CIRGenFunction::emitDelegateCXXConstructorCall(
258258
/*Delegating=*/true, thisAddr, delegateArgs, loc);
259259
}
260260

261+
void CIRGenFunction::emitImplicitAssignmentOperatorBody(FunctionArgList &args) {
262+
const auto *assignOp = cast<CXXMethodDecl>(curGD.getDecl());
263+
assert(assignOp->isCopyAssignmentOperator() ||
264+
assignOp->isMoveAssignmentOperator());
265+
const Stmt *rootS = assignOp->getBody();
266+
assert(isa<CompoundStmt>(rootS) &&
267+
"Body of an implicit assignment operator should be compound stmt.");
268+
const auto *rootCS = cast<CompoundStmt>(rootS);
269+
270+
assert(!cir::MissingFeatures::incrementProfileCounter());
271+
assert(!cir::MissingFeatures::runCleanupsScope());
272+
273+
// Classic codegen uses a special class to attempt to replace member
274+
// initializers with memcpy. We could possibly defer that to the
275+
// lowering or optimization phases to keep the memory accesses more
276+
// explicit. For now, we don't insert memcpy at all, though in some
277+
// cases the AST contains a call to memcpy.
278+
assert(!cir::MissingFeatures::assignMemcpyizer());
279+
for (Stmt *s : rootCS->body())
280+
if (emitStmt(s, /*useCurrentScope=*/true).failed())
281+
cgm.errorNYI(s->getSourceRange(),
282+
std::string("emitImplicitAssignmentOperatorBody: ") +
283+
s->getStmtClassName());
284+
}
285+
261286
void CIRGenFunction::emitDelegatingCXXConstructorCall(
262287
const CXXConstructorDecl *ctor, const FunctionArgList &args) {
263288
assert(ctor->isDelegatingConstructor());

clang/lib/CIR/CodeGen/CIRGenFunction.cpp

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -462,21 +462,23 @@ cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl gd, cir::FuncOp fn,
462462

463463
startFunction(gd, retTy, fn, funcType, args, loc, bodyRange.getBegin());
464464

465-
if (isa<CXXDestructorDecl>(funcDecl))
465+
if (isa<CXXDestructorDecl>(funcDecl)) {
466466
getCIRGenModule().errorNYI(bodyRange, "C++ destructor definition");
467-
else if (isa<CXXConstructorDecl>(funcDecl))
467+
} else if (isa<CXXConstructorDecl>(funcDecl)) {
468468
emitConstructorBody(args);
469-
else if (getLangOpts().CUDA && !getLangOpts().CUDAIsDevice &&
470-
funcDecl->hasAttr<CUDAGlobalAttr>())
469+
} else if (getLangOpts().CUDA && !getLangOpts().CUDAIsDevice &&
470+
funcDecl->hasAttr<CUDAGlobalAttr>()) {
471471
getCIRGenModule().errorNYI(bodyRange, "CUDA kernel");
472-
else if (isa<CXXMethodDecl>(funcDecl) &&
473-
cast<CXXMethodDecl>(funcDecl)->isLambdaStaticInvoker())
472+
} else if (isa<CXXMethodDecl>(funcDecl) &&
473+
cast<CXXMethodDecl>(funcDecl)->isLambdaStaticInvoker()) {
474474
getCIRGenModule().errorNYI(bodyRange, "Lambda static invoker");
475-
else if (funcDecl->isDefaulted() && isa<CXXMethodDecl>(funcDecl) &&
476-
(cast<CXXMethodDecl>(funcDecl)->isCopyAssignmentOperator() ||
477-
cast<CXXMethodDecl>(funcDecl)->isMoveAssignmentOperator()))
478-
getCIRGenModule().errorNYI(bodyRange, "Default assignment operator");
479-
else if (body) {
475+
} else if (funcDecl->isDefaulted() && isa<CXXMethodDecl>(funcDecl) &&
476+
(cast<CXXMethodDecl>(funcDecl)->isCopyAssignmentOperator() ||
477+
cast<CXXMethodDecl>(funcDecl)->isMoveAssignmentOperator())) {
478+
// Implicit copy-assignment gets the same special treatment as implicit
479+
// copy-constructors.
480+
emitImplicitAssignmentOperatorBody(args);
481+
} else if (body) {
480482
if (mlir::failed(emitFunctionBody(body))) {
481483
fn.erase();
482484
return nullptr;

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,8 @@ class CIRGenFunction : public CIRGenTypeCache {
878878

879879
mlir::LogicalResult emitFunctionBody(const clang::Stmt *body);
880880

881+
void emitImplicitAssignmentOperatorBody(FunctionArgList &args);
882+
881883
void emitInitializerForField(clang::FieldDecl *field, LValue lhs,
882884
clang::Expr *init);
883885

clang/lib/CIR/CodeGen/CIRGenModule.cpp

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -393,12 +393,6 @@ void CIRGenModule::emitGlobal(clang::GlobalDecl gd) {
393393
void CIRGenModule::emitGlobalFunctionDefinition(clang::GlobalDecl gd,
394394
mlir::Operation *op) {
395395
auto const *funcDecl = cast<FunctionDecl>(gd.getDecl());
396-
if (funcDecl->getIdentifier() == nullptr) {
397-
errorNYI(funcDecl->getSourceRange().getBegin(),
398-
"function definition with a non-identifier for a name");
399-
return;
400-
}
401-
402396
const CIRGenFunctionInfo &fi = getTypes().arrangeGlobalDeclaration(gd);
403397
cir::FuncType funcType = getTypes().getFunctionType(fi);
404398
cir::FuncOp funcOp = dyn_cast_if_present<cir::FuncOp>(op);
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// RUN: %clang_cc1 -std=c++11 -triple aarch64-none-linux-android21 -fclangir -emit-cir %s -o %t.cir
2+
// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
3+
// RUN: %clang_cc1 -std=c++11 -triple aarch64-none-linux-android21 -fclangir -emit-llvm %s -o %t-cir.ll
4+
// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
5+
// RUN: %clang_cc1 -std=c++11 -triple aarch64-none-linux-android21 -emit-llvm %s -o %t.ll
6+
// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
7+
8+
class x {
9+
public: int operator=(int);
10+
};
11+
void a() {
12+
x a;
13+
a = 1u;
14+
}
15+
16+
// CIR: cir.func private @_ZN1xaSEi(!cir.ptr<!rec_x>, !s32i)
17+
// CIR: cir.func{{.*}} @_Z1av()
18+
// CIR: %[[A_ADDR:.*]] = cir.alloca !rec_x, !cir.ptr<!rec_x>, ["a"]
19+
// CIR: %[[ONE:.*]] = cir.const #cir.int<1> : !u32i
20+
// CIR: %[[ONE_CAST:.*]] = cir.cast(integral, %[[ONE]] : !u32i), !s32i
21+
// CIR: %[[RET:.*]] = cir.call @_ZN1xaSEi(%[[A_ADDR]], %[[ONE_CAST]]) : (!cir.ptr<!rec_x>, !s32i) -> !s32i
22+
23+
// LLVM: define{{.*}} @_Z1av()
24+
// OGCG: define{{.*}} @_Z1av()
25+
26+
void f(int i, int j) {
27+
(i += j) = 17;
28+
}
29+
30+
// CIR: cir.func{{.*}} @_Z1fii(%arg0: !s32i {{.*}}, %arg1: !s32i {{.*}})
31+
// CIR: %[[I_ADDR:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["i", init]
32+
// CIR: %[[J_ADDR:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["j", init]
33+
// CIR: cir.store %arg0, %[[I_ADDR]] : !s32i, !cir.ptr<!s32i>
34+
// CIR: cir.store %arg1, %[[J_ADDR]] : !s32i, !cir.ptr<!s32i>
35+
// CIR: %[[SEVENTEEN:.*]] = cir.const #cir.int<17> : !s32i
36+
// CIR: %[[J_LOAD:.*]] = cir.load align(4) %[[J_ADDR]] : !cir.ptr<!s32i>, !s32i
37+
// CIR: %[[I_LOAD:.*]] = cir.load align(4) %[[I_ADDR]] : !cir.ptr<!s32i>, !s32i
38+
// CIR: %[[ADD:.*]] = cir.binop(add, %[[I_LOAD]], %[[J_LOAD]]) nsw : !s32i
39+
// CIR: cir.store align(4) %[[ADD]], %[[I_ADDR]] : !s32i, !cir.ptr<!s32i>
40+
// CIR: cir.store align(4) %[[SEVENTEEN]], %[[I_ADDR]] : !s32i, !cir.ptr<!s32i>
41+
// CIR: cir.return
42+
43+
// Ensure that we use memcpy when we would have selected a trivial assignment
44+
// operator, even for a non-trivially-copyable type.
45+
struct A {
46+
A &operator=(const A&);
47+
};
48+
struct B {
49+
B(const B&);
50+
B &operator=(const B&) = default;
51+
int n;
52+
};
53+
struct C {
54+
A a;
55+
B b[16];
56+
};
57+
void copy_c(C &c1, C &c2) {
58+
c1 = c2;
59+
}
60+
61+
// CIR: cir.func private @_ZN1AaSERKS_(!cir.ptr<!rec_A>, !cir.ptr<!rec_A>) -> !cir.ptr<!rec_A>
62+
// CIR: cir.func private @memcpy(!cir.ptr<!void>, !cir.ptr<!void>, !u64i) -> !cir.ptr<!void>
63+
64+
// Implicit assignment operator for C.
65+
66+
// CIR: cir.func comdat linkonce_odr @_ZN1CaSERKS_(%arg0: !cir.ptr<!rec_C> {{.*}}, %arg1: !cir.ptr<!rec_C> {{.*}}) -> !cir.ptr<!rec_C>
67+
// CIR: %[[THIS_ADDR:.*]] = cir.alloca !cir.ptr<!rec_C>, !cir.ptr<!cir.ptr<!rec_C>>, ["this", init]
68+
// CIR: %[[ARG1_ADDR:.*]] = cir.alloca !cir.ptr<!rec_C>, !cir.ptr<!cir.ptr<!rec_C>>, ["", init, const]
69+
// CIR: %[[RET_ADDR:.*]] = cir.alloca !cir.ptr<!rec_C>, !cir.ptr<!cir.ptr<!rec_C>>, ["__retval"]
70+
// CIR: cir.store %arg0, %[[THIS_ADDR]]
71+
// CIR: cir.store %arg1, %[[ARG1_ADDR]]
72+
// CIR: %[[THIS:.*]] = cir.load{{.*}} %[[THIS_ADDR]]
73+
// CIR: %[[A_MEMBER:.*]] = cir.get_member %[[THIS]][0] {name = "a"}
74+
// CIR: %[[ARG1_LOAD:.*]] = cir.load{{.*}} %[[ARG1_ADDR]]
75+
// CIR: %[[A_MEMBER_2:.*]] = cir.get_member %[[ARG1_LOAD]][0] {name = "a"}
76+
// CIR: %[[C_A:.*]] = cir.call @_ZN1AaSERKS_(%[[A_MEMBER]], %[[A_MEMBER_2]])
77+
// CIR: %[[B_MEMBER:.*]] = cir.get_member %[[THIS]][1] {name = "b"}
78+
// CIR: %[[B_VOID_PTR:.*]] = cir.cast(bitcast, %[[B_MEMBER]] : !cir.ptr<!cir.array<!rec_B x 16>>), !cir.ptr<!void>
79+
// CIR: %[[RET_LOAD:.*]] = cir.load %[[ARG1_ADDR]]
80+
// CIR: %[[B_MEMBER_2:.*]] = cir.get_member %[[RET_LOAD]][1] {name = "b"}
81+
// CIR: %[[B_VOID_PTR_2:.*]] = cir.cast(bitcast, %[[B_MEMBER_2]] : !cir.ptr<!cir.array<!rec_B x 16>>), !cir.ptr<!void>
82+
// CIR: %[[SIZE:.*]] = cir.const #cir.int<64> : !u64i
83+
// CIR: %[[COUNT:.*]] = cir.call @memcpy(%[[B_VOID_PTR]], %[[B_VOID_PTR_2]], %[[SIZE]])
84+
// CIR: cir.store %[[THIS]], %[[RET_ADDR]]
85+
// CIR: %[[RET_VAL:.*]] = cir.load{{.*}} %[[RET_ADDR]]
86+
// CIR: cir.return %[[RET_VAL]]
87+
88+
// CIR: cir.func{{.*}} @_Z6copy_cR1CS0_(%arg0: !cir.ptr<!rec_C> {{.*}}, %arg1: !cir.ptr<!rec_C> {{.*}})
89+
// CIR: %[[C1_ADDR:.*]] = cir.alloca !cir.ptr<!rec_C>, !cir.ptr<!cir.ptr<!rec_C>>, ["c1", init, const]
90+
// CIR: %[[C2_ADDR:.*]] = cir.alloca !cir.ptr<!rec_C>, !cir.ptr<!cir.ptr<!rec_C>>, ["c2", init, const]
91+
// CIR: cir.store %arg0, %[[C1_ADDR]]
92+
// CIR: cir.store %arg1, %[[C2_ADDR]]
93+
// CIR: %[[C2_LOAD:.*]] = cir.load{{.*}} %[[C2_ADDR]]
94+
// CIR: %[[C1_LOAD:.*]] = cir.load{{.*}} %[[C1_ADDR]]
95+
// CIR: %[[RET:.*]] = cir.call @_ZN1CaSERKS_(%[[C1_LOAD]], %[[C2_LOAD]])
96+
97+
struct D {
98+
D &operator=(const D&);
99+
};
100+
struct E {
101+
D &get_d_ref() { return d; }
102+
private:
103+
D d;
104+
};
105+
106+
void copy_ref_to_ref(E &e1, E &e2) {
107+
e1.get_d_ref() = e2.get_d_ref();
108+
}
109+
110+
// The call to e2.get_d_ref() must occur before the call to e1.get_d_ref().
111+
112+
// CIR: cir.func{{.*}} @_Z15copy_ref_to_refR1ES0_(%arg0: !cir.ptr<!rec_E> {{.*}}, %arg1: !cir.ptr<!rec_E> {{.*}})
113+
// CIR: %[[E1_ADDR:.*]] = cir.alloca !cir.ptr<!rec_E>, !cir.ptr<!cir.ptr<!rec_E>>, ["e1", init, const]
114+
// CIR: %[[E2_ADDR:.*]] = cir.alloca !cir.ptr<!rec_E>, !cir.ptr<!cir.ptr<!rec_E>>, ["e2", init, const]
115+
// CIR: cir.store %arg0, %[[E1_ADDR]] : !cir.ptr<!rec_E>, !cir.ptr<!cir.ptr<!rec_E>>
116+
// CIR: cir.store %arg1, %[[E2_ADDR]] : !cir.ptr<!rec_E>, !cir.ptr<!cir.ptr<!rec_E>>
117+
// CIR: %[[E2:.*]] = cir.load %[[E2_ADDR]]
118+
// CIR: %[[D2_REF:.*]] = cir.call @_ZN1E9get_d_refEv(%[[E2]])
119+
// CIR: %[[E1:.*]] = cir.load %[[E1_ADDR]]
120+
// CIR: %[[D1_REF:.*]] = cir.call @_ZN1E9get_d_refEv(%[[E1]])
121+
// CIR: %[[D1_REF_2:.*]] = cir.call @_ZN1DaSERKS_(%[[D1_REF]], %[[D2_REF]])
122+
// CIR: cir.return
123+
124+
// LLVM: define{{.*}} void @_Z15copy_ref_to_refR1ES0_(ptr %[[ARG0:.*]], ptr %[[ARG1:.*]]) {
125+
// LLVM: %[[E1_ADDR:.*]] = alloca ptr
126+
// LLVM: %[[E2_ADDR:.*]] = alloca ptr
127+
// LLVM: store ptr %[[ARG0]], ptr %[[E1_ADDR]]
128+
// LLVM: store ptr %[[ARG1]], ptr %[[E2_ADDR]]
129+
// LLVM: %[[E2:.*]] = load ptr, ptr %[[E2_ADDR]]
130+
// LLVM: %[[D2_REF:.*]] = call ptr @_ZN1E9get_d_refEv(ptr %[[E2]])
131+
// LLVM: %[[E1:.*]] = load ptr, ptr %[[E1_ADDR]]
132+
// LLVM: %[[D1_REF:.*]] = call ptr @_ZN1E9get_d_refEv(ptr %[[E1]])
133+
// LLVM: %[[D1_REF_2:.*]] = call ptr @_ZN1DaSERKS_(ptr %[[D1_REF]], ptr %[[D2_REF]])
134+
135+
// OGCG: define{{.*}} void @_Z15copy_ref_to_refR1ES0_(ptr{{.*}} %[[ARG0:.*]], ptr{{.*}} %[[ARG1:.*]])
136+
// OGCG: %[[E1_ADDR:.*]] = alloca ptr
137+
// OGCG: %[[E2_ADDR:.*]] = alloca ptr
138+
// OGCG: store ptr %[[ARG0]], ptr %[[E1_ADDR]]
139+
// OGCG: store ptr %[[ARG1]], ptr %[[E2_ADDR]]
140+
// OGCG: %[[E2:.*]] = load ptr, ptr %[[E2_ADDR]]
141+
// OGCG: %[[D2_REF:.*]] = call{{.*}} ptr @_ZN1E9get_d_refEv(ptr{{.*}} %[[E2]])
142+
// OGCG: %[[E1:.*]] = load ptr, ptr %[[E1_ADDR]]
143+
// OGCG: %[[D1_REF:.*]] = call{{.*}} ptr @_ZN1E9get_d_refEv(ptr{{.*}} %[[E1]])
144+
// OGCG: %[[D1_REF_2:.*]] = call{{.*}} ptr @_ZN1DaSERKS_(ptr{{.*}} %[[D1_REF]], ptr{{.*}} %[[D2_REF]])

0 commit comments

Comments
 (0)