diff --git a/BUILD.gn b/BUILD.gn index 44898e9f1c..246f1bebc3 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -1050,6 +1050,7 @@ source_set("tint_unittests_hlsl_writer_src") { "src/writer/hlsl/generator_impl_return_test.cc", "src/writer/hlsl/generator_impl_switch_test.cc", "src/writer/hlsl/generator_impl_test.cc", + "src/writer/hlsl/generator_impl_type_test.cc", "src/writer/hlsl/generator_impl_unary_op_test.cc", "src/writer/hlsl/namer_test.cc", ] diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index efa3aeb146..43932c3f6e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -558,6 +558,7 @@ if (${TINT_BUILD_HLSL_WRITER}) writer/hlsl/generator_impl_return_test.cc writer/hlsl/generator_impl_switch_test.cc writer/hlsl/generator_impl_test.cc + writer/hlsl/generator_impl_type_test.cc writer/hlsl/generator_impl_unary_op_test.cc writer/hlsl/namer_test.cc ) diff --git a/src/writer/hlsl/generator_impl.cc b/src/writer/hlsl/generator_impl.cc index 1937bb65f8..9807979488 100644 --- a/src/writer/hlsl/generator_impl.cc +++ b/src/writer/hlsl/generator_impl.cc @@ -22,7 +22,15 @@ #include "src/ast/identifier_expression.h" #include "src/ast/return_statement.h" #include "src/ast/sint_literal.h" +#include "src/ast/struct.h" #include "src/ast/switch_statement.h" +#include "src/ast/type/alias_type.h" +#include "src/ast/type/array_type.h" +#include "src/ast/type/f32_type.h" +#include "src/ast/type/i32_type.h" +#include "src/ast/type/matrix_type.h" +#include "src/ast/type/struct_type.h" +#include "src/ast/type/vector_type.h" #include "src/ast/uint_literal.h" #include "src/ast/unary_op_expression.h" @@ -372,6 +380,98 @@ bool GeneratorImpl::EmitSwitch(ast::SwitchStatement* stmt) { return true; } +bool GeneratorImpl::EmitType(ast::type::Type* type, const std::string& name) { + if (type->IsAlias()) { + auto* alias = type->AsAlias(); + out_ << namer_.NameFor(alias->name()); + } else if (type->IsArray()) { + auto* ary = type->AsArray(); + + ast::type::Type* base_type = ary; + std::vector sizes; + while (base_type->IsArray()) { + if (base_type->AsArray()->IsRuntimeArray()) { + // TODO(dsinclair): Support runtime arrays + // https://bugs.chromium.org/p/tint/issues/detail?id=185 + error_ = "runtime array not supported yet."; + return false; + } else { + sizes.push_back(base_type->AsArray()->size()); + } + base_type = base_type->AsArray()->type(); + } + if (!EmitType(base_type, "")) { + return false; + } + if (!name.empty()) { + out_ << " " << namer_.NameFor(name); + } + for (uint32_t size : sizes) { + out_ << "[" << size << "]"; + } + } else if (type->IsBool()) { + out_ << "bool"; + } else if (type->IsF32()) { + out_ << "float"; + } else if (type->IsI32()) { + out_ << "int"; + } else if (type->IsMatrix()) { + auto* mat = type->AsMatrix(); + out_ << "matrix<"; + if (!EmitType(mat->type(), "")) { + return false; + } + out_ << ", " << mat->rows() << ", " << mat->columns() << ">"; + } else if (type->IsPointer()) { + // TODO(dsinclair): What do we do with pointers in HLSL? + // https://bugs.chromium.org/p/tint/issues/detail?id=183 + error_ = "pointers not supported in HLSL"; + return false; + } else if (type->IsStruct()) { + auto* str = type->AsStruct()->impl(); + // TODO(dsinclair): Block decoration? + // if (str->decoration() != ast::StructDecoration::kNone) { + // } + out_ << "struct {" << std::endl; + + increment_indent(); + for (const auto& mem : str->members()) { + make_indent(); + // TODO(dsinclair): Handle [[offset]] annotation on structs + // https://bugs.chromium.org/p/tint/issues/detail?id=184 + + if (!EmitType(mem->type(), mem->name())) { + return false; + } + // Array member name will be output with the type + if (!mem->type()->IsArray()) { + out_ << " " << namer_.NameFor(mem->name()); + } + out_ << ";" << std::endl; + } + decrement_indent(); + make_indent(); + + out_ << "}"; + } else if (type->IsU32()) { + out_ << "uint"; + } else if (type->IsVector()) { + auto* vec = type->AsVector(); + out_ << "vector<"; + if (!EmitType(vec->type(), "")) { + return false; + } + out_ << ", " << vec->size() << ">"; + } else if (type->IsVoid()) { + out_ << "void"; + } else { + error_ = "unknown type in EmitType"; + return false; + } + + return true; +} + bool GeneratorImpl::EmitUnaryOp(ast::UnaryOpExpression* expr) { switch (expr->op()) { case ast::UnaryOp::kNot: diff --git a/src/writer/hlsl/generator_impl.h b/src/writer/hlsl/generator_impl.h index 9931639127..896bf306d9 100644 --- a/src/writer/hlsl/generator_impl.h +++ b/src/writer/hlsl/generator_impl.h @@ -80,6 +80,11 @@ class GeneratorImpl : public TextGenerator { /// @param stmt the statement to emit /// @returns true if the statement was emitted bool EmitSwitch(ast::SwitchStatement* stmt); + /// Handles generating type + /// @param type the type to generate + /// @param name the name of the variable, only used for array emission + /// @returns true if the type is emitted + bool EmitType(ast::type::Type* type, const std::string& name); /// Handles a unary op expression /// @param expr the expression to emit /// @returns true if the expression was emitted diff --git a/src/writer/hlsl/generator_impl_type_test.cc b/src/writer/hlsl/generator_impl_type_test.cc new file mode 100644 index 0000000000..6b27c00e3e --- /dev/null +++ b/src/writer/hlsl/generator_impl_type_test.cc @@ -0,0 +1,345 @@ +// 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 "gtest/gtest.h" +#include "src/ast/module.h" +#include "src/ast/struct.h" +#include "src/ast/struct_decoration.h" +#include "src/ast/struct_member.h" +#include "src/ast/struct_member_decoration.h" +#include "src/ast/struct_member_offset_decoration.h" +#include "src/ast/type/array_type.h" +#include "src/ast/type/bool_type.h" +#include "src/ast/type/f32_type.h" +#include "src/ast/type/i32_type.h" +#include "src/ast/type/matrix_type.h" +#include "src/ast/type/pointer_type.h" +#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/void_type.h" +#include "src/writer/hlsl/generator_impl.h" + +namespace tint { +namespace writer { +namespace hlsl { +namespace { + +using HlslGeneratorImplTest = testing::Test; + +TEST_F(HlslGeneratorImplTest, EmitType_Alias) { + ast::type::F32Type f32; + ast::type::AliasType alias("alias", &f32); + + ast::Module m; + GeneratorImpl g(&m); + ASSERT_TRUE(g.EmitType(&alias, "")) << g.error(); + EXPECT_EQ(g.result(), "alias"); +} + +TEST_F(HlslGeneratorImplTest, EmitType_Alias_NameCollision) { + ast::type::F32Type f32; + ast::type::AliasType alias("bool", &f32); + + ast::Module m; + GeneratorImpl g(&m); + ASSERT_TRUE(g.EmitType(&alias, "")) << g.error(); + EXPECT_EQ(g.result(), "bool_tint_0"); +} + +TEST_F(HlslGeneratorImplTest, EmitType_Array) { + ast::type::BoolType b; + ast::type::ArrayType a(&b, 4); + + ast::Module m; + GeneratorImpl g(&m); + ASSERT_TRUE(g.EmitType(&a, "ary")) << g.error(); + EXPECT_EQ(g.result(), "bool ary[4]"); +} + +TEST_F(HlslGeneratorImplTest, EmitType_ArrayOfArray) { + ast::type::BoolType b; + ast::type::ArrayType a(&b, 4); + ast::type::ArrayType c(&a, 5); + + ast::Module m; + GeneratorImpl g(&m); + ASSERT_TRUE(g.EmitType(&c, "ary")) << g.error(); + EXPECT_EQ(g.result(), "bool ary[5][4]"); +} + +// TODO(dsinclair): Is this possible? What order should it output in? +TEST_F(HlslGeneratorImplTest, DISABLED_EmitType_ArrayOfArrayOfRuntimeArray) { + ast::type::BoolType b; + ast::type::ArrayType a(&b, 4); + ast::type::ArrayType c(&a, 5); + ast::type::ArrayType d(&c); + + ast::Module m; + GeneratorImpl g(&m); + ASSERT_TRUE(g.EmitType(&c, "ary")) << g.error(); + EXPECT_EQ(g.result(), "bool ary[5][4][1]"); +} + +TEST_F(HlslGeneratorImplTest, EmitType_ArrayOfArrayOfArray) { + ast::type::BoolType b; + ast::type::ArrayType a(&b, 4); + ast::type::ArrayType c(&a, 5); + ast::type::ArrayType d(&c, 6); + + ast::Module m; + GeneratorImpl g(&m); + ASSERT_TRUE(g.EmitType(&d, "ary")) << g.error(); + EXPECT_EQ(g.result(), "bool ary[6][5][4]"); +} + +TEST_F(HlslGeneratorImplTest, EmitType_Array_NameCollision) { + ast::type::BoolType b; + ast::type::ArrayType a(&b, 4); + + ast::Module m; + GeneratorImpl g(&m); + ASSERT_TRUE(g.EmitType(&a, "bool")) << g.error(); + EXPECT_EQ(g.result(), "bool bool_tint_0[4]"); +} + +TEST_F(HlslGeneratorImplTest, EmitType_Array_WithoutName) { + ast::type::BoolType b; + ast::type::ArrayType a(&b, 4); + + ast::Module m; + GeneratorImpl g(&m); + ASSERT_TRUE(g.EmitType(&a, "")) << g.error(); + EXPECT_EQ(g.result(), "bool[4]"); +} + +TEST_F(HlslGeneratorImplTest, DISABLED_EmitType_RuntimeArray) { + ast::type::BoolType b; + ast::type::ArrayType a(&b); + + ast::Module m; + GeneratorImpl g(&m); + ASSERT_TRUE(g.EmitType(&a, "ary")) << g.error(); + EXPECT_EQ(g.result(), "bool ary[]"); +} + +TEST_F(HlslGeneratorImplTest, DISABLED_EmitType_RuntimeArray_NameCollision) { + ast::type::BoolType b; + ast::type::ArrayType a(&b); + + ast::Module m; + GeneratorImpl g(&m); + ASSERT_TRUE(g.EmitType(&a, "double")) << g.error(); + EXPECT_EQ(g.result(), "bool double_tint_0[]"); +} + +TEST_F(HlslGeneratorImplTest, EmitType_Bool) { + ast::type::BoolType b; + + ast::Module m; + GeneratorImpl g(&m); + ASSERT_TRUE(g.EmitType(&b, "")) << g.error(); + EXPECT_EQ(g.result(), "bool"); +} + +TEST_F(HlslGeneratorImplTest, EmitType_F32) { + ast::type::F32Type f32; + + ast::Module m; + GeneratorImpl g(&m); + ASSERT_TRUE(g.EmitType(&f32, "")) << g.error(); + EXPECT_EQ(g.result(), "float"); +} + +TEST_F(HlslGeneratorImplTest, EmitType_I32) { + ast::type::I32Type i32; + + ast::Module m; + GeneratorImpl g(&m); + ASSERT_TRUE(g.EmitType(&i32, "")) << g.error(); + EXPECT_EQ(g.result(), "int"); +} + +TEST_F(HlslGeneratorImplTest, EmitType_Matrix) { + ast::type::F32Type f32; + ast::type::MatrixType m(&f32, 3, 2); + + ast::Module mod; + GeneratorImpl g(&mod); + ASSERT_TRUE(g.EmitType(&m, "")) << g.error(); + EXPECT_EQ(g.result(), "matrix"); +} + +// TODO(dsinclair): How to annotate as workgroup? +TEST_F(HlslGeneratorImplTest, DISABLED_EmitType_Pointer) { + ast::type::F32Type f32; + ast::type::PointerType p(&f32, ast::StorageClass::kWorkgroup); + + ast::Module m; + GeneratorImpl g(&m); + ASSERT_TRUE(g.EmitType(&p, "")) << g.error(); + EXPECT_EQ(g.result(), "float*"); +} + +TEST_F(HlslGeneratorImplTest, EmitType_Struct) { + ast::type::I32Type i32; + ast::type::F32Type f32; + + ast::StructMemberList members; + members.push_back(std::make_unique( + "a", &i32, ast::StructMemberDecorationList{})); + + ast::StructMemberDecorationList b_deco; + b_deco.push_back(std::make_unique(4)); + members.push_back( + std::make_unique("b", &f32, std::move(b_deco))); + + auto str = std::make_unique(); + str->set_members(std::move(members)); + + ast::type::StructType s(std::move(str)); + + ast::Module m; + GeneratorImpl g(&m); + ASSERT_TRUE(g.EmitType(&s, "")) << g.error(); + EXPECT_EQ(g.result(), R"(struct { + int a; + float b; +})"); +} + +TEST_F(HlslGeneratorImplTest, DISABLED_EmitType_Struct_InjectPadding) { + ast::type::I32Type i32; + ast::type::F32Type f32; + + ast::StructMemberDecorationList decos; + decos.push_back(std::make_unique(4)); + + ast::StructMemberList members; + members.push_back( + std::make_unique("a", &i32, std::move(decos))); + + decos.push_back(std::make_unique(32)); + members.push_back( + std::make_unique("b", &f32, std::move(decos))); + + decos.push_back(std::make_unique(128)); + members.push_back( + std::make_unique("c", &f32, std::move(decos))); + + auto str = std::make_unique(); + str->set_members(std::move(members)); + + ast::type::StructType s(std::move(str)); + + ast::Module m; + GeneratorImpl g(&m); + ASSERT_TRUE(g.EmitType(&s, "")) << g.error(); + EXPECT_EQ(g.result(), R"(struct { + int8_t pad_0[4]; + int a; + int8_t pad_1[24]; + float b; + int8_t pad_2[92]; + float c; +})"); +} + +TEST_F(HlslGeneratorImplTest, EmitType_Struct_NameCollision) { + ast::type::I32Type i32; + ast::type::F32Type f32; + + ast::StructMemberList members; + members.push_back(std::make_unique( + "double", &i32, ast::StructMemberDecorationList{})); + + ast::StructMemberDecorationList b_deco; + members.push_back( + std::make_unique("float", &f32, std::move(b_deco))); + + auto str = std::make_unique(); + str->set_members(std::move(members)); + + ast::type::StructType s(std::move(str)); + + ast::Module m; + GeneratorImpl g(&m); + ASSERT_TRUE(g.EmitType(&s, "")) << g.error(); + EXPECT_EQ(g.result(), R"(struct { + int double_tint_0; + float float_tint_0; +})"); +} + +// TODO(dsinclair): How to translate [[block]] +TEST_F(HlslGeneratorImplTest, DISABLED_EmitType_Struct_WithDecoration) { + ast::type::I32Type i32; + ast::type::F32Type f32; + + ast::StructMemberList members; + members.push_back(std::make_unique( + "a", &i32, ast::StructMemberDecorationList{})); + + ast::StructMemberDecorationList b_deco; + b_deco.push_back(std::make_unique(4)); + members.push_back( + std::make_unique("b", &f32, std::move(b_deco))); + + auto str = std::make_unique(); + str->set_members(std::move(members)); + str->set_decoration(ast::StructDecoration::kBlock); + + ast::type::StructType s(std::move(str)); + + ast::Module m; + GeneratorImpl g(&m); + ASSERT_TRUE(g.EmitType(&s, "")) << g.error(); + EXPECT_EQ(g.result(), R"(struct { + int a; + float b; +})"); +} + +TEST_F(HlslGeneratorImplTest, EmitType_U32) { + ast::type::U32Type u32; + + ast::Module m; + GeneratorImpl g(&m); + ASSERT_TRUE(g.EmitType(&u32, "")) << g.error(); + EXPECT_EQ(g.result(), "uint"); +} + +TEST_F(HlslGeneratorImplTest, EmitType_Vector) { + ast::type::F32Type f32; + ast::type::VectorType v(&f32, 3); + + ast::Module m; + GeneratorImpl g(&m); + ASSERT_TRUE(g.EmitType(&v, "")) << g.error(); + EXPECT_EQ(g.result(), "vector"); +} + +TEST_F(HlslGeneratorImplTest, EmitType_Void) { + ast::type::VoidType v; + + ast::Module m; + GeneratorImpl g(&m); + ASSERT_TRUE(g.EmitType(&v, "")) << g.error(); + EXPECT_EQ(g.result(), "void"); +} + +} // namespace +} // namespace hlsl +} // namespace writer +} // namespace tint