1//===-- SPIRVControlFlowOps.td - SPIR-V Control Flow Ops ---*- tablegen -*-===// 2// 3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4// See https://llvm.org/LICENSE.txt for license information. 5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6// 7//===----------------------------------------------------------------------===// 8// 9// This file contains control flow ops for the SPIR-V dialect. It corresponds 10// to "3.32.17. Control-Flow Instructions" of the SPIR-V specification. 11// 12//===----------------------------------------------------------------------===// 13 14#ifndef SPIRV_CONTROLFLOW_OPS 15#define SPIRV_CONTROLFLOW_OPS 16 17include "mlir/Dialect/SPIRV/SPIRVBase.td" 18include "mlir/Interfaces/CallInterfaces.td" 19include "mlir/Interfaces/ControlFlowInterfaces.td" 20include "mlir/Interfaces/SideEffectInterfaces.td" 21 22// ----- 23 24def SPV_BranchOp : SPV_Op<"Branch", [ 25 DeclareOpInterfaceMethods<BranchOpInterface>, InFunctionScope, NoSideEffect, 26 Terminator]> { 27 let summary = "Unconditional branch to target block."; 28 29 let description = [{ 30 This instruction must be the last instruction in a block. 31 32 <!-- End of AutoGen section --> 33 34 ``` 35 branch-op ::= `spv.Branch` successor 36 successor ::= bb-id branch-use-list? 37 branch-use-list ::= `(` ssa-use-list `:` type-list-no-parens `)` 38 ``` 39 40 #### Example: 41 42 ```mlir 43 spv.Branch ^target 44 spv.Branch ^target(%0, %1: i32, f32) 45 ``` 46 }]; 47 48 let arguments = (ins Variadic<SPV_Type>:$targetOperands); 49 50 let results = (outs); 51 52 let successors = (successor AnySuccessor:$target); 53 54 let verifier = [{ return success(); }]; 55 56 let builders = [ 57 OpBuilderDAG<(ins "Block *":$successor, CArg<"ValueRange", "{}">:$arguments), 58 [{ 59 $_state.addSuccessors(successor); 60 $_state.addOperands(arguments); 61 }]> 62 ]; 63 64 let skipDefaultBuilders = 1; 65 66 let extraClassDeclaration = [{ 67 /// Returns the branch target block. 68 Block *getTarget() { return target(); } 69 70 /// Returns the block arguments. 71 operand_range getBlockArguments() { return targetOperands(); } 72 }]; 73 74 let autogenSerialization = 0; 75 76 let assemblyFormat = [{ 77 $target (`(` $targetOperands^ `:` type($targetOperands) `)`)? attr-dict 78 }]; 79} 80 81// ----- 82 83def SPV_BranchConditionalOp : SPV_Op<"BranchConditional", [ 84 AttrSizedOperandSegments, DeclareOpInterfaceMethods<BranchOpInterface>, 85 InFunctionScope, NoSideEffect, Terminator]> { 86 let summary = [{ 87 If Condition is true, branch to true block, otherwise branch to false 88 block. 89 }]; 90 91 let description = [{ 92 Condition must be a Boolean type scalar. 93 94 Branch weights are unsigned 32-bit integer literals. There must be 95 either no Branch Weights or exactly two branch weights. If present, the 96 first is the weight for branching to True Label, and the second is the 97 weight for branching to False Label. The implied probability that a 98 branch is taken is its weight divided by the sum of the two Branch 99 weights. At least one weight must be non-zero. A weight of zero does not 100 imply a branch is dead or permit its removal; branch weights are only 101 hints. The two weights must not overflow a 32-bit unsigned integer when 102 added together. 103 104 This instruction must be the last instruction in a block. 105 106 <!-- End of AutoGen section --> 107 108 ``` 109 branch-conditional-op ::= `spv.BranchConditional` ssa-use 110 (`[` integer-literal, integer-literal `]`)? 111 `,` successor `,` successor 112 successor ::= bb-id branch-use-list? 113 branch-use-list ::= `(` ssa-use-list `:` type-list-no-parens `)` 114 ``` 115 116 #### Example: 117 118 ```mlir 119 spv.BranchConditional %condition, ^true_branch, ^false_branch 120 spv.BranchConditional %condition, ^true_branch(%0: i32), ^false_branch(%1: i32) 121 ``` 122 }]; 123 124 let arguments = (ins 125 SPV_Bool:$condition, 126 Variadic<SPV_Type>:$trueTargetOperands, 127 Variadic<SPV_Type>:$falseTargetOperands, 128 OptionalAttr<I32ArrayAttr>:$branch_weights 129 ); 130 131 let results = (outs); 132 133 let successors = (successor AnySuccessor:$trueTarget, 134 AnySuccessor:$falseTarget); 135 136 let builders = [ 137 OpBuilderDAG<(ins "Value":$condition, "Block *":$trueBlock, 138 "ValueRange":$trueArguments, "Block *":$falseBlock, 139 "ValueRange":$falseArguments, 140 CArg<"Optional<std::pair<uint32_t, uint32_t>>", "{}">:$weights), 141 [{ 142 ArrayAttr weightsAttr; 143 if (weights) { 144 weightsAttr = 145 $_builder.getI32ArrayAttr({static_cast<int32_t>(weights->first), 146 static_cast<int32_t>(weights->second)}); 147 } 148 build($_builder, $_state, condition, trueArguments, falseArguments, 149 weightsAttr, trueBlock, falseBlock); 150 }]> 151 ]; 152 153 let autogenSerialization = 0; 154 155 let extraClassDeclaration = [{ 156 /// Branch indices into the successor list. 157 enum { kTrueIndex = 0, kFalseIndex = 1 }; 158 159 /// Returns the target block for the true branch. 160 Block *getTrueBlock() { return getOperation()->getSuccessor(kTrueIndex); } 161 162 /// Returns the target block for the false branch. 163 Block *getFalseBlock() { return getOperation()->getSuccessor(kFalseIndex); } 164 165 /// Returns the number of arguments to the true target block. 166 unsigned getNumTrueBlockArguments() { 167 return trueTargetOperands().size(); 168 } 169 170 /// Returns the number of arguments to the false target block. 171 unsigned getNumFalseBlockArguments() { 172 return falseTargetOperands().size(); 173 } 174 175 // Iterator and range support for true target block arguments. 176 operand_range getTrueBlockArguments() { 177 return trueTargetOperands(); 178 } 179 180 // Iterator and range support for false target block arguments. 181 operand_range getFalseBlockArguments() { 182 return falseTargetOperands(); 183 } 184 185 private: 186 /// Gets the index of the first true block argument in the operand list. 187 unsigned getTrueBlockArgumentIndex() { 188 return 1; // Omit the first argument, which is the condition. 189 } 190 191 /// Gets the index of the first false block argument in the operand list. 192 unsigned getFalseBlockArgumentIndex() { 193 return getTrueBlockArgumentIndex() + getNumTrueBlockArguments(); 194 } 195 }]; 196} 197 198// ----- 199 200def SPV_FunctionCallOp : SPV_Op<"FunctionCall", [ 201 InFunctionScope, DeclareOpInterfaceMethods<CallOpInterface>]> { 202 let summary = "Call a function."; 203 204 let description = [{ 205 Result Type is the type of the return value of the function. It must be 206 the same as the Return Type operand of the Function Type operand of the 207 Function operand. 208 209 Function is an OpFunction instruction. This could be a forward 210 reference. 211 212 Argument N is the object to copy to parameter N of Function. 213 214 Note: A forward call is possible because there is no missing type 215 information: Result Type must match the Return Type of the function, and 216 the calling argument types must match the formal parameter types. 217 218 <!-- End of AutoGen section --> 219 220 ``` 221 function-call-op ::= `spv.FunctionCall` function-id `(` ssa-use-list `)` 222 `:` function-type 223 ``` 224 225 #### Example: 226 227 ```mlir 228 spv.FunctionCall @f_void(%arg0) : (i32) -> () 229 %0 = spv.FunctionCall @f_iadd(%arg0, %arg1) : (i32, i32) -> i32 230 ``` 231 }]; 232 233 let arguments = (ins 234 FlatSymbolRefAttr:$callee, 235 Variadic<SPV_Type>:$arguments 236 ); 237 238 let results = (outs 239 Optional<SPV_Type>:$result 240 ); 241 242 let autogenSerialization = 0; 243 244 let assemblyFormat = [{ 245 $callee `(` $arguments `)` attr-dict `:` 246 functional-type($arguments, results) 247 }]; 248} 249 250// ----- 251 252def SPV_LoopOp : SPV_Op<"loop", [InFunctionScope]> { 253 let summary = "Define a structured loop."; 254 255 let description = [{ 256 SPIR-V can explicitly declare structured control-flow constructs using merge 257 instructions. These explicitly declare a header block before the control 258 flow diverges and a merge block where control flow subsequently converges. 259 These blocks delimit constructs that must nest, and can only be entered 260 and exited in structured ways. See "2.11. Structured Control Flow" of the 261 SPIR-V spec for more details. 262 263 Instead of having a `spv.LoopMerge` op to directly model loop merge 264 instruction for indicating the merge and continue target, we use regions 265 to delimit the boundary of the loop: the merge target is the next op 266 following the `spv.loop` op and the continue target is the block that 267 has a back-edge pointing to the entry block inside the `spv.loop`'s region. 268 This way it's easier to discover all blocks belonging to a construct and 269 it plays nicer with the MLIR system. 270 271 The `spv.loop` region should contain at least four blocks: one entry block, 272 one loop header block, one loop continue block, one loop merge block. 273 The entry block should be the first block and it should jump to the loop 274 header block, which is the second block. The loop merge block should be the 275 last block. The merge block should only contain a `spv.mlir.merge` op. 276 The continue block should be the second to last block and it should have a 277 branch to the loop header block. The loop continue block should be the only 278 block, except the entry block, branching to the header block. 279 }]; 280 281 let arguments = (ins 282 SPV_LoopControlAttr:$loop_control 283 ); 284 285 let results = (outs); 286 287 let regions = (region AnyRegion:$body); 288 289 let builders = [OpBuilderDAG<(ins)>]; 290 291 let extraClassDeclaration = [{ 292 // Returns the entry block. 293 Block *getEntryBlock(); 294 295 // Returns the loop header block. 296 Block *getHeaderBlock(); 297 298 // Returns the loop continue block. 299 Block *getContinueBlock(); 300 301 // Returns the loop merge block. 302 Block *getMergeBlock(); 303 304 // Adds an empty entry block and loop merge block containing one 305 // spv.mlir.merge op. 306 void addEntryAndMergeBlock(); 307 }]; 308 309 let hasOpcode = 0; 310 311 let autogenSerialization = 0; 312} 313 314// ----- 315 316def SPV_MergeOp : SPV_Op<"mlir.merge", [NoSideEffect, Terminator]> { 317 let summary = "A special terminator for merging a structured selection/loop."; 318 319 let description = [{ 320 We use `spv.selection`/`spv.loop` for modelling structured selection/loop. 321 This op is a terminator used inside their regions to mean jumping to the 322 merge point, which is the next op following the `spv.selection` or 323 `spv.loop` op. This op does not have a corresponding instruction in the 324 SPIR-V binary format; it's solely for structural purpose. 325 }]; 326 327 let arguments = (ins); 328 329 let results = (outs); 330 331 let assemblyFormat = "attr-dict"; 332 333 let hasOpcode = 0; 334 335 let autogenSerialization = 0; 336} 337 338// ----- 339 340def SPV_ReturnOp : SPV_Op<"Return", [InFunctionScope, NoSideEffect, 341 Terminator]> { 342 let summary = "Return with no value from a function with void return type."; 343 344 let description = [{ 345 This instruction must be the last instruction in a block. 346 347 <!-- End of AutoGen section --> 348 349 ``` 350 return-op ::= `spv.Return` 351 ``` 352 }]; 353 354 let arguments = (ins); 355 356 let results = (outs); 357 358 let assemblyFormat = "attr-dict"; 359} 360 361// ----- 362 363def SPV_UnreachableOp : SPV_Op<"Unreachable", [InFunctionScope, Terminator]> { 364 let summary = "Declares that this block is not reachable in the CFG."; 365 366 let description = [{ 367 This instruction must be the last instruction in a block. 368 369 <!-- End of AutoGen section --> 370 371 ``` 372 unreachable-op ::= `spv.Unreachable` 373 ``` 374 }]; 375 376 let arguments = (ins); 377 378 let results = (outs); 379 380 let assemblyFormat = "attr-dict"; 381} 382 383// ----- 384 385def SPV_ReturnValueOp : SPV_Op<"ReturnValue", [InFunctionScope, NoSideEffect, 386 Terminator]> { 387 let summary = "Return a value from a function."; 388 389 let description = [{ 390 Value is the value returned, by copy, and must match the Return Type 391 operand of the OpTypeFunction type of the OpFunction body this return 392 instruction is in. 393 394 This instruction must be the last instruction in a block. 395 396 <!-- End of AutoGen section --> 397 398 ``` 399 return-value-op ::= `spv.ReturnValue` ssa-use `:` spirv-type 400 ``` 401 402 #### Example: 403 404 ```mlir 405 spv.ReturnValue %0 : f32 406 ``` 407 }]; 408 409 let arguments = (ins 410 SPV_Type:$value 411 ); 412 413 let results = (outs); 414 415 let assemblyFormat = "$value attr-dict `:` type($value)"; 416} 417 418def SPV_SelectionOp : SPV_Op<"selection", [InFunctionScope]> { 419 let summary = "Define a structured selection."; 420 421 let description = [{ 422 SPIR-V can explicitly declare structured control-flow constructs using merge 423 instructions. These explicitly declare a header block before the control 424 flow diverges and a merge block where control flow subsequently converges. 425 These blocks delimit constructs that must nest, and can only be entered 426 and exited in structured ways. See "2.11. Structured Control Flow" of the 427 SPIR-V spec for more details. 428 429 Instead of having a `spv.SelectionMerge` op to directly model selection 430 merge instruction for indicating the merge target, we use regions to delimit 431 the boundary of the selection: the merge target is the next op following the 432 `spv.selection` op. This way it's easier to discover all blocks belonging to 433 the selection and it plays nicer with the MLIR system. 434 435 The `spv.selection` region should contain at least two blocks: one selection 436 header block, and one selection merge. The selection header block should be 437 the first block. The selection merge block should be the last block. 438 The merge block should only contain a `spv.mlir.merge` op. 439 }]; 440 441 let arguments = (ins 442 SPV_SelectionControlAttr:$selection_control 443 ); 444 445 let results = (outs); 446 447 let regions = (region AnyRegion:$body); 448 449 let extraClassDeclaration = [{ 450 /// Returns the selection header block. 451 Block *getHeaderBlock(); 452 453 /// Returns the selection merge block. 454 Block *getMergeBlock(); 455 456 /// Adds a selection merge block containing one spv.mlir.merge op. 457 void addMergeBlock(); 458 459 /// Creates a spv.selection op for `if (<condition>) then { <thenBody> }` 460 /// with `builder`. `builder`'s insertion point will remain at after the 461 /// newly inserted spv.selection op afterwards. 462 static SelectionOp createIfThen( 463 Location loc, Value condition, 464 function_ref<void(OpBuilder &builder)> thenBody, 465 OpBuilder &builder); 466 }]; 467 468 let hasOpcode = 0; 469 470 let autogenSerialization = 0; 471 472 let hasCanonicalizer = 1; 473} 474 475#endif // SPIRV_CONTROLFLOW_OPS 476