Skip to content

[CIR] Upstream initial support for unary op #131369

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

Merged
merged 4 commits into from
Mar 17, 2025
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
48 changes: 48 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,54 @@ def BrOp : CIR_Op<"br",
}];
}

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

def UnaryOpKind_Inc : I32EnumAttrCase<"Inc", 1, "inc">;
def UnaryOpKind_Dec : I32EnumAttrCase<"Dec", 2, "dec">;
def UnaryOpKind_Plus : I32EnumAttrCase<"Plus", 3, "plus">;
def UnaryOpKind_Minus : I32EnumAttrCase<"Minus", 4, "minus">;
def UnaryOpKind_Not : I32EnumAttrCase<"Not", 5, "not">;

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

def UnaryOp : CIR_Op<"unary", [Pure, SameOperandsAndResultType]> {
let summary = "Unary operations";
let description = [{
`cir.unary` performs the unary operation according to
the specified opcode kind: [inc, dec, plus, minus, not].

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 CIR_AnyType:$result);
let arguments = (ins Arg<UnaryOpKind, "unary op kind">:$kind, Arg<CIR_AnyType>:$input);

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

let hasVerifier = 1;
let hasFolder = 1;
}

//===----------------------------------------------------------------------===//
// GlobalOp
//===----------------------------------------------------------------------===//
Expand Down
9 changes: 9 additions & 0 deletions clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ struct MissingFeatures {
static bool opFuncLinkage() { return false; }
static bool opFuncVisibility() { return false; }

// Unary operator handling
static bool opUnarySignedOverflow() { return false; }
static bool opUnaryPromotionType() { return false; }

// Misc
static bool scalarConversionOpts() { return false; }
static bool tryEmitAsConstant() { return false; }
Expand All @@ -86,6 +90,11 @@ struct MissingFeatures {
static bool aggValueSlot() { return false; }

static bool unsizedTypes() { return false; }
static bool sanitizers() { return false; }
static bool CGFPOptionsRAII() { return false; }

// Missing types
static bool vectorType() { return false; }
};

} // namespace cir
Expand Down
48 changes: 48 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,54 @@ LValue CIRGenFunction::emitDeclRefLValue(const DeclRefExpr *e) {
return LValue();
}

LValue CIRGenFunction::emitUnaryOpLValue(const UnaryOperator *e) {
UnaryOperatorKind op = e->getOpcode();

// __extension__ doesn't affect lvalue-ness.
if (op == UO_Extension)
return emitLValue(e->getSubExpr());

switch (op) {
case UO_Deref: {
cgm.errorNYI(e->getSourceRange(), "UnaryOp dereference");
return LValue();
}
case UO_Real:
case UO_Imag: {
cgm.errorNYI(e->getSourceRange(), "UnaryOp real/imag");
return LValue();
}
case UO_PreInc:
case UO_PreDec: {
bool isInc = e->isIncrementOp();
LValue lv = emitLValue(e->getSubExpr());

assert(e->isPrefix() && "Prefix operator in unexpected state!");

if (e->getType()->isAnyComplexType()) {
cgm.errorNYI(e->getSourceRange(), "UnaryOp complex inc/dec");
return LValue();
} else {
emitScalarPrePostIncDec(e, lv, isInc, /*isPre=*/true);
}

return lv;
}
case UO_Extension:
llvm_unreachable("UnaryOperator extension should be handled above!");
case UO_Plus:
case UO_Minus:
case UO_Not:
case UO_LNot:
case UO_AddrOf:
case UO_PostInc:
case UO_PostDec:
case UO_Coawait:
llvm_unreachable("UnaryOperator of non-lvalue kind!");
}
llvm_unreachable("Unknown unary operator kind!");
}

/// 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) {
Expand Down
211 changes: 211 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,210 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {

mlir::Value VisitUnaryExprOrTypeTraitExpr(const UnaryExprOrTypeTraitExpr *e);

// Unary Operators.
mlir::Value VisitUnaryPostDec(const UnaryOperator *e) {
LValue lv = cgf.emitLValue(e->getSubExpr());
return emitScalarPrePostIncDec(e, lv, false, false);
}
mlir::Value VisitUnaryPostInc(const UnaryOperator *e) {
LValue lv = cgf.emitLValue(e->getSubExpr());
return emitScalarPrePostIncDec(e, lv, true, false);
}
mlir::Value VisitUnaryPreDec(const UnaryOperator *e) {
LValue lv = cgf.emitLValue(e->getSubExpr());
return emitScalarPrePostIncDec(e, lv, false, true);
}
mlir::Value VisitUnaryPreInc(const UnaryOperator *e) {
LValue lv = cgf.emitLValue(e->getSubExpr());
return emitScalarPrePostIncDec(e, lv, true, true);
}
mlir::Value emitScalarPrePostIncDec(const UnaryOperator *e, LValue lv,
bool isInc, bool isPre) {
if (cgf.getLangOpts().OpenMP)
cgf.cgm.errorNYI(e->getSourceRange(), "inc/dec OpenMP");

QualType type = e->getSubExpr()->getType();

mlir::Value value;
mlir::Value input;

if (type->getAs<AtomicType>()) {
cgf.cgm.errorNYI(e->getSourceRange(), "Atomic inc/dec");
// TODO(cir): This is not correct, but it will produce reasonable code
// until atomic operations are implemented.
value = cgf.emitLoadOfLValue(lv, e->getExprLoc()).getScalarVal();
input = value;
} else {
value = cgf.emitLoadOfLValue(lv, e->getExprLoc()).getScalarVal();
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 (isInc && type->isBooleanType()) {
value = builder.create<cir::ConstantOp>(cgf.getLoc(e->getExprLoc()),
cgf.convertType(type),
builder.getCIRBoolAttr(true));
} else if (type->isIntegerType()) {
QualType promotedType;
bool canPerformLossyDemotionCheck = false;
if (cgf.getContext().isPromotableIntegerType(type)) {
promotedType = cgf.getContext().getPromotedIntegerType(type);
assert(promotedType != type && "Shouldn't promote to the same type.");
canPerformLossyDemotionCheck = true;
canPerformLossyDemotionCheck &=
cgf.getContext().getCanonicalType(type) !=
cgf.getContext().getCanonicalType(promotedType);
canPerformLossyDemotionCheck &=
type->isIntegerType() && promotedType->isIntegerType();

// TODO(cir): Currently, we store bitwidths in CIR types only for
// integers. This might also be required for other types.

assert(
(!canPerformLossyDemotionCheck ||
type->isSignedIntegerOrEnumerationType() ||
promotedType->isSignedIntegerOrEnumerationType() ||
mlir::cast<cir::IntType>(cgf.convertType(type)).getWidth() ==
mlir::cast<cir::IntType>(cgf.convertType(type)).getWidth()) &&
"The following check expects that if we do promotion to different "
"underlying canonical type, at least one of the types (either "
"base or promoted) will be signed, or the bitwidths will match.");
}

assert(!cir::MissingFeatures::sanitizers());
if (e->canOverflow() && type->isSignedIntegerOrEnumerationType()) {
value = emitIncDecConsiderOverflowBehavior(e, value, isInc);
} else {
cir::UnaryOpKind kind =
e->isIncrementOp() ? cir::UnaryOpKind::Inc : cir::UnaryOpKind::Dec;
// NOTE(CIR): clang calls CreateAdd but folds this to a unary op
value = emitUnaryOp(e, kind, input);
}
} else if (const PointerType *ptr = type->getAs<PointerType>()) {
cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec pointer");
return {};
} else if (type->isVectorType()) {
cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec vector");
return {};
} else if (type->isRealFloatingType()) {
assert(!cir::MissingFeatures::CGFPOptionsRAII());

if (type->isHalfType() &&
!cgf.getContext().getLangOpts().NativeHalfType) {
cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec half");
return {};
}

if (mlir::isa<cir::SingleType, cir::DoubleType>(value.getType())) {
// Create the inc/dec operation.
// NOTE(CIR): clang calls CreateAdd but folds this to a unary op
cir::UnaryOpKind kind =
(isInc ? cir::UnaryOpKind::Inc : cir::UnaryOpKind::Dec);
value = emitUnaryOp(e, kind, value);
} else {
cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec other fp type");
return {};
}
} else if (type->isFixedPointType()) {
cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec other fixed point");
return {};
} else {
assert(type->castAs<ObjCObjectPointerType>());
cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec ObjectiveC pointer");
return {};
}

CIRGenFunction::SourceLocRAIIObject sourceloc{
cgf, cgf.getLoc(e->getSourceRange())};

// Store the updated result through the lvalue
if (lv.isBitField()) {
cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec bitfield");
return {};
} else {
cgf.emitStoreThroughLValue(RValue::get(value), lv);
}

// If this is a postinc, return the value read from memory, otherwise use
// the updated value.
return isPre ? value : input;
}

mlir::Value emitIncDecConsiderOverflowBehavior(const UnaryOperator *e,
mlir::Value inVal,
bool isInc) {
assert(!cir::MissingFeatures::opUnarySignedOverflow());
cir::UnaryOpKind kind =
e->isIncrementOp() ? cir::UnaryOpKind::Inc : cir::UnaryOpKind::Dec;
switch (cgf.getLangOpts().getSignedOverflowBehavior()) {
case LangOptions::SOB_Defined:
return emitUnaryOp(e, kind, inVal);
case LangOptions::SOB_Undefined:
assert(!cir::MissingFeatures::sanitizers());
return emitUnaryOp(e, kind, inVal);
break;
case LangOptions::SOB_Trapping:
if (!e->canOverflow())
return emitUnaryOp(e, kind, inVal);
cgf.cgm.errorNYI(e->getSourceRange(), "inc/def overflow SOB_Trapping");
return {};
}
llvm_unreachable("Unexpected signed overflow behavior kind");
}

mlir::Value VisitUnaryPlus(const UnaryOperator *e,
QualType promotionType = QualType()) {
if (!promotionType.isNull())
cgf.cgm.errorNYI(e->getSourceRange(), "VisitUnaryPlus: promotionType");
assert(!cir::MissingFeatures::opUnaryPromotionType());
mlir::Value result = emitUnaryPlusOrMinus(e, cir::UnaryOpKind::Plus);
return result;
}

mlir::Value VisitUnaryMinus(const UnaryOperator *e,
QualType promotionType = QualType()) {
if (!promotionType.isNull())
cgf.cgm.errorNYI(e->getSourceRange(), "VisitUnaryMinus: promotionType");
assert(!cir::MissingFeatures::opUnaryPromotionType());
mlir::Value result = emitUnaryPlusOrMinus(e, cir::UnaryOpKind::Minus);
return result;
}

mlir::Value emitUnaryPlusOrMinus(const UnaryOperator *e,
cir::UnaryOpKind kind) {
ignoreResultAssign = false;

assert(!cir::MissingFeatures::opUnaryPromotionType());
mlir::Value operand = Visit(e->getSubExpr());

assert(!cir::MissingFeatures::opUnarySignedOverflow());

// NOTE: LLVM codegen will lower this directly to either a FNeg
// or a Sub instruction. In CIR this will be handled later in LowerToLLVM.
return emitUnaryOp(e, kind, operand);
}

mlir::Value emitUnaryOp(const UnaryOperator *e, cir::UnaryOpKind kind,
mlir::Value input) {
return builder.create<cir::UnaryOp>(
cgf.getLoc(e->getSourceRange().getBegin()), input.getType(), kind,
input);
}

mlir::Value VisitUnaryNot(const UnaryOperator *e) {
ignoreResultAssign = false;
mlir::Value op = Visit(e->getSubExpr());
return emitUnaryOp(e, cir::UnaryOpKind::Not, op);
}

/// Emit a conversion from the specified type to the specified destination
/// type, both of which are CIR scalar types.
/// TODO: do we need ScalarConversionOpts here? Should be done in another
Expand Down Expand Up @@ -188,3 +392,10 @@ mlir::Value ScalarExprEmitter::VisitUnaryExprOrTypeTraitExpr(
loc, builder.getAttr<cir::IntAttr>(
cgf.cgm.UInt64Ty, e->EvaluateKnownConstInt(cgf.getContext())));
}

mlir::Value CIRGenFunction::emitScalarPrePostIncDec(const UnaryOperator *E,
LValue LV, bool isInc,
bool isPre) {
return ScalarExprEmitter(*this, builder)
.emitScalarPrePostIncDec(E, LV, isInc, isPre);
}
2 changes: 2 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,8 @@ LValue CIRGenFunction::emitLValue(const Expr *e) {
std::string("l-value not implemented for '") +
e->getStmtClassName() + "'");
return LValue();
case Expr::UnaryOperatorClass:
return emitUnaryOpLValue(cast<UnaryOperator>(e));
case Expr::DeclRefExprClass:
return emitDeclRefLValue(cast<DeclRefExpr>(e));
}
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ class CIRGenFunction : public CIRGenTypeCache {
LValue lvalue, bool capturedByInit = false);

LValue emitDeclRefLValue(const clang::DeclRefExpr *e);
LValue emitUnaryOpLValue(const clang::UnaryOperator *e);

/// Determine whether the given initializer is trivial in the sense
/// that it requires no code to be generated.
Expand Down Expand Up @@ -305,6 +306,9 @@ class CIRGenFunction : public CIRGenTypeCache {
// TODO: Add symbol table support
}

mlir::Value emitScalarPrePostIncDec(const UnaryOperator *e, LValue lv,
bool isInc, bool isPre);

/// Emit the computation of the specified expression of scalar type.
mlir::Value emitScalarExpr(const clang::Expr *e);
cir::FuncOp generateCode(clang::GlobalDecl gd, cir::FuncOp fn,
Expand Down
1 change: 1 addition & 0 deletions clang/lib/CIR/CodeGen/CIRGenValue.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class LValue {

public:
bool isSimple() const { return lvType == Simple; }
bool isBitField() const { return lvType == BitField; }

// TODO: Add support for volatile
bool isVolatile() const { return false; }
Expand Down
Loading
Loading