Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,9 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
}

/// Create a copy with inferred length.
cir::CopyOp createCopy(mlir::Value dst, mlir::Value src) {
return cir::CopyOp::create(*this, dst.getLoc(), dst, src);
cir::CopyOp createCopy(mlir::Value dst, mlir::Value src,
bool isVolatile = false) {
return cir::CopyOp::create(*this, dst.getLoc(), dst, src, isVolatile);
}

cir::StoreOp createStore(mlir::Location loc, mlir::Value val, mlir::Value dst,
Expand Down
7 changes: 5 additions & 2 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -2724,6 +2724,8 @@ def CIR_CopyOp : CIR_Op<"copy",[
type of `src` and `dst` must match and both must implement the
`DataLayoutTypeInterface`.

The `volatile` keyword indicates that the operation is volatile.

Examples:

```mlir
Expand All @@ -2734,10 +2736,11 @@ def CIR_CopyOp : CIR_Op<"copy",[

let arguments = (ins
Arg<CIR_PointerType, "", [MemWrite]>:$dst,
Arg<CIR_PointerType, "", [MemRead]>:$src
Arg<CIR_PointerType, "", [MemRead]>:$src,
UnitAttr:$is_volatile
);

let assemblyFormat = [{$src `to` $dst
let assemblyFormat = [{$src `to` $dst (`volatile` $is_volatile^)?
attr-dict `:` qualified(type($dst))
}];
let hasVerifier = 1;
Expand Down
3 changes: 2 additions & 1 deletion clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,10 @@ struct MissingFeatures {
static bool atomicInfo() { return false; }
static bool atomicInfoGetAtomicPointer() { return false; }
static bool atomicInfoGetAtomicAddress() { return false; }
static bool atomicUseLibCall() { return false; }
static bool atomicScope() { return false; }
static bool atomicSyncScopeID() { return false; }
static bool atomicTypes() { return false; }
static bool atomicUseLibCall() { return false; }

// Global ctor handling
static bool globalCtorLexOrder() { return false; }
Expand Down
10 changes: 10 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -740,6 +740,16 @@ struct CallStackRestore final : EHScopeStack::Cleanup {
};
} // namespace

/// Push the standard destructor for the given type as
/// at least a normal cleanup.
void CIRGenFunction::pushDestroy(QualType::DestructionKind dtorKind,
Address addr, QualType type) {
assert(dtorKind && "cannot push destructor for trivial type");

CleanupKind cleanupKind = getCleanupKind(dtorKind);
pushDestroy(cleanupKind, addr, type, getDestroyer(dtorKind));
}

void CIRGenFunction::pushDestroy(CleanupKind cleanupKind, Address addr,
QualType type, Destroyer *destroyer) {
pushFullExprCleanup<DestroyObject>(cleanupKind, addr, type, destroyer);
Expand Down
8 changes: 4 additions & 4 deletions clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1626,14 +1626,15 @@ LValue CIRGenFunction::emitBinaryOperatorLValue(const BinaryOperator *e) {

/// Emit code to compute the specified expression which
/// can have any type. The result is returned as an RValue struct.
RValue CIRGenFunction::emitAnyExpr(const Expr *e, AggValueSlot aggSlot) {
RValue CIRGenFunction::emitAnyExpr(const Expr *e, AggValueSlot aggSlot,
bool ignoreResult) {
switch (CIRGenFunction::getEvaluationKind(e->getType())) {
case cir::TEK_Scalar:
return RValue::get(emitScalarExpr(e));
case cir::TEK_Complex:
return RValue::getComplex(emitComplexExpr(e));
case cir::TEK_Aggregate: {
if (aggSlot.isIgnored())
if (!ignoreResult && aggSlot.isIgnored())
aggSlot = createAggTemp(e->getType(), getLoc(e->getSourceRange()),
getCounterAggTmpAsString());
emitAggExpr(e, aggSlot);
Expand Down Expand Up @@ -1869,8 +1870,7 @@ RValue CIRGenFunction::emitCallExpr(const clang::CallExpr *e,
/// Emit code to compute the specified expression, ignoring the result.
void CIRGenFunction::emitIgnoredExpr(const Expr *e) {
if (e->isPRValue()) {
assert(!cir::MissingFeatures::aggValueSlot());
emitAnyExpr(e);
emitAnyExpr(e, AggValueSlot::ignored(), /*ignoreResult=*/true);
return;
}

Expand Down
123 changes: 114 additions & 9 deletions clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,73 @@ using namespace clang;
using namespace clang::CIRGen;

namespace {
// FIXME(cir): This should be a common helper between CIRGen
// and traditional CodeGen
/// Is the value of the given expression possibly a reference to or
/// into a __block variable?
static bool isBlockVarRef(const Expr *e) {
// Make sure we look through parens.
e = e->IgnoreParens();

// Check for a direct reference to a __block variable.
if (const DeclRefExpr *dre = dyn_cast<DeclRefExpr>(e)) {
const VarDecl *var = dyn_cast<VarDecl>(dre->getDecl());
return (var && var->hasAttr<BlocksAttr>());
}

// More complicated stuff.

// Binary operators.
if (const BinaryOperator *op = dyn_cast<BinaryOperator>(e)) {
// For an assignment or pointer-to-member operation, just care
// about the LHS.
if (op->isAssignmentOp() || op->isPtrMemOp())
return isBlockVarRef(op->getLHS());

// For a comma, just care about the RHS.
if (op->getOpcode() == BO_Comma)
return isBlockVarRef(op->getRHS());

// FIXME: pointer arithmetic?
return false;

// Check both sides of a conditional operator.
} else if (const AbstractConditionalOperator *op =
dyn_cast<AbstractConditionalOperator>(e)) {
return isBlockVarRef(op->getTrueExpr()) ||
isBlockVarRef(op->getFalseExpr());

// OVEs are required to support BinaryConditionalOperators.
} else if (const OpaqueValueExpr *op = dyn_cast<OpaqueValueExpr>(e)) {
if (const Expr *src = op->getSourceExpr())
return isBlockVarRef(src);

// Casts are necessary to get things like (*(int*)&var) = foo().
// We don't really care about the kind of cast here, except
// we don't want to look through l2r casts, because it's okay
// to get the *value* in a __block variable.
} else if (const CastExpr *cast = dyn_cast<CastExpr>(e)) {
if (cast->getCastKind() == CK_LValueToRValue)
return false;
return isBlockVarRef(cast->getSubExpr());

// Handle unary operators. Again, just aggressively look through
// it, ignoring the operation.
} else if (const UnaryOperator *uop = dyn_cast<UnaryOperator>(e)) {
return isBlockVarRef(uop->getSubExpr());

// Look into the base of a field access.
} else if (const MemberExpr *mem = dyn_cast<MemberExpr>(e)) {
return isBlockVarRef(mem->getBase());

// Look into the base of a subscript.
} else if (const ArraySubscriptExpr *sub = dyn_cast<ArraySubscriptExpr>(e)) {
return isBlockVarRef(sub->getBase());
}

return false;
}

class AggExprEmitter : public StmtVisitor<AggExprEmitter> {

CIRGenFunction &cgf;
Expand All @@ -41,9 +108,7 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> {
AggValueSlot ensureSlot(mlir::Location loc, QualType t) {
if (!dest.isIgnored())
return dest;

cgf.cgm.errorNYI(loc, "Slot for ignored address");
return dest;
return cgf.createAggTemp(t, loc, "agg.tmp.ensured");
}

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

void VisitBinAssign(const BinaryOperator *e) {
// For an assignment to work, the value on the right has
// to be compatible with the value on the left.
assert(cgf.getContext().hasSameUnqualifiedType(e->getLHS()->getType(),
e->getRHS()->getType()) &&
"Invalid assignment");

if (isBlockVarRef(e->getLHS()) &&
e->getRHS()->HasSideEffects(cgf.getContext())) {
cgf.cgm.errorNYI(e->getSourceRange(),
"block var reference with side effects");
return;
}

LValue lhs = cgf.emitLValue(e->getLHS());

// If we have an atomic type, evaluate into the destination and then
// do an atomic copy.
assert(!cir::MissingFeatures::atomicTypes());

// Codegen the RHS so that it stores directly into the LHS.
assert(!cir::MissingFeatures::aggValueSlotGC());
AggValueSlot lhsSlot = AggValueSlot::forLValue(
lhs, AggValueSlot::IsDestructed, AggValueSlot::IsAliased,
AggValueSlot::MayOverlap);

// A non-volatile aggregate destination might have volatile member.
if (!lhsSlot.isVolatile() && cgf.hasVolatileMember(e->getLHS()->getType()))
lhsSlot.setVolatile(true);

cgf.emitAggExpr(e->getRHS(), lhsSlot);

// Copy into the destination if the assignment isn't ignored.
emitFinalDestCopy(e->getType(), lhs);

if (!dest.isIgnored() && !dest.isExternallyDestructed() &&
e->getType().isDestructedType() == QualType::DK_nontrivial_c_struct)
cgf.pushDestroy(QualType::DK_nontrivial_c_struct, dest.getAddress(),
e->getType());
}

void VisitDeclRefExpr(DeclRefExpr *e) { emitAggLoadOfLValue(e); }

void VisitInitListExpr(InitListExpr *e);
Expand Down Expand Up @@ -195,9 +301,6 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> {
cgf.cgm.errorNYI(e->getSourceRange(),
"AggExprEmitter: VisitPointerToDataMemberBinaryOperator");
}
void VisitBinAssign(const BinaryOperator *e) {
cgf.cgm.errorNYI(e->getSourceRange(), "AggExprEmitter: VisitBinAssign");
}
void VisitBinComma(const BinaryOperator *e) {
cgf.emitIgnoredExpr(e->getLHS());
Visit(e->getRHS());
Expand Down Expand Up @@ -487,7 +590,8 @@ void AggExprEmitter::emitCopy(QualType type, const AggValueSlot &dest,
LValue destLV = cgf.makeAddrLValue(dest.getAddress(), type);
LValue srcLV = cgf.makeAddrLValue(src.getAddress(), type);
assert(!cir::MissingFeatures::aggValueSlotVolatile());
cgf.emitAggregateCopy(destLV, srcLV, type, dest.mayOverlap());
cgf.emitAggregateCopy(destLV, srcLV, type, dest.mayOverlap(),
dest.isVolatile() || src.isVolatile());
}

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

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

[[maybe_unused]] cir::CopyOp copyOp =
builder.createCopy(destPtr.getPointer(), srcPtr.getPointer());
builder.createCopy(destPtr.getPointer(), srcPtr.getPointer(), isVolatile);

assert(!cir::MissingFeatures::opTBAA());
}
Expand Down
16 changes: 14 additions & 2 deletions clang/lib/CIR/CodeGen/CIRGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,13 @@ class CIRGenFunction : public CIRGenTypeCache {
FunctionArgList args, clang::SourceLocation loc,
clang::SourceLocation startLoc);

/// returns true if aggregate type has a volatile member.
bool hasVolatileMember(QualType t) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a good opportunity to share this one with OG?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that really makes sense. The logical place to put it would be in QualType, but it is only meaningful if this is a record type. In theory we could move the getAsRecordDecl check inside QualType, but that doesn't feel right. I'm inclined to just remove the comment about sharing.

if (const auto *rd = t->getAsRecordDecl())
return rd->hasVolatileMember();
return false;
}

/// The cleanup depth enclosing all the cleanups associated with the
/// parameters.
EHScopeStack::stable_iterator prologueCleanupDepth;
Expand Down Expand Up @@ -1077,6 +1084,9 @@ class CIRGenFunction : public CIRGenTypeCache {

static Destroyer destroyCXXObject;

void pushDestroy(QualType::DestructionKind dtorKind, Address addr,
QualType type);

void pushDestroy(CleanupKind kind, Address addr, QualType type,
Destroyer *destroyer);

Expand Down Expand Up @@ -1131,14 +1141,16 @@ class CIRGenFunction : public CIRGenTypeCache {
/// occupied by some other object. More efficient code can often be
/// generated if not.
void emitAggregateCopy(LValue dest, LValue src, QualType eltTy,
AggValueSlot::Overlap_t mayOverlap);
AggValueSlot::Overlap_t mayOverlap,
bool isVolatile = false);

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

/// Emits the code necessary to evaluate an arbitrary expression into the
/// given memory location.
Expand Down
9 changes: 9 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenValue.h
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,15 @@ class AggValueSlot {

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

bool isVolatile() const { return quals.hasVolatile(); }

void setVolatile(bool flag) {
if (flag)
quals.addVolatile();
else
quals.removeVolatile();
}

Address getAddress() const { return addr; }

bool isIgnored() const { return !addr.isValid(); }
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ mlir::LogicalResult CIRToLLVMCopyOpLowering::matchAndRewrite(
rewriter, op.getLoc(), rewriter.getI32Type(), op.getLength(layout));
assert(!cir::MissingFeatures::aggValueSlotVolatile());
rewriter.replaceOpWithNewOp<mlir::LLVM::MemcpyOp>(
op, adaptor.getDst(), adaptor.getSrc(), length, /*isVolatile=*/false);
op, adaptor.getDst(), adaptor.getSrc(), length, op.getIsVolatile());
return mlir::success();
}

Expand Down
48 changes: 47 additions & 1 deletion clang/test/CIR/CodeGen/binassign.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %clang_cc1 -std=c23 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
// 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
// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
// RUN: %clang_cc1 -std=c23 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-llvm %s -o %t-cir.ll
// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
Expand Down Expand Up @@ -54,3 +54,49 @@ void binary_assign(void) {
// OGCG: store float 0x40091EB860000000, ptr %[[F_PTR]]
// OGCG: store i32 42, ptr %[[I_PTR]]
// OGCG: ret void

struct S {
int a;
float b;
};

struct SV {
int a;
volatile float b;
};

struct S gs;
struct SV gsv;

void binary_assign_struct() {
// Test normal struct assignment
struct S ls;
ls = gs;

// Test assignment of a struct with a volatile member
struct SV lsv;
lsv = gsv;
}

// CIR: cir.func{{.*}} @binary_assign_struct()
// CIR: %[[LS:.*]] = cir.alloca ![[REC_S:.*]], !cir.ptr<![[REC_S]]>, ["ls"]
// CIR: %[[LSV:.*]] = cir.alloca ![[REC_SV:.*]], !cir.ptr<![[REC_SV]]>, ["lsv"]
// CIR: %[[GS_PTR:.*]] = cir.get_global @gs : !cir.ptr<![[REC_S]]>
// CIR: cir.copy %[[GS_PTR]] to %[[LS]] : !cir.ptr<![[REC_S]]>
// CIR: %[[GSV_PTR:.*]] = cir.get_global @gsv : !cir.ptr<![[REC_SV]]>
// CIR: cir.copy %[[GSV_PTR]] to %[[LSV]] volatile : !cir.ptr<![[REC_SV]]>
// CIR: cir.return

// LLVM: define {{.*}}void @binary_assign_struct()
// LLVM: %[[LS_PTR:.*]] = alloca %struct.S
// LLVM: %[[LSV_PTR:.*]] = alloca %struct.SV
// LLVM: call void @llvm.memcpy.p0.p0.i32(ptr %[[LS_PTR]], ptr @gs, i32 8, i1 false)
// LLVM: call void @llvm.memcpy.p0.p0.i32(ptr %[[LSV_PTR]], ptr @gsv, i32 8, i1 true)
// LLVM: ret void

// OGCG: define {{.*}}void @binary_assign_struct()
// OGCG: %[[LS_PTR:.*]] = alloca %struct.S
// OGCG: %[[LSV_PTR:.*]] = alloca %struct.SV
// OGCG: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %[[LS_PTR]], ptr align 4 @gs, i64 8, i1 false)
// OGCG: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %[[LSV_PTR]], ptr align 4 @gsv, i64 8, i1 true)
// OGCG: ret void
2 changes: 1 addition & 1 deletion clang/test/CIR/CodeGen/dtors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ void test_temporary_dtor() {
}

// CIR: cir.func dso_local @_Z19test_temporary_dtorv()
// CIR: %[[ALLOCA:.*]] = cir.alloca !rec_A, !cir.ptr<!rec_A>, ["agg.tmp0"]
// CIR: %[[ALLOCA:.*]] = cir.alloca !rec_A, !cir.ptr<!rec_A>, ["agg.tmp.ensured"]
// CIR: cir.call @_ZN1AD1Ev(%[[ALLOCA]]) nothrow : (!cir.ptr<!rec_A>) -> ()

// LLVM: define dso_local void @_Z19test_temporary_dtorv(){{.*}}
Expand Down
Loading