blob: a057bf784693e82a4ac9d62d510f518ab508d577 [file] [log] [blame]
//===-- SPIRVStructureOps.td - MLIR SPIR-V Structure 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 ops for defining the SPIR-V structure: module, function,
// and module-level operations. The representational form of these ops deviate
// from the SPIR-V binary format in order to utilize MLIR mechanisms.
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_DIALECT_SPIRV_IR_STRUCTURE_OPS
#define MLIR_DIALECT_SPIRV_IR_STRUCTURE_OPS
include "mlir/Dialect/SPIRV/IR/SPIRVBase.td"
include "mlir/IR/OpAsmInterface.td"
include "mlir/IR/SymbolInterfaces.td"
include "mlir/Interfaces/CallInterfaces.td"
include "mlir/Interfaces/SideEffectInterfaces.td"
// -----
def SPV_AddressOfOp : SPV_Op<"mlir.addressof",
[DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>,
InFunctionScope, NoSideEffect]> {
let summary = "Get the address of a global variable.";
let description = [{
Variables in module scope are defined using symbol names. This op generates
an SSA value that can be used to refer to the symbol within function scope
for use in ops that expect an SSA value. This operation has no corresponding
SPIR-V instruction; it's merely used for modelling purpose in the SPIR-V
dialect. Since variables in module scope in SPIR-V dialect are of pointer
type, this op returns a pointer type as well, and the type is the same as
the variable referenced.
<!-- End of AutoGen section -->
```
spv-address-of-op ::= ssa-id `=` `spv.mlir.addressof` symbol-ref-id
`:` spirv-pointer-type
```
#### Example:
```mlir
%0 = spv.mlir.addressof @global_var : !spv.ptr<f32, Input>
```
}];
let arguments = (ins
FlatSymbolRefAttr:$variable
);
let results = (outs
SPV_AnyPtr:$pointer
);
let hasOpcode = 0;
let autogenSerialization = 0;
let builders = [OpBuilder<(ins "spirv::GlobalVariableOp":$var)>];
let assemblyFormat = "$variable attr-dict `:` type($pointer)";
}
// -----
def SPV_ConstantOp : SPV_Op<"Constant",
[ConstantLike,
DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmResultNames"]>,
NoSideEffect]> {
let summary = "The op that declares a SPIR-V normal constant";
let description = [{
This op declares a SPIR-V normal constant. SPIR-V has multiple constant
instructions covering different constant types:
* `OpConstantTrue` and `OpConstantFalse` for boolean constants
* `OpConstant` for scalar constants
* `OpConstantComposite` for composite constants
* `OpConstantNull` for null constants
* ...
Having such a plethora of constant instructions renders IR transformations
more tedious. Therefore, we use a single `spv.Constant` op to represent
them all. Note that conversion between those SPIR-V constant instructions
and this op is purely mechanical; so it can be scoped to the binary
(de)serialization process.
<!-- End of AutoGen section -->
```
spv.Constant-op ::= ssa-id `=` `spv.Constant` attribute-value
(`:` spirv-type)?
```
#### Example:
```mlir
%0 = spv.Constant true
%1 = spv.Constant dense<[2, 3]> : vector<2xf32>
%2 = spv.Constant [dense<3.0> : vector<2xf32>] : !spv.array<1xvector<2xf32>>
```
TODO: support constant structs
}];
let arguments = (ins
AnyAttr:$value
);
let results = (outs
SPV_Type:$constant
);
let hasFolder = 1;
let extraClassDeclaration = [{
// Returns true if a constant can be built for the given `type`.
static bool isBuildableWith(Type type);
// Creates a constant zero/one of the given `type` at the current insertion
// point of `builder` and returns it.
static spirv::ConstantOp getZero(Type type, Location loc,
OpBuilder &builder);
static spirv::ConstantOp getOne(Type type, Location loc,
OpBuilder &builder);
}];
let hasOpcode = 0;
let autogenSerialization = 0;
}
// -----
def SPV_EntryPointOp : SPV_Op<"EntryPoint", [InModuleScope]> {
let summary = [{
Declare an entry point, its execution model, and its interface.
}];
let description = [{
Execution Model is the execution model for the entry point and its
static call tree. See Execution Model.
Entry Point must be the Result <id> of an OpFunction instruction.
Name is a name string for the entry point. A module cannot have two
OpEntryPoint instructions with the same Execution Model and the same
Name string.
Interface is a list of symbol references to `spv.GlobalVariable`
operations. These declare the set of global variables from a
module that form the interface of this entry point. The set of
Interface symbols must be equal to or a superset of the
`spv.GlobalVariable`s referenced by the entry point’s static call
tree, within the interface’s storage classes. Before version 1.4,
the interface’s storage classes are limited to the Input and
Output storage classes. Starting with version 1.4, the interface’s
storage classes are all storage classes used in declaring all
global variables referenced by the entry point’s call tree.
<!-- End of AutoGen section -->
```
execution-model ::= "Vertex" | "TesellationControl" |
<and other SPIR-V execution models...>
entry-point-op ::= ssa-id `=` `spv.EntryPoint` execution-model
symbol-reference (`, ` symbol-reference)*
```
#### Example:
```mlir
spv.EntryPoint "GLCompute" @foo
spv.EntryPoint "Kernel" @foo, @var1, @var2
```
}];
let arguments = (ins
SPV_ExecutionModelAttr:$execution_model,
FlatSymbolRefAttr:$fn,
SymbolRefArrayAttr:$interface
);
let results = (outs);
let autogenSerialization = 0;
let builders = [
OpBuilder<(ins "spirv::ExecutionModel":$executionModel,
"spirv::FuncOp":$function, "ArrayRef<Attribute>":$interfaceVars)>];
}
// -----
def SPV_ExecutionModeOp : SPV_Op<"ExecutionMode", [InModuleScope]> {
let summary = "Declare an execution mode for an entry point.";
let description = [{
Entry Point must be the Entry Point <id> operand of an OpEntryPoint
instruction.
Mode is the execution mode. See Execution Mode.
This instruction is only valid when the Mode operand is an execution
mode that takes no Extra Operands, or takes Extra Operands that are not
<id> operands.
<!-- End of AutoGen section -->
```
execution-mode ::= "Invocations" | "SpacingEqual" |
<and other SPIR-V execution modes...>
execution-mode-op ::= `spv.ExecutionMode ` ssa-use execution-mode
(integer-literal (`, ` integer-literal)* )?
```
#### Example:
```mlir
spv.ExecutionMode @foo "ContractionOff"
spv.ExecutionMode @bar "LocalSizeHint", 3, 4, 5
```
}];
let arguments = (ins
FlatSymbolRefAttr:$fn,
SPV_ExecutionModeAttr:$execution_mode,
I32ArrayAttr:$values
);
let results = (outs);
let verifier = [{ return success(); }];
let autogenSerialization = 0;
let builders = [
OpBuilder<(ins "spirv::FuncOp":$function,
"spirv::ExecutionMode":$executionMode, "ArrayRef<int32_t>":$params)>];
}
// -----
def SPV_FuncOp : SPV_Op<"func", [
AutomaticAllocationScope, DeclareOpInterfaceMethods<CallableOpInterface>,
FunctionLike, InModuleScope, IsolatedFromAbove, Symbol
]> {
let summary = "Declare or define a function";
let description = [{
This op declares or defines a SPIR-V function using one region, which
contains one or more blocks.
Different from the SPIR-V binary format, this op is not allowed to
implicitly capture global values, and all external references must use
function arguments or symbol references. This op itself defines a symbol
that is unique in the enclosing module op.
This op itself takes no operands and generates no results. Its region
can take zero or more arguments and return zero or one values.
<!-- End of AutoGen section -->
```
spv-function-control ::= "None" | "Inline" | "DontInline" | ...
spv-function-op ::= `spv.func` function-signature
spv-function-control region
```
#### Example:
```mlir
spv.func @foo() -> () "None" { ... }
spv.func @bar() -> () "Inline|Pure" { ... }
```
}];
let arguments = (ins
TypeAttr:$type,
StrAttr:$sym_name,
SPV_FunctionControlAttr:$function_control
);
let results = (outs);
let regions = (region AnyRegion:$body);
let verifier = [{ return success(); }];
let builders = [
OpBuilder<(ins "StringRef":$name, "FunctionType":$type,
CArg<"spirv::FunctionControl", "spirv::FunctionControl::None">:$control,
CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs)>];
let hasOpcode = 0;
let autogenSerialization = 0;
let extraClassDeclaration = [{
private:
// This trait needs access to the hooks defined below.
friend class OpTrait::FunctionLike<FuncOp>;
/// Returns the number of arguments. Hook for OpTrait::FunctionLike.
unsigned getNumFuncArguments() { return getType().getNumInputs(); }
/// Returns the number of results. Hook for OpTrait::FunctionLike.
unsigned getNumFuncResults() { return getType().getNumResults(); }
/// Hook for OpTrait::FunctionLike, called after verifying that the 'type'
/// attribute is present and checks if it holds a function type. Ensures
/// getType, getNumFuncArguments, and getNumFuncResults can be called safely
LogicalResult verifyType();
/// Hook for OpTrait::FunctionLike, called after verifying the function
/// type and the presence of the (potentially empty) function body.
/// Ensures SPIR-V specific semantics.
LogicalResult verifyBody();
}];
}
// -----
def SPV_GlobalVariableOp : SPV_Op<"GlobalVariable", [InModuleScope, Symbol]> {
let summary = [{
Allocate an object in memory at module scope. The object is
referenced using a symbol name.
}];
let description = [{
The variable 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. It
cannot be Generic. It must be the same as the Storage Class operand of
the variable types. Only those storage classes that are valid at module
scope (like Input, Output, StorageBuffer, etc.) are valid.
Initializer is optional. If Initializer is present, it will be
the initial value of the variable’s memory content. Initializer
must be an symbol defined from a constant instruction or other
`spv.GlobalVariable` operation in module scope. Initializer must
have the same type as the type of the defined symbol.
<!-- End of AutoGen section -->
```
variable-op ::= `spv.GlobalVariable` spirv-type symbol-ref-id
(`initializer(` symbol-ref-id `)`)?
(`bind(` integer-literal, integer-literal `)`)?
(`built_in(` string-literal `)`)?
attribute-dict?
```
where `initializer` specifies initializer and `bind` specifies the
descriptor set and binding number. `built_in` specifies SPIR-V
BuiltIn decoration associated with the op.
#### Example:
```mlir
spv.GlobalVariable @var0 : !spv.ptr<f32, Input> @var0
spv.GlobalVariable @var1 initializer(@var0) : !spv.ptr<f32, Output>
spv.GlobalVariable @var2 bind(1, 2) : !spv.ptr<f32, Uniform>
spv.GlobalVariable @var3 built_in("GlobalInvocationId") : !spv.ptr<vector<3xi32>, Input>
```
}];
let arguments = (ins
TypeAttr:$type,
StrAttr:$sym_name,
OptionalAttr<FlatSymbolRefAttr>:$initializer,
OptionalAttr<I32Attr>:$location,
OptionalAttr<I32Attr>:$binding,
OptionalAttr<I32Attr>:$descriptorSet,
OptionalAttr<StrAttr>:$builtin
);
let results = (outs);
let builders = [
OpBuilder<(ins "TypeAttr":$type,
"StringAttr":$sym_name,
CArg<"FlatSymbolRefAttr", "nullptr">:$initializer),
[{
$_state.addAttribute("type", type);
$_state.addAttribute(sym_nameAttrName($_state.name), sym_name);
if (initializer)
$_state.addAttribute(initializerAttrName($_state.name), initializer);
}]>,
OpBuilder<(ins "TypeAttr":$type, "ArrayRef<NamedAttribute>":$namedAttrs),
[{
$_state.addAttribute("type", type);
$_state.addAttributes(namedAttrs);
}]>,
OpBuilder<(ins "Type":$type, "StringRef":$name,
"unsigned":$descriptorSet, "unsigned":$binding)>,
OpBuilder<(ins "Type":$type, "StringRef":$name,
"spirv::BuiltIn":$builtin)>,
OpBuilder<(ins "Type":$type,
"StringRef":$sym_name,
CArg<"FlatSymbolRefAttr", "{}">:$initializer),
[{
$_state.addAttribute("type", TypeAttr::get(type));
$_state.addAttribute(sym_nameAttrName($_state.name), $_builder.getStringAttr(sym_name));
if (initializer)
$_state.addAttribute(initializerAttrName($_state.name), initializer);
}]>
];
let hasOpcode = 0;
let autogenSerialization = 0;
let extraClassDeclaration = [{
::mlir::spirv::StorageClass storageClass() {
return this->type().cast<::mlir::spirv::PointerType>().getStorageClass();
}
}];
}
// -----
def SPV_ModuleOp : SPV_Op<"module",
[IsolatedFromAbove, NoRegionArguments, NoTerminator,
SingleBlock, SymbolTable, Symbol]> {
let summary = "The top-level op that defines a SPIR-V module";
let description = [{
This op defines a SPIR-V module using a MLIR region. The region contains
one block. Module-level operations, including functions definitions,
are all placed in this block.
Using an op with a region to define a SPIR-V module enables "embedding"
SPIR-V modules in other dialects in a clean manner: this op guarantees
the validity and serializability of a SPIR-V module and thus serves as
a clear-cut boundary.
This op takes no operands and generates no results. This op should not
implicitly capture values from the enclosing environment.
This op has only one region, which only contains one block. The block
has no terminator.
<!-- End of AutoGen section -->
```
addressing-model ::= `Logical` | `Physical32` | `Physical64` | ...
memory-model ::= `Simple` | `GLSL450` | `OpenCL` | `Vulkan` | ...
spv-module-op ::= `spv.module` addressing-model memory-model
(requires spirv-vce-attribute)?
(`attributes` attribute-dict)?
region
```
#### Example:
```mlir
spv.module Logical GLSL450 {}
spv.module Logical Vulkan
requires #spv.vce<v1.0, [Shader], [SPV_KHR_vulkan_memory_model]>
attributes { some_additional_attr = ... } {
spv.func @do_nothing() -> () {
spv.Return
}
}
```
}];
let arguments = (ins
SPV_AddressingModelAttr:$addressing_model,
SPV_MemoryModelAttr:$memory_model,
OptionalAttr<SPV_VerCapExtAttr>:$vce_triple,
OptionalAttr<StrAttr>:$sym_name
);
let results = (outs);
let regions = (region AnyRegion);
let builders = [
OpBuilder<(ins CArg<"Optional<StringRef>", "llvm::None">:$name)>,
OpBuilder<(ins "spirv::AddressingModel":$addressing_model,
"spirv::MemoryModel":$memory_model,
CArg<"Optional<spirv::VerCapExtAttr>", "llvm::None">:$vce_triple,
CArg<"Optional<StringRef>", "llvm::None">:$name)>
];
// We need to ensure the block inside the region is properly terminated;
// the auto-generated builders do not guarantee that.
let skipDefaultBuilders = 1;
let hasOpcode = 0;
let autogenSerialization = 0;
let extraClassDeclaration = [{
bool isOptionalSymbol() { return true; }
Optional<StringRef> getName() { return sym_name(); }
static StringRef getVCETripleAttrName() { return "vce_triple"; }
}];
}
// -----
def SPV_ReferenceOfOp : SPV_Op<"mlir.referenceof", [NoSideEffect]> {
let summary = "Reference a specialization constant.";
let description = [{
Specialization constants in module scope are defined using symbol names.
This op generates an SSA value that can be used to refer to the symbol
within function scope for use in ops that expect an SSA value.
This operation has no corresponding SPIR-V instruction; it's merely used
for modelling purpose in the SPIR-V dialect. This op's return type is
the same as the specialization constant.
<!-- End of AutoGen section -->
```
spv-reference-of-op ::= ssa-id `=` `spv.mlir.referenceof` symbol-ref-id
`:` spirv-scalar-type
```
#### Example:
```mlir
%0 = spv.mlir.referenceof @spec_const : f32
```
TODO Add support for composite specialization constants.
}];
let arguments = (ins
FlatSymbolRefAttr:$spec_const
);
let results = (outs
SPV_Type:$reference
);
let hasOpcode = 0;
let autogenSerialization = 0;
let assemblyFormat = "$spec_const attr-dict `:` type($reference)";
}
// -----
def SPV_SpecConstantOp : SPV_Op<"SpecConstant", [InModuleScope, Symbol]> {
let summary = "The op that declares a SPIR-V specialization constant";
let description = [{
This op declares a SPIR-V scalar specialization constant. SPIR-V has
multiple constant instructions covering different scalar types:
* `OpSpecConstantTrue` and `OpSpecConstantFalse` for boolean constants
* `OpSpecConstant` for scalar constants
Similar as `spv.Constant`, this op represents all of the above cases.
`OpSpecConstantComposite` and `OpSpecConstantOp` are modelled with
separate ops.
<!-- End of AutoGen section -->
```
spv-spec-constant-op ::= `spv.SpecConstant` symbol-ref-id
`spec_id(` integer `)`
`=` attribute-value (`:` spirv-type)?
```
where `spec_id` specifies the SPIR-V SpecId decoration associated with
the op.
#### Example:
```mlir
spv.SpecConstant @spec_const1 = true
spv.SpecConstant @spec_const2 spec_id(5) = 42 : i32
```
}];
let arguments = (ins
StrAttr:$sym_name,
AnyAttr:$default_value
);
let results = (outs);
let hasOpcode = 0;
let autogenSerialization = 0;
}
// -----
def SPV_SpecConstantCompositeOp : SPV_Op<"SpecConstantComposite", [
InModuleScope, Symbol]> {
let summary = "Declare a new composite specialization constant.";
let description = [{
This op declares a SPIR-V composite specialization constant. This covers
the `OpSpecConstantComposite` SPIR-V instruction. Scalar constants are
covered by `spv.SpecConstant`.
A constituent of a spec constant composite can be:
- A symbol referring of another spec constant.
- The SSA ID of a non-specialization constant (i.e. defined through
`spv.SpecConstant`).
- The SSA ID of a `spv.Undef`.
```
spv-spec-constant-composite-op ::= `spv.SpecConstantComposite` symbol-ref-id ` (`
symbol-ref-id (`, ` symbol-ref-id)*
`) :` composite-type
```
where `composite-type` is some non-scalar type that can be represented in the `spv`
dialect: `spv.struct`, `spv.array`, or `vector`.
#### Example:
```mlir
spv.SpecConstant @sc1 = 1 : i32
spv.SpecConstant @sc2 = 2.5 : f32
spv.SpecConstant @sc3 = 3.5 : f32
spv.SpecConstantComposite @scc (@sc1, @sc2, @sc3) : !spv.struct<i32, f32, f32>
```
TODO Add support for constituents that are:
- regular constants.
- undef.
- spec constant composite.
}];
let arguments = (ins
TypeAttr:$type,
StrAttr:$sym_name,
SymbolRefArrayAttr:$constituents
);
let results = (outs);
let hasOpcode = 0;
let autogenSerialization = 0;
}
// -----
def SPV_SpecConstantOperationOp : SPV_Op<"SpecConstantOperation", [
NoSideEffect, InFunctionScope,
SingleBlockImplicitTerminator<"YieldOp">]> {
let summary = [{
Declare a new specialization constant that results from doing an operation.
}];
let description = [{
This op declares a SPIR-V specialization constant that results from
doing an operation on other constants (specialization or otherwise).
In the `spv` dialect, this op is modelled as follows:
```
spv-spec-constant-operation-op ::= `spv.SpecConstantOperation` `wraps`
generic-spirv-op `:` function-type
```
In particular, an `spv.SpecConstantOperation` contains exactly one
region. In turn, that region, contains exactly 2 instructions:
- One of SPIR-V's instructions that are allowed within an
OpSpecConstantOp.
- An `spv.mlir.yield` instruction as the terminator.
The following SPIR-V instructions are valid:
- OpSConvert,
- OpUConvert,
- OpFConvert,
- OpSNegate,
- OpNot,
- OpIAdd,
- OpISub,
- OpIMul,
- OpUDiv,
- OpSDiv,
- OpUMod,
- OpSRem,
- OpSMod
- OpShiftRightLogical,
- OpShiftRightArithmetic,
- OpShiftLeftLogical
- OpBitwiseOr,
- OpBitwiseXor,
- OpBitwiseAnd
- OpVectorShuffle,
- OpCompositeExtract,
- OpCompositeInsert
- OpLogicalOr,
- OpLogicalAnd,
- OpLogicalNot,
- OpLogicalEqual,
- OpLogicalNotEqual
- OpSelect
- OpIEqual,
- OpINotEqual
- OpULessThan,
- OpSLessThan
- OpUGreaterThan,
- OpSGreaterThan
- OpULessThanEqual,
- OpSLessThanEqual
- OpUGreaterThanEqual,
- OpSGreaterThanEqual
TODO Add capability-specific ops when supported.
#### Example:
```mlir
%0 = spv.Constant 1: i32
%1 = spv.Constant 1: i32
%2 = spv.SpecConstantOperation wraps "spv.IAdd"(%0, %1) : (i32, i32) -> i32
```
}];
let arguments = (ins);
let results = (outs AnyType:$result);
let regions = (region SizedRegion<1>:$body);
let hasOpcode = 0;
let autogenSerialization = 0;
}
// -----
def SPV_YieldOp : SPV_Op<"mlir.yield", [
HasParent<"SpecConstantOperationOp">, NoSideEffect, Terminator]> {
let summary = [{
Yields the result computed in `spv.SpecConstantOperation`'s
region back to the parent op.
}];
let description = [{
This op is a special terminator whose only purpose is to terminate
an `spv.SpecConstantOperation`'s enclosed region. It accepts a
single operand produced by the preceeding (and only other) instruction
in its parent block (see SPV_SpecConstantOperation for further
details). This op has no corresponding SPIR-V instruction.
```
spv.mlir.yield ::= `spv.mlir.yield` ssa-id : spirv-type
```
#### Example:
```mlir
%0 = ... (some op supported by SPIR-V OpSpecConstantOp)
spv.mlir.yield %0
```
}];
let arguments = (ins AnyType:$operand);
let results = (outs);
let hasOpcode = 0;
let autogenSerialization = 0;
let assemblyFormat = "attr-dict $operand `:` type($operand)";
let verifier = [{ return success(); }];
}
// -----
#endif // MLIR_DIALECT_SPIRV_IR_STRUCTURE_OPS