Files
gps/tools/local/circt/include/mlir/Dialect/Arith/IR/ArithOps.td
T
2026-07-12 11:01:51 +03:00

2072 lines
78 KiB
TableGen

//===- ArithOps.td - Arith op definitions ------------------*- tablegen -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef ARITH_OPS
#define ARITH_OPS
include "mlir/Dialect/Arith/IR/ArithBase.td"
include "mlir/Dialect/Arith/IR/ArithOpsInterfaces.td"
include "mlir/Interfaces/CastInterfaces.td"
include "mlir/Interfaces/ControlFlowInterfaces.td"
include "mlir/Interfaces/InferIntDivisibilityOpInterface.td"
include "mlir/Interfaces/InferIntRangeInterface.td"
include "mlir/Interfaces/InferTypeOpInterface.td"
include "mlir/Interfaces/SideEffectInterfaces.td"
include "mlir/Interfaces/VectorInterfaces.td"
include "mlir/IR/BuiltinAttributeInterfaces.td"
include "mlir/IR/OpAsmInterface.td"
include "mlir/IR/EnumAttr.td"
// Type constraint for signless-integer-or-index-like types that additionally
// excludes i0 (zero-bitwidth integers), used for arith integer operations.
def Arith_SignlessIntegerOrIndexLike : TypeOrValueSemanticsContainer<
AnyNonZeroBitwidthSignlessIntegerOrIndex,
"signless-non-zero-bitwidth-integer-like">;
// Base class for Arith dialect ops. Ops in this dialect have no memory
// effects and can be applied element-wise to vectors and tensors.
class Arith_Op<string mnemonic, list<Trait> traits = []> :
Op<Arith_Dialect, mnemonic,
traits #
[DeclareOpInterfaceMethods<VectorUnrollOpInterface>, NoMemoryEffect] #
ElementwiseMappable.traits>;
// Base class for integer and floating point arithmetic ops. All ops have one
// result, require operands and results to be of the same type, and can accept
// tensors or vectors of integers or floats.
class Arith_ArithOp<string mnemonic, list<Trait> traits = []> :
Arith_Op<mnemonic, traits # [SameOperandsAndResultType]>;
// Base class for unary arithmetic operations.
class Arith_UnaryOp<string mnemonic, list<Trait> traits = []> :
Arith_ArithOp<mnemonic, traits # [Pure]> {
let assemblyFormat = "$operand attr-dict `:` type($result)";
}
// Base class for binary arithmetic operations.
class Arith_BinaryOp<string mnemonic, list<Trait> traits = []> :
Arith_ArithOp<mnemonic, traits> {
let assemblyFormat = "$lhs `,` $rhs attr-dict `:` type($result)";
}
// Base class for integer binary operations.
class Arith_IntBinaryOp<string mnemonic, list<Trait> traits = []> :
Arith_BinaryOp<mnemonic, traits #
[DeclareOpInterfaceMethods<InferIntRangeInterface, ["inferResultRanges"]>]>,
Arguments<(ins Arith_SignlessIntegerOrIndexLike:$lhs, Arith_SignlessIntegerOrIndexLike:$rhs)>,
Results<(outs Arith_SignlessIntegerOrIndexLike:$result)>;
// Base class for integer binary operations without undefined behavior.
class Arith_TotalIntBinaryOp<string mnemonic, list<Trait> traits = []> :
Arith_IntBinaryOp<mnemonic, traits # [Pure]>;
// Base class for floating point unary operations.
class Arith_FloatUnaryOp<string mnemonic, list<Trait> traits = []> :
Arith_UnaryOp<mnemonic,
!listconcat([DeclareOpInterfaceMethods<ArithFastMathInterface>],
traits)>,
Arguments<(ins FloatLike:$operand,
DefaultValuedAttr<
Arith_FastMathAttr, "::mlir::arith::FastMathFlags::none">:$fastmath)>,
Results<(outs FloatLike:$result)> {
let assemblyFormat = [{ $operand (`fastmath` `` $fastmath^)?
attr-dict `:` type($result) }];
}
// Base class for floating point binary operations.
class Arith_FloatBinaryOp<string mnemonic, list<Trait> traits = []> :
Arith_BinaryOp<mnemonic,
!listconcat([Pure, DeclareOpInterfaceMethods<ArithFastMathInterface>],
traits)> {
let arguments = (ins FloatLike:$lhs, FloatLike:$rhs,
DefaultValuedAttr<
Arith_FastMathAttr, "::mlir::arith::FastMathFlags::none">:$fastmath);
let results = (outs FloatLike:$result);
let assemblyFormat = [{ $lhs `,` $rhs (`fastmath` `` $fastmath^)?
attr-dict `:` type($result) }];
}
// Base class for floating point binary operations with an optional rounding
// mode.
class Arith_FloatBinaryOpWithRoundingMode<string mnemonic,
list<Trait> traits = []> :
Arith_FloatBinaryOp<mnemonic,
!listconcat([DeclareOpInterfaceMethods<ArithRoundingModeInterface>],
traits)> {
let arguments = (ins FloatLike:$lhs, FloatLike:$rhs,
DefaultValuedAttr<
Arith_FastMathAttr, "::mlir::arith::FastMathFlags::none">:$fastmath,
OptionalAttr<Arith_RoundingModeAttr>:$roundingmode);
let builders = [
OpBuilder<(ins "Value":$lhs, "Value":$rhs,
CArg<"::mlir::arith::FastMathFlags",
"::mlir::arith::FastMathFlags::none">:$fastmath), [{
build($_builder, $_state, lhs, rhs, fastmath,
::mlir::arith::RoundingModeAttr{});
}]>,
OpBuilder<(ins "Value":$lhs, "Value":$rhs,
"::mlir::arith::FastMathFlagsAttr":$fastmath), [{
build($_builder, $_state, lhs, rhs, fastmath,
::mlir::arith::RoundingModeAttr{});
}]>,
OpBuilder<(ins "Type":$type, "Value":$lhs, "Value":$rhs,
CArg<"::mlir::arith::FastMathFlags",
"::mlir::arith::FastMathFlags::none">:$fastmath), [{
build($_builder, $_state, type, lhs, rhs,
::mlir::arith::FastMathFlagsAttr::get(
$_builder.getContext(), fastmath),
::mlir::arith::RoundingModeAttr{});
}]>,
];
let assemblyFormat = [{ $lhs `,` $rhs ($roundingmode^)?
(`fastmath` `` $fastmath^)?
attr-dict `:` type($result) }];
}
// Checks that tensor input and outputs have identical shapes. This is stricker
// than the verification done in `SameOperandsAndResultShape` that allows for
// tensor dimensions to be 'compatible' (e.g., dynamic dimensions being
// compatible with static ones).
def SameInputOutputTensorDims : PredOpTrait<
"input and output have the same tensor dimensions",
AllMatchSameOperatorPred<["in", "out"],
"(::llvm::isa<::mlir::TensorType>($_self.getType()) ?"
" ::llvm::cast<::mlir::TensorType>($_self.getType()).getShape() :"
" ::llvm::ArrayRef<int64_t>{})">>;
// Base class for arithmetic cast operations. Requires a single operand and
// result. If either is a shaped type, then the other must be of the same
// shape. In the case of tensor types, this also includes the corresponding
// operand/result dimensions being equal.
class Arith_CastOp<string mnemonic, TypeConstraint From, TypeConstraint To,
list<Trait> traits = []> :
Arith_Op<mnemonic, traits # [Pure, SameOperandsAndResultShape,
SameInputOutputTensorDims, DeclareOpInterfaceMethods<CastOpInterface>]>,
Arguments<(ins From:$in)>,
Results<(outs To:$out)> {
let assemblyFormat = "$in attr-dict `:` type($in) `to` type($out)";
}
// Casts do not accept indices. Type constraint for signless-integer-like types
// excluding indices: signless integers, vectors or tensors thereof.
// i0 (zero-bitwidth) integers are excluded as they have no meaningful
// representation in cast operations.
def SignlessFixedWidthIntegerLike : TypeConstraint<Or<[
AnyNonZeroBitwidthSignlessInteger.predicate,
VectorOfAnyRankOf<[AnyNonZeroBitwidthSignlessInteger]>.predicate,
TensorOf<[AnyNonZeroBitwidthSignlessInteger]>.predicate]>,
"signless-fixed-width-integer-like">;
// Cast from an integer type to another integer type.
class Arith_IToICastOp<string mnemonic, list<Trait> traits = []> :
Arith_CastOp<mnemonic, SignlessFixedWidthIntegerLike,
SignlessFixedWidthIntegerLike,
traits #
[DeclareOpInterfaceMethods<InferIntRangeInterface, ["inferResultRanges"]>]>;
// Cast from an integer type to a floating point type.
class Arith_IToFCastOp<string mnemonic, list<Trait> traits = []> :
Arith_CastOp<mnemonic, SignlessFixedWidthIntegerLike, FloatLike, traits>;
// Cast from a floating point type to an integer type.
class Arith_FToICastOp<string mnemonic, list<Trait> traits = []> :
Arith_CastOp<mnemonic, FloatLike, SignlessFixedWidthIntegerLike, traits>;
// Cast from a floating point type to another floating point type.
class Arith_FToFCastOp<string mnemonic, list<Trait> traits = []> :
Arith_CastOp<mnemonic, FloatLike, FloatLike, traits>;
// Base class for compare operations. Requires two operands of the same type
// and returns a single `BoolLike` result. If the operand type is a vector or
// tensor, then the result will be one of `i1` of the same shape.
class Arith_CompareOp<string mnemonic, list<Trait> traits = []> :
Arith_Op<mnemonic, traits # [Pure, SameTypeOperands, TypesMatchWith<
"result type has i1 element type and same shape as operands",
"lhs", "result", "::getI1SameShape($_self)">]> {
let results = (outs BoolLike:$result);
let assemblyFormat = "$predicate `,` $lhs `,` $rhs attr-dict `:` type($lhs)";
}
class Arith_IntBinaryOpWithOverflowFlags<string mnemonic, list<Trait> traits = []> :
Arith_BinaryOp<mnemonic, traits #
[Pure, DeclareOpInterfaceMethods<InferIntRangeInterface, ["inferResultRanges"]>,
DeclareOpInterfaceMethods<ArithIntegerOverflowFlagsInterface>]>,
Arguments<(ins Arith_SignlessIntegerOrIndexLike:$lhs, Arith_SignlessIntegerOrIndexLike:$rhs,
DefaultValuedAttr<
Arith_IntegerOverflowAttr,
"::mlir::arith::IntegerOverflowFlags::none">:$overflowFlags)>,
Results<(outs Arith_SignlessIntegerOrIndexLike:$result)> {
let assemblyFormat = [{ $lhs `,` $rhs (`overflow` `` $overflowFlags^)?
attr-dict `:` type($result) }];
}
class Arith_IntBinaryOpWithExactFlag<string mnemonic, list<Trait> traits = []> :
Arith_BinaryOp<mnemonic, traits #
[DeclareOpInterfaceMethods<InferIntRangeInterface, ["inferResultRanges"]>]>,
Arguments<(ins Arith_SignlessIntegerOrIndexLike:$lhs,
Arith_SignlessIntegerOrIndexLike:$rhs,
UnitAttr:$isExact)>,
Results<(outs Arith_SignlessIntegerOrIndexLike:$result)> {
let assemblyFormat = [{ $lhs `,` $rhs (`exact` $isExact^)?
attr-dict `:` type($result) }];
}
//===----------------------------------------------------------------------===//
// ConstantOp
//===----------------------------------------------------------------------===//
def Arith_ConstantOp : Op<Arith_Dialect, "constant",
[ConstantLike, Pure,
DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>,
AllTypesMatch<["value", "result"]>,
DeclareOpInterfaceMethods<InferIntDivisibilityOpInterface>,
DeclareOpInterfaceMethods<InferIntRangeInterface, ["inferResultRanges"]>]> {
let summary = "integer or floating point constant";
let description = [{
The `constant` operation produces an SSA value equal to some integer or
floating-point constant specified by an attribute. This is the way MLIR
forms simple integer and floating point constants.
Example:
```
// Integer constant
%1 = arith.constant 42 : i32
// Equivalent generic form
%1 = "arith.constant"() {value = 42 : i32} : () -> i32
```
}];
let arguments = (ins TypedAttrInterface:$value);
// TODO: Disallow arith.constant to return anything other than a signless
// integer or float like. Downstream users of Arith should only be
// working with signless integers, floats, or vectors/tensors thereof.
// However, it is necessary to allow arith.constant to return vectors/tensors
// of strings and signed/unsigned integers (for now) as an artefact of
// splitting the Standard dialect.
let results = (outs /*SignlessIntegerOrIndexOrFloatLike*/AnyType:$result);
let extraClassDeclaration = [{
/// Whether the constant op can be constructed with a particular value and
/// type.
static bool isBuildableWith(Attribute value, Type type);
/// Build the constant op with `value` and `type` if possible, otherwise
/// returns null.
static ConstantOp materialize(OpBuilder &builder, Attribute value,
Type type, Location loc);
}];
let hasFolder = 1;
let assemblyFormat = "attr-dict $value";
let hasVerifier = 1;
}
//===----------------------------------------------------------------------===//
// AddIOp
//===----------------------------------------------------------------------===//
def Arith_AddIOp : Arith_IntBinaryOpWithOverflowFlags<"addi",
[Commutative, DeclareOpInterfaceMethods<InferIntDivisibilityOpInterface>]> {
let summary = "integer addition operation";
let description = [{
Performs N-bit addition on the operands. The operands are interpreted as
unsigned bitvectors. The result is represented by a bitvector containing the
mathematical value of the addition modulo 2^n, where `n` is the bitwidth.
Because `arith` integers use a two's complement representation, this operation
is applicable on both signed and unsigned integer operands.
The `addi` operation takes two operands and returns one result, each of
these is required to be the same type. This type may be an integer scalar type,
a vector whose element type is integer, or a tensor of integers.
This op supports `nuw`/`nsw` overflow flags which stands for
"No Unsigned Wrap" and "No Signed Wrap", respectively. If the `nuw` and/or
`nsw` flags are present, and an unsigned/signed overflow occurs
(respectively), the result is poison.
Example:
```mlir
// Scalar addition.
%a = arith.addi %b, %c : i64
// Scalar addition with overflow flags.
%a = arith.addi %b, %c overflow<nsw, nuw> : i64
// SIMD vector element-wise addition.
%f = arith.addi %g, %h : vector<4xi32>
// Tensor element-wise addition.
%x = arith.addi %y, %z : tensor<4x?xi8>
```
}];
let hasFolder = 1;
let hasCanonicalizer = 1;
}
//===----------------------------------------------------------------------===//
// AddUIExtendedOp
//===----------------------------------------------------------------------===//
def Arith_AddUIExtendedOp : Arith_Op<"addui_extended", [Pure, Commutative,
AllTypesMatch<["lhs", "rhs", "sum"]>]> {
let summary = [{
extended unsigned integer addition operation returning sum and overflow bit
}];
let description = [{
Performs (N+1)-bit addition on zero-extended operands. Returns two results:
the N-bit sum (same type as both operands), and the overflow bit
(boolean-like), where `1` indicates unsigned addition overflow, while `0`
indicates no overflow.
Example:
```mlir
// Scalar addition.
%sum, %overflow = arith.addui_extended %b, %c : i64, i1
// Vector element-wise addition.
%d:2 = arith.addui_extended %e, %f : vector<4xi32>, vector<4xi1>
// Tensor element-wise addition.
%x:2 = arith.addui_extended %y, %z : tensor<4x?xi8>, tensor<4x?xi1>
```
}];
let arguments = (ins Arith_SignlessIntegerOrIndexLike:$lhs, Arith_SignlessIntegerOrIndexLike:$rhs);
let results = (outs Arith_SignlessIntegerOrIndexLike:$sum, BoolLike:$overflow);
let assemblyFormat = [{
$lhs `,` $rhs attr-dict `:` type($sum) `,` type($overflow)
}];
let builders = [
OpBuilder<(ins "Value":$lhs, "Value":$rhs), [{
build($_builder, $_state, lhs.getType(), ::getI1SameShape(lhs.getType()),
lhs, rhs);
}]>
];
let hasFolder = 1;
let hasCanonicalizer = 1;
let extraClassDeclaration = [{
std::optional<SmallVector<int64_t, 4>> getShapeForUnroll();
}];
}
//===----------------------------------------------------------------------===//
// SubUIExtendedOp
//===----------------------------------------------------------------------===//
def Arith_SubUIExtendedOp : Arith_Op<"subui_extended", [Pure,
AllTypesMatch<["lhs", "rhs", "diff"]>]> {
let summary = [{
extended unsigned integer subtraction operation returning difference and
borrow bit
}];
let description = [{
Performs (N+1)-bit subtraction on zero-extended operands. Returns two
results: the N-bit difference (same type as both operands), and the borrow
bit (boolean-like), where `1` indicates unsigned subtraction underflow
(i.e. `lhs < rhs` when interpreted as unsigned), while `0` indicates no
underflow.
Example:
```mlir
// Scalar subtraction.
%diff, %borrow = arith.subui_extended %b, %c : i64, i1
// Vector element-wise subtraction.
%d:2 = arith.subui_extended %e, %f : vector<4xi32>, vector<4xi1>
// Tensor element-wise subtraction.
%x:2 = arith.subui_extended %y, %z : tensor<4x?xi8>, tensor<4x?xi1>
```
}];
let arguments = (ins Arith_SignlessIntegerOrIndexLike:$lhs, Arith_SignlessIntegerOrIndexLike:$rhs);
let results = (outs Arith_SignlessIntegerOrIndexLike:$diff, BoolLike:$borrow);
let assemblyFormat = [{
$lhs `,` $rhs attr-dict `:` type($diff) `,` type($borrow)
}];
let builders = [
OpBuilder<(ins "Value":$lhs, "Value":$rhs), [{
build($_builder, $_state, lhs.getType(), ::getI1SameShape(lhs.getType()),
lhs, rhs);
}]>
];
let hasFolder = 1;
let hasCanonicalizer = 1;
let extraClassDeclaration = [{
std::optional<SmallVector<int64_t, 4>> getShapeForUnroll();
}];
}
//===----------------------------------------------------------------------===//
// SubIOp
//===----------------------------------------------------------------------===//
def Arith_SubIOp : Arith_IntBinaryOpWithOverflowFlags<"subi",
[DeclareOpInterfaceMethods<InferIntDivisibilityOpInterface>]> {
let summary = [{
Integer subtraction operation.
}];
let description = [{
Performs N-bit subtraction on the operands. The operands are interpreted as unsigned
bitvectors. The result is represented by a bitvector containing the mathematical
value of the subtraction modulo 2^n, where `n` is the bitwidth. Because `arith`
integers use a two's complement representation, this operation is applicable on
both signed and unsigned integer operands.
The `subi` operation takes two operands and returns one result, each of
these is required to be the same type. This type may be an integer scalar type,
a vector whose element type is integer, or a tensor of integers.
This op supports `nuw`/`nsw` overflow flags which stands for
"No Unsigned Wrap" and "No Signed Wrap", respectively. If the `nuw` and/or
`nsw` flags are present, and an unsigned/signed overflow occurs
(respectively), the result is poison.
Example:
```mlir
// Scalar subtraction.
%a = arith.subi %b, %c : i64
// Scalar subtraction with overflow flags.
%a = arith.subi %b, %c overflow<nsw, nuw> : i64
// SIMD vector element-wise subtraction.
%f = arith.subi %g, %h : vector<4xi32>
// Tensor element-wise subtraction.
%x = arith.subi %y, %z : tensor<4x?xi8>
```
}];
let hasFolder = 1;
let hasCanonicalizer = 1;
}
//===----------------------------------------------------------------------===//
// MulIOp
//===----------------------------------------------------------------------===//
def Arith_MulIOp : Arith_IntBinaryOpWithOverflowFlags<"muli",
[Commutative,
DeclareOpInterfaceMethods<InferIntDivisibilityOpInterface>,
DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>]
> {
let summary = [{
Integer multiplication operation.
}];
let description = [{
Performs N-bit multiplication on the operands. The operands are interpreted as
unsigned bitvectors. The result is represented by a bitvector containing the
mathematical value of the multiplication modulo 2^n, where `n` is the bitwidth.
Because `arith` integers use a two's complement representation, this operation is
applicable on both signed and unsigned integer operands.
The `muli` operation takes two operands and returns one result, each of
these is required to be the same type. This type may be an integer scalar type,
a vector whose element type is integer, or a tensor of integers.
This op supports `nuw`/`nsw` overflow flags which stands for
"No Unsigned Wrap" and "No Signed Wrap", respectively. If the `nuw` and/or
`nsw` flags are present, and an unsigned/signed overflow occurs
(respectively), the result is poison.
Example:
```mlir
// Scalar multiplication.
%a = arith.muli %b, %c : i64
// Scalar multiplication with overflow flags.
%a = arith.muli %b, %c overflow<nsw, nuw> : i64
// SIMD vector element-wise multiplication.
%f = arith.muli %g, %h : vector<4xi32>
// Tensor element-wise multiplication.
%x = arith.muli %y, %z : tensor<4x?xi8>
```
}];
let hasFolder = 1;
let hasCanonicalizer = 1;
}
//===----------------------------------------------------------------------===//
// MulSIExtendedOp
//===----------------------------------------------------------------------===//
def Arith_MulSIExtendedOp : Arith_Op<"mulsi_extended", [Pure, Commutative,
AllTypesMatch<["lhs", "rhs", "low", "high"]>]> {
let summary = [{
extended signed integer multiplication operation
}];
let description = [{
Performs (2*N)-bit multiplication on sign-extended operands. Returns two
N-bit results: the low and the high halves of the product. The low half has
the same value as the result of regular multiplication `arith.muli` with
the same operands.
Example:
```mlir
// Scalar multiplication.
%low, %high = arith.mulsi_extended %a, %b : i32
// Vector element-wise multiplication.
%c:2 = arith.mulsi_extended %d, %e : vector<4xi32>
// Tensor element-wise multiplication.
%x:2 = arith.mulsi_extended %y, %z : tensor<4x?xi8>
```
}];
let arguments = (ins Arith_SignlessIntegerOrIndexLike:$lhs, Arith_SignlessIntegerOrIndexLike:$rhs);
let results = (outs Arith_SignlessIntegerOrIndexLike:$low, Arith_SignlessIntegerOrIndexLike:$high);
let assemblyFormat = "$lhs `,` $rhs attr-dict `:` type($lhs)";
let hasFolder = 1;
let hasCanonicalizer = 1;
let extraClassDeclaration = [{
std::optional<SmallVector<int64_t, 4>> getShapeForUnroll();
}];
}
//===----------------------------------------------------------------------===//
// MulUIExtendedOp
//===----------------------------------------------------------------------===//
def Arith_MulUIExtendedOp : Arith_Op<"mului_extended", [Pure, Commutative,
AllTypesMatch<["lhs", "rhs", "low", "high"]>]> {
let summary = [{
extended unsigned integer multiplication operation
}];
let description = [{
Performs (2*N)-bit multiplication on zero-extended operands. Returns two
N-bit results: the low and the high halves of the product. The low half has
the same value as the result of regular multiplication `arith.muli` with
the same operands.
Example:
```mlir
// Scalar multiplication.
%low, %high = arith.mului_extended %a, %b : i32
// Vector element-wise multiplication.
%c:2 = arith.mului_extended %d, %e : vector<4xi32>
// Tensor element-wise multiplication.
%x:2 = arith.mului_extended %y, %z : tensor<4x?xi8>
```
}];
let arguments = (ins Arith_SignlessIntegerOrIndexLike:$lhs, Arith_SignlessIntegerOrIndexLike:$rhs);
let results = (outs Arith_SignlessIntegerOrIndexLike:$low, Arith_SignlessIntegerOrIndexLike:$high);
let assemblyFormat = "$lhs `,` $rhs attr-dict `:` type($lhs)";
let hasFolder = 1;
let hasCanonicalizer = 1;
let extraClassDeclaration = [{
std::optional<SmallVector<int64_t, 4>> getShapeForUnroll();
}];
}
//===----------------------------------------------------------------------===//
// DivUIOp
//===----------------------------------------------------------------------===//
def Arith_DivUIOp : Arith_IntBinaryOpWithExactFlag<"divui",
[ConditionallySpeculatable,
DeclareOpInterfaceMethods<InferIntDivisibilityOpInterface>]> {
let summary = "unsigned integer division operation";
let description = [{
Unsigned integer division. Rounds towards zero. Treats the leading bit as
the most significant, i.e. for `i16` given two's complement representation,
`6 / -2 = 6 / (2^16 - 2) = 0`.
Division by zero is undefined behavior. When applied to `vector` and
`tensor` values, the behavior is undefined if _any_ elements are divided by
zero.
If the `exact` attribute is present, the result value is poison if `lhs` is
not a multiple of `rhs`.
Example:
```mlir
// Scalar unsigned integer division.
%a = arith.divui %b, %c : i64
// Scalar unsigned integer division where %b is known to be a multiple of %c.
%a = arith.divui %b, %c exact : i64
// SIMD vector element-wise division.
%f = arith.divui %g, %h : vector<4xi32>
// Tensor element-wise integer division.
%x = arith.divui %y, %z : tensor<4x?xi8>
```
}];
let extraClassDeclaration = [{
/// Interface method for ConditionallySpeculatable.
Speculation::Speculatability getSpeculatability();
}];
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// DivSIOp
//===----------------------------------------------------------------------===//
def Arith_DivSIOp : Arith_IntBinaryOpWithExactFlag<"divsi",
[ConditionallySpeculatable]> {
let summary = "signed integer division operation";
let description = [{
Signed integer division. Rounds towards zero. Treats the leading bit as
sign, i.e. `6 / -2 = -3`.
Divison by zero, or signed division overflow (minimum value divided by -1)
is undefined behavior. When applied to `vector` and `tensor` values, the
behavior is undefined if _any_ of its elements are divided by zero or has a
signed division overflow.
If the `exact` attribute is present, the result value is poison if `lhs` is
not a multiple of `rhs`.
Example:
```mlir
// Scalar signed integer division.
%a = arith.divsi %b, %c : i64
// Scalar signed integer division where %b is known to be a multiple of %c.
%a = arith.divsi %b, %c exact : i64
// SIMD vector element-wise division.
%f = arith.divsi %g, %h : vector<4xi32>
// Tensor element-wise integer division.
%x = arith.divsi %y, %z : tensor<4x?xi8>
```
}];
let extraClassDeclaration = [{
/// Interface method for ConditionallySpeculatable.
Speculation::Speculatability getSpeculatability();
}];
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// CeilDivUIOp
//===----------------------------------------------------------------------===//
def Arith_CeilDivUIOp : Arith_IntBinaryOp<"ceildivui",
[ConditionallySpeculatable]> {
let summary = "unsigned ceil integer division operation";
let description = [{
Unsigned integer division. Rounds towards positive infinity. Treats the
leading bit as the most significant, i.e. for `i16` given two's complement
representation, `6 / -2 = 6 / (2^16 - 2) = 1`.
Division by zero is undefined behavior. When applied to `vector` and
`tensor` values, the behavior is undefined if _any_ elements are divided by
zero.
Example:
```mlir
// Scalar unsigned integer division.
%a = arith.ceildivui %b, %c : i64
```
}];
let extraClassDeclaration = [{
/// Interface method for ConditionallySpeculatable.
Speculation::Speculatability getSpeculatability();
}];
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// CeilDivSIOp
//===----------------------------------------------------------------------===//
def Arith_CeilDivSIOp : Arith_IntBinaryOp<"ceildivsi",
[ConditionallySpeculatable]> {
let summary = "signed ceil integer division operation";
let description = [{
Signed integer division. Rounds towards positive infinity, i.e. `7 / -2 = -3`.
Divison by zero, or signed division overflow (minimum value divided by -1)
is undefined behavior. When applied to `vector` and `tensor` values, the
behavior is undefined if _any_ of its elements are divided by zero or has a
signed division overflow.
Example:
```mlir
// Scalar signed integer division.
%a = arith.ceildivsi %b, %c : i64
```
}];
let extraClassDeclaration = [{
/// Interface method for ConditionallySpeculatable.
Speculation::Speculatability getSpeculatability();
}];
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// FloorDivSIOp
//===----------------------------------------------------------------------===//
def Arith_FloorDivSIOp : Arith_TotalIntBinaryOp<"floordivsi"> {
let summary = "signed floor integer division operation";
let description = [{
Signed integer division. Rounds towards negative infinity, i.e. `5 / -2 = -3`.
Divison by zero, or signed division overflow (minimum value divided by -1)
is undefined behavior. When applied to `vector` and `tensor` values, the
behavior is undefined if _any_ of its elements are divided by zero or has a
signed division overflow.
Example:
```mlir
// Scalar signed integer division.
%a = arith.floordivsi %b, %c : i64
```
}];
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// RemUIOp
//===----------------------------------------------------------------------===//
def Arith_RemUIOp : Arith_IntBinaryOp<"remui",
[ConditionallySpeculatable]> {
let summary = "unsigned integer division remainder operation";
let description = [{
Unsigned integer division remainder. Treats the leading bit as the most
significant, i.e. for `i16`, `6 % -2 = 6 % (2^16 - 2) = 6`.
Division by zero is undefined behavior. When applied to `vector` and
`tensor` values, the behavior is undefined if _any_ elements are divided by
zero.
Example:
```mlir
// Scalar unsigned integer division remainder.
%a = arith.remui %b, %c : i64
// SIMD vector element-wise division remainder.
%f = arith.remui %g, %h : vector<4xi32>
// Tensor element-wise integer division remainder.
%x = arith.remui %y, %z : tensor<4x?xi8>
```
}];
let extraClassDeclaration = [{
/// Interface method for ConditionallySpeculatable.
Speculation::Speculatability getSpeculatability();
}];
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// RemSIOp
//===----------------------------------------------------------------------===//
def Arith_RemSIOp : Arith_IntBinaryOp<"remsi",
[ConditionallySpeculatable]> {
let summary = "signed integer division remainder operation";
let description = [{
Signed integer division remainder. Treats the leading bit as sign, i.e. `6 %
-2 = 0`.
Division by zero is undefined behavior. When applied to `vector` and
`tensor` values, the behavior is undefined if _any_ elements are divided by
zero.
Example:
```mlir
// Scalar signed integer division remainder.
%a = arith.remsi %b, %c : i64
// SIMD vector element-wise division remainder.
%f = arith.remsi %g, %h : vector<4xi32>
// Tensor element-wise integer division remainder.
%x = arith.remsi %y, %z : tensor<4x?xi8>
```
}];
let extraClassDeclaration = [{
/// Interface method for ConditionallySpeculatable.
Speculation::Speculatability getSpeculatability();
}];
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// AndIOp
//===----------------------------------------------------------------------===//
def Arith_AndIOp : Arith_TotalIntBinaryOp<"andi", [Commutative, Idempotent]> {
let summary = "integer binary and";
let description = [{
The `andi` operation takes two operands and returns one result, each of
these is required to be the same type. This type may be an integer scalar
type, a vector whose element type is integer, or a tensor of integers. It
has no standard attributes.
Example:
```mlir
// Scalar integer bitwise and.
%a = arith.andi %b, %c : i64
// SIMD vector element-wise bitwise integer and.
%f = arith.andi %g, %h : vector<4xi32>
// Tensor element-wise bitwise integer and.
%x = arith.andi %y, %z : tensor<4x?xi8>
```
}];
let hasFolder = 1;
let hasCanonicalizer = 1;
}
//===----------------------------------------------------------------------===//
// OrIOp
//===----------------------------------------------------------------------===//
def Arith_OrIOp : Arith_TotalIntBinaryOp<"ori", [Commutative, Idempotent]> {
let summary = "integer binary or";
let description = [{
The `ori` operation takes two operands and returns one result, each of these
is required to be the same type. This type may be an integer scalar type, a
vector whose element type is integer, or a tensor of integers. It has no
standard attributes.
Example:
```mlir
// Scalar integer bitwise or.
%a = arith.ori %b, %c : i64
// SIMD vector element-wise bitwise integer or.
%f = arith.ori %g, %h : vector<4xi32>
// Tensor element-wise bitwise integer or.
%x = arith.ori %y, %z : tensor<4x?xi8>
```
}];
let hasFolder = 1;
let hasCanonicalizer = 1;
}
//===----------------------------------------------------------------------===//
// XOrIOp
//===----------------------------------------------------------------------===//
def Arith_XOrIOp : Arith_TotalIntBinaryOp<"xori", [Commutative]> {
let summary = "integer binary xor";
let description = [{
The `xori` operation takes two operands and returns one result, each of
these is required to be the same type. This type may be an integer scalar
type, a vector whose element type is integer, or a tensor of integers. It
has no standard attributes.
Example:
```mlir
// Scalar integer bitwise xor.
%a = arith.xori %b, %c : i64
// SIMD vector element-wise bitwise integer xor.
%f = arith.xori %g, %h : vector<4xi32>
// Tensor element-wise bitwise integer xor.
%x = arith.xori %y, %z : tensor<4x?xi8>
```
}];
let hasFolder = 1;
let hasCanonicalizer = 1;
}
//===----------------------------------------------------------------------===//
// ShLIOp
//===----------------------------------------------------------------------===//
def Arith_ShLIOp : Arith_IntBinaryOpWithOverflowFlags<"shli"> {
let summary = "integer left-shift";
let description = [{
The `shli` operation shifts the integer value of the first operand to the left
by the integer value of the second operand. The second operand is interpreted as
unsigned. The low order bits are filled with zeros. If the value of the second
operand is greater or equal than the bitwidth of the first operand, then the
operation returns poison.
This op supports `nuw`/`nsw` overflow flags which stands for
"No Unsigned Wrap" and "No Signed Wrap", respectively. If the `nuw` and/or
`nsw` flags are present, and an unsigned/signed overflow occurs
(respectively), the result is poison.
Example:
```mlir
%1 = arith.constant 5 : i8 // %1 is 0b00000101
%2 = arith.constant 3 : i8
%3 = arith.shli %1, %2 : i8 // %3 is 0b00101000
%4 = arith.shli %1, %2 overflow<nsw, nuw> : i8
```
}];
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// ShRUIOp
//===----------------------------------------------------------------------===//
def Arith_ShRUIOp : Arith_IntBinaryOpWithExactFlag<"shrui", [Pure]> {
let summary = "unsigned integer right-shift";
let description = [{
The `shrui` operation shifts an integer value of the first operand to the right
by the value of the second operand. The first operand is interpreted as unsigned,
and the second operand is interpreted as unsigned. The high order bits are always
filled with zeros. If the value of the second operand is greater or equal than the
bitwidth of the first operand, then the operation returns poison.
If the `exact` attribute is present, the result value of shrui is a poison
value if any of the bits shifted out are non-zero.
Example:
```mlir
%1 = arith.constant 160 : i8 // %1 is 0b10100000
%2 = arith.constant 3 : i8
%3 = arith.constant 6 : i8
%4 = arith.shrui %1, %2 exact : i8 // %4 is 0b00010100
%5 = arith.shrui %1, %3 : i8 // %3 is 0b00000010
```
}];
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// ShRSIOp
//===----------------------------------------------------------------------===//
def Arith_ShRSIOp : Arith_IntBinaryOpWithExactFlag<"shrsi", [Pure]> {
let summary = "signed integer right-shift";
let description = [{
The `shrsi` operation shifts an integer value of the first operand to the right
by the value of the second operand. The first operand is interpreted as signed,
and the second operand is interpreter as unsigned. The high order bits in the
output are filled with copies of the most-significant bit of the shifted value
(which means that the sign of the value is preserved). If the value of the second
operand is greater or equal than bitwidth of the first operand, then the operation
returns poison.
If the `exact` attribute is present, the result value of shrsi is a poison
value if any of the bits shifted out are non-zero.
Example:
```mlir
%1 = arith.constant 160 : i8 // %1 is 0b10100000
%2 = arith.constant 3 : i8
%3 = arith.shrsi %1, %2 exact : i8 // %3 is 0b11110100
%4 = arith.constant 98 : i8 // %4 is 0b01100010
%5 = arith.shrsi %4, %2 : i8 // %5 is 0b00001100
```
}];
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// NegFOp
//===----------------------------------------------------------------------===//
def Arith_NegFOp : Arith_FloatUnaryOp<"negf"> {
let summary = "floating point negation";
let description = [{
The `negf` operation computes the negation of a given value. It takes one
operand and returns one result of the same type. This type may be a float
scalar type, a vector whose element type is float, or a tensor of floats.
It has no standard attributes.
Example:
```mlir
// Scalar negation value.
%a = arith.negf %b : f64
// SIMD vector element-wise negation value.
%f = arith.negf %g : vector<4xf32>
// Tensor element-wise negation value.
%x = arith.negf %y : tensor<4x?xf8>
```
}];
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// FlushDenormalsOp
//===----------------------------------------------------------------------===//
def Arith_FlushDenormalsOp : Arith_FloatUnaryOp<"flush_denormals"> {
let summary = "flush denormal floating-point values to zero";
let description = [{
The `flush_denormals` operation takes a floating-point value and returns
the input value if it is a normal (or zero, infinity, or NaN) value, or
a zero of the same type if the input is a denormal (subnormal) value.
The sign of zero is preserved when flushing a denormal: negative
denormals flush to `-0.0`, positive denormals flush to `+0.0`.
A denormal number ("subnormal number" in IEEE-754) is a non-zero floating
point number that is smaller (closer to zero) than the smallest normal
number. Denormals fill the underflow gap around zero in floating-point
arithmetics, but may come at a runtime cost on some architectures.
The input and result are required to be the same type. This type may be
a floating-point scalar type, a vector whose element type is a
floating-point type, or a tensor of floats. When operating on vectors
or tensors, the operation is applied elementwise.
Example:
```mlir
// Scalar denormal flushing.
%a = arith.flush_denormals %b : f32
// SIMD vector element-wise denormal flushing.
%f = arith.flush_denormals %g : vector<4xf32>
// Tensor element-wise denormal flushing.
%x = arith.flush_denormals %y : tensor<4x?xbf16>
```
}];
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// AddFOp
//===----------------------------------------------------------------------===//
def Arith_AddFOp : Arith_FloatBinaryOpWithRoundingMode<"addf", [Commutative]> {
let summary = "floating point addition operation";
let description = [{
The `addf` operation takes two operands and returns one result, each of
these is required to be the same type. This type may be a floating point
scalar type, a vector whose element type is a floating point type, or a
floating point tensor.
Example:
```mlir
// Scalar addition.
%a = arith.addf %b, %c : f64
// SIMD vector addition, e.g. for Intel SSE.
%f = arith.addf %g, %h : vector<4xf32>
// Tensor addition.
%x = arith.addf %y, %z : tensor<4x?xbf16>
// Scalar addition with rounding mode.
%a = arith.addf %b, %c to_nearest_even : f64
```
}];
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// SubFOp
//===----------------------------------------------------------------------===//
def Arith_SubFOp : Arith_FloatBinaryOpWithRoundingMode<"subf"> {
let summary = "floating point subtraction operation";
let description = [{
The `subf` operation takes two operands and returns one result, each of
these is required to be the same type. This type may be a floating point
scalar type, a vector whose element type is a floating point type, or a
floating point tensor.
Example:
```mlir
// Scalar subtraction.
%a = arith.subf %b, %c : f64
// SIMD vector subtraction, e.g. for Intel SSE.
%f = arith.subf %g, %h : vector<4xf32>
// Tensor subtraction.
%x = arith.subf %y, %z : tensor<4x?xbf16>
// Scalar subtraction with rounding mode.
%a = arith.subf %b, %c downward : f64
```
}];
let hasFolder = 1;
let hasCanonicalizer = 1;
}
//===----------------------------------------------------------------------===//
// MaximumFOp
//===----------------------------------------------------------------------===//
def Arith_MaximumFOp : Arith_FloatBinaryOp<"maximumf", [Commutative]> {
let summary = "floating-point maximum operation";
let description = [{
Returns the maximum of the two arguments, treating -0.0 as less than +0.0.
If one of the arguments is NaN, then the result is also NaN.
Example:
```mlir
// Scalar floating-point maximum.
%a = arith.maximumf %b, %c : f64
```
}];
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// MaxNumFOp
//===----------------------------------------------------------------------===//
def Arith_MaxNumFOp : Arith_FloatBinaryOp<"maxnumf", [Commutative]> {
let summary = "floating-point maximum operation";
let description = [{
Returns the maximum of the two arguments.
If the arguments are -0.0 and +0.0, then the result is either of them.
If one of the arguments is NaN, then the result is the other argument.
Example:
```mlir
// Scalar floating-point maximum.
%a = arith.maxnumf %b, %c : f64
```
}];
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// MaxSIOp
//===----------------------------------------------------------------------===//
def Arith_MaxSIOp : Arith_TotalIntBinaryOp<"maxsi",
[Commutative, DeclareOpInterfaceMethods<InferIntDivisibilityOpInterface>]> {
let summary = "signed integer maximum operation";
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// MaxUIOp
//===----------------------------------------------------------------------===//
def Arith_MaxUIOp : Arith_TotalIntBinaryOp<"maxui",
[Commutative, DeclareOpInterfaceMethods<InferIntDivisibilityOpInterface>]> {
let summary = "unsigned integer maximum operation";
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// MinimumFOp
//===----------------------------------------------------------------------===//
def Arith_MinimumFOp : Arith_FloatBinaryOp<"minimumf", [Commutative]> {
let summary = "floating-point minimum operation";
let description = [{
Returns the minimum of the two arguments, treating -0.0 as less than +0.0.
If one of the arguments is NaN, then the result is also NaN.
Example:
```mlir
// Scalar floating-point minimum.
%a = arith.minimumf %b, %c : f64
```
}];
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// MinNumFOp
//===----------------------------------------------------------------------===//
def Arith_MinNumFOp : Arith_FloatBinaryOp<"minnumf", [Commutative]> {
let summary = "floating-point minimum operation";
let description = [{
Returns the minimum of the two arguments.
If the arguments are -0.0 and +0.0, then the result is either of them.
If one of the arguments is NaN, then the result is the other argument.
Example:
```mlir
// Scalar floating-point minimum.
%a = arith.minnumf %b, %c : f64
```
}];
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// MinSIOp
//===----------------------------------------------------------------------===//
def Arith_MinSIOp : Arith_TotalIntBinaryOp<"minsi",
[Commutative, DeclareOpInterfaceMethods<InferIntDivisibilityOpInterface>]> {
let summary = "signed integer minimum operation";
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// MinUIOp
//===----------------------------------------------------------------------===//
def Arith_MinUIOp : Arith_TotalIntBinaryOp<"minui",
[Commutative, DeclareOpInterfaceMethods<InferIntDivisibilityOpInterface>]> {
let summary = "unsigned integer minimum operation";
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// MulFOp
//===----------------------------------------------------------------------===//
def Arith_MulFOp : Arith_FloatBinaryOpWithRoundingMode<"mulf", [Commutative]> {
let summary = "floating point multiplication operation";
let description = [{
The `mulf` operation takes two operands and returns one result, each of
these is required to be the same type. This type may be a floating point
scalar type, a vector whose element type is a floating point type, or a
floating point tensor.
Example:
```mlir
// Scalar multiplication.
%a = arith.mulf %b, %c : f64
// SIMD pointwise vector multiplication, e.g. for Intel SSE.
%f = arith.mulf %g, %h : vector<4xf32>
// Tensor pointwise multiplication.
%x = arith.mulf %y, %z : tensor<4x?xbf16>
// Scalar multiplication with rounding mode.
%a = arith.mulf %b, %c upward : f64
```
}];
let hasFolder = 1;
let hasCanonicalizer = 1;
}
//===----------------------------------------------------------------------===//
// DivFOp
//===----------------------------------------------------------------------===//
def Arith_DivFOp : Arith_FloatBinaryOpWithRoundingMode<"divf"> {
let summary = "floating point division operation";
let description = [{
The `divf` operation takes two operands and returns one result, each of
these is required to be the same type. This type may be a floating point
scalar type, a vector whose element type is a floating point type, or a
floating point tensor.
Example:
```mlir
// Scalar division.
%a = arith.divf %b, %c : f64
// Scalar division with rounding mode.
%a = arith.divf %b, %c toward_zero : f64
```
}];
let hasFolder = 1;
let hasCanonicalizer = 1;
}
//===----------------------------------------------------------------------===//
// RemFOp
//===----------------------------------------------------------------------===//
def Arith_RemFOp : Arith_FloatBinaryOp<"remf"> {
let summary = "floating point division remainder operation";
let description = [{
Returns the floating point division remainder.
The remainder has the same sign as the dividend (lhs operand).
TODO: Add support for rounding modes.
}];
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// ExtUIOp
//===----------------------------------------------------------------------===//
def Arith_ExtUIOp :
Arith_IToICastOp<"extui", [DeclareOpInterfaceMethods<ArithNonNegFlagInterface>]> {
let summary = "integer zero extension operation";
let description = [{
The integer zero extension operation takes an integer input of
width M and an integer destination type of width N. The destination
bit-width must be larger than the input bit-width (N > M).
The top-most (N - M) bits of the output are filled with zeros.
When the `nneg` flag is present, the operand is assumed to have
the most significant bit set to 0. In this case, zero extension is
equivalent to sign extension. When this assumption is violated, the
result is poison.
Example:
```mlir
%1 = arith.constant 5 : i3 // %1 is 0b101
%2 = arith.extui %1 : i3 to i6 // %2 is 0b000101
%3 = arith.constant 2 : i3 // %3 is 0b010
%4 = arith.extui %3 : i3 to i6 // %4 is 0b000010
%5 = arith.extui %0 : vector<2 x i32> to vector<2 x i64>
// Zero extension with nneg flag.
%6 = arith.extui %3 nneg : i3 to i6
```
}];
let arguments = (ins SignlessFixedWidthIntegerLike:$in, UnitAttr:$nonNeg);
let results = (outs SignlessFixedWidthIntegerLike:$out);
let assemblyFormat = [{
$in (`nneg` $nonNeg^)? attr-dict `:` type($in) `to` type($out)
}];
let hasFolder = 1;
let hasVerifier = 1;
}
//===----------------------------------------------------------------------===//
// ExtSIOp
//===----------------------------------------------------------------------===//
def Arith_ExtSIOp : Arith_IToICastOp<"extsi"> {
let summary = "integer sign extension operation";
let description = [{
The integer sign extension operation takes an integer input of
width M and an integer destination type of width N. The destination
bit-width must be larger than the input bit-width (N > M).
The top-most (N - M) bits of the output are filled with copies
of the most-significant bit of the input.
Example:
```mlir
%1 = arith.constant 5 : i3 // %1 is 0b101
%2 = arith.extsi %1 : i3 to i6 // %2 is 0b111101
%3 = arith.constant 2 : i3 // %3 is 0b010
%4 = arith.extsi %3 : i3 to i6 // %4 is 0b000010
%5 = arith.extsi %0 : vector<2 x i32> to vector<2 x i64>
```
}];
let hasFolder = 1;
let hasCanonicalizer = 1;
let hasVerifier = 1;
}
//===----------------------------------------------------------------------===//
// ExtFOp
//===----------------------------------------------------------------------===//
def Arith_ExtFOp : Arith_FToFCastOp<"extf", [DeclareOpInterfaceMethods<ArithFastMathInterface>]> {
let summary = "cast from floating-point to wider floating-point";
let description = [{
Cast a floating-point value to a larger floating-point-typed value.
The destination type must to be strictly wider than the source type.
When operating on vectors, casts elementwise.
}];
let hasVerifier = 1;
let hasFolder = 1;
let arguments = (ins FloatLike:$in,
OptionalAttr<Arith_FastMathAttr>:$fastmath);
let results = (outs FloatLike:$out);
let assemblyFormat = [{ $in (`fastmath` `` $fastmath^)?
attr-dict `:` type($in) `to` type($out) }];
}
//===----------------------------------------------------------------------===//
// Scaling ExtFOp
//===----------------------------------------------------------------------===//
def Arith_ScalingExtFOp
: Arith_Op<
"scaling_extf", [Pure, SameInputOutputTensorDims,
DeclareOpInterfaceMethods<ArithFastMathInterface>,
DeclareOpInterfaceMethods<CastOpInterface>]>,
Arguments<(ins FloatLike:$in, FloatLike:$scale,
OptionalAttr<Arith_FastMathAttr>:$fastmath)>,
Results<(outs FloatLike:$out)> {
let summary = "Upcasts input floats using provided scales values following "
"OCP MXFP Spec";
let description = [{
This operation upcasts input floating-point values using provided scale
values. It expects both scales and the input operand to be of the same shape,
making the operation elementwise. Scales are usually calculated per block
following the OCP MXFP spec as described in https://arxiv.org/abs/2310.10537.
If scales are calculated per block where blockSize != 1, then scales may
require broadcasting to make this operation elementwise. For example, let's
say the input is of shape `<dim1 x dim2 x ... dimN>`. Given blockSize != 1 and
assuming quantization happens on the last axis, the input can be reshaped to
`<dim1 x dim2 x ... (dimN/blockSize) x blockSize>`. Scales will be calculated
per block on the last axis. Therefore, scales will be of shape
`<dim1 x dim2 x ... (dimN/blockSize) x 1>`. Scales could also be of some other
shape as long as it is broadcast compatible with the input, e.g.,
`<1 x 1 x ... (dimN/blockSize) x 1>`.
In this example, before calling into `arith.scaling_extf`, scales must be
broadcasted to `<dim1 x dim2 x dim3 ... (dimN/blockSize) x blockSize>`. Note
that there could be multiple quantization axes. Internally,
`arith.scaling_extf` would perform the following:
```mlir
// Cast scale to result type.
%0 = arith.truncf %1 : f32 to f8E8M0FNU
%1 = arith.extf %0 : f8E8M0FNU to f16
// Cast input to result type.
%2 = arith.extf %3 : f4E2M1FN to f16
// Perform scaling
%3 = arith.mulf %2, %1 : f16
```
It propagates NaN values. Therefore, if either scale or the input element
contains NaN, then the output element value will also be a NaN.
Example:
```mlir
// Upcast from f4E2M1FN to f32.
%a = arith.scaling_extf %b, %c : f4E2M1FN, f8E8M0FNU to f32
// Element-wise upcast with broadcast (blockSize = 32).
%f = vector.broadcast %g : vector<1xf8E8M0FNU> to vector<32xf8E8M0FNU>
%h = arith.scaling_extf %i, %f : vector<32xf4E2M1FN>, vector<32xf8E8M0FNU> to vector<32xbf16>
```
}];
let hasVerifier = 1;
let assemblyFormat =
[{ $in `,` $scale (`fastmath` `` $fastmath^)? attr-dict `:`
type($in) `,` type($scale) `to` type($out)}];
}
//===----------------------------------------------------------------------===//
// TruncIOp
//===----------------------------------------------------------------------===//
def Arith_TruncIOp : Arith_Op<"trunci",
[Pure, SameOperandsAndResultShape, SameInputOutputTensorDims,
DeclareOpInterfaceMethods<CastOpInterface>,
DeclareOpInterfaceMethods<InferIntRangeInterface, ["inferResultRanges"]>,
DeclareOpInterfaceMethods<ArithIntegerOverflowFlagsInterface>]> {
let summary = "integer truncation operation";
let description = [{
The integer truncation operation takes an integer input of
width M and an integer destination type of width N. The destination
bit-width must be smaller than the input bit-width (N < M).
The top-most (N - M) bits of the input are discarded.
This op supports `nuw`/`nsw` overflow flags which stands for "No Unsigned
Wrap" and "No Signed Wrap", respectively. If the nuw keyword is present,
and any of the truncated bits are non-zero, the result is a poison value.
If the nsw keyword is present, and any of the truncated bits are not the
same as the top bit of the truncation result, the result is a poison value.
Example:
```mlir
// Scalar truncation.
%1 = arith.constant 21 : i5 // %1 is 0b10101
%2 = arith.trunci %1 : i5 to i4 // %2 is 0b0101
%3 = arith.trunci %1 : i5 to i3 // %3 is 0b101
// Vector truncation.
%4 = arith.trunci %0 : vector<2 x i32> to vector<2 x i16>
// Scalar truncation with overflow flags.
%5 = arith.trunci %a overflow<nsw, nuw> : i32 to i16
```
}];
let arguments = (ins
SignlessFixedWidthIntegerLike:$in,
DefaultValuedAttr<Arith_IntegerOverflowAttr,
"::mlir::arith::IntegerOverflowFlags::none">:$overflowFlags);
let results = (outs SignlessFixedWidthIntegerLike:$out);
let assemblyFormat = [{
$in (`overflow` `` $overflowFlags^)? attr-dict
`:` type($in) `to` type($out)
}];
let hasFolder = 1;
let hasCanonicalizer = 1;
let hasVerifier = 1;
}
//===----------------------------------------------------------------------===//
// TruncFOp
//===----------------------------------------------------------------------===//
def Arith_TruncFOp :
Arith_Op<"truncf",
[Pure, SameOperandsAndResultShape, SameInputOutputTensorDims,
DeclareOpInterfaceMethods<ArithRoundingModeInterface>,
DeclareOpInterfaceMethods<ArithFastMathInterface>,
DeclareOpInterfaceMethods<CastOpInterface>]>,
Arguments<(ins FloatLike:$in,
OptionalAttr<Arith_RoundingModeAttr>:$roundingmode,
OptionalAttr<Arith_FastMathAttr>:$fastmath)>,
Results<(outs FloatLike:$out)> {
let summary = "cast from floating-point to narrower floating-point";
let description = [{
Truncate a floating-point value to a smaller floating-point-typed value.
The destination type must be strictly narrower than the source type.
When operating on vectors, casts elementwise.
}];
let builders = [
OpBuilder<(ins "Type":$out, "Value":$in), [{
$_state.addOperands(in);
$_state.addTypes(out);
}]>
];
let hasFolder = 1;
let hasCanonicalizer = 1;
let hasVerifier = 1;
let assemblyFormat = [{ $in ($roundingmode^)?
(`fastmath` `` $fastmath^)?
attr-dict `:` type($in) `to` type($out) }];
}
//===----------------------------------------------------------------------===//
// ConvertFOp
//===----------------------------------------------------------------------===//
def Arith_ConvertFOp :
Arith_Op<"convertf",
[Pure, SameOperandsAndResultShape, SameInputOutputTensorDims,
DeclareOpInterfaceMethods<ArithRoundingModeInterface>,
DeclareOpInterfaceMethods<ArithFastMathInterface>,
DeclareOpInterfaceMethods<CastOpInterface>]>,
Arguments<(ins FloatLike:$in,
OptionalAttr<Arith_RoundingModeAttr>:$roundingmode,
OptionalAttr<Arith_FastMathAttr>:$fastmath)>,
Results<(outs FloatLike:$out)> {
let summary = "cast between floating-point types of the same bitwidth";
let description = [{
Cast a floating-point value to a different floating-point type of the same
bitwidth. This operation handles conversions between types that have the
same bitwidth but different semantics (e.g., f16 to bf16), which cannot
be represented by `arith.extf` or `arith.truncf`.
The source and destination element types must be different and must have
the same bitwidth. When operating on vectors, casts elementwise.
}];
let hasFolder = 1;
let hasVerifier = 1;
let assemblyFormat = [{ $in ($roundingmode^)?
(`fastmath` `` $fastmath^)?
attr-dict `:` type($in) `to` type($out) }];
}
//===----------------------------------------------------------------------===//
// Scaling TruncFOp
//===----------------------------------------------------------------------===//
def Arith_ScalingTruncFOp
: Arith_Op<"scaling_truncf",
[Pure, SameInputOutputTensorDims,
DeclareOpInterfaceMethods<ArithRoundingModeInterface>,
DeclareOpInterfaceMethods<ArithFastMathInterface>,
DeclareOpInterfaceMethods<CastOpInterface>]>,
Arguments<(ins FloatLike:$in, FloatLike:$scale,
OptionalAttr<Arith_RoundingModeAttr>:$roundingmode,
OptionalAttr<Arith_FastMathAttr>:$fastmath)>,
Results<(outs FloatLike:$out)> {
let summary = "Downcasts input floating point values using provided scales "
"values following OCP MXFP Spec";
let description = [{
This operation downcasts input using the provided scale values. It expects
both scales and the input operand to be of the same shape and, therefore,
makes the operation elementwise. Scales are usually calculated per block
following the OCP MXFP spec as described in https://arxiv.org/abs/2310.10537.
Users are required to normalize and clamp the scales as necessary before calling
passing them to this operation. OCP MXFP spec also does the flushing of denorms
on the input operand, which should be handled during lowering by passing appropriate
fastMath flag to this operation.
If scales are calculated per block where blockSize != 1, scales may require
broadcasting to make this operation elementwise. For example, let's say the
input is of shape `<dim1 x dim2 x ... dimN>`. Given blockSize != 1 and
assuming quantization happens on the last axis, the input can be reshaped to
`<dim1 x dim2 x ... (dimN/blockSize) x blockSize>`. Scales will be calculated
per block on the last axis. Therefore, scales will be of shape
`<dim1 x dim2 x ... (dimN/blockSize) x 1>`. Scales could also be of some other
shape as long as it is broadcast compatible with the input, e.g.,
`<1 x 1 x ... (dimN/blockSize) x 1>`.
In this example, before calling into `arith.scaling_truncf`, scales must be
broadcasted to `<dim1 x dim2 x dim3 ... (dimN/blockSize) x blockSize>`. Note
that there could be multiple quantization axes. Internally,
`arith.scaling_truncf` would perform the following:
```mlir
// Cast scale to input type.
%0 = arith.truncf %1 : f32 to f8E8M0FNU
%1 = arith.extf %0 : f8E8M0FNU to f16
// Perform scaling.
%3 = arith.divf %2, %1 : f16
// Cast to result type.
%4 = arith.truncf %3 : f16 to f4E2M1FN
```
Example:
```mlir
// Downcast from f32 to f4E2M1FN.
%a = arith.scaling_truncf %b, %c : f32, f8E8M0FNU to f4E2M1FN
// Element-wise downcast with broadcast (blockSize = 32).
%f = vector.broadcast %g : vector<1xf8E8M0FNU> to vector<32xf8E8M0FNU>
%h = arith.scaling_truncf %i, %f : vector<32xbf16>, vector<32xf8E8M0FNU> to vector<32xf4E2M1FN>
```
}];
let hasVerifier = 1;
let assemblyFormat =
[{ $in `,` $scale ($roundingmode^)? (`fastmath` `` $fastmath^)? attr-dict `:`
type($in) `,` type($scale) `to` type($out)}];
}
//===----------------------------------------------------------------------===//
// UIToFPOp
//===----------------------------------------------------------------------===//
def Arith_UIToFPOp :
Arith_IToFCastOp<"uitofp", [DeclareOpInterfaceMethods<ArithNonNegFlagInterface>]> {
let summary = "cast from unsigned integer type to floating-point";
let description = [{
Cast from a value interpreted as unsigned integer to the corresponding
floating-point value. When operating on vectors, casts elementwise.
When the `nneg` flag is present, the operand is assumed to have
the most significant bit set to 0. In this case, zero extension is
equivalent to sign extension. When this assumption is violated, the
result is poison.
Example:
```mlir
// Without nneg flag.
%0 = arith.uitofp %a : i32 to f64
// With nneg flag.
%1 = arith.uitofp %a nneg : i32 to f64
```
}];
let arguments = (ins SignlessFixedWidthIntegerLike:$in, UnitAttr:$nonNeg);
let results = (outs FloatLike:$out);
let assemblyFormat = [{
$in (`nneg` $nonNeg^)? attr-dict `:` type($in) `to` type($out)
}];
let hasFolder = 1;
let hasCanonicalizer = 1;
}
//===----------------------------------------------------------------------===//
// SIToFPOp
//===----------------------------------------------------------------------===//
def Arith_SIToFPOp : Arith_IToFCastOp<"sitofp"> {
let summary = "cast from integer type to floating-point";
let description = [{
Cast from a value interpreted as a signed integer to the corresponding
floating-point value. When operating on vectors, casts elementwise.
}];
let hasFolder = 1;
let hasCanonicalizer = 1;
}
//===----------------------------------------------------------------------===//
// FPToUIOp
//===----------------------------------------------------------------------===//
def Arith_FPToUIOp : Arith_FToICastOp<"fptoui"> {
let summary = "cast from floating-point type to integer type";
let description = [{
Cast from a value interpreted as floating-point to the nearest (rounding
towards zero) unsigned integer value. When operating on vectors, casts
elementwise.
}];
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// FPToSIOp
//===----------------------------------------------------------------------===//
def Arith_FPToSIOp : Arith_FToICastOp<"fptosi"> {
let summary = "cast from floating-point type to integer type";
let description = [{
Cast from a value interpreted as floating-point to the nearest (rounding
towards zero) signed integer value. When operating on vectors, casts
elementwise.
}];
let hasFolder = 1;
}
//===----------------------------------------------------------------------===//
// IndexCastOp
//===----------------------------------------------------------------------===//
// Index cast can convert between memrefs of signless integers and indices too.
def IndexCastTypeConstraint : TypeConstraint<Or<[
Arith_SignlessIntegerOrIndexLike.predicate,
MemRefOf<[AnyNonZeroBitwidthSignlessInteger, Index]>.predicate]>,
"signless-non-zero-bitwidth-integer-like or memref of signless-integer">;
def Arith_IndexCastOp
: Arith_CastOp<"index_cast", IndexCastTypeConstraint, IndexCastTypeConstraint,
[DeclareOpInterfaceMethods<InferIntRangeInterface, ["inferResultRanges"]>]> {
let summary = "cast between index and integer types";
let description = [{
Casts between scalar or vector integers and corresponding 'index' scalar or
vectors. Index is an integer of platform-specific bit width. If casting to
a wider integer, the value is sign-extended. If casting to a narrower
integer, the value is truncated.
}];
let hasFolder = 1;
let hasCanonicalizer = 1;
}
//===----------------------------------------------------------------------===//
// IndexCastUIOp
//===----------------------------------------------------------------------===//
def Arith_IndexCastUIOp
: Arith_CastOp<"index_castui", IndexCastTypeConstraint, IndexCastTypeConstraint,
[DeclareOpInterfaceMethods<InferIntRangeInterface, ["inferResultRanges"]>,
DeclareOpInterfaceMethods<ArithNonNegFlagInterface>]> {
let summary = "unsigned cast between index and integer types";
let description = [{
Casts between scalar or vector integers and corresponding 'index' scalar or
vectors. Index is an integer of platform-specific bit width. If casting to
a wider integer, the value is zero-extended. If casting to a narrower
integer, the value is truncated.
When the `nneg` flag is present, the operand is assumed to have
the most significant bit set to 0. In this case, zero extension
is equivalent to sign extension. When this assumption is violated,
the result is poison.
Example:
```mlir
%0 = arith.index_castui %a : i32 to index
%1 = arith.index_castui %a nneg : i32 to index
%2 = arith.index_castui %b nneg : index to i64
```
}];
let arguments = (ins IndexCastTypeConstraint:$in, UnitAttr:$nonNeg);
let results = (outs IndexCastTypeConstraint:$out);
let assemblyFormat = [{
$in (`nneg` $nonNeg^)? attr-dict `:` type($in) `to` type($out)
}];
let hasFolder = 1;
let hasCanonicalizer = 1;
}
//===----------------------------------------------------------------------===//
// BitcastOp
//===----------------------------------------------------------------------===//
// Bitcast can convert between memrefs of signless integers and floats.
// i0 (zero-bitwidth) integers are excluded: a 0-bit bitcast is meaningless.
def BitcastTypeConstraint : TypeConstraint<Or<[
SignlessFixedWidthIntegerLike.predicate,
FloatLike.predicate,
MemRefOf<[AnyNonZeroBitwidthSignlessInteger, AnyFloat]>.predicate]>,
"non-zero-bitwidth-signless-integer-or-float-like or memref of non-zero-bitwidth signless integer or float">;
def Arith_BitcastOp : Arith_CastOp<"bitcast", BitcastTypeConstraint,
BitcastTypeConstraint> {
let summary = "bitcast between values of equal bit width";
let description = [{
Bitcast an integer or floating point value to an integer or floating point
value of equal bit width. When operating on vectors, casts elementwise.
Note that this implements a logical bitcast independent of target
endianness. This allows constant folding without target information and is
consitent with the bitcast constant folders in LLVM (see
https://github.com/llvm/llvm-project/blob/18c19414eb/llvm/lib/IR/ConstantFold.cpp#L168)
For targets where the source and target type have the same endianness (which
is the standard), this cast will also change no bits at runtime, but it may
still require an operation, for example if the machine has different
floating point and integer register files. For targets that have a different
endianness for the source and target types (e.g. float is big-endian and
integer is little-endian) a proper lowering would add operations to swap the
order of words in addition to the bitcast.
}];
let hasFolder = 1;
let hasCanonicalizer = 1;
}
//===----------------------------------------------------------------------===//
// CmpIOp
//===----------------------------------------------------------------------===//
def Arith_CmpIOp : Arith_CompareOp<"cmpi",
[DeclareOpInterfaceMethods<InferIntRangeInterface,
["inferResultRanges"]>]> {
let summary = "integer comparison operation";
let description = [{
The `cmpi` operation is a generic comparison for integer-like types. Its two
arguments can be integers, vectors or tensors thereof as long as their types
match. The operation produces an i1 for the former case, a vector or a
tensor of i1 with the same shape as inputs in the other cases.
Its first argument is an attribute that defines which type of comparison is
performed. The following comparisons are supported:
- equal (mnemonic: `"eq"`; integer value: `0`)
- not equal (mnemonic: `"ne"`; integer value: `1`)
- signed less than (mnemonic: `"slt"`; integer value: `2`)
- signed less than or equal (mnemonic: `"sle"`; integer value: `3`)
- signed greater than (mnemonic: `"sgt"`; integer value: `4`)
- signed greater than or equal (mnemonic: `"sge"`; integer value: `5`)
- unsigned less than (mnemonic: `"ult"`; integer value: `6`)
- unsigned less than or equal (mnemonic: `"ule"`; integer value: `7`)
- unsigned greater than (mnemonic: `"ugt"`; integer value: `8`)
- unsigned greater than or equal (mnemonic: `"uge"`; integer value: `9`)
The result is `1` if the comparison is true and `0` otherwise. For vector or
tensor operands, the comparison is performed elementwise and the element of
the result indicates whether the comparison is true for the operand elements
with the same indices as those of the result.
Note: while the custom assembly form uses strings, the actual underlying
attribute has integer type (or rather enum class in C++ code) as seen from
the generic assembly form. String literals are used to improve readability
of the IR by humans.
This operation only applies to integer-like operands, but not floats. The
main reason being that comparison operations have diverging sets of
attributes: integers require sign specification while floats require various
floating point-related particularities, e.g., `-ffast-math` behavior,
IEEE754 compliance, etc
([rationale](../Rationale/Rationale.md#splitting-floating-point-vs-integer-operations)).
The type of comparison is specified as attribute to avoid introducing ten
similar operations, taking into account that they are often implemented
using the same operation downstream
([rationale](../Rationale/Rationale.md#specifying-comparison-kind-as-attribute)). The
separation between signed and unsigned order comparisons is necessary
because of integers being signless. The comparison operation must know how
to interpret values with the foremost bit being set: negatives in two's
complement or large positives
([rationale](../Rationale/Rationale.md#specifying-sign-in-integer-comparison-operations)).
Example:
```mlir
// Custom form of scalar "signed less than" comparison.
%x = arith.cmpi slt, %lhs, %rhs : i32
// Generic form of the same operation.
%x = "arith.cmpi"(%lhs, %rhs) {predicate = 2 : i64} : (i32, i32) -> i1
// Custom form of vector equality comparison.
%x = arith.cmpi eq, %lhs, %rhs : vector<4xi64>
// Generic form of the same operation.
%x = "arith.cmpi"(%lhs, %rhs) {predicate = 0 : i64}
: (vector<4xi64>, vector<4xi64>) -> vector<4xi1>
```
}];
let arguments = (ins Arith_CmpIPredicateAttr:$predicate,
Arith_SignlessIntegerOrIndexLike:$lhs,
Arith_SignlessIntegerOrIndexLike:$rhs);
let hasFolder = 1;
let hasCanonicalizer = 1;
}
//===----------------------------------------------------------------------===//
// CmpFOp
//===----------------------------------------------------------------------===//
def Arith_CmpFOp : Arith_CompareOp<"cmpf",
[DeclareOpInterfaceMethods<ArithFastMathInterface>]> {
let summary = "floating-point comparison operation";
let description = [{
The `cmpf` operation compares its two operands according to the float
comparison rules and the predicate specified by the respective attribute.
The predicate defines the type of comparison: (un)orderedness, (in)equality
and signed less/greater than (or equal to) as well as predicates that are
always true or false. The operands must have the same type, and this type
must be a float type, or a vector or tensor thereof. The result is an i1,
or a vector/tensor thereof having the same shape as the inputs. Unlike cmpi,
the operands are always treated as signed. The u prefix indicates
*unordered* comparison, not unsigned comparison, so "une" means unordered or
not equal. For the sake of readability by humans, custom assembly form for
the operation uses a string-typed attribute for the predicate. The value of
this attribute corresponds to lower-cased name of the predicate constant,
e.g., "one" means "ordered not equal". The string representation of the
attribute is merely a syntactic sugar and is converted to an integer
attribute by the parser.
Example:
```mlir
%r1 = arith.cmpf oeq, %0, %1 : f32
%r2 = arith.cmpf ult, %0, %1 : tensor<42x42xf64>
%r3 = "arith.cmpf"(%0, %1) {predicate: 0} : (f8, f8) -> i1
```
}];
let arguments = (ins Arith_CmpFPredicateAttr:$predicate,
FloatLike:$lhs,
FloatLike:$rhs,
DefaultValuedAttr<
Arith_FastMathAttr, "::mlir::arith::FastMathFlags::none">:$fastmath);
let hasFolder = 1;
let hasCanonicalizer = 1;
let assemblyFormat = [{ $predicate `,` $lhs `,` $rhs (`fastmath` `` $fastmath^)?
attr-dict `:` type($lhs)}];
}
//===----------------------------------------------------------------------===//
// SelectOp
//===----------------------------------------------------------------------===//
class BooleanConditionOrMatchingShape<string condition, string result> :
PredOpTrait<
condition # " is signless i1 or has matching shape",
Or<[TypeIsPred<condition, I1>,
And<[SubstLeaves<"$_self", "$" # condition # ".getType()", IsShapedTypePred>,
SubstLeaves<"$_self", "$" # result # ".getType()", IsShapedTypePred>,
AllShapesMatch<[condition, result]>.predicate]>]>>;
def SelectOp : Arith_Op<"select", [Pure,
AllTypesMatch<["true_value", "false_value", "result"]>,
BooleanConditionOrMatchingShape<"condition", "result">,
DeclareOpInterfaceMethods<InferIntDivisibilityOpInterface>,
DeclareOpInterfaceMethods<InferIntRangeInterface, ["inferResultRangesFromOptional"]>,
DeclareOpInterfaceMethods<SelectLikeOpInterface>]> {
let summary = "select operation";
let description = [{
The `arith.select` operation chooses one value based on a binary condition
supplied as its first operand.
If the value of the first operand (the condition) is `1`, then the second
operand is returned, and the third operand is ignored, even if it was poison.
If the value of the first operand (the condition) is `0`, then the third
operand is returned, and the second operand is ignored, even if it was poison.
If the value of the first operand (the condition) is poison, then the
operation returns poison.
The operation applies to vectors and tensors elementwise given the _shape_
of all operands is identical. The choice is made for each element
individually based on the value at the same position as the element in the
condition operand. If an i1 is provided as the condition, the entire vector
or tensor is chosen.
Example:
```mlir
// Custom form of scalar selection.
%x = arith.select %cond, %true, %false : i32
// Generic form of the same operation.
%x = "arith.select"(%cond, %true, %false) : (i1, i32, i32) -> i32
// Element-wise vector selection.
%vx = arith.select %vcond, %vtrue, %vfalse : vector<42xi1>, vector<42xf32>
// Full vector selection.
%vx = arith.select %cond, %vtrue, %vfalse : vector<42xf32>
```
}];
let arguments = (ins BoolLike:$condition,
AnyType:$true_value,
AnyType:$false_value);
let results = (outs AnyType:$result);
let hasCanonicalizer = 1;
let hasFolder = 1;
let hasVerifier = 1;
// FIXME: Switch this to use the declarative assembly format.
let hasCustomAssemblyFormat = 1;
}
#endif // ARITH_OPS