• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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