[hlsl-writer] Generate intrinsics.

This CL adds the beginning of intrinsic emission for the HLSL backend.
The `outer_product`, `is_normal` and `select` intrinsics are currently
missing.

Bug: tint:7
Change-Id: Ice7a2b285eeb52041e3accd9751e127d6c5a0177
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/26927
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: David Neto <dneto@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index 3d4f7d3..e8648f8 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1073,6 +1073,7 @@
     "src/writer/hlsl/generator_impl_function_test.cc",
     "src/writer/hlsl/generator_impl_identifier_test.cc",
     "src/writer/hlsl/generator_impl_if_test.cc",
+    "src/writer/hlsl/generator_impl_intrinsic_test.cc",
     "src/writer/hlsl/generator_impl_loop_test.cc",
     "src/writer/hlsl/generator_impl_member_accessor_test.cc",
     "src/writer/hlsl/generator_impl_module_constant_test.cc",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ddf5b85..ed9bf7c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -584,6 +584,7 @@
     writer/hlsl/generator_impl_function_test.cc
     writer/hlsl/generator_impl_identifier_test.cc
     writer/hlsl/generator_impl_if_test.cc
+    writer/hlsl/generator_impl_intrinsic_test.cc
     writer/hlsl/generator_impl_loop_test.cc
     writer/hlsl/generator_impl_member_accessor_test.cc
     writer/hlsl/generator_impl_module_constant_test.cc
diff --git a/src/writer/hlsl/generator_impl.cc b/src/writer/hlsl/generator_impl.cc
index 83e34e1..554f332 100644
--- a/src/writer/hlsl/generator_impl.cc
+++ b/src/writer/hlsl/generator_impl.cc
@@ -339,6 +339,49 @@
   return true;
 }
 
+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 == "dpdy") {
+    return "ddy";
+  }
+  if (name == "dpdy_fine") {
+    return "ddy_fine";
+  }
+  if (name == "dpdy_coarse") {
+    return "ddy_coarse";
+  }
+  if (name == "dpdx") {
+    return "ddx";
+  }
+  if (name == "dpdx_fine") {
+    return "ddx_fine";
+  }
+  if (name == "dpdx_coarse") {
+    return "ddx_coarse";
+  }
+  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";
@@ -347,8 +390,91 @@
 
   auto* ident = expr->func()->AsIdentifier();
   if (!ident->has_path() && ast::intrinsic::IsIntrinsic(ident->name())) {
-    error_ = "Intrinsics not supported in HLSL backend.";
-    return false;
+    const auto& params = expr->params();
+    if (ident->name() == "select") {
+      error_ = "select not supported in HLSL backend yet";
+      return false;
+    } else if (ident->name() == "is_normal") {
+      error_ = "is_normal not supported in HLSL backend yet";
+      return false;
+    } else 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/hlsl/generator_impl.h b/src/writer/hlsl/generator_impl.h
index 503f6de..eac3f65 100644
--- a/src/writer/hlsl/generator_impl.h
+++ b/src/writer/hlsl/generator_impl.h
@@ -197,6 +197,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);
   /// Converts a builtin to an attribute name
   /// @param builtin the builtin to convert
   /// @returns the string name of the builtin or blank on error
diff --git a/src/writer/hlsl/generator_impl_intrinsic_test.cc b/src/writer/hlsl/generator_impl_intrinsic_test.cc
new file mode 100644
index 0000000..d485b37
--- /dev/null
+++ b/src/writer/hlsl/generator_impl_intrinsic_test.cc
@@ -0,0 +1,136 @@
+// 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/hlsl/generator_impl.h"
+
+namespace tint {
+namespace writer {
+namespace hlsl {
+namespace {
+
+using HlslGeneratorImplTest = testing::Test;
+
+struct IntrinsicData {
+  const char* name;
+  const char* hlsl_name;
+};
+inline std::ostream& operator<<(std::ostream& out, IntrinsicData data) {
+  out << data.name;
+  return out;
+}
+using HlslIntrinsicTest = testing::TestWithParam<IntrinsicData>;
+TEST_P(HlslIntrinsicTest, Emit) {
+  auto param = GetParam();
+
+  ast::Module m;
+  GeneratorImpl g(&m);
+  EXPECT_EQ(g.generate_intrinsic_name(param.name), param.hlsl_name);
+}
+INSTANTIATE_TEST_SUITE_P(
+    HlslGeneratorImplTest,
+    HlslIntrinsicTest,
+    testing::Values(IntrinsicData{"any", "any"},
+                    IntrinsicData{"all", "all"},
+                    IntrinsicData{"dot", "dot"},
+                    IntrinsicData{"dpdx", "ddx"},
+                    IntrinsicData{"dpdx_coarse", "ddx_coarse"},
+                    IntrinsicData{"dpdx_fine", "ddx_fine"},
+                    IntrinsicData{"dpdy", "ddy"},
+                    IntrinsicData{"dpdy_coarse", "ddy_coarse"},
+                    IntrinsicData{"dpdy_fine", "ddy_fine"},
+                    IntrinsicData{"fwidth", "fwidth"},
+                    IntrinsicData{"fwidth_coarse", "fwidth"},
+                    IntrinsicData{"fwidth_fine", "fwidth"},
+                    IntrinsicData{"is_finite", "isfinite"},
+                    IntrinsicData{"is_inf", "isinf"},
+                    IntrinsicData{"is_nan", "isnan"}));
+
+TEST_F(HlslGeneratorImplTest, DISABLED_Intrinsic_IsNormal) {
+  FAIL();
+}
+
+TEST_F(HlslGeneratorImplTest, DISABLED_Intrinsic_Select) {
+  FAIL();
+}
+
+TEST_F(HlslGeneratorImplTest, 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(HlslGeneratorImplTest, Intrinsic_Bad_Name) {
+  ast::Module m;
+  GeneratorImpl g(&m);
+  EXPECT_EQ(g.generate_intrinsic_name("unknown name"), "");
+}
+
+TEST_F(HlslGeneratorImplTest, 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 hlsl
+}  // namespace writer
+}  // namespace tint