Skip to content

Commit de619df

Browse files
committed
[CIR] Upstream aggregate binary assign handling
This upstreams the implementation for handling binary assignment involving aggregate types.
1 parent 01c0cb9 commit de619df

File tree

12 files changed

+212
-24
lines changed

12 files changed

+212
-24
lines changed

clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,8 +303,9 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
303303
}
304304

305305
/// Create a copy with inferred length.
306-
cir::CopyOp createCopy(mlir::Value dst, mlir::Value src) {
307-
return cir::CopyOp::create(*this, dst.getLoc(), dst, src);
306+
cir::CopyOp createCopy(mlir::Value dst, mlir::Value src,
307+
bool isVolatile = false) {
308+
return cir::CopyOp::create(*this, dst.getLoc(), dst, src, isVolatile);
308309
}
309310

310311
cir::StoreOp createStore(mlir::Location loc, mlir::Value val, mlir::Value dst,

clang/include/clang/CIR/Dialect/IR/CIROps.td

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2719,6 +2719,8 @@ def CIR_CopyOp : CIR_Op<"copy",[
27192719
type of `src` and `dst` must match and both must implement the
27202720
`DataLayoutTypeInterface`.
27212721

2722+
The `volatile` keyword indicates that the operation is volatile.
2723+
27222724
Examples:
27232725

27242726
```mlir
@@ -2729,10 +2731,11 @@ def CIR_CopyOp : CIR_Op<"copy",[
27292731

27302732
let arguments = (ins
27312733
Arg<CIR_PointerType, "", [MemWrite]>:$dst,
2732-
Arg<CIR_PointerType, "", [MemRead]>:$src
2734+
Arg<CIR_PointerType, "", [MemRead]>:$src,
2735+
UnitAttr:$is_volatile
27332736
);
27342737

2735-
let assemblyFormat = [{$src `to` $dst
2738+
let assemblyFormat = [{$src `to` $dst (`volatile` $is_volatile^)?
27362739
attr-dict `:` qualified(type($dst))
27372740
}];
27382741
let hasVerifier = 1;

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,9 +170,10 @@ struct MissingFeatures {
170170
static bool atomicInfo() { return false; }
171171
static bool atomicInfoGetAtomicPointer() { return false; }
172172
static bool atomicInfoGetAtomicAddress() { return false; }
173-
static bool atomicUseLibCall() { return false; }
174173
static bool atomicScope() { return false; }
175174
static bool atomicSyncScopeID() { return false; }
175+
static bool atomicTypes() { return false; }
176+
static bool atomicUseLibCall() { return false; }
176177

177178
// Global ctor handling
178179
static bool globalCtorLexOrder() { return false; }

clang/lib/CIR/CodeGen/CIRGenDecl.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,16 @@ struct CallStackRestore final : EHScopeStack::Cleanup {
740740
};
741741
} // namespace
742742

743+
/// Push the standard destructor for the given type as
744+
/// at least a normal cleanup.
745+
void CIRGenFunction::pushDestroy(QualType::DestructionKind dtorKind,
746+
Address addr, QualType type) {
747+
assert(dtorKind && "cannot push destructor for trivial type");
748+
749+
CleanupKind cleanupKind = getCleanupKind(dtorKind);
750+
pushDestroy(cleanupKind, addr, type, getDestroyer(dtorKind));
751+
}
752+
743753
void CIRGenFunction::pushDestroy(CleanupKind cleanupKind, Address addr,
744754
QualType type, Destroyer *destroyer) {
745755
pushFullExprCleanup<DestroyObject>(cleanupKind, addr, type, destroyer);

clang/lib/CIR/CodeGen/CIRGenExpr.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1626,14 +1626,15 @@ LValue CIRGenFunction::emitBinaryOperatorLValue(const BinaryOperator *e) {
16261626

16271627
/// Emit code to compute the specified expression which
16281628
/// can have any type. The result is returned as an RValue struct.
1629-
RValue CIRGenFunction::emitAnyExpr(const Expr *e, AggValueSlot aggSlot) {
1629+
RValue CIRGenFunction::emitAnyExpr(const Expr *e, AggValueSlot aggSlot,
1630+
bool ignoreResult) {
16301631
switch (CIRGenFunction::getEvaluationKind(e->getType())) {
16311632
case cir::TEK_Scalar:
16321633
return RValue::get(emitScalarExpr(e));
16331634
case cir::TEK_Complex:
16341635
return RValue::getComplex(emitComplexExpr(e));
16351636
case cir::TEK_Aggregate: {
1636-
if (aggSlot.isIgnored())
1637+
if (!ignoreResult && aggSlot.isIgnored())
16371638
aggSlot = createAggTemp(e->getType(), getLoc(e->getSourceRange()),
16381639
getCounterAggTmpAsString());
16391640
emitAggExpr(e, aggSlot);
@@ -1869,8 +1870,7 @@ RValue CIRGenFunction::emitCallExpr(const clang::CallExpr *e,
18691870
/// Emit code to compute the specified expression, ignoring the result.
18701871
void CIRGenFunction::emitIgnoredExpr(const Expr *e) {
18711872
if (e->isPRValue()) {
1872-
assert(!cir::MissingFeatures::aggValueSlot());
1873-
emitAnyExpr(e);
1873+
emitAnyExpr(e, AggValueSlot::ignored(), true);
18741874
return;
18751875
}
18761876

clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp

Lines changed: 114 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,73 @@ using namespace clang;
2424
using namespace clang::CIRGen;
2525

2626
namespace {
27+
// FIXME(cir): This should be a common helper between CIRGen
28+
// and traditional CodeGen
29+
/// Is the value of the given expression possibly a reference to or
30+
/// into a __block variable?
31+
static bool isBlockVarRef(const Expr *e) {
32+
// Make sure we look through parens.
33+
e = e->IgnoreParens();
34+
35+
// Check for a direct reference to a __block variable.
36+
if (const DeclRefExpr *dre = dyn_cast<DeclRefExpr>(e)) {
37+
const VarDecl *var = dyn_cast<VarDecl>(dre->getDecl());
38+
return (var && var->hasAttr<BlocksAttr>());
39+
}
40+
41+
// More complicated stuff.
42+
43+
// Binary operators.
44+
if (const BinaryOperator *op = dyn_cast<BinaryOperator>(e)) {
45+
// For an assignment or pointer-to-member operation, just care
46+
// about the LHS.
47+
if (op->isAssignmentOp() || op->isPtrMemOp())
48+
return isBlockVarRef(op->getLHS());
49+
50+
// For a comma, just care about the RHS.
51+
if (op->getOpcode() == BO_Comma)
52+
return isBlockVarRef(op->getRHS());
53+
54+
// FIXME: pointer arithmetic?
55+
return false;
56+
57+
// Check both sides of a conditional operator.
58+
} else if (const AbstractConditionalOperator *op =
59+
dyn_cast<AbstractConditionalOperator>(e)) {
60+
return isBlockVarRef(op->getTrueExpr()) ||
61+
isBlockVarRef(op->getFalseExpr());
62+
63+
// OVEs are required to support BinaryConditionalOperators.
64+
} else if (const OpaqueValueExpr *op = dyn_cast<OpaqueValueExpr>(e)) {
65+
if (const Expr *src = op->getSourceExpr())
66+
return isBlockVarRef(src);
67+
68+
// Casts are necessary to get things like (*(int*)&var) = foo().
69+
// We don't really care about the kind of cast here, except
70+
// we don't want to look through l2r casts, because it's okay
71+
// to get the *value* in a __block variable.
72+
} else if (const CastExpr *cast = dyn_cast<CastExpr>(e)) {
73+
if (cast->getCastKind() == CK_LValueToRValue)
74+
return false;
75+
return isBlockVarRef(cast->getSubExpr());
76+
77+
// Handle unary operators. Again, just aggressively look through
78+
// it, ignoring the operation.
79+
} else if (const UnaryOperator *uop = dyn_cast<UnaryOperator>(e)) {
80+
return isBlockVarRef(uop->getSubExpr());
81+
82+
// Look into the base of a field access.
83+
} else if (const MemberExpr *mem = dyn_cast<MemberExpr>(e)) {
84+
return isBlockVarRef(mem->getBase());
85+
86+
// Look into the base of a subscript.
87+
} else if (const ArraySubscriptExpr *sub = dyn_cast<ArraySubscriptExpr>(e)) {
88+
return isBlockVarRef(sub->getBase());
89+
}
90+
91+
return false;
92+
}
93+
2794
class AggExprEmitter : public StmtVisitor<AggExprEmitter> {
2895

2996
CIRGenFunction &cgf;
@@ -41,9 +108,7 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> {
41108
AggValueSlot ensureSlot(mlir::Location loc, QualType t) {
42109
if (!dest.isIgnored())
43110
return dest;
44-
45-
cgf.cgm.errorNYI(loc, "Slot for ignored address");
46-
return dest;
111+
return cgf.createAggTemp(t, loc, "agg.tmp.ensured");
47112
}
48113

49114
void ensureDest(mlir::Location loc, QualType ty) {
@@ -89,6 +154,47 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> {
89154
(void)cgf.emitCompoundStmt(*e->getSubStmt(), &retAlloca, dest);
90155
}
91156

157+
void VisitBinAssign(const BinaryOperator *e) {
158+
// For an assignment to work, the value on the right has
159+
// to be compatible with the value on the left.
160+
assert(cgf.getContext().hasSameUnqualifiedType(e->getLHS()->getType(),
161+
e->getRHS()->getType()) &&
162+
"Invalid assignment");
163+
164+
if (isBlockVarRef(e->getLHS()) &&
165+
e->getRHS()->HasSideEffects(cgf.getContext())) {
166+
cgf.cgm.errorNYI(e->getSourceRange(),
167+
"block var reference with side effects");
168+
return;
169+
}
170+
171+
LValue lhs = cgf.emitLValue(e->getLHS());
172+
173+
// If we have an atomic type, evaluate into the destination and then
174+
// do an atomic copy.
175+
assert(!cir::MissingFeatures::atomicTypes());
176+
177+
// Codegen the RHS so that it stores directly into the LHS.
178+
assert(!cir::MissingFeatures::aggValueSlotGC());
179+
AggValueSlot lhsSlot = AggValueSlot::forLValue(
180+
lhs, AggValueSlot::IsDestructed, AggValueSlot::IsAliased,
181+
AggValueSlot::MayOverlap);
182+
183+
// A non-volatile aggregate destination might have volatile member.
184+
if (!lhsSlot.isVolatile() && cgf.hasVolatileMember(e->getLHS()->getType()))
185+
lhsSlot.setVolatile(true);
186+
187+
cgf.emitAggExpr(e->getRHS(), lhsSlot);
188+
189+
// Copy into the destination if the assignment isn't ignored.
190+
emitFinalDestCopy(e->getType(), lhs);
191+
192+
if (!dest.isIgnored() && !dest.isExternallyDestructed() &&
193+
e->getType().isDestructedType() == QualType::DK_nontrivial_c_struct)
194+
cgf.pushDestroy(QualType::DK_nontrivial_c_struct, dest.getAddress(),
195+
e->getType());
196+
}
197+
92198
void VisitDeclRefExpr(DeclRefExpr *e) { emitAggLoadOfLValue(e); }
93199

94200
void VisitInitListExpr(InitListExpr *e);
@@ -195,9 +301,6 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> {
195301
cgf.cgm.errorNYI(e->getSourceRange(),
196302
"AggExprEmitter: VisitPointerToDataMemberBinaryOperator");
197303
}
198-
void VisitBinAssign(const BinaryOperator *e) {
199-
cgf.cgm.errorNYI(e->getSourceRange(), "AggExprEmitter: VisitBinAssign");
200-
}
201304
void VisitBinComma(const BinaryOperator *e) {
202305
cgf.emitIgnoredExpr(e->getLHS());
203306
Visit(e->getRHS());
@@ -487,7 +590,8 @@ void AggExprEmitter::emitCopy(QualType type, const AggValueSlot &dest,
487590
LValue destLV = cgf.makeAddrLValue(dest.getAddress(), type);
488591
LValue srcLV = cgf.makeAddrLValue(src.getAddress(), type);
489592
assert(!cir::MissingFeatures::aggValueSlotVolatile());
490-
cgf.emitAggregateCopy(destLV, srcLV, type, dest.mayOverlap());
593+
cgf.emitAggregateCopy(destLV, srcLV, type, dest.mayOverlap(),
594+
dest.isVolatile() || src.isVolatile());
491595
}
492596

493597
void AggExprEmitter::emitInitializationToLValue(Expr *e, LValue lv) {
@@ -788,7 +892,8 @@ void CIRGenFunction::emitAggExpr(const Expr *e, AggValueSlot slot) {
788892
}
789893

790894
void CIRGenFunction::emitAggregateCopy(LValue dest, LValue src, QualType ty,
791-
AggValueSlot::Overlap_t mayOverlap) {
895+
AggValueSlot::Overlap_t mayOverlap,
896+
bool isVolatile) {
792897
// TODO(cir): this function needs improvements, commented code for now since
793898
// this will be touched again soon.
794899
assert(!ty->isAnyComplexType() && "Unexpected copy of complex");
@@ -844,7 +949,7 @@ void CIRGenFunction::emitAggregateCopy(LValue dest, LValue src, QualType ty,
844949
cgm.errorNYI("emitAggregateCopy: GC");
845950

846951
[[maybe_unused]] cir::CopyOp copyOp =
847-
builder.createCopy(destPtr.getPointer(), srcPtr.getPointer());
952+
builder.createCopy(destPtr.getPointer(), srcPtr.getPointer(), isVolatile);
848953

849954
assert(!cir::MissingFeatures::opTBAA());
850955
}

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,14 @@ class CIRGenFunction : public CIRGenTypeCache {
853853
FunctionArgList args, clang::SourceLocation loc,
854854
clang::SourceLocation startLoc);
855855

856+
/// returns true if aggregate type has a volatile member.
857+
/// TODO(cir): this could be a common AST helper between LLVM / CIR.
858+
bool hasVolatileMember(QualType t) {
859+
if (const auto *rd = t->getAsRecordDecl())
860+
return rd->hasVolatileMember();
861+
return false;
862+
}
863+
856864
/// The cleanup depth enclosing all the cleanups associated with the
857865
/// parameters.
858866
EHScopeStack::stable_iterator prologueCleanupDepth;
@@ -1077,6 +1085,9 @@ class CIRGenFunction : public CIRGenTypeCache {
10771085

10781086
static Destroyer destroyCXXObject;
10791087

1088+
void pushDestroy(QualType::DestructionKind dtorKind, Address addr,
1089+
QualType type);
1090+
10801091
void pushDestroy(CleanupKind kind, Address addr, QualType type,
10811092
Destroyer *destroyer);
10821093

@@ -1131,14 +1142,16 @@ class CIRGenFunction : public CIRGenTypeCache {
11311142
/// occupied by some other object. More efficient code can often be
11321143
/// generated if not.
11331144
void emitAggregateCopy(LValue dest, LValue src, QualType eltTy,
1134-
AggValueSlot::Overlap_t mayOverlap);
1145+
AggValueSlot::Overlap_t mayOverlap,
1146+
bool isVolatile = false);
11351147

11361148
/// Emit code to compute the specified expression which can have any type. The
11371149
/// result is returned as an RValue struct. If this is an aggregate
11381150
/// expression, the aggloc/agglocvolatile arguments indicate where the result
11391151
/// should be returned.
11401152
RValue emitAnyExpr(const clang::Expr *e,
1141-
AggValueSlot aggSlot = AggValueSlot::ignored());
1153+
AggValueSlot aggSlot = AggValueSlot::ignored(),
1154+
bool ignoreResult = false);
11421155

11431156
/// Emits the code necessary to evaluate an arbitrary expression into the
11441157
/// given memory location.

clang/lib/CIR/CodeGen/CIRGenValue.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,15 @@ class AggValueSlot {
380380

381381
clang::Qualifiers getQualifiers() const { return quals; }
382382

383+
bool isVolatile() const { return quals.hasVolatile(); }
384+
385+
void setVolatile(bool flag) {
386+
if (flag)
387+
quals.addVolatile();
388+
else
389+
quals.removeVolatile();
390+
}
391+
383392
Address getAddress() const { return addr; }
384393

385394
bool isIgnored() const { return !addr.isValid(); }

clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ mlir::LogicalResult CIRToLLVMCopyOpLowering::matchAndRewrite(
182182
rewriter, op.getLoc(), rewriter.getI32Type(), op.getLength(layout));
183183
assert(!cir::MissingFeatures::aggValueSlotVolatile());
184184
rewriter.replaceOpWithNewOp<mlir::LLVM::MemcpyOp>(
185-
op, adaptor.getDst(), adaptor.getSrc(), length, /*isVolatile=*/false);
185+
op, adaptor.getDst(), adaptor.getSrc(), length, op.getIsVolatile());
186186
return mlir::success();
187187
}
188188

clang/test/CIR/CodeGen/binassign.c

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %clang_cc1 -std=c23 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
1+
// RUN: %clang_cc1 -std=c23 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir -mmlir --mlir-print-ir-before=cir-lowering-prepare %s -o %t.cir 2> %t-before-lp.cir
22
// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
33
// RUN: %clang_cc1 -std=c23 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-llvm %s -o %t-cir.ll
44
// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
@@ -54,3 +54,49 @@ void binary_assign(void) {
5454
// OGCG: store float 0x40091EB860000000, ptr %[[F_PTR]]
5555
// OGCG: store i32 42, ptr %[[I_PTR]]
5656
// OGCG: ret void
57+
58+
struct S {
59+
int a;
60+
float b;
61+
};
62+
63+
struct SV {
64+
int a;
65+
volatile float b;
66+
};
67+
68+
struct S gs;
69+
struct SV gsv;
70+
71+
void binary_assign_struct() {
72+
// Test normal struct assignment
73+
struct S ls;
74+
ls = gs;
75+
76+
// Test assignment of a struct with a volatile member
77+
struct SV lsv;
78+
lsv = gsv;
79+
}
80+
81+
// CIR: cir.func{{.*}} @binary_assign_struct() {
82+
// CIR: %[[LS:.*]] = cir.alloca ![[REC_S:.*]], !cir.ptr<![[REC_S]]>, ["ls"]
83+
// CIR: %[[LSV:.*]] = cir.alloca ![[REC_SV:.*]], !cir.ptr<![[REC_SV]]>, ["lsv"]
84+
// CIR: %[[GS_PTR:.*]] = cir.get_global @gs : !cir.ptr<![[REC_S]]>
85+
// CIR: cir.copy %[[GS_PTR]] to %[[LS]] : !cir.ptr<![[REC_S]]>
86+
// CIR: %[[GSV_PTR:.*]] = cir.get_global @gsv : !cir.ptr<![[REC_SV]]>
87+
// CIR: cir.copy %[[GSV_PTR]] to %[[LSV]] volatile : !cir.ptr<![[REC_SV]]>
88+
// CIR: cir.return
89+
90+
// LLVM: define {{.*}}void @binary_assign_struct() {
91+
// LLVM: %[[LS_PTR:.*]] = alloca %struct.S
92+
// LLVM: %[[LSV_PTR:.*]] = alloca %struct.SV
93+
// LLVM: call void @llvm.memcpy.p0.p0.i32(ptr %[[LS_PTR]], ptr @gs, i32 8, i1 false)
94+
// LLVM: call void @llvm.memcpy.p0.p0.i32(ptr %[[LSV_PTR]], ptr @gsv, i32 8, i1 true)
95+
// LLVM: ret void
96+
97+
// OGCG: define {{.*}}void @binary_assign_struct()
98+
// OGCG: %[[LS_PTR:.*]] = alloca %struct.S
99+
// OGCG: %[[LSV_PTR:.*]] = alloca %struct.SV
100+
// OGCG: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %[[LS_PTR]], ptr align 4 @gs, i64 8, i1 false)
101+
// OGCG: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %[[LSV_PTR]], ptr align 4 @gsv, i64 8, i1 true)
102+
// OGCG: ret void

0 commit comments

Comments
 (0)