[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:
parent
e2be489756
commit
468c26b233
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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_;
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue