diff --git a/BUILD.gn b/BUILD.gn index 888a974c9c..035e61fb99 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -996,6 +996,7 @@ source_set("tint_unittests_msl_writer_src") { "src/writer/msl/generator_impl_identifier_test.cc", "src/writer/msl/generator_impl_if_test.cc", "src/writer/msl/generator_impl_import_test.cc", + "src/writer/msl/generator_impl_intrinsic_test.cc", "src/writer/msl/generator_impl_loop_test.cc", "src/writer/msl/generator_impl_member_accessor_test.cc", "src/writer/msl/generator_impl_module_constant_test.cc", diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5575b35a79..49304eb94f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -527,6 +527,7 @@ if(${TINT_BUILD_MSL_WRITER}) writer/msl/generator_impl_identifier_test.cc writer/msl/generator_impl_if_test.cc writer/msl/generator_impl_import_test.cc + writer/msl/generator_impl_intrinsic_test.cc writer/msl/generator_impl_loop_test.cc writer/msl/generator_impl_member_accessor_test.cc writer/msl/generator_impl_module_constant_test.cc diff --git a/src/writer/msl/generator_impl.cc b/src/writer/msl/generator_impl.cc index 21848a8052..a6fe654250 100644 --- a/src/writer/msl/generator_impl.cc +++ b/src/writer/msl/generator_impl.cc @@ -400,6 +400,43 @@ std::string GeneratorImpl::current_ep_var_name(VarType type) { return name; } +std::string GeneratorImpl::generate_intrinsic_name(const std::string& name) { + if (name == "any") { + return "any"; + } + if (name == "all") { + return "all"; + } + if (name == "dot") { + return "dot"; + } + if (name == "is_finite") { + return "isfinite"; + } + if (name == "is_inf") { + return "isinf"; + } + if (name == "is_nan") { + return "isnan"; + } + if (name == "is_normal") { + return "isnormal"; + } + if (name == "select") { + return "select"; + } + if (name == "dpdy" || name == "dpdy_fine" || name == "dpdy_coarse") { + return "dfdy"; + } + if (name == "dpdx" || name == "dpdx_fine" || name == "dpdx_coarse") { + return "dfdx"; + } + if (name == "fwidth" || name == "fwidth_fine" || name == "fwidth_coarse") { + return "fwidth"; + } + return ""; +} + bool GeneratorImpl::EmitCall(ast::CallExpression* expr) { if (!expr->func()->IsIdentifier()) { error_ = "invalid function name"; @@ -407,11 +444,86 @@ bool GeneratorImpl::EmitCall(ast::CallExpression* expr) { } auto* ident = expr->func()->AsIdentifier(); - if (!ident->has_path() && ast::intrinsic::IsIntrinsic(ident->name())) { - // TODO(dsinclair): Generate intrinsic - error_ = "intrinsics not generated yet"; - return false; + const auto& params = expr->params(); + if (ident->name() == "outer_product") { + error_ = "outer_product not supported yet"; + return false; + // TODO(dsinclair): This gets tricky. We need to generate two variables to + // hold the outer_product expressions, but we maybe inside an expression + // ourselves. So, this will need to, possibly, output the variables + // _before_ the expression which contains the outer product. + // + // This then has the follow on, what if we have `(false && + // outer_product())` in that case, we shouldn't evaluate the expressions + // at all because of short circuting. + // + // So .... this turns out to be hard ... + + // // We create variables to hold the two parameters in case they're + // // function calls with side effects. + // auto* param0 = param[0].get(); + // auto* name0 = generate_name("outer_product_expr_0"); + + // auto* param1 = param[1].get(); + // auto* name1 = generate_name("outer_product_expr_1"); + + // make_indent(); + // if (!EmitType(expr->result_type(), "")) { + // return false; + // } + // out_ << "("; + + // auto param1_type = params[1]->result_type()->UnwrapPtrIfNeeded(); + // if (!param1_type->IsVector()) { + // error_ = "invalid param type in outer_product got: " + + // param1_type->type_name(); + // return false; + // } + + // for (uint32_t i = 0; i < param1_type->AsVector()->size(); ++i) { + // if (i > 0) { + // out_ << ", "; + // } + + // if (!EmitExpression(params[0].get())) { + // return false; + // } + // out_ << " * "; + + // if (!EmitExpression(params[1].get())) { + // return false; + // } + // out_ << "[" << i << "]"; + // } + + // out_ << ")"; + } else { + auto name = generate_intrinsic_name(ident->name()); + if (name.empty()) { + error_ = "unable to determine intrinsic name for intrinsic: " + + ident->name(); + return false; + } + + make_indent(); + out_ << name << "("; + + bool first = true; + for (const auto& param : params) { + if (!first) { + out_ << ", "; + } + first = false; + + if (!EmitExpression(param.get())) { + return false; + } + } + + out_ << ")"; + } + return true; } if (!ident->has_path()) { diff --git a/src/writer/msl/generator_impl.h b/src/writer/msl/generator_impl.h index 3f0a29aeb1..3e4aa4f0ec 100644 --- a/src/writer/msl/generator_impl.h +++ b/src/writer/msl/generator_impl.h @@ -224,6 +224,10 @@ class GeneratorImpl : public TextGenerator { /// @param prefix the prefix of the name to generate /// @returns the name std::string generate_name(const std::string& prefix); + /// Generates an intrinsic name from the given name + /// @param name the name to convert to an intrinsic + /// @returns the intrinsic name or blank on error + std::string generate_intrinsic_name(const std::string& name); /// Checks if the global variable is in an input or output struct /// @param var the variable to check diff --git a/src/writer/msl/generator_impl_intrinsic_test.cc b/src/writer/msl/generator_impl_intrinsic_test.cc new file mode 100644 index 0000000000..b52955c83a --- /dev/null +++ b/src/writer/msl/generator_impl_intrinsic_test.cc @@ -0,0 +1,130 @@ +// 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/call_expression.h" +#include "src/ast/identifier_expression.h" +#include "src/ast/module.h" +#include "src/ast/type/f32_type.h" +#include "src/ast/type/vector_type.h" +#include "src/context.h" +#include "src/type_determiner.h" +#include "src/writer/msl/generator_impl.h" + +namespace tint { +namespace writer { +namespace msl { +namespace { + +using MslGeneratorImplTest = testing::Test; + +struct IntrinsicData { + const char* name; + const char* msl_name; +}; +inline std::ostream& operator<<(std::ostream& out, IntrinsicData data) { + out << data.name; + return out; +} +using MslIntrinsicTest = testing::TestWithParam; +TEST_P(MslIntrinsicTest, Emit) { + auto param = GetParam(); + + ast::Module m; + GeneratorImpl g(&m); + EXPECT_EQ(g.generate_intrinsic_name(param.name), param.msl_name); +} +INSTANTIATE_TEST_SUITE_P(MslGeneratorImplTest, + MslIntrinsicTest, + testing::Values(IntrinsicData{"any", "any"}, + IntrinsicData{"all", "all"}, + IntrinsicData{"dot", "dot"}, + IntrinsicData{"dpdx", "dfdx"}, + IntrinsicData{"dpdx_coarse", "dfdx"}, + IntrinsicData{"dpdx_fine", "dfdx"}, + IntrinsicData{"dpdy", "dfdy"}, + IntrinsicData{"dpdy_coarse", "dfdy"}, + IntrinsicData{"dpdy_fine", "dfdy"}, + IntrinsicData{"fwidth", "fwidth"}, + IntrinsicData{"fwidth_coarse", + "fwidth"}, + IntrinsicData{"fwidth_fine", "fwidth"}, + IntrinsicData{"is_finite", "isfinite"}, + IntrinsicData{"is_inf", "isinf"}, + IntrinsicData{"is_nan", "isnan"}, + IntrinsicData{"is_normal", "isnormal"}, + IntrinsicData{"select", "select"})); + +TEST_F(MslGeneratorImplTest, DISABLED_Intrinsic_OuterProduct) { + ast::type::F32Type f32; + ast::type::VectorType vec2(&f32, 2); + ast::type::VectorType vec3(&f32, 3); + + auto a = + std::make_unique("a", ast::StorageClass::kNone, &vec2); + auto b = + std::make_unique("b", ast::StorageClass::kNone, &vec3); + + ast::ExpressionList params; + params.push_back(std::make_unique("a")); + params.push_back(std::make_unique("b")); + + ast::CallExpression call( + std::make_unique("outer_product"), + std::move(params)); + + Context ctx; + ast::Module m; + TypeDeterminer td(&ctx, &m); + td.RegisterVariableForTesting(a.get()); + td.RegisterVariableForTesting(b.get()); + + m.AddGlobalVariable(std::move(a)); + m.AddGlobalVariable(std::move(b)); + + ASSERT_TRUE(td.Determine()) << td.error(); + ASSERT_TRUE(td.DetermineResultType(&call)) << td.error(); + + GeneratorImpl g(&m); + + g.increment_indent(); + ASSERT_TRUE(g.EmitExpression(&call)) << g.error(); + EXPECT_EQ(g.result(), " float3x2(a * b[0], a * b[1], a * b[2])"); +} + +TEST_F(MslGeneratorImplTest, Intrinsic_Bad_Name) { + ast::Module m; + GeneratorImpl g(&m); + EXPECT_EQ(g.generate_intrinsic_name("unknown name"), ""); +} + +TEST_F(MslGeneratorImplTest, Intrinsic_Call) { + ast::ExpressionList params; + params.push_back(std::make_unique("param1")); + params.push_back(std::make_unique("param2")); + + ast::CallExpression call(std::make_unique("dot"), + std::move(params)); + + ast::Module m; + GeneratorImpl g(&m); + g.increment_indent(); + ASSERT_TRUE(g.EmitExpression(&call)) << g.error(); + EXPECT_EQ(g.result(), " dot(param1, param2)"); +} + +} // namespace +} // namespace msl +} // namespace writer +} // namespace tint