[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