diff --git a/mlir/include/mlir/Dialect/SPIRV/SPIRVStructureOps.td b/mlir/include/mlir/Dialect/SPIRV/SPIRVStructureOps.td --- a/mlir/include/mlir/Dialect/SPIRV/SPIRVStructureOps.td +++ b/mlir/include/mlir/Dialect/SPIRV/SPIRVStructureOps.td @@ -607,6 +607,130 @@ let autogenSerialization = 0; } + +def SPV_YieldOp : SPV_Op<"mlir.yield", [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)"; +} + +def SPV_SpecConstantOperationOp : SPV_Op<"SpecConstantOperation", [ + InFunctionScope, NoSideEffect + ]> { + 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"` + `(`ssa-id (`, ` ssa-id)`)` + `({` + ssa-id = spirv-op + `spv.mlir.yield` ssa-id + `})` `:` 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.SpecConstantOperation"(%0) ({ + %ret = spv.IAdd %0, %0 : i32 + spv.mlir.yield %ret : i32 + }) : (i32) -> i32 + ``` + }]; + + let arguments = (ins Variadic:$operands); + + let results = (outs Variadic:$results); + + let regions = (region SizedRegion<1>:$body); + + let hasOpcode = 0; + + let autogenSerialization = 0; +} + // ----- #endif // SPIRV_STRUCTURE_OPS diff --git a/mlir/lib/Dialect/SPIRV/SPIRVOps.cpp b/mlir/lib/Dialect/SPIRV/SPIRVOps.cpp --- a/mlir/lib/Dialect/SPIRV/SPIRVOps.cpp +++ b/mlir/lib/Dialect/SPIRV/SPIRVOps.cpp @@ -3394,6 +3394,78 @@ return success(); } +//===----------------------------------------------------------------------===// +// spv.mlir.yield +//===----------------------------------------------------------------------===// + +static LogicalResult verify(spirv::YieldOp yieldOp) { + Operation *parentOp = yieldOp.getParentOp(); + + if (!parentOp || !isa(parentOp)) + return yieldOp.emitOpError( + "expected parent op to be 'spv.SpecConstantOperation'"); + + Block &block = parentOp->getRegion(0).getBlocks().front(); + Operation &enclosedOp = block.getOperations().front(); + + if (yieldOp.getOperand().getDefiningOp() != &enclosedOp) + return yieldOp.emitOpError( + "expected operand to be defined by preceeding op"); + + return success(); +} + +static ParseResult parseSpecConstantOperationOp(OpAsmParser &parser, + OperationState &state) { + // TODO: For now, only generic form is supported. + return failure(); +} + +static void print(spirv::SpecConstantOperationOp op, OpAsmPrinter &printer) { + // TODO + printer.printGenericOp(op); +} + +static LogicalResult verify(spirv::SpecConstantOperationOp constOp) { + Block &block = constOp.getRegion().getBlocks().front(); + + if (block.getOperations().size() != 2) + return constOp.emitOpError("expected exactly 2 nested ops"); + + Operation &yieldOp = block.getOperations().back(); + + if (!isa(yieldOp)) + return constOp.emitOpError("expected terminator to be a yield op"); + + Operation &enclosedOp = block.getOperations().front(); + + // TODO Add a `UsableInSpecConstantOp` trait and mark ops from the list below + // with it instead. + if (!isa(enclosedOp)) + return constOp.emitOpError("invalid enclosed op"); + + for (auto operand : constOp.getOperands()) { + if (!isa( + operand.getDefiningOp())) + return constOp.emitOpError("invalid operand"); + } + + return success(); +} + namespace mlir { namespace spirv { diff --git a/mlir/test/Dialect/SPIRV/structure-ops.mlir b/mlir/test/Dialect/SPIRV/structure-ops.mlir --- a/mlir/test/Dialect/SPIRV/structure-ops.mlir +++ b/mlir/test/Dialect/SPIRV/structure-ops.mlir @@ -757,3 +757,109 @@ // expected-error @+1 {{unsupported composite type}} spv.specConstantComposite @scc (@sc1) : !spv.coopmatrix<8x16xf32, Device> } +//===----------------------------------------------------------------------===// +// spv.SpecConstantOperation +//===----------------------------------------------------------------------===// + +// ----- + +spv.module Logical GLSL450 { + spv.func @foo() -> i32 "None" { + %0 = spv.constant 1: i32 + + %1 = "spv.SpecConstantOperation"(%0) ({ + %ret = spv.IAdd %0, %0 : i32 + spv.mlir.yield %ret : i32 + }) : (i32) -> i32 + + spv.ReturnValue %1 : i32 + } +} + +// ----- + +spv.module Logical GLSL450 { + spv.func @foo() -> i32 "None" { + %0 = spv.constant 1: i32 + // expected-error @+1 {{expected parent op to be 'spv.SpecConstantOperation'}} + spv.mlir.yield %0 : i32 + } +} + +// ----- + +spv.module Logical GLSL450 { + spv.func @foo() -> i32 "None" { + %0 = spv.constant 1: i32 + + %1 = "spv.SpecConstantOperation"(%0) ({ + %ret = spv.ISub %0, %0 : i32 + // expected-error @+1 {{expected operand to be defined by preceeding op}} + spv.mlir.yield %0 : i32 + }) : (i32) -> i32 + + spv.ReturnValue %1 : i32 + } +} + +// ----- + +spv.module Logical GLSL450 { + spv.func @foo() -> i32 "None" { + %0 = spv.constant 1: i32 + + // expected-error @+1 {{expected exactly 2 nested ops}} + %1 = "spv.SpecConstantOperation"(%0) ({ + %ret = spv.IAdd %0, %0 : i32 + %ret2 = spv.IAdd %0, %0 : i32 + spv.mlir.yield %ret : i32 + }) : (i32) -> i32 + + spv.ReturnValue %1 : i32 + } +} + +// ----- + +spv.module Logical GLSL450 { + spv.func @foo() -> i32 "None" { + %0 = spv.constant 1: i32 + + // expected-error @+1 {{expected terminator to be a yield op}} + %1 = "spv.SpecConstantOperation"(%0) ({ + %ret = spv.IAdd %0, %0 : i32 + spv.ReturnValue %ret : i32 + }) : (i32) -> i32 + + spv.ReturnValue %1 : i32 + } +} + +// ----- + +spv.module Logical GLSL450 { + spv.func @foo() -> () "None" { + %0 = spv.Variable : !spv.ptr + + // expected-error @+1 {{invalid enclosed op}} + %2 = "spv.SpecConstantOperation"(%0) ({ + %ret = spv.Load "Function" %0 : i32 + spv.mlir.yield %ret : i32 + }) : (!spv.ptr) -> i32 + } +} + +// ----- + +spv.module Logical GLSL450 { + spv.func @foo() -> () "None" { + %0 = spv.Variable : !spv.ptr + %1 = spv.Load "Function" %0 : i32 + + // expected-error @+1 {{invalid operand}} + %2 = "spv.SpecConstantOperation"(%1) ({ + %ret = spv.IAdd %1, %1 : i32 + spv.mlir.yield %ret : i32 + }) : (i32) -> i32 + } +}