[spirv-reader] Emit non-header OpBranchConditional

This emits the equivalent of break-if, break-unless, continue-if,
continue-unless.  But we do it via a regular if-then-else.

Adds a test matrix.
Adds all required tests except for those needing OpSwitch.

Bug: tint:3
Change-Id: I960a40aa00f95f394a92a099c8b12104010ad49f
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/22603
Reviewed-by: dan sinclair <dsinclair@google.com>
This commit is contained in:
David Neto 2020-06-08 18:26:03 +00:00 committed by dan sinclair
parent bff3b78313
commit b0d308c9fe
3 changed files with 2449 additions and 44 deletions

View File

@ -1656,6 +1656,12 @@ bool FunctionEmitter::EmitBasicBlock(const BlockInfo& block_info) {
// Have we emitted the statements for this block?
bool emitted = false;
// When entering an if-selection or switch-selection, we will emit the WGSL
// construct to cause the divergent branching. But otherwise, we will
// emit a "normal" block terminator, which occurs at the end of this method.
bool has_normal_terminator = true;
for (auto iter = entering_constructs.rbegin();
iter != entering_constructs.rend(); ++iter) {
const Construct* construct = *iter;
@ -1695,9 +1701,11 @@ bool FunctionEmitter::EmitBasicBlock(const BlockInfo& block_info) {
if (!EmitIfStart(block_info)) {
return false;
}
has_normal_terminator = false;
break;
case Construct::kSwitchSelection:
has_normal_terminator = false;
return Fail() << "unhandled: switch construct";
}
}
@ -1708,8 +1716,10 @@ bool FunctionEmitter::EmitBasicBlock(const BlockInfo& block_info) {
return false;
}
if (!EmitNormalTerminator(block_info)) {
return false;
if (has_normal_terminator) {
if (!EmitNormalTerminator(block_info)) {
return false;
}
}
return success();
}
@ -1864,10 +1874,54 @@ bool FunctionEmitter::EmitNormalTerminator(const BlockInfo& block_info) {
AddStatement(MakeBranch(block_info, *GetBlockInfo(dest_id)));
return true;
}
case SpvOpBranchConditional: {
// If both destinations are the same, then do the same as we would
// for an unconditional branch (OpBranch).
const auto true_dest = terminator.GetSingleWordInOperand(1);
const auto false_dest = terminator.GetSingleWordInOperand(2);
if (true_dest == false_dest) {
// This is like an uncondtional branch.
AddStatement(MakeBranch(block_info, *GetBlockInfo(true_dest)));
return true;
}
const EdgeKind true_kind = block_info.succ_edge.find(true_dest)->second;
const EdgeKind false_kind = block_info.succ_edge.find(false_dest)->second;
auto* const true_info = GetBlockInfo(true_dest);
auto* const false_info = GetBlockInfo(false_dest);
auto cond = MakeExpression(terminator.GetSingleWordInOperand(0)).expr;
// We have two distinct destinations. But we only get here if this
// is a normal terminator; in particular the source block is *not* the
// start of an if-selection or a switch-selection. So at most one branch
// is a kForward, kCaseFallThrough, or kIfBreak.
// The fallthrough case is special because WGSL requires the fallthrough
// statement to be last in the case clause.
if (true_kind == EdgeKind::kCaseFallThrough ||
false_kind == EdgeKind::kCaseFallThrough) {
return Fail() << "fallthrough is unhandled";
}
// At this point, at most one edge is kForward or kIfBreak.
// Emit an 'if' statement to express the *other* branch as a conditional
// break or continue. Either or both of these could be nullptr.
// (A nullptr is generated for kIfBreak, kForward, or kBack.)
auto true_branch = MakeBranch(block_info, *true_info);
auto false_branch = MakeBranch(block_info, *false_info);
AddStatement(MakeSimpleIf(std::move(cond), std::move(true_branch),
std::move(false_branch)));
return true;
}
case SpvOpSwitch:
// TODO(dneto)
break;
default:
break;
}
// TODO(dneto): emit fallthrough, break, continue
// TODO(dneto): emit fallthrough
return success();
}
@ -1903,6 +1957,31 @@ std::unique_ptr<ast::Statement> FunctionEmitter::MakeBranch(
return {nullptr};
}
std::unique_ptr<ast::Statement> FunctionEmitter::MakeSimpleIf(
std::unique_ptr<ast::Expression> condition,
std::unique_ptr<ast::Statement> then_stmt,
std::unique_ptr<ast::Statement> else_stmt) const {
if ((then_stmt == nullptr) && (else_stmt == nullptr)) {
return nullptr;
}
auto if_stmt = std::make_unique<ast::IfStatement>();
if_stmt->set_condition(std::move(condition));
if (then_stmt != nullptr) {
ast::StatementList stmts;
stmts.emplace_back(std::move(then_stmt));
if_stmt->set_body(std::move(stmts));
}
if (else_stmt != nullptr) {
ast::StatementList stmts;
stmts.emplace_back(std::move(else_stmt));
ast::ElseStatementList else_stmts;
else_stmts.emplace_back(
std::make_unique<ast::ElseStatement>(nullptr, std::move(stmts)));
if_stmt->set_else_statements(std::move(else_stmts));
}
return if_stmt;
}
bool FunctionEmitter::EmitStatementsInBasicBlock(const BlockInfo& block_info,
bool* already_emitted) {
if (*already_emitted) {

View File

@ -341,12 +341,25 @@ class FunctionEmitter {
/// Returns a new statement to represent the given branch representing a
/// "normal" terminator, as in the sense of EmitNormalTerminator. If no
/// WGSL statement is required, the statement will nullptr.
/// WGSL statement is required, the statement will be nullptr.
/// @param src_info the source block
/// @param dest_info the destination block
/// @returns the new statement, or a null statement
std::unique_ptr<ast::Statement> MakeBranch(const BlockInfo& src_info,
const BlockInfo& dest_info) const;
/// Returns a new if statement with the given statements as the then-clause
/// and the else-clause. Either or both clauses might be nullptr. If both
/// are nullptr, then don't make a new statement and instead return nullptr.
/// @param condition the branching condition
/// @param then_stmt the statement for the then clause of the if, or nullptr
/// @param else_stmt the statement for the else clause of the if, or nullptr
/// @returns the new statement, or nullptr
std::unique_ptr<ast::Statement> MakeSimpleIf(
std::unique_ptr<ast::Expression> condition,
std::unique_ptr<ast::Statement> then_stmt,
std::unique_ptr<ast::Statement> else_stmt) const;
/// Emits a normal instruction: not a terminator, label, or variable
/// declaration.
/// @param inst the instruction

File diff suppressed because it is too large Load Diff