return failure();
}
if (opcode == spirv::Opcode::OpFunctionEnd) {
- LLVM_DEBUG(llvm::dbgs() << "[fn] completed function " << fnName << " (type="
- << fnType << ", id=" << operands[1] << ")\n");
+ LLVM_DEBUG(llvm::dbgs()
+ << "[fn] completed function '" << fnName << "' (type=" << fnType
+ << ", id=" << operands[1] << ")\n");
return processFunctionEnd(instOperands);
}
if (opcode != spirv::Opcode::OpLabel) {
return failure();
}
- LLVM_DEBUG(llvm::dbgs() << "[fn] completed function " << fnName << " (type="
+ LLVM_DEBUG(llvm::dbgs() << "[fn] completed function '" << fnName << "' (type="
<< fnType << ", id=" << operands[1] << ")\n");
return processFunctionEnd(instOperands);
}
Region &body = op->getRegion(0);
BlockAndValueMapping mapper;
- // All references to the old merge block should be directed to the loop
- // merge block in the LoopOp's region.
+ // All references to the old merge block should be directed to the
+ // selection/loop merge block in the SelectionOp/LoopOp's region.
mapper.map(mergeBlock, &body.back());
collectBlocksInConstruct();
// block in this loop construct.
OpBuilder builder(body);
for (auto *block : constructBlocks) {
- assert(block->getNumArguments() == 0 &&
- "block in loop construct should not have arguments");
-
- // Create an block and insert it before the loop merge block in the
- // LoopOp's region.
+ // Create a block and insert it before the selection/loop merge block in the
+ // SelectionOp/LoopOp's region.
auto *newBlock = builder.createBlock(&body.back());
mapper.map(block, newBlock);
+ LLVM_DEBUG(llvm::dbgs() << "[cf] cloned block " << newBlock
+ << " from block " << block << "\n");
for (auto &op : *block)
newBlock->push_back(op.clone(mapper));
}
// All the blocks cloned into the SelectionOp/LoopOp's region can now be
- // deleted.
+ // cleaned up.
+ LLVM_DEBUG(llvm::dbgs() << "[cf] cleaning up blocks after clone\n");
+ // First we need to drop all uses on ops inside all blocks. This is needed
+ // because we can have blocks referencing SSA values from one another.
for (auto *block : constructBlocks)
- block->erase();
+ block->dropAllReferences();
+
+ // Then erase all blocks except the old header block.
+ for (auto *block : constructBlocks) {
+ // The structured selection/loop's entry block does not have arguments.
+ // If the function's header block is also part of the structured control
+ // flow, we cannot just simply erase it because it may contain arguments
+ // matching the function signature and used by the cloned blocks.
+ if (block->isEntryBlock() && isa<FuncOp>(block->getParentOp())) {
+ LLVM_DEBUG(llvm::dbgs() << "[cf] changing entry block " << block
+ << " to only contain a spv.Branch op\n");
+ // Still keep the function entry block for the potential block arguments,
+ // but replace all ops inside with a branch to the merge block.
+ block->clear();
+ builder.setInsertionPointToEnd(block);
+ builder.create<spirv::BranchOp>(location, mergeBlock);
+ } else {
+ LLVM_DEBUG(llvm::dbgs() << "[cf] erasing block " << block << "\n");
+ block->erase();
+ }
+ }
return success();
}
LogicalResult Deserializer::structurizeControlFlow() {
- LLVM_DEBUG(llvm::dbgs() << "[cf] structurizing control flow\n");
+ LLVM_DEBUG(llvm::dbgs() << "[cf] start structurizing control flow\n");
while (!blockMergeInfo.empty()) {
auto *headerBlock = blockMergeInfo.begin()->first;
uint32_t assignBlockID(Block *block);
// Processes the given `block` and emits SPIR-V instructions for all ops
- // inside. `actionBeforeTerminator` is a callback that will be invoked before
- // handling the terminator op. It can be used to inject the Op*Merge
- // instruction if this is a SPIR-V selection/loop header block.
+ // inside. Does not emit OpLabel for this block if `omitLabel` is true.
+ // `actionBeforeTerminator` is a callback that will be invoked before handling
+ // the terminator op. It can be used to inject the Op*Merge instruction if
+ // this is a SPIR-V selection/loop header block.
LogicalResult
- processBlock(Block *block,
+ processBlock(Block *block, bool omitLabel = false,
llvm::function_ref<void()> actionBeforeTerminator = nullptr);
LogicalResult processSelectionOp(spirv::SelectionOp selectionOp);
}
LogicalResult
-Serializer::processBlock(Block *block,
+Serializer::processBlock(Block *block, bool omitLabel,
llvm::function_ref<void()> actionBeforeTerminator) {
- auto blockID = findBlockID(block);
- if (blockID == 0) {
- blockID = assignBlockID(block);
- }
+ if (!omitLabel) {
+ auto blockID = findBlockID(block);
+ if (blockID == 0) {
+ blockID = assignBlockID(block);
+ }
- // Emit OpLabel for this block.
- encodeInstructionInto(functions, spirv::Opcode::OpLabel, {blockID});
+ // Emit OpLabel for this block.
+ encodeInstructionInto(functions, spirv::Opcode::OpLabel, {blockID});
+ }
// Process each op in this block except the terminator.
for (auto &op : llvm::make_range(block->begin(), std::prev(block->end()))) {
auto *headerBlock = selectionOp.getHeaderBlock();
auto *mergeBlock = selectionOp.getMergeBlock();
- auto headerID = findBlockID(headerBlock);
auto mergeID = findBlockID(mergeBlock);
- // This selection is in some MLIR block with preceding and following ops. In
- // the binary format, it should reside in separate SPIR-V blocks from its
- // preceding and following ops. So we need to emit unconditional branches to
- // jump to this LoopOp's SPIR-V blocks and jumping back to the normal flow
- // afterwards.
- encodeInstructionInto(functions, spirv::Opcode::OpBranch, {headerID});
-
// Emit the selection header block, which dominates all other blocks, first.
// We need to emit an OpSelectionMerge instruction before the loop header
// block's terminator.
functions, spirv::Opcode::OpSelectionMerge,
{mergeID, static_cast<uint32_t>(spirv::LoopControl::None)});
};
- if (failed(processBlock(headerBlock, emitSelectionMerge)))
+ // For structured selection, we cannot have blocks in the selection construct
+ // branching to the selection header block. Entering the selection (and
+ // reaching the selection header) must be from the block containing the
+ // spv.selection op. If there are ops ahead of the spv.selection op in the
+ // block, we can "merge" them into the selection header. So here we don't need
+ // to emit a separate block; just continue with the existing block.
+ if (failed(processBlock(headerBlock, /*omitLabel=*/true, emitSelectionMerge)))
return failure();
// Process all blocks with a depth-first visitor starting from the header
functions, spirv::Opcode::OpLoopMerge,
{mergeID, continueID, static_cast<uint32_t>(spirv::LoopControl::None)});
};
- if (failed(processBlock(headerBlock, emitLoopMerge)))
+ if (failed(processBlock(headerBlock, /*omitLabel=*/false, emitLoopMerge)))
return failure();
// Process all blocks with a depth-first visitor starting from the header
-// RUN: mlir-translate -test-spirv-roundtrip %s | FileCheck %s
+// RUN: mlir-translate -test-spirv-roundtrip -split-input-file %s | FileCheck %s
+
+// Single loop
spv.module "Logical" "GLSL450" {
// for (int i = 0; i < count; ++i) {}
capabilities = ["Shader"]
}
+// -----
+
+// Nested loop
+
+spv.module "Logical" "GLSL450" {
+ // for (int i = 0; i < count; ++i) {
+ // for (int j = 0; j < count; ++j) { }
+ // }
+ func @loop(%count : i32) -> () {
+ %zero = spv.constant 0: i32
+ %one = spv.constant 1: i32
+ %ivar = spv.Variable init(%zero) : !spv.ptr<i32, Function>
+ %jvar = spv.Variable init(%zero) : !spv.ptr<i32, Function>
+
+// CHECK: spv.Branch ^bb1
+// CHECK-NEXT: ^bb1:
+// CHECK-NEXT: spv.loop
+ spv.loop {
+// CHECK-NEXT: spv.Branch ^bb1
+ spv.Branch ^header
+
+// CHECK-NEXT: ^bb1:
+ ^header:
+// CHECK-NEXT: spv.Load
+ %ival0 = spv.Load "Function" %ivar : i32
+// CHECK-NEXT: spv.SLessThan
+ %icmp = spv.SLessThan %ival0, %count : i32
+// CHECK-NEXT: spv.BranchConditional %{{.*}}, ^bb2, ^bb5
+ spv.BranchConditional %icmp, ^body, ^merge
+
+// CHECK-NEXT: ^bb2:
+ ^body:
+// CHECK-NEXT: spv.constant 0
+// CHECK-NEXT: spv.Store
+ spv.Store "Function" %jvar, %zero : i32
+// CHECK-NEXT: spv.Branch ^bb3
+// CHECK-NEXT: ^bb3:
+// CHECK-NEXT: spv.loop {
+ spv.loop {
+// CHECK-NEXT: spv.Branch ^bb1
+ spv.Branch ^header
+
+// CHECK-NEXT: ^bb1:
+ ^header:
+// CHECK-NEXT: spv.Load
+ %jval0 = spv.Load "Function" %jvar : i32
+// CHECK-NEXT: spv.SLessThan
+ %jcmp = spv.SLessThan %jval0, %count : i32
+// CHECK-NEXT: spv.BranchConditional %{{.*}}, ^bb2, ^bb4
+ spv.BranchConditional %jcmp, ^body, ^merge
+
+// CHECK-NEXT: ^bb2:
+ ^body:
+ // Do nothing
+// CHECK-NEXT: spv.Branch ^bb3
+ spv.Branch ^continue
+
+// CHECK-NEXT: ^bb3:
+ ^continue:
+// CHECK-NEXT: spv.Load
+ %jval1 = spv.Load "Function" %jvar : i32
+// CHECK-NEXT: spv.constant 1
+// CHECK-NEXT: spv.IAdd
+ %add = spv.IAdd %jval1, %one : i32
+// CHECK-NEXT: spv.Store
+ spv.Store "Function" %jvar, %add : i32
+// CHECK-NEXT: spv.Branch ^bb1
+ spv.Branch ^header
+
+// CHECK-NEXT: ^bb4:
+ ^merge:
+// CHECK-NEXT: spv._merge
+ spv._merge
+ } // end inner loop
+
+// CHECK: spv.Branch ^bb4
+ spv.Branch ^continue
+
+// CHECK-NEXT: ^bb4:
+ ^continue:
+// CHECK-NEXT: spv.Load
+ %ival1 = spv.Load "Function" %ivar : i32
+// CHECK-NEXT: spv.constant 1
+// CHECK-NEXT: spv.IAdd
+ %add = spv.IAdd %ival1, %one : i32
+// CHECK-NEXT: spv.Store
+ spv.Store "Function" %ivar, %add : i32
+// CHECK-NEXT: spv.Branch ^bb1
+ spv.Branch ^header
+
+// CHECK-NEXT: ^bb5:
+// CHECK-NEXT: spv._merge
+ ^merge:
+ spv._merge
+ } // end outer loop
+ spv.Return
+ }
+
+ func @main() -> () {
+ spv.Return
+ }
+ spv.EntryPoint "GLCompute" @main
+} attributes {
+ capabilities = ["Shader"]
+}
+
-// RUN: mlir-translate -test-spirv-roundtrip %s | FileCheck %s
+// RUN: mlir-translate -test-spirv-roundtrip -split-input-file %s | FileCheck %s
+
+// Selection with both then and else branches
spv.module "Logical" "GLSL450" {
func @selection(%cond: i1) -> () {
+// CHECK: spv.Branch ^bb1
+// CHECK-NEXT: ^bb1:
%zero = spv.constant 0: i32
%one = spv.constant 1: i32
%two = spv.constant 2: i32
%var = spv.Variable init(%zero) : !spv.ptr<i32, Function>
-// CHECK: spv.Branch ^bb1
-// CHECK-NEXT: ^bb1:
-// CHECK-NEXT: spv.selection
+// CHECK-NEXT: spv.selection {
+// CHECK-NEXT: spv.constant 0
+// CHECK-NEXT: spv.Variable
spv.selection {
// CHECK-NEXT: spv.BranchConditional %{{.*}}, ^bb1, ^bb2
spv.BranchConditional %cond, ^then, ^else
} attributes {
capabilities = ["Shader"]
}
+
+// -----
+
+// Selection with only then branch
+// Selection in function entry block
+
+spv.module "Logical" "GLSL450" {
+// CHECK: func @selection(%[[ARG:.*]]: i1
+ func @selection(%cond: i1) -> (i32) {
+// CHECK: spv.Branch ^bb1
+// CHECK-NEXT: ^bb1:
+// CHECK-NEXT: spv.selection
+ spv.selection {
+// CHECK-NEXT: spv.BranchConditional %[[ARG]], ^bb1, ^bb2
+ spv.BranchConditional %cond, ^then, ^merge
+
+// CHECK: ^bb1:
+ ^then:
+ %zero = spv.constant 0 : i32
+ spv.ReturnValue %zero : i32
+
+// CHECK: ^bb2:
+ ^merge:
+// CHECK-NEXT: spv._merge
+ spv._merge
+ }
+
+ %one = spv.constant 1 : i32
+ spv.ReturnValue %one : i32
+ }
+
+ func @main() -> () {
+ spv.Return
+ }
+ spv.EntryPoint "GLCompute" @main
+ spv.ExecutionMode @main "LocalSize", 1, 1, 1
+} attributes {
+ capabilities = ["Shader"]
+}
+