Skip to content

Commit 671e312

Browse files
smeenailanza
authored andcommitted
[CIR][CIRGen] Support emitting memcpys for fields (#1195)
Default assignment operator generation was failing because of memcpy generation for fields being unsupported. Implement it following CodeGen's example, as usual. Follow-ups will avoid emitting memcpys for fields of trivial class types, and extend this to copy constructors as well. Fixes #1128
1 parent 90815ba commit 671e312

File tree

3 files changed

+139
-13
lines changed

3 files changed

+139
-13
lines changed

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ struct MissingFeatures {
174174

175175
// ABIInfo queries.
176176
static bool useTargetLoweringABIInfo() { return false; }
177+
static bool isEmptyFieldForLayout() { return false; }
177178

178179
// Misc
179180
static bool cacheRecordLayouts() { return false; }

clang/lib/CIR/CodeGen/CIRGenClass.cpp

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,13 @@ class CopyingValueRepresentation {
9898

9999
class FieldMemcpyizer {
100100
public:
101-
FieldMemcpyizer(CIRGenFunction &CGF, const CXXRecordDecl *ClassDecl,
101+
FieldMemcpyizer(CIRGenFunction &CGF, const CXXMethodDecl *MethodDecl,
102102
const VarDecl *SrcRec)
103-
: CGF(CGF), ClassDecl(ClassDecl),
104-
// SrcRec(SrcRec),
103+
: CGF(CGF), MethodDecl(MethodDecl), ClassDecl(MethodDecl->getParent()),
104+
SrcRec(SrcRec),
105105
RecLayout(CGF.getContext().getASTRecordLayout(ClassDecl)),
106106
FirstField(nullptr), LastField(nullptr), FirstFieldOffset(0),
107-
LastFieldOffset(0), LastAddedFieldIndex(0) {
108-
(void)SrcRec;
109-
}
107+
LastFieldOffset(0), LastAddedFieldIndex(0) {}
110108

111109
bool isMemcpyableField(FieldDecl *F) const {
112110
// Never memcpy fields when we are adding poised paddings.
@@ -115,11 +113,11 @@ class FieldMemcpyizer {
115113
Qualifiers Qual = F->getType().getQualifiers();
116114
if (Qual.hasVolatile() || Qual.hasObjCLifetime())
117115
return false;
118-
119116
return true;
120117
}
121118

122119
void addMemcpyableField(FieldDecl *F) {
120+
assert(!cir::MissingFeatures::isEmptyFieldForLayout());
123121
if (F->isZeroSize(CGF.getContext()))
124122
return;
125123
if (!FirstField)
@@ -148,18 +146,54 @@ class FieldMemcpyizer {
148146
return;
149147
}
150148

151-
llvm_unreachable("NYI");
149+
uint64_t firstByteOffset;
150+
if (FirstField->isBitField()) {
151+
const CIRGenRecordLayout &rl =
152+
CGF.getTypes().getCIRGenRecordLayout(FirstField->getParent());
153+
const CIRGenBitFieldInfo &bfInfo = rl.getBitFieldInfo(FirstField);
154+
// FirstFieldOffset is not appropriate for bitfields,
155+
// we need to use the storage offset instead.
156+
firstByteOffset = CGF.getContext().toBits(bfInfo.StorageOffset);
157+
} else {
158+
firstByteOffset = FirstFieldOffset;
159+
}
160+
161+
CharUnits memcpySize = getMemcpySize(firstByteOffset);
162+
QualType recordTy = CGF.getContext().getTypeDeclType(ClassDecl);
163+
Address thisPtr = CGF.LoadCXXThisAddress();
164+
LValue destLv = CGF.makeAddrLValue(thisPtr, recordTy);
165+
LValue dest = CGF.emitLValueForFieldInitialization(destLv, FirstField,
166+
FirstField->getName());
167+
cir::LoadOp srcPtr = CGF.getBuilder().createLoad(
168+
CGF.getLoc(MethodDecl->getLocation()), CGF.GetAddrOfLocalVar(SrcRec));
169+
LValue srcLv = CGF.MakeNaturalAlignAddrLValue(srcPtr, recordTy);
170+
LValue src = CGF.emitLValueForFieldInitialization(srcLv, FirstField,
171+
FirstField->getName());
172+
173+
emitMemcpyIR(dest.isBitField() ? dest.getBitFieldAddress()
174+
: dest.getAddress(),
175+
src.isBitField() ? src.getBitFieldAddress() : src.getAddress(),
176+
memcpySize);
177+
reset();
152178
}
153179

154180
void reset() { FirstField = nullptr; }
155181

156182
protected:
157183
CIRGenFunction &CGF;
184+
const CXXMethodDecl *MethodDecl;
158185
const CXXRecordDecl *ClassDecl;
159186

160187
private:
161188
void emitMemcpyIR(Address DestPtr, Address SrcPtr, CharUnits Size) {
162-
llvm_unreachable("NYI");
189+
mlir::Location loc = CGF.getLoc(MethodDecl->getLocation());
190+
cir::ConstantOp sizeOp =
191+
CGF.getBuilder().getConstInt(loc, CGF.SizeTy, Size.getQuantity());
192+
mlir::Value dest =
193+
CGF.getBuilder().createBitcast(DestPtr.getPointer(), CGF.VoidPtrTy);
194+
mlir::Value src =
195+
CGF.getBuilder().createBitcast(SrcPtr.getPointer(), CGF.VoidPtrTy);
196+
CGF.getBuilder().createMemCpy(loc, dest, src, sizeOp);
163197
}
164198

165199
void addInitialField(FieldDecl *F) {
@@ -192,7 +226,7 @@ class FieldMemcpyizer {
192226
}
193227
}
194228

195-
// const VarDecl *SrcRec;
229+
const VarDecl *SrcRec;
196230
const ASTRecordLayout &RecLayout;
197231
FieldDecl *FirstField;
198232
FieldDecl *LastField;
@@ -307,8 +341,7 @@ class ConstructorMemcpyizer : public FieldMemcpyizer {
307341
public:
308342
ConstructorMemcpyizer(CIRGenFunction &CGF, const CXXConstructorDecl *CD,
309343
FunctionArgList &Args)
310-
: FieldMemcpyizer(CGF, CD->getParent(),
311-
getTrivialCopySource(CGF, CD, Args)),
344+
: FieldMemcpyizer(CGF, CD, getTrivialCopySource(CGF, CD, Args)),
312345
ConstructorDecl(CD),
313346
MemcpyableCtor(CD->isDefaulted() && CD->isCopyOrMoveConstructor() &&
314347
CGF.getLangOpts().getGC() == LangOptions::NonGC),
@@ -446,7 +479,7 @@ class AssignmentMemcpyizer : public FieldMemcpyizer {
446479
public:
447480
AssignmentMemcpyizer(CIRGenFunction &CGF, const CXXMethodDecl *AD,
448481
FunctionArgList &Args)
449-
: FieldMemcpyizer(CGF, AD->getParent(), Args[Args.size() - 1]),
482+
: FieldMemcpyizer(CGF, AD, Args[Args.size() - 1]),
450483
AssignmentsMemcpyable(CGF.getLangOpts().getGC() == LangOptions::NonGC) {
451484
assert(Args.size() == 2);
452485
}

clang/test/CIR/CodeGen/assign-operator.cpp

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,95 @@ int main() {
9999
// CHECK: %2 = cir.load %0 : !cir.ptr<!s32i>, !s32i
100100
// CHECK: cir.return %2 : !s32i
101101
// CHECK: }
102+
103+
struct HasNonTrivialAssignOp {
104+
HasNonTrivialAssignOp &operator=(const HasNonTrivialAssignOp &);
105+
};
106+
107+
struct ContainsNonTrivial {
108+
HasNonTrivialAssignOp start;
109+
int i;
110+
int *j;
111+
HasNonTrivialAssignOp middle;
112+
int k : 4;
113+
int l : 4;
114+
int m : 4;
115+
HasNonTrivialAssignOp end;
116+
ContainsNonTrivial &operator=(const ContainsNonTrivial &);
117+
};
118+
119+
// CHECK-LABEL: cir.func @_ZN18ContainsNonTrivialaSERKS_(
120+
// CHECK-NEXT: %[[#THIS:]] = cir.alloca !cir.ptr<!ty_ContainsNonTrivial>
121+
// CHECK-NEXT: %[[#OTHER:]] = cir.alloca !cir.ptr<!ty_ContainsNonTrivial>
122+
// CHECK-NEXT: %[[#RETVAL:]] = cir.alloca !cir.ptr<!ty_ContainsNonTrivial>
123+
// CHECK-NEXT: cir.store %arg0, %[[#THIS]]
124+
// CHECK-NEXT: cir.store %arg1, %[[#OTHER]]
125+
// CHECK-NEXT: %[[#THIS_LOAD:]] = cir.load deref %[[#THIS]]
126+
// CHECK-NEXT: %[[#THIS_START:]] = cir.get_member %[[#THIS_LOAD]][0] {name = "start"}
127+
// CHECK-NEXT: %[[#OTHER_LOAD:]] = cir.load %[[#OTHER]]
128+
// CHECK-NEXT: %[[#OTHER_START:]] = cir.get_member %[[#OTHER_LOAD]][0] {name = "start"}
129+
// CHECK-NEXT: cir.call @_ZN21HasNonTrivialAssignOpaSERKS_(%[[#THIS_START]], %[[#OTHER_START]])
130+
// CHECK-NEXT: %[[#THIS_I:]] = cir.get_member %[[#THIS_LOAD]][2] {name = "i"}
131+
// CHECK-NEXT: %[[#OTHER_LOAD:]] = cir.load %[[#OTHER]]
132+
// CHECK-NEXT: %[[#OTHER_I:]] = cir.get_member %[[#OTHER_LOAD]][2] {name = "i"}
133+
// CHECK-NEXT: %[[#MEMCPY_SIZE:]] = cir.const #cir.int<12> : !u64i
134+
// CHECK-NEXT: %[[#THIS_I_CAST:]] = cir.cast(bitcast, %[[#THIS_I]] : !cir.ptr<!s32i>), !cir.ptr<!void>
135+
// CHECK-NEXT: %[[#OTHER_I_CAST:]] = cir.cast(bitcast, %[[#OTHER_I]] : !cir.ptr<!s32i>), !cir.ptr<!void>
136+
// CHECK-NEXT: cir.libc.memcpy %[[#MEMCPY_SIZE]] bytes from %[[#OTHER_I_CAST]] to %[[#THIS_I_CAST]]
137+
// CHECK-NEXT: %[[#THIS_MIDDLE:]] = cir.get_member %[[#THIS_LOAD]][4] {name = "middle"}
138+
// CHECK-NEXT: %[[#OTHER_LOAD:]] = cir.load %[[#OTHER]]
139+
// CHECK-NEXT: %[[#OTHER_MIDDLE:]] = cir.get_member %[[#OTHER_LOAD]][4] {name = "middle"}
140+
// CHECK-NEXT: cir.call @_ZN21HasNonTrivialAssignOpaSERKS_(%[[#THIS_MIDDLE]], %[[#OTHER_MIDDLE]])
141+
// CHECK-NEXT: %[[#THIS_K:]] = cir.get_member %[[#THIS_LOAD]][5] {name = "k"}
142+
// CHECK-NEXT: %[[#OTHER_LOAD:]] = cir.load %[[#OTHER]]
143+
// CHECK-NEXT: %[[#OTHER_K:]] = cir.get_member %[[#OTHER_LOAD]][5] {name = "k"}
144+
// CHECK-NEXT: %[[#MEMCPY_SIZE:]] = cir.const #cir.int<2> : !u64i
145+
// CHECK-NEXT: %[[#THIS_K_CAST:]] = cir.cast(bitcast, %[[#THIS_K]] : !cir.ptr<!u16i>), !cir.ptr<!void>
146+
// CHECK-NEXT: %[[#OTHER_K_CAST:]] = cir.cast(bitcast, %[[#OTHER_K]] : !cir.ptr<!u16i>), !cir.ptr<!void>
147+
// CHECK-NEXT: cir.libc.memcpy %[[#MEMCPY_SIZE]] bytes from %[[#OTHER_K_CAST]] to %[[#THIS_K_CAST]]
148+
// CHECK-NEXT: %[[#THIS_END:]] = cir.get_member %[[#THIS_LOAD]][6] {name = "end"}
149+
// CHECK-NEXT: %[[#OTHER_LOAD:]] = cir.load %[[#OTHER]]
150+
// CHECK-NEXT: %[[#OTHER_END:]] = cir.get_member %[[#OTHER_LOAD]][6] {name = "end"}
151+
// CHECK-NEXT: cir.call @_ZN21HasNonTrivialAssignOpaSERKS_(%[[#THIS_END]], %[[#OTHER_END]])
152+
// CHECK-NEXT: cir.store %[[#THIS_LOAD]], %[[#RETVAL]]
153+
// CHECK-NEXT: %[[#RETVAL_LOAD:]] = cir.load %[[#RETVAL]]
154+
// CHECK-NEXT: cir.return %[[#RETVAL_LOAD]]
155+
// CHECK-NEXT: }
156+
ContainsNonTrivial &
157+
ContainsNonTrivial::operator=(const ContainsNonTrivial &) = default;
158+
159+
struct Trivial {
160+
int i;
161+
int *j;
162+
double k;
163+
int l[3];
164+
};
165+
166+
// CHECK-LABEL: cir.func linkonce_odr @_ZN7TrivialaSERKS_(
167+
// CHECK-NEXT: %[[#THIS:]] = cir.alloca !cir.ptr<!ty_Trivial>
168+
// CHECK-NEXT: %[[#OTHER:]] = cir.alloca !cir.ptr<!ty_Trivial>
169+
// CHECK-NEXT: %[[#RETVAL:]] = cir.alloca !cir.ptr<!ty_Trivial>
170+
// CHECK-NEXT: cir.store %arg0, %[[#THIS]]
171+
// CHECK-NEXT: cir.store %arg1, %[[#OTHER]]
172+
// CHECK-NEXT: %[[#THIS_LOAD:]] = cir.load deref %[[#THIS]]
173+
// CHECK-NEXT: %[[#THIS_I:]] = cir.get_member %[[#THIS_LOAD]][0] {name = "i"}
174+
// CHECK-NEXT: %[[#OTHER_LOAD:]] = cir.load %[[#OTHER]]
175+
// CHECK-NEXT: %[[#OTHER_I:]] = cir.get_member %[[#OTHER_LOAD]][0] {name = "i"}
176+
// Note that tail padding bytes are not included.
177+
// CHECK-NEXT: %[[#MEMCPY_SIZE:]] = cir.const #cir.int<36> : !u64i
178+
// CHECK-NEXT: %[[#THIS_I_CAST:]] = cir.cast(bitcast, %[[#THIS_I]] : !cir.ptr<!s32i>), !cir.ptr<!void>
179+
// CHECK-NEXT: %[[#OTHER_I_CAST:]] = cir.cast(bitcast, %[[#OTHER_I]] : !cir.ptr<!s32i>), !cir.ptr<!void>
180+
// CHECK-NEXT: cir.libc.memcpy %[[#MEMCPY_SIZE]] bytes from %[[#OTHER_I_CAST]] to %[[#THIS_I_CAST]]
181+
// CHECK-NEXT: cir.store %[[#THIS_LOAD]], %[[#RETVAL]]
182+
// CHECK-NEXT: cir.br ^bb1
183+
// CHECK-NEXT: ^bb1:
184+
// CHECK-NEXT: %[[#RETVAL_LOAD:]] = cir.load %[[#RETVAL]]
185+
// CHECK-NEXT: cir.return %[[#RETVAL_LOAD]]
186+
// CHECK-NEXT: }
187+
188+
// We should explicitly call operator= even for trivial types.
189+
// CHECK-LABEL: cir.func @_Z11copyTrivialR7TrivialS0_(
190+
// CHECK: cir.call @_ZN7TrivialaSERKS_(
191+
void copyTrivial(Trivial &a, Trivial &b) {
192+
a = b;
193+
}

0 commit comments

Comments
 (0)