Skip to content

Commit 5f86666

Browse files
authored
[CIR] Upstream initial support for unary op (llvm#131369)
This adds support for the cir.unary plus, minus, inc, dec, and not operations for integer, floating point, and boolean types.
1 parent 1b31646 commit 5f86666

File tree

12 files changed

+901
-2
lines changed

12 files changed

+901
-2
lines changed

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,54 @@ def BrOp : CIR_Op<"br",
468468
}];
469469
}
470470

471+
//===----------------------------------------------------------------------===//
472+
// UnaryOp
473+
//===----------------------------------------------------------------------===//
474+
475+
def UnaryOpKind_Inc : I32EnumAttrCase<"Inc", 1, "inc">;
476+
def UnaryOpKind_Dec : I32EnumAttrCase<"Dec", 2, "dec">;
477+
def UnaryOpKind_Plus : I32EnumAttrCase<"Plus", 3, "plus">;
478+
def UnaryOpKind_Minus : I32EnumAttrCase<"Minus", 4, "minus">;
479+
def UnaryOpKind_Not : I32EnumAttrCase<"Not", 5, "not">;
480+
481+
def UnaryOpKind : I32EnumAttr<
482+
"UnaryOpKind",
483+
"unary operation kind",
484+
[UnaryOpKind_Inc,
485+
UnaryOpKind_Dec,
486+
UnaryOpKind_Plus,
487+
UnaryOpKind_Minus,
488+
UnaryOpKind_Not,
489+
]> {
490+
let cppNamespace = "::cir";
491+
}
492+
493+
def UnaryOp : CIR_Op<"unary", [Pure, SameOperandsAndResultType]> {
494+
let summary = "Unary operations";
495+
let description = [{
496+
`cir.unary` performs the unary operation according to
497+
the specified opcode kind: [inc, dec, plus, minus, not].
498+
499+
It requires one input operand and has one result, both types
500+
should be the same.
501+
502+
```mlir
503+
%7 = cir.unary(inc, %1) : i32 -> i32
504+
%8 = cir.unary(dec, %2) : i32 -> i32
505+
```
506+
}];
507+
508+
let results = (outs CIR_AnyType:$result);
509+
let arguments = (ins Arg<UnaryOpKind, "unary op kind">:$kind, Arg<CIR_AnyType>:$input);
510+
511+
let assemblyFormat = [{
512+
`(` $kind `,` $input `)` `:` type($input) `,` type($result) attr-dict
513+
}];
514+
515+
let hasVerifier = 1;
516+
let hasFolder = 1;
517+
}
518+
471519
//===----------------------------------------------------------------------===//
472520
// GlobalOp
473521
//===----------------------------------------------------------------------===//

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ struct MissingFeatures {
7272
static bool opFuncLinkage() { return false; }
7373
static bool opFuncVisibility() { return false; }
7474

75+
// Unary operator handling
76+
static bool opUnarySignedOverflow() { return false; }
77+
static bool opUnaryPromotionType() { return false; }
78+
7579
// Misc
7680
static bool scalarConversionOpts() { return false; }
7781
static bool tryEmitAsConstant() { return false; }
@@ -86,6 +90,11 @@ struct MissingFeatures {
8690
static bool aggValueSlot() { return false; }
8791

8892
static bool unsizedTypes() { return false; }
93+
static bool sanitizers() { return false; }
94+
static bool CGFPOptionsRAII() { return false; }
95+
96+
// Missing types
97+
static bool vectorType() { return false; }
8998
};
9099

91100
} // namespace cir

clang/lib/CIR/CodeGen/CIRGenExpr.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,54 @@ LValue CIRGenFunction::emitDeclRefLValue(const DeclRefExpr *e) {
165165
return LValue();
166166
}
167167

168+
LValue CIRGenFunction::emitUnaryOpLValue(const UnaryOperator *e) {
169+
UnaryOperatorKind op = e->getOpcode();
170+
171+
// __extension__ doesn't affect lvalue-ness.
172+
if (op == UO_Extension)
173+
return emitLValue(e->getSubExpr());
174+
175+
switch (op) {
176+
case UO_Deref: {
177+
cgm.errorNYI(e->getSourceRange(), "UnaryOp dereference");
178+
return LValue();
179+
}
180+
case UO_Real:
181+
case UO_Imag: {
182+
cgm.errorNYI(e->getSourceRange(), "UnaryOp real/imag");
183+
return LValue();
184+
}
185+
case UO_PreInc:
186+
case UO_PreDec: {
187+
bool isInc = e->isIncrementOp();
188+
LValue lv = emitLValue(e->getSubExpr());
189+
190+
assert(e->isPrefix() && "Prefix operator in unexpected state!");
191+
192+
if (e->getType()->isAnyComplexType()) {
193+
cgm.errorNYI(e->getSourceRange(), "UnaryOp complex inc/dec");
194+
return LValue();
195+
} else {
196+
emitScalarPrePostIncDec(e, lv, isInc, /*isPre=*/true);
197+
}
198+
199+
return lv;
200+
}
201+
case UO_Extension:
202+
llvm_unreachable("UnaryOperator extension should be handled above!");
203+
case UO_Plus:
204+
case UO_Minus:
205+
case UO_Not:
206+
case UO_LNot:
207+
case UO_AddrOf:
208+
case UO_PostInc:
209+
case UO_PostDec:
210+
case UO_Coawait:
211+
llvm_unreachable("UnaryOperator of non-lvalue kind!");
212+
}
213+
llvm_unreachable("Unknown unary operator kind!");
214+
}
215+
168216
/// Emit code to compute the specified expression which
169217
/// can have any type. The result is returned as an RValue struct.
170218
RValue CIRGenFunction::emitAnyExpr(const Expr *e) {

clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,210 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
9494

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

97+
// Unary Operators.
98+
mlir::Value VisitUnaryPostDec(const UnaryOperator *e) {
99+
LValue lv = cgf.emitLValue(e->getSubExpr());
100+
return emitScalarPrePostIncDec(e, lv, false, false);
101+
}
102+
mlir::Value VisitUnaryPostInc(const UnaryOperator *e) {
103+
LValue lv = cgf.emitLValue(e->getSubExpr());
104+
return emitScalarPrePostIncDec(e, lv, true, false);
105+
}
106+
mlir::Value VisitUnaryPreDec(const UnaryOperator *e) {
107+
LValue lv = cgf.emitLValue(e->getSubExpr());
108+
return emitScalarPrePostIncDec(e, lv, false, true);
109+
}
110+
mlir::Value VisitUnaryPreInc(const UnaryOperator *e) {
111+
LValue lv = cgf.emitLValue(e->getSubExpr());
112+
return emitScalarPrePostIncDec(e, lv, true, true);
113+
}
114+
mlir::Value emitScalarPrePostIncDec(const UnaryOperator *e, LValue lv,
115+
bool isInc, bool isPre) {
116+
if (cgf.getLangOpts().OpenMP)
117+
cgf.cgm.errorNYI(e->getSourceRange(), "inc/dec OpenMP");
118+
119+
QualType type = e->getSubExpr()->getType();
120+
121+
mlir::Value value;
122+
mlir::Value input;
123+
124+
if (type->getAs<AtomicType>()) {
125+
cgf.cgm.errorNYI(e->getSourceRange(), "Atomic inc/dec");
126+
// TODO(cir): This is not correct, but it will produce reasonable code
127+
// until atomic operations are implemented.
128+
value = cgf.emitLoadOfLValue(lv, e->getExprLoc()).getScalarVal();
129+
input = value;
130+
} else {
131+
value = cgf.emitLoadOfLValue(lv, e->getExprLoc()).getScalarVal();
132+
input = value;
133+
}
134+
135+
// NOTE: When possible, more frequent cases are handled first.
136+
137+
// Special case of integer increment that we have to check first: bool++.
138+
// Due to promotion rules, we get:
139+
// bool++ -> bool = bool + 1
140+
// -> bool = (int)bool + 1
141+
// -> bool = ((int)bool + 1 != 0)
142+
// An interesting aspect of this is that increment is always true.
143+
// Decrement does not have this property.
144+
if (isInc && type->isBooleanType()) {
145+
value = builder.create<cir::ConstantOp>(cgf.getLoc(e->getExprLoc()),
146+
cgf.convertType(type),
147+
builder.getCIRBoolAttr(true));
148+
} else if (type->isIntegerType()) {
149+
QualType promotedType;
150+
bool canPerformLossyDemotionCheck = false;
151+
if (cgf.getContext().isPromotableIntegerType(type)) {
152+
promotedType = cgf.getContext().getPromotedIntegerType(type);
153+
assert(promotedType != type && "Shouldn't promote to the same type.");
154+
canPerformLossyDemotionCheck = true;
155+
canPerformLossyDemotionCheck &=
156+
cgf.getContext().getCanonicalType(type) !=
157+
cgf.getContext().getCanonicalType(promotedType);
158+
canPerformLossyDemotionCheck &=
159+
type->isIntegerType() && promotedType->isIntegerType();
160+
161+
// TODO(cir): Currently, we store bitwidths in CIR types only for
162+
// integers. This might also be required for other types.
163+
164+
assert(
165+
(!canPerformLossyDemotionCheck ||
166+
type->isSignedIntegerOrEnumerationType() ||
167+
promotedType->isSignedIntegerOrEnumerationType() ||
168+
mlir::cast<cir::IntType>(cgf.convertType(type)).getWidth() ==
169+
mlir::cast<cir::IntType>(cgf.convertType(type)).getWidth()) &&
170+
"The following check expects that if we do promotion to different "
171+
"underlying canonical type, at least one of the types (either "
172+
"base or promoted) will be signed, or the bitwidths will match.");
173+
}
174+
175+
assert(!cir::MissingFeatures::sanitizers());
176+
if (e->canOverflow() && type->isSignedIntegerOrEnumerationType()) {
177+
value = emitIncDecConsiderOverflowBehavior(e, value, isInc);
178+
} else {
179+
cir::UnaryOpKind kind =
180+
e->isIncrementOp() ? cir::UnaryOpKind::Inc : cir::UnaryOpKind::Dec;
181+
// NOTE(CIR): clang calls CreateAdd but folds this to a unary op
182+
value = emitUnaryOp(e, kind, input);
183+
}
184+
} else if (const PointerType *ptr = type->getAs<PointerType>()) {
185+
cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec pointer");
186+
return {};
187+
} else if (type->isVectorType()) {
188+
cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec vector");
189+
return {};
190+
} else if (type->isRealFloatingType()) {
191+
assert(!cir::MissingFeatures::CGFPOptionsRAII());
192+
193+
if (type->isHalfType() &&
194+
!cgf.getContext().getLangOpts().NativeHalfType) {
195+
cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec half");
196+
return {};
197+
}
198+
199+
if (mlir::isa<cir::SingleType, cir::DoubleType>(value.getType())) {
200+
// Create the inc/dec operation.
201+
// NOTE(CIR): clang calls CreateAdd but folds this to a unary op
202+
cir::UnaryOpKind kind =
203+
(isInc ? cir::UnaryOpKind::Inc : cir::UnaryOpKind::Dec);
204+
value = emitUnaryOp(e, kind, value);
205+
} else {
206+
cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec other fp type");
207+
return {};
208+
}
209+
} else if (type->isFixedPointType()) {
210+
cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec other fixed point");
211+
return {};
212+
} else {
213+
assert(type->castAs<ObjCObjectPointerType>());
214+
cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec ObjectiveC pointer");
215+
return {};
216+
}
217+
218+
CIRGenFunction::SourceLocRAIIObject sourceloc{
219+
cgf, cgf.getLoc(e->getSourceRange())};
220+
221+
// Store the updated result through the lvalue
222+
if (lv.isBitField()) {
223+
cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec bitfield");
224+
return {};
225+
} else {
226+
cgf.emitStoreThroughLValue(RValue::get(value), lv);
227+
}
228+
229+
// If this is a postinc, return the value read from memory, otherwise use
230+
// the updated value.
231+
return isPre ? value : input;
232+
}
233+
234+
mlir::Value emitIncDecConsiderOverflowBehavior(const UnaryOperator *e,
235+
mlir::Value inVal,
236+
bool isInc) {
237+
assert(!cir::MissingFeatures::opUnarySignedOverflow());
238+
cir::UnaryOpKind kind =
239+
e->isIncrementOp() ? cir::UnaryOpKind::Inc : cir::UnaryOpKind::Dec;
240+
switch (cgf.getLangOpts().getSignedOverflowBehavior()) {
241+
case LangOptions::SOB_Defined:
242+
return emitUnaryOp(e, kind, inVal);
243+
case LangOptions::SOB_Undefined:
244+
assert(!cir::MissingFeatures::sanitizers());
245+
return emitUnaryOp(e, kind, inVal);
246+
break;
247+
case LangOptions::SOB_Trapping:
248+
if (!e->canOverflow())
249+
return emitUnaryOp(e, kind, inVal);
250+
cgf.cgm.errorNYI(e->getSourceRange(), "inc/def overflow SOB_Trapping");
251+
return {};
252+
}
253+
llvm_unreachable("Unexpected signed overflow behavior kind");
254+
}
255+
256+
mlir::Value VisitUnaryPlus(const UnaryOperator *e,
257+
QualType promotionType = QualType()) {
258+
if (!promotionType.isNull())
259+
cgf.cgm.errorNYI(e->getSourceRange(), "VisitUnaryPlus: promotionType");
260+
assert(!cir::MissingFeatures::opUnaryPromotionType());
261+
mlir::Value result = emitUnaryPlusOrMinus(e, cir::UnaryOpKind::Plus);
262+
return result;
263+
}
264+
265+
mlir::Value VisitUnaryMinus(const UnaryOperator *e,
266+
QualType promotionType = QualType()) {
267+
if (!promotionType.isNull())
268+
cgf.cgm.errorNYI(e->getSourceRange(), "VisitUnaryMinus: promotionType");
269+
assert(!cir::MissingFeatures::opUnaryPromotionType());
270+
mlir::Value result = emitUnaryPlusOrMinus(e, cir::UnaryOpKind::Minus);
271+
return result;
272+
}
273+
274+
mlir::Value emitUnaryPlusOrMinus(const UnaryOperator *e,
275+
cir::UnaryOpKind kind) {
276+
ignoreResultAssign = false;
277+
278+
assert(!cir::MissingFeatures::opUnaryPromotionType());
279+
mlir::Value operand = Visit(e->getSubExpr());
280+
281+
assert(!cir::MissingFeatures::opUnarySignedOverflow());
282+
283+
// NOTE: LLVM codegen will lower this directly to either a FNeg
284+
// or a Sub instruction. In CIR this will be handled later in LowerToLLVM.
285+
return emitUnaryOp(e, kind, operand);
286+
}
287+
288+
mlir::Value emitUnaryOp(const UnaryOperator *e, cir::UnaryOpKind kind,
289+
mlir::Value input) {
290+
return builder.create<cir::UnaryOp>(
291+
cgf.getLoc(e->getSourceRange().getBegin()), input.getType(), kind,
292+
input);
293+
}
294+
295+
mlir::Value VisitUnaryNot(const UnaryOperator *e) {
296+
ignoreResultAssign = false;
297+
mlir::Value op = Visit(e->getSubExpr());
298+
return emitUnaryOp(e, cir::UnaryOpKind::Not, op);
299+
}
300+
97301
/// Emit a conversion from the specified type to the specified destination
98302
/// type, both of which are CIR scalar types.
99303
/// TODO: do we need ScalarConversionOpts here? Should be done in another
@@ -188,3 +392,10 @@ mlir::Value ScalarExprEmitter::VisitUnaryExprOrTypeTraitExpr(
188392
loc, builder.getAttr<cir::IntAttr>(
189393
cgf.cgm.UInt64Ty, e->EvaluateKnownConstInt(cgf.getContext())));
190394
}
395+
396+
mlir::Value CIRGenFunction::emitScalarPrePostIncDec(const UnaryOperator *E,
397+
LValue LV, bool isInc,
398+
bool isPre) {
399+
return ScalarExprEmitter(*this, builder)
400+
.emitScalarPrePostIncDec(E, LV, isInc, isPre);
401+
}

clang/lib/CIR/CodeGen/CIRGenFunction.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,8 @@ LValue CIRGenFunction::emitLValue(const Expr *e) {
305305
std::string("l-value not implemented for '") +
306306
e->getStmtClassName() + "'");
307307
return LValue();
308+
case Expr::UnaryOperatorClass:
309+
return emitUnaryOpLValue(cast<UnaryOperator>(e));
308310
case Expr::DeclRefExprClass:
309311
return emitDeclRefLValue(cast<DeclRefExpr>(e));
310312
}

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ class CIRGenFunction : public CIRGenTypeCache {
206206
LValue lvalue, bool capturedByInit = false);
207207

208208
LValue emitDeclRefLValue(const clang::DeclRefExpr *e);
209+
LValue emitUnaryOpLValue(const clang::UnaryOperator *e);
209210

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

309+
mlir::Value emitScalarPrePostIncDec(const UnaryOperator *e, LValue lv,
310+
bool isInc, bool isPre);
311+
308312
/// Emit the computation of the specified expression of scalar type.
309313
mlir::Value emitScalarExpr(const clang::Expr *e);
310314
cir::FuncOp generateCode(clang::GlobalDecl gd, cir::FuncOp fn,

clang/lib/CIR/CodeGen/CIRGenValue.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ class LValue {
9393

9494
public:
9595
bool isSimple() const { return lvType == Simple; }
96+
bool isBitField() const { return lvType == BitField; }
9697

9798
// TODO: Add support for volatile
9899
bool isVolatile() const { return false; }

0 commit comments

Comments
 (0)