[hlsl] Add start of FXC polyfill for HLSL IR backend.

This CL adds the start of the FXC polyfill and implements the switch
with only a default case transformation.

Bug: 42251045
Change-Id: I4fb874296afad6438a5292ab0924a9170a6e7aa3
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/194621
Reviewed-by: James Price <jrprice@google.com>
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Auto-Submit: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/cmd/test/BUILD.bazel b/src/tint/cmd/test/BUILD.bazel
index de049c2..cc3866d 100644
--- a/src/tint/cmd/test/BUILD.bazel
+++ b/src/tint/cmd/test/BUILD.bazel
@@ -52,6 +52,7 @@
     "//src/tint/lang/core/type:test",
     "//src/tint/lang/core:test",
     "//src/tint/lang/hlsl/writer/common:test",
+    "//src/tint/lang/hlsl/writer/raise:test",
     "//src/tint/lang/msl/ir:test",
     "//src/tint/lang/msl/type:test",
     "//src/tint/lang/spirv/ir:test",
diff --git a/src/tint/cmd/test/BUILD.cmake b/src/tint/cmd/test/BUILD.cmake
index cb94ba9..341bf6e 100644
--- a/src/tint/cmd/test/BUILD.cmake
+++ b/src/tint/cmd/test/BUILD.cmake
@@ -53,6 +53,7 @@
   tint_lang_core_type_test
   tint_lang_core_test
   tint_lang_hlsl_writer_common_test
+  tint_lang_hlsl_writer_raise_test
   tint_lang_msl_ir_test
   tint_lang_msl_type_test
   tint_lang_spirv_ir_test
diff --git a/src/tint/cmd/test/BUILD.gn b/src/tint/cmd/test/BUILD.gn
index 7d0c39c..ea2de9c 100644
--- a/src/tint/cmd/test/BUILD.gn
+++ b/src/tint/cmd/test/BUILD.gn
@@ -58,6 +58,7 @@
       "${tint_src_dir}/lang/core/ir/transform/common:unittests",
       "${tint_src_dir}/lang/core/type:unittests",
       "${tint_src_dir}/lang/hlsl/writer/common:unittests",
+      "${tint_src_dir}/lang/hlsl/writer/raise:unittests",
       "${tint_src_dir}/lang/msl/ir:unittests",
       "${tint_src_dir}/lang/msl/type:unittests",
       "${tint_src_dir}/lang/spirv/ir:unittests",
diff --git a/src/tint/lang/hlsl/writer/raise/BUILD.bazel b/src/tint/lang/hlsl/writer/raise/BUILD.bazel
index 4285f0c..f989717 100644
--- a/src/tint/lang/hlsl/writer/raise/BUILD.bazel
+++ b/src/tint/lang/hlsl/writer/raise/BUILD.bazel
@@ -39,29 +39,70 @@
 cc_library(
   name = "raise",
   srcs = [
+    "fxc_polyfill.cc",
     "raise.cc",
   ],
   hdrs = [
+    "fxc_polyfill.h",
     "raise.h",
   ],
   deps = [
     "//src/tint/api/common",
     "//src/tint/lang/core",
+    "//src/tint/lang/core/constant",
+    "//src/tint/lang/core/intrinsic",
+    "//src/tint/lang/core/ir",
     "//src/tint/lang/core/ir/transform",
+    "//src/tint/lang/core/type",
     "//src/tint/lang/hlsl/writer/common",
     "//src/tint/utils/containers",
     "//src/tint/utils/diagnostic",
     "//src/tint/utils/ice",
+    "//src/tint/utils/id",
     "//src/tint/utils/macros",
     "//src/tint/utils/math",
     "//src/tint/utils/memory",
     "//src/tint/utils/reflection",
     "//src/tint/utils/result",
     "//src/tint/utils/rtti",
+    "//src/tint/utils/symbol",
     "//src/tint/utils/text",
     "//src/tint/utils/traits",
   ],
   copts = COPTS,
   visibility = ["//visibility:public"],
 )
+cc_library(
+  name = "test",
+  alwayslink = True,
+  srcs = [
+    "fxc_polyfill_test.cc",
+  ],
+  deps = [
+    "//src/tint/api/common",
+    "//src/tint/lang/core",
+    "//src/tint/lang/core/constant",
+    "//src/tint/lang/core/intrinsic",
+    "//src/tint/lang/core/ir",
+    "//src/tint/lang/core/ir/transform:test",
+    "//src/tint/lang/core/type",
+    "//src/tint/lang/hlsl/writer/raise",
+    "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
+    "//src/tint/utils/ice",
+    "//src/tint/utils/id",
+    "//src/tint/utils/macros",
+    "//src/tint/utils/math",
+    "//src/tint/utils/memory",
+    "//src/tint/utils/reflection",
+    "//src/tint/utils/result",
+    "//src/tint/utils/rtti",
+    "//src/tint/utils/symbol",
+    "//src/tint/utils/text",
+    "//src/tint/utils/traits",
+    "@gtest",
+  ],
+  copts = COPTS,
+  visibility = ["//visibility:public"],
+)
 
diff --git a/src/tint/lang/hlsl/writer/raise/BUILD.cmake b/src/tint/lang/hlsl/writer/raise/BUILD.cmake
index 7934f8d..47e9ce4 100644
--- a/src/tint/lang/hlsl/writer/raise/BUILD.cmake
+++ b/src/tint/lang/hlsl/writer/raise/BUILD.cmake
@@ -39,6 +39,8 @@
 # Kind:      lib
 ################################################################################
 tint_add_target(tint_lang_hlsl_writer_raise lib
+  lang/hlsl/writer/raise/fxc_polyfill.cc
+  lang/hlsl/writer/raise/fxc_polyfill.h
   lang/hlsl/writer/raise/raise.cc
   lang/hlsl/writer/raise/raise.h
 )
@@ -46,17 +48,59 @@
 tint_target_add_dependencies(tint_lang_hlsl_writer_raise lib
   tint_api_common
   tint_lang_core
+  tint_lang_core_constant
+  tint_lang_core_intrinsic
+  tint_lang_core_ir
   tint_lang_core_ir_transform
+  tint_lang_core_type
   tint_lang_hlsl_writer_common
   tint_utils_containers
   tint_utils_diagnostic
   tint_utils_ice
+  tint_utils_id
   tint_utils_macros
   tint_utils_math
   tint_utils_memory
   tint_utils_reflection
   tint_utils_result
   tint_utils_rtti
+  tint_utils_symbol
   tint_utils_text
   tint_utils_traits
 )
+
+################################################################################
+# Target:    tint_lang_hlsl_writer_raise_test
+# Kind:      test
+################################################################################
+tint_add_target(tint_lang_hlsl_writer_raise_test test
+  lang/hlsl/writer/raise/fxc_polyfill_test.cc
+)
+
+tint_target_add_dependencies(tint_lang_hlsl_writer_raise_test test
+  tint_api_common
+  tint_lang_core
+  tint_lang_core_constant
+  tint_lang_core_intrinsic
+  tint_lang_core_ir
+  tint_lang_core_ir_transform_test
+  tint_lang_core_type
+  tint_lang_hlsl_writer_raise
+  tint_utils_containers
+  tint_utils_diagnostic
+  tint_utils_ice
+  tint_utils_id
+  tint_utils_macros
+  tint_utils_math
+  tint_utils_memory
+  tint_utils_reflection
+  tint_utils_result
+  tint_utils_rtti
+  tint_utils_symbol
+  tint_utils_text
+  tint_utils_traits
+)
+
+tint_target_add_external_dependencies(tint_lang_hlsl_writer_raise_test test
+  "gtest"
+)
diff --git a/src/tint/lang/hlsl/writer/raise/BUILD.gn b/src/tint/lang/hlsl/writer/raise/BUILD.gn
index c287af3..0d755f2 100644
--- a/src/tint/lang/hlsl/writer/raise/BUILD.gn
+++ b/src/tint/lang/hlsl/writer/raise/BUILD.gn
@@ -38,26 +38,67 @@
 
 import("${tint_src_dir}/tint.gni")
 
+if (tint_build_unittests || tint_build_benchmarks) {
+  import("//testing/test.gni")
+}
+
 libtint_source_set("raise") {
   sources = [
+    "fxc_polyfill.cc",
+    "fxc_polyfill.h",
     "raise.cc",
     "raise.h",
   ]
   deps = [
     "${tint_src_dir}/api/common",
     "${tint_src_dir}/lang/core",
+    "${tint_src_dir}/lang/core/constant",
+    "${tint_src_dir}/lang/core/intrinsic",
+    "${tint_src_dir}/lang/core/ir",
     "${tint_src_dir}/lang/core/ir/transform",
+    "${tint_src_dir}/lang/core/type",
     "${tint_src_dir}/lang/hlsl/writer/common",
     "${tint_src_dir}/utils/containers",
     "${tint_src_dir}/utils/diagnostic",
     "${tint_src_dir}/utils/ice",
+    "${tint_src_dir}/utils/id",
     "${tint_src_dir}/utils/macros",
     "${tint_src_dir}/utils/math",
     "${tint_src_dir}/utils/memory",
     "${tint_src_dir}/utils/reflection",
     "${tint_src_dir}/utils/result",
     "${tint_src_dir}/utils/rtti",
+    "${tint_src_dir}/utils/symbol",
     "${tint_src_dir}/utils/text",
     "${tint_src_dir}/utils/traits",
   ]
 }
+if (tint_build_unittests) {
+  tint_unittests_source_set("unittests") {
+    sources = [ "fxc_polyfill_test.cc" ]
+    deps = [
+      "${tint_src_dir}:gmock_and_gtest",
+      "${tint_src_dir}/api/common",
+      "${tint_src_dir}/lang/core",
+      "${tint_src_dir}/lang/core/constant",
+      "${tint_src_dir}/lang/core/intrinsic",
+      "${tint_src_dir}/lang/core/ir",
+      "${tint_src_dir}/lang/core/ir/transform:unittests",
+      "${tint_src_dir}/lang/core/type",
+      "${tint_src_dir}/lang/hlsl/writer/raise",
+      "${tint_src_dir}/utils/containers",
+      "${tint_src_dir}/utils/diagnostic",
+      "${tint_src_dir}/utils/ice",
+      "${tint_src_dir}/utils/id",
+      "${tint_src_dir}/utils/macros",
+      "${tint_src_dir}/utils/math",
+      "${tint_src_dir}/utils/memory",
+      "${tint_src_dir}/utils/reflection",
+      "${tint_src_dir}/utils/result",
+      "${tint_src_dir}/utils/rtti",
+      "${tint_src_dir}/utils/symbol",
+      "${tint_src_dir}/utils/text",
+      "${tint_src_dir}/utils/traits",
+    ]
+  }
+}
diff --git a/src/tint/lang/hlsl/writer/raise/fxc_polyfill.cc b/src/tint/lang/hlsl/writer/raise/fxc_polyfill.cc
new file mode 100644
index 0000000..7369b40
--- /dev/null
+++ b/src/tint/lang/hlsl/writer/raise/fxc_polyfill.cc
@@ -0,0 +1,88 @@
+// 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/writer/raise/fxc_polyfill.h"
+
+#include "src/tint/lang/core/ir/block.h"
+#include "src/tint/lang/core/ir/builder.h"
+#include "src/tint/lang/core/ir/exit.h"
+#include "src/tint/lang/core/ir/loop.h"
+#include "src/tint/lang/core/ir/switch.h"
+#include "src/tint/lang/core/ir/validator.h"
+#include "src/tint/lang/core/type/manager.h"
+#include "src/tint/utils/containers/vector.h"
+#include "src/tint/utils/ice/ice.h"
+#include "src/tint/utils/result/result.h"
+
+namespace tint::hlsl::writer::raise {
+namespace {
+
+using namespace tint::core::fluent_types;  // NOLINT
+
+/// PIMPL state for the transform.
+struct State {
+    /// The IR module.
+    core::ir::Module& ir;
+
+    /// The IR builder.
+    core::ir::Builder b{ir};
+
+    /// The type manager.
+    core::type::Manager& ty{ir.Types()};
+
+    /// Process the module.
+    void Process() {
+        for (auto* inst : ir.Instructions()) {
+            if (auto* swtch = inst->As<core::ir::Switch>()) {
+                // BUG(crbug.com/tint/1188): work around default-only switches
+                //
+                // FXC fails to compile a switch with just a default case, ignoring the
+                // default case body. We work around this here by emitting the zero case as a
+                // fallthrough to the default case
+                if (swtch->Cases().Length() == 1 && swtch->Cases()[0].selectors.Length() == 1 &&
+                    swtch->Cases()[0].selectors[0].IsDefault()) {
+                    swtch->Cases()[0].selectors.Push({b.Zero(swtch->Condition()->Type())});
+                }
+            }
+        }
+    }
+};
+
+}  // namespace
+
+Result<SuccessType> FxcPolyfill(core::ir::Module& ir) {
+    auto result = ValidateAndDumpIfNeeded(ir, "FxcPolyfill transform");
+    if (result != Success) {
+        return result.Failure();
+    }
+
+    State{ir}.Process();
+
+    return Success;
+}
+
+}  // namespace tint::hlsl::writer::raise
diff --git a/src/tint/lang/hlsl/writer/raise/fxc_polyfill.h b/src/tint/lang/hlsl/writer/raise/fxc_polyfill.h
new file mode 100644
index 0000000..1cedaac
--- /dev/null
+++ b/src/tint/lang/hlsl/writer/raise/fxc_polyfill.h
@@ -0,0 +1,50 @@
+// 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_WRITER_RAISE_FXC_POLYFILL_H_
+#define SRC_TINT_LANG_HLSL_WRITER_RAISE_FXC_POLYFILL_H_
+
+#include <string>
+
+#include "src/tint/utils/result/result.h"
+
+// Forward declarations.
+namespace tint::core::ir {
+class Module;
+}  // namespace tint::core::ir
+
+namespace tint::hlsl::writer::raise {
+
+/// FxcPollyfill is a transform that replaces code constructs which cause FXC mis-compiles with
+/// safer constructs.
+/// @param module the module to transform
+/// @returns success or failure
+Result<SuccessType> FxcPolyfill(core::ir::Module& module);
+
+}  // namespace tint::hlsl::writer::raise
+
+#endif  // SRC_TINT_LANG_HLSL_WRITER_RAISE_FXC_POLYFILL_H_
diff --git a/src/tint/lang/hlsl/writer/raise/fxc_polyfill_test.cc b/src/tint/lang/hlsl/writer/raise/fxc_polyfill_test.cc
new file mode 100644
index 0000000..45302a4
--- /dev/null
+++ b/src/tint/lang/hlsl/writer/raise/fxc_polyfill_test.cc
@@ -0,0 +1,376 @@
+// 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/writer/raise/fxc_polyfill.h"
+
+#include <gtest/gtest.h>
+
+#include "src/tint/lang/core/fluent_types.h"
+#include "src/tint/lang/core/ir/function.h"
+#include "src/tint/lang/core/ir/transform/helper_test.h"
+#include "src/tint/lang/core/number.h"
+
+using namespace tint::core::fluent_types;     // NOLINT
+using namespace tint::core::number_suffixes;  // NOLINT
+
+namespace tint::hlsl::writer::raise {
+namespace {
+
+using HlslWriterFxcPolyfillTest = core::ir::transform::TransformTest;
+
+// No change, no switch
+TEST_F(HlslWriterFxcPolyfillTest, NoSwitch) {
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(func->Block(), [&] { b.Return(func); });
+
+    auto* src = R"(
+%foo = @fragment func():void {
+  $B1: {
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = src;
+    Run(FxcPolyfill);
+
+    EXPECT_EQ(expect, str());
+}
+
+// No change, switch with case and default
+TEST_F(HlslWriterFxcPolyfillTest, SwitchCaseAndDefault) {
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(func->Block(), [&] {
+        auto* s = b.Switch(1_i);
+        b.Append(b.Case(s, {b.Constant(0_i)}), [&] { b.ExitSwitch(s); });
+        b.Append(b.DefaultCase(s), [&] { b.ExitSwitch(s); });
+
+        b.Return(func);
+    });
+
+    auto* src = R"(
+%foo = @fragment func():void {
+  $B1: {
+    switch 1i [c: (0i, $B2), c: (default, $B3)] {  # switch_1
+      $B2: {  # case
+        exit_switch  # switch_1
+      }
+      $B3: {  # case
+        exit_switch  # switch_1
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = src;
+    Run(FxcPolyfill);
+
+    EXPECT_EQ(expect, str());
+}
+
+// No change, switch with multi-selector default case
+TEST_F(HlslWriterFxcPolyfillTest, SwitchMultiSelectorDefault) {
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(func->Block(), [&] {
+        auto* s = b.Switch(1_i);
+        b.Append(b.Case(s, {b.Constant(0_i), nullptr}), [&] { b.ExitSwitch(s); });
+        b.Return(func);
+    });
+
+    auto* src = R"(
+%foo = @fragment func():void {
+  $B1: {
+    switch 1i [c: (0i default, $B2)] {  # switch_1
+      $B2: {  # case
+        exit_switch  # switch_1
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = src;
+    Run(FxcPolyfill);
+
+    EXPECT_EQ(expect, str());
+}
+
+// Switch body just has a ExitSwitch
+TEST_F(HlslWriterFxcPolyfillTest, Switch) {
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+    b.Append(func->Block(), [&] {
+        auto* s = b.Switch(1_i);
+        b.Append(b.DefaultCase(s), [&] { b.ExitSwitch(s); });
+        b.Return(func);
+    });
+
+    auto* src = R"(
+%foo = @fragment func():void {
+  $B1: {
+    switch 1i [c: (default, $B2)] {  # switch_1
+      $B2: {  # case
+        exit_switch  # switch_1
+      }
+    }
+    ret
+  }
+}
+)";
+
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = @fragment func():void {
+  $B1: {
+    switch 1i [c: (default 0i, $B2)] {  # switch_1
+      $B2: {  # case
+        exit_switch  # switch_1
+      }
+    }
+    ret
+  }
+}
+)";
+
+    Run(FxcPolyfill);
+
+    EXPECT_EQ(expect, str());
+}
+
+// Switch body with assignment
+TEST_F(HlslWriterFxcPolyfillTest, SwitchWithAssignment) {
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+
+    auto* a = b.Var<private_>("a", b.Zero<i32>());
+    b.ir.root_block->Append(a);
+
+    b.Append(func->Block(), [&] {
+        auto* s = b.Switch(1_i);
+        b.Append(b.DefaultCase(s), [&] {
+            b.Store(a, 1_i);
+            b.ExitSwitch(s);
+        });
+        b.Return(func);
+    });
+
+    auto* src = R"(
+$B1: {  # root
+  %a:ptr<private, i32, read_write> = var, 0i
+}
+
+%foo = @fragment func():void {
+  $B2: {
+    switch 1i [c: (default, $B3)] {  # switch_1
+      $B3: {  # case
+        store %a, 1i
+        exit_switch  # switch_1
+      }
+    }
+    ret
+  }
+}
+)";
+
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+$B1: {  # root
+  %a:ptr<private, i32, read_write> = var, 0i
+}
+
+%foo = @fragment func():void {
+  $B2: {
+    switch 1i [c: (default 0i, $B3)] {  # switch_1
+      $B3: {  # case
+        store %a, 1i
+        exit_switch  # switch_1
+      }
+    }
+    ret
+  }
+}
+)";
+
+    Run(FxcPolyfill);
+
+    EXPECT_EQ(expect, str());
+}
+
+// Switch with if with break
+TEST_F(HlslWriterFxcPolyfillTest, SwitchWithIfBreak) {
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+
+    auto* a = b.Var<private_>("a", b.Zero<i32>());
+    b.ir.root_block->Append(a);
+
+    b.Append(func->Block(), [&] {
+        auto* s = b.Switch(1_i);
+        b.Append(b.DefaultCase(s), [&] {
+            auto* i = b.If(true);
+            b.Append(i->True(), [&] {
+                b.Store(a, 1_i);
+                b.ExitSwitch(s);
+            });
+            b.ExitSwitch(s);
+        });
+        b.Return(func);
+    });
+
+    auto* src = R"(
+$B1: {  # root
+  %a:ptr<private, i32, read_write> = var, 0i
+}
+
+%foo = @fragment func():void {
+  $B2: {
+    switch 1i [c: (default, $B3)] {  # switch_1
+      $B3: {  # case
+        if true [t: $B4] {  # if_1
+          $B4: {  # true
+            store %a, 1i
+            exit_switch  # switch_1
+          }
+        }
+        exit_switch  # switch_1
+      }
+    }
+    ret
+  }
+}
+)";
+
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+$B1: {  # root
+  %a:ptr<private, i32, read_write> = var, 0i
+}
+
+%foo = @fragment func():void {
+  $B2: {
+    switch 1i [c: (default 0i, $B3)] {  # switch_1
+      $B3: {  # case
+        if true [t: $B4] {  # if_1
+          $B4: {  # true
+            store %a, 1i
+            exit_switch  # switch_1
+          }
+        }
+        exit_switch  # switch_1
+      }
+    }
+    ret
+  }
+}
+)";
+
+    Run(FxcPolyfill);
+
+    EXPECT_EQ(expect, str());
+}
+
+// Switch with loop with break
+TEST_F(HlslWriterFxcPolyfillTest, SwitchWithLoopBreak) {
+    auto* func = b.Function("foo", ty.void_(), core::ir::Function::PipelineStage::kFragment);
+
+    auto* a = b.Var<private_>("a", b.Zero<i32>());
+    b.ir.root_block->Append(a);
+
+    b.Append(func->Block(), [&] {
+        auto* s = b.Switch(1_i);
+        b.Append(b.DefaultCase(s), [&] {
+            auto* l = b.Loop();
+            b.Append(l->Body(), [&] {
+                b.Store(a, 1_i);
+                b.ExitLoop(l);
+            });
+            b.ExitSwitch(s);
+        });
+        b.Return(func);
+    });
+
+    auto* src = R"(
+$B1: {  # root
+  %a:ptr<private, i32, read_write> = var, 0i
+}
+
+%foo = @fragment func():void {
+  $B2: {
+    switch 1i [c: (default, $B3)] {  # switch_1
+      $B3: {  # case
+        loop [b: $B4] {  # loop_1
+          $B4: {  # body
+            store %a, 1i
+            exit_loop  # loop_1
+          }
+        }
+        exit_switch  # switch_1
+      }
+    }
+    ret
+  }
+}
+)";
+
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+$B1: {  # root
+  %a:ptr<private, i32, read_write> = var, 0i
+}
+
+%foo = @fragment func():void {
+  $B2: {
+    switch 1i [c: (default 0i, $B3)] {  # switch_1
+      $B3: {  # case
+        loop [b: $B4] {  # loop_1
+          $B4: {  # body
+            store %a, 1i
+            exit_loop  # loop_1
+          }
+        }
+        exit_switch  # switch_1
+      }
+    }
+    ret
+  }
+}
+)";
+
+    Run(FxcPolyfill);
+
+    EXPECT_EQ(expect, str());
+}
+
+}  // namespace
+}  // namespace tint::hlsl::writer::raise
diff --git a/src/tint/lang/hlsl/writer/raise/raise.cc b/src/tint/lang/hlsl/writer/raise/raise.cc
index ddb6442..da40afa 100644
--- a/src/tint/lang/hlsl/writer/raise/raise.cc
+++ b/src/tint/lang/hlsl/writer/raise/raise.cc
@@ -30,11 +30,12 @@
 #include "src/tint/lang/core/ir/transform/add_empty_entry_point.h"
 #include "src/tint/lang/core/ir/transform/remove_terminator_args.h"
 #include "src/tint/lang/hlsl/writer/common/options.h"
+#include "src/tint/lang/hlsl/writer/raise/fxc_polyfill.h"
 #include "src/tint/utils/result/result.h"
 
 namespace tint::hlsl::writer {
 
-Result<SuccessType> Raise(core::ir::Module& module, const Options&) {
+Result<SuccessType> Raise(core::ir::Module& module, const Options& options) {
 #define RUN_TRANSFORM(name, ...)                   \
     do {                                           \
         auto result = name(module, ##__VA_ARGS__); \
@@ -45,6 +46,10 @@
 
     RUN_TRANSFORM(core::ir::transform::AddEmptyEntryPoint);
 
+    if (options.compiler == Options::Compiler::kFXC) {
+        RUN_TRANSFORM(raise::FxcPolyfill);
+    }
+
     // These transforms need to be run last as various transforms introduce terminator arguments,
     // naming conflicts, and expressions that need to be explicitly not inlined.
     RUN_TRANSFORM(core::ir::transform::RemoveTerminatorArgs);
diff --git a/src/tint/lang/hlsl/writer/switch_test.cc b/src/tint/lang/hlsl/writer/switch_test.cc
index 01f1152..f6458cd 100644
--- a/src/tint/lang/hlsl/writer/switch_test.cc
+++ b/src/tint/lang/hlsl/writer/switch_test.cc
@@ -39,7 +39,7 @@
 
     b.Append(f->Block(), [&] {
         auto* a = b.Var("a", b.Zero<i32>());
-        auto* s = b.Switch(a);
+        auto* s = b.Switch(b.Load(a));
         b.Append(b.Case(s, {b.Constant(5_i)}), [&] { b.ExitSwitch(s); });
         b.Append(b.DefaultCase(s), [&] { b.ExitSwitch(s); });
         b.Return(f);
@@ -71,7 +71,7 @@
 
     b.Append(f->Block(), [&] {
         auto* a = b.Var("a", b.Zero<i32>());
-        auto* s = b.Switch(a);
+        auto* s = b.Switch(b.Load(a));
         auto* c = b.Case(s, {b.Constant(5_i), nullptr});
         b.Append(c, [&] { b.ExitSwitch(s); });
         b.Return(f);
@@ -111,7 +111,7 @@
     b.Append(f->Block(), [&] {
         auto* cond = b.Var("cond", b.Zero<i32>());
         auto* a = b.Var("a", b.Zero<i32>());
-        auto* s = b.Switch(cond);
+        auto* s = b.Switch(b.Load(cond));
         b.Append(b.DefaultCase(s), [&] {
             b.Store(a, 42_i);
             b.ExitSwitch(s);
@@ -202,8 +202,7 @@
 )");
 }
 
-// TODO(dsinclair): Needs transfrom to convert single default switch to while loop
-TEST_F(HlslWriterTest, DISABLED_SwitchOnlyDefaultCaseNoSideEffectsConditionFXC) {
+TEST_F(HlslWriterTest, SwitchOnlyDefaultCaseNoSideEffectsConditionFXC) {
     // var<private> cond : i32;
     // var<private> a : i32;
     // fn test() {
@@ -220,7 +219,7 @@
     b.Append(f->Block(), [&] {
         auto* cond = b.Var("cond", b.Zero<i32>());
         auto* a = b.Var("a", b.Zero<i32>());
-        auto* s = b.Switch(cond);
+        auto* s = b.Switch(b.Load(cond));
         b.Append(b.DefaultCase(s), [&] {
             b.Store(a, 42_i);
             b.ExitSwitch(s);
@@ -235,17 +234,22 @@
     EXPECT_EQ(output_.hlsl, R"(
 [numthreads(1, 1, 1)]
 void foo() {
-  while(true) {
-    a = 42;
-    break;
+  int cond = 0;
+  int a = 0;
+  switch(cond) {
+    default:
+    case 0:
+    {
+      a = 42;
+      break;
+    }
   }
 }
 
 )");
 }
 
-// TODO(dsinclair): Needs transfrom to convert single default switch to while loop
-TEST_F(HlslWriterTest, DISABLED_SwitchOnlyDefaultCaseSideEffectsConditionFXC) {
+TEST_F(HlslWriterTest, SwitchOnlyDefaultCaseSideEffectsConditionFXC) {
     // var<private> global : i32;
     // fn bar() -> i32 {
     //   global = 84;
@@ -290,9 +294,10 @@
 
     ASSERT_TRUE(Generate(options)) << err_ << output_.hlsl;
     EXPECT_EQ(output_.hlsl, R"(
-static int global = 0;
-static int a = 0;
-
+static
+int global = 0;
+static
+int a = 0;
 int bar() {
   global = 84;
   return global;
@@ -300,10 +305,13 @@
 
 [numthreads(1, 1, 1)]
 void foo() {
-  bar();
-  while(true) {
-    a = 42;
-    break;
+  switch(bar()) {
+    default:
+    case 0:
+    {
+      a = 42;
+      break;
+    }
   }
 }