diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d0b678cb97..d603688503 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -411,6 +411,7 @@ if(${TINT_BUILD_SPV_WRITER}) writer/spirv/builder_entry_point_test.cc writer/spirv/builder_function_test.cc writer/spirv/builder_global_variable_test.cc + writer/spirv/builder_initializer_expression_test.cc writer/spirv/builder_literal_test.cc writer/spirv/builder_test.cc writer/spirv/builder_type_test.cc diff --git a/src/ast/variable.h b/src/ast/variable.h index 3fec0d6777..752296d0af 100644 --- a/src/ast/variable.h +++ b/src/ast/variable.h @@ -79,6 +79,8 @@ class Variable : public Node { } /// @returns the initializer expression or nullptr if none set Expression* initializer() const { return initializer_.get(); } + /// @returns true if the variable has an initializer + bool has_initializer() const { return initializer_ != nullptr; } /// Sets if the variable is constant /// @param val the value to be set diff --git a/src/writer/spirv/builder.cc b/src/writer/spirv/builder.cc index 7896ae007b..9e1b9b8ff6 100644 --- a/src/writer/spirv/builder.cc +++ b/src/writer/spirv/builder.cc @@ -13,14 +13,17 @@ #include "src/writer/spirv/builder.h" +#include #include #include "spirv/unified1/spirv.h" #include "src/ast/binding_decoration.h" #include "src/ast/bool_literal.h" #include "src/ast/builtin_decoration.h" +#include "src/ast/const_initializer_expression.h" #include "src/ast/decorated_variable.h" #include "src/ast/float_literal.h" +#include "src/ast/initializer_expression.h" #include "src/ast/int_literal.h" #include "src/ast/location_decoration.h" #include "src/ast/set_decoration.h" @@ -33,6 +36,7 @@ #include "src/ast/type/struct_type.h" #include "src/ast/type/u32_type.h" #include "src/ast/type/vector_type.h" +#include "src/ast/type_initializer_expression.h" #include "src/ast/uint_literal.h" namespace tint { @@ -167,6 +171,15 @@ bool Builder::GenerateEntryPoint(ast::EntryPoint* ep) { return true; } +uint32_t Builder::GenerateExpression(ast::Expression* expr) { + if (expr->IsInitializer()) { + return GenerateInitializerExpression(expr->AsInitializer(), false); + } + + error_ = "unknown expression type"; + return 0; +} + bool Builder::GenerateFunction(ast::Function* func) { uint32_t func_type_id = GenerateFunctionTypeIfNeeded(func); if (func_type_id == 0) { @@ -220,14 +233,33 @@ uint32_t Builder::GenerateFunctionTypeIfNeeded(ast::Function* func) { } bool Builder::GenerateGlobalVariable(ast::Variable* var) { - auto result = result_op(); - auto var_id = result.to_i(); + uint32_t init_id = 0; + if (var->has_initializer()) { + if (!var->initializer()->IsInitializer()) { + error_ = "constant initializer expected"; + return false; + } + + init_id = GenerateInitializerExpression(var->initializer()->AsInitializer(), + true); + if (init_id == 0) { + return false; + } + } if (var->is_const()) { - // TODO(dsinclair): Handle const variables - return false; + if (!var->has_initializer()) { + error_ = "missing initializer for constant"; + return false; + } + + // TODO(dsinclair): Store variable name to id + return true; } + auto result = result_op(); + auto var_id = result.to_i(); + auto sc = var->storage_class() == ast::StorageClass::kNone ? ast::StorageClass::kPrivate : var->storage_class(); @@ -238,11 +270,16 @@ bool Builder::GenerateGlobalVariable(ast::Variable* var) { return false; } - // TODO(dsinclair): Handle variable initializer push_debug(spv::Op::OpName, {Operand::Int(var_id), Operand::String(var->name())}); - push_type(spv::Op::OpVariable, {Operand::Int(type_id), result, - Operand::Int(ConvertStorageClass(sc))}); + + std::vector ops = {Operand::Int(type_id), result, + Operand::Int(ConvertStorageClass(sc))}; + if (var->has_initializer()) { + ops.push_back(Operand::Int(init_id)); + } + + push_type(spv::Op::OpVariable, std::move(ops)); if (var->IsDecorated()) { for (const auto& deco : var->AsDecorated()->decorations()) { @@ -270,6 +307,8 @@ bool Builder::GenerateGlobalVariable(ast::Variable* var) { } } + // TODO(dsinclair) Mapping of variable name to id + return true; } @@ -283,6 +322,58 @@ void Builder::GenerateImport(ast::Import* imp) { import_name_to_id_[imp->name()] = id; } +uint32_t Builder::GenerateInitializerExpression( + ast::InitializerExpression* expr, + bool is_global_init) { + if (expr->IsConstInitializer()) { + return GenerateLiteralIfNeeded(expr->AsConstInitializer()->literal()); + } + if (expr->IsTypeInitializer()) { + auto init = expr->AsTypeInitializer(); + auto type_id = GenerateTypeIfNeeded(init->type()); + if (type_id == 0) { + return 0; + } + + std::ostringstream out; + out << "__const"; + + std::vector ops; + for (const auto& e : init->values()) { + if (is_global_init && !e->IsInitializer()) { + error_ = "initializer must be a constant expression"; + return 0; + } + auto id = + GenerateInitializerExpression(e->AsInitializer(), is_global_init); + if (id == 0) { + return 0; + } + + out << "_" << id; + ops.push_back(Operand::Int(id)); + } + + auto str = out.str(); + auto val = const_to_id_.find(str); + if (val != const_to_id_.end()) { + return val->second; + } + + auto result = result_op(); + ops.insert(ops.begin(), result); + ops.insert(ops.begin(), Operand::Int(type_id)); + + const_to_id_[str] = result.to_i(); + + push_type(spv::Op::OpCompositeConstruct, ops); + return result.to_i(); + } + + error_ = "unknown initializer expression"; + return 0; +} + uint32_t Builder::GenerateLiteralIfNeeded(ast::Literal* lit) { auto type_id = GenerateTypeIfNeeded(lit->type()); if (type_id == 0) { diff --git a/src/writer/spirv/builder.h b/src/writer/spirv/builder.h index 6c4437e3f6..5490b163f1 100644 --- a/src/writer/spirv/builder.h +++ b/src/writer/spirv/builder.h @@ -137,6 +137,10 @@ class Builder { /// @param ep the entry point /// @returns true if the instruction was generated, false otherwise bool GenerateEntryPoint(ast::EntryPoint* ep); + /// Generates an expression + /// @param expr the expression to generate + /// @returns the resulting ID of the expression or 0 on error + uint32_t GenerateExpression(ast::Expression* expr); /// Generates the instructions for a function /// @param func the function to generate /// @returns true if the instructions were generated @@ -152,6 +156,12 @@ class Builder { /// Generates an import instruction /// @param imp the import void GenerateImport(ast::Import* imp); + /// Generates an initializer expression + /// @param expr the expression to generate + /// @param is_global_init set true if this is a global variable initializer + /// @returns the ID of the expression or 0 on failure. + uint32_t GenerateInitializerExpression(ast::InitializerExpression* expr, + bool is_global_init); /// Generates a literal constant if needed /// @param lit the literal to generate /// @returns the ID on success or 0 on failure diff --git a/src/writer/spirv/builder_global_variable_test.cc b/src/writer/spirv/builder_global_variable_test.cc index 693ea9330d..0a668991de 100644 --- a/src/writer/spirv/builder_global_variable_test.cc +++ b/src/writer/spirv/builder_global_variable_test.cc @@ -18,11 +18,15 @@ #include "src/ast/binding_decoration.h" #include "src/ast/builtin.h" #include "src/ast/builtin_decoration.h" +#include "src/ast/const_initializer_expression.h" #include "src/ast/decorated_variable.h" +#include "src/ast/float_literal.h" #include "src/ast/location_decoration.h" #include "src/ast/set_decoration.h" #include "src/ast/storage_class.h" #include "src/ast/type/f32_type.h" +#include "src/ast/type/vector_type.h" +#include "src/ast/type_initializer_expression.h" #include "src/ast/variable.h" #include "src/ast/variable_decoration.h" #include "src/writer/spirv/builder.h" @@ -63,9 +67,70 @@ TEST_F(BuilderTest, GlobalVar_WithStorageClass) { )"); } -TEST_F(BuilderTest, DISABLED_GlobalVar_WithInitializer) {} +TEST_F(BuilderTest, GlobalVar_WithInitializer) { + ast::type::F32Type f32; + ast::type::VectorType vec(&f32, 3); -TEST_F(BuilderTest, DISABLED_GlobalVar_Const) {} + std::vector> vals; + vals.push_back(std::make_unique( + std::make_unique(&f32, 1.0f))); + vals.push_back(std::make_unique( + std::make_unique(&f32, 1.0f))); + vals.push_back(std::make_unique( + std::make_unique(&f32, 3.0f))); + + auto init = + std::make_unique(&vec, std::move(vals)); + + ast::Variable v("var", ast::StorageClass::kOutput, &f32); + v.set_initializer(std::move(init)); + + Builder b; + EXPECT_TRUE(b.GenerateGlobalVariable(&v)) << b.error(); + ASSERT_FALSE(b.has_error()) << b.error(); + + EXPECT_EQ(DumpInstructions(b.debug()), R"(OpName %6 "var" +)"); + EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32 +%1 = OpTypeVector %2 3 +%3 = OpConstant %2 1 +%4 = OpConstant %2 3 +%5 = OpCompositeConstruct %1 %3 %3 %4 +%7 = OpTypePointer Output %2 +%6 = OpVariable %7 Output %5 +)"); +} + +TEST_F(BuilderTest, GlobalVar_Const) { + ast::type::F32Type f32; + ast::type::VectorType vec(&f32, 3); + + std::vector> vals; + vals.push_back(std::make_unique( + std::make_unique(&f32, 1.0f))); + vals.push_back(std::make_unique( + std::make_unique(&f32, 1.0f))); + vals.push_back(std::make_unique( + std::make_unique(&f32, 3.0f))); + + auto init = + std::make_unique(&vec, std::move(vals)); + + ast::Variable v("var", ast::StorageClass::kOutput, &f32); + v.set_initializer(std::move(init)); + v.set_is_const(true); + + Builder b; + EXPECT_TRUE(b.GenerateGlobalVariable(&v)) << b.error(); + ASSERT_FALSE(b.has_error()) << b.error(); + + EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32 +%1 = OpTypeVector %2 3 +%3 = OpConstant %2 1 +%4 = OpConstant %2 3 +%5 = OpCompositeConstruct %1 %3 %3 %4 +)"); +} TEST_F(BuilderTest, GlobalVar_WithLocation) { ast::type::F32Type f32; diff --git a/src/writer/spirv/builder_initializer_expression_test.cc b/src/writer/spirv/builder_initializer_expression_test.cc new file mode 100644 index 0000000000..443493fa7e --- /dev/null +++ b/src/writer/spirv/builder_initializer_expression_test.cc @@ -0,0 +1,122 @@ +// 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 + +#include "gtest/gtest.h" +#include "spirv/unified1/spirv.h" +#include "spirv/unified1/spirv.hpp11" +#include "src/ast/const_initializer_expression.h" +#include "src/ast/float_literal.h" +#include "src/ast/relational_expression.h" +#include "src/ast/type/f32_type.h" +#include "src/ast/type/vector_type.h" +#include "src/ast/type_initializer_expression.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, Initializer_Const) { + ast::type::F32Type f32; + auto fl = std::make_unique(&f32, 42.2f); + ast::ConstInitializerExpression c(std::move(fl)); + + Builder b; + EXPECT_EQ(b.GenerateInitializerExpression(&c, true), 2); + ASSERT_FALSE(b.has_error()) << b.error(); + + EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32 +%2 = OpConstant %1 42.2000008 +)"); +} + +TEST_F(BuilderTest, Initializer_Type) { + ast::type::F32Type f32; + ast::type::VectorType vec(&f32, 3); + + std::vector> vals; + vals.push_back(std::make_unique( + std::make_unique(&f32, 1.0f))); + vals.push_back(std::make_unique( + std::make_unique(&f32, 1.0f))); + vals.push_back(std::make_unique( + std::make_unique(&f32, 3.0f))); + + ast::TypeInitializerExpression t(&vec, std::move(vals)); + + Builder b; + EXPECT_EQ(b.GenerateInitializerExpression(&t, true), 5); + ASSERT_FALSE(b.has_error()) << b.error(); + + EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32 +%1 = OpTypeVector %2 3 +%3 = OpConstant %2 1 +%4 = OpConstant %2 3 +%5 = OpCompositeConstruct %1 %3 %3 %4 +)"); +} + +TEST_F(BuilderTest, Initializer_Type_Dedups) { + ast::type::F32Type f32; + ast::type::VectorType vec(&f32, 3); + + std::vector> vals; + vals.push_back(std::make_unique( + std::make_unique(&f32, 1.0f))); + vals.push_back(std::make_unique( + std::make_unique(&f32, 1.0f))); + vals.push_back(std::make_unique( + std::make_unique(&f32, 3.0f))); + + ast::TypeInitializerExpression t(&vec, std::move(vals)); + + Builder b; + EXPECT_EQ(b.GenerateInitializerExpression(&t, true), 5); + EXPECT_EQ(b.GenerateInitializerExpression(&t, true), 5); + ASSERT_FALSE(b.has_error()) << b.error(); +} + +TEST_F(BuilderTest, Initializer_NonConst_Type_Fails) { + ast::type::F32Type f32; + ast::type::VectorType vec(&f32, 2); + auto rel = std::make_unique( + ast::Relation::kAdd, + std::make_unique( + std::make_unique(&f32, 3.0f)), + std::make_unique( + std::make_unique(&f32, 3.0f))); + + std::vector> vals; + vals.push_back(std::make_unique( + std::make_unique(&f32, 1.0f))); + vals.push_back(std::move(rel)); + + ast::TypeInitializerExpression t(&vec, std::move(vals)); + + Builder b; + EXPECT_EQ(b.GenerateInitializerExpression(&t, true), 0); + EXPECT_TRUE(b.has_error()); + EXPECT_EQ(b.error(), R"(initializer must be a constant expression)"); +} + +} // namespace +} // namespace spirv +} // namespace writer +} // namespace tint diff --git a/src/writer/spirv/operand.h b/src/writer/spirv/operand.h index 161f2634de..de66a2265c 100644 --- a/src/writer/spirv/operand.h +++ b/src/writer/spirv/operand.h @@ -49,6 +49,9 @@ class Operand { Operand(const Operand&) = default; ~Operand(); + /// Copy assignment + Operand& operator=(const Operand&) = default; + /// Sets the float value /// @param val the value to set void set_float(float val) { float_val_ = val; }