//===-- 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 SPIRV_AccessChainOp : SPIRV_Op<"AccessChain", [Pure]> {
let summary = "Create a pointer into a composite object.";
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 `=` `spirv.AccessChain` ssa-use
`[` ssa-use (',' ssa-use)* `]`
`:` pointer-type
```
#### Example:
```mlir
%0 = "spirv.Constant"() { value = 1: i32} : () -> i32
%1 = spirv.Variable : !spirv.ptr<!spirv.struct<f32, !spirv.array<4xf32>>, Function>
%2 = spirv.AccessChain %1[%0] : !spirv.ptr<!spirv.struct<f32, !spirv.array<4xf32>>, Function>
%3 = spirv.Load "Function" %2 ["Volatile"] : !spirv.array<4xf32>
```
}];
let arguments = (ins
SPIRV_AnyPtr:$base_ptr,
Variadic<SPIRV_Integer>:$indices
);
let results = (outs
SPIRV_AnyPtr:$component_ptr
);
let builders = [OpBuilder<(ins "Value":$basePtr, "ValueRange":$indices)>];
let hasCanonicalizer = 1;
}
// -----
def SPIRV_CopyMemoryOp : SPIRV_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 ::= `spirv.CopyMemory ` storage-class ssa-use
storage-class ssa-use
(`[` memory-access `]` (`, [` memory-access `]`)?)?
` : ` spirv-element-type
```
#### Example:
```mlir
%0 = spirv.Variable : !spirv.ptr<f32, Function>
%1 = spirv.Variable : !spirv.ptr<f32, Function>
spirv.CopyMemory "Function" %0, "Function" %1 : f32
```
}];
let arguments = (ins
SPIRV_AnyPtr:$target,
SPIRV_AnyPtr:$source,
OptionalAttr<SPIRV_MemoryAccessAttr>:$memory_access,
OptionalAttr<I32Attr>:$alignment,
OptionalAttr<SPIRV_MemoryAccessAttr>:$source_memory_access,
OptionalAttr<I32Attr>:$source_alignment
);
let results = (outs);
let autogenSerialization = 0;
}
// -----
def SPIRV_InBoundsPtrAccessChainOp : SPIRV_Op<"InBoundsPtrAccessChain", [Pure]> {
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 `=` `spirv.InBoundsPtrAccessChain` ssa-use
`[` ssa-use (',' ssa-use)* `]`
`:` pointer-type
```
#### Example:
```mlir
func @inbounds_ptr_access_chain(%arg0: !spirv.ptr<f32, CrossWorkgroup>, %arg1 : i64) -> () {
%0 = spirv.InBoundsPtrAccessChain %arg0[%arg1] : !spirv.ptr<f32, CrossWorkgroup>, i64
...
}
```
}];
let availability = [
MinVersion<SPIRV_V_1_0>,
MaxVersion<SPIRV_V_1_6>,
Extension<[]>,
Capability<[SPIRV_C_Addresses]>
];
let arguments = (ins
SPIRV_AnyPtr:$base_ptr,
SPIRV_Integer:$element,
Variadic<SPIRV_Integer>:$indices
);
let results = (outs
SPIRV_AnyPtr:$result
);
let builders = [OpBuilder<(ins "Value":$basePtr, "Value":$element, "ValueRange":$indices)>];
}
// -----
def SPIRV_LoadOp : SPIRV_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 ` = spirv.Load ` storage-class ssa-use
(`[` memory-access `]`)? ` : ` spirv-element-type
```
#### Example:
```mlir
%0 = spirv.Variable : !spirv.ptr<f32, Function>
%1 = spirv.Load "Function" %0 : f32
%2 = spirv.Load "Function" %0 ["Volatile"] : f32
%3 = spirv.Load "Function" %0 ["Aligned", 4] : f32
```
}];
let arguments = (ins
SPIRV_AnyPtr:$ptr,
OptionalAttr<SPIRV_MemoryAccessAttr>:$memory_access,
OptionalAttr<I32Attr>:$alignment
);
let results = (outs
SPIRV_Type:$value
);
let builders = [
OpBuilder<(ins "Value":$basePtr,
CArg<"MemoryAccessAttr", "{}">:$memory_access,
CArg<"IntegerAttr", "{}">:$alignment)>
];
}
// -----
def SPIRV_PtrAccessChainOp : SPIRV_Op<"PtrAccessChain", [Pure]> {
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 `=` `spirv.PtrAccessChain` ssa-use
`[` ssa-use (',' ssa-use)* `]`
`:` pointer-type
```
#### Example:
```mlir
func @ptr_access_chain(%arg0: !spirv.ptr<f32, CrossWorkgroup>, %arg1 : i64) -> () {
%0 = spirv.PtrAccessChain %arg0[%arg1] : !spirv.ptr<f32, CrossWorkgroup>, i64
...
}
```
}];
let availability = [
MinVersion<SPIRV_V_1_0>,
MaxVersion<SPIRV_V_1_6>,
Extension<[]>,
Capability<[
SPIRV_C_Addresses, SPIRV_C_PhysicalStorageBufferAddresses,
SPIRV_C_VariablePointers, SPIRV_C_VariablePointersStorageBuffer]>
];
let arguments = (ins
SPIRV_AnyPtr:$base_ptr,
SPIRV_Integer:$element,
Variadic<SPIRV_Integer>:$indices
);
let results = (outs
SPIRV_AnyPtr:$result
);
let builders = [OpBuilder<(ins "Value":$basePtr, "Value":$element, "ValueRange":$indices)>];
}
// -----
def SPIRV_StoreOp : SPIRV_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 ::= `spirv.Store ` storage-class ssa-use `, ` ssa-use `, `
(`[` memory-access `]`)? `:` spirv-element-type
```
#### Example:
```mlir
%0 = spirv.Variable : !spirv.ptr<f32, Function>
%1 = spirv.FMul ... : f32
spirv.Store "Function" %0, %1 : f32
spirv.Store "Function" %0, %1 ["Volatile"] : f32
spirv.Store "Function" %0, %1 ["Aligned", 4] : f32
```
}];
let arguments = (ins
SPIRV_AnyPtr:$ptr,
SPIRV_Type:$value,
OptionalAttr<SPIRV_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 SPIRV_VariableOp : SPIRV_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.
From `SPV_KHR_physical_storage_buffer`:
If an OpVariable's pointee type is a pointer (or array of pointers) in
PhysicalStorageBuffer storage class, then the variable must be decorated
with exactly one of AliasedPointer or RestrictPointer.
<!-- End of AutoGen section -->
```
variable-op ::= ssa-id `=` `spirv.Variable` (`init(` ssa-use `)`)?
attribute-dict? `:` spirv-pointer-type
```
where `init` specifies initializer.
#### Example:
```mlir
%0 = spirv.Constant ...
%1 = spirv.Variable : !spirv.ptr<f32, Function>
%2 = spirv.Variable init(%0): !spirv.ptr<f32, Function>
%3 = spirv.Variable {aliased_pointer} :
!spirv.ptr<!spirv.ptr<f32, PhysicalStorageBuffer>, Function>
```
}];
let arguments = (ins
SPIRV_StorageClassAttr:$storage_class,
Optional<AnyType>:$initializer
);
let results = (outs
SPIRV_AnyPtr:$pointer
);
let extraClassDeclaration = [{
::mlir::spirv::PointerType getPointerType() {
return ::llvm::cast<::mlir::spirv::PointerType>(getType());
}
::mlir::Type getPointeeType() {
return getPointerType().getPointeeType();
}
}];
}
#endif // MLIR_DIALECT_SPIRV_IR_MEMORY_OPS