| // 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_HAL_OPS |
| #define IREE_DIALECT_HAL_OPS |
| |
| include "iree/compiler/Dialect/HAL/IR/HALBase.td" |
| include "iree/compiler/Dialect/HAL/IR/HALInterfaces.td" |
| include "iree/compiler/Dialect/Util/IR/UtilAttrs.td" |
| include "iree/compiler/Dialect/Util/IR/UtilInterfaces.td" |
| include "mlir/IR/BuiltinAttributeInterfaces.td" |
| include "mlir/Interfaces/FunctionInterfaces.td" |
| include "mlir/IR/OpAsmInterface.td" |
| include "mlir/IR/SymbolInterfaces.td" |
| include "mlir/Interfaces/CallInterfaces.td" |
| include "mlir/Interfaces/SideEffectInterfaces.td" |
| include "mlir/Interfaces/ViewLikeInterface.td" |
| |
| class HAL_PureOp<string mnemonic, list<Trait> traits = []> : |
| HAL_Op<mnemonic, !listconcat(traits, [Pure])>; |
| |
| class HAL_MakeTupleOp<string mnemonic, list<Trait> traits = []> : |
| HAL_PureOp<mnemonic, traits>; |
| |
| //===----------------------------------------------------------------------===// |
| // Magic temporary hacks |
| //===----------------------------------------------------------------------===// |
| // TODO(benvanik): remove these as the sequencer/other HAL ops are added. |
| |
| def OpGroupExperimentalOps : OpDocGroup { |
| let summary = "Experimental ops"; |
| let description = "Temporary hack ops expected to be removed in the future."; |
| } |
| |
| let opDocGroup = OpGroupExperimentalOps in { |
| |
| def HAL_ExSharedDeviceOp : HAL_PureOp<"ex.shared_device", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| ]> { |
| let results = (outs |
| HAL_Device:$result |
| ); |
| |
| let assemblyFormat = "attr-dict `:` type($result)"; |
| |
| let skipDefaultBuilders = 1; |
| let builders = [ |
| OpBuilder<(ins), |
| [{ |
| $_state.addTypes({DeviceType::get($_builder.getContext())}); |
| }]>, |
| ]; |
| } |
| |
| def HAL_ExFileFromMemoryOp : HAL_Op<"ex.file.from_memory", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| ]> { |
| let summary = [{creates a file mapped into a byte range of a host buffer}]; |
| let description = [{ |
| Returns a file handle that is backed by the given `buffer` contents. |
| Behavior is undefined if the buffer contents change while the accesses are |
| in-flight. |
| |
| Experimental as the exact interface for getting files from module contents |
| still needs iteration. Most hardware APIs require a file descriptor or |
| native platform handle but here we only have host pointers. When |
| memory-mapped some systems allow for retrieval of the platform handle from |
| a virtual address (GetMappedFileNameA/posix_mem_offset) but the APIs are |
| sketchy and likely slow. Instead we should probably have a way to query for |
| a file handle derived from the calling module by stack-walking and asking |
| the VM module for its handle. Until we can figure this out this method will |
| be marked epxerimental. |
| }]; |
| |
| let arguments = (ins |
| HAL_Device:$device, |
| HAL_DeviceQueueAffinity:$queue_affinity, |
| HAL_MemoryAccessBitfieldAttr:$access, |
| Util_BufferType:$buffer, |
| HAL_DeviceSize:$offset, |
| HAL_DeviceSize:$length, |
| I32:$flags |
| ); |
| let results = (outs |
| HAL_File:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `device` `(` $device `:` type($device) `)` |
| `affinity` `(` $queue_affinity `)` |
| `access` `(` $access `)` |
| `buffer` `(` $buffer `:` type($buffer) `)` |
| `` `[` $offset `for` $length `]` |
| `flags` `(` $flags `)` |
| `:` type($result) |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| } // OpGroupExperimentalOps |
| |
| //===----------------------------------------------------------------------===// |
| // Pseudo ops for conversion support |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupPseudoOps : OpDocGroup { |
| let summary = "Pseudo Ops"; |
| let description = "Pseudo ops for conversion support."; |
| } |
| |
| let opDocGroup = OpGroupPseudoOps in { |
| |
| def HAL_TensorImportOp : HAL_PureOp<"tensor.import", [ |
| AttrSizedOperandSegments, |
| DeclareOpInterfaceMethods<Util_TiedOpInterface, [ |
| "getTiedResult", |
| "getTiedResultOperandIndex", |
| "getTiedResultOperandIndices", |
| ]>, |
| Util_ShapeAwareOp, |
| ]> { |
| let summary = [{imports a tensor from a HAL buffer view}]; |
| let description = [{ |
| Defines an import of an external HAL buffer view into a SSA-form tensor. |
| An optional semaphore timepoint can be specified indicating when the |
| buffer view is available for use. If no semaphore timepoint is provided it |
| is assumed the buffer view is immediately available. |
| |
| The provided `target_encoding`, if different from the `target` type, |
| indicates that the ABI-facing type may differ from the internal |
| representation. The types must be bitcastable (same storage size) and |
| dynamically shaped values must have the same number of dynamic dimensions. |
| This allows for casting between rank-0 and rank-N types, different element |
| types, etc. |
| }]; |
| |
| let arguments = (ins |
| AnyTypeOf<[HAL_Buffer, HAL_BufferView]>:$source, |
| TypeAttr:$target_encoding, |
| HAL_ShapeDynamicDims:$target_dims, |
| Optional<HAL_Fence>:$wait_fence, |
| OptionalAttr<StrAttr>:$name |
| ); |
| let results = (outs |
| AnyTensor:$target |
| ); |
| |
| let assemblyFormat = [{ |
| (`wait` `(` $wait_fence^ `)` `=` `` `>`)? |
| $source |
| ($name^)? |
| `:` type($source) `->` |
| custom<TypeAlias>($target_encoding, type($target)) (`{` $target_dims^ `}`)? |
| attr-dict |
| }]; |
| |
| let builders = [ |
| OpBuilder<(ins |
| "Type":$resultType, |
| "Value":$source, |
| "TypeAttr":$targetEncoding, |
| "StringAttr":$name |
| )>, |
| OpBuilder<(ins |
| "Type":$resultType, |
| "Value":$source, |
| "TypeAttr":$targetEncoding, |
| "Value":$waitFence, |
| "StringAttr":$name |
| )>, |
| ]; |
| |
| let extraClassDeclaration = [{ |
| ValueRange getOperandDynamicDims(unsigned idx) { return {}; } |
| ValueRange getResultDynamicDims(unsigned idx) { return getTargetDims(); } |
| }]; |
| |
| let hasVerifier = 1; |
| |
| let hasFolder = 1; |
| } |
| |
| def HAL_TensorExportOp : HAL_PureOp<"tensor.export", [ |
| AttrSizedOperandSegments, |
| DeclareOpInterfaceMethods<Util_TiedOpInterface, [ |
| "getTiedResult", |
| "getTiedResultOperandIndex", |
| "getTiedResultOperandIndices", |
| ]>, |
| Util_ShapeAwareOp, |
| ]> { |
| let summary = [{exports a tensor to a HAL buffer view}]; |
| let description = [{ |
| Defines an export of an SSA-form tensor to an external HAL buffer view. |
| |
| The provided `source_encoding`, if different from the `source` type, |
| indicates that the ABI-facing type may differ from the internal |
| representation. The types must be bitcastable (same storage size) and |
| dynamically shaped values must have the same number of dynamic dimensions. |
| This allows for casting between rank-0 and rank-N types, different element |
| types, etc. |
| |
| An optional `target_storage` buffer can be provided to hold the exported |
| result. The export will fail at runtime if the storage is null or if it has |
| insufficient capacity to store the output. The storage must be |
| device-visible and defined for transfer-target and dispatch usage. |
| }]; |
| |
| let arguments = (ins |
| AnyTensor:$source, |
| TypeAttr:$source_encoding, |
| HAL_ShapeDynamicDims:$source_dims, |
| Optional<AnyTypeOf<[HAL_Buffer, HAL_BufferView]>>:$target_storage, |
| OptionalAttr<StrAttr>:$name |
| ); |
| let results = (outs |
| AnyTypeOf<[HAL_Buffer, HAL_BufferView]>:$target |
| ); |
| |
| let assemblyFormat = [{ |
| $source |
| ($name^)? |
| (`into` `(` $target_storage^ `:` type($target_storage) `)`)? |
| `:` |
| custom<TypeAlias>($source_encoding, type($source)) (`{` $source_dims^ `}`)? |
| `->` |
| type($target) |
| attr-dict |
| }]; |
| |
| let builders = [ |
| OpBuilder<(ins |
| "Type":$resultType, |
| "Value":$source, |
| "TypeAttr":$sourceEncoding, |
| "StringAttr":$name |
| )>, |
| ]; |
| |
| let extraClassDeclaration = [{ |
| ValueRange getOperandDynamicDims(unsigned idx) { return getSourceDims(); } |
| ValueRange getResultDynamicDims(unsigned idx) { return {}; } |
| }]; |
| |
| let hasVerifier = 1; |
| |
| let hasFolder = 1; |
| } |
| |
| def HAL_TensorBarrierOp : HAL_Op<"tensor.barrier", [ |
| AllTypesMatch<["sources", "results"]>, |
| DeclareOpInterfaceMethods<Util_TiedOpInterface, [ |
| "getTiedResult", |
| "getTiedResultOperandIndex", |
| "getTiedResultOperandIndices", |
| ]>, |
| ]> { |
| let summary = [{signals a fence when all tensors are available}]; |
| let description = [{ |
| Defines a barrier that is used to indicate availability of an entire set of |
| tensors by signaling a fence. The source tensors are returned for chaining. |
| }]; |
| |
| let arguments = (ins |
| Variadic<AnyTensor>:$sources, |
| HAL_Fence:$signal_fence |
| ); |
| let results = (outs |
| Variadic<AnyTensor>:$results |
| ); |
| |
| let assemblyFormat = [{ |
| `join` `` `(` $sources `:` type($sources) `)` |
| `=` `` `>` |
| $signal_fence `:` type($signal_fence) |
| attr-dict-with-keyword |
| }]; |
| |
| let builders = [ |
| OpBuilder<(ins |
| "ValueRange":$sources, |
| "Value":$signalFence |
| )>, |
| ]; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| def HAL_DispatchExternOp : HAL_PureOp<"dispatch.extern", [ |
| IsolatedFromAbove, |
| AttrSizedOperandSegments, |
| DeclareOpInterfaceMethods<Util_TiedOpInterface, [ |
| "getTiedOperandsIndexAndLength", |
| ]>, |
| Util_ShapeAwareOp, |
| ]> { |
| let summary = [{a dispatch of workgroups across a 3-dimensional grid}]; |
| let description = [{ |
| Dispatches some number of workgroups across a 3-dimensional grid using a |
| function defined externally in one or more referenced objects. Objects are |
| declared per executable target and selected automatically during linking |
| based on where the dispatch is used. Semantically this is equivalent to |
| a `flow.dispatch.workgroups` but with the workgroup region invisible to the |
| compiler. See `hal.executable` for more information about object linkage. |
| |
| Note that since this happens at tensor level the dispatch operation has |
| value semantics: some tensors (and optionally other primitive types) are |
| consumed and one or more new result tensors are produced. Inside each |
| workgroup, however, the input and output tensors are available for arbitrary |
| loads and stores. In many cases each workgroup will load some particular |
| tile(s) from the input tensors and store some particular tile(s) to the |
| output tensors unique to that workgroup. Though it's possible for multiple |
| workgroups to load the same regions of the input tensors behavior is |
| undefined if multiple workgroups store to the same regions of the output |
| tensors. Codegen guarantees this behavior but when sourcing externally |
| authored dispatch functions it's critical that this behavior is observed. |
| |
| Though the representation is similar to the GPU-style grid dispatch model |
| here we still have not yet allocated buffers, determined the target device |
| for execution, or even completed fully resolving shapes/types/etc. Because |
| of this it's important that the workgroup body use the platform-dependent |
| primitives for accessing workgroup ID, size, and count intrinsics instead |
| of hardcoding them to a particular set of values. Assume that any workgroup |
| dispatch may end up being specialized for several different target devices |
| and even several different variants for a particular target device |
| (differing workgroup sizes, etc). To aid deduplication code producing these |
| external dispatches should try not to specialize early for particular shapes |
| and instead emit the most generic code possible as having 500 slightly |
| different `hal.dispatch.extern` ops pointing at the same object file is |
| likely to require 500 copies of the object instead of 500 calls to the same |
| object. |
| |
| Because at this point in the layering devices have not yet been selected the |
| workgroup count cannot be fully evaluated. Instead workload parameters are |
| captured that are then passed to a function that when later evaluated |
| computes the actual workgroup count based on target information. The |
| workload is not limited to the 3D XYZ grid dispatch of the workgroup count |
| and can contain any number of parameters used to compute it. If workgroup |
| size or distribution varies based on the target device a `!hal.device` |
| argument can be used by the workgroup count calculation region to factor in |
| device parameters. See `hal.device.query` for more information on how to |
| query information. |
| |
| ```mlir |
| %r = hal.dispatch.extern "some_function"[%c5, %c5](%0, %1) |
| : (tensor<5x5xf32>, tensor<5xf32>) -> tensor<5x5xf32> |
| ... |
| ``` |
| |
| The number of results of the operation is equal to the number of results |
| in the type signature (`(tensor<5x5xf32>, tensor<5xf32>) -> tensor<5x5xf32>`). |
| Each tensor argument and result in the type signature has a corresponding |
| pipeline layout slot and must be declared. If multiple arguments or results |
| share the same layout slot they can be aliased using the `bindings` |
| attribute and otherwise each is assumed unique. |
| |
| There are no `arguments` operands for results, but a result can be tied an |
| argument by writing the argument operand's SSA value instead of its type: |
| E.g., in the above example, `-> %0` would tie the first argument to the |
| result. In that case, there would be no separate block argument for the |
| result. |
| }]; |
| |
| let arguments = (ins |
| StrAttr:$export, |
| Variadic<Index>:$workload, |
| Variadic<AnyType>:$arguments, |
| HAL_ShapeDynamicDims:$argument_dims, |
| HAL_ShapeDynamicDims:$result_dims, |
| HAL_PipelineLayoutAttr:$layout, |
| HAL_ExecutableObjectsAttr:$objects, |
| OptionalAttr<HAL_WorkgroupSizeAttr>:$workgroup_size, |
| OptionalAttr<HAL_SubgroupSizeAttr>:$subgroup_size, |
| OptionalAttr<IndexAttr>:$workgroup_local_memory, |
| OptionalAttr<HAL_InterfaceBindingArrayAttr>:$bindings, |
| OptionalAttr<Util_TiedOpStorageAttr>:$tied_operands |
| ); |
| let results = (outs |
| Variadic<AnyType>:$results |
| ); |
| |
| let regions = (region |
| AnyRegion:$workgroup_count |
| ); |
| |
| let assemblyFormat = [{ |
| $export |
| (`[` $workload^ `]`)? `` |
| `(` $arguments `)` `:` |
| custom<ShapedFunctionType>(ref($arguments), |
| type($arguments), $argument_dims, |
| type($results), $result_dims, |
| $tied_operands) |
| `count` `` custom<WorkgroupCountRegion>($workgroup_count) |
| `layout` `(` $layout `)` |
| (`bindings` `(` $bindings^ `)`)? |
| `objects` `(` $objects `)` |
| attr-dict-with-keyword |
| }]; |
| |
| let skipDefaultBuilders = 1; |
| let builders = [ |
| OpBuilder<(ins |
| "ValueRange":$workload, |
| "TypeRange":$resultTypes, "ValueRange":$resultDims, |
| "ValueRange":$arguments, "ValueRange":$argumentDims, |
| "ArrayRef<int64_t>":$tiedOperands, |
| CArg<"ArrayRef<NamedAttribute>", "{}">:$attributes)>, |
| ]; |
| |
| let extraClassDeclaration = [{ |
| FunctionType getDispatchType() { |
| return FunctionType::get( |
| getContext(), llvm::map_to_vector(getArguments(), [](Value value) { |
| return value.getType(); |
| }), |
| getResultTypes()); |
| } |
| |
| /// Returns the index of the args() operand in the Operation operands list. |
| unsigned mapArgOperandToOpOperand(unsigned i) { return i + getWorkload().size(); }; |
| |
| ValueRange getOperandDynamicDims(unsigned idx) { |
| return IREE::Util::findVariadicDynamicDims(idx - getWorkload().size(), getArguments(), getArgumentDims()); |
| } |
| ValueRange getResultDynamicDims(unsigned idx) { |
| return IREE::Util::findVariadicDynamicDims(idx, getResults(), getResultDims()); |
| } |
| }]; |
| |
| let hasVerifier = 1; |
| } |
| |
| } // OpGroupPseudoOps |
| |
| //===----------------------------------------------------------------------===// |
| // !hal.allocator / iree_hal_allocator_t |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupAllocatorOps : OpDocGroup { |
| let summary = "Allocator ops"; |
| let description = "Ops for `!hal.allocator` / `iree_hal_allocator_t`."; |
| } |
| |
| let opDocGroup = OpGroupAllocatorOps in { |
| |
| def HAL_AllocatorAllocateOp : HAL_Op<"allocator.allocate", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| DeclareOpInterfaceMethods<Util_SizeAwareOp>, |
| ]> { |
| let summary = [{empty buffer allocation operation}]; |
| let description = [{ |
| Allocates a buffer of the given size from the allocator. |
| The size of the buffer returned may be larger than the requested size if the |
| allocator has specific alignment requirements or minimum allocation sizes. |
| }]; |
| |
| let arguments = (ins |
| HAL_Allocator:$allocator, |
| HAL_DeviceQueueAffinity:$queue_affinity, |
| HAL_MemoryTypeBitfieldAttr:$memory_types, |
| HAL_BufferUsageBitfieldAttr:$buffer_usage, |
| HAL_DeviceSize:$result_size |
| ); |
| let results = (outs |
| HAL_Buffer:$result |
| ); |
| |
| // TODO(benvanik): change type/usage to ref params. |
| let assemblyFormat = [{ |
| `<` $allocator `:` type($allocator) `>` |
| `affinity` `(` $queue_affinity `)` |
| `type` `(` $memory_types `)` |
| `usage` `(` $buffer_usage `)` |
| `:` custom<SizeAwareType>(type($result), $result_size) |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_AllocatorImportOp : HAL_Op<"allocator.import", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| DeclareOpInterfaceMethods<Util_SizeAwareOp>, |
| ]> { |
| let summary = [{allocator-supported host buffer import operation}]; |
| let description = [{ |
| Tries importing host memory backed by the given byte buffer into a |
| device accessible `!hal.buffer`. The returned buffer may be host-only and |
| not directly usable on devices. If the mapping cannot be completed (such as |
| trying to map the host memory as device-local on devices with discrete |
| memory) then `did_import` will indicate that the returned buffer is null. |
| }]; |
| |
| let arguments = (ins |
| HAL_Allocator:$allocator, |
| HAL_DeviceQueueAffinity:$queue_affinity, |
| HAL_MemoryTypeBitfieldAttr:$memory_types, |
| HAL_BufferUsageBitfieldAttr:$buffer_usage, |
| // TODO(benvanik): support other types (and mutable buffers). |
| Util_BufferType:$source, |
| HAL_DeviceSize:$offset, |
| HAL_DeviceSize:$length |
| ); |
| let results = (outs |
| I1:$did_import, |
| HAL_Buffer:$result |
| ); |
| |
| // TODO(benvanik): change type/usage to ref params. |
| let assemblyFormat = [{ |
| `<` $allocator `:` type($allocator) `>` |
| `source` `(` $source `:` type($source) `)` `` `[` $offset `,` $length `]` |
| `affinity` `(` $queue_affinity `)` |
| `type` `(` $memory_types `)` |
| `usage` `(` $buffer_usage `)` |
| `:` type($did_import) `,` type($result) |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| } // OpGroupAllocatorOps |
| |
| //===----------------------------------------------------------------------===// |
| // !hal.buffer / iree_hal_buffer_t |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupBufferOps : OpDocGroup { |
| let summary = "Buffer ops"; |
| let description = "Ops for `!hal.buffer` / `iree_hal_buffer_t`."; |
| } |
| |
| let opDocGroup = OpGroupBufferOps in { |
| |
| def HAL_BufferAssertOp : HAL_Op<"buffer.assert", []> { |
| let summary = [{buffer compatibility assertion}]; |
| let description = [{ |
| Asserts that the buffer is compatible with the given allocator and usage. |
| Program execution will abort as if `std.assert` had been used. |
| |
| This only checks that the buffer can be used and not that it matches the |
| given parameters exactly. Buffers may be from other allocators so long as |
| the allocators are compatible (devices can address each other's memory), |
| the type and usage contain all the requested bits (having more bits is ok), |
| and the length is at least the requested minimum (as padding may be |
| ignored). |
| }]; |
| |
| let arguments = (ins |
| HAL_Buffer:$buffer, |
| StrAttr:$message, |
| HAL_Allocator:$allocator, |
| HAL_DeviceSize:$minimum_length, |
| HAL_MemoryTypeBitfieldAttr:$memory_types, |
| HAL_BufferUsageBitfieldAttr:$buffer_usage |
| ); |
| let results = (outs); |
| |
| let assemblyFormat = [{ |
| `<` $buffer `:` type($buffer) `>` |
| `message` `(` $message `)` |
| `allocator` `(` $allocator `:` type($allocator) `)` |
| `minimum_length` `(` $minimum_length `)` |
| `type` `(` $memory_types `)` |
| `usage` `(` $buffer_usage `)` |
| attr-dict-with-keyword |
| }]; |
| |
| // TODO(benvanik): fold away when we know some properties of the buffer |
| // (such as when we create it ourselves earlier on) or we've already asserted. |
| } |
| |
| def HAL_BufferSubspanOp : HAL_PureOp<"buffer.subspan", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| DeclareOpInterfaceMethods<Util_SizeAwareOp>, |
| ]> { |
| let summary = [{buffer subspan operation}]; |
| let description = [{ |
| Returns a reference to a subspan of the buffer. |
| }]; |
| |
| let arguments = (ins |
| HAL_BufferType:$source_buffer, |
| HAL_DeviceSize:$source_offset, |
| HAL_DeviceSize:$length |
| ); |
| let results = (outs |
| HAL_BufferType:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $source_buffer `:` type($source_buffer) `>` |
| `` `[` $source_offset `,` $length `]` |
| `:` type($result) |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_BufferLengthOp : HAL_PureOp<"buffer.length", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| ]> { |
| let summary = [{buffer byte length accessor}]; |
| let description = [{ |
| Returns the allocated size of a buffer in bytes. |
| May be less than the underlying buffer allocation if this is a subspan or |
| view into another buffer. |
| }]; |
| |
| let arguments = (ins |
| HAL_BufferType:$buffer |
| ); |
| let results = (outs |
| HAL_DeviceSize:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $buffer `:` type($buffer) `>` |
| `:` type($result) |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_BufferLoadOp : HAL_PureOp<"buffer.load"> { |
| let summary = [{buffer element load operation}]; |
| let description = [{ |
| Loads a value from a buffer by mapping it. |
| }]; |
| |
| let arguments = (ins |
| HAL_BufferType:$source_buffer, |
| HAL_DeviceSize:$source_offset |
| ); |
| let results = (outs |
| AnyTypeOf<[HAL_PrimitiveType, AnyVector]>:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $source_buffer `:` type($source_buffer) `>` |
| `` `[` $source_offset `]` |
| `:` type($result) |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_BufferStoreOp : HAL_Op<"buffer.store"> { |
| let summary = [{buffer element store operation}]; |
| let description = [{ |
| Stores a value into a buffer by mapping it. |
| }]; |
| |
| let arguments = (ins |
| AnyTypeOf<[HAL_PrimitiveType, AnyVector]>:$value, |
| HAL_BufferType:$target_buffer, |
| HAL_DeviceSize:$target_offset |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $target_buffer `:` type($target_buffer) `>` |
| `` `[` $target_offset `]` |
| `value` `(` $value `:` type($value) `)` |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| } // OpGroupBufferOps |
| |
| //===----------------------------------------------------------------------===// |
| // !hal.buffer_view / iree_hal_buffer_view_t |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupBufferViewOps : OpDocGroup { |
| let summary = "Buffer view ops"; |
| let description = "Ops for `!hal.buffer_view` / `iree_hal_buffer_view_t`."; |
| } |
| |
| let opDocGroup = OpGroupBufferViewOps in { |
| |
| def HAL_BufferViewCreateOp : HAL_PureOp<"buffer_view.create", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| ]> { |
| let summary = [{buffer view reference initializer}]; |
| let description = [{ |
| Creates a reference to a buffer with a particular shape and element type. |
| The buffer is not copied and both the original and view references must be |
| synchronized. This makes it easier to associate commonly-carried metadata |
| along with the contents. |
| }]; |
| |
| let arguments = (ins |
| HAL_BufferType:$source_buffer, |
| HAL_DeviceSize:$source_offset, |
| HAL_DeviceSize:$source_length, |
| HAL_ElementType:$element_type, |
| HAL_EncodingType:$encoding_type, |
| HAL_Shape:$shape |
| ); |
| let results = (outs |
| HAL_BufferView:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `buffer` `(` $source_buffer `:` type($source_buffer) `)` |
| `` `[` $source_offset `,` $source_length `]` |
| `shape` `(` `[` $shape `]` `)` |
| `type` `(` $element_type `)` |
| `encoding` `(` $encoding_type `)` |
| `:` type($result) |
| attr-dict-with-keyword |
| }]; |
| |
| let skipDefaultBuilders = 1; |
| let builders = [ |
| OpBuilder<(ins |
| "Value":$sourceBuffer, |
| "Value":$sourceOffset, |
| "Value":$sourceLength, |
| "int32_t":$elementType, |
| "int32_t":$encodingType, |
| "ValueRange":$shape |
| )>, |
| OpBuilder<(ins |
| "Value":$sourceBuffer, |
| "Value":$sourceOffset, |
| "Value":$sourceLength, |
| "Value":$elementType, |
| "Value":$encodingType, |
| "ValueRange":$shape |
| )>, |
| ]; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| def HAL_BufferViewAssertOp : HAL_Op<"buffer_view.assert", []> { |
| let summary = [{buffer view contents assertion}]; |
| let description = [{ |
| Asserts that the buffer view contains a data compatible tensor with the |
| given encoding. Program execution will abort as if `std.assert` had been |
| used. |
| }]; |
| |
| let arguments = (ins |
| HAL_BufferView:$buffer_view, |
| StrAttr:$message, |
| HAL_ElementType:$element_type, |
| HAL_EncodingType:$encoding_type, |
| HAL_Shape:$shape |
| ); |
| let results = (outs); |
| |
| let assemblyFormat = [{ |
| `<` $buffer_view `:` type($buffer_view) `>` |
| `message` `(` $message `)` |
| `shape` `(` `[` $shape `]` `)` |
| `type` `(` $element_type `)` |
| `encoding` `(` $encoding_type `)` |
| attr-dict-with-keyword |
| }]; |
| |
| // TODO(benvanik): fold away when we know some properties of the buffer view |
| // (such as when we create it ourselves earlier on) or we've already asserted. |
| } |
| |
| def HAL_BufferViewBufferOp : HAL_PureOp<"buffer_view.buffer", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| ]> { |
| let summary = [{buffer view buffer accessor}]; |
| let description = [{ |
| Returns the buffer backing this view's contents. |
| }]; |
| |
| let arguments = (ins |
| HAL_BufferView:$buffer_view |
| ); |
| let results = (outs |
| HAL_BufferType:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $buffer_view `:` type($buffer_view) `>` |
| `:` type($result) |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_BufferViewElementTypeOp : HAL_PureOp<"buffer_view.element_type"> { |
| let summary = [{buffer view element type query}]; |
| let description = [{ |
| Returns the element type of the buffer view. |
| }]; |
| |
| let arguments = (ins |
| HAL_BufferView:$buffer_view |
| ); |
| let results = (outs |
| HAL_ElementType:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $buffer_view `:` type($buffer_view) `>` |
| `:` type($result) |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_BufferViewEncodingTypeOp : HAL_PureOp<"buffer_view.encoding_type"> { |
| let summary = [{buffer view encoding type query}]; |
| let description = [{ |
| Returns the encoding type of the buffer view. |
| }]; |
| |
| let arguments = (ins |
| HAL_BufferView:$buffer_view |
| ); |
| let results = (outs |
| HAL_EncodingType:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $buffer_view `:` type($buffer_view) `>` |
| `:` type($result) |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_BufferViewRankOp : HAL_PureOp<"buffer_view.rank"> { |
| let summary = [{buffer view rank query}]; |
| let description = [{ |
| Returns the rank of the buffer view. |
| }]; |
| |
| let arguments = (ins |
| HAL_BufferView:$buffer_view |
| ); |
| let results = (outs |
| HAL_Dim:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $buffer_view `:` type($buffer_view) `>` |
| `:` type($result) |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_BufferViewDimOp : HAL_PureOp<"buffer_view.dim"> { |
| let summary = [{buffer view dimension value query}]; |
| let description = [{ |
| Returns the value of the given dimension. |
| }]; |
| |
| let arguments = (ins |
| HAL_BufferView:$buffer_view, |
| IndexAttr:$index |
| ); |
| let results = (outs |
| HAL_Dim:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $buffer_view `:` type($buffer_view) `>` |
| `` `[` $index `]` |
| `:` type($result) |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_BufferViewTraceOp : HAL_Op<"buffer_view.trace", []> { |
| let summary = [{trace value(s) operation}]; |
| let description = [{ |
| Traces out to a runtime trace sink (console, log file, etc) the given buffer |
| views and titles them with the given key. The key is informational only and |
| useful for titling/marking specific sets of buffers for easier searching. |
| }]; |
| |
| let arguments = (ins |
| StrAttr:$key, |
| Variadic<HAL_BufferView>:$operands |
| ); |
| |
| let assemblyFormat = [{ |
| $key `=` |
| $operands `:` type($operands) |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| } // OpGroupBufferViewOps |
| |
| //===----------------------------------------------------------------------===// |
| // !hal.channel / iree_hal_channel_t |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupChannelOps : OpDocGroup { |
| let summary = "Channel ops"; |
| let description = "Ops for `!hal.channel` / `iree_hal_channel_t`."; |
| } |
| |
| let opDocGroup = OpGroupChannelOps in { |
| |
| def HAL_ChannelCreateOp : HAL_Op<"channel.create", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| ]> { |
| let summary = [{creates a new channel for collective communication}]; |
| let description = [{ |
| Returns a new channel with the given rank associated with the given device |
| queue. Collective operations using this channel must only be submitted on |
| compatible queues. |
| |
| The group and ID are optional and may be null. A rank or count of -1 can be |
| used to indicate a default inherited from the environment or device |
| configuration. |
| }]; |
| |
| let arguments = (ins |
| HAL_Device:$device, |
| HAL_DeviceQueueAffinity:$queue_affinity, |
| I32Attr:$flags, |
| Util_BufferType:$id, |
| Util_BufferType:$group, |
| I32:$rank, |
| I32:$count |
| ); |
| let results = (outs |
| HAL_Channel:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `device` `(` $device `:` type($device) `)` |
| `affinity` `(` $queue_affinity `)` |
| `flags` `(` $flags `)` |
| `id` `(` $id `)` |
| `group` `(` $group `)` |
| `rank` `(` $rank `)` |
| `count` `(` $count `)` |
| `:` type($result) |
| attr-dict-with-keyword |
| }]; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| def HAL_ChannelSplitOp : HAL_Op<"channel.split", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| ]> { |
| let summary = [{splits a collective communication channel}]; |
| let description = [{ |
| Partitions the group associated with the given channel into disjoint |
| subgroups for each unique value of color. Each new subgroup contains all |
| participants of the same color and within each subgroup the key argument |
| is used to define the rank order. When multiple participants in a group |
| use the same key the tie will be broken using their rank in the parent |
| group. A color of -1 indicates that the rank does not participate in any |
| subgroup and will return a null channel. |
| }]; |
| |
| let arguments = (ins |
| HAL_Channel:$channel, |
| I32:$color, |
| I32:$key, |
| I32Attr:$flags |
| ); |
| let results = (outs |
| HAL_Channel:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $channel `:` type($channel) `>` |
| `color` `(` $color `)` |
| `key` `(` $key `)` |
| `flags` `(` $flags `)` |
| `:` type($result) |
| attr-dict-with-keyword |
| }]; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| def HAL_ChannelRankAndCountOp : HAL_PureOp<"channel.rank_and_count", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| ]> { |
| let summary = [{returns the rank of the local participant in the group}]; |
| let description = [{ |
| Returns the rank the channel represents as a participant in a collective |
| group in `[0, count)` and the total participant count. |
| }]; |
| |
| let arguments = (ins |
| HAL_Channel:$channel |
| ); |
| let results = (outs |
| I32:$rank, |
| I32:$count |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $channel `:` type($channel) `>` |
| `:` type($rank) `,` type($count) |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| } // OpGroupChannelOps |
| |
| //===----------------------------------------------------------------------===// |
| // !hal.command_buffer / iree_hal_command_buffer_t |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupCommandBufferOps : OpDocGroup { |
| let summary = "Command buffer ops"; |
| let description = "Ops for `!hal.command_buffer` / `iree_hal_command_buffer_t`."; |
| } |
| |
| let opDocGroup = OpGroupCommandBufferOps in { |
| |
| def HAL_CommandBufferCreateOp : HAL_Op<"command_buffer.create", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| ]> { |
| let summary = [{command buffer allocation operation}]; |
| let description = [{ |
| Returns a command buffer from the device pool ready to begin recording. |
| }]; |
| |
| let arguments = (ins |
| HAL_Device:$device, |
| HAL_CommandBufferModeBitfieldAttr:$modes, |
| HAL_CommandCategoryBitfieldAttr:$command_categories, |
| Optional<Index>:$binding_capacity |
| ); |
| let results = (outs |
| HAL_CommandBuffer:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `device` `(` $device `:` type($device) `)` |
| `mode` `(` $modes `)` |
| `categories` `(` $command_categories `)` |
| (`bindings` `(` $binding_capacity^ `)`)? |
| `:` type($result) |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_CommandBufferFinalizeOp : HAL_Op<"command_buffer.finalize"> { |
| let summary = [{finalizes command buffer recording}]; |
| let description = [{ |
| Ends recording into the command buffer and prepares it for submission. |
| No more commands may be recorded into the command buffer. |
| }]; |
| |
| let arguments = (ins |
| HAL_CommandBuffer:$command_buffer |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $command_buffer `:` type($command_buffer) `>` |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_CommandBufferDeviceOp : HAL_PureOp<"command_buffer.device"> { |
| let summary = [{command buffer device query operation}]; |
| let description = [{ |
| Used during conversion to access the device used to create a command buffer. |
| }]; |
| |
| let arguments = (ins |
| HAL_CommandBuffer:$command_buffer |
| ); |
| let results = (outs |
| HAL_Device:$device |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $command_buffer `:` type($command_buffer) `>` |
| `:` type($device) |
| attr-dict-with-keyword |
| }]; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| def HAL_CommandBufferBeginDebugGroupOp : HAL_Op<"command_buffer.begin_debug_group"> { |
| let summary = [{pushes a command buffer debug group label}]; |
| let description = [{ |
| Pushes a new debug group with the given label. |
| All commands between this and a mandatory matching call to |
| `hal.command_buffer.end_debug_group` will be grouped together with the |
| given label. |
| }]; |
| |
| let arguments = (ins |
| HAL_CommandBuffer:$command_buffer, |
| StrAttr:$label |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $command_buffer `:` type($command_buffer) `>` |
| `label` `(` $label `)` |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_CommandBufferEndDebugGroupOp : HAL_Op<"command_buffer.end_debug_group"> { |
| let summary = [{pops a command buffer debug group label}]; |
| let description = [{ |
| Pops a debug group from the stack. |
| }]; |
| |
| let arguments = (ins |
| HAL_CommandBuffer:$command_buffer |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $command_buffer `:` type($command_buffer) `>` |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_CommandBufferExecutionBarrierOp : HAL_Op<"command_buffer.execution_barrier"> { |
| let summary = [{command buffer execution barrier recording operation}]; |
| let description = [{ |
| Defines an execution dependency between all commands recorded before the |
| barrier and all commands recorded after the barrier. Only the stages |
| provided will be affected. |
| }]; |
| |
| let arguments = (ins |
| HAL_CommandBuffer:$command_buffer, |
| HAL_ExecutionStageBitfieldAttr:$source_stage_mask, |
| HAL_ExecutionStageBitfieldAttr:$target_stage_mask, |
| HAL_ExecutionBarrierFlagBitfieldAttr:$flags |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $command_buffer `:` type($command_buffer) `>` |
| `source` `(` $source_stage_mask `)` |
| `target` `(` $target_stage_mask `)` |
| `flags` `(` $flags `)` |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| // TODO(benvanik): event ops. |
| |
| def HAL_CommandBufferFillBufferOp : HAL_Op<"command_buffer.fill_buffer"> { |
| let summary = [{command buffer buffer fill recording operation}]; |
| let description = [{ |
| Fills the target buffer with the given repeating value. |
| }]; |
| |
| let arguments = (ins |
| HAL_CommandBuffer:$command_buffer, |
| HAL_BufferType:$target_buffer, |
| HAL_DeviceSize:$target_offset, |
| HAL_DeviceSize:$length, |
| HAL_FillPatternType:$pattern |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $command_buffer `:` type($command_buffer) `>` |
| `target` `(` $target_buffer `:` type($target_buffer) `)` |
| `` `[` $target_offset `,` $length `]` |
| `pattern` `(` $pattern `:` type($pattern) `)` |
| attr-dict-with-keyword |
| }]; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| // TODO(benvanik): update buffer op. |
| |
| def HAL_CommandBufferCopyBufferOp : HAL_Op<"command_buffer.copy_buffer"> { |
| let summary = [{command buffer buffer copy recording operation}]; |
| let description = [{ |
| Copies a range of one buffer to another. |
| }]; |
| |
| let arguments = (ins |
| HAL_CommandBuffer:$command_buffer, |
| HAL_BufferType:$source_buffer, |
| HAL_DeviceSize:$source_offset, |
| HAL_BufferType:$target_buffer, |
| HAL_DeviceSize:$target_offset, |
| HAL_DeviceSize:$length |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $command_buffer `:` type($command_buffer) `>` |
| `source` `(` $source_buffer `:` type($source_buffer) `)` |
| `` `[` $source_offset `]` |
| `target` `(` $target_buffer `:` type($target_buffer) `)` |
| `` `[` $target_offset `]` |
| `length` `(` $length `)` |
| attr-dict-with-keyword |
| }]; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| def HAL_CommandBufferCollectiveOp : |
| HAL_Op<"command_buffer.collective", [ |
| AttrSizedOperandSegments, |
| ]> { |
| let summary = [{command buffer collective dispatch recording operation}]; |
| let description = [{ |
| Dispatches a collective operation defined by op using the given buffers. |
| }]; |
| |
| let arguments = (ins |
| HAL_CommandBuffer:$command_buffer, |
| HAL_Channel:$channel, |
| HAL_CollectiveAttr:$op, |
| HAL_DeviceSize:$element_count, |
| Optional<I32>:$param, |
| // TODO(benvanik): change this to take descriptor set + binding instead. |
| // This would let us use indirect bindings. |
| Optional<HAL_BufferType>:$send_buffer, |
| Optional<HAL_DeviceSize>:$send_offset, |
| Optional<HAL_DeviceSize>:$send_length, |
| Optional<HAL_BufferType>:$recv_buffer, |
| Optional<HAL_DeviceSize>:$recv_offset, |
| Optional<HAL_DeviceSize>:$recv_length |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $command_buffer `:` type($command_buffer) `>` |
| `channel` `(` $channel `:` type($channel) `)` |
| `op` `(` $op `)` |
| (`param` `(` $param^ `:` type($param) `)`)? |
| (`send` `(` $send_buffer^ `:` type($send_buffer) `)` |
| `` `[` $send_offset `,` $send_length `]`)? |
| (`recv` `(` $recv_buffer^ `:` type($recv_buffer) `)` |
| `` `[` $recv_offset `,` $recv_length `]`)? |
| `count` `(` $element_count `)` |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_CommandBufferPushConstantsOp : |
| HAL_Op<"command_buffer.push_constants"> { |
| let summary = [{command buffer push constants operation}]; |
| let description = [{ |
| Pushes an inline set of constants that can be accessed by subsequent |
| dispatches using a compatible pipeline layout. |
| |
| Push constants are always 4-byte values and treated as opaque, meaning that |
| they may be bit-casted floats, bit-packed booleans, etc. |
| }]; |
| |
| let arguments = (ins |
| HAL_CommandBuffer:$command_buffer, |
| HAL_PipelineLayout:$pipeline_layout, |
| IndexAttr:$offset, |
| Variadic<I32>:$values |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $command_buffer `:` type($command_buffer) `>` |
| `layout` `(` $pipeline_layout `:` type($pipeline_layout) `)` |
| `offset` `(` $offset `)` |
| `values` `(` `[` $values `]` `)` |
| `:` type($values) |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_CommandBufferPushDescriptorSetOp : |
| HAL_Op<"command_buffer.push_descriptor_set", [ |
| SameVariadicOperandSize, |
| ]> { |
| let summary = [{command buffer descriptor set push binding operation}]; |
| let description = [{ |
| Pushes an inline-defined descriptor set to the command buffer. |
| The provided buffers may either be HAL buffers or indirect references into |
| the command buffer binding table. |
| }]; |
| |
| let arguments = (ins |
| HAL_CommandBuffer:$command_buffer, |
| HAL_PipelineLayout:$pipeline_layout, |
| Index:$set, |
| Variadic<Index>:$binding_ordinals, |
| Variadic<AnyTypeOf<[Index, HAL_BufferType]>>:$binding_buffers, |
| Variadic<HAL_DeviceSize>:$binding_offsets, |
| Variadic<HAL_DeviceSize>:$binding_lengths |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $command_buffer `:` type($command_buffer) `>` |
| `layout` `(` $pipeline_layout `:` type($pipeline_layout) `)` |
| `` `[` $set `]` |
| `bindings` `(` `[` |
| custom<DescriptorSetBindings>($binding_ordinals, |
| $binding_buffers, |
| type($binding_buffers), |
| $binding_offsets, |
| $binding_lengths) |
| `]` `)` |
| attr-dict-with-keyword |
| }]; |
| |
| let skipDefaultBuilders = 1; |
| let builders = [ |
| OpBuilder<(ins "Value":$commandBuffer, "Value":$pipelineLayout, |
| "int64_t":$set, "ArrayRef<DescriptorSetBindingValue>":$bindings)>, |
| OpBuilder<(ins "Value":$commandBuffer, "Value":$pipelineLayout, |
| "Value":$set, "ArrayRef<DescriptorSetBindingValue>":$bindings)>, |
| ]; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| def HAL_CommandBufferDispatchSymbolOp : HAL_Op<"command_buffer.dispatch.symbol"> { |
| let summary = [{command buffer dispatch recording operation, using symbolref}]; |
| let description = [{ |
| Dispatches an execution request, using a nested symbol reference to the entry point. |
| }]; |
| |
| let arguments = (ins |
| HAL_CommandBuffer:$command_buffer, |
| SymbolRefAttr:$entry_point, |
| HAL_Dim:$workgroup_x, |
| HAL_Dim:$workgroup_y, |
| HAL_Dim:$workgroup_z |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $command_buffer `:` type($command_buffer) `>` |
| `target` `(` $entry_point `)` |
| `workgroups` `(` `[` |
| $workgroup_x `,` |
| $workgroup_y `,` |
| $workgroup_z |
| `]` `)` |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_CommandBufferDispatchOp : HAL_Op<"command_buffer.dispatch"> { |
| let summary = [{command buffer dispatch recording operation}]; |
| let description = [{ |
| Dispatches an execution request. |
| }]; |
| |
| let arguments = (ins |
| HAL_CommandBuffer:$command_buffer, |
| HAL_Executable:$executable, |
| HAL_OrdinalAttr:$entry_point, |
| HAL_Dim:$workgroup_x, |
| HAL_Dim:$workgroup_y, |
| HAL_Dim:$workgroup_z |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $command_buffer `:` type($command_buffer) `>` |
| `target` `(` $executable `:` type($executable) `)` |
| `` `[` $entry_point `]` |
| `workgroups` `(` `[` |
| $workgroup_x `,` |
| $workgroup_y `,` |
| $workgroup_z |
| `]` `)` |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_CommandBufferDispatchIndirectSymbolOp : HAL_Op<"command_buffer.dispatch.indirect.symbol"> { |
| let summary = [{command buffer indirect dispatch recording operation, using symbolref}]; |
| let description = [{ |
| Dispatches an execution request with the dispatch parameters loaded from the |
| given buffer, using using a nested symbol reference to the entry point. |
| |
| ```mlir |
| hal.command_buffer.dispatch.indirect.symbol %cmd, @executable::@target::@entry, |
| workgroups = %buffer[%offset] |
| ``` |
| }]; |
| |
| let arguments = (ins |
| HAL_CommandBuffer:$command_buffer, |
| SymbolRefAttr:$entry_point, |
| HAL_BufferType:$workgroups_buffer, |
| HAL_DeviceSize:$workgroups_offset |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $command_buffer `:` type($command_buffer) `>` |
| `target` `(` $entry_point `)` |
| `workgroups` `(` $workgroups_buffer `:` type($workgroups_buffer) `)` |
| `` `[` $workgroups_offset `]` |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_CommandBufferDispatchIndirectOp : HAL_Op<"command_buffer.dispatch.indirect"> { |
| let summary = [{command buffer indirect dispatch recording operation}]; |
| let description = [{ |
| Dispatches an execution request with the dispatch parameters loaded from the |
| given buffer. |
| }]; |
| |
| let arguments = (ins |
| HAL_CommandBuffer:$command_buffer, |
| HAL_Executable:$executable, |
| HAL_OrdinalAttr:$entry_point, |
| HAL_BufferType:$workgroups_buffer, |
| HAL_DeviceSize:$workgroups_offset |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $command_buffer `:` type($command_buffer) `>` |
| `target` `(` $executable `:` type($executable) `)` |
| `` `[` $entry_point `]` |
| `workgroups` `(` $workgroups_buffer `:` type($workgroups_buffer) `)` |
| `` `[` $workgroups_offset `]` |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| } // OpGroupCommandBufferOps |
| |
| //===----------------------------------------------------------------------===// |
| // !hal.descriptor_set_layout / iree_hal_descriptor_set_layout_t |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupDescriptorSetLayoutOps : OpDocGroup { |
| let summary = "Descriptor set layout ops"; |
| let description = [{ |
| Ops for `!hal.descriptor_set_layout` / `iree_hal_descriptor_set_layout_t`. |
| }]; |
| } |
| |
| let opDocGroup = OpGroupDescriptorSetLayoutOps in { |
| |
| def HAL_DescriptorSetLayoutCreateOp : |
| HAL_PureOp<"descriptor_set_layout.create", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| ]> { |
| let summary = [{creates a descriptor set layout}]; |
| let description = [{ |
| Creates a descriptor set layout that defines the bindings used within a set. |
| The same descriptor set layout may be shared with many different executable |
| layouts and by doing so some runtime binding overhead when switching between |
| executables that use the same set layouts can be reduced. |
| }]; |
| |
| let arguments = (ins |
| HAL_Device:$device, |
| HAL_DescriptorSetLayoutFlagsAttr:$flags, |
| HAL_DescriptorSetLayoutBindingArrayAttr:$bindings |
| ); |
| let results = (outs |
| HAL_DescriptorSetLayout:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `device` `(` $device `:` type($device) `)` |
| `flags` `(` $flags `)` |
| `bindings` `(` $bindings `)` |
| `:` type($result) |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_DescriptorSetLayoutLookupOp : HAL_PureOp<"descriptor_set_layout.lookup", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| ]> { |
| let summary = [{descriptor set layout cache lookup pseudo-op}]; |
| let description = [{ |
| Used during conversion to provide a placeholder for a globally cached and |
| possibly lazy-initialized descriptor set layout. |
| }]; |
| |
| let arguments = (ins |
| HAL_Device:$device, |
| HAL_DescriptorSetLayoutFlagsAttr:$flags, |
| HAL_DescriptorSetLayoutBindingArrayAttr:$bindings |
| ); |
| let results = (outs |
| HAL_DescriptorSetLayout:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `device` `(` $device `:` type($device) `)` |
| `flags` `(` $flags `)` |
| `bindings` `(` $bindings `)` |
| `:` type($result) |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| } // OpGroupDescriptorSetLayoutOps |
| |
| //===----------------------------------------------------------------------===// |
| // !hal.device / iree_hal_device_t |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupDeviceOps : OpDocGroup { |
| let summary = "Device ops"; |
| let description = "Ops for `!hal.device` / `iree_hal_device_t`."; |
| } |
| |
| let opDocGroup = OpGroupDeviceOps in { |
| |
| def HAL_DeviceAllocatorOp : HAL_PureOp<"device.allocator", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| ]> { |
| let summary = [{device allocator accessor operation}]; |
| let description = [{ |
| Returns the allocator that can be used to allocate buffers compatible with |
| the device. |
| }]; |
| |
| let arguments = (ins |
| HAL_Device:$device |
| ); |
| let results = (outs |
| HAL_Allocator:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $device `:` type($device) `>` `:` type($result) attr-dict-with-keyword |
| }]; |
| |
| let skipDefaultBuilders = 1; |
| let builders = [ |
| OpBuilder<(ins "Value":$device), |
| [{ |
| $_state.addOperands({device}); |
| $_state.addTypes({AllocatorType::get($_builder.getContext())}); |
| }]>, |
| ]; |
| } |
| |
| def HAL_ReturnOp : HAL_Op<"return", [Terminator]> { |
| let summary = [{return from a hal.* region}]; |
| let description = [{ |
| Returns the given values from the region and back to the host code. |
| }]; |
| |
| let arguments = (ins |
| Variadic<AnyType>:$operands |
| ); |
| |
| let assemblyFormat = [{ |
| ($operands^ `:` type($operands))? attr-dict |
| }]; |
| |
| let builders = [ |
| OpBuilder<(ins), |
| [{ |
| build($_builder, $_state, std::nullopt); |
| }]>, |
| ]; |
| |
| let hasVerifier = 1; |
| } |
| |
| def HAL_DeviceQueryOp : |
| HAL_PureOp<"device.query", [HAL_DeviceQuery]> { |
| let summary = [{returns a runtime configuration parameter from the device}]; |
| let description = [{ |
| Queries a device configuration parameter with the given key. |
| Returns a status indicating whether the pair was recognized/available and if |
| it was the value converted to the specified type. Queries must return the |
| same value for the lifetime of the module though may vary from run to run. |
| |
| This is roughly equivalent to the `sysconf` linux syscall |
| (https://man7.org/linux/man-pages/man3/sysconf.3.html) in that the exact |
| set of keys available and their interpretation is target-dependent. If there |
| is a HAL match attribute (`#hal.device.match.*`) or op |
| (`hal.device.match.*`) prefer to use that in order to get compile-time |
| propagation when the target is specified and elide the runtime query and |
| get compile-time verification when a runtime query is required. |
| |
| Users of the op must check the `ok` result before using the value as what |
| set of keys is available may change over time. If in doubt: don't use this. |
| Each key used adds additional versioning and testing complexity as runtime |
| code path changes will explode combinatorially and should be treated with as |
| much care as a binary file format change. Keys should be prefixed with `ex.` |
| when experimental indicating that they are not expected to be present |
| forever; all non-experimental keys should be vetted. |
| |
| Well-known keys: |
| |
| * hal.executable.format :: {some format} |
| Returns 1 if the given format is supported by the device loader. |
| |
| * hal.device :: concurrency |
| The maximum concurrently executable submissions, mapping roughly to the |
| queue count. The actual concurrency available may be less than this based |
| on dynamic runtime parameters such as power/thermal modes, quota limits, |
| or user choice. |
| |
| * hal.dispatch :: concurrency |
| The maximum concurrently executable workgroups for a particular dispatch. |
| The actual concurrency available may be less depending on device state. |
| }]; |
| |
| let arguments = (ins |
| HAL_Device:$device, |
| StrAttr:$category, |
| StrAttr:$key, |
| OptionalAttr<TypedAttrInterface>:$default_value |
| ); |
| let results = (outs |
| I1:$ok, |
| AnyType:$value |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $device `:` type($device) `>` |
| `key` `(` $category `:` `` `:` $key `)` |
| `:` type($ok) `,` type($value) |
| (`=` $default_value^)? |
| attr-dict-with-keyword |
| }]; |
| |
| let hasVerifier = 1; |
| } |
| |
| def HAL_DeviceQueueAllocaOp : HAL_Op<"device.queue.alloca", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| DeclareOpInterfaceMethods<Util_SizeAwareOp>, |
| ]> { |
| let summary = [{allocates a queue-ordered transient buffer}]; |
| let description = [{ |
| Returns a queue-ordered transient buffer that will be available for use when |
| the signal fence is reached. The allocation will not be made until the |
| wait fence has been reached. |
| |
| The size of the buffer returned may be larger than the requested size if the |
| allocator has specific alignment requirements or minimum allocation sizes. |
| |
| The buffer handle will remain live so long as there are retainers but the |
| contents are undefined before the allocation signal fence has been signaled |
| and after the deallocation wait fence has been reached. |
| }]; |
| |
| let arguments = (ins |
| HAL_Device:$device, |
| HAL_DeviceQueueAffinity:$queue_affinity, |
| HAL_Fence:$wait_fence, |
| HAL_Fence:$signal_fence, |
| HAL_DeviceQueuePool:$pool, |
| HAL_MemoryTypeBitfieldAttr:$memory_types, |
| HAL_BufferUsageBitfieldAttr:$buffer_usage, |
| HAL_DeviceSize:$result_size |
| ); |
| let results = (outs |
| HAL_Buffer:$result |
| ); |
| |
| // TODO(benvanik): change type/usage to ref params. |
| let assemblyFormat = [{ |
| `<` $device `:` type($device) `>` |
| `affinity` `(` $queue_affinity `)` |
| `wait` `(` $wait_fence `)` |
| `signal` `(` $signal_fence `)` |
| `pool` `(` $pool `)` |
| `type` `(` $memory_types `)` |
| `usage` `(` $buffer_usage `)` |
| `:` custom<SizeAwareType>(type($result), $result_size) |
| attr-dict-with-keyword |
| }]; |
| |
| let hasVerifier = 1; |
| } |
| |
| def HAL_DeviceQueueDeallocaOp : HAL_Op<"device.queue.dealloca"> { |
| let summary = [{deallocates a queue-ordered transient buffer}]; |
| let description = [{ |
| Deallocates a queue-ordered transient buffer. |
| The deallocation will not be made until the wait fence has been reached and |
| once the storage is available for reuse the signal fence will be signaled. |
| |
| After deallocation the contents of the buffer may still be accessible but |
| will have undefined contents as other operations reuse the memory. |
| }]; |
| |
| let arguments = (ins |
| HAL_Device:$device, |
| HAL_DeviceQueueAffinity:$queue_affinity, |
| HAL_Fence:$wait_fence, |
| HAL_Fence:$signal_fence, |
| HAL_Buffer:$buffer |
| ); |
| let results = (outs); |
| |
| let assemblyFormat = [{ |
| `<` $device `:` type($device) `>` |
| `affinity` `(` $queue_affinity `)` |
| `wait` `(` $wait_fence `)` |
| `signal` `(` $signal_fence `)` |
| `buffer` `(` $buffer `:` type($buffer) `)` |
| attr-dict-with-keyword |
| }]; |
| |
| let hasVerifier = 1; |
| } |
| |
| def HAL_DeviceQueueReadOp : HAL_Op<"device.queue.read"> { |
| let summary = [{reads a segment from a file into a device buffer}]; |
| let description = [{ |
| Enqueues a file read operation that streams a segment of the source file |
| defined by the source offset and length into the target HAL buffer at the |
| specified target offset. The queue affinity should be set to where the |
| target buffer will be consumed. The source file must have read permission |
| and the target buffer must have transfer-target usage. Read failure will |
| result in propagated semaphore failure or device loss. |
| }]; |
| |
| let arguments = (ins |
| HAL_Device:$device, |
| HAL_DeviceQueueAffinity:$queue_affinity, |
| HAL_Fence:$wait_fence, |
| HAL_Fence:$signal_fence, |
| HAL_File:$source_file, |
| I64:$source_offset, |
| HAL_Buffer:$target_buffer, |
| HAL_DeviceSize:$target_offset, |
| HAL_DeviceSize:$length, |
| I32Attr:$flags |
| ); |
| let results = (outs); |
| |
| let assemblyFormat = [{ |
| `<` $device `:` type($device) `>` |
| `affinity` `(` $queue_affinity `)` |
| `wait` `(` $wait_fence `)` |
| `signal` `(` $signal_fence `)` |
| `source` `(` $source_file `:` type($source_file) `)` |
| `` `[` $source_offset `]` |
| `target` `(` $target_buffer `:` type($target_buffer) `)` |
| `` `[` $target_offset `]` |
| `length` `(` $length `)` |
| `flags` `(` $flags `)` |
| attr-dict-with-keyword |
| }]; |
| |
| let hasVerifier = 1; |
| } |
| |
| def HAL_DeviceQueueWriteOp : HAL_Op<"device.queue.write"> { |
| let summary = [{writes a segment from a device buffer into a file}]; |
| let description = [{ |
| Enqueues a file write operation that streams a segment of the source HAL |
| buffer defined by the source offset and length into the target file at the |
| specified target offset. The queue affinity should be set to where the |
| source buffer was produced. The source buffer must have transfer-source |
| usage and the target file must have write permission. Write failure will |
| result in propagated semaphore failure or device loss. |
| }]; |
| |
| let arguments = (ins |
| HAL_Device:$device, |
| HAL_DeviceQueueAffinity:$queue_affinity, |
| HAL_Fence:$wait_fence, |
| HAL_Fence:$signal_fence, |
| HAL_Buffer:$source_buffer, |
| HAL_DeviceSize:$source_offset, |
| HAL_File:$target_file, |
| I64:$target_offset, |
| HAL_DeviceSize:$length, |
| I32Attr:$flags |
| ); |
| let results = (outs); |
| |
| let assemblyFormat = [{ |
| `<` $device `:` type($device) `>` |
| `affinity` `(` $queue_affinity `)` |
| `wait` `(` $wait_fence `)` |
| `signal` `(` $signal_fence `)` |
| `source` `(` $source_buffer `:` type($source_buffer) `)` |
| `` `[` $source_offset `]` |
| `target` `(` $target_file `:` type($target_file) `)` |
| `` `[` $target_offset `]` |
| `length` `(` $length `)` |
| `flags` `(` $flags `)` |
| attr-dict-with-keyword |
| }]; |
| |
| let hasVerifier = 1; |
| } |
| |
| def HAL_DeviceQueueExecuteOp : HAL_Op<"device.queue.execute"> { |
| let summary = [{enqueues command buffer execution}]; |
| let description = [{ |
| Executes one or more command buffers on a device queue. |
| The command buffers are executed in order as if they were recorded as one. |
| No commands will execute until the wait fence has been reached and the |
| signal fence will be signaled when all commands have completed. |
| }]; |
| |
| let arguments = (ins |
| HAL_Device:$device, |
| HAL_DeviceQueueAffinity:$queue_affinity, |
| HAL_Fence:$wait_fence, |
| HAL_Fence:$signal_fence, |
| Variadic<HAL_CommandBuffer>:$command_buffers |
| ); |
| let results = (outs); |
| |
| let assemblyFormat = [{ |
| `<` $device `:` type($device) `>` |
| `affinity` `(` $queue_affinity `)` |
| `wait` `(` $wait_fence `)` |
| `signal` `(` $signal_fence `)` |
| (`commands` `(` `[` $command_buffers^ `]` `)`)? |
| attr-dict-with-keyword |
| }]; |
| |
| let extraClassDeclaration = [{ |
| // Returns true if the execution represents a barrier. |
| bool isBarrier() { return getCommandBuffers().empty(); } |
| }]; |
| |
| let hasCanonicalizer = 1; |
| let hasVerifier = 1; |
| } |
| |
| def HAL_DeviceQueueFlushOp : HAL_Op<"device.queue.flush"> { |
| let summary = [{flushes locally-pending submissions to the queue}]; |
| let description = [{ |
| Flushes any locally-pending submissions in the queue. |
| When submitting many queue operations this can be used to eagerly flush |
| earlier submissions while later ones are still being constructed. |
| This may be a no-op. |
| }]; |
| |
| let arguments = (ins |
| HAL_Device:$device, |
| HAL_DeviceQueueAffinity:$queue_affinity |
| ); |
| let results = (outs); |
| |
| let assemblyFormat = [{ |
| `<` $device `:` type($device) `>` |
| `affinity` `(` $queue_affinity `)` |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| } // OpGroupDeviceOps |
| |
| //===----------------------------------------------------------------------===// |
| // !hal.executable / iree_hal_executable_t |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupExecutableOps : OpDocGroup { |
| let summary = "Executable ops"; |
| let description = "Ops for `!hal.executable` / `iree_hal_executable_t`."; |
| } |
| |
| let opDocGroup = OpGroupExecutableOps in { |
| |
| def HAL_ExecutableSourceOp : HAL_Op<"executable.source", [ |
| IsolatedFromAbove, |
| SingleBlockImplicitTerminator<"IREE::HAL::ExecutableSourceEndOp">, |
| Symbol, |
| SymbolTable, |
| ]> { |
| let summary = [{generic source contents of an executable op}]; |
| let description = [{ |
| This is an unspecialized source representation of an executable |
| module without an assigned target. This is useful for hand-authoring |
| executables prior to device specification. |
| }]; |
| |
| let arguments = (ins |
| OptionalAttr<StrAttr>:$sym_visibility, |
| SymbolNameAttr:$sym_name, |
| OptionalAttr<HAL_ExecutableObjectsAttr>:$objects |
| ); |
| |
| let regions = (region |
| SizedRegion<1>:$body |
| ); |
| |
| let assemblyFormat = [{ |
| custom<SymbolVisibility>($sym_visibility) |
| $sym_name |
| attr-dict-with-keyword |
| `` |
| $body |
| }]; |
| |
| let extraClassDeclaration = [{ |
| Block& getBlock() { return getBody().front(); } |
| |
| IREE::HAL::ExecutableConditionOp getConditionOp() { |
| auto conditionOps = getBody().getOps<IREE::HAL::ExecutableConditionOp>(); |
| return !conditionOps.empty() ? *conditionOps.begin() : IREE::HAL::ExecutableConditionOp{}; |
| } |
| iterator_range<Region::op_iterator<IREE::HAL::ExecutableConstantBlockOp>> |
| getConstantBlockOps() { |
| return getBody().getOps<IREE::HAL::ExecutableConstantBlockOp>(); |
| } |
| iterator_range<Region::op_iterator<IREE::HAL::ExecutableExportOp>> |
| getExportOps() { |
| return getBody().getOps<IREE::HAL::ExecutableExportOp>(); |
| } |
| |
| bool isExternal() { |
| return getBlock().getOps<::mlir::ModuleOp>().empty(); |
| } |
| |
| ::mlir::ModuleOp getInnerModule() { |
| auto it = getBlock().getOps<::mlir::ModuleOp>(); |
| if (it.empty()) return {}; |
| return *it.begin(); |
| } |
| }]; |
| |
| let hasVerifier = 1; |
| } |
| |
| def HAL_ExecutableSourceEndOp : HAL_Op<"executable.source_end", [ |
| HasParent<"IREE::HAL::ExecutableSourceOp">, |
| Terminator, |
| ]> { |
| let summary = [{terminator pseudo-op for the executable source op}]; |
| let assemblyFormat = "attr-dict"; |
| } |
| |
| def HAL_ExecutableOp : HAL_Op<"executable", [ |
| IsolatedFromAbove, |
| SingleBlockImplicitTerminator<"IREE::HAL::ExecutableEndOp">, |
| Symbol, |
| SymbolTable, |
| ]> { |
| let summary = [{target-specific executable module}]; |
| let description = [{ |
| An executable module representing a target-specific compiled |
| kernel/shader/etc. |
| }]; |
| |
| let arguments = (ins |
| OptionalAttr<StrAttr>:$sym_visibility, |
| SymbolNameAttr:$sym_name |
| // TODO(benvanik): entry point types for verification. |
| ); |
| |
| let regions = (region SizedRegion<1>:$body); |
| |
| let assemblyFormat = [{ |
| custom<SymbolVisibility>($sym_visibility) |
| $sym_name |
| attr-dict-with-keyword |
| regions |
| }]; |
| |
| let skipDefaultBuilders = 1; |
| let builders = [ |
| OpBuilder<(ins "StringRef":$name)>, |
| ]; |
| |
| let extraClassDeclaration = [{ |
| Block& getBlock() { return getBody().front(); } |
| }]; |
| |
| let hasVerifier = 1; |
| } |
| |
| def HAL_ExecutableEndOp : HAL_Op<"executable_end", [ |
| HasParent<"IREE::HAL::ExecutableOp">, |
| Terminator, |
| ]> { |
| let summary = [{terminator pseudo-op for the executable op}]; |
| let assemblyFormat = "attr-dict"; |
| } |
| |
| def HAL_ExecutableExportOp : HAL_Op<"executable.export", [ |
| Symbol, |
| ParentOneOf<[ |
| "IREE::HAL::ExecutableSourceOp", |
| "IREE::HAL::ExecutableVariantOp", |
| ]>, |
| IsolatedFromAbove, |
| ]> { |
| let summary = [{executable entry point declaration}]; |
| let description = [{ |
| An entry point exported by the executable with statically-available |
| information describing the IO interface it uses and other dispatch metadata. |
| |
| The `workgroup_count` region represents the computation that |
| returns the number of workgroups to use in the 3D grid dispatch. |
| The arguments to the region represents the workload as captured by each |
| dispatch. It returns the number of workgroups along x, y, and z. |
| }]; |
| |
| let arguments = (ins |
| OptionalAttr<StrAttr>:$sym_visibility, |
| SymbolNameAttr:$sym_name, |
| OptionalAttr<HAL_OrdinalAttr>:$ordinal, |
| HAL_PipelineLayoutAttr:$layout, |
| OptionalAttr<HAL_WorkgroupSizeAttr>:$workgroup_size, |
| OptionalAttr<HAL_SubgroupSizeAttr>:$subgroup_size, |
| OptionalAttr<IndexAttr>:$workgroup_local_memory |
| ); |
| |
| let regions = (region AnyRegion:$workgroup_count); |
| |
| let builders = [ |
| OpBuilder<(ins |
| "::mlir::StringAttr":$sym_name, |
| "::mlir::IntegerAttr":$ordinal, |
| "IREE::HAL::PipelineLayoutAttr":$layout, |
| "::mlir::ArrayAttr":$workgroup_size, |
| "::mlir::IntegerAttr":$subgroup_size, |
| "::mlir::IntegerAttr":$workgroup_local_memory |
| ), [{ |
| build($_builder, $_state, nullptr, sym_name, ordinal, layout, |
| workgroup_size, subgroup_size, workgroup_local_memory); |
| }]>, |
| ]; |
| |
| let hasVerifier = 1; |
| |
| let extraClassDeclaration = [{ |
| /// For now assume that the workload is at max 3D. |
| /// Arguments to the region are workload along x, y and z. |
| // TODO: Propogate this and avoid magic constants. |
| static int64_t getNumWorkgroupDims() { return 3; } |
| |
| Block* getWorkgroupCountBody() { |
| if (getWorkgroupCount().empty()) return nullptr; |
| return &getWorkgroupCount().front(); |
| } |
| |
| // Calculates an XYZ workgroup count based on the given |workload|. |
| std::array<Value, 3> calculateWorkgroupCount( |
| Location loc, Value device, ValueRange workload, OpBuilder &builder); |
| |
| // Calculates an XYZ workgroup size based on the given |workload|. |
| std::array<Value, 3> calculateWorkgroupSize( |
| Location loc, Value device, ValueRange workload, OpBuilder &builder); |
| }]; |
| } |
| |
| def HAL_ExecutableVariantOp : HAL_Op<"executable.variant", [ |
| IsolatedFromAbove, |
| HasParent<"IREE::HAL::ExecutableOp">, |
| SingleBlockImplicitTerminator<"IREE::HAL::ExecutableVariantEndOp">, |
| Symbol, |
| SymbolTable, |
| ]> { |
| let summary = [{target-specific variant of an executable op}]; |
| let description = [{ |
| The target IR for the executable. This can be preserved for debugging but |
| is usually removed during transformation. |
| |
| Variants are selected based on their target and an optional condition |
| op that returns true if the variant is valid for use on the provided |
| runtime `!hal.device`. If no variants within an executable are valid then |
| loading will fail at runtime. If multiple variants are valid the first valid |
| one found will be loaded and used for execution. |
| }]; |
| |
| let arguments = (ins |
| OptionalAttr<StrAttr>:$sym_visibility, |
| SymbolNameAttr:$sym_name, |
| HAL_ExecutableTargetAttr:$target, |
| OptionalAttr<HAL_ExecutableObjectArrayAttr>:$objects |
| ); |
| |
| let regions = (region |
| SizedRegion<1>:$body |
| ); |
| |
| let assemblyFormat = [{ |
| custom<SymbolVisibility>($sym_visibility) |
| $sym_name |
| `target` `(` $target `)` |
| (`objects` `(` $objects^ `)` )? |
| attr-dict-with-keyword |
| $body |
| }]; |
| |
| let skipDefaultBuilders = 1; |
| let builders = [ |
| OpBuilder<(ins "StringRef":$name, "IREE::HAL::ExecutableTargetAttr":$target)>, |
| ]; |
| |
| let extraClassDeclaration = [{ |
| Block& getBlock() { return getBody().front(); } |
| |
| IREE::HAL::ExecutableConditionOp getConditionOp() { |
| auto conditionOps = getBody().getOps<IREE::HAL::ExecutableConditionOp>(); |
| return !conditionOps.empty() ? *conditionOps.begin() : IREE::HAL::ExecutableConditionOp{}; |
| } |
| iterator_range<Region::op_iterator<IREE::HAL::ExecutableConstantBlockOp>> |
| getConstantBlockOps() { |
| return getBody().getOps<IREE::HAL::ExecutableConstantBlockOp>(); |
| } |
| iterator_range<Region::op_iterator<IREE::HAL::ExecutableExportOp>> |
| getExportOps() { |
| return getBody().getOps<IREE::HAL::ExecutableExportOp>(); |
| } |
| |
| bool isExternal() { |
| return getBlock().getOps<::mlir::ModuleOp>().empty(); |
| } |
| |
| ::mlir::ModuleOp getInnerModule() { |
| auto it = getBlock().getOps<::mlir::ModuleOp>(); |
| if (it.empty()) return {}; |
| return *it.begin(); |
| } |
| |
| // Returns a map of constant key attributes to ordinals across all constant |
| // blocks inside the variant. |
| DenseMap<Attribute, int> gatherConstantOrdinals(); |
| |
| // Returns an i1 indicating whether this variant should be selected. |
| Value buildCondition(Value device, OpBuilder &builder); |
| }]; |
| |
| let hasCanonicalizer = 1; |
| let hasVerifier = 1; |
| } |
| |
| def HAL_ExecutableVariantEndOp : HAL_Op<"executable.variant_end", [ |
| HasParent<"IREE::HAL::ExecutableVariantOp">, |
| Terminator, |
| ]> { |
| let summary = [{terminator pseudo-op for the executable variant op}]; |
| let assemblyFormat = "attr-dict"; |
| } |
| |
| def HAL_ExecutableConditionOp : HAL_Op<"executable.condition", [ |
| IsolatedFromAbove, |
| FunctionOpInterface, |
| CallableOpInterface, |
| ]> { |
| let summary = [{host code to determine if the executable is enabled}]; |
| let description = [{ |
| Variants are selected based on their target and this optional condition |
| op that returns true if the variant is valid for use on the provided |
| runtime `!hal.device`. If no variants within an executable are valid then |
| loading will fail at runtime. If multiple variants are valid the first valid |
| one found will be loaded and used for execution. |
| }]; |
| |
| 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(); |
| |
| ArrayRef<Type> getArgumentTypes() { return getFunctionType().getInputs(); } |
| ArrayRef<Type> getResultTypes() { return getFunctionType().getResults(); } |
| |
| LogicalResult verifyType() { return success(); } |
| |
| Region *getCallableRegion() { return &getBody(); } |
| ArrayRef<Type> getCallableResults() { return getResultTypes(); } |
| |
| ::mlir::ArrayAttr getCallableArgAttrs() { return nullptr; } |
| ::mlir::ArrayAttr getCallableResAttrs() { return nullptr; } |
| |
| /// Make symbol optional as this op has no symbol. |
| bool isOptionalSymbol() { return true; } |
| }]; |
| |
| let hasVerifier = 1; |
| } |
| |
| def HAL_ExecutableConstantBlockOp : |
| HAL_Op<"executable.constant.block", [ |
| ParentOneOf<[ |
| "IREE::HAL::ExecutableSourceOp", |
| "IREE::HAL::ExecutableVariantOp", |
| ]>, |
| IsolatedFromAbove, |
| CallableOpInterface, |
| FunctionOpInterface, |
| ]> { |
| let summary = [{executable constant block initializer}]; |
| let description = [{ |
| Initializes one or more constants in the executable constant block by |
| returning one value per identified constant. Each constant block is |
| evaluated on the host prior to instantiating the executable for a given |
| device and allows for the executable to be specialized based on device |
| capabilities and limits. |
| |
| The keys specified are unique per variant and will be deduplicated across |
| multiple constant blocks when present. They are only used during lowering |
| and will not survive to runtime so they need only have descriptive enough |
| names to avoid collisions and represent the semantics of the value. |
| |
| Constant values can be loaded in the device code with the |
| `hal.executable.constant.load` op: |
| |
| ```mlir |
| hal.executable.variant public @target { |
| hal.executable.constant.block(%device: !hal.device) -> (i32, i32) as ("foo", "bar") { |
| %0 = hal.device.query<%device> key("some.device.prop")... |
| %1 = hal.device.query<%device> key("another.device.prop")... |
| hal.return %0, %1 : i32, i32 |
| } |
| builtin.module { |
| func @dispatch0() { |
| %0 = hal.executable.constant.load "foo" : i32 |
| %1 = hal.executable.constant.load "bar" : i32 |
| return |
| } |
| } |
| } |
| ``` |
| |
| Each target backend will implement the constant initialization and access in |
| a way compatible with its execution model. Examples: |
| - CPU: read-only buffer initialized on load and passed to each dispatch |
| - CUDA: read-only buffer initialized on load and passed to each dispatch |
| - SPIR-V: specialization constants |
| - Metal: function constants |
| - WebGPU: pipeline-overridable constants |
| }]; |
| |
| let arguments = (ins |
| TypeAttrOf<FunctionType>:$function_type, |
| ArrayAttr:$keys, |
| OptionalAttr<DictArrayAttr>:$arg_attrs, |
| OptionalAttr<DictArrayAttr>:$res_attrs |
| ); |
| |
| let regions = (region AnyRegion:$body); |
| |
| let builders = [ |
| OpBuilder<(ins |
| "FunctionType":$type, |
| CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs, |
| CArg<"ArrayRef<DictionaryAttr>", "{}">:$argAttrs)>, |
| ]; |
| |
| let extraClassDeclaration = [{ |
| // CallableOpInterface: |
| ::mlir::Region *getCallableRegion() { return &getBody(); } |
| ArrayRef<Type> getCallableResults() { return getFunctionType().getResults(); } |
| |
| /// 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); |
| } |
| |
| // FunctionOpInterface: |
| ArrayRef<Type> getArgumentTypes() { return getFunctionType().getInputs(); } |
| ArrayRef<Type> getResultTypes() { return getFunctionType().getResults(); } |
| |
| // Placeholder attribute added to ops that look up constants by key. |
| // Ordinal assignment passes use this to identify and update usage. |
| static StringRef getKeyAttrName() { return "hal.executable.constant.key"; } |
| |
| /// Make symbol optional as this op has no symbol. |
| bool isOptionalSymbol() { return true; } |
| }]; |
| |
| let hasCanonicalizer = 1; |
| let hasCustomAssemblyFormat = 1; |
| let hasVerifier = 1; |
| } |
| |
| def HAL_ExecutableConstantLoadOp : HAL_PureOp<"executable.constant.load"> { |
| let summary = [{loads a constant value from the executable constant block}]; |
| let description = [{ |
| Loads a scalar constant value from the static executable constant block. |
| The value provided by a constant block with the given key will be loaded and |
| bitcast (possibly with truncation or zero-extension) to the result type. |
| |
| Note that backends are allowed to implement their own mechanisms for |
| referencing constant block values and this is provided only as a default for |
| those not needing special behavior. |
| }]; |
| |
| let arguments = (ins |
| StrAttr:$key |
| ); |
| let results = (outs |
| HAL_PrimitiveType:$result |
| ); |
| |
| let assemblyFormat = [{ |
| $key attr-dict `:` type($result) |
| }]; |
| } |
| |
| def HAL_ExecutableBinaryOp : HAL_Op<"executable.binary", [ |
| HasParent<"IREE::HAL::ExecutableOp">, |
| Symbol, |
| ]> { |
| let summary = [{compiled executable binary data}]; |
| let description = [{ |
| A compiled executable binary with an optional nested module containing the |
| IR prior to serialization (for debugging). |
| }]; |
| |
| let arguments = (ins |
| OptionalAttr<StrAttr>:$sym_visibility, |
| SymbolNameAttr:$sym_name, |
| StrAttr:$format, |
| HAL_ExecutableDataAttr:$data, |
| OptionalAttr<StrAttr>:$mime_type |
| // TODO(benvanik): add compatibility and versioning attributes. |
| ); |
| |
| let assemblyFormat = [{ |
| custom<SymbolVisibility>($sym_visibility) |
| $sym_name |
| attr-dict-with-keyword |
| }]; |
| |
| let skipDefaultBuilders = 1; |
| let builders = [ |
| OpBuilder<(ins |
| "StringRef":$name, |
| "StringRef":$format, |
| "std::vector<uint8_t>":$data |
| )>, |
| OpBuilder<(ins |
| "StringRef":$name, |
| "StringAttr":$format, |
| "DenseIntElementsAttr":$data |
| )>, |
| ]; |
| } |
| |
| def HAL_ExecutableCreateOp : HAL_PureOp<"executable.create", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| AttrSizedOperandSegments, |
| ]> { |
| let summary = [{creates an executable}]; |
| let description = [{ |
| Creates a target-dependent executable cached on the provided device. Entry |
| points contained within the executable can be dispatched using the resulting |
| executable handle. |
| |
| Depending on the driver creation may take a non-trivial amount of time |
| (such as when JITing/etc). As the cache is internally synchronized callers |
| can issue preparation requests from multiple threads - even for the same |
| executables - and calls will block until preparation completes. |
| |
| Optional constants provide for specialization of the executable based on |
| runtime-derived parameters. |
| }]; |
| |
| let arguments = (ins |
| HAL_Device:$device, |
| SymbolRefAttr:$executable_target, |
| Variadic<HAL_PipelineLayout>:$layouts, |
| Variadic<I32>:$constants |
| ); |
| let results = (outs |
| HAL_Executable:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `device` `(` $device `:` type($device) `)` |
| `target` `(` $executable_target `)` |
| `layouts` `(` `[` $layouts `]` `)` |
| (`constants` `(` `[` $constants^ `]` `)`)? |
| `:` type($result) |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_ExecutableLookupOp : HAL_PureOp<"executable.lookup", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| ]> { |
| let summary = [{executable cache lookup pseudo-op}]; |
| let description = [{ |
| Used during conversion to provide a placeholder for a globally cached and |
| possibly lazy-initialized executable. |
| }]; |
| |
| let arguments = (ins |
| HAL_Device:$device, |
| FlatSymbolRefAttr:$executable |
| ); |
| let results = (outs |
| HAL_Executable:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `device` `(` $device `:` type($device) `)` |
| `executable` `(` $executable `)` |
| `:` type($result) |
| attr-dict-with-keyword |
| }]; |
| |
| let skipDefaultBuilders = 1; |
| let builders = [ |
| OpBuilder<(ins "Value":$device, "StringRef":$executable), |
| [{ |
| $_state.addOperands({device}); |
| $_state.addAttribute("executable", mlir::SymbolRefAttr::get($_builder.getContext(), executable)); |
| $_state.addTypes({ExecutableType::get($_builder.getContext())}); |
| }]>, |
| ]; |
| } |
| |
| def HAL_ExecutableCalculateWorkgroupsOp : HAL_PureOp<"executable.calculate_workgroups"> { |
| let summary = [{calculates workgroup count from workload for an exported function}]; |
| let description = [{ |
| Calculates the workgroup count (grid XYZ) based on the given workload using |
| the workgroup count calculation region of the target |
| `hal.executable.export` op. |
| }]; |
| |
| let arguments = (ins |
| HAL_Device:$device, |
| SymbolRefAttr:$entry_point, |
| Variadic<Index>:$workload |
| ); |
| let results = (outs |
| HAL_Dim:$workgroup_x, |
| HAL_Dim:$workgroup_y, |
| HAL_Dim:$workgroup_z |
| ); |
| |
| let assemblyFormat = [{ |
| `device` `(` $device `:` type($device) `)` |
| `target` `(` $entry_point `)` |
| (`workload` `(` `[` $workload^ `]` `)`)? |
| `:` type($workgroup_x) `,` type($workgroup_y) `,` type($workgroup_z) |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| } // OpGroupExecutableOps |
| |
| //===----------------------------------------------------------------------===// |
| // hal.instrument.* |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupInstrumentOps : OpDocGroup { |
| let summary = "Instrument ops"; |
| let description = "Ops for `!hal.instrument.*`."; |
| } |
| |
| let opDocGroup = OpGroupInstrumentOps in { |
| |
| def HAL_InstrumentWorkgroupOp : HAL_Op<"instrument.workgroup", []> { |
| let summary = [{emits a dispatch workgroup instrumentation event}]; |
| let description = [{ |
| Emits an `iree_instrument_dispatch_workgroup_t` event into the |
| instrumentation stream. The workgroup event identifies the unique dispatch, |
| its workgroup count, and the ID of the emitting workgroup within the |
| dispatch. Optionally targets that support querying the processor ID |
| executing the workgroup can attach that information for tracking purposes. |
| |
| On targets such as CPUs where entire workgroups execute as atomic units |
| only one workgroup event should be emitted. On targets such as GPUs where |
| there may be multiple invocations executing as part of a single workgroup |
| only the first invocation within the workgroup should emit the workgroup |
| event (by checking if the LocalInvocationIndex or threadIdx == 0, etc). |
| |
| The resulting workgroup key is used by subsequent workgroup-specific |
| instrumentation events. |
| }]; |
| |
| let arguments = (ins |
| AnyMemRef:$buffer, |
| I32:$dispatchId |
| ); |
| let results = (outs |
| Index:$workgroupKey |
| ); |
| |
| let assemblyFormat = [{ |
| `` `[` $buffer `:` type($buffer) `]` |
| `dispatch` `(` $dispatchId `)` |
| attr-dict `:` type($workgroupKey) |
| }]; |
| } |
| |
| def HAL_InstrumentPrintOp : HAL_Op<"instrument.print", []> { |
| let summary = [{emits a human-readable printf-style string event}]; |
| let description = [{ |
| Formats a string using a limited subset of printf format specifiers and the |
| provided values and then emits an `iree_instrument_dispatch_print_t` event. Final |
| formatted string lengths may be limited to as much as 1024 characters and |
| should be kept as small as possible to avoid easily exceeding the |
| instrumentation storage buffers with redundant strings. |
| }]; |
| |
| let arguments = (ins |
| AnyMemRef:$buffer, |
| Index:$workgroupKey, |
| StrAttr:$format, |
| Variadic<AnyType>:$values |
| ); |
| |
| let assemblyFormat = [{ |
| `` `[` $buffer `:` type($buffer) `for` $workgroupKey `]` |
| $format (`*` `(` $values^ `:` type($values) `)`)? |
| attr-dict |
| }]; |
| } |
| |
| def HAL_InstrumentValueOp : HAL_Op<"instrument.value", [ |
| AllTypesMatch<["operand", "result"]>, |
| ]> { |
| let summary = [{emits a scalar value instrumentation event}]; |
| let description = [{ |
| Emits a workgroup-specific typed value with the given workgroup-relative |
| ordinal. |
| |
| This op will be preserved even if the output is not used as it is only for |
| debugging purposes. |
| }]; |
| |
| let arguments = (ins |
| AnyMemRef:$buffer, |
| Index:$workgroupKey, |
| AnyI8Attr:$ordinal, |
| AnyType:$operand |
| ); |
| let results = (outs |
| AnyType:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `` `[` $buffer `:` type($buffer) `for` $workgroupKey `]` |
| $ordinal `=` $operand attr-dict `:` type($operand) |
| }]; |
| } |
| |
| def HAL_InstrumentMemoryLoadOp : HAL_PureOp<"instrument.memory.load", [ |
| AllTypesMatch<["loadValue", "result"]>, |
| ]> { |
| let summary = [{emits a memory load instrumentation event}]; |
| let description = [{ |
| Emits a workgroup-specific memory load event indicating that a number of |
| bytes from the given resolved pointer have been loaded by the workgroup. |
| }]; |
| |
| let arguments = (ins |
| AnyMemRef:$buffer, |
| Index:$workgroupKey, |
| AnyType:$loadValue, |
| AnyMemRef:$base, |
| Variadic<Index>:$indices |
| ); |
| let results = (outs |
| AnyType:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `` `[` $buffer `:` type($buffer) `for` $workgroupKey `]` |
| $base `[` $indices `]` `,` $loadValue |
| attr-dict `:` type($base) `,` type($result) |
| }]; |
| } |
| |
| def HAL_InstrumentMemoryStoreOp : HAL_PureOp<"instrument.memory.store", [ |
| AllTypesMatch<["storeValue", "result"]>, |
| ]> { |
| let summary = [{emits a memory store instrumentation event}]; |
| let description = [{ |
| Emits a workgroup-specific memory store event indicating that a number of |
| bytes have been stored to the given resolved pointer by the workgroup. |
| }]; |
| |
| let arguments = (ins |
| AnyMemRef:$buffer, |
| Index:$workgroupKey, |
| AnyType:$storeValue, |
| AnyMemRef:$base, |
| Variadic<Index>:$indices |
| ); |
| let results = (outs |
| AnyType:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `` `[` $buffer `:` type($buffer) `for` $workgroupKey `]` |
| $base `[` $indices `]` `,` $storeValue |
| attr-dict `:` type($base) `,` type($result) |
| }]; |
| } |
| |
| } // OpGroupInstrumentOps |
| |
| //===----------------------------------------------------------------------===// |
| // hal.interface |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupInterfaceOps : OpDocGroup { |
| let summary = "Interface ops"; |
| let description = "Ops for `!hal.interface.*`."; |
| } |
| |
| let opDocGroup = OpGroupInterfaceOps in { |
| |
| def HAL_InterfaceWorkgroupIDOp : HAL_PureOp<"interface.workgroup.id", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| ]> { |
| let summary = [{returns the index of the current workgroup in the grid}]; |
| let description = [{ |
| The global workgroup ID of the current tile in the range of |
| `[0, hal.interface.workgroup.count)` along each XYZ dimension. |
| |
| Corresponds to the `WorkgroupId` SPIR-V built-in and the `blockIdx` CUDA |
| built-in variable. |
| |
| ```mlir |
| %x = hal.interface.workgroup.id[0] : index |
| %y = hal.interface.workgroup.id[1] : index |
| %z = hal.interface.workgroup.id[2] : index |
| ``` |
| }]; |
| |
| let arguments = (ins IndexAttr:$dimension); |
| let results = (outs HAL_Dim:$result); |
| |
| let builders = [ |
| OpBuilder<(ins "unsigned":$dim), |
| [{ |
| build($_builder, $_state, $_builder.getIndexType(), $_builder.getIndexAttr(dim)); |
| }]>, |
| ]; |
| |
| let assemblyFormat = [{ |
| `[` $dimension `]` attr-dict `:` type($result) |
| }]; |
| } |
| |
| def HAL_InterfaceWorkgroupCountOp : HAL_PureOp<"interface.workgroup.count", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| ]> { |
| let summary = [{returns the total workgroup count of the grid}]; |
| let description = [{ |
| The total number of workgroups along each dimension in the dispatch grid. |
| Matches what was passed to the `hal.command_buffer.dispatch` command (or |
| what was indirectly specified). |
| |
| Corresponds to the `NumWorkgroups` SPIR-V built-in and the `gridDim` CUDA |
| built-in variable. |
| |
| ```mlir |
| %x = hal.interface.workgroup.count[0] : index |
| %y = hal.interface.workgroup.count[1] : index |
| %z = hal.interface.workgroup.count[2] : index |
| ``` |
| }]; |
| |
| let arguments = (ins IndexAttr:$dimension); |
| let results = (outs HAL_Dim:$result); |
| |
| let builders = [ |
| OpBuilder<(ins "unsigned":$dim), |
| [{ |
| build($_builder, $_state, $_builder.getIndexType(), $_builder.getIndexAttr(dim)); |
| }]>, |
| ]; |
| |
| let assemblyFormat = [{ |
| `[` $dimension `]` attr-dict `:` type($result) |
| }]; |
| } |
| |
| def HAL_InterfaceWorkgroupSizeOp : HAL_PureOp<"interface.workgroup.size", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| ]> { |
| let summary = [{returns the size of each workgroup in invocations}]; |
| let description = [{ |
| The number of local invocations within the current workgroup along each |
| dimension. Depending on backend this may map to the SIMT thread count or |
| inner loop nest parameters. |
| |
| Corresponds to the `WorkgroupSize` SPIR-V built-in and the `blockDim` CUDA |
| built-in variable. |
| |
| ```mlir |
| %x = hal.interface.workgroup.size[0] : index |
| %y = hal.interface.workgroup.size[1] : index |
| %z = hal.interface.workgroup.size[2] : index |
| ``` |
| }]; |
| |
| let arguments = (ins IndexAttr:$dimension); |
| let results = (outs HAL_Dim:$result); |
| |
| let builders = [ |
| OpBuilder<(ins "unsigned":$dim), |
| [{ |
| build($_builder, $_state, $_builder.getIndexType(), $_builder.getIndexAttr(dim)); |
| }]>, |
| ]; |
| |
| let assemblyFormat = [{ |
| `[` $dimension `]` attr-dict `:` type($result) |
| }]; |
| } |
| |
| def HAL_InterfaceConstantLoadOp : HAL_PureOp<"interface.constant.load"> { |
| let summary = [{loads a constant value from the interface constant block}]; |
| let description = [{ |
| Loads a scalar constant value from an executable IO push constant block. |
| The value will be loaded from the given constant offset and will be |
| bitcast (possibly with truncation or zero-extension) to the result type. |
| |
| An optional alignment indicates the byte alignment of potential values for |
| the constant when it could be determined from analysis. If omitted the value |
| may be anything and its interpretation is up to the usage. This is intended |
| to provide pointer alignment-like semantics to constants that are used to |
| index into binding resources. |
| |
| An optional set of values indicates all possible values that can be passed |
| to the constant from all dispatch sites in the program. If omitted the value |
| may be from an unanalyzable source (outside of the program, indirect, etc) |
| and must be assumed to have any value. |
| }]; |
| |
| let arguments = (ins |
| HAL_HostSizeAttr:$index, |
| OptionalAttr<IndexAttr>:$alignment, |
| OptionalAttr<ArrayAttr>:$values |
| ); |
| let results = (outs |
| HAL_PrimitiveType:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `` `[` $index `]` |
| (`alignment` `(` $alignment^ `)`)? |
| (`values` `(` $values^ `)`)? |
| attr-dict `:` type($result) |
| }]; |
| } |
| |
| def HAL_InterfaceBindingSubspanOp : HAL_PureOp<"interface.binding.subspan", [ |
| AttrSizedOperandSegments, |
| Util_ShapeAwareOp, |
| ]> { |
| let summary = [{returns an alias to a subspan of interface binding data}]; |
| let description = [{ |
| Returns a subspan of an interface binding storage buffer in a generic type. |
| The exact shape, type, and alignment of the returned type are defined by |
| the result type (tensor, memref, etc). |
| |
| An optional alignment indicates the byte alignment of the base binding |
| resource. Note that the byte offset is added to the base and the alignment |
| will be the minimum of the two. |
| }]; |
| |
| let arguments = (ins |
| IndexAttr:$set, |
| IndexAttr:$binding, |
| HAL_DescriptorTypeAttr:$descriptor_type, |
| Optional<HAL_DeviceSize>:$byte_offset, |
| HAL_ShapeDynamicDims:$dynamic_dims, |
| OptionalAttr<IndexAttr>:$alignment, |
| OptionalAttr<HAL_DescriptorFlagsAttr>:$descriptor_flags |
| ); |
| let results = (outs |
| AnyType:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `set` `(` $set `)` |
| `binding` `(` $binding `)` |
| `type` `(` custom<DescriptorType>($descriptor_type) `)` |
| (`alignment` `(` $alignment^ `)`)? |
| (`offset` `(` $byte_offset^ `)`)? |
| (`flags` `(` $descriptor_flags^ `)`)? |
| attr-dict `:` type($result) (`{` $dynamic_dims^ `}`)? |
| }]; |
| |
| let builders = [ |
| OpBuilder<(ins |
| "Type":$resultType, |
| "APInt":$set, |
| "APInt":$binding, |
| "IREE::HAL::DescriptorType":$descriptor_type, |
| "Value":$byte_offset, |
| "ValueRange":$dynamic_dims, |
| "IntegerAttr":$alignment, |
| CArg<"std::optional<DescriptorFlags>", "std::nullopt">:$flags |
| )>, |
| ]; |
| |
| let hasVerifier = 1; |
| |
| let extraClassDeclaration = [{ |
| ValueRange getOperandDynamicDims(unsigned idx) { return ValueRange{}; } |
| ValueRange getResultDynamicDims(unsigned idx) { return getDynamicDims(); } |
| |
| // Returns the alignment of the base buffer pointer (before offset). |
| llvm::MaybeAlign getBaseAlignment(); |
| |
| // Attempts to calculate an alignment of the final subspan offset in the |
| // parent storage buffer. This is a combination of both the binding |
| // alignment and the byte offset and may be as small as the natural |
| // alignment of the element type being accessed. |
| llvm::Align calculateAlignment(); |
| }]; |
| } |
| |
| } // OpGroupInterfaceOps |
| |
| //===----------------------------------------------------------------------===// |
| // !hal.pipeline_layout / iree_hal_pipeline_layout_t |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupPipelineLayoutOps : OpDocGroup { |
| let summary = "Pipeline layout ops"; |
| let description = [{ |
| Ops for `!hal.pipeline_layout` / `iree_hal_pipeline_layout_t`. |
| }]; |
| } |
| |
| let opDocGroup = OpGroupPipelineLayoutOps in { |
| |
| def HAL_PipelineLayoutCreateOp : HAL_PureOp<"pipeline_layout.create", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| ]> { |
| let summary = [{creates an pipeline layout}]; |
| let description = [{ |
| Creates an pipeline layout from the given descriptor sets and push |
| constant required size. Pipeline layouts can be shared across any |
| executable that uses the same layout and push constant information. Sharing |
| the layout between executables will reduce runtime binding overhead and it |
| is often worth the cost to allow a small number of unused bindings in one |
| executable such that it can share layouts with others that will be scheduled |
| adjacent to it. |
| }]; |
| |
| let arguments = (ins |
| HAL_Device:$device, |
| IndexAttr:$push_constants, |
| Variadic<HAL_DescriptorSetLayout>:$set_layouts |
| ); |
| let results = (outs |
| HAL_PipelineLayout:$result |
| ); |
| |
| // TODO(benvanik): include descriptor set layout types. |
| let assemblyFormat = [{ |
| `device` `(` $device `:` type($device) `)` |
| `push_constants` `(` $push_constants `)` |
| `layouts` `(` `[` $set_layouts `]` `)` |
| `:` type($result) |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_PipelineLayoutLookupOp : HAL_PureOp<"pipeline_layout.lookup", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| ]> { |
| let summary = [{pipeline layout cache lookup pseudo-op}]; |
| let description = [{ |
| Used during conversion to provide a placeholder for a globally cached and |
| possibly lazy-initialized pipeline layout. |
| }]; |
| |
| let arguments = (ins |
| HAL_Device:$device, |
| HAL_PipelineLayoutAttr:$layout |
| ); |
| let results = (outs |
| HAL_PipelineLayout:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `device` `(` $device `:` type($device) `)` |
| `layout` `(` $layout `)` |
| `:` type($result) |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| } // OpGroupPipelineLayoutOps |
| |
| //===----------------------------------------------------------------------===// |
| // !hal.fence / iree_hal_fence_t |
| //===----------------------------------------------------------------------===// |
| |
| def OpGroupFenceOps : OpDocGroup { |
| let summary = "Fence ops"; |
| let description = "Ops for `!hal.fence` / `iree_hal_fence_t`."; |
| } |
| |
| let opDocGroup = OpGroupFenceOps in { |
| |
| def HAL_FenceCreateOp : HAL_Op<"fence.create", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| MemoryEffects<[MemAlloc]>, |
| ]> { |
| let summary = [{creates an unsignaled fence}]; |
| let description = [{ |
| Returns a fence that defines a point in time. By default fences will remain |
| unsignaled unless they are explicitly signaled with `hal.fence.signal` or |
| asynchronously signaled by the device by passing them as an operand to |
| queue submission ops. |
| }]; |
| |
| let arguments = (ins |
| HAL_Device:$device, |
| HAL_FenceFlagBitfieldAttr:$flags |
| ); |
| let results = (outs |
| HAL_Fence:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `device` `(` $device `:` type($device) `)` |
| `flags` `(` $flags `)` |
| `:` type($result) |
| attr-dict-with-keyword |
| }]; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| def HAL_FenceJoinOp : HAL_Op<"fence.join", [ |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| ]> { |
| let summary = [{creates a fence from the given timepoints}]; |
| let description = [{ |
| Returns a fence that joins the input fences as a wait-all operation. |
| }]; |
| |
| let arguments = (ins |
| Variadic<HAL_Fence>:$fences |
| ); |
| let results = (outs |
| HAL_Fence:$result |
| ); |
| |
| let assemblyFormat = [{ |
| `at` `(` `[` $fences `]` `)` |
| `->` type($result) |
| attr-dict-with-keyword |
| }]; |
| |
| let hasFolder = 1; |
| let hasCanonicalizer = 1; |
| } |
| |
| def HAL_FenceQueryOp : HAL_Op<"fence.query"> { |
| let summary = [{fence query operation}]; |
| let description = [{ |
| Queries whether the fence has been reached and its status. |
| Returns OK if the fence has been signaled successfully, DEFERRED if it is |
| unsignaled, and otherwise an error indicating the failure. |
| }]; |
| |
| let arguments = (ins |
| HAL_Fence:$fence |
| ); |
| let results = (outs |
| Util_Status:$status |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $fence `:` type($fence) `>` |
| `:` type($status) |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_FenceSignalOp : HAL_Op<"fence.signal"> { |
| let summary = [{fence signal operation}]; |
| let description = [{ |
| Signals the fence to indicate that the timepoints contained have been |
| reached. Waiting work may begin immediately. |
| }]; |
| |
| let arguments = (ins |
| HAL_Fence:$fence |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $fence `:` type($fence) `>` |
| attr-dict-with-keyword |
| }]; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| def HAL_FenceFailOp : HAL_Op<"fence.fail"> { |
| let summary = [{fence failure operation}]; |
| let description = [{ |
| Signals the fence with a failure. The `status` will be returned from |
| each timepoint semaphores `hal.semaphore.query` and `hal.semaphore.signal` |
| for the lifetime of each semaphore. |
| }]; |
| |
| let arguments = (ins |
| HAL_Fence:$fence, |
| Util_Status:$status |
| ); |
| |
| let assemblyFormat = [{ |
| `<` $fence `:` type($fence) `>` |
| `status` `(` $status `)` |
| attr-dict-with-keyword |
| }]; |
| } |
| |
| def HAL_FenceAwaitOp : HAL_Op<"fence.await", [ |
| Util_YieldPoint, |
| DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>, |
| ]> { |
| let summary = [{asynchronous fence wait operation}]; |
| let description = [{ |
| Yields the caller until all fences is reached. Returns the `status` of the |
| fence after the wait, with a non-zero value indicating failure. |
| }]; |
| |
| let arguments = (ins |
| I32:$timeout_millis, |
| Variadic<HAL_Fence>:$fences |
| ); |
| let results = (outs |
| Util_Status:$status |
| ); |
| |
| let assemblyFormat = [{ |
| `until` `(` `[` $fences `]` `)` |
| `timeout_millis` `(` $timeout_millis `)` |
| `:` type($status) |
| attr-dict-with-keyword |
| }]; |
| |
| let hasCanonicalizer = 1; |
| } |
| |
| } // OpGroupFenceOps |
| |
| #endif // IREE_DIALECT_HAL_OPS |