spirv-writer: Fix termination of basic blocks
There are a few places where a branch or return is created, conditionally on whether a terminator was the last thing seen. The goal is to generate a SPIR-V basic block terminator exactly when needed, and to avoid generating a branch or return immediately after a prior terminator. Previously, the decision was based on the last thing seen in the AST. But we should instead check the emitted SPIR-V instead. This fixes cases such as a break or return inside an else-if. That's because an if/elseif is actually a selection inside a selection. Looking at the AST only works when trying to terminate the *inside* selection. In the outer recursive call, the last AST node is no longer a terminator, and we would skip generating the branch to the merge block. Fixed: tint:1315 Change-Id: I6b886ce85d1d681f2063997e469e0c1b4e5973a2 Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/73480 Kokoro: Kokoro <noreply+kokoro@google.com> Auto-Submit: David Neto <dneto@google.com> Reviewed-by: James Price <jrprice@google.com> Commit-Queue: James Price <jrprice@google.com>
This commit is contained in:
parent
a9d6c34d86
commit
66e7569e15
|
@ -96,23 +96,6 @@ bool LastIsFallthrough(const ast::BlockStatement* stmts) {
|
||||||
return !stmts->Empty() && stmts->Last()->Is<ast::FallthroughStatement>();
|
return !stmts->Empty() && stmts->Last()->Is<ast::FallthroughStatement>();
|
||||||
}
|
}
|
||||||
|
|
||||||
// A terminator is anything which will cause a SPIR-V terminator to be emitted.
|
|
||||||
// This means things like breaks, fallthroughs and continues which all emit an
|
|
||||||
// OpBranch or return for the OpReturn emission.
|
|
||||||
bool LastIsTerminator(const ast::BlockStatement* stmts) {
|
|
||||||
if (IsAnyOf<ast::BreakStatement, ast::ContinueStatement,
|
|
||||||
ast::DiscardStatement, ast::ReturnStatement,
|
|
||||||
ast::FallthroughStatement>(stmts->Last())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto* block = As<ast::BlockStatement>(stmts->Last())) {
|
|
||||||
return LastIsTerminator(block);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the matrix type that is `type` or that is wrapped by
|
/// Returns the matrix type that is `type` or that is wrapped by
|
||||||
/// one or more levels of an arrays inside of `type`.
|
/// one or more levels of an arrays inside of `type`.
|
||||||
/// @param type the given type, which must not be null
|
/// @param type the given type, which must not be null
|
||||||
|
@ -650,7 +633,7 @@ bool Builder::GenerateFunction(const ast::Function* func_ast) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!LastIsTerminator(func_ast->body)) {
|
if (InsideBasicBlock()) {
|
||||||
if (func->ReturnType()->Is<sem::Void>()) {
|
if (func->ReturnType()->Is<sem::Void>()) {
|
||||||
push_function_inst(spv::Op::OpReturn, {});
|
push_function_inst(spv::Op::OpReturn, {});
|
||||||
} else {
|
} else {
|
||||||
|
@ -3500,7 +3483,7 @@ bool Builder::GenerateConditionalBlock(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// We only branch if the last element of the body didn't already branch.
|
// We only branch if the last element of the body didn't already branch.
|
||||||
if (!LastIsTerminator(true_body)) {
|
if (InsideBasicBlock()) {
|
||||||
if (!push_function_inst(spv::Op::OpBranch,
|
if (!push_function_inst(spv::Op::OpBranch,
|
||||||
{Operand::Int(merge_block_id)})) {
|
{Operand::Int(merge_block_id)})) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -3525,7 +3508,7 @@ bool Builder::GenerateConditionalBlock(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!LastIsTerminator(else_stmt->body)) {
|
if (InsideBasicBlock()) {
|
||||||
if (!push_function_inst(spv::Op::OpBranch,
|
if (!push_function_inst(spv::Op::OpBranch,
|
||||||
{Operand::Int(merge_block_id)})) {
|
{Operand::Int(merge_block_id)})) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -3673,7 +3656,7 @@ bool Builder::GenerateSwitchStatement(const ast::SwitchStatement* stmt) {
|
||||||
{Operand::Int(case_ids[i + 1])})) {
|
{Operand::Int(case_ids[i + 1])})) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else if (!LastIsTerminator(item->body)) {
|
} else if (InsideBasicBlock()) {
|
||||||
if (!push_function_inst(spv::Op::OpBranch,
|
if (!push_function_inst(spv::Op::OpBranch,
|
||||||
{Operand::Int(merge_block_id)})) {
|
{Operand::Int(merge_block_id)})) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -3765,7 +3748,7 @@ bool Builder::GenerateLoopStatement(const ast::LoopStatement* stmt) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We only branch if the last element of the body didn't already branch.
|
// We only branch if the last element of the body didn't already branch.
|
||||||
if (!LastIsTerminator(stmt->body)) {
|
if (InsideBasicBlock()) {
|
||||||
if (!push_function_inst(spv::Op::OpBranch,
|
if (!push_function_inst(spv::Op::OpBranch,
|
||||||
{Operand::Int(continue_block_id)})) {
|
{Operand::Int(continue_block_id)})) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -4414,6 +4397,34 @@ bool Builder::push_function_inst(spv::Op op, const OperandList& operands) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Builder::InsideBasicBlock() const {
|
||||||
|
if (functions_.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const auto& instructions = functions_.back().instructions();
|
||||||
|
if (instructions.empty()) {
|
||||||
|
// The Function object does not explicitly represent its entry block
|
||||||
|
// label. So return *true* because an empty list means the only
|
||||||
|
// thing in the function is that entry block label.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const auto& inst = instructions.back();
|
||||||
|
switch (inst.opcode()) {
|
||||||
|
case spv::Op::OpBranch:
|
||||||
|
case spv::Op::OpBranchConditional:
|
||||||
|
case spv::Op::OpSwitch:
|
||||||
|
case spv::Op::OpReturn:
|
||||||
|
case spv::Op::OpReturnValue:
|
||||||
|
case spv::Op::OpUnreachable:
|
||||||
|
case spv::Op::OpKill:
|
||||||
|
case spv::Op::OpTerminateInvocation:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Builder::ContinuingInfo::ContinuingInfo(
|
Builder::ContinuingInfo::ContinuingInfo(
|
||||||
const ast::Statement* the_last_statement,
|
const ast::Statement* the_last_statement,
|
||||||
uint32_t loop_id,
|
uint32_t loop_id,
|
||||||
|
|
|
@ -216,6 +216,10 @@ class Builder {
|
||||||
functions_.back().push_var(operands);
|
functions_.back().push_var(operands);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @returns true if the current instruction insertion point is
|
||||||
|
/// inside a basic block.
|
||||||
|
bool InsideBasicBlock() const;
|
||||||
|
|
||||||
/// Converts a storage class to a SPIR-V storage class.
|
/// Converts a storage class to a SPIR-V storage class.
|
||||||
/// @param klass the storage class to convert
|
/// @param klass the storage class to convert
|
||||||
/// @returns the SPIR-V storage class or SpvStorageClassMax on error.
|
/// @returns the SPIR-V storage class or SpvStorageClassMax on error.
|
||||||
|
|
|
@ -603,6 +603,91 @@ OpReturn
|
||||||
)");
|
)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(BuilderTest, If_ElseIf_WithReturn) {
|
||||||
|
// crbug.com/tint/1315
|
||||||
|
// if (false) {
|
||||||
|
// } else if (true) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
auto* if_stmt = If(Expr(false), Block(),
|
||||||
|
ast::ElseStatementList{Else(Expr(true), Block(Return()))});
|
||||||
|
auto* fn = Func("f", {}, ty.void_(), {if_stmt});
|
||||||
|
|
||||||
|
spirv::Builder& b = Build();
|
||||||
|
|
||||||
|
EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
|
||||||
|
EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
|
||||||
|
%1 = OpTypeFunction %2
|
||||||
|
%5 = OpTypeBool
|
||||||
|
%6 = OpConstantFalse %5
|
||||||
|
%10 = OpConstantTrue %5
|
||||||
|
)");
|
||||||
|
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
|
||||||
|
R"(OpSelectionMerge %7 None
|
||||||
|
OpBranchConditional %6 %8 %9
|
||||||
|
%8 = OpLabel
|
||||||
|
OpBranch %7
|
||||||
|
%9 = OpLabel
|
||||||
|
OpSelectionMerge %11 None
|
||||||
|
OpBranchConditional %10 %12 %11
|
||||||
|
%12 = OpLabel
|
||||||
|
OpReturn
|
||||||
|
%11 = OpLabel
|
||||||
|
OpBranch %7
|
||||||
|
%7 = OpLabel
|
||||||
|
OpReturn
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(BuilderTest, Loop_If_ElseIf_WithBreak) {
|
||||||
|
// crbug.com/tint/1315
|
||||||
|
// loop {
|
||||||
|
// if (false) {
|
||||||
|
// } else if (true) {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
auto* if_stmt = If(Expr(false), Block(),
|
||||||
|
ast::ElseStatementList{Else(Expr(true), Block(Break()))});
|
||||||
|
auto* fn = Func("f", {}, ty.void_(), {Loop(Block(if_stmt))});
|
||||||
|
|
||||||
|
spirv::Builder& b = Build();
|
||||||
|
|
||||||
|
EXPECT_TRUE(b.GenerateFunction(fn)) << b.error();
|
||||||
|
EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeVoid
|
||||||
|
%1 = OpTypeFunction %2
|
||||||
|
%9 = OpTypeBool
|
||||||
|
%10 = OpConstantFalse %9
|
||||||
|
%14 = OpConstantTrue %9
|
||||||
|
)");
|
||||||
|
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
|
||||||
|
R"(OpBranch %5
|
||||||
|
%5 = OpLabel
|
||||||
|
OpLoopMerge %6 %7 None
|
||||||
|
OpBranch %8
|
||||||
|
%8 = OpLabel
|
||||||
|
OpSelectionMerge %11 None
|
||||||
|
OpBranchConditional %10 %12 %13
|
||||||
|
%12 = OpLabel
|
||||||
|
OpBranch %11
|
||||||
|
%13 = OpLabel
|
||||||
|
OpSelectionMerge %15 None
|
||||||
|
OpBranchConditional %14 %16 %15
|
||||||
|
%16 = OpLabel
|
||||||
|
OpBranch %6
|
||||||
|
%15 = OpLabel
|
||||||
|
OpBranch %11
|
||||||
|
%11 = OpLabel
|
||||||
|
OpBranch %7
|
||||||
|
%7 = OpLabel
|
||||||
|
OpBranch %5
|
||||||
|
%6 = OpLabel
|
||||||
|
OpReturn
|
||||||
|
)");
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
} // namespace spirv
|
} // namespace spirv
|
||||||
} // namespace writer
|
} // namespace writer
|
||||||
|
|
|
@ -32,7 +32,7 @@ class Function {
|
||||||
|
|
||||||
/// Constructor
|
/// Constructor
|
||||||
/// @param declaration the function declaration
|
/// @param declaration the function declaration
|
||||||
/// @param label_op the operand for the initial function label
|
/// @param label_op the operand for function's entry block label
|
||||||
/// @param params the function parameters
|
/// @param params the function parameters
|
||||||
Function(const Instruction& declaration,
|
Function(const Instruction& declaration,
|
||||||
const Operand& label_op,
|
const Operand& label_op,
|
||||||
|
@ -49,7 +49,7 @@ class Function {
|
||||||
/// @returns the declaration
|
/// @returns the declaration
|
||||||
const Instruction& declaration() const { return declaration_; }
|
const Instruction& declaration() const { return declaration_; }
|
||||||
|
|
||||||
/// @returns the function label id
|
/// @returns the label ID for the function entry block
|
||||||
uint32_t label_id() const { return label_op_.to_i(); }
|
uint32_t label_id() const { return label_op_.to_i(); }
|
||||||
|
|
||||||
/// Adds an instruction to the instruction list
|
/// Adds an instruction to the instruction list
|
||||||
|
|
Loading…
Reference in New Issue