[spirv-reader] Emit control flow: if/then/else

Bug: tint:3
Change-Id: Ief0544415f27842913a6234a962d163ecedb48df
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/21821
Reviewed-by: dan sinclair <dsinclair@google.com>
This commit is contained in:
David Neto 2020-06-02 13:25:06 +00:00 committed by dan sinclair
parent e2be489756
commit 468c26b233
3 changed files with 890 additions and 29 deletions

View File

@ -27,15 +27,20 @@
#include "src/ast/as_expression.h" #include "src/ast/as_expression.h"
#include "src/ast/assignment_statement.h" #include "src/ast/assignment_statement.h"
#include "src/ast/binary_expression.h" #include "src/ast/binary_expression.h"
#include "src/ast/else_statement.h"
#include "src/ast/identifier_expression.h" #include "src/ast/identifier_expression.h"
#include "src/ast/if_statement.h"
#include "src/ast/loop_statement.h"
#include "src/ast/member_accessor_expression.h" #include "src/ast/member_accessor_expression.h"
#include "src/ast/scalar_constructor_expression.h" #include "src/ast/scalar_constructor_expression.h"
#include "src/ast/storage_class.h" #include "src/ast/storage_class.h"
#include "src/ast/switch_statement.h"
#include "src/ast/uint_literal.h" #include "src/ast/uint_literal.h"
#include "src/ast/unary_op.h" #include "src/ast/unary_op.h"
#include "src/ast/unary_op_expression.h" #include "src/ast/unary_op_expression.h"
#include "src/ast/variable.h" #include "src/ast/variable.h"
#include "src/ast/variable_decl_statement.h" #include "src/ast/variable_decl_statement.h"
#include "src/reader/spirv/construct.h"
#include "src/reader/spirv/fail_stream.h" #include "src/reader/spirv/fail_stream.h"
#include "src/reader/spirv/parser_impl.h" #include "src/reader/spirv/parser_impl.h"
@ -368,19 +373,37 @@ FunctionEmitter::FunctionEmitter(ParserImpl* pi,
fail_stream_(pi->fail_stream()), fail_stream_(pi->fail_stream()),
namer_(pi->namer()), namer_(pi->namer()),
function_(function) { function_(function) {
statements_stack_.emplace_back(ast::StatementList{}); PushNewStatementBlock(nullptr, 0, nullptr);
} }
FunctionEmitter::~FunctionEmitter() = default; FunctionEmitter::~FunctionEmitter() = default;
const ast::StatementList& FunctionEmitter::ast_body() { void FunctionEmitter::PushNewStatementBlock(const Construct* construct,
assert(!statements_stack_.empty()); uint32_t end_id,
return statements_stack_[0]; CompletionAction action) {
statements_stack_.emplace_back(StatementBlock{construct, end_id, action,
ast::StatementList{},
ast::CaseStatementList{}});
} }
void FunctionEmitter::AddStatement(std::unique_ptr<ast::Statement> statement) { const ast::StatementList& FunctionEmitter::ast_body() {
assert(!statements_stack_.empty()); assert(!statements_stack_.empty());
statements_stack_.back().emplace_back(std::move(statement)); return statements_stack_[0].statements;
}
ast::Statement* FunctionEmitter::AddStatement(
std::unique_ptr<ast::Statement> statement) {
assert(!statements_stack_.empty());
auto* result = statement.get();
statements_stack_.back().statements.emplace_back(std::move(statement));
return result;
}
ast::Statement* FunctionEmitter::LastStatement() {
assert(!statements_stack_.empty());
const auto& statement_list = statements_stack_.back().statements;
assert(!statement_list.empty());
return statement_list.back().get();
} }
bool FunctionEmitter::Emit() { bool FunctionEmitter::Emit() {
@ -406,10 +429,11 @@ bool FunctionEmitter::Emit() {
"element but has " "element but has "
<< statements_stack_.size(); << statements_stack_.size();
} }
ast::StatementList body(std::move(statements_stack_[0])); ast::StatementList body(std::move(statements_stack_[0].statements));
parser_impl_.get_module().functions().back()->set_body(std::move(body)); parser_impl_.get_module().functions().back()->set_body(std::move(body));
// Maintain the invariant by repopulating the one and only element. // Maintain the invariant by repopulating the one and only element.
statements_stack_[0] = ast::StatementList{}; statements_stack_.clear();
PushNewStatementBlock(constructs_[0].get(), 0, nullptr);
return success(); return success();
} }
@ -502,6 +526,9 @@ bool FunctionEmitter::EmitBody() {
return false; return false;
} }
// TODO(dneto): register phis
// TODO(dneto): register SSA values which need to be hoisted
if (!EmitFunctionVariables()) { if (!EmitFunctionVariables()) {
return false; return false;
} }
@ -1244,8 +1271,8 @@ bool FunctionEmitter::FindIfSelectionInternalHeaders() {
if (construct->kind != Construct::kIfSelection) { if (construct->kind != Construct::kIfSelection) {
continue; continue;
} }
const auto* branch = auto* if_header_info = GetBlockInfo(construct->begin_id);
GetBlockInfo(construct->begin_id)->basic_block->terminator(); const auto* branch = if_header_info->basic_block->terminator();
const auto true_head = branch->GetSingleWordInOperand(1); const auto true_head = branch->GetSingleWordInOperand(1);
const auto false_head = branch->GetSingleWordInOperand(2); const auto false_head = branch->GetSingleWordInOperand(2);
@ -1259,9 +1286,11 @@ bool FunctionEmitter::FindIfSelectionInternalHeaders() {
if (contains_true) { if (contains_true) {
true_head_info->true_head_for = construct.get(); true_head_info->true_head_for = construct.get();
if_header_info->true_head = true_head_info;
} }
if (contains_false) { if (contains_false) {
false_head_info->false_head_for = construct.get(); false_head_info->false_head_for = construct.get();
if_header_info->false_head = false_head_info;
} }
if ((!contains_true) && contains_false) { if ((!contains_true) && contains_false) {
false_head_info->exclusive_false_head_for = construct.get(); false_head_info->exclusive_false_head_for = construct.get();
@ -1337,7 +1366,9 @@ bool FunctionEmitter::FindIfSelectionInternalHeaders() {
<< " going to " << premerge_id << " and " << dest_id; << " going to " << premerge_id << " and " << dest_id;
} }
premerge_id = dest_id; premerge_id = dest_id;
GetBlockInfo(dest_id)->premerge_head_for = construct.get(); auto* dest_block_info = GetBlockInfo(dest_id);
dest_block_info->premerge_head_for = construct.get();
if_header_info->premerge_head = dest_block_info;
} }
break; break;
} }
@ -1427,14 +1458,269 @@ TypedExpression FunctionEmitter::MakeExpression(uint32_t id) {
} }
bool FunctionEmitter::EmitFunctionBodyStatements() { bool FunctionEmitter::EmitFunctionBodyStatements() {
// TODO(dneto): For now, emit only regular statements in the entry block. // Dump the basic blocks in order, grouped by construct.
// We'll use assignments as markers in the tests, to be able to tell where
// code is placed in control flow. First prove that we can emit assignments. // We maintain a stack of StatementBlock objects, where new statements
return EmitStatementsInBasicBlock(*function_.entry()); // are always written to the topmost entry of the stack. By this point in
// processing, we have already recorded the interesting control flow
// boundaries in the BlockInfo and associated Construct objects. As we
// enter a new statement grouping, we push onto the stack, and also schedule
// the statement block's completion and removal at a future block's ID.
// Upon entry, the statement stack has one entry representing the whole
// function.
assert(!constructs_.empty());
Construct* function_construct = constructs_[0].get();
assert(function_construct != nullptr);
assert(function_construct->kind == Construct::kFunction);
// Make the first entry valid by filling in the construct field, which
// had not been computed at the time the entry was first created.
// TODO(dneto): refactor how the first construct is created vs.
// this statements stack entry is populated.
assert(statements_stack_.size() == 1);
statements_stack_[0].construct = function_construct;
for (auto block_id : block_order()) {
if (!EmitBasicBlock(*GetBlockInfo(block_id))) {
return false;
}
}
return success();
} }
bool FunctionEmitter::EmitStatementsInBasicBlock( bool FunctionEmitter::EmitBasicBlock(const BlockInfo& block_info) {
const spvtools::opt::BasicBlock& bb) { // Close off previous constructs.
while (!statements_stack_.empty() &&
(statements_stack_.back().end_id == block_info.id)) {
StatementBlock& sb = statements_stack_.back();
sb.completion_action(&sb);
statements_stack_.pop_back();
}
if (statements_stack_.empty()) {
return Fail() << "internal error: statements stack empty at block "
<< block_info.id;
}
// Enter new constructs.
std::vector<const Construct*> entering_constructs; // inner most comes first
{
auto* here = block_info.construct;
auto* const top_construct = statements_stack_.back().construct;
while (here != top_construct) {
// Only enter a construct at its header block.
if (here->begin_id == block_info.id) {
entering_constructs.push_back(here);
}
here = here->parent;
}
}
// What constructs can we have entered?
// - It can't be kFunction, because there is only one of those, and it was
// already on the stack at the outermost level.
// - We have at most one of kIfSelection, kSwitchSelection, or kLoop because
// each of those is headed by a block with a merge instruction, and the
// kIfSelection and kSwitchSelection header blocks end in different branch
// instructions.
// - A kContinue can contain a kContinue
// This is possible in Vulkan SPIR-V, but Tint disallows this by the rule
// that a block can be continue target for at most one header block. See
// test DISABLED_BlockIsContinueForMoreThanOneHeader. If we generalize this,
// then by a dominance argument, the inner loop continue target can only be
// a single-block loop.
// TODO(dneto): Handle this case.
// - All that's left is a kContinue and one of kIfSelection, kSwitchSelection,
// kLoop.
//
// The kContinue can be the parent of the other. For example, a selection
// starting at the first block of a continue construct.
//
// The kContinue can't be the child of the other because either:
// - Either it would be a single block loop but in that case there is no
// kLoop construct for it, by construction.
// - The kContinue is in a loop that is not single-block; and the
// selection contains the kContinue block but not the loop block. That
// breaks dominance rules. That is, the continue target is dominated by
// that loop header, and so gets found on the outside before the
// selection is found. The selection is inside the outer loop.
//
// So we fall into one of the following cases:
// - We are entering 0 or 1 constructs, or
// - We are entering 2 constructs, with the outer one being a kContinue, the
// inner one is not a continue.
if (entering_constructs.size() > 2) {
return Fail() << "internal error: bad construct nesting found";
}
if (entering_constructs.size() == 2) {
auto inner_kind = entering_constructs[0]->kind;
auto outer_kind = entering_constructs[1]->kind;
if (outer_kind != Construct::kContinue) {
return Fail() << "internal error: bad construct nesting. Only Continue "
"construct can be outer construct on same block";
}
if (inner_kind == Construct::kContinue) {
return Fail() << "internal error: unsupported construct nesting: "
"Continue around Continue";
}
if (inner_kind != Construct::kIfSelection &&
inner_kind != Construct::kSwitchSelection &&
inner_kind != Construct::kLoop) {
return Fail() << "internal error: bad construct nesting. Continue around "
"something other than if, switch, or loop";
}
}
// Enter constructs from outermost to innermost.
// kLoop and kContinue push a new statement-block onto the stack before
// emitting statements in the block.
// kIfSelection and kSwitchSelection emit statements in the block and then
// emit push a new statement-block. Only emit the statements in the block
// once.
// Have we emitted the statements for this block?
bool emitted = false;
for (auto iter = entering_constructs.rbegin();
iter != entering_constructs.rend(); ++iter) {
const Construct* construct = *iter;
switch (construct->kind) {
case Construct::kFunction:
return Fail() << "internal error: nested function construct";
case Construct::kLoop:
return Fail() << "unhandled: loop construct";
case Construct::kContinue:
return Fail() << "unhandled: continue construct";
case Construct::kIfSelection:
if (!EmitStatementsInBasicBlock(block_info, &emitted)) {
return false;
}
if (!EmitIfStart(block_info)) {
return false;
}
break;
case Construct::kSwitchSelection:
return Fail() << "unhandled: switch construct";
}
}
// If we aren't starting or transitioning, then emit the normal
// statements now.
if (!EmitStatementsInBasicBlock(block_info, &emitted)) {
return false;
}
if (!EmitNormalTerminator(block_info)) {
return false;
}
return success();
}
bool FunctionEmitter::EmitIfStart(const BlockInfo& block_info) {
// The block is the if-header block. So its construct is the if construct.
auto* construct = block_info.construct;
assert(construct->kind == Construct::kIfSelection);
assert(construct->begin_id == block_info.id);
const auto* const false_head = block_info.false_head;
const auto* const premerge_head = block_info.premerge_head;
auto* const if_stmt =
AddStatement(std::make_unique<ast::IfStatement>())->AsIf();
const auto condition_id =
block_info.basic_block->terminator()->GetSingleWordInOperand(0);
// Generate the code for the condition.
if_stmt->set_condition(std::move(MakeExpression(condition_id).expr));
// Compute the block IDs that should end the then-clause and the else-clause.
// We need to know where the *emitted* selection should end, i.e. the intended
// merge block id. That should be the current premerge block, if it exists,
// or otherwise the declared merge block.
//
// This is another way to think about it:
// If there is a premerge, then there are three cases:
// - premerge_head is different from the true_head and false_head:
// - Premerge comes last. In effect, move the selection merge up
// to where the premerge begins.
// - premerge_head is the same as the false_head
// - This is really an if-then without an else clause.
// Move the merge up to where the premerge is.
// - premerge_head is the same as the true_head
// - This is really an if-else without an then clause.
// Emit it as: if (cond) {} else {....}
// Move the merge up to where the premerge is.
const uint32_t intended_merge =
premerge_head ? premerge_head->id : construct->end_id;
// then-clause:
// If true_head exists:
// spans from true head to the earlier of the false head (if it exists)
// or the selection merge.
// Otherwise:
// ends at from the false head (if it exists), otherwise the selection
// end.
const uint32_t then_end = false_head ? false_head->id : intended_merge;
// else-clause:
// ends at the premerge head (if it exists) or at the selection end.
const uint32_t else_end = premerge_head ? premerge_head->id : intended_merge;
// Push statement blocks for the then-clause and the else-clause.
// But make sure we do it in the right order.
auto push_then = [this, if_stmt, then_end, construct]() {
// Push the then clause onto the stack.
PushNewStatementBlock(construct, then_end, [if_stmt](StatementBlock* s) {
// The "then" consists of the statement list
// from the top of statments stack, without an
// elseif condition.
if_stmt->set_body(std::move(s->statements));
});
};
auto push_else = [this, if_stmt, else_end, construct]() {
// Push the else clause onto the stack first.
PushNewStatementBlock(construct, else_end, [if_stmt](StatementBlock* s) {
// The "else" consists of the statement list from the top of statments
// stack, without an elseif condition.
ast::ElseStatementList else_stmts;
else_stmts.emplace_back(std::make_unique<ast::ElseStatement>(
nullptr, std::move(s->statements)));
if_stmt->set_else_statements(std::move(else_stmts));
});
};
if (GetBlockInfo(else_end)->pos < GetBlockInfo(then_end)->pos) {
// Process the else-clause first. The then-clause will be empty so avoid
// pushing onto the stack at all.
push_else();
} else {
// Blocks for the then-clause appear before blocks for the else-clause.
// So push the else-clause handling onto the stack first. The else-clause
// might be empty, but this works anyway.
push_else();
push_then();
}
return success();
}
bool FunctionEmitter::EmitNormalTerminator(const BlockInfo&) {
// TODO(dneto): emit fallthrough, break, continue, return, kill
return true;
}
bool FunctionEmitter::EmitStatementsInBasicBlock(const BlockInfo& block_info,
bool* already_emitted) {
if (*already_emitted) {
// Only emit this part of the basic block once.
return true;
}
const spvtools::opt::BasicBlock& bb = *(block_info.basic_block);
const auto* terminator = bb.terminator(); const auto* terminator = bb.terminator();
const auto* merge = bb.GetMergeInst(); // Might be nullptr const auto* merge = bb.GetMergeInst(); // Might be nullptr
// Emit regular statements. // Emit regular statements.
@ -1447,7 +1733,7 @@ bool FunctionEmitter::EmitStatementsInBasicBlock(
return false; return false;
} }
} }
// TODO(dneto): Handle the terminator *already_emitted = true;
return true; return true;
} }

View File

@ -15,6 +15,7 @@
#ifndef SRC_READER_SPIRV_FUNCTION_H_ #ifndef SRC_READER_SPIRV_FUNCTION_H_
#define SRC_READER_SPIRV_FUNCTION_H_ #define SRC_READER_SPIRV_FUNCTION_H_
#include <functional>
#include <memory> #include <memory>
#include <ostream> #include <ostream>
#include <unordered_map> #include <unordered_map>
@ -27,8 +28,10 @@
#include "source/opt/instruction.h" #include "source/opt/instruction.h"
#include "source/opt/ir_context.h" #include "source/opt/ir_context.h"
#include "source/opt/type_manager.h" #include "source/opt/type_manager.h"
#include "src/ast/case_statement.h"
#include "src/ast/expression.h" #include "src/ast/expression.h"
#include "src/ast/module.h" #include "src/ast/module.h"
#include "src/ast/statement.h"
#include "src/reader/spirv/construct.h" #include "src/reader/spirv/construct.h"
#include "src/reader/spirv/fail_stream.h" #include "src/reader/spirv/fail_stream.h"
#include "src/reader/spirv/namer.h" #include "src/reader/spirv/namer.h"
@ -138,7 +141,21 @@ struct BlockInfo {
const Construct* premerge_head_for = nullptr; const Construct* premerge_head_for = nullptr;
/// The construct for which this block is the false head, and that construct /// The construct for which this block is the false head, and that construct
/// does not have a true head. /// does not have a true head.
/// TODO(dneto): I think we can remove |exclusive_false_head_for|
const Construct* exclusive_false_head_for = nullptr; const Construct* exclusive_false_head_for = nullptr;
/// If not null, then this block is an if-selection header, and |true_head| is
/// the target of the true branch on the OpBranchConditional.
/// In particular, true_head->true_head_for == this
const BlockInfo* true_head = nullptr;
/// If not null, then this block is an if-selection header, and |false_head|
/// is the target of the false branch on the OpBranchConditional.
/// In particular, false_head->false_head_for == this
const BlockInfo* false_head = nullptr;
/// If not null, then this block is an if-selection header, and when following
/// the flow via the true and false branches, control first reconverges at
/// |premerge_head|, and |premerge_head| is still inside the if-selection.
/// In particular, premerge_head->premerge_head_for == this
const BlockInfo* premerge_head = nullptr;
}; };
inline std::ostream& operator<<(std::ostream& o, const BlockInfo& bi) { inline std::ostream& operator<<(std::ostream& o, const BlockInfo& bi) {
@ -268,10 +285,35 @@ class FunctionEmitter {
/// @returns false if emission failed. /// @returns false if emission failed.
bool EmitFunctionBodyStatements(); bool EmitFunctionBodyStatements();
/// Emits a basic block /// Emits a basic block.
/// @param bb internal representation of the basic block /// @param block_info the block to emit
/// @returns false if emission failed. /// @returns false if emission failed.
bool EmitStatementsInBasicBlock(const spvtools::opt::BasicBlock& bb); bool EmitBasicBlock(const BlockInfo& block_info);
/// Emits an IfStatement, including its condition expression, and sets
/// up the statement stack to accumulate subsequent basic blocks into
/// the "then" and "else" clauses.
/// @param block_info the if-selection header block
/// @returns false if emission failed.
bool EmitIfStart(const BlockInfo& block_info);
/// Emits the non-control-flow parts of a basic block, but only once.
/// The |already_emitted| parameter indicates whether the code has already
/// been emitted, and is used to signal that this invocation actually emitted
/// it.
/// @param block_info the block to emit
/// @param already_emitted the block to emit
/// @returns false if the code had not yet been emitted, but emission failed
bool EmitStatementsInBasicBlock(const BlockInfo& block_info,
bool* already_emitted);
/// Emits code for terminators, but that aren't part of entering or
/// resolving structured control flow. That is, if the basic block
/// terminator calls for it, emit the fallthrough, break, continue, return,
/// or kill commands.
/// @param block_info the block with the terminator to emit (if any)
/// @returns false if emission failed
bool EmitNormalTerminator(const BlockInfo& block_info);
/// Emits a normal instruction: not a terminator, label, or variable /// Emits a normal instruction: not a terminator, label, or variable
/// declaration. /// declaration.
@ -347,7 +389,41 @@ class FunctionEmitter {
BlockInfo* HeaderIfBreakable(const Construct* c); BlockInfo* HeaderIfBreakable(const Construct* c);
/// Appends a new statement to the top of the statement stack. /// Appends a new statement to the top of the statement stack.
void AddStatement(std::unique_ptr<ast::Statement> statement); /// @param statement the new statement
/// @returns a pointer to the statement.
ast::Statement* AddStatement(std::unique_ptr<ast::Statement> statement);
/// @returns the last statetment in the top of the statement stack.
ast::Statement* LastStatement();
struct StatementBlock;
using CompletionAction = std::function<void(StatementBlock*)>;
// A StatementBlock represents a braced-list of statements while it is being
// constructed.
struct StatementBlock {
// The construct to which this construct constributes.
const Construct* construct;
// The ID of the block at which the completion action should be triggerd
// and this statement block discarded. This is often the |end_id| of
// |construct| itself.
uint32_t end_id;
// The completion action finishes processing this statement block.
CompletionAction completion_action;
// Only one of |statements| or |cases| is active.
// The list of statements being built.
ast::StatementList statements;
// The list of cases being built, for a switch.
ast::CaseStatementList cases;
};
/// Pushes an empty statement block onto the statements stack.
/// @param action the completion action for this block
void PushNewStatementBlock(const Construct* construct,
uint32_t end_id,
CompletionAction action);
ParserImpl& parser_impl_; ParserImpl& parser_impl_;
ast::Module& ast_module_; ast::Module& ast_module_;
@ -362,7 +438,9 @@ class FunctionEmitter {
// A stack of statement lists. Each list is contained in a construct in // A stack of statement lists. Each list is contained in a construct in
// the next deeper element of stack. The 0th entry represents the statements // the next deeper element of stack. The 0th entry represents the statements
// for the entire function. This stack is never empty. // for the entire function. This stack is never empty.
std::vector<ast::StatementList> statements_stack_; // The |construct| member for the 0th element is only valid during the
// lifetime of the EmitFunctionBodyStatements method.
std::vector<StatementBlock> statements_stack_;
// The set of IDs that have already had an identifier name generated for it. // The set of IDs that have already had an identifier name generated for it.
std::unordered_set<uint32_t> identifier_values_; std::unordered_set<uint32_t> identifier_values_;

View File

@ -27,6 +27,9 @@ namespace reader {
namespace spirv { namespace spirv {
namespace { namespace {
using ::testing::Eq;
using ::testing::HasSubstr;
std::string Dump(const std::vector<uint32_t>& v) { std::string Dump(const std::vector<uint32_t>& v) {
std::ostringstream o; std::ostringstream o;
o << "{"; o << "{";
@ -46,16 +49,29 @@ std::string CommonTypes() {
OpCapability Shader OpCapability Shader
OpMemoryModel Logical Simple OpMemoryModel Logical Simple
OpName %var "var"
%void = OpTypeVoid %void = OpTypeVoid
%voidfn = OpTypeFunction %void %voidfn = OpTypeFunction %void
%bool = OpTypeBool %bool = OpTypeBool
%cond = OpUndef %bool %cond = OpConstantNull %bool
%cond2 = OpUndef %bool %cond2 = OpConstantTrue %bool
%cond3 = OpUndef %bool %cond3 = OpConstantFalse %bool
%uint = OpTypeInt 32 0 %uint = OpTypeInt 32 0
%selector = OpUndef %uint %selector = OpConstant %uint 42
%uint_0 = OpConstant %uint 0
%uint_1 = OpConstant %uint 1
%uint_2 = OpConstant %uint 2
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_5 = OpConstant %uint 5
%uint_6 = OpConstant %uint 6
%ptr_Private_uint = OpTypePointer Private %uint
%var = OpVariable %ptr_Private_uint Private
%999 = OpConstant %uint 999 %999 = OpConstant %uint 999
)"; )";
@ -677,7 +693,7 @@ TEST_F(SpvParserTest, RegisterMerges_BadMergeBlock) {
fe.RegisterBasicBlocks(); fe.RegisterBasicBlocks();
EXPECT_FALSE(fe.RegisterMerges()); EXPECT_FALSE(fe.RegisterMerges());
EXPECT_THAT(p->error(), EXPECT_THAT(p->error(),
Eq("Structured header block 10 declares invalid merge block 1")); Eq("Structured header block 10 declares invalid merge block 2"));
} }
TEST_F(SpvParserTest, RegisterMerges_HeaderIsItsOwnMerge) { TEST_F(SpvParserTest, RegisterMerges_HeaderIsItsOwnMerge) {
@ -6642,7 +6658,6 @@ TEST_F(SpvParserTest,
OpFunctionEnd OpFunctionEnd
)"; )";
auto* p = parser(test::Assemble(assembly)); auto* p = parser(test::Assemble(assembly));
std::cout << assembly;
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error(); ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100)); FunctionEmitter fe(p, *spirv_function(100));
EXPECT_FALSE(FlowFindIfSelectionInternalHeaders(&fe)); EXPECT_FALSE(FlowFindIfSelectionInternalHeaders(&fe));
@ -6729,6 +6744,488 @@ TEST_F(SpvParserTest, DISABLED_BlockIsContinueForMoreThanOneHeader) {
)"; )";
} }
TEST_F(SpvParserTest, EmitBody_If_Empty) {
auto* p = parser(test::Assemble(CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpSelectionMerge %99 None
OpBranchConditional %cond %99 %99
%99 = OpLabel
OpReturn
OpFunctionEnd
)"));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(fe.EmitBody()) << p->error();
EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(If{
(
ScalarConstructor{false}
)
{
}
}
Else{
{
}
}
)"));
}
TEST_F(SpvParserTest, EmitBody_If_Then_NoElse) {
auto* p = parser(test::Assemble(CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpStore %var %uint_0
OpSelectionMerge %99 None
OpBranchConditional %cond %30 %99
%99 = OpLabel
OpStore %var %999
OpReturn
%30 = OpLabel
OpStore %var %uint_1
OpBranch %99
OpFunctionEnd
)"));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(fe.EmitBody()) << p->error();
EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
Identifier{var}
ScalarConstructor{0}
}
If{
(
ScalarConstructor{false}
)
{
Assignment{
Identifier{var}
ScalarConstructor{1}
}
}
}
Else{
{
}
}
Assignment{
Identifier{var}
ScalarConstructor{999}
}
)"));
}
TEST_F(SpvParserTest, EmitBody_If_NoThen_Else) {
auto* p = parser(test::Assemble(CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpStore %var %uint_0
OpSelectionMerge %99 None
OpBranchConditional %cond %99 %30
%99 = OpLabel
OpStore %var %999
OpReturn
%30 = OpLabel
OpStore %var %uint_1
OpBranch %99
OpFunctionEnd
)"));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(fe.EmitBody()) << p->error();
EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
Identifier{var}
ScalarConstructor{0}
}
If{
(
ScalarConstructor{false}
)
{
}
}
Else{
{
Assignment{
Identifier{var}
ScalarConstructor{1}
}
}
}
Assignment{
Identifier{var}
ScalarConstructor{999}
}
)"));
}
TEST_F(SpvParserTest, EmitBody_If_Then_Else) {
auto* p = parser(test::Assemble(CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpStore %var %uint_0
OpSelectionMerge %99 None
OpBranchConditional %cond %30 %40
%99 = OpLabel
OpStore %var %999
OpReturn
%30 = OpLabel
OpStore %var %uint_1
OpBranch %99
%40 = OpLabel
OpStore %var %uint_2
OpBranch %99
OpFunctionEnd
)"));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(fe.EmitBody()) << p->error();
EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
Identifier{var}
ScalarConstructor{0}
}
If{
(
ScalarConstructor{false}
)
{
Assignment{
Identifier{var}
ScalarConstructor{1}
}
}
}
Else{
{
Assignment{
Identifier{var}
ScalarConstructor{2}
}
}
}
Assignment{
Identifier{var}
ScalarConstructor{999}
}
)"));
}
TEST_F(SpvParserTest, EmitBody_If_Then_Else_Premerge) {
// TODO(dneto): This should get an extra if(true) around
// the premerge code.
// See https://bugs.chromium.org/p/tint/issues/detail?id=82
auto* p = parser(test::Assemble(CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpStore %var %uint_0
OpSelectionMerge %99 None
OpBranchConditional %cond %30 %40
%80 = OpLabel ; premerge
OpStore %var %uint_3
OpBranch %99
%99 = OpLabel
OpStore %var %999
OpReturn
%30 = OpLabel
OpStore %var %uint_1
OpBranch %80
%40 = OpLabel
OpStore %var %uint_2
OpBranch %80
OpFunctionEnd
)"));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(fe.EmitBody()) << p->error();
EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
Identifier{var}
ScalarConstructor{0}
}
If{
(
ScalarConstructor{false}
)
{
Assignment{
Identifier{var}
ScalarConstructor{1}
}
}
}
Else{
{
Assignment{
Identifier{var}
ScalarConstructor{2}
}
}
}
Assignment{
Identifier{var}
ScalarConstructor{3}
}
Assignment{
Identifier{var}
ScalarConstructor{999}
}
)"));
}
TEST_F(SpvParserTest, EmitBody_If_Then_Premerge) {
// The premerge *is* the else.
auto* p = parser(test::Assemble(CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpStore %var %uint_0
OpSelectionMerge %99 None
OpBranchConditional %cond %30 %80
%80 = OpLabel ; premerge
OpStore %var %uint_3
OpBranch %99
%99 = OpLabel
OpStore %var %999
OpReturn
%30 = OpLabel
OpStore %var %uint_1
OpBranch %80
OpFunctionEnd
)"));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(fe.EmitBody()) << p->error();
EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
Identifier{var}
ScalarConstructor{0}
}
If{
(
ScalarConstructor{false}
)
{
Assignment{
Identifier{var}
ScalarConstructor{1}
}
}
}
Else{
{
}
}
Assignment{
Identifier{var}
ScalarConstructor{3}
}
Assignment{
Identifier{var}
ScalarConstructor{999}
}
)"));
}
TEST_F(SpvParserTest, EmitBody_If_Else_Premerge) {
// The premerge *is* the then-clause.
auto* p = parser(test::Assemble(CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpStore %var %uint_0
OpSelectionMerge %99 None
OpBranchConditional %cond %80 %30
%80 = OpLabel ; premerge
OpStore %var %uint_3
OpBranch %99
%99 = OpLabel
OpStore %var %999
OpReturn
%30 = OpLabel
OpStore %var %uint_1
OpBranch %80
OpFunctionEnd
)"));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(fe.EmitBody()) << p->error();
EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
Identifier{var}
ScalarConstructor{0}
}
If{
(
ScalarConstructor{false}
)
{
}
}
Else{
{
Assignment{
Identifier{var}
ScalarConstructor{1}
}
}
}
Assignment{
Identifier{var}
ScalarConstructor{3}
}
Assignment{
Identifier{var}
ScalarConstructor{999}
}
)"));
}
TEST_F(SpvParserTest, EmitBody_If_Nest_If) {
auto* p = parser(test::Assemble(CommonTypes() + R"(
%100 = OpFunction %void None %voidfn
%10 = OpLabel
OpStore %var %uint_0
OpSelectionMerge %99 None
OpBranchConditional %cond %30 %40
%30 = OpLabel ;; inner if #1
OpStore %var %uint_1
OpSelectionMerge %39 None
OpBranchConditional %cond2 %33 %39
%33 = OpLabel
OpStore %var %uint_2
OpBranch %39
%39 = OpLabel ;; inner merge
OpStore %var %uint_3
OpBranch %99
%40 = OpLabel ;; inner if #2
OpStore %var %uint_4
OpSelectionMerge %49 None
OpBranchConditional %cond2 %49 %43
%43 = OpLabel
OpStore %var %uint_5
OpBranch %49
%49 = OpLabel ;; 2nd inner merge
OpStore %var %uint_6
OpBranch %99
%99 = OpLabel
OpStore %var %999
OpReturn
OpFunctionEnd
)"));
ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
FunctionEmitter fe(p, *spirv_function(100));
EXPECT_TRUE(fe.EmitBody()) << p->error();
EXPECT_THAT(ToString(fe.ast_body()), Eq(R"(Assignment{
Identifier{var}
ScalarConstructor{0}
}
If{
(
ScalarConstructor{false}
)
{
Assignment{
Identifier{var}
ScalarConstructor{1}
}
If{
(
ScalarConstructor{true}
)
{
Assignment{
Identifier{var}
ScalarConstructor{2}
}
}
}
Else{
{
}
}
Assignment{
Identifier{var}
ScalarConstructor{3}
}
}
}
Else{
{
Assignment{
Identifier{var}
ScalarConstructor{4}
}
If{
(
ScalarConstructor{true}
)
{
}
}
Else{
{
Assignment{
Identifier{var}
ScalarConstructor{5}
}
}
}
Assignment{
Identifier{var}
ScalarConstructor{6}
}
}
}
Assignment{
Identifier{var}
ScalarConstructor{999}
}
)"));
}
} // namespace } // namespace
} // namespace spirv } // namespace spirv
} // namespace reader } // namespace reader