[spirv-writer] Add initial loop support.

This CL adds the initial generation of a loop construct into the
spirv-writer. This does not support break or continue yet.

Bug: tint:5
Change-Id: I41f5d9b634a1a4120f880c4143feacc7e58e2147
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/19620
Reviewed-by: David Neto <dneto@google.com>
This commit is contained in:
dan sinclair 2020-04-20 14:07:29 +00:00
parent 340a51e787
commit 6866cb7677
5 changed files with 220 additions and 0 deletions

View File

@ -558,6 +558,7 @@ source_set("tint_unittests_spv_writer_src") {
"src/writer/spirv/builder_ident_expression_test.cc",
"src/writer/spirv/builder_if_test.cc",
"src/writer/spirv/builder_literal_test.cc",
"src/writer/spirv/builder_loop_test.cc",
"src/writer/spirv/builder_return_test.cc",
"src/writer/spirv/builder_test.cc",
"src/writer/spirv/builder_type_test.cc",

View File

@ -431,6 +431,7 @@ if(${TINT_BUILD_SPV_WRITER})
writer/spirv/builder_ident_expression_test.cc
writer/spirv/builder_if_test.cc
writer/spirv/builder_literal_test.cc
writer/spirv/builder_loop_test.cc
writer/spirv/builder_return_test.cc
writer/spirv/builder_test.cc
writer/spirv/builder_type_test.cc

View File

@ -30,6 +30,7 @@
#include "src/ast/if_statement.h"
#include "src/ast/int_literal.h"
#include "src/ast/location_decoration.h"
#include "src/ast/loop_statement.h"
#include "src/ast/return_statement.h"
#include "src/ast/scalar_constructor_expression.h"
#include "src/ast/set_decoration.h"
@ -808,6 +809,43 @@ bool Builder::GenerateReturnStatement(ast::ReturnStatement* stmt) {
return true;
}
bool Builder::GenerateLoopStatement(ast::LoopStatement* stmt) {
auto loop_header = result_op();
auto loop_header_id = loop_header.to_i();
push_function_inst(spv::Op::OpBranch, {Operand::Int(loop_header_id)});
push_function_inst(spv::Op::OpLabel, {loop_header});
auto merge_block = result_op();
auto merge_block_id = merge_block.to_i();
auto continue_block = result_op();
auto continue_block_id = continue_block.to_i();
auto body_block = result_op();
auto body_block_id = body_block.to_i();
push_function_inst(
spv::Op::OpLoopMerge,
{Operand::Int(merge_block_id), Operand::Int(continue_block_id),
Operand::Int(SpvLoopControlMaskNone)});
push_function_inst(spv::Op::OpBranch, {Operand::Int(body_block_id)});
push_function_inst(spv::Op::OpLabel, {body_block});
if (!GenerateStatementList(stmt->body())) {
return false;
}
push_function_inst(spv::Op::OpBranch, {Operand::Int(continue_block_id)});
push_function_inst(spv::Op::OpLabel, {continue_block});
if (!GenerateStatementList(stmt->continuing())) {
return false;
}
push_function_inst(spv::Op::OpBranch, {Operand::Int(loop_header_id)});
push_function_inst(spv::Op::OpLabel, {merge_block});
return true;
}
bool Builder::GenerateStatementList(const ast::StatementList& list) {
for (const auto& inst : list) {
if (!GenerateStatement(inst.get())) {
@ -824,6 +862,9 @@ bool Builder::GenerateStatement(ast::Statement* stmt) {
if (stmt->IsIf()) {
return GenerateIfStatement(stmt->AsIf());
}
if (stmt->IsLoop()) {
return GenerateLoopStatement(stmt->AsLoop());
}
if (stmt->IsReturn()) {
return GenerateReturnStatement(stmt->AsReturn());
}

View File

@ -202,6 +202,10 @@ class Builder {
/// @param expr the expression to generate
/// @returns the expression ID on success or 0 otherwise
uint32_t GenerateBinaryExpression(ast::BinaryExpression* expr);
/// Generates a loop statement
/// @param stmt the statement to generate
/// @returns true on successful generation
bool GenerateLoopStatement(ast::LoopStatement* stmt);
/// Generates a return statement
/// @param stmt the statement to generate
/// @returns true on success, false otherwise

View File

@ -0,0 +1,173 @@
// Copyright 2020 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <memory>
#include "gtest/gtest.h"
#include "src/ast/assignment_statement.h"
#include "src/ast/identifier_expression.h"
#include "src/ast/int_literal.h"
#include "src/ast/loop_statement.h"
#include "src/ast/scalar_constructor_expression.h"
#include "src/ast/type/i32_type.h"
#include "src/context.h"
#include "src/type_determiner.h"
#include "src/writer/spirv/builder.h"
#include "src/writer/spirv/spv_dump.h"
namespace tint {
namespace writer {
namespace spirv {
namespace {
using BuilderTest = testing::Test;
TEST_F(BuilderTest, Loop_Empty) {
// loop {
// }
ast::LoopStatement expr;
Context ctx;
TypeDeterminer td(&ctx);
ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
Builder b;
b.push_function(Function{});
EXPECT_TRUE(b.GenerateLoopStatement(&expr)) << b.error();
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
R"(OpBranch %1
%1 = OpLabel
OpLoopMerge %2 %3 None
OpBranch %4
%4 = OpLabel
OpBranch %3
%3 = OpLabel
OpBranch %1
%2 = OpLabel
)");
}
TEST_F(BuilderTest, Loop_WithoutContinuing) {
ast::type::I32Type i32;
// loop {
// v = 2;
// }
auto var =
std::make_unique<ast::Variable>("v", ast::StorageClass::kPrivate, &i32);
ast::StatementList body;
body.push_back(std::make_unique<ast::AssignmentStatement>(
std::make_unique<ast::IdentifierExpression>("v"),
std::make_unique<ast::ScalarConstructorExpression>(
std::make_unique<ast::IntLiteral>(&i32, 2))));
ast::StatementList continuing;
ast::LoopStatement expr(std::move(body), std::move(continuing));
Context ctx;
TypeDeterminer td(&ctx);
td.RegisterVariableForTesting(var.get());
ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
Builder b;
b.push_function(Function{});
ASSERT_TRUE(b.GenerateGlobalVariable(var.get())) << b.error();
EXPECT_TRUE(b.GenerateLoopStatement(&expr)) << b.error();
EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
%2 = OpTypePointer Private %3
%1 = OpVariable %2 Private
%8 = OpConstant %3 2
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
R"(OpBranch %4
%4 = OpLabel
OpLoopMerge %5 %6 None
OpBranch %7
%7 = OpLabel
OpStore %1 %8
OpBranch %6
%6 = OpLabel
OpBranch %4
%5 = OpLabel
)");
}
TEST_F(BuilderTest, Loop_WithContinuing) {
ast::type::I32Type i32;
// loop {
// a = 2;
// continuing {
// a = 3;
// }
// }
auto var =
std::make_unique<ast::Variable>("v", ast::StorageClass::kPrivate, &i32);
ast::StatementList body;
body.push_back(std::make_unique<ast::AssignmentStatement>(
std::make_unique<ast::IdentifierExpression>("v"),
std::make_unique<ast::ScalarConstructorExpression>(
std::make_unique<ast::IntLiteral>(&i32, 2))));
ast::StatementList continuing;
continuing.push_back(std::make_unique<ast::AssignmentStatement>(
std::make_unique<ast::IdentifierExpression>("v"),
std::make_unique<ast::ScalarConstructorExpression>(
std::make_unique<ast::IntLiteral>(&i32, 3))));
ast::LoopStatement expr(std::move(body), std::move(continuing));
Context ctx;
TypeDeterminer td(&ctx);
td.RegisterVariableForTesting(var.get());
ASSERT_TRUE(td.DetermineResultType(&expr)) << td.error();
Builder b;
b.push_function(Function{});
ASSERT_TRUE(b.GenerateGlobalVariable(var.get())) << b.error();
EXPECT_TRUE(b.GenerateLoopStatement(&expr)) << b.error();
EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
%2 = OpTypePointer Private %3
%1 = OpVariable %2 Private
%8 = OpConstant %3 2
%9 = OpConstant %3 3
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
R"(OpBranch %4
%4 = OpLabel
OpLoopMerge %5 %6 None
OpBranch %7
%7 = OpLabel
OpStore %1 %8
OpBranch %6
%6 = OpLabel
OpStore %1 %9
OpBranch %4
%5 = OpLabel
)");
}
TEST_F(BuilderTest, DISABLED_Loop_WithContinuing) {}
TEST_F(BuilderTest, DISABLED_Loop_WithBreak) {}
} // namespace
} // namespace spirv
} // namespace writer
} // namespace tint