[hlsl] Polyfill the HLSL IR select builtin.

This Cl adds a polyfil for the `select` builtin and converts it to a
custom HLSL ternary instruction.

Bug: 42251045
Change-Id: Iecb0b6d6e10fdd48c2df332890f5757dedd22ccf
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/195374
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/lang/hlsl/ir/BUILD.bazel b/src/tint/lang/hlsl/ir/BUILD.bazel
index 758aef2..b8a34ae 100644
--- a/src/tint/lang/hlsl/ir/BUILD.bazel
+++ b/src/tint/lang/hlsl/ir/BUILD.bazel
@@ -40,9 +40,11 @@
   name = "ir",
   srcs = [
     "builtin_call.cc",
+    "ternary.cc",
   ],
   hdrs = [
     "builtin_call.h",
+    "ternary.h",
   ],
   deps = [
     "//src/tint/api/common",
@@ -75,6 +77,7 @@
   alwayslink = True,
   srcs = [
     "builtin_call_test.cc",
+    "ternary_test.cc",
   ],
   deps = [
     "//src/tint/api/common",
diff --git a/src/tint/lang/hlsl/ir/BUILD.cmake b/src/tint/lang/hlsl/ir/BUILD.cmake
index c94d5f4..f89052f 100644
--- a/src/tint/lang/hlsl/ir/BUILD.cmake
+++ b/src/tint/lang/hlsl/ir/BUILD.cmake
@@ -41,6 +41,8 @@
 tint_add_target(tint_lang_hlsl_ir lib
   lang/hlsl/ir/builtin_call.cc
   lang/hlsl/ir/builtin_call.h
+  lang/hlsl/ir/ternary.cc
+  lang/hlsl/ir/ternary.h
 )
 
 tint_target_add_dependencies(tint_lang_hlsl_ir lib
@@ -73,6 +75,7 @@
 ################################################################################
 tint_add_target(tint_lang_hlsl_ir_test test
   lang/hlsl/ir/builtin_call_test.cc
+  lang/hlsl/ir/ternary_test.cc
 )
 
 tint_target_add_dependencies(tint_lang_hlsl_ir_test test
diff --git a/src/tint/lang/hlsl/ir/BUILD.gn b/src/tint/lang/hlsl/ir/BUILD.gn
index 2cb3e39..21087ee 100644
--- a/src/tint/lang/hlsl/ir/BUILD.gn
+++ b/src/tint/lang/hlsl/ir/BUILD.gn
@@ -46,6 +46,8 @@
   sources = [
     "builtin_call.cc",
     "builtin_call.h",
+    "ternary.cc",
+    "ternary.h",
   ]
   deps = [
     "${tint_src_dir}/api/common",
@@ -73,7 +75,10 @@
 }
 if (tint_build_unittests) {
   tint_unittests_source_set("unittests") {
-    sources = [ "builtin_call_test.cc" ]
+    sources = [
+      "builtin_call_test.cc",
+      "ternary_test.cc",
+    ]
     deps = [
       "${tint_src_dir}:gmock_and_gtest",
       "${tint_src_dir}/api/common",
diff --git a/src/tint/lang/hlsl/ir/ternary.cc b/src/tint/lang/hlsl/ir/ternary.cc
new file mode 100644
index 0000000..64af60a10
--- /dev/null
+++ b/src/tint/lang/hlsl/ir/ternary.cc
@@ -0,0 +1,49 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/hlsl/ir/ternary.h"
+
+#include "src/tint/lang/core/ir/module.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::hlsl::ir::Ternary);
+
+namespace tint::hlsl::ir {
+
+Ternary::Ternary(core::ir::InstructionResult* result, VectorRef<core::ir::Value*> args) {
+    AddResult(result);
+    AddOperands(ArgsOperandOffset(), args);
+}
+
+Ternary::~Ternary() = default;
+
+Ternary* Ternary::Clone(core::ir::CloneContext& ctx) {
+    auto new_result = ctx.Clone(Result(0));
+    auto new_args = ctx.Remap<Ternary::kDefaultNumOperands>(operands_);
+    return ctx.ir.allocators.instructions.Create<Ternary>(new_result, new_args);
+}
+
+}  // namespace tint::hlsl::ir
diff --git a/src/tint/lang/hlsl/ir/ternary.h b/src/tint/lang/hlsl/ir/ternary.h
new file mode 100644
index 0000000..136dccd
--- /dev/null
+++ b/src/tint/lang/hlsl/ir/ternary.h
@@ -0,0 +1,66 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef SRC_TINT_LANG_HLSL_IR_TERNARY_H_
+#define SRC_TINT_LANG_HLSL_IR_TERNARY_H_
+
+#include <string>
+
+#include "src/tint/lang/core/ir/call.h"
+#include "src/tint/lang/core/ir/clone_context.h"
+#include "src/tint/lang/core/ir/instruction_result.h"
+#include "src/tint/lang/core/ir/value.h"
+#include "src/tint/utils/rtti/castable.h"
+
+namespace tint::hlsl::ir {
+
+/// A ternary instruction in the IR.
+class Ternary final : public Castable<Ternary, core::ir::Call> {
+  public:
+    /// Constructor
+    Ternary(core::ir::InstructionResult* result, VectorRef<core::ir::Value*> args);
+    ~Ternary() override;
+
+    /// @copydoc Instruction::Clone()
+    Ternary* Clone(core::ir::CloneContext& ctx) override;
+
+    /// @returns the false value
+    core::ir::Value* False() const { return operands_[ArgsOperandOffset() + 0]; }
+
+    /// @returns the true value
+    core::ir::Value* True() const { return operands_[ArgsOperandOffset() + 1]; }
+
+    /// @returns the compare value
+    core::ir::Value* Cmp() const { return operands_[ArgsOperandOffset() + 2]; }
+
+    /// @returns the friendly name for the instruction
+    std::string FriendlyName() const override { return "hlsl.ternary"; }
+};
+
+}  // namespace tint::hlsl::ir
+
+#endif  // SRC_TINT_LANG_HLSL_IR_TERNARY_H_
diff --git a/src/tint/lang/hlsl/ir/ternary_test.cc b/src/tint/lang/hlsl/ir/ternary_test.cc
new file mode 100644
index 0000000..13bb252
--- /dev/null
+++ b/src/tint/lang/hlsl/ir/ternary_test.cc
@@ -0,0 +1,95 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/hlsl/ir/ternary.h"
+
+#include "gmock/gmock.h"
+#include "src/tint/lang/core/ir/ir_helper_test.h"
+
+using namespace tint::core::fluent_types;     // NOLINT
+using namespace tint::core::number_suffixes;  // NOLINT
+
+namespace tint::hlsl::ir {
+namespace {
+
+using HlslIRTest = core::ir::IRTestHelper;
+using HlslIRDeathTest = HlslIRTest;
+
+TEST_F(HlslIRTest, SetsUsage) {
+    auto* true_ = b.Constant(u32(1));
+    auto* false_ = b.Constant(u32(2));
+    auto* cmp = b.Constant(true);
+
+    Vector<core::ir::Value*, 3> args = {false_, true_, cmp};
+    auto* t = b.ir.allocators.instructions.Create<Ternary>(b.InstructionResult(ty.u32()), args);
+
+    EXPECT_THAT(false_->Usages(), testing::UnorderedElementsAre(core::ir::Usage{t, 0u}));
+    EXPECT_THAT(true_->Usages(), testing::UnorderedElementsAre(core::ir::Usage{t, 1u}));
+    EXPECT_THAT(cmp->Usages(), testing::UnorderedElementsAre(core::ir::Usage{t, 2u}));
+}
+
+TEST_F(HlslIRTest, Result) {
+    auto* true_ = b.Constant(u32(1));
+    auto* false_ = b.Constant(u32(2));
+    auto* cmp = b.Constant(true);
+
+    Vector<core::ir::Value*, 3> args = {false_, true_, cmp};
+    auto* t = b.ir.allocators.instructions.Create<Ternary>(b.InstructionResult(ty.u32()), args);
+
+    EXPECT_EQ(t->Results().Length(), 1u);
+
+    EXPECT_TRUE(t->Result(0)->Is<core::ir::InstructionResult>());
+    EXPECT_EQ(t, t->Result(0)->Instruction());
+}
+
+TEST_F(HlslIRTest, Clone) {
+    auto* true_ = b.Constant(u32(1));
+    auto* false_ = b.Constant(u32(2));
+    auto* cmp = b.Constant(true);
+
+    Vector<core::ir::Value*, 3> args = {false_, true_, cmp};
+    auto* t = b.ir.allocators.instructions.Create<Ternary>(b.InstructionResult(ty.u32()), args);
+
+    auto* new_t = clone_ctx.Clone(t);
+
+    EXPECT_NE(t, new_t);
+
+    EXPECT_NE(t->Result(0), new_t->Result(0));
+    EXPECT_EQ(ty.u32(), new_t->Result(0)->Type());
+
+    EXPECT_NE(nullptr, new_t->True());
+    EXPECT_EQ(t->True(), new_t->True());
+
+    EXPECT_NE(nullptr, new_t->False());
+    EXPECT_EQ(t->False(), new_t->False());
+
+    EXPECT_NE(nullptr, new_t->Cmp());
+    EXPECT_EQ(t->Cmp(), new_t->Cmp());
+}
+
+}  // namespace
+}  // namespace tint::hlsl::ir
diff --git a/src/tint/lang/hlsl/writer/BUILD.bazel b/src/tint/lang/hlsl/writer/BUILD.bazel
index 38b2173..f1743b9 100644
--- a/src/tint/lang/hlsl/writer/BUILD.bazel
+++ b/src/tint/lang/hlsl/writer/BUILD.bazel
@@ -92,6 +92,7 @@
     "access_test.cc",
     "binary_test.cc",
     "bitcast_test.cc",
+    "builtin_test.cc",
     "constant_test.cc",
     "construct_test.cc",
     "convert_test.cc",
diff --git a/src/tint/lang/hlsl/writer/BUILD.cmake b/src/tint/lang/hlsl/writer/BUILD.cmake
index a0b853c..2ea842e 100644
--- a/src/tint/lang/hlsl/writer/BUILD.cmake
+++ b/src/tint/lang/hlsl/writer/BUILD.cmake
@@ -103,6 +103,7 @@
   lang/hlsl/writer/access_test.cc
   lang/hlsl/writer/binary_test.cc
   lang/hlsl/writer/bitcast_test.cc
+  lang/hlsl/writer/builtin_test.cc
   lang/hlsl/writer/constant_test.cc
   lang/hlsl/writer/construct_test.cc
   lang/hlsl/writer/convert_test.cc
diff --git a/src/tint/lang/hlsl/writer/BUILD.gn b/src/tint/lang/hlsl/writer/BUILD.gn
index 3e6fd99..36e2f40 100644
--- a/src/tint/lang/hlsl/writer/BUILD.gn
+++ b/src/tint/lang/hlsl/writer/BUILD.gn
@@ -95,6 +95,7 @@
         "access_test.cc",
         "binary_test.cc",
         "bitcast_test.cc",
+        "builtin_test.cc",
         "constant_test.cc",
         "construct_test.cc",
         "convert_test.cc",
diff --git a/src/tint/lang/hlsl/writer/builtin_test.cc b/src/tint/lang/hlsl/writer/builtin_test.cc
new file mode 100644
index 0000000..843951e
--- /dev/null
+++ b/src/tint/lang/hlsl/writer/builtin_test.cc
@@ -0,0 +1,87 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/core/fluent_types.h"
+#include "src/tint/lang/core/ir/function.h"
+#include "src/tint/lang/core/number.h"
+#include "src/tint/lang/hlsl/writer/helper_test.h"
+
+#include "gtest/gtest.h"
+
+using namespace tint::core::fluent_types;     // NOLINT
+using namespace tint::core::number_suffixes;  // NOLINT
+
+namespace tint::hlsl::writer {
+namespace {
+
+TEST_F(HlslWriterTest, SelectScalar) {
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(func->Block(), [&] {
+        auto* x = b.Let("x", 1_i);
+        auto* y = b.Let("y", 2_i);
+
+        auto* c = b.Call(ty.i32(), core::BuiltinFn::kSelect, x, y, true);
+        b.Let("w", c);
+        b.Return(func);
+    });
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+void foo() {
+  int x = 1;
+  int y = 2;
+  int w = ((true) ? (y) : (x));
+}
+
+)");
+}
+
+TEST_F(HlslWriterTest, SelectVector) {
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(func->Block(), [&] {
+        auto* x = b.Let("x", b.Construct<vec2<i32>>(1_i, 2_i));
+        auto* y = b.Let("y", b.Construct<vec2<i32>>(3_i, 4_i));
+        auto* cmp = b.Construct<vec2<bool>>(true, false);
+
+        auto* c = b.Call(ty.vec2<i32>(), core::BuiltinFn::kSelect, x, y, cmp);
+        b.Let("w", c);
+        b.Return(func);
+    });
+
+    ASSERT_TRUE(Generate()) << err_ << output_.hlsl;
+    EXPECT_EQ(output_.hlsl, R"(
+void foo() {
+  int2 x = int2(1, 2);
+  int2 y = int2(3, 4);
+  int2 w = ((bool2(true, false)) ? (y) : (x));
+}
+
+)");
+}
+
+}  // namespace
+}  // namespace tint::hlsl::writer
diff --git a/src/tint/lang/hlsl/writer/printer/printer.cc b/src/tint/lang/hlsl/writer/printer/printer.cc
index 79847cb..bbbb103 100644
--- a/src/tint/lang/hlsl/writer/printer/printer.cc
+++ b/src/tint/lang/hlsl/writer/printer/printer.cc
@@ -103,6 +103,7 @@
 #include "src/tint/lang/core/type/vector.h"
 #include "src/tint/lang/core/type/void.h"
 #include "src/tint/lang/hlsl/ir/builtin_call.h"
+#include "src/tint/lang/hlsl/ir/ternary.h"
 #include "src/tint/utils/containers/hashmap.h"
 #include "src/tint/utils/containers/map.h"
 #include "src/tint/utils/generator/text_generator.h"
@@ -625,13 +626,24 @@
                     [&](const core::ir::Var* var) { out << NameOf(var->Result(0)); },  //
 
                     [&](const hlsl::ir::BuiltinCall* c) { EmitHlslBuiltinCall(out, c); },  //
-
+                    [&](const hlsl::ir::Ternary* t) { EmitTernary(out, t); },              //
                     TINT_ICE_ON_NO_MATCH);
             },
             [&](const core::ir::FunctionParam* p) { out << NameOf(p); },  //
             TINT_ICE_ON_NO_MATCH);
     }
 
+    void EmitTernary(StringStream& out, const hlsl::ir::Ternary* t) {
+        out << "((";
+        EmitValue(out, t->Cmp());
+        out << ") ? (";
+        EmitValue(out, t->True());
+        out << ") : (";
+        EmitValue(out, t->False());
+        out << "))";
+        return;
+    }
+
     void EmitHlslBuiltinCall(StringStream& out, const hlsl::ir::BuiltinCall* c) {
         out << c->Func() << "(";
         bool needs_comma = false;
diff --git a/src/tint/lang/hlsl/writer/raise/builtin_polyfill.cc b/src/tint/lang/hlsl/writer/raise/builtin_polyfill.cc
index eddb32c..b04094d 100644
--- a/src/tint/lang/hlsl/writer/raise/builtin_polyfill.cc
+++ b/src/tint/lang/hlsl/writer/raise/builtin_polyfill.cc
@@ -37,6 +37,7 @@
 #include "src/tint/lang/core/type/manager.h"
 #include "src/tint/lang/hlsl/builtin_fn.h"
 #include "src/tint/lang/hlsl/ir/builtin_call.h"
+#include "src/tint/lang/hlsl/ir/ternary.h"
 #include "src/tint/utils/containers/hashmap.h"
 #include "src/tint/utils/math/hash.h"
 
@@ -68,9 +69,21 @@
     void Process() {
         // Find the bitcasts that need replacing.
         Vector<core::ir::Bitcast*, 4> bitcast_worklist;
+        Vector<core::ir::CoreBuiltinCall*, 4> call_worklist;
         for (auto* inst : ir.Instructions()) {
             if (auto* bitcast = inst->As<core::ir::Bitcast>()) {
                 bitcast_worklist.Push(bitcast);
+                continue;
+            }
+            if (auto* call = inst->As<core::ir::CoreBuiltinCall>()) {
+                switch (call->Func()) {
+                    case core::BuiltinFn::kSelect:
+                        call_worklist.Push(call);
+                        break;
+                    default:
+                        break;
+                }
+                continue;
             }
         }
 
@@ -90,6 +103,25 @@
                 ReplaceBitcastWithAs(bitcast);
             }
         }
+
+        // Replace the builtin calls that we found
+        for (auto* call : call_worklist) {
+            switch (call->Func()) {
+                case core::BuiltinFn::kSelect:
+                    Select(call);
+                    break;
+                default:
+                    TINT_UNREACHABLE();
+            }
+        }
+    }
+
+    void Select(core::ir::CoreBuiltinCall* call) {
+        Vector<core::ir::Value*, 4> args = call->Args();
+        auto* ternary =
+            b.ir.allocators.instructions.Create<hlsl::ir::Ternary>(call->DetachResult(), args);
+        ternary->InsertBefore(call);
+        call->Destroy();
     }
 
     /// Replaces an identity bitcast result with the value.