Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic Increment and Decrement support #10

Merged
merged 3 commits into from
Sep 29, 2022
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
47 changes: 47 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,53 @@ def ScopeOp : CIR_Op<"scope", [DeclareOpInterfaceMethods<RegionBranchOpInterface
];
}

//===----------------------------------------------------------------------===//
// UnaryOp
//===----------------------------------------------------------------------===//

def UnaryOpKind_Inc : I32EnumAttrCase<"Inc", 1, "inc">;
def UnaryOpKind_Dec : I32EnumAttrCase<"Dec", 2, "dec">;

def UnaryOpKind : I32EnumAttr<
"UnaryOpKind",
"unary operation kind",
[UnaryOpKind_Inc,
UnaryOpKind_Dec]> {
let cppNamespace = "::mlir::cir";
}

// FIXME: NoSideEffect won't work when we add overloading.
def UnaryOp : CIR_Op<"unary",
[NoSideEffect,
SameOperandsAndResultType]> {
let summary = "Unary operations";
let description = [{
`cir.unary` performs the unary operation according to
the specified opcode kind: [inc, dec].

Note for inc and dec: the operation corresponds only to the
addition/subtraction, its input is expect to come from a load
and the result to be used by a corresponding store.

It requires one input operand and has one result, both types
should be the same.

```mlir
%7 = cir.unary(inc, %1) : i32 -> i32
%8 = cir.unary(dec, %2) : i32 -> i32
```
}];

let results = (outs AnyType:$result);
let arguments = (ins Arg<UnaryOpKind, "unary op kind">:$kind, Arg<AnyType>:$input);

let assemblyFormat = [{
`(` $kind `,` $input `)` `:` type($input) `,` type($result) attr-dict
}];

let hasVerifier = 1;
}

//===----------------------------------------------------------------------===//
// BinOp
//===----------------------------------------------------------------------===//
Expand Down
112 changes: 108 additions & 4 deletions clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,16 +216,113 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {

// Unary Operators.
mlir::Value VisitUnaryPostDec(const UnaryOperator *E) {
llvm_unreachable("NYI");
return buildScalarPrePostIncDec(E);
}
mlir::Value VisitUnaryPostInc(const UnaryOperator *E) {
llvm_unreachable("NYI");
return buildScalarPrePostIncDec(E);
}
mlir::Value VisitUnaryPreDec(const UnaryOperator *E) {
llvm_unreachable("NYI");
return buildScalarPrePostIncDec(E);
}
mlir::Value VisitUnaryPreInc(const UnaryOperator *E) {
llvm_unreachable("NYI");
return buildScalarPrePostIncDec(E);
}
mlir::Value buildScalarPrePostIncDec(const UnaryOperator *E) {
cmarcelo marked this conversation as resolved.
Show resolved Hide resolved
QualType type = E->getSubExpr()->getType();

auto LV = CGF.buildLValue(E->getSubExpr());
mlir::Value Value;
mlir::Value Input;

if (const AtomicType *atomicTy = type->getAs<AtomicType>()) {
assert(0 && "no atomics inc/dec yet");
} else {
Value = buildLoadOfLValue(LV, E->getExprLoc());
Input = Value;
}

// NOTE: When possible, more frequent cases are handled first.

// Special case of integer increment that we have to check first: bool++.
// Due to promotion rules, we get:
// bool++ -> bool = bool + 1
// -> bool = (int)bool + 1
// -> bool = ((int)bool + 1 != 0)
// An interesting aspect of this is that increment is always true.
// Decrement does not have this property.
if (E->isIncrementOp() && type->isBooleanType()) {
assert(0 && "inc simplification for booleans not implemented yet");

// NOTE: We likely want the code below, but loading/store booleans need to work first.
// See CIRGenFunction::buildFromMemory().
Value = Builder.create<mlir::cir::ConstantOp>(CGF.getLoc(E->getExprLoc()),
CGF.getCIRType(type),
Builder.getBoolAttr(true));
} else if (type->isIntegerType()) {
bool canPerformLossyDemotionCheck = false;
if (type->isPromotableIntegerType()) {
canPerformLossyDemotionCheck = true;
assert(0 && "no promotable integer inc/dec yet");
}

if (CGF.SanOpts.hasOneOf(
SanitizerKind::ImplicitIntegerArithmeticValueChange) &&
canPerformLossyDemotionCheck) {
assert(0 &&
"perform lossy demotion case for inc/dec not implemented yet");
} else if (E->canOverflow() && type->isSignedIntegerOrEnumerationType()) {
Value = buildIncDecConsiderOverflowBehavior(E, Value);
} else if (E->canOverflow() && type->isUnsignedIntegerType() &&
CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow)) {
assert(0 &&
"unsigned integer overflow sanitized inc/dec not implemented");
} else {
auto Kind = E->isIncrementOp() ? mlir::cir::UnaryOpKind::Inc
: mlir::cir::UnaryOpKind::Dec;
Value = buildUnaryOp(E, Kind, Input);
}
} else if (const PointerType *ptr = type->getAs<PointerType>()) {
assert(0 && "no pointer inc/dec yet");
} else if (type->isVectorType()) {
assert(0 && "no vector inc/dec yet");
} else if (type->isRealFloatingType()) {
assert(0 && "no float inc/dec yet");
} else if (type->isFixedPointType()) {
assert(0 && "no fixed point inc/dec yet");
} else {
assert(type->castAs<ObjCObjectPointerType>());
assert(0 && "no objc pointer type inc/dec yet");
}

CIRGenFunction::SourceLocRAIIObject sourceloc{
CGF, CGF.getLoc(E->getSourceRange())};

if (LV.isBitField())
assert(0 && "no bitfield inc/dec yet");
else
CGF.buildStoreThroughLValue(RValue::get(Value), LV, nullptr);

return E->isPrefix() ? Value : Input;
}

mlir::Value buildIncDecConsiderOverflowBehavior(const UnaryOperator *E,
mlir::Value V) {
switch (CGF.getLangOpts().getSignedOverflowBehavior()) {
case LangOptions::SOB_Defined: {
auto Kind = E->isIncrementOp() ? mlir::cir::UnaryOpKind::Inc
: mlir::cir::UnaryOpKind::Dec;
return buildUnaryOp(E, Kind, V);
break;
}
case LangOptions::SOB_Undefined:
assert(0 &&
"inc/dec overflow behavior SOB_Undefined not implemented yet");
break;
case LangOptions::SOB_Trapping:
assert(0 && "inc/dec overflow behavior SOB_Trapping not implemented yet");
break;
}
llvm_unreachable("Unknown SignedOverflowBehaviorTy");
}

mlir::Value VisitUnaryAddrOf(const UnaryOperator *E) {
Expand Down Expand Up @@ -256,6 +353,13 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
llvm_unreachable("NYI");
}

mlir::Value buildUnaryOp(const UnaryOperator *E, mlir::cir::UnaryOpKind kind,
mlir::Value input) {
return Builder.create<mlir::cir::UnaryOp>(
CGF.getLoc(E->getSourceRange().getBegin()),
CGF.getCIRType(E->getType()), kind, input);
}

// C++
mlir::Value VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *E) {
llvm_unreachable("NYI");
Expand Down
35 changes: 33 additions & 2 deletions clang/lib/CIR/CodeGen/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,37 @@ class CIRFuncLowering : public mlir::OpRewritePattern<mlir::cir::FuncOp> {
}
};

class CIRUnaryOpLowering : public mlir::OpRewritePattern<mlir::cir::UnaryOp> {
public:
using OpRewritePattern<mlir::cir::UnaryOp>::OpRewritePattern;

mlir::LogicalResult
matchAndRewrite(mlir::cir::UnaryOp op,
mlir::PatternRewriter &rewriter) const override {
mlir::Type type = op.getInput().getType();
assert(type.isa<mlir::IntegerType>() && "operand type not supported yet");

switch (op.getKind()) {
case mlir::cir::UnaryOpKind::Inc: {
auto One = rewriter.create<mlir::arith::ConstantOp>(
op.getLoc(), type, mlir::IntegerAttr::get(type, 1));
rewriter.replaceOpWithNewOp<mlir::arith::AddIOp>(op, op.getType(),
op.getInput(), One);
break;
}
case mlir::cir::UnaryOpKind::Dec: {
auto One = rewriter.create<mlir::arith::ConstantOp>(
op.getLoc(), type, mlir::IntegerAttr::get(type, 1));
rewriter.replaceOpWithNewOp<mlir::arith::SubIOp>(op, op.getType(),
op.getInput(), One);
break;
}
}

return mlir::LogicalResult::success();
}
};

class CIRBinOpLowering : public mlir::OpRewritePattern<mlir::cir::BinOp> {
public:
using OpRewritePattern<mlir::cir::BinOp>::OpRewritePattern;
Expand Down Expand Up @@ -447,8 +478,8 @@ class CIRBrOpLowering : public mlir::OpRewritePattern<mlir::cir::BrOp> {

void populateCIRToMemRefConversionPatterns(mlir::RewritePatternSet &patterns) {
patterns.add<CIRAllocaLowering, CIRLoadLowering, CIRStoreLowering,
CIRConstantLowering, CIRBinOpLowering, CIRCmpOpLowering,
CIRBrOpLowering>(patterns.getContext());
CIRConstantLowering, CIRUnaryOpLowering, CIRBinOpLowering,
CIRCmpOpLowering, CIRBrOpLowering>(patterns.getContext());
}

void ConvertCIRToLLVMPass::runOnOperation() {
Expand Down
27 changes: 27 additions & 0 deletions clang/lib/CIR/Dialect/IR/CIRDialect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1274,6 +1274,33 @@ FunctionType CallOp::getCalleeType() {
return FunctionType::get(getContext(), getOperandTypes(), getResultTypes());
}

//===----------------------------------------------------------------------===//
// UnaryOp
//===----------------------------------------------------------------------===//

LogicalResult UnaryOp::verify() {
switch (getKind()) {
case cir::UnaryOpKind::Inc:
LLVM_FALLTHROUGH;
case cir::UnaryOpKind::Dec: {
// TODO: Consider looking at the memory interface instead of LoadOp/StoreOp.
auto loadOp = getInput().getDefiningOp<cir::LoadOp>();
if (!loadOp)
return emitOpError() << "requires input to be defined by a memory load";

for (const auto user : getResult().getUsers()) {
auto storeOp = dyn_cast<cir::StoreOp>(user);
if (storeOp && storeOp.getAddr() == loadOp.getAddr())
return success();
}
return emitOpError() << "requires result to be used by a memory store "
"to the same address as the input memory load";
Copy link
Member

Choose a reason for hiding this comment

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

Can you add a test for the error tracking in clang/test/CIR/IR/invalid.cir?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added, could you please take another look?

}
}

llvm_unreachable("Unknown UnaryOp kind?");
}

//===----------------------------------------------------------------------===//
// CIR defined traits
//===----------------------------------------------------------------------===//
Expand Down
29 changes: 29 additions & 0 deletions clang/test/CIR/CIRToLLVM/unary-inc-dec.cir
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// RUN: cir-tool %s -cir-to-func -cir-to-memref -o - | FileCheck %s -check-prefix=MLIR
// RUN: cir-tool %s -cir-to-func -cir-to-memref -cir-to-llvm -o - | mlir-translate -mlir-to-llvmir | FileCheck %s -check-prefix=LLVM

module {
cir.func @foo() {
%0 = cir.alloca i32, cir.ptr <i32>, ["a", cinit] {alignment = 4 : i64}
%1 = cir.alloca i32, cir.ptr <i32>, ["b", cinit] {alignment = 4 : i64}
%2 = cir.cst(2 : i32) : i32
cir.store %2, %0 : i32, cir.ptr <i32>
cir.store %2, %1 : i32, cir.ptr <i32>

%3 = cir.load %0 : cir.ptr <i32>, i32
%4 = cir.unary(inc, %3) : i32, i32
cir.store %4, %0 : i32, cir.ptr <i32>

%5 = cir.load %1 : cir.ptr <i32>, i32
%6 = cir.unary(dec, %5) : i32, i32
cir.store %6, %1 : i32, cir.ptr <i32>
cir.return
}
}

// MLIR: = arith.constant 1
// MLIR: = arith.addi
// MLIR: = arith.constant 1
// MLIR: = arith.subi

// LLVM: = add i32 %[[#]], 1
// LLVM: = sub i32 %[[#]], 1
55 changes: 55 additions & 0 deletions clang/test/CIR/CodeGen/inc-dec.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fenable-clangir -Wno-unused-value -emit-cir %s -o %t.cir
cmarcelo marked this conversation as resolved.
Show resolved Hide resolved
// RUN: FileCheck --input-file=%t.cir %s

unsigned id0() {
unsigned a = 1;
return ++a;
}

// CHECK: cir.func @_Z3id0v() -> i32 {
// CHECK: %[[#RET:]] = cir.alloca i32, cir.ptr <i32>, ["__retval", uninitialized]
// CHECK: %[[#A:]] = cir.alloca i32, cir.ptr <i32>, ["a", cinit]
// CHECK: %[[#BEFORE_A:]] = cir.load %[[#A]]
// CHECK: %[[#AFTER_A:]] = cir.unary(inc, %[[#BEFORE_A]])
// CHECK: cir.store %[[#AFTER_A]], %[[#A]]
// CHECK: cir.store %[[#AFTER_A]], %[[#RET]]


unsigned id1() {
unsigned a = 1;
return --a;
}

// CHECK: cir.func @_Z3id1v() -> i32 {
// CHECK: %[[#RET:]] = cir.alloca i32, cir.ptr <i32>, ["__retval", uninitialized]
// CHECK: %[[#A:]] = cir.alloca i32, cir.ptr <i32>, ["a", cinit]
// CHECK: %[[#BEFORE_A:]] = cir.load %[[#A]]
// CHECK: %[[#AFTER_A:]] = cir.unary(dec, %[[#BEFORE_A]])
// CHECK: cir.store %[[#AFTER_A]], %[[#A]]
// CHECK: cir.store %[[#AFTER_A]], %[[#RET]]

unsigned id2() {
unsigned a = 1;
return a++;
}

// CHECK: cir.func @_Z3id2v() -> i32 {
// CHECK: %[[#RET:]] = cir.alloca i32, cir.ptr <i32>, ["__retval", uninitialized]
// CHECK: %[[#A:]] = cir.alloca i32, cir.ptr <i32>, ["a", cinit]
// CHECK: %[[#BEFORE_A:]] = cir.load %[[#A]]
// CHECK: %[[#AFTER_A:]] = cir.unary(inc, %[[#BEFORE_A]])
// CHECK: cir.store %[[#AFTER_A]], %[[#A]]
// CHECK: cir.store %[[#BEFORE_A]], %[[#RET]]

unsigned id3() {
unsigned a = 1;
return a--;
}

// CHECK: cir.func @_Z3id3v() -> i32 {
// CHECK: %[[#RET:]] = cir.alloca i32, cir.ptr <i32>, ["__retval", uninitialized]
// CHECK: %[[#A:]] = cir.alloca i32, cir.ptr <i32>, ["a", cinit]
// CHECK: %[[#BEFORE_A:]] = cir.load %[[#A]]
// CHECK: %[[#AFTER_A:]] = cir.unary(dec, %[[#BEFORE_A]])
// CHECK: cir.store %[[#AFTER_A]], %[[#A]]
// CHECK: cir.store %[[#BEFORE_A]], %[[#RET]]
23 changes: 23 additions & 0 deletions clang/test/CIR/IR/invalid.cir
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,26 @@ module {
module {
cir.global "public" internal @v = 3 : i32 // expected-error {{public visibility not allowed with 'internal' linkage}}
}

// -----

cir.func @unary0() {
%0 = cir.alloca i32, cir.ptr <i32>, ["a", cinit] {alignment = 4 : i64}
%1 = cir.cst(2 : i32) : i32

%3 = cir.unary(inc, %1) : i32, i32 // expected-error {{'cir.unary' op requires input to be defined by a memory load}}
cir.store %3, %0 : i32, cir.ptr <i32>
cir.return
}

// -----

cir.func @unary1() {
%0 = cir.alloca i32, cir.ptr <i32>, ["a", cinit] {alignment = 4 : i64}
%1 = cir.cst(2 : i32) : i32
cir.store %1, %0 : i32, cir.ptr <i32>

%2 = cir.load %0 : cir.ptr <i32>, i32
%3 = cir.unary(dec, %2) : i32, i32 // // expected-error {{'cir.unary' op requires result to be used by a memory store to the same address as the input memory load}}
cir.return
}