[msl-writer] Emit intrinsics.

This CL adds intrinsics to the MSL backend.

Bug: tint:8, tint:159
Change-Id: I03e3c4bdf234ec4ca437ab1b1a0d4835e3342b0c
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/25500
Reviewed-by: David Neto <dneto@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 888a974..035e61f 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -996,6 +996,7 @@
     "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 5575b35..49304eb 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -527,6 +527,7 @@
     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 21848a8..a6fe654 100644
--- a/src/writer/msl/generator_impl.cc
+++ b/src/writer/msl/generator_impl.cc
@@ -400,6 +400,43 @@
   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 @@
   }
 
   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 3f0a29a..3e4aa4f 100644
--- a/src/writer/msl/generator_impl.h
+++ b/src/writer/msl/generator_impl.h
@@ -224,6 +224,10 @@
   /// @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 0000000..b52955c
--- /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<IntrinsicData>;
+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<ast::Variable>("a", ast::StorageClass::kNone, &vec2);
+  auto b =
+      std::make_unique<ast::Variable>("b", ast::StorageClass::kNone, &vec3);
+
+  ast::ExpressionList params;
+  params.push_back(std::make_unique<ast::IdentifierExpression>("a"));
+  params.push_back(std::make_unique<ast::IdentifierExpression>("b"));
+
+  ast::CallExpression call(
+      std::make_unique<ast::IdentifierExpression>("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<ast::IdentifierExpression>("param1"));
+  params.push_back(std::make_unique<ast::IdentifierExpression>("param2"));
+
+  ast::CallExpression call(std::make_unique<ast::IdentifierExpression>("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