Skip to content

Commit

Permalink
[CIR][CIRGen] Partially support statement expressions return values
Browse files Browse the repository at this point in the history
Adds support for GCC statement expressions return values as well as
StmtExpr LValue emissions.

To simplify the lowering process, the scope return value is not used.
Instead, a temporary allocation is created on the parent scope where the
return value is stored. For classes, a second scope is created around
this temporary allocation to ensure any destructors are called.

This does not implement the full semantics of statement expressions.

ghstack-source-id: 64e03fc3df45975590ddbcab44959c2b49601101
Pull Request resolved: #314
  • Loading branch information
sitio-couto authored and lanza committed Jan 10, 2024
1 parent d744421 commit 50d414a
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 8 deletions.
7 changes: 7 additions & 0 deletions clang/lib/CIR/CodeGen/Address.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,13 @@ class Address {
PointerAndKnownNonNull.setInt(true);
return *this;
}

/// Get the operation which defines this address.
mlir::Operation *getDefiningOp() const {
if (!isValid())
return nullptr;
return getPointer().getDefiningOp();
}
};

} // namespace cir
Expand Down
12 changes: 12 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,18 @@ class CIRGenBuilderTy : public CIRBaseBuilderTy {
mlir::Value v) {
return create<mlir::cir::StackRestoreOp>(loc, v);
}

// TODO(cir): Change this to hoist alloca to the parent *scope* instead.
/// Move alloca operation to the parent region.
void hoistAllocaToParentRegion(mlir::cir::AllocaOp alloca) {
auto &block = alloca->getParentOp()->getParentRegion()->front();
const auto allocas = block.getOps<mlir::cir::AllocaOp>();
if (allocas.empty()) {
alloca->moveBefore(&block, block.begin());
} else {
alloca->moveAfter(*std::prev(allocas.end()));
}
}
};

} // namespace cir
Expand Down
9 changes: 9 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,13 @@ RValue CIRGenFunction::buildCallExpr(const clang::CallExpr *E,
return buildCall(E->getCallee()->getType(), callee, E, ReturnValue);
}

LValue CIRGenFunction::buildStmtExprLValue(const StmtExpr *E) {
// Can only get l-value for message expression returning aggregate type
RValue RV = buildAnyExprToTemp(E);
return makeAddrLValue(RV.getAggregateAddress(), E->getType(),
AlignmentSource::Decl);
}

RValue CIRGenFunction::buildCall(clang::QualType CalleeType,
const CIRGenCallee &OrigCallee,
const clang::CallExpr *E,
Expand Down Expand Up @@ -2165,6 +2172,8 @@ LValue CIRGenFunction::buildLValue(const Expr *E) {

case Expr::ObjCPropertyRefExprClass:
llvm_unreachable("cannot emit a property reference directly");
case Expr::StmtExprClass:
return buildStmtExprLValue(cast<StmtExpr>(E));
}

return LValue::makeAddr(Address::invalid(), E->getType());
Expand Down
7 changes: 6 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenExprAgg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,12 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> {
// Operators.
void VisitCastExpr(CastExpr *E);
void VisitCallExpr(const CallExpr *E);
void VisitStmtExpr(const StmtExpr *E) { llvm_unreachable("NYI"); }

void VisitStmtExpr(const StmtExpr *E) {
assert(!UnimplementedFeature::stmtExprEvaluation() && "NYI");
CGF.buildCompoundStmt(*E->getSubStmt(), /*getLast=*/true, Dest);
}

void VisitBinaryOperator(const BinaryOperator *E) { llvm_unreachable("NYI"); }
void VisitPointerToDataMemberBinaryOperator(const BinaryOperator *E) {
llvm_unreachable("NYI");
Expand Down
20 changes: 19 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
//
//===----------------------------------------------------------------------===//

#include "Address.h"
#include "CIRDataLayout.h"
#include "CIRGenFunction.h"
#include "CIRGenModule.h"
Expand Down Expand Up @@ -291,7 +292,24 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
}
mlir::Value VisitCastExpr(CastExpr *E);
mlir::Value VisitCallExpr(const CallExpr *E);
mlir::Value VisitStmtExpr(StmtExpr *E) { llvm_unreachable("NYI"); }

mlir::Value VisitStmtExpr(StmtExpr *E) {
assert(!UnimplementedFeature::stmtExprEvaluation() && "NYI");
Address retAlloca =
CGF.buildCompoundStmt(*E->getSubStmt(), !E->getType()->isVoidType());
if (!retAlloca.isValid())
return {};

// FIXME(cir): This is a work around the ScopeOp builder. If we build the
// ScopeOp before its body, we would be able to create the retAlloca
// direclty in the parent scope removing the need to hoist it.
assert(retAlloca.getDefiningOp() && "expected a alloca op");
CGF.getBuilder().hoistAllocaToParentRegion(
cast<mlir::cir::AllocaOp>(retAlloca.getDefiningOp()));

return CGF.buildLoadOfScalar(CGF.makeAddrLValue(retAlloca, E->getType()),
E->getExprLoc());
}

// Unary Operators.
mlir::Value VisitUnaryPostDec(const UnaryOperator *E) {
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,8 @@ class CIRGenFunction : public CIRGenTypeCache {
void checkTargetFeatures(const CallExpr *E, const FunctionDecl *TargetDecl);
void checkTargetFeatures(SourceLocation Loc, const FunctionDecl *TargetDecl);

LValue buildStmtExprLValue(const StmtExpr *E);

/// Generate a call of the given function, expecting the given
/// result type, and using the given argument list which specifies both the
/// LLVM arguments and the types they were derived from.
Expand Down
47 changes: 41 additions & 6 deletions clang/lib/CIR/CodeGen/CIRGenStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
#include "Address.h"
#include "CIRGenFunction.h"
#include "mlir/IR/Value.h"
#include "clang/AST/CharUnits.h"
#include "clang/AST/Stmt.h"
#include "clang/CIR/Dialect/IR/CIRTypes.h"
#include "llvm/Support/ErrorHandling.h"

using namespace cir;
using namespace clang;
Expand All @@ -21,11 +25,42 @@ using namespace mlir::cir;
Address CIRGenFunction::buildCompoundStmtWithoutScope(const CompoundStmt &S,
bool getLast,
AggValueSlot slot) {
for (auto *CurStmt : S.body())
if (buildStmt(CurStmt, /*useCurrentScope=*/false).failed())
return Address::invalid();
const Stmt *ExprResult = S.getStmtExprResult();
assert((!getLast || (getLast && ExprResult)) &&
"If getLast is true then the CompoundStmt must have a StmtExprResult");

return Address::invalid();
Address retAlloca = Address::invalid();

for (auto *CurStmt : S.body()) {
if (getLast && ExprResult == CurStmt) {
while (!isa<Expr>(ExprResult)) {
if (const auto *LS = dyn_cast<LabelStmt>(ExprResult))
llvm_unreachable("labels are NYI");
else if (const auto *AS = dyn_cast<AttributedStmt>(ExprResult))
llvm_unreachable("statement attributes are NYI");
else
llvm_unreachable("Unknown value statement");
}

const Expr *E = cast<Expr>(ExprResult);
QualType exprTy = E->getType();
if (hasAggregateEvaluationKind(exprTy)) {
buildAggExpr(E, slot);
} else {
// We can't return an RValue here because there might be cleanups at
// the end of the StmtExpr. Because of that, we have to emit the result
// here into a temporary alloca.
retAlloca = CreateMemTemp(exprTy, getLoc(E->getSourceRange()));
buildAnyExprToMem(E, retAlloca, Qualifiers(),
/*IsInit*/ false);
}
} else {
if (buildStmt(CurStmt, /*useCurrentScope=*/false).failed())
llvm_unreachable("failed to build statement");
}
}

return retAlloca;
}

Address CIRGenFunction::buildCompoundStmt(const CompoundStmt &S, bool getLast,
Expand All @@ -37,9 +72,9 @@ Address CIRGenFunction::buildCompoundStmt(const CompoundStmt &S, bool getLast,
auto scopeLoc = getLoc(S.getSourceRange());
builder.create<mlir::cir::ScopeOp>(
scopeLoc, /*scopeBuilder=*/
[&](mlir::OpBuilder &b, mlir::Location loc) {
[&](mlir::OpBuilder &b, mlir::Type &type, mlir::Location loc) {
LexicalScope lexScope{*this, loc, builder.getInsertionBlock()};
retAlloca = buildCompoundStmtWithoutScope(S);
retAlloca = buildCompoundStmtWithoutScope(S, getLast, slot);
});

return retAlloca;
Expand Down
1 change: 1 addition & 0 deletions clang/lib/CIR/CodeGen/UnimplementedFeatureGuarding.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ struct UnimplementedFeature {
static bool metaDataNode() { return false; }
static bool isSEHTryScope() { return false; }
static bool emitScalarRangeCheck() { return false; }
static bool stmtExprEvaluation() { return false; }
};
} // namespace cir

Expand Down
42 changes: 42 additions & 0 deletions clang/test/CIR/CodeGen/stmt-expr.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
// RUN: FileCheck --input-file=%t.cir %s

// Yields void.
void test1() { ({ }); }
// CHECK: @test1
// CHECK: cir.scope {
// CHECK-NOT: cir.yield
// CHECK: }

// Yields an out-of-scope scalar.
void test2() { ({int x = 3; x; }); }
// CHECK: @test2
// CHECK: %[[#RETVAL:]] = cir.alloca !s32i, cir.ptr <!s32i>
// CHECK: cir.scope {
// CHECK: %[[#VAR:]] = cir.alloca !s32i, cir.ptr <!s32i>, ["x", init]
// [...]
// CHECK: %[[#TMP:]] = cir.load %[[#VAR]] : cir.ptr <!s32i>, !s32i
// CHECK: cir.store %[[#TMP]], %[[#RETVAL]] : !s32i, cir.ptr <!s32i>
// CHECK: }
// CHECK: %{{.+}} = cir.load %[[#RETVAL]] : cir.ptr <!s32i>, !s32i

// Yields an aggregate.
struct S { int x; };
int test3() { return ({ struct S s = {1}; s; }).x; }
// CHECK: @test3
// CHECK: %[[#RETVAL:]] = cir.alloca !ty_22S22, cir.ptr <!ty_22S22>
// CHECK: cir.scope {
// CHECK: %[[#VAR:]] = cir.alloca !ty_22S22, cir.ptr <!ty_22S22>
// [...]
// CHECK: cir.copy %[[#VAR]] to %[[#RETVAL]] : !cir.ptr<!ty_22S22>
// CHECK: }
// CHECK: %[[#RETADDR:]] = cir.get_member %1[0] {name = "x"} : !cir.ptr<!ty_22S22> -> !cir.ptr<!s32i>
// CHECK: %{{.+}} = cir.load %[[#RETADDR]] : cir.ptr <!s32i>, !s32i

// Expression is wrapped in an expression attribute (just ensure it does not crash).
void test4(int x) { ({[[gsl::suppress("foo")]] x;}); }
// CHECK: @test4

// TODO(cir): Missing label support.
// // Expression is wrapped in a label.
// // void test5(int x) { x = ({ label: x; }); }
31 changes: 31 additions & 0 deletions clang/test/CIR/CodeGen/stmt-expr.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
// RUN: FileCheck --input-file=%t.cir %s

class A {
public:
A(): x(0) {}
A(A &a) : x(a.x) {}
// TODO(cir): Ensure dtors are properly called. The dtor below crashes.
// ~A() {}
int x;
void Foo() {}
};

void test1() {
({
A a;
a;
}).Foo();
}
// CHECK: @_Z5test1v
// CHECK: cir.scope {
// CHECK: %[[#RETVAL:]] = cir.alloca !ty_22A22, cir.ptr <!ty_22A22>
// CHECK: cir.scope {
// CHECK: %[[#VAR:]] = cir.alloca !ty_22A22, cir.ptr <!ty_22A22>, ["a", init] {alignment = 4 : i64}
// CHECK: cir.call @_ZN1AC1Ev(%[[#VAR]]) : (!cir.ptr<!ty_22A22>) -> ()
// CHECK: cir.call @_ZN1AC1ERS_(%[[#RETVAL]], %[[#VAR]]) : (!cir.ptr<!ty_22A22>, !cir.ptr<!ty_22A22>) -> ()
// TODO(cir): the local VAR should be destroyed here.
// CHECK: }
// CHECK: cir.call @_ZN1A3FooEv(%[[#RETVAL]]) : (!cir.ptr<!ty_22A22>) -> ()
// TODO(cir): the temporary RETVAL should be destroyed here.
// CHECK: }

0 comments on commit 50d414a

Please sign in to comment.