[spirv-reader] Start adding functions
Only emit functions that have bodies.
Don't emit their bodies yet.
Emit them so that callees precede callers.
Bug: tint:3
Change-Id: Ia67ba2fe1d47c7c7d5c290d1d4fc5256fa293b51
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/18620
Reviewed-by: dan sinclair <dsinclair@google.com>
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 7b96830..1c0d736 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -325,6 +325,7 @@
reader/spirv/parser_impl_convert_member_decoration_test.cc
reader/spirv/parser_impl_convert_type_test.cc
reader/spirv/parser_impl_entry_point_test.cc
+ reader/spirv/parser_impl_function_decl_test.cc
reader/spirv/parser_impl_get_decorations_test.cc
reader/spirv/parser_impl_import_test.cc
reader/spirv/parser_impl_module_var_test.cc
diff --git a/src/reader/spirv/parser_impl.cc b/src/reader/spirv/parser_impl.cc
index e843e72..e1641c6 100644
--- a/src/reader/spirv/parser_impl.cc
+++ b/src/reader/spirv/parser_impl.cc
@@ -19,10 +19,14 @@
#include <limits>
#include <memory>
#include <string>
+#include <unordered_map>
+#include <unordered_set>
#include <utility>
+#include "source/opt/basic_block.h"
#include "source/opt/build_module.h"
#include "source/opt/decoration_manager.h"
+#include "source/opt/function.h"
#include "source/opt/instruction.h"
#include "source/opt/module.h"
#include "source/opt/type_manager.h"
@@ -60,6 +64,53 @@
const spv_target_env kTargetEnv = SPV_ENV_WEBGPU_0;
+// A FunctionTraverser is used to compute an ordering of functions in the
+// module such that callees precede callers.
+class FunctionTraverser {
+ public:
+ explicit FunctionTraverser(const spvtools::opt::Module& module)
+ : module_(module) {}
+
+ // @returns the functions in the modules such that callees precede callers.
+ std::vector<const spvtools::opt::Function*> TopologicallyOrderedFunctions() {
+ visited_.clear();
+ ordered_.clear();
+ id_to_func_.clear();
+ for (const auto& f : module_) {
+ id_to_func_[f.result_id()] = &f;
+ }
+ for (const auto& f : module_) {
+ Visit(f);
+ }
+ return ordered_;
+ }
+
+ private:
+ void Visit(const spvtools::opt::Function& f) {
+ if (visited_.count(&f)) {
+ return;
+ }
+ visited_.insert(&f);
+ for (const auto& bb : f) {
+ for (const auto& inst : bb) {
+ if (inst.opcode() != SpvOpFunctionCall) {
+ continue;
+ }
+ const auto* callee = id_to_func_[inst.GetSingleWordInOperand(0)];
+ if (callee) {
+ Visit(*callee);
+ }
+ }
+ }
+ ordered_.push_back(&f);
+ }
+
+ const spvtools::opt::Module& module_;
+ std::unordered_set<const spvtools::opt::Function*> visited_;
+ std::unordered_map<uint32_t, const spvtools::opt::Function*> id_to_func_;
+ std::vector<const spvtools::opt::Function*> ordered_;
+};
+
} // namespace
ParserImpl::ParserImpl(Context* ctx, const std::vector<uint32_t>& spv_binary)
@@ -174,7 +225,10 @@
case spvtools::opt::analysis::Type::kPointer:
return save(ConvertType(spirv_type->AsPointer()));
case spvtools::opt::analysis::Type::kFunction:
- // TODO(dneto). For now return null without erroring out.
+ // Tint doesn't have a Function type.
+ // We need to convert the result type and parameter types.
+ // But the SPIR-V defines those before defining the function
+ // type. No further work is required here.
return nullptr;
default:
break;
@@ -299,8 +353,10 @@
if (!EmitModuleScopeVariables()) {
return false;
}
- // TODO(dneto): fill in the rest
- return true;
+ if (!EmitFunctions()) {
+ return false;
+ }
+ return success_;
}
bool ParserImpl::RegisterExtendedInstructionImports() {
@@ -635,39 +691,8 @@
<< var.type_id();
}
auto* ast_store_type = ast_type->AsPointer()->type();
- if (!namer_.HasName(var.result_id())) {
- namer_.SuggestSanitizedName(var.result_id(),
- "x_" + std::to_string(var.result_id()));
- }
- auto ast_var = std::make_unique<ast::Variable>(
- namer_.GetName(var.result_id()), ast_storage_class, ast_store_type);
-
- std::vector<std::unique_ptr<ast::VariableDecoration>> ast_decorations;
- for (auto& deco : GetDecorationsFor(var.result_id())) {
- if (deco.empty()) {
- return Fail() << "malformed decoration on ID " << var.result_id()
- << ": it is empty";
- }
- if (deco[0] == SpvDecorationBuiltIn) {
- if (deco.size() == 1) {
- return Fail() << "malformed BuiltIn decoration on ID "
- << var.result_id() << ": has no operand";
- }
- auto ast_builtin =
- enum_converter_.ToBuiltin(static_cast<SpvBuiltIn>(deco[1]));
- if (ast_builtin == ast::Builtin::kNone) {
- return false;
- }
- ast_decorations.emplace_back(
- std::make_unique<ast::BuiltinDecoration>(ast_builtin));
- }
- }
- if (!ast_decorations.empty()) {
- auto decorated_var =
- std::make_unique<ast::DecoratedVariable>(std::move(ast_var));
- decorated_var->set_decorations(std::move(ast_decorations));
- ast_var = std::move(decorated_var);
- }
+ auto ast_var =
+ MakeVariable(var.result_id(), ast_storage_class, ast_store_type);
// TODO(dneto): initializers (a.k.a. constructor expression)
ast_module_.AddGlobalVariable(std::move(ast_var));
@@ -675,6 +700,99 @@
return success_;
}
+std::unique_ptr<ast::Variable> ParserImpl::MakeVariable(uint32_t id,
+ ast::StorageClass sc,
+ ast::type::Type* type) {
+ if (type == nullptr) {
+ Fail() << "internal error: can't make ast::Variable for null type";
+ return nullptr;
+ }
+
+ auto ast_var = std::make_unique<ast::Variable>(Name(id), sc, type);
+
+ std::vector<std::unique_ptr<ast::VariableDecoration>> ast_decorations;
+ for (auto& deco : GetDecorationsFor(id)) {
+ if (deco.empty()) {
+ Fail() << "malformed decoration on ID " << id << ": it is empty";
+ return nullptr;
+ }
+ if (deco[0] == SpvDecorationBuiltIn) {
+ if (deco.size() == 1) {
+ Fail() << "malformed BuiltIn decoration on ID " << id
+ << ": has no operand";
+ return nullptr;
+ }
+ auto ast_builtin =
+ enum_converter_.ToBuiltin(static_cast<SpvBuiltIn>(deco[1]));
+ if (ast_builtin == ast::Builtin::kNone) {
+ return nullptr;
+ }
+ ast_decorations.emplace_back(
+ std::make_unique<ast::BuiltinDecoration>(ast_builtin));
+ }
+ }
+ if (!ast_decorations.empty()) {
+ auto decorated_var =
+ std::make_unique<ast::DecoratedVariable>(std::move(ast_var));
+ decorated_var->set_decorations(std::move(ast_decorations));
+ ast_var = std::move(decorated_var);
+ }
+ return ast_var;
+}
+
+bool ParserImpl::EmitFunctions() {
+ if (!success_) {
+ return false;
+ }
+ for (const auto* f :
+ FunctionTraverser(*module_).TopologicallyOrderedFunctions()) {
+ EmitFunction(*f);
+ }
+ return success_;
+}
+
+bool ParserImpl::EmitFunction(const spvtools::opt::Function& f) {
+ if (!success_) {
+ return false;
+ }
+ // We only care about functions with bodies.
+ if (f.cbegin() == f.cend()) {
+ return true;
+ }
+
+ const auto name = Name(f.result_id());
+ // Surprisingly, the "type id" on an OpFunction is the result type of the
+ // function, not the type of the function. This is the one exceptional case
+ // in SPIR-V where the type ID is not the type of the result ID.
+ auto* ret_ty = ConvertType(f.type_id());
+ if (!success_) {
+ return false;
+ }
+ if (ret_ty == nullptr) {
+ return Fail()
+ << "internal error: unregistered return type for function with ID "
+ << f.result_id();
+ }
+
+ std::vector<std::unique_ptr<ast::Variable>> ast_params;
+ f.ForEachParam([this, &ast_params](const spvtools::opt::Instruction* param) {
+ auto* ast_type = ConvertType(param->type_id());
+ if (ast_type != nullptr) {
+ ast_params.emplace_back(std::move(MakeVariable(
+ param->result_id(), ast::StorageClass::kNone, ast_type)));
+ }
+ });
+ if (!success_) {
+ return false;
+ }
+
+ auto ast_fn =
+ std::make_unique<ast::Function>(name, std::move(ast_params), ret_ty);
+ ast_module_.AddFunction(std::move(ast_fn));
+
+ return success_;
+}
+
} // namespace spirv
} // namespace reader
} // namespace tint
diff --git a/src/reader/spirv/parser_impl.h b/src/reader/spirv/parser_impl.h
index 2c6a098..f9a6e45 100644
--- a/src/reader/spirv/parser_impl.h
+++ b/src/reader/spirv/parser_impl.h
@@ -178,7 +178,26 @@
/// @returns true if parser is still successful.
bool EmitModuleScopeVariables();
+ /// Emits functions, with callees preceding their callers.
+ /// This is a no-op if the parser has already failed.
+ /// @returns true if parser is still successful.
+ bool EmitFunctions();
+
+ /// Emits a single function, if it has a body.
+ /// This is a no-op if the parser has already failed.
+ /// @param f the function to emit
+ /// @returns true if parser is still successful.
+ bool EmitFunction(const spvtools::opt::Function& f);
+
private:
+ /// @returns a name for the given ID. Generates a name if non exists.
+ std::string Name(uint32_t id) {
+ if (!namer_.HasName(id)) {
+ namer_.SuggestSanitizedName(id, "x_" + std::to_string(id));
+ }
+ return namer_.GetName(id);
+ }
+
/// Converts a specific SPIR-V type to a Tint type. Integer case
ast::type::Type* ConvertType(const spvtools::opt::analysis::Integer* int_ty);
/// Converts a specific SPIR-V type to a Tint type. Float case
@@ -198,6 +217,16 @@
/// Converts a specific SPIR-V type to a Tint type. Pointer case
ast::type::Type* ConvertType(const spvtools::opt::analysis::Pointer* ptr_ty);
+ /// Creates an AST Variable node for a SPIR-V ID, including any attached
+ /// decorations.
+ /// @param id the SPIR-V result ID
+ /// @param sc the storage class, which can be ast::StorageClass::kNone
+ /// @param type the type
+ /// @returns a new Variable node, or null in the error case
+ std::unique_ptr<ast::Variable> MakeVariable(uint32_t id,
+ ast::StorageClass sc,
+ ast::type::Type* type);
+
// The SPIR-V binary we're parsing
std::vector<uint32_t> spv_binary_;
diff --git a/src/reader/spirv/parser_impl_function_decl_test.cc b/src/reader/spirv/parser_impl_function_decl_test.cc
new file mode 100644
index 0000000..e95cab8
--- /dev/null
+++ b/src/reader/spirv/parser_impl_function_decl_test.cc
@@ -0,0 +1,224 @@
+// 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 <sstream>
+#include <string>
+#include <vector>
+
+#include "gmock/gmock.h"
+#include "src/reader/spirv/parser_impl.h"
+#include "src/reader/spirv/parser_impl_test_helper.h"
+#include "src/reader/spirv/spirv_tools_helpers_test.h"
+
+namespace tint {
+namespace reader {
+namespace spirv {
+namespace {
+
+using ::testing::HasSubstr;
+
+/// @returns a SPIR-V assembly segment which assigns debug names
+/// to particular IDs.
+std::string Names(std::vector<std::string> ids) {
+ std::ostringstream outs;
+ for (auto& id : ids) {
+ outs << " OpName %" << id << " \"" << id << "\"\n";
+ }
+ return outs.str();
+}
+
+std::string CommonTypes() {
+ return R"(
+ %void = OpTypeVoid
+ %voidfn = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %uint = OpTypeInt 32 0
+ %int = OpTypeInt 32 1
+ %float_0 = OpConstant %float 0.0
+ )";
+}
+
+TEST_F(SpvParserTest, EmitFunctions_NoFunctions) {
+ auto p = parser(test::Assemble(CommonTypes()));
+ EXPECT_TRUE(p->BuildAndParseInternalModule());
+ EXPECT_TRUE(p->error().empty());
+ const auto module_ast = p->module().to_str();
+ EXPECT_THAT(module_ast, Not(HasSubstr("Function{")));
+}
+
+TEST_F(SpvParserTest, EmitFunctions_FunctionWithoutBody) {
+ auto p = parser(test::Assemble(Names({"main"}) + CommonTypes() + R"(
+ %main = OpFunction %void None %voidfn
+ OpFunctionEnd
+ )"));
+ EXPECT_TRUE(p->BuildAndParseInternalModule());
+ EXPECT_TRUE(p->error().empty());
+ const auto module_ast = p->module().to_str();
+ EXPECT_THAT(module_ast, Not(HasSubstr("Function{")));
+}
+
+TEST_F(SpvParserTest, EmitFunctions_VoidFunctionWithoutParams) {
+ auto p = parser(test::Assemble(Names({"main"}) + CommonTypes() + R"(
+ %main = OpFunction %void None %voidfn
+ %entry = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )"));
+ EXPECT_TRUE(p->BuildAndParseInternalModule());
+ EXPECT_TRUE(p->error().empty());
+ const auto module_ast = p->module().to_str();
+ EXPECT_THAT(module_ast, HasSubstr(R"(
+ Function main -> __void
+ ()
+ {)"));
+}
+
+TEST_F(SpvParserTest, EmitFunctions_CalleePrecedesCaller) {
+ auto p = parser(
+ test::Assemble(Names({"root", "branch", "leaf"}) + CommonTypes() + R"(
+ %root = OpFunction %void None %voidfn
+ %root_entry = OpLabel
+ %branch_result = OpFunctionCall %void %branch
+ OpReturn
+ OpFunctionEnd
+
+ %branch = OpFunction %void None %voidfn
+ %branch_entry = OpLabel
+ %leaf_result = OpFunctionCall %void %leaf
+ OpReturn
+ OpFunctionEnd
+
+ %leaf = OpFunction %void None %voidfn
+ %leaf_entry = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )"));
+ EXPECT_TRUE(p->BuildAndParseInternalModule());
+ EXPECT_TRUE(p->error().empty());
+ const auto module_ast = p->module().to_str();
+ EXPECT_THAT(module_ast, HasSubstr(R"(
+ Function leaf -> __void
+ ()
+ {
+ }
+ Function branch -> __void
+ ()
+ {
+ }
+ Function root -> __void
+ ()
+ {
+ })"));
+}
+
+TEST_F(SpvParserTest, EmitFunctions_NonVoidResultType) {
+ auto p = parser(test::Assemble(Names({"ret_float"}) + CommonTypes() + R"(
+ %fn_ret_float = OpTypeFunction %float
+
+ %ret_float = OpFunction %float None %nf_ret_float
+ %ret_float_entry = OpLabel
+ OpReturnValue %float_0
+ OpFunctionEnd
+ )"));
+ EXPECT_TRUE(p->BuildAndParseInternalModule());
+ EXPECT_TRUE(p->error().empty());
+ const auto module_ast = p->module().to_str();
+ EXPECT_THAT(module_ast, HasSubstr(R"(
+ Function ret_float -> __f32
+ ()
+ {
+ })"));
+}
+
+TEST_F(SpvParserTest, EmitFunctions_MixedParamTypes) {
+ auto p = parser(test::Assemble(Names({"mixed_params", "a", "b", "c"}) +
+ CommonTypes() + R"(
+ %fn_mixed_params = OpTypeFunction %float %uint %float %int
+
+ %mixed_params = OpFunction %void None %fn_mixed_params
+ %a = OpFunctionParameter %uint
+ %b = OpFunctionParameter %float
+ %c = OpFunctionParameter %int
+ %mixed_entry = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )"));
+ EXPECT_TRUE(p->BuildAndParseInternalModule());
+ EXPECT_TRUE(p->error().empty());
+ const auto module_ast = p->module().to_str();
+ EXPECT_THAT(module_ast, HasSubstr(R"(
+ Function mixed_params -> __void
+ (
+ Variable{
+ a
+ none
+ __u32
+ }
+ Variable{
+ b
+ none
+ __f32
+ }
+ Variable{
+ c
+ none
+ __i32
+ }
+ )
+ {
+ })"));
+}
+
+TEST_F(SpvParserTest, EmitFunctions_GenerateParamNames) {
+ auto p = parser(test::Assemble(Names({"mixed_params"}) + CommonTypes() + R"(
+ %fn_mixed_params = OpTypeFunction %float %uint %float %int
+
+ %mixed_params = OpFunction %void None %fn_mixed_params
+ %14 = OpFunctionParameter %uint
+ %15 = OpFunctionParameter %float
+ %16 = OpFunctionParameter %int
+ %mixed_entry = OpLabel
+ OpReturn
+ OpFunctionEnd
+ )"));
+ EXPECT_TRUE(p->BuildAndParseInternalModule());
+ EXPECT_TRUE(p->error().empty());
+ const auto module_ast = p->module().to_str();
+ EXPECT_THAT(module_ast, HasSubstr(R"(
+ Function mixed_params -> __void
+ (
+ Variable{
+ x_14
+ none
+ __u32
+ }
+ Variable{
+ x_15
+ none
+ __f32
+ }
+ Variable{
+ x_16
+ none
+ __i32
+ }
+ )
+ {
+ })"));
+}
+
+} // namespace
+} // namespace spirv
+} // namespace reader
+} // namespace tint