blob: 63cd3fe0213da889f0b833ce19a0233c567e0aef [file] [log] [blame]
//===-- SPIRVMemoryOps.td - MLIR SPIR-V Memory Ops ---------*- tablegen -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file contains memory ops for the SPIR-V dialect. It corresponds
// to "3.32.8. Memory Instructions" of the SPIR-V spec.
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_DIALECT_SPIRV_IR_MEMORY_OPS
#define MLIR_DIALECT_SPIRV_IR_MEMORY_OPS
include "mlir/Dialect/SPIRV/IR/SPIRVBase.td"
// -----
def SPV_AccessChainOp : SPV_Op<"AccessChain", [NoSideEffect]> {
let summary = [{
Create a pointer into a composite object that can be used with OpLoad
and OpStore.
}];
let description = [{
Result Type must be an OpTypePointer. Its Type operand must be the type
reached by walking the Base’s type hierarchy down to the last provided
index in Indexes, and its Storage Class operand must be the same as the
Storage Class of Base.
Base must be a pointer, pointing to the base of a composite object.
Indexes walk the type hierarchy to the desired depth, potentially down
to scalar granularity. The first index in Indexes will select the top-
level member/element/component/element of the base composite. All
composite constituents use zero-based numbering, as described by their
OpType… instruction. The second index will apply similarly to that
result, and so on. Once any non-composite type is reached, there must be
no remaining (unused) indexes.
Each index in Indexes
- must be a scalar integer type,
- is treated as a signed count, and
- must be an OpConstant when indexing into a structure.
<!-- End of AutoGen section -->
```
access-chain-op ::= ssa-id `=` `spv.AccessChain` ssa-use
`[` ssa-use (',' ssa-use)* `]`
`:` pointer-type
```
#### Example:
```mlir
%0 = "spv.Constant"() { value = 1: i32} : () -> i32
%1 = spv.Variable : !spv.ptr<!spv.struct<f32, !spv.array<4xf32>>, Function>
%2 = spv.AccessChain %1[%0] : !spv.ptr<!spv.struct<f32, !spv.array<4xf32>>, Function>
%3 = spv.Load "Function" %2 ["Volatile"] : !spv.array<4xf32>
```
}];
let arguments = (ins
SPV_AnyPtr:$base_ptr,
Variadic<SPV_Integer>:$indices
);
let results = (outs
SPV_AnyPtr:$component_ptr
);
let builders = [OpBuilder<(ins "Value":$basePtr, "ValueRange":$indices)>];
let hasCanonicalizer = 1;
}
// -----
def SPV_CopyMemoryOp : SPV_Op<"CopyMemory", []> {
let summary = [{
Copy from the memory pointed to by Source to the memory pointed to by
Target. Both operands must be non-void pointers and having the same <id>
Type operand in their OpTypePointer type declaration. Matching Storage
Class is not required. The amount of memory copied is the size of the
type pointed to. The copied type must have a fixed size; i.e., it must
not be, nor include, any OpTypeRuntimeArray types.
}];
let description = [{
If present, any Memory Operands must begin with a memory operand
literal. If not present, it is the same as specifying the memory operand
None. Before version 1.4, at most one memory operands mask can be
provided. Starting with version 1.4 two masks can be provided, as
described in Memory Operands. If no masks or only one mask is present,
it applies to both Source and Target. If two masks are present, the
first applies to Target and cannot include MakePointerVisible, and the
second applies to Source and cannot include MakePointerAvailable.
<!-- End of AutoGen section -->
```
copy-memory-op ::= `spv.CopyMemory ` storage-class ssa-use
storage-class ssa-use
(`[` memory-access `]` (`, [` memory-access `]`)?)?
` : ` spirv-element-type
```
#### Example:
```mlir
%0 = spv.Variable : !spv.ptr<f32, Function>
%1 = spv.Variable : !spv.ptr<f32, Function>
spv.CopyMemory "Function" %0, "Function" %1 : f32
```
}];
let arguments = (ins
SPV_AnyPtr:$target,
SPV_AnyPtr:$source,
OptionalAttr<SPV_MemoryAccessAttr>:$memory_access,
OptionalAttr<I32Attr>:$alignment,
OptionalAttr<SPV_MemoryAccessAttr>:$source_memory_access,
OptionalAttr<I32Attr>:$source_alignment
);
let results = (outs);
let verifier = [{ return verifyCopyMemory(*this); }];
let autogenSerialization = 0;
}
// -----
def SPV_InBoundsPtrAccessChainOp : SPV_Op<"InBoundsPtrAccessChain", [NoSideEffect]> {
let summary = [{
Has the same semantics as OpPtrAccessChain, with the addition that the
resulting pointer is known to point within the base object.
}];
let description = [{
<!-- End of AutoGen section -->
```
access-chain-op ::= ssa-id `=` `spv.InBoundsPtrAccessChain` ssa-use
`[` ssa-use (',' ssa-use)* `]`
`:` pointer-type
```mlir
#### Example:
```
func @inbounds_ptr_access_chain(%arg0: !spv.ptr<f32, CrossWorkgroup>, %arg1 : i64) -> () {
%0 = spv.InBoundsPtrAccessChain %arg0[%arg1] : !spv.ptr<f32, CrossWorkgroup>, i64
...
}
```
}];
let availability = [
MinVersion<SPV_V_1_0>,
MaxVersion<SPV_V_1_5>,
Extension<[]>,
Capability<[SPV_C_Addresses]>
];
let arguments = (ins
SPV_AnyPtr:$base_ptr,
SPV_Integer:$element,
Variadic<SPV_Integer>:$indices
);
let results = (outs
SPV_AnyPtr:$result
);
let builders = [OpBuilder<(ins "Value":$basePtr, "Value":$element, "ValueRange":$indices)>];
}
// -----
def SPV_LoadOp : SPV_Op<"Load", []> {
let summary = "Load through a pointer.";
let description = [{
Result Type is the type of the loaded object. It must be a type with
fixed size; i.e., it cannot be, nor include, any OpTypeRuntimeArray
types.
Pointer is the pointer to load through. Its type must be an
OpTypePointer whose Type operand is the same as Result Type.
If present, any Memory Operands must begin with a memory operand
literal. If not present, it is the same as specifying the memory operand
None.
<!-- End of AutoGen section -->
```
memory-access ::= `"None"` | `"Volatile"` | `"Aligned", ` integer-literal
| `"NonTemporal"`
load-op ::= ssa-id ` = spv.Load ` storage-class ssa-use
(`[` memory-access `]`)? ` : ` spirv-element-type
```
#### Example:
```mlir
%0 = spv.Variable : !spv.ptr<f32, Function>
%1 = spv.Load "Function" %0 : f32
%2 = spv.Load "Function" %0 ["Volatile"] : f32
%3 = spv.Load "Function" %0 ["Aligned", 4] : f32
```
}];
let arguments = (ins
SPV_AnyPtr:$ptr,
OptionalAttr<SPV_MemoryAccessAttr>:$memory_access,
OptionalAttr<I32Attr>:$alignment
);
let results = (outs
SPV_Type:$value
);
let builders = [
OpBuilder<(ins "Value":$basePtr,
CArg<"MemoryAccessAttr", "{}">:$memory_access,
CArg<"IntegerAttr", "{}">:$alignment)>
];
}
// -----
def SPV_PtrAccessChainOp : SPV_Op<"PtrAccessChain", [NoSideEffect]> {
let summary = [{
Has the same semantics as OpAccessChain, with the addition of the
Element operand.
}];
let description = [{
Element is used to do an initial dereference of Base: Base is treated as
the address of an element in an array, and a new element address is
computed from Base and Element to become the OpAccessChain Base to
dereference as per OpAccessChain. This computed Base has the same type
as the originating Base.
To compute the new element address, Element is treated as a signed count
of elements E, relative to the original Base element B, and the address
of element B + E is computed using enough precision to avoid overflow
and underflow. For objects in the Uniform, StorageBuffer, or
PushConstant storage classes, the element's address or location is
calculated using a stride, which will be the Base-type's Array Stride if
the Base type is decorated with ArrayStride. For all other objects, the
implementation calculates the element's address or location.
With one exception, undefined behavior results when B + E is not an
element in the same array (same innermost array, if array types are
nested) as B. The exception being when B + E = L, where L is the length
of the array: the address computation for element L is done with the
same stride as any other B + E computation that stays within the array.
Note: If Base is typed to be a pointer to an array and the desired
operation is to select an element of that array, OpAccessChain should be
directly used, as its first Index selects the array element.
<!-- End of AutoGen section -->
```
[access-chain-op ::= ssa-id `=` `spv.PtrAccessChain` ssa-use
`[` ssa-use (',' ssa-use)* `]`
`:` pointer-type
```mlir
#### Example:
```
func @ptr_access_chain(%arg0: !spv.ptr<f32, CrossWorkgroup>, %arg1 : i64) -> () {
%0 = spv.PtrAccessChain %arg0[%arg1] : !spv.ptr<f32, CrossWorkgroup>, i64
...
}
```
}];
let availability = [
MinVersion<SPV_V_1_0>,
MaxVersion<SPV_V_1_5>,
Extension<[]>,
Capability<[SPV_C_Addresses, SPV_C_PhysicalStorageBufferAddresses, SPV_C_VariablePointers, SPV_C_VariablePointersStorageBuffer]>
];
let arguments = (ins
SPV_AnyPtr:$base_ptr,
SPV_Integer:$element,
Variadic<SPV_Integer>:$indices
);
let results = (outs
SPV_AnyPtr:$result
);
let builders = [OpBuilder<(ins "Value":$basePtr, "Value":$element, "ValueRange":$indices)>];
}
// -----
def SPV_StoreOp : SPV_Op<"Store", []> {
let summary = "Store through a pointer.";
let description = [{
Pointer is the pointer to store through. Its type must be an
OpTypePointer whose Type operand is the same as the type of Object.
Object is the object to store.
If present, any Memory Operands must begin with a memory operand
literal. If not present, it is the same as specifying the memory operand
None.
<!-- End of AutoGen section -->
```
store-op ::= `spv.Store ` storage-class ssa-use `, ` ssa-use `, `
(`[` memory-access `]`)? `:` spirv-element-type
```
#### Example:
```mlir
%0 = spv.Variable : !spv.ptr<f32, Function>
%1 = spv.FMul ... : f32
spv.Store "Function" %0, %1 : f32
spv.Store "Function" %0, %1 ["Volatile"] : f32
spv.Store "Function" %0, %1 ["Aligned", 4] : f32
```
}];
let arguments = (ins
SPV_AnyPtr:$ptr,
SPV_Type:$value,
OptionalAttr<SPV_MemoryAccessAttr>:$memory_access,
OptionalAttr<I32Attr>:$alignment
);
let results = (outs);
let builders = [
OpBuilder<(ins "Value":$ptr, "Value":$value,
CArg<"ArrayRef<NamedAttribute>", "{}">:$namedAttrs),
[{
$_state.addOperands(ptr);
$_state.addOperands(value);
$_state.addAttributes(namedAttrs);
}]>
];
}
// -----
def SPV_VariableOp : SPV_Op<"Variable", []> {
let summary = [{
Allocate an object in memory, resulting in a pointer to it, which can be
used with OpLoad and OpStore.
}];
let description = [{
Result Type must be an OpTypePointer. Its Type operand is the type of
object in memory.
Storage Class is the Storage Class of the memory holding the object.
Since the op is used to model function-level variables, the storage class
must be the `Function` Storage Class.
Initializer is optional. If Initializer is present, it will be the
initial value of the variable’s memory content. Initializer must be an
<id> from a constant instruction or a global (module scope) OpVariable
instruction. Initializer must have the same type as the type pointed to
by Result Type.
<!-- End of AutoGen section -->
```
variable-op ::= ssa-id `=` `spv.Variable` (`init(` ssa-use `)`)?
attribute-dict? `:` spirv-pointer-type
```
where `init` specifies initializer.
#### Example:
```mlir
%0 = spv.Constant ...
%1 = spv.Variable : !spv.ptr<f32, Function>
%2 = spv.Variable init(%0): !spv.ptr<f32, Function>
```
}];
let arguments = (ins
SPV_StorageClassAttr:$storage_class,
Optional<AnyType>:$initializer
);
let results = (outs
SPV_AnyPtr:$pointer
);
}
#endif // MLIR_DIALECT_SPIRV_IR_MEMORY_OPS