[ir] Add a SPIRV BuiltinCall instruction

This CL adds a `BuiltinCall` instruction to SPIR-V IR and moves
`VectorTimesScalar` over to the builtin.

Bug: tint:1718
Change-Id: I4fd92aab01d32897c5bdb4add97e22b698ded377
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/150100
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/lang/core/ir/builder.h b/src/tint/lang/core/ir/builder.h
index ae5ae69..ffa0347 100644
--- a/src/tint/lang/core/ir/builder.h
+++ b/src/tint/lang/core/ir/builder.h
@@ -620,13 +620,26 @@
             InstructionResult(type), func, Values(std::forward<ARGS>(args)...)));
     }
 
+    /// Creates a core builtin call instruction
+    /// @param type the return type of the call
+    /// @param func the builtin function to call
+    /// @param args the call arguments
+    /// @returns the instruction
+    template <typename KLASS, typename FUNC, typename... ARGS>
+    tint::traits::EnableIf<tint::traits::IsTypeOrDerived<KLASS, ir::BuiltinCall>, KLASS*>
+    Call(const core::type::Type* type, FUNC func, ARGS&&... args) {
+        return Append(ir.instructions.Create<KLASS>(InstructionResult(type), func,
+                                                    Values(std::forward<ARGS>(args)...)));
+    }
+
     /// Creates an intrinsic call instruction
     /// @param type the return type of the call
     /// @param kind the intrinsic function to call
     /// @param args the call arguments
     /// @returns the intrinsic call instruction
     template <typename KLASS, typename KIND, typename... ARGS>
-    ir::IntrinsicCall* Call(const core::type::Type* type, KIND kind, ARGS&&... args) {
+    tint::traits::EnableIf<tint::traits::IsTypeOrDerived<KLASS, ir::IntrinsicCall>, KLASS*>
+    Call(const core::type::Type* type, KIND kind, ARGS&&... args) {
         return Append(ir.instructions.Create<KLASS>(InstructionResult(type), kind,
                                                     Values(std::forward<ARGS>(args)...)));
     }
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index d712e65..182c035 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -527,7 +527,9 @@
         [&](Convert*) {},                                      //
         [&](Discard*) {},                                      //
         [&](UserCall*) {},                                     //
-        [&](Default) { AddError(call, InstError(call, "missing validation")); });
+        [&](Default) {
+            // Validation of custom IR instructions
+        });
 }
 
 void Validator::CheckCoreBuiltinCall(CoreBuiltinCall* call) {
diff --git a/src/tint/lang/spirv/ir/BUILD.cmake b/src/tint/lang/spirv/ir/BUILD.cmake
index 7f0e19a..ad4b8a2 100644
--- a/src/tint/lang/spirv/ir/BUILD.cmake
+++ b/src/tint/lang/spirv/ir/BUILD.cmake
@@ -26,6 +26,10 @@
 # Kind:      lib
 ################################################################################
 tint_add_target(tint_lang_spirv_ir lib
+  lang/spirv/ir/builtin_call.cc
+  lang/spirv/ir/builtin_call.h
+  lang/spirv/ir/function.cc
+  lang/spirv/ir/function.h
   lang/spirv/ir/intrinsic.cc
   lang/spirv/ir/intrinsic.h
   lang/spirv/ir/intrinsic_call.cc
diff --git a/src/tint/lang/spirv/ir/BUILD.gn b/src/tint/lang/spirv/ir/BUILD.gn
index 452982f..9938f26 100644
--- a/src/tint/lang/spirv/ir/BUILD.gn
+++ b/src/tint/lang/spirv/ir/BUILD.gn
@@ -31,6 +31,10 @@
 
 libtint_source_set("ir") {
   sources = [
+    "builtin_call.cc",
+    "builtin_call.h",
+    "function.cc",
+    "function.h",
     "intrinsic.cc",
     "intrinsic.h",
     "intrinsic_call.cc",
diff --git a/src/tint/lang/spirv/ir/builtin_call.cc b/src/tint/lang/spirv/ir/builtin_call.cc
new file mode 100644
index 0000000..9cf3f08
--- /dev/null
+++ b/src/tint/lang/spirv/ir/builtin_call.cc
@@ -0,0 +1,34 @@
+// Copyright 2023 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 "src/tint/lang/spirv/ir/builtin_call.h"
+
+#include <utility>
+
+#include "src/tint/utils/ice/ice.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::spirv::ir::BuiltinCall);
+
+namespace tint::spirv::ir {
+
+BuiltinCall::BuiltinCall(core::ir::InstructionResult* result,
+                         Function func,
+                         VectorRef<core::ir::Value*> arguments)
+    : Base(result, arguments), func_(func) {
+    TINT_ASSERT(func != Function::kNone);
+}
+
+BuiltinCall::~BuiltinCall() = default;
+
+}  // namespace tint::spirv::ir
diff --git a/src/tint/lang/spirv/ir/builtin_call.h b/src/tint/lang/spirv/ir/builtin_call.h
new file mode 100644
index 0000000..9f8b888
--- /dev/null
+++ b/src/tint/lang/spirv/ir/builtin_call.h
@@ -0,0 +1,50 @@
+// Copyright 2023 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.
+
+#ifndef SRC_TINT_LANG_SPIRV_IR_BUILTIN_CALL_H_
+#define SRC_TINT_LANG_SPIRV_IR_BUILTIN_CALL_H_
+
+#include <string>
+
+#include "src/tint/lang/core/ir/builtin_call.h"
+#include "src/tint/lang/spirv/ir/function.h"
+#include "src/tint/utils/rtti/castable.h"
+
+namespace tint::spirv::ir {
+
+/// A spirv builtin call instruction in the IR.
+class BuiltinCall : public Castable<BuiltinCall, core::ir::BuiltinCall> {
+  public:
+    /// Constructor
+    /// @param result the result value
+    /// @param func the builtin function
+    /// @param args the conversion arguments
+    BuiltinCall(core::ir::InstructionResult* result,
+                Function func,
+                VectorRef<core::ir::Value*> args = tint::Empty);
+    ~BuiltinCall() override;
+
+    /// @returns the builtin function
+    Function Func() { return func_; }
+
+    /// @returns the friendly name for the instruction
+    std::string FriendlyName() override { return str(func_); }
+
+  private:
+    Function func_;
+};
+
+}  // namespace tint::spirv::ir
+
+#endif  // SRC_TINT_LANG_SPIRV_IR_BUILTIN_CALL_H_
diff --git a/src/tint/lang/spirv/ir/function.cc b/src/tint/lang/spirv/ir/function.cc
new file mode 100644
index 0000000..f7612f3
--- /dev/null
+++ b/src/tint/lang/spirv/ir/function.cc
@@ -0,0 +1,38 @@
+// Copyright 2023 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.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by 'tools/src/cmd/gen' using the template:
+//   src/tint/lang/spirv/ir/function.cc.tmpl
+//
+// To regenerate run: './tools/run gen'
+//
+//                       Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#include "src/tint/lang/spirv/ir/function.h"
+
+namespace tint::spirv::ir {
+
+const char* str(Function i) {
+    switch (i) {
+        case Function::kNone:
+            return "<none>";
+        case Function::kVectorTimesScalar:
+            return "spirv.vector_times_scalar";
+    }
+    return "<unknown>";
+}
+
+}  // namespace tint::spirv::ir
diff --git a/src/tint/lang/spirv/ir/function.cc.tmpl b/src/tint/lang/spirv/ir/function.cc.tmpl
new file mode 100644
index 0000000..2a301e3
--- /dev/null
+++ b/src/tint/lang/spirv/ir/function.cc.tmpl
@@ -0,0 +1,31 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate function.cc
+
+To update the generated file, run:
+    ./tools/run gen
+
+See:
+* tools/src/cmd/gen for structures used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- $I := LoadIntrinsics "src/tint/lang/spirv/ir/spirv.def" -}}
+#include "src/tint/lang/spirv/ir/function.h"
+
+namespace tint::spirv::ir {
+
+const char* str(Function i) {
+    switch (i) {
+        case Function::kNone:
+            return "<none>";
+{{- range $I.Sem.Builtins  }}
+        case Function::k{{PascalCase .Name}}:
+            return "spirv.{{.Name}}";
+{{- end  }}
+    }
+    return "<unknown>";
+}
+
+}  // namespace tint::spirv::ir
diff --git a/src/tint/lang/spirv/ir/function.h b/src/tint/lang/spirv/ir/function.h
new file mode 100644
index 0000000..99bf16d
--- /dev/null
+++ b/src/tint/lang/spirv/ir/function.h
@@ -0,0 +1,55 @@
+// Copyright 2023 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.
+
+////////////////////////////////////////////////////////////////////////////////
+// File generated by 'tools/src/cmd/gen' using the template:
+//   src/tint/lang/spirv/ir/function.h.tmpl
+//
+// To regenerate run: './tools/run gen'
+//
+//                       Do not modify this file directly
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef SRC_TINT_LANG_SPIRV_IR_FUNCTION_H_
+#define SRC_TINT_LANG_SPIRV_IR_FUNCTION_H_
+
+#include <cstdint>
+#include <string>
+
+#include "src/tint/utils/traits/traits.h"
+
+// \cond DO_NOT_DOCUMENT
+namespace tint::spirv::ir {
+
+/// Enumerator of all builtin functions
+enum class Function : uint8_t {
+    kVectorTimesScalar,
+    kNone,
+};
+
+/// @returns the name of the builtin function type. The spelling, including
+/// case, matches the name in the WGSL spec.
+const char* str(Function i);
+
+/// Emits the name of the builtin function type. The spelling, including case,
+/// matches the name in the WGSL spec.
+template <typename STREAM, typename = traits::EnableIfIsOStream<STREAM>>
+auto& operator<<(STREAM& o, Function i) {
+    return o << str(i);
+}
+
+}  // namespace tint::spirv::ir
+// \endcond
+
+#endif  // SRC_TINT_LANG_SPIRV_IR_FUNCTION_H_
diff --git a/src/tint/lang/spirv/ir/function.h.tmpl b/src/tint/lang/spirv/ir/function.h.tmpl
new file mode 100644
index 0000000..e983e8a
--- /dev/null
+++ b/src/tint/lang/spirv/ir/function.h.tmpl
@@ -0,0 +1,49 @@
+{{- /*
+--------------------------------------------------------------------------------
+Template file for use with tools/src/cmd/gen to generate function.h
+
+To update the generated file, run:
+    ./tools/run gen
+
+See:
+* tools/src/cmd/gen for structures used by this template
+* https://golang.org/pkg/text/template/ for documentation on the template syntax
+--------------------------------------------------------------------------------
+*/ -}}
+
+{{- $I := LoadIntrinsics "src/tint/lang/spirv/ir/spirv.def" -}}
+
+#ifndef SRC_TINT_LANG_SPIRV_IR_FUNCTION_H_
+#define SRC_TINT_LANG_SPIRV_IR_FUNCTION_H_
+
+#include <cstdint>
+#include <string>
+
+#include "src/tint/utils/traits/traits.h"
+
+// \cond DO_NOT_DOCUMENT
+namespace tint::spirv::ir {
+
+/// Enumerator of all builtin functions
+enum class Function : uint8_t {
+{{- range $I.Sem.Builtins }}
+    k{{PascalCase .Name}},
+{{- end }}
+    kNone,
+};
+
+/// @returns the name of the builtin function type. The spelling, including
+/// case, matches the name in the WGSL spec.
+const char* str(Function i);
+
+/// Emits the name of the builtin function type. The spelling, including case,
+/// matches the name in the WGSL spec.
+template <typename STREAM, typename = traits::EnableIfIsOStream<STREAM>>
+auto& operator<<(STREAM& o, Function i) {
+  return o << str(i);
+}
+
+}  // namespace tint::spirv::ir
+// \endcond
+
+#endif  // SRC_TINT_LANG_SPIRV_IR_FUNCTION_H_
diff --git a/src/tint/lang/spirv/ir/intrinsic.cc b/src/tint/lang/spirv/ir/intrinsic.cc
index d2141ef..febe468 100644
--- a/src/tint/lang/spirv/ir/intrinsic.cc
+++ b/src/tint/lang/spirv/ir/intrinsic.cc
@@ -122,9 +122,6 @@
     if (str == "vector_times_matrix") {
         return Intrinsic::kVectorTimesMatrix;
     }
-    if (str == "vector_times_scalar") {
-        return Intrinsic::kVectorTimesScalar;
-    }
     return Intrinsic::kUndefined;
 }
 
@@ -194,8 +191,6 @@
             return "select";
         case Intrinsic::kVectorTimesMatrix:
             return "vector_times_matrix";
-        case Intrinsic::kVectorTimesScalar:
-            return "vector_times_scalar";
     }
     return "<unknown>";
 }
diff --git a/src/tint/lang/spirv/ir/intrinsic.h b/src/tint/lang/spirv/ir/intrinsic.h
index d94ed51..c99eb53 100644
--- a/src/tint/lang/spirv/ir/intrinsic.h
+++ b/src/tint/lang/spirv/ir/intrinsic.h
@@ -65,7 +65,6 @@
     kSampledImage,
     kSelect,
     kVectorTimesMatrix,
-    kVectorTimesScalar,
 };
 
 /// @param value the enum value
@@ -117,7 +116,6 @@
     "sampled_image",
     "select",
     "vector_times_matrix",
-    "vector_times_scalar",
 };
 
 }  // namespace tint::spirv::ir
diff --git a/src/tint/lang/spirv/ir/spirv.def b/src/tint/lang/spirv/ir/spirv.def
index c395c30..754c6a5 100644
--- a/src/tint/lang/spirv/ir/spirv.def
+++ b/src/tint/lang/spirv/ir/spirv.def
@@ -16,6 +16,17 @@
 // Spirv builtin definition file                                              //
 ////////////////////////////////////////////////////////////////////////////////
 
+// TODO(crbug.com/2036): add an include facility and move these duplicate match and type lines
+// into a common file.
+match f32_f16: f32 | f16
+
+type f32
+type f16
+type vec2<T>
+type vec3<T>
+type vec4<T>
+@display("vec{N}<{T}>")     type vec<N: num, T>
+
 ////////////////////////////////////////////////////////////////////////////////
 // Enumerators                                                                //
 ////////////////////////////////////////////////////////////////////////////////
@@ -52,5 +63,10 @@
   sampled_image
   select
   vector_times_matrix
-  vector_times_scalar
 }
+
+////////////////////////////////////////////////////////////////////////////////
+// Builtin Functions                                                          //
+////////////////////////////////////////////////////////////////////////////////
+fn vector_times_scalar<T: f32_f16, N: num>(vec<N, T>, T) -> vec<N, T>
+
diff --git a/src/tint/lang/spirv/writer/printer/printer.cc b/src/tint/lang/spirv/writer/printer/printer.cc
index 331986f..1e7dbc7 100644
--- a/src/tint/lang/spirv/writer/printer/printer.cc
+++ b/src/tint/lang/spirv/writer/printer/printer.cc
@@ -763,6 +763,7 @@
             [&](core::ir::Binary* b) { EmitBinary(b); },                          //
             [&](core::ir::Bitcast* b) { EmitBitcast(b); },                        //
             [&](core::ir::CoreBuiltinCall* b) { EmitCoreBuiltinCall(b); },        //
+            [&](spirv::ir::BuiltinCall* b) { EmitSpirvBuiltinCall(b); },          //
             [&](core::ir::Construct* c) { EmitConstruct(c); },                    //
             [&](core::ir::Convert* c) { EmitConvert(c); },                        //
             [&](spirv::ir::IntrinsicCall* i) { EmitIntrinsicCall(i); },           //
@@ -1086,6 +1087,29 @@
                                 {Type(ty), Value(bitcast), Value(bitcast->Val())});
 }
 
+void Printer::EmitSpirvBuiltinCall(spirv::ir::BuiltinCall* builtin) {
+    auto id = Value(builtin);
+
+    spv::Op op = spv::Op::Max;
+    switch (builtin->Func()) {
+        case spirv::ir::Function::kVectorTimesScalar:
+            op = spv::Op::OpVectorTimesScalar;
+            break;
+        case spirv::ir::Function::kNone:
+            TINT_ICE() << "undefined spirv ir function";
+            return;
+    }
+
+    OperandList operands;
+    if (!builtin->Result()->Type()->Is<core::type::Void>()) {
+        operands = {Type(builtin->Result()->Type()), id};
+    }
+    for (auto* arg : builtin->Args()) {
+        operands.push_back(Value(arg));
+    }
+    current_function_.push_inst(op, operands);
+}
+
 void Printer::EmitCoreBuiltinCall(core::ir::CoreBuiltinCall* builtin) {
     auto* result_ty = builtin->Result()->Type();
 
@@ -1630,9 +1654,6 @@
         case spirv::ir::Intrinsic::kVectorTimesMatrix:
             op = spv::Op::OpVectorTimesMatrix;
             break;
-        case spirv::ir::Intrinsic::kVectorTimesScalar:
-            op = spv::Op::OpVectorTimesScalar;
-            break;
         case spirv::ir::Intrinsic::kUndefined:
             TINT_ICE() << "undefined spirv intrinsic";
             return;
diff --git a/src/tint/lang/spirv/writer/printer/printer.h b/src/tint/lang/spirv/writer/printer/printer.h
index 36d10a4..f991bfe 100644
--- a/src/tint/lang/spirv/writer/printer/printer.h
+++ b/src/tint/lang/spirv/writer/printer/printer.h
@@ -24,6 +24,7 @@
 #include "src/tint/lang/core/ir/builder.h"
 #include "src/tint/lang/core/ir/constant.h"
 #include "src/tint/lang/core/texel_format.h"
+#include "src/tint/lang/spirv/ir/builtin_call.h"
 #include "src/tint/lang/spirv/ir/intrinsic_call.h"
 #include "src/tint/lang/spirv/writer/common/binary_writer.h"
 #include "src/tint/lang/spirv/writer/common/function.h"
@@ -201,6 +202,10 @@
 
     /// Emit a builtin function call instruction.
     /// @param call the builtin call instruction to emit
+    void EmitSpirvBuiltinCall(spirv::ir::BuiltinCall* call);
+
+    /// Emit a builtin function call instruction.
+    /// @param call the builtin call instruction to emit
     void EmitCoreBuiltinCall(core::ir::CoreBuiltinCall* call);
 
     /// Emit a construct instruction.
diff --git a/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc b/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc
index 19b6e99..c222b71 100644
--- a/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc
+++ b/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc
@@ -19,7 +19,8 @@
 #include "src/tint/lang/core/ir/builder.h"
 #include "src/tint/lang/core/ir/module.h"
 #include "src/tint/lang/core/ir/validator.h"
-#include "src/tint/lang/spirv/ir/intrinsic_call.h"
+#include "src/tint/lang/spirv/ir/builtin_call.h"
+#include "src/tint/lang/spirv/ir/function.h"
 
 using namespace tint::core::number_suffixes;  // NOLINT
 
@@ -89,8 +90,8 @@
         auto* result_ty = binary->Result()->Type();
         if (result_ty->is_float_vector() && binary->Kind() == core::ir::Binary::Kind::kMultiply) {
             // Use OpVectorTimesScalar for floating point multiply.
-            auto* vts = b.Call<spirv::ir::IntrinsicCall>(result_ty,
-                                                         spirv::ir::Intrinsic::kVectorTimesScalar);
+            auto* vts =
+                b.Call<spirv::ir::BuiltinCall>(result_ty, spirv::ir::Function::kVectorTimesScalar);
             if (binary->LHS()->Type()->Is<core::type::Scalar>()) {
                 vts->AppendArg(binary->RHS());
                 vts->AppendArg(binary->LHS());