| // Copyright 2019 The IREE Authors |
| // |
| // Licensed 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 IREE_DIALECT_UTIL_IR_UTIL_OPS |
| #define IREE_DIALECT_UTIL_IR_UTIL_OPS |
| |
| include "iree/compiler/Dialect/Util/IR/UtilAttrs.td" |
| include "iree/compiler/Dialect/Util/IR/UtilBase.td" |
| include "iree/compiler/Dialect/Util/IR/UtilInterfaces.td" |
| include "iree/compiler/Dialect/Util/IR/UtilTypes.td" |
| include "mlir/IR/BuiltinAttributeInterfaces.td" |
| include "mlir/IR/OpAsmInterface.td" |
| include "mlir/IR/SymbolInterfaces.td" |
| include "mlir/Interfaces/CallInterfaces.td" |
| include "mlir/Interfaces/CastInterfaces.td" |
| include "mlir/Interfaces/ControlFlowInterfaces.td" |
| include "mlir/Interfaces/FunctionInterfaces.td" |
| include "mlir/Interfaces/InferIntRangeInterface.td" |
| include "mlir/Interfaces/InferTypeOpInterface.td" |
| include "mlir/Interfaces/SideEffectInterfaces.td" |
| include "mlir/Interfaces/ViewLikeInterface.td" |
| |
| //===----------------------------------------------------------------------===// |
| // Op types |
| //===----------------------------------------------------------------------===// |
| |
| class Util_Op<string mnemonic, list<Trait> traits = []> : |
| Op<Util_Dialect, mnemonic, traits> { |
| let hasCustomAssemblyFormat = 1; |
| } |
| |
| class Util_PureOp<string mnemonic, list<Trait> traits = []> : |
| Util_Op<mnemonic, !listconcat(traits, [Pure])>; |
| |
| //===----------------------------------------------------------------------===// |
| // Type manipulation |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupTypeManipulationOps : OpDocGroup { |
| let summary = "Type manipulation ops"; |
| let description = ""; |
| } |
| |
| let opDocGroup = OpGroupTypeManipulationOps in { |
| |
| def Util_NullOp : Util_PureOp<"null", [ |
| ConstantLike, |
| ]> { |
| let summary = [{returns a null type value}]; |
| let description = [{ |
| Defines an SSA value that is lowered into dialects supporting |
| null/undefined/optional/etc values. |
| }]; |
| |
| let results = (outs |
| AnyType:$result |
| ); |
| |
| let assemblyFormat = [{ |
| attr-dict `:` type($result) |
| }]; |
| |
| let hasFolder = 1; |
| } |
| |
| def Util_CastOp : Util_PureOp<"cast", [ |
| DeclareOpInterfaceMethods<CastOpInterface>, |
| DeclareOpInterfaceMethods<Util_TiedOpInterface, [ |
| "getTiedResult", |
| "getTiedResultOperand", |
| "getTiedResultOperandIndex", |
| "getTiedResultOperandIndices", |
| ]>, |
| ]> { |
| let summary = [{casts one util type to another ala static_cast/dynamic_cast}]; |
| let description = [{ |
| Performs a type cast between object types known to the util dialect. |
| }]; |
| |
| let arguments = (ins |
| AnyType:$operand |
| ); |
| let results = (outs |
| AnyType:$result |
| ); |
| |
| let assemblyFormat = [{ |
| $operand attr-dict `:` type($operand) `to` type($result) |
| }]; |
| |
| let hasFolder = 1; |
| let hasCanonicalizer = 1; |
| let hasVerifier = 1; |
| } |
| |
| def Util_CmpEQOp : Util_PureOp<"cmp.eq", [ |
| AllTypesMatch<["lhs", "rhs"]>, |
| Commutative, |
| ]> { |
| let summary = [{compares two values for equality}]; |
| let description = [{ |
| Compares two operands for equality. This is intended for comparing IREE |
| reference types (like !util.buffer) that cannot be used with std.cmpi. |
| }]; |
| |
| let arguments = (ins |
| AnyType:$lhs, |
| AnyType:$rhs |
| ); |
| let results = (outs |
| I1:$result |
| ); |
| |
| let assemblyFormat = [{ |
| operands attr-dict `:` type($lhs) |
| }]; |
| |
| let hasFolder = 1; |
| } |
| |
| def Util_CmpNEOp : Util_PureOp<"cmp.ne", [ |
| AllTypesMatch<["lhs", "rhs"]>, |
| Commutative, |
| ]> { |
| let summary = [{compares two values for inequality}]; |
| let description = [{ |
| Compares two operands for inequality. This is intended for comparing IREE |
| reference types (like !util.buffer) that cannot be used with std.cmpi. |
| }]; |
| |
| let arguments = (ins |
| AnyType:$lhs, |
| AnyType:$rhs |
| ); |
| let results = (outs |
| I1:$result |
| ); |
| |
| let assemblyFormat = [{ |
| operands attr-dict `:` type($lhs) |
| }]; |
| |
| let hasFolder = 1; |
| } |
| |
| } // OpGroupTypeManipulationOps |
| |
| //===----------------------------------------------------------------------===// |
| // Data type conversions |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupDataTypeConversionOps : OpDocGroup { |
| let summary = "Data type conversion ops"; |
| let description = ""; |
| } |
| |
| let opDocGroup = OpGroupDataTypeConversionOps in { |
| |
| def Util_NumericOptionalNarrowOp : Util_PureOp<"numeric.optional_narrow", [ |
| SameOperandsAndResultType |
| ]> { |
| let summary = "memorializes an optional numeric narrowing that is valid"; |
| let description = [{ |
| Serves as a placeholder for points in the computation where an optional |
| numeric narrowing can be performed without loss of information. Such ops |
| can guide optimization passes wishing to perform precision reduction. |
| |
| In addition to the operand and result type, this op takes an additional |
| `semantic_type` attribute representing the semantic target type which can |
| be: |
| * FloatType |
| * Signed IntegerType |
| * Unsigned IntegerType |
| |
| Note that this `semantic_type` must be a sign-carrying integer if using an |
| integer type and cannot be IndexType (i.e. it can be used to indicate a |
| possible narrowing of an IndexType to a specific integer). |
| |
| If the operand is a TensorType, then the result must be a TensorType. The |
| `semantic_type` constrains the element type. |
| |
| Optionally, the minimum and maximum integer values (for integer semantic |
| types) are tracked if known. |
| }]; |
| |
| let arguments = (ins |
| AnyTypeOf<[Util_Element, Util_Tensor]>:$operand, |
| TypeAttr:$semantic_type, |
| OptionalAttr<APIntAttr>:$min_value, |
| OptionalAttr<APIntAttr>:$max_value |
| ); |
| let results = (outs |
| AnyTypeOf<[Util_Element, Util_Tensor]>:$result |
| ); |
| |
| let assemblyFormat = [{ |
| $operand `:` type($operand) `as` $semantic_type attr-dict |
| }]; |
| |
| let builders = [ |
| OpBuilder<(ins |
| "Value":$operand, |
| "Type":$type, |
| "std::optional<std::pair<int64_t, int64_t>>":$integerRange |
| ), |
| [{ |
| IntegerAttr minValueAttr; |
| IntegerAttr maxValueAttr; |
| if (integerRange) { |
| minValueAttr = $_builder.getIntegerAttr(type, integerRange->first); |
| maxValueAttr = $_builder.getIntegerAttr(type, integerRange->second); |
| } |
| build($_builder, $_state, operand.getType(), operand, TypeAttr::get(type), |
| minValueAttr, maxValueAttr); |
| }]>, |
| ]; |
| |
| let extraClassDeclaration = [{ |
| bool isSigned() { |
| if (auto integerType = dyn_cast<IntegerType>(getType())) { |
| return !integerType.isUnsigned(); |
| } |
| return true; |
| } |
| |
| std::optional<std::pair<int64_t, int64_t>> getIntegerRange(); |
| }]; |
| } |
| |
| } // OpGroupDataTypeConversionOps |
| |
| //===----------------------------------------------------------------------===// |
| // Range arithmetic |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupRangeArithmeticOps : OpDocGroup { |
| let summary = "Range arithmetic ops"; |
| let description = ""; |
| } |
| |
| let opDocGroup = OpGroupRangeArithmeticOps in { |
| |
| def Util_RangeMinOp : Util_PureOp<"range.min", [ |
| SameOperandsAndResultType, |
| SameVariadicOperandSize, |
| ]> { |
| let summary = [{returns the min of all values}]; |
| let description = [{ |
| Computes the min of a variadic list of operands. Though it's possible to |
| express this with standard arithmetic this op enables more semantically |
| meaningful folding/optimizations. |
| }]; |
| |
| let arguments = (ins |
| Variadic<Util_Range>:$operands |
| ); |
| let results = (outs |
| Util_Range:$result |
| ); |
| |
| let assemblyFormat = [{ |
| $operands attr-dict `:` type($result) |
| }]; |
| |
| let hasCanonicalizer = 1; |
| let hasFolder = 1; |
| } |
| |
| def Util_RangeMaxOp : Util_PureOp<"range.max", [ |
| SameOperandsAndResultType, |
| SameVariadicOperandSize, |
| ]> { |
| let summary = [{returns the max of all values}]; |
| let description = [{ |
| Computes the max of a variadic list of operands. Though it's possible to |
| express this with standard arithmetic this op enables more semantically |
| meaningful folding/optimizations. |
| }]; |
| |
| let arguments = (ins |
| Variadic<Util_Range>:$operands |
| ); |
| let results = (outs |
| Util_Range:$result |
| ); |
| |
| let assemblyFormat = [{ |
| $operands attr-dict `:` type($result) |
| }]; |
| |
| let hasCanonicalizer = 1; |
| let hasFolder = 1; |
| } |
| |
| def Util_RangeExtentsOp : Util_PureOp<"range.extents", [ |
| SameOperandsAndResultType, |
| SameVariadicOperandSize, |
| ]> { |
| let summary = [{returns the min/max of a union of a set of ranges}]; |
| let description = [{ |
| Computes min(offsets) and max(offsets + lengths). Though it's possible to |
| express this with standard arithmetic this op enables more semantically |
| meaningful folding/optimizations. |
| }]; |
| |
| let arguments = (ins |
| Variadic<Util_Range>:$offsets, |
| Variadic<Util_Range>:$lengths |
| ); |
| let results = (outs |
| Util_Range:$min, |
| Util_Range:$max |
| ); |
| |
| let assemblyFormat = [{ |
| custom<RangeList>($offsets, $lengths) attr-dict `:` type($min) |
| }]; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| } // OpGroupRangeArithmeticOps |
| |
| //===----------------------------------------------------------------------===// |
| // Address/offset arithmetic |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupAddressOffsetArithmeticOps : OpDocGroup { |
| let summary = "Address/offset arithmetic ops"; |
| let description = ""; |
| } |
| |
| let opDocGroup = OpGroupAddressOffsetArithmeticOps in { |
| |
| def Util_AlignOp : Util_PureOp<"align", [ |
| SameOperandsAndResultType, |
| DeclareOpInterfaceMethods<InferIntRangeInterface, ["inferResultRanges"]>, |
| DeclareOpInterfaceMethods<InferIntDivisibilityOpInterface, ["inferResultRanges"]> |
| ]> { |
| let summary = "Aligns up to a power-of-two alignment if required"; |
| let description = [{ |
| Aligns |value| up to the given power-of-two |alignment| if required. |
| }]; |
| |
| let arguments = (ins |
| SignlessIntegerLike:$value, |
| SignlessIntegerLike:$alignment |
| ); |
| |
| let results = (outs |
| SignlessIntegerLike:$result |
| ); |
| |
| let assemblyFormat = [{ |
| $value `,` $alignment attr-dict `:` type($result) |
| }]; |
| |
| let builders = [ |
| OpBuilder<(ins |
| "Value":$value, |
| "int64_t":$alignment |
| ), |
| [{ |
| build($_builder, $_state, value.getType(), value, |
| $_builder.createOrFold<arith::ConstantIndexOp>($_state.location, alignment)); |
| }]>, |
| ]; |
| |
| let hasFolder = 1; |
| } |
| |
| def Util_SizeOfOp : Util_PureOp<"sizeof"> { |
| let summary = [{returns the size in bytes of a datatype}]; |
| let description = [{ |
| Most datatypes have a static size at all layers of the compilation stack. |
| However, those that only have a size for certain lowering flows can be |
| challenging. This op represents such sizes in a way that can be specialized |
| later. |
| |
| Returns the size in bytes, rounded up to the next whole byte of the |
| specified type. This op will fold to a constant index value for IntegerType |
| and FloatType. All others are not folded. |
| }]; |
| |
| let arguments = (ins |
| TypeAttr:$sizedType |
| ); |
| let results = (outs |
| Index:$size |
| ); |
| |
| let assemblyFormat = [{ |
| $sizedType attr-dict-with-keyword |
| }]; |
| |
| let hasFolder = 1; |
| } |
| |
| } // OpGroupAddressOffsetArithmeticOps |
| |
| //===----------------------------------------------------------------------===// |
| // Value utility ops |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupValueUtilityOps : OpDocGroup { |
| let summary = "Value utility ops"; |
| let description = ""; |
| } |
| |
| let opDocGroup = OpGroupValueUtilityOps in { |
| |
| def Util_SwitchOp : Util_PureOp<"switch", [ |
| AllTypesMatch<["default_value", "result"]>, |
| ]> { |
| let summary = [{primitive switch operation}]; |
| let description = [{ |
| Returns the value with the given `index` in `values` or `default_value` if |
| the index is out of bounds. |
| |
| ```mlir |
| // Switch %index to cases of %c100/%c200/%c300 if index==0, ==1, ==2. |
| // If %index is out of range (<0 or >2) then default to %c5. |
| %0 = util.switch %index[%c100, %c200, %c300] else %c5 : i32 |
| ``` |
| }]; |
| |
| let arguments = (ins |
| Index:$index, |
| Util_Primitive:$default_value, |
| Variadic<Util_Primitive>:$values |
| ); |
| let results = (outs |
| Util_Primitive:$result |
| ); |
| |
| let assemblyFormat = [{ |
| type($default_value) `from` |
| custom<TypedValueList>(ref(type($default_value)), $values, type($values)) |
| `at` $index |
| `else` $default_value |
| attr-dict |
| `:` type($result) |
| }]; |
| |
| let hasFolder = 1; |
| } |
| |
| } // OpGroupValueUtilityOps |
| |
| //===----------------------------------------------------------------------===// |
| // Compiler hints |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupCompilerHintOps : OpDocGroup { |
| let summary = "Compiler hint ops"; |
| let description = ""; |
| } |
| |
| let opDocGroup = OpGroupCompilerHintOps in { |
| |
| def Util_AssumeIntOp : Util_PureOp<"assume.int", [ |
| DeclareOpInterfaceMethods<InferIntRangeInterface, ["inferResultRanges"]>, |
| DeclareOpInterfaceMethods<InferIntDivisibilityOpInterface, ["inferResultRanges"]>, |
| AllTypesMatch<["operands", "results"]> |
| ]> { |
| let summary = "memorializes assumptions about index/integer values."; |
| let description = [{ |
| This op is used to memorialize the result of some integer analysis or |
| outside knowledge across a boundary beyond which such information can |
| not be easily recovered. Assumptions are made per op/result pair. |
| |
| Assumptions are tied to operands as rows of permutations of an |
| `#util.assume.int` per operand. The number of permutations is the rank. |
| Typically multiple permutations record a specific subset of assumptions |
| broken down per call-site in some way that is meaningful to the receiver. |
| Implementations can use this information to specialize on each |
| permutation if it is meaninful to do so (i.e. vs unioning across them). |
| In such cases, there will typically be one such op at the top of a |
| function or scope which passes all covered operands through it. |
| }]; |
| |
| let arguments = (ins |
| Variadic<AnySignlessIntegerOrIndex>:$operands, |
| Util_MultiValueIntAssumptionAttrList:$assumptions |
| ); |
| let results = (outs |
| Variadic<AnySignlessIntegerOrIndex>:$results |
| ); |
| let builders = [ |
| // Helper for building simple single operand/single assumption ops. |
| OpBuilder<(ins |
| "Value":$singleOperand, |
| "IntAssumptionAttr":$singleAssumption |
| )>, |
| OpBuilder<(ins |
| "ArrayRef<Value>":$operands, |
| "ArrayRef<ArrayAttr>":$assumptions |
| )>, |
| ]; |
| |
| let extraClassDeclaration = [{ |
| // Gets the list of assumptions for an operand. |
| SmallVector<IntAssumptionAttr> getOperandAssumptions(unsigned operandIndex); |
| |
| // Gets the unioned unsigned range for an operand. If there are multiple |
| // assumptions for the operand, this will return the bounding range for |
| // them all. If there is no umin/umax for any row in the set, then |
| // std::nullopt will be returned for that position. |
| std::pair<std::optional<uint64_t>, std::optional<uint64_t>> |
| getUnionedUnsignedRange(unsigned operandIndex); |
| |
| // Gets the unioned divisor for an operand. If there are multiple divisor |
| // assumptions, the gcd of all of them is returned. If there are no |
| // divisor assumptions or if there is not a udiv for any row, std::nullopt |
| // is returned. |
| std::optional<uint64_t> getUnionedUnsignedDivisor(unsigned operandIndex); |
| }]; |
| |
| let hasCanonicalizeMethod = 1; |
| let hasCustomAssemblyFormat = 1; |
| let hasVerifier = 1; |
| } |
| |
| def Util_OptimizationBarrierOp : Util_Op<"optimization_barrier", [ |
| SameOperandsAndResultType, |
| ]> { |
| let summary = "Prevents compiler optimizations across a value."; |
| let description = [{ |
| Wraps any operands in an unoptimizable identity to prevent its results from |
| being folded. It will be dropped during the final step in compilation and |
| has no effect at runtime. |
| }]; |
| let arguments = (ins Variadic<AnyType>:$operands); |
| let results = (outs Variadic<AnyType>:$results); |
| |
| let assemblyFormat = [{ |
| attr-dict |
| ($operands^ `:` type($operands))? |
| }]; |
| |
| let builders = [ |
| OpBuilder<(ins |
| "ValueRange":$operands, |
| CArg<"ArrayRef<NamedAttribute>", "{}">:$attributes |
| )>, |
| ]; |
| |
| let hasVerifier = 1; |
| } |
| |
| def Util_UnfoldableConstantOp : Util_Op<"unfoldable_constant"> { |
| let summary = "A constant that cannot be folded by the compiler."; |
| let description = [{ |
| Similar to a std.constant, but is declared as having a side effect and has |
| no folder. This is really just syntactic sugar as it is canonicalized to a |
| std.constant wrapped in an util.optimization_barrier. |
| }]; |
| |
| let arguments = (ins AnyAttr:$value); |
| let results = (outs AnyType); |
| |
| let builders = [ |
| OpBuilder<(ins "TypedAttr":$value), |
| [{ build($_builder, $_state, value.getType(), value); }]>]; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| def Util_UnreachableOp : Util_Op<"unreachable", [ |
| NoMemoryEffect, |
| ReturnLike, |
| Terminator |
| ]> { |
| let summary = [{unreachable assertion op}]; |
| let description = [{ |
| Signals to the compiler that the parent block should not be reachable. |
| This may be converted into a runtime assertion, though ideally they are |
| stripped during translation. |
| |
| ```mlir |
| ^bb0: |
| %true = arith.constant true |
| cond_br %true, ^bb2, ^bb1 |
| ^bb1: |
| // Indicates that this branch should never be taken. |
| util.unreachable "shouldn't be here" |
| ^bb2: |
| ... |
| |
| ``` |
| }]; |
| |
| let arguments = (ins StrAttr:$message); |
| |
| let assemblyFormat = "$message attr-dict"; |
| } |
| |
| } // OpGroupCompilerHintOps |
| |
| //===----------------------------------------------------------------------===// |
| // Structural ops |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupStructuralOps : OpDocGroup { |
| let summary = "Structural ops"; |
| let description = ""; |
| } |
| |
| let opDocGroup = OpGroupStructuralOps in { |
| |
| def Util_InitializerOp : Util_Op<"initializer", [ |
| IsolatedFromAbove, |
| FunctionOpInterface, |
| CallableOpInterface, |
| Util_InitializerOpInterface, |
| ]> { |
| let summary = [{global initialization function}]; |
| let description = [{ |
| A function that is called in definition order upon module initialization. |
| Must not load any globals that are defined or initialized after it in the |
| module. |
| }]; |
| |
| let arguments = (ins |
| TypeAttrOf<FunctionType>:$function_type, |
| OptionalAttr<DictArrayAttr>:$arg_attrs, |
| OptionalAttr<DictArrayAttr>:$res_attrs |
| ); |
| |
| let regions = (region AnyRegion:$body); |
| |
| let skipDefaultBuilders = 1; |
| let builders = [ |
| OpBuilder<(ins |
| CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs |
| )>, |
| ]; |
| |
| let extraClassDeclaration = [{ |
| /// Add an entry block to an empty function and set up the block arguments |
| /// to match the signature of the function. |
| Block *addEntryBlock(); |
| Block *addBlock(); |
| |
| /// Returns the argument types of this function. |
| ArrayRef<Type> getArgumentTypes() { return {}; } |
| |
| /// Returns the result types of this function. |
| ArrayRef<Type> getResultTypes() { return {}; } |
| |
| LogicalResult verifyType() { return success(); } |
| |
| Region *getCallableRegion() { return &getBody(); } |
| ArrayRef<Type> getCallableResults() { return {}; } |
| |
| /// Returns the argument attributes for all callable region arguments or |
| /// null if there are none. |
| ::mlir::ArrayAttr getCallableArgAttrs() { |
| return getArgAttrs().value_or(nullptr); |
| } |
| |
| /// Returns the result attributes for all callable region results or |
| /// null if there are none. |
| ::mlir::ArrayAttr getCallableResAttrs() { |
| return getResAttrs().value_or(nullptr); |
| } |
| |
| /// Make symbol optional as this op has no symbol. |
| bool isOptionalSymbol() { return true; } |
| }]; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| def Util_FuncOp : Util_Op<"func", [ |
| AffineScope, |
| AutomaticAllocationScope, |
| FunctionOpInterface, |
| IsolatedFromAbove, |
| OpAsmOpInterface, |
| ]> { |
| let summary = [{function operation containing a CFG region}]; |
| let description = [{ |
| An operation declaring a callable function. |
| |
| An external function declaration (used when referring to a function declared |
| in some other module) has no body. |
| }]; |
| |
| let arguments = (ins |
| SymbolNameAttr:$sym_name, |
| TypeAttrOf<FunctionType>:$function_type, |
| OptionalAttr<Util_TiedOpStorageAttr>:$tied_operands, |
| OptionalAttr<StrAttr>:$sym_visibility, |
| OptionalAttr<DictArrayAttr>:$arg_attrs, |
| OptionalAttr<DictArrayAttr>:$res_attrs, |
| OptionalAttr<Util_InliningPolicyAttrInterface>:$inlining_policy |
| ); |
| |
| let regions = (region AnyRegion:$body); |
| |
| let builders = [ |
| OpBuilder<(ins |
| "StringRef":$name, |
| "FunctionType":$type, |
| CArg<"ArrayAttr", "{}">:$tiedOperands, |
| CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs, |
| CArg<"ArrayRef<DictionaryAttr>", "{}">:$argAttrs, |
| CArg<"ArrayRef<DictionaryAttr>", "{}">:$resAttrs |
| )>, |
| ]; |
| |
| let extraClassDeclaration = [{ |
| static FuncOp create(Location location, StringRef name, FunctionType type, |
| ArrayRef<int64_t> tiedOperands = {}, |
| ArrayRef<NamedAttribute> attrs = {}, |
| ArrayRef<DictionaryAttr> argAttrs = {}, |
| ArrayRef<DictionaryAttr> resAttrs = {}); |
| |
| bool isDeclaration() { return isExternal(); } |
| |
| ::mlir::Region *getCallableRegion() { return &getBody(); } |
| ArrayRef<Type> getCallableResults() { return getFunctionType().getResults(); } |
| ::mlir::ArrayAttr getCallableArgAttrs() { return getArgAttrs().value_or(nullptr); } |
| ::mlir::ArrayAttr getCallableResAttrs() { return getResAttrs().value_or(nullptr); } |
| |
| ArrayRef<Type> getArgumentTypes() { return getFunctionType().getInputs(); } |
| ArrayRef<Type> getResultTypes() { return getFunctionType().getResults(); } |
| |
| bool canDiscardOnUseEmpty(); |
| |
| // Returns true if any operand is tied to a result. |
| bool hasAnyTiedOperands(); |
| |
| // Updates the function signature to potentially expand each argument and |
| // result. Only the signature and the metadata on the function (tied |
| // operands, argument/result attrs, etc) are updated and the body region |
| // remains unchanged. |
| // |
| // Any type that may be tied must remain in the same relative order (expand |
| // by appending types after the base type). |
| // |
| // If |newSignature| is provided |
| void expandSignature( |
| std::function<void(unsigned, Type, SmallVectorImpl<Type> &)> expandArgument, |
| std::function<void(unsigned, Type, SmallVectorImpl<Type> &)> expandResult); |
| }]; |
| |
| let hasCustomAssemblyFormat = 1; |
| } |
| |
| def Util_CallOp : Util_Op<"call", [ |
| CallOpInterface, |
| Util_TiedOpInterface, |
| DeclareOpInterfaceMethods<SymbolUserOpInterface>, |
| ]> { |
| let summary = [{function call operation}]; |
| let description = [{ |
| Represents a direct call to a function that is within the same symbol scope |
| as the call. The operands and result types of the call must match the |
| specified function type. |
| |
| Calls support tied operands which indicate that specific results alias |
| a specific operand. The operand and result types are allowed to differ if |
| a cast is performed within the callee. |
| |
| Example: |
| ```mlir |
| util.func @fn(%arg0: i32, %arg1: tensor<f32>) -> (f32, %arg1 as tensor<i32>) |
| ... |
| %0 = util.call @fn(%0, %1) : (i32, tensor<f32>) -> (f32, %1 as tensor<i32>) |
| ``` |
| }]; |
| |
| let arguments = (ins |
| FlatSymbolRefAttr:$callee, |
| Variadic<AnyType>:$operands, |
| OptionalAttr<Util_TiedOpStorageAttr>:$tied_operands |
| ); |
| let results = (outs |
| Variadic<AnyType>:$results |
| ); |
| |
| let builders = [ |
| OpBuilder<(ins |
| CArg<"FunctionOpInterface">:$callee, |
| CArg<"ValueRange">:$operands, |
| CArg<"ArrayAttr", "{}">:$tied_operands, |
| CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs |
| ), [{ |
| build($_builder, $_state, callee.getResultTypes(), callee.getName(), |
| operands, tied_operands); |
| $_state.addAttributes(attrs); |
| }]>, |
| ]; |
| |
| let assemblyFormat = [{ |
| $callee `(` $operands `)` |
| attr-dict `:` |
| custom<OperandTypeList>(type($operands)) |
| `->` |
| custom<TiedFunctionResultList>(ref($operands), |
| ref(type($operands)), |
| type($results), |
| $tied_operands) |
| }]; |
| |
| let extraClassDeclaration = [{ |
| FunctionType getCalleeType(); |
| |
| operand_range getArgOperands() { return {arg_operand_begin(), arg_operand_end()}; } |
| MutableOperandRange getArgOperandsMutable() { return getOperandsMutable(); } |
| operand_iterator arg_operand_begin() { return operand_begin(); } |
| operand_iterator arg_operand_end() { return operand_end(); } |
| |
| CallInterfaceCallable getCallableForCallee() { |
| return (*this)->getAttrOfType<SymbolRefAttr>("callee"); |
| } |
| void setCalleeFromCallable(CallInterfaceCallable callee) { |
| (*this)->setAttr("callee", callee.get<SymbolRefAttr>()); |
| } |
| |
| // Clones the call and potentially expands each operand and result. |
| // Callers can then replace result uses using the returned op. |
| // Any type that may be tied must remain in the same relative order (expand |
| // by appending types after the base type). |
| IREE::Util::CallOp cloneAndExpand( |
| std::function<void(unsigned, Value, SmallVectorImpl<Value> &)> expandOperand, |
| std::function<void(unsigned, Type, SmallVectorImpl<Type> &)> expandResult, |
| OpBuilder &builder); |
| }]; |
| } |
| |
| def Util_ReturnOp : Util_Op<"return", [ |
| ParentOneOf<[ |
| "IREE::Util::InitializerOp", |
| "IREE::Util::FuncOp", |
| ]>, |
| Pure, |
| ReturnLike, |
| Terminator, |
| ]> { |
| let summary = [{return from a util.initializer}]; |
| let description = [{ |
| Returns control from an initializer function. |
| }]; |
| |
| let arguments = (ins |
| Variadic<AnyType>:$operands |
| ); |
| |
| let builders = [ |
| OpBuilder<(ins), [{ |
| build($_builder, $_state, std::nullopt); |
| }]>, |
| ]; |
| |
| let assemblyFormat = [{ |
| attr-dict |
| ($operands^ `:` type($operands))? |
| }]; |
| |
| let hasVerifier = 1; |
| } |
| |
| } // OpGroupStructuralOps |
| |
| //===----------------------------------------------------------------------===// |
| // Globals |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupGlobalOps : OpDocGroup { |
| let summary = "Global ops"; |
| let description = ""; |
| } |
| |
| let opDocGroup = OpGroupGlobalOps in { |
| |
| def Util_GlobalOp : Util_Op<"global", [ |
| Symbol, |
| Util_GlobalOpInterface, |
| ]> { |
| let summary = [{stateful global variable declaration}]; |
| let description = [{ |
| Declares a global variable that maintains its value across invocations. |
| The value is tied to the execution context of the module and different |
| contexts will have different variable storage. |
| }]; |
| |
| let arguments = (ins |
| OptionalAttr<StrAttr>:$sym_visibility, |
| SymbolNameAttr:$sym_name, |
| TypeAttr:$type, |
| UnitAttr:$is_mutable, |
| OptionalAttr<TypedAttrInterface>:$initial_value, |
| OptionalAttr<Util_InliningPolicyAttrInterface>:$inlining_policy |
| ); |
| |
| let assemblyFormat = [{ |
| custom<SymbolVisibility>($sym_visibility) |
| (`mutable` $is_mutable^)? |
| $sym_name |
| attr-dict |
| custom<TypeOrAttr>($type, $initial_value) |
| }]; |
| |
| let builders = [ |
| OpBuilder<(ins |
| "StringRef":$name, |
| "bool":$isMutable, |
| "Type":$type, |
| "std::optional<TypedAttr>":$initialValue, |
| CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs |
| )>, |
| OpBuilder<(ins |
| "StringRef":$name, |
| "bool":$isMutable, |
| "Type":$type, |
| CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs |
| )>, |
| ]; |
| |
| let extraClassDeclaration = [{ |
| bool canDiscardOnUseEmpty(); |
| |
| IREE::Util::GlobalLoadOpInterface createLoadOp(Location loc, OpBuilder &builder); |
| IREE::Util::GlobalStoreOpInterface createStoreOp(Location loc, Value value, OpBuilder &builder); |
| }]; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| def Util_GlobalAddressOp : Util_PureOp<"global.address", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| SymbolUserOpInterface, |
| Util_GlobalAddressOpInterface, |
| ]> { |
| let summary = [{returns an address reference to a global}]; |
| let description = [{ |
| Returns the address of a global as a typed reference. Can be used with the |
| global load and store indirect ops. |
| }]; |
| |
| let arguments = (ins |
| Util_GlobalRefAttr:$global, |
| UnitAttr:$is_immutable |
| ); |
| let results = (outs |
| Util_AnyGlobalPtr:$result |
| ); |
| |
| let assemblyFormat = [{ |
| (`immutable` $is_immutable^)? |
| $global attr-dict `:` qualified(type($result)) |
| }]; |
| |
| let extraClassDeclaration = [{ |
| LogicalResult verifySymbolUses(SymbolTableCollection &symbolTable) { |
| return IREE::Util::detail::verifyGlobalAddressOp(*this, symbolTable); |
| } |
| }]; |
| } |
| |
| def Util_GlobalLoadOp : Util_Op<"global.load", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| // HACK: works around the lack of symbol side effects in C++. |
| DeclareOpInterfaceMethods<MemoryEffectsOpInterface>, |
| SymbolUserOpInterface, |
| Util_GlobalLoadOpInterface, |
| ]> { |
| let summary = [{loads a value from a global variable}]; |
| let description = [{ |
| Returns a global variable value. |
| }]; |
| |
| let arguments = (ins |
| Arg<Util_GlobalRefAttr, "", []>:$global, |
| UnitAttr:$is_immutable |
| ); |
| let results = (outs |
| AnyType:$result |
| ); |
| |
| let assemblyFormat = [{ |
| (`immutable` $is_immutable^)? |
| $global attr-dict `:` type($result) |
| }]; |
| |
| let builders = [ |
| OpBuilder<(ins |
| "IREE::Util::GlobalOpInterface":$globalOp, |
| CArg<"ArrayRef<NamedAttribute>", "{}">:$attributes |
| )>, |
| ]; |
| |
| let extraClassDeclaration = [{ |
| LogicalResult verifySymbolUses(SymbolTableCollection &symbolTable) { |
| return IREE::Util::detail::verifyGlobalLoadOp(*this, symbolTable); |
| } |
| }]; |
| } |
| |
| def Util_GlobalLoadIndirectOp : Util_Op<"global.load.indirect", [ |
| Util_GlobalLoadIndirectOpInterface, |
| ]> { |
| let summary = [{loads a value from a global variable}]; |
| let description = [{ |
| Returns a copy of the global variable value. |
| }]; |
| |
| let arguments = (ins |
| Arg<Util_AnyGlobalPtr, "", []>:$global, |
| UnitAttr:$is_immutable |
| ); |
| let results = (outs |
| AnyType:$result |
| ); |
| |
| let assemblyFormat = [{ |
| (`immutable` $is_immutable^)? |
| $global attr-dict `:` qualified(type($global)) `->` type($result) |
| }]; |
| |
| let hasVerifier = 1; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| def Util_GlobalStoreOp : Util_Op<"global.store", [ |
| SymbolUserOpInterface, |
| Util_GlobalStoreOpInterface, |
| ]> { |
| let summary = [{stores a value into a global variable}]; |
| let description = [{ |
| Stores a copy of the value into a global variable. |
| }]; |
| |
| let arguments = (ins |
| AnyType:$value, |
| Arg<Util_GlobalRefAttr, "", []>:$global |
| ); |
| |
| let assemblyFormat = [{ |
| $value `,` $global attr-dict `:` type($value) |
| }]; |
| |
| let builders = [ |
| OpBuilder<(ins |
| "Value":$value, |
| "IREE::Util::GlobalOpInterface":$globalOp, |
| CArg<"ArrayRef<NamedAttribute>", "{}">:$attributes |
| )>, |
| ]; |
| |
| let extraClassDeclaration = [{ |
| LogicalResult verifySymbolUses(SymbolTableCollection &symbolTable) { |
| return IREE::Util::detail::verifyGlobalStoreOp(*this, symbolTable); |
| } |
| }]; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| def Util_GlobalStoreIndirectOp : Util_Op<"global.store.indirect", [ |
| Util_GlobalStoreIndirectOpInterface, |
| ]> { |
| let summary = [{stores a value into a global variable}]; |
| let description = [{ |
| Stores a copy of the value into a global variable. |
| }]; |
| |
| let arguments = (ins |
| AnyType:$value, |
| Arg<Util_AnyGlobalPtr, "", []>:$global |
| ); |
| |
| let assemblyFormat = [{ |
| $value `,` $global attr-dict `:` type($value) `->` qualified(type($global)) |
| }]; |
| |
| let hasVerifier = 1; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| } // OpGroupGlobalOps |
| |
| //===----------------------------------------------------------------------===// |
| // !util.list<T> |
| //===----------------------------------------------------------------------===// |
| // NOTE: this type is mostly just a placeholder. Ideally we'd make this |
| // immutable and have operations like util.list.append/concat/etc the returned |
| // new SSA values. This would make optimizing the list usage much easier and |
| // enable hoisting/CSE of list access/mutation. |
| |
| def OpGroupListOps : OpDocGroup { |
| let summary = "List ops"; |
| let description = [{ |
| Ops for `!util.list<T>` (mostly just a placeholder for now). |
| }]; |
| } |
| |
| let opDocGroup = OpGroupListOps in { |
| |
| def Util_ListCreateOp : Util_PureOp<"list.create", [ |
| MemoryEffects<[MemAlloc]>, |
| ]> { |
| let summary = [{creates a new empty list}]; |
| let description = [{ |
| Creates a new empty list with an optional initial capacity. |
| }]; |
| |
| let arguments = (ins |
| Optional<Index>:$initial_capacity |
| ); |
| let results = (outs |
| Util_AnyListType:$result |
| ); |
| |
| let assemblyFormat = "($initial_capacity^)? attr-dict `:` qualified(type($result))"; |
| } |
| |
| def Util_ListSizeOp : Util_Op<"list.size", [ |
| MemoryEffects<[MemRead]>, |
| ]> { |
| let summary = [{the size of the list in elements}]; |
| let description = [{ |
| Returns the current size of the list in elements. |
| }]; |
| |
| let arguments = (ins |
| Util_AnyListType:$list |
| ); |
| let results = (outs |
| Index:$result |
| ); |
| |
| let assemblyFormat = "operands attr-dict `:` qualified(type($list))"; |
| } |
| |
| def Util_ListResizeOp : Util_Op<"list.resize", [ |
| MemoryEffects<[MemWrite]>, |
| ]> { |
| let summary = [{resizes the list to a new count in elements}]; |
| let description = [{ |
| Resizes the list to contain `new_size` elements. This will either truncate |
| the list if the existing size is greater than `new_size` or extend the list |
| with the default list value of the element type. |
| }]; |
| |
| let arguments = (ins |
| Util_AnyListType:$list, |
| Index:$new_size |
| ); |
| |
| let assemblyFormat = "operands attr-dict `:` qualified(type($list))"; |
| } |
| |
| def Util_ListGetOp : Util_Op<"list.get", [ |
| MemoryEffects<[MemRead]>, |
| ]> { |
| let summary = [{element accessor}]; |
| let description = [{ |
| Returns the value of the element at the given index. Note that the value |
| may be null if the element is null or the type does not match. |
| }]; |
| |
| let arguments = (ins |
| Util_AnyListType:$list, |
| Index:$index |
| ); |
| let results = (outs |
| AnyType:$result |
| ); |
| |
| let assemblyFormat = "$list `[` $index `]` attr-dict `:` custom<ListTypeGet>(type($list), type($result))"; |
| |
| let hasVerifier = 1; |
| } |
| |
| def Util_ListSetOp : Util_Op<"list.set", [ |
| MemoryEffects<[MemWrite]>, |
| ]> { |
| let summary = [{element mutator}]; |
| let description = [{ |
| Sets the element at the given index to the new value. |
| }]; |
| |
| let arguments = (ins |
| Util_AnyListType:$list, |
| Index:$index, |
| AnyType:$value |
| ); |
| |
| let assemblyFormat = "$list `[` $index `]` `,` $value attr-dict `:` custom<ListTypeSet>(type($list), type($value))"; |
| |
| let hasVerifier = 1; |
| } |
| |
| } // OpGroupListOps |
| |
| //===----------------------------------------------------------------------===// |
| // !util.buffer |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupBufferOps : OpDocGroup { |
| let summary = "Buffer ops"; |
| let description = ""; |
| } |
| |
| let opDocGroup = OpGroupBufferOps in { |
| |
| def Util_BufferConstantOp : Util_PureOp<"buffer.constant", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| ]> { |
| let summary = [{constant host-side byte buffer}]; |
| let description = [{ |
| Defines a compile-time byte buffer based on the given attribute value. |
| The attribute will be serialized into the canonical IREE format for the |
| chosen host target. |
| }]; |
| |
| let arguments = (ins |
| OptionalAttr<StrAttr>:$name, |
| Util_AnySerializableAttr:$value, |
| OptionalAttr<IndexAttr>:$alignment, |
| OptionalAttr<StrAttr>:$mime_type |
| ); |
| let results = (outs |
| Util_BufferType:$result |
| ); |
| |
| let assemblyFormat = [{ |
| ($name^)? attr-dict `:` type($result) `=` $value |
| }]; |
| |
| let hasVerifier = 1; |
| } |
| |
| def Util_BufferAllocOp : Util_PureOp<"buffer.alloc", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| MemoryEffects<[MemAlloc]>, |
| Util_SizeAwareOp, |
| ]> { |
| let summary = [{allocates a buffer with undefined contents}]; |
| let description = [{ |
| Allocates a buffer with undefined contents. Consumers of the allocated |
| result must assume nothing of the contents. |
| }]; |
| |
| let arguments = (ins |
| Util_Size:$storage_size, |
| OptionalAttr<IndexAttr>:$alignment |
| ); |
| let results = (outs |
| Util_BufferType:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `uninitialized` |
| attr-dict |
| `:` |
| type($result) `` `{` $storage_size `}` |
| }]; |
| |
| let extraClassDeclaration = [{ |
| Value getOperandSize(unsigned idx) { return {}; } |
| Value getResultSize(unsigned idx) { return getStorageSize(); } |
| }]; |
| |
| let hasVerifier = 1; |
| let hasCanonicalizer = 1; |
| } |
| |
| def Util_BufferDeallocOp : Util_PureOp<"buffer.dealloc", [ |
| MemoryEffects<[MemFree]>, |
| Util_SizeAwareOp, |
| ]> { |
| let summary = [{deallocates a buffer}]; |
| let description = [{ |
| Hints that the buffer contents can be discarded. Buffers are reference |
| counted and other owners may keep it live beyond the dealloc. |
| }]; |
| |
| let arguments = (ins |
| Util_BufferType:$operand, |
| Util_Size:$operand_size |
| ); |
| |
| let assemblyFormat = [{ |
| $operand `:` type($operand) `{` $operand_size `}` |
| attr-dict-with-keyword |
| }]; |
| |
| let extraClassDeclaration = [{ |
| Value getOperandSize(unsigned idx) { return getOperandSize(); } |
| Value getResultSize(unsigned idx) { return {}; } |
| }]; |
| } |
| |
| def Util_BufferSliceOp : Util_PureOp<"buffer.slice", [ |
| AllTypesMatch<["source", "result"]>, |
| MemoryEffects<[MemAlloc, MemRead]>, |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| Util_SizeAwareOp, |
| DeclareOpInterfaceMethods<Util_SubrangeOperandOpInterface>, |
| ]> { |
| let summary = [{clones a subregion of a buffer}]; |
| let description = [{ |
| Returns a copy of the contents from the source buffer. |
| }]; |
| |
| let arguments = (ins |
| Util_BufferType:$source, |
| Util_Size:$source_size, |
| Util_Offset:$source_offset, |
| Util_Size:$result_size, |
| OptionalAttr<IndexAttr>:$alignment |
| ); |
| let results = (outs |
| Util_BufferType:$result |
| ); |
| |
| let assemblyFormat = [{ |
| $source `[` $source_offset `]` attr-dict `:` |
| type($source) `` `{` $source_size `}` `->` |
| type($result) `` `{` $result_size `}` |
| }]; |
| |
| let extraClassDeclaration = [{ |
| Value getOperandSize(unsigned idx) { return getSourceSize(); } |
| Value getResultSize(unsigned idx) { return getResultSize(); } |
| }]; |
| } |
| |
| def Util_BufferSubspanOp : Util_PureOp<"buffer.subspan", [ |
| AllTypesMatch<["source", "result"]>, |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| DeclareOpInterfaceMethods<ViewLikeOpInterface>, |
| Util_SizeAwareOp, |
| Util_SubrangeOp, |
| DeclareOpInterfaceMethods<Util_SubrangeOperandOpInterface>, |
| DeclareOpInterfaceMethods<Util_TiedOpInterface, [ |
| "getTiedResult", |
| "getTiedResultOperandIndex", |
| "getTiedResultOperandIndices", |
| ]>, |
| ]> { |
| let summary = [{returns a reference to a subrange of a buffer}]; |
| let description = [{ |
| Returns a logical view into an underlying source buffer. This induces |
| aliasing and multiple SSA values may allow access to the same underlying |
| buffer storage. |
| |
| Subspans are a compiler-only concept and are propagated by an analysis pass |
| to result in absolute offsets on accesses any place the subrange would have |
| been used. |
| }]; |
| |
| let arguments = (ins |
| Util_BufferType:$source, |
| Util_Size:$source_size, |
| Util_Offset:$source_offset, |
| Util_Size:$result_size |
| ); |
| let results = (outs |
| Util_BufferType:$result |
| ); |
| |
| let assemblyFormat = [{ |
| $source `[` $source_offset `]` `:` |
| type($source) `` `{` $source_size `}` `->` |
| type($result) `` `{` $result_size `}` |
| attr-dict-with-keyword |
| }]; |
| |
| let extraClassDeclaration = [{ |
| Value getOperandSize(unsigned idx) { return getSourceSize(); } |
| Value getResultSize(unsigned idx) { return getResultSize(); } |
| |
| Value getSubrangeResource() { return getSource(); } |
| Value getSubrangeResourceSize() { return getSourceSize(); } |
| Value getSubrangeOffset() { return getSourceOffset(); } |
| Value getSubrangeLength() { return getResultSize(); } |
| Value getSubrangeResult() { return getResult(); } |
| |
| // Walks up the use-def chain to find a subspan op that feeds into |value|. |
| static IREE::Util::BufferSubspanOp findSubspanOp(Value value); |
| }]; |
| |
| let hasCanonicalizer = 1; |
| let hasFolder = 1; |
| } |
| |
| def Util_BufferSizeOp : Util_PureOp<"buffer.size", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| Util_SizeAwareOp, |
| ]> { |
| let summary = [{returns the total buffer storage size in bytes}]; |
| let description = [{ |
| Returns the total length of the buffer in bytes from its base offset. |
| }]; |
| |
| let arguments = (ins |
| Util_BufferType:$operand |
| ); |
| let results = (outs |
| Util_Size:$result |
| ); |
| |
| let assemblyFormat = [{ |
| $operand |
| `:` type($operand) |
| attr-dict-with-keyword |
| }]; |
| |
| let extraClassDeclaration = [{ |
| Value getOperandSize(unsigned idx) { return getResult(); } |
| Value getResultSize(unsigned idx) { return {}; } |
| }]; |
| |
| let hasCanonicalizer = 1; |
| let hasFolder = 1; |
| } |
| |
| def Util_BufferStorageOp : Util_PureOp<"buffer.storage", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| Util_SizeAwareOp, |
| ]> { |
| let summary = [{returns the underlying buffer storage range}]; |
| let description = [{ |
| Returns the buffer storage as a memref that must be offset and restricted to |
| the returned range. The memref may be of any type and the user is |
| responsible for ensuring that the reinterpret_cast-like behavior makes sense |
| for the data they are accessing. |
| }]; |
| |
| let arguments = (ins |
| Util_BufferType:$operand, |
| Util_Size:$operand_size |
| ); |
| let results = (outs |
| AnyMemRef:$result, |
| Util_Offset:$offset |
| ); |
| |
| let assemblyFormat = [{ |
| $operand |
| `:` type($operand) `` `{` $operand_size `}` `->` `(` type($result) `,` type($offset) `)` |
| attr-dict-with-keyword |
| }]; |
| |
| let extraClassDeclaration = [{ |
| Value getOperandSize(unsigned idx) { return getOperandSize(); } |
| Value getResultSize(unsigned idx) { return {}; } |
| }]; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| def Util_BufferCopyOp : Util_Op<"buffer.copy", [ |
| MemoryEffects<[MemRead, MemWrite]>, |
| Util_SizeAwareOp, |
| DeclareOpInterfaceMethods<Util_SubrangeOperandOpInterface>, |
| ]> { |
| let summary = [{copies a range of bytes between buffers}]; |
| let description = [{ |
| Copies a range of bytes as with memcpy (no overlapping). |
| }]; |
| |
| let arguments = (ins |
| Util_BufferType:$source, |
| Util_Size:$source_size, |
| Util_Offset:$source_offset, |
| Util_BufferType:$target, |
| Util_Size:$target_size, |
| Util_Offset:$target_offset, |
| Util_Size:$length |
| ); |
| |
| let assemblyFormat = [{ |
| $source `[` $source_offset `]` `,` |
| $target `[` $target_offset `]` `,` |
| $length `:` |
| type($source) `` `{` $source_size `}` `->` |
| type($target) `` `{` $target_size `}` |
| attr-dict-with-keyword |
| }]; |
| |
| let extraClassDeclaration = [{ |
| Value getOperandSize(unsigned idx) { return idx == 0 ? getSourceSize() : getTargetSize(); } |
| Value getResultSize(unsigned idx) { return {}; } |
| }]; |
| } |
| |
| def Util_BufferCompareOp : Util_PureOp<"buffer.compare", [ |
| MemoryEffects<[MemRead]>, |
| Util_SizeAwareOp, |
| DeclareOpInterfaceMethods<Util_SubrangeOperandOpInterface>, |
| ]> { |
| let summary = [{compares a range of two buffers}]; |
| let description = [{ |
| Returns true if the two ranges are bitwise equivalent, somewhat like memcmp. |
| }]; |
| |
| let arguments = (ins |
| Util_BufferType:$lhs, |
| Util_Size:$lhs_size, |
| Util_Offset:$lhs_offset, |
| Util_BufferType:$rhs, |
| Util_Size:$rhs_size, |
| Util_Offset:$rhs_offset, |
| Util_Size:$length |
| ); |
| let results = (outs |
| I1:$result |
| ); |
| |
| let assemblyFormat = [{ |
| $lhs `[` $lhs_offset `]` `,` |
| $rhs `[` $rhs_offset `]` `,` |
| $length `:` |
| type($lhs) `` `{` $lhs_size `}` `,` |
| type($rhs) `` `{` $rhs_size `}` |
| attr-dict-with-keyword |
| }]; |
| |
| let extraClassDeclaration = [{ |
| Value getOperandSize(unsigned idx) { return idx == 0 ? getLhsSize() : getRhsSize(); } |
| Value getResultSize(unsigned idx) { return {}; } |
| }]; |
| } |
| |
| def Util_BufferFillOp : Util_Op<"buffer.fill", [ |
| MemoryEffects<[MemWrite]>, |
| Util_SizeAwareOp, |
| DeclareOpInterfaceMethods<Util_SubrangeOperandOpInterface>, |
| ]> { |
| let summary = [{fills a range of bytes with a value}]; |
| let description = [{ |
| Fills the contents of the buffer in the given byte range with a pattern. |
| The offset and length must match the natural alignment of the pattern type. |
| }]; |
| |
| let arguments = (ins |
| Util_FillPattern:$pattern, |
| Util_BufferType:$target, |
| Util_Size:$target_size, |
| Util_Offset:$target_offset, |
| Util_Size:$length |
| ); |
| |
| let assemblyFormat = [{ |
| $pattern `,` |
| $target `[` $target_offset `for` $length `]` `:` |
| type($pattern) `->` |
| type($target) `` `{` $target_size `}` |
| attr-dict-with-keyword |
| }]; |
| |
| let extraClassDeclaration = [{ |
| Value getOperandSize(unsigned idx) { return getTargetSize(); } |
| Value getResultSize(unsigned idx) { return {}; } |
| }]; |
| } |
| |
| def Util_BufferLoadOp : Util_Op<"buffer.load", [ |
| MemoryEffects<[MemRead]>, |
| Util_SizeAwareOp, |
| DeclareOpInterfaceMethods<Util_SubrangeOperandOpInterface>, |
| ]> { |
| let summary = [{loads a value from a buffer}]; |
| let description = [{ |
| Loads a value at a byte offset. Must be aligned to the natural size of the |
| result type. |
| }]; |
| |
| let arguments = (ins |
| Util_BufferType:$source, |
| Util_Size:$source_size, |
| Util_Offset:$source_offset, |
| Util_Size:$length |
| ); |
| let results = (outs |
| Util_Primitive:$result |
| ); |
| |
| let assemblyFormat = [{ |
| $source `[` $source_offset `for` $length `]` |
| `:` type($source) `` `{` $source_size `}` `->` type($result) |
| attr-dict-with-keyword |
| }]; |
| |
| let extraClassDeclaration = [{ |
| Value getOperandSize(unsigned idx) { return getSourceSize(); } |
| Value getResultSize(unsigned idx) { return {}; } |
| }]; |
| |
| let hasFolder = 1; |
| } |
| |
| def Util_BufferStoreOp : Util_Op<"buffer.store", [ |
| MemoryEffects<[MemWrite]>, |
| Util_SizeAwareOp, |
| DeclareOpInterfaceMethods<Util_SubrangeOperandOpInterface>, |
| ]> { |
| let summary = [{stores a value into a buffer}]; |
| let description = [{ |
| Stores a value at a byte offset. Must be aligned to the natural size of the |
| source type. |
| }]; |
| |
| let arguments = (ins |
| Util_Primitive:$source, |
| Util_BufferType:$target, |
| Util_Size:$target_size, |
| Util_Offset:$target_offset, |
| Util_Size:$length |
| ); |
| |
| let assemblyFormat = [{ |
| $source `,` |
| $target `[` $target_offset `for` $length `]` |
| `:` type($source) `->` type($target) `` `{` $target_size `}` |
| attr-dict-with-keyword |
| }]; |
| |
| let extraClassDeclaration = [{ |
| Value getOperandSize(unsigned idx) { return getTargetSize(); } |
| Value getResultSize(unsigned idx) { return {}; } |
| }]; |
| } |
| |
| def Util_BufferHashOp : Util_Op<"buffer.hash", [ |
| MemoryEffects<[MemRead]>, |
| Util_SizeAwareOp, |
| DeclareOpInterfaceMethods<Util_SubrangeOperandOpInterface>, |
| ]> { |
| let summary = [{computes the hash of a byte range of a buffer}]; |
| let description = [{ |
| Computes the SipHash-2-4 of a value at a byte offset with the given length. |
| This always uses a seed of `0x0001020304...0e0f` and produces a single 64 |
| bit value. |
| }]; |
| |
| let arguments = (ins |
| Util_BufferType:$source, |
| Util_Size:$source_size, |
| Util_Offset:$source_offset, |
| Util_Size:$length |
| ); |
| let results = (outs |
| I64:$result |
| ); |
| |
| let assemblyFormat = [{ |
| $source `[` $source_offset `for` $length `]` |
| `:` type($source) `` `{` $source_size `}` `->` type($result) |
| attr-dict-with-keyword |
| }]; |
| |
| let extraClassDeclaration = [{ |
| Value getOperandSize(unsigned idx) { return getSourceSize(); } |
| Value getResultSize(unsigned idx) { return {}; } |
| }]; |
| } |
| |
| } // OpGroupBufferOps |
| |
| //===----------------------------------------------------------------------===// |
| // Status |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupStatusOps : OpDocGroup { |
| let summary = "Status ops"; |
| let description = ""; |
| } |
| |
| let opDocGroup = OpGroupStatusOps in { |
| |
| def Util_StatusCheckOkOp : Util_Op<"status.check_ok"> { |
| let summary = [{raises a global failure if a status is not 'ok'}]; |
| let description = [{ |
| When the status is not 'ok' this signals a runtime failure that causes the |
| entire active invocation - and possibly *all* in-flight and pending |
| invocations - to fail with the given status. The status will be propagated |
| back via the available runtime error handling mechanisms such as semaphores |
| or synchronous invocation results. |
| |
| As the IREE execution model is deeply pipelined it's possible that failures |
| have a latency between when they are emitted and when the application can |
| observe the failure. It's also possible that other work that is in-flight |
| or pending when the failure occurs will complete. |
| }]; |
| |
| let arguments = (ins |
| Util_Status:$status, |
| OptionalAttr<StrAttr>:$message |
| ); |
| |
| let assemblyFormat = [{ |
| $status (`,` $message^)? attr-dict |
| }]; |
| |
| let builders = [ |
| OpBuilder<(ins "Value":$status, CArg<"StringRef", [{""}]>:$message), |
| [{ |
| build( |
| $_builder, $_state, status, |
| message.empty() ? StringAttr{} : $_builder.getStringAttr(message)); |
| }]>, |
| ]; |
| } |
| |
| } // OpGroupStatusOps |
| |
| #endif // IREE_DIALECT_UTIL_IR_UTIL_OPS |