Import Tint changes from Dawn

Changes:
  - 25b7e98d1100f3dd5b261915867fc2fbf3dedae0 tint/writer/glsl: Inline constant expressions by Ben Clayton <bclayton@google.com>
  - 37d92ca244bfef3de3d6f550fef4cf8547186397 tint/spirv: Fix atomicCompareExchangeWeak by James Price <jrprice@google.com>
  - 7e495d8f2e06c7cea5813abb1cc5efbfe101faae tint/resolver: Implement candidate overload resolution by Ben Clayton <bclayton@google.com>
  - bfb5fd794c13e846e40a4cb89e7dc20fbc1275a9 tint/sem: Add more helpers to Constant by Ben Clayton <bclayton@google.com>
  - 8bd5fec4827798143c44422db34a378124a3bedd tint/writer/wgsl: Emit 'f' suffix on FloatLiteralExpressi... by Ben Clayton <bclayton@google.com>
  - 22bd00440900120d9245a3459cbf367a853e9e42 tint/resolver: Materialize RHS of non-phony assignments by Ben Clayton <bclayton@google.com>
  - 649d3d9602e46f59b4cd895ac4f40f74c43d109e tint/resolver: Materialize array size expression by Ben Clayton <bclayton@google.com>
  - 49a09140b9384da37715738cdb9fb45f93a14abd tint/resolver: Materialize array index expression by Ben Clayton <bclayton@google.com>
  - 08f4b557fcf03e7fa6fea0342fb47b7c194f27be Implement atomicCompareExchangeWeak returning struct inst... by Antonio Maiorano <amaiorano@google.com>
  - 61537d3f57736c99aed0b0077a3229fca5b01ed9 tint: Add Checked[Add|Mul|Madd]() by Ben Clayton <bclayton@google.com>
GitOrigin-RevId: 25b7e98d1100f3dd5b261915867fc2fbf3dedae0
Change-Id: Ie16c7cf14e70eec1b1a6dc8b34984cf329043147
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/92200
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/ast/float_literal_expression.cc b/src/tint/ast/float_literal_expression.cc
index ab2d6cf..36cb42a 100644
--- a/src/tint/ast/float_literal_expression.cc
+++ b/src/tint/ast/float_literal_expression.cc
@@ -36,4 +36,15 @@
     return ctx->dst->create<FloatLiteralExpression>(src, value, suffix);
 }
 
+std::ostream& operator<<(std::ostream& out, FloatLiteralExpression::Suffix suffix) {
+    switch (suffix) {
+        default:
+            return out;
+        case FloatLiteralExpression::Suffix::kF:
+            return out << "f";
+        case FloatLiteralExpression::Suffix::kH:
+            return out << "h";
+    }
+}
+
 }  // namespace tint::ast
diff --git a/src/tint/ast/float_literal_expression.h b/src/tint/ast/float_literal_expression.h
index 72a395f..7f03caf 100644
--- a/src/tint/ast/float_literal_expression.h
+++ b/src/tint/ast/float_literal_expression.h
@@ -55,6 +55,12 @@
     const Suffix suffix;
 };
 
+/// Writes the float literal suffix to the std::ostream.
+/// @param out the std::ostream to write to
+/// @param suffix the suffix to write
+/// @returns out so calls can be chained
+std::ostream& operator<<(std::ostream& out, FloatLiteralExpression::Suffix suffix);
+
 }  // namespace tint::ast
 
 #endif  // SRC_TINT_AST_FLOAT_LITERAL_EXPRESSION_H_
diff --git a/src/tint/ast/float_literal_expression_test.cc b/src/tint/ast/float_literal_expression_test.cc
index a2f9b25..5ec5f0f 100644
--- a/src/tint/ast/float_literal_expression_test.cc
+++ b/src/tint/ast/float_literal_expression_test.cc
@@ -33,5 +33,24 @@
     EXPECT_EQ(i->suffix, FloatLiteralExpression::Suffix::kF);
 }
 
+TEST_F(FloatLiteralExpressionTest, SuffixH) {
+    auto* i = create<FloatLiteralExpression>(42.0, FloatLiteralExpression::Suffix::kH);
+    ASSERT_TRUE(i->Is<FloatLiteralExpression>());
+    EXPECT_EQ(i->value, 42);
+    EXPECT_EQ(i->suffix, FloatLiteralExpression::Suffix::kH);
+}
+
+TEST_F(FloatLiteralExpressionTest, SuffixStringStream) {
+    auto to_str = [](FloatLiteralExpression::Suffix suffix) {
+        std::stringstream ss;
+        ss << suffix;
+        return ss.str();
+    };
+
+    EXPECT_EQ("", to_str(FloatLiteralExpression::Suffix::kNone));
+    EXPECT_EQ("f", to_str(FloatLiteralExpression::Suffix::kF));
+    EXPECT_EQ("h", to_str(FloatLiteralExpression::Suffix::kH));
+}
+
 }  // namespace
 }  // namespace tint::ast
diff --git a/src/tint/ast/int_literal_expression_test.cc b/src/tint/ast/int_literal_expression_test.cc
index 8bc3d2f..969e1b9 100644
--- a/src/tint/ast/int_literal_expression_test.cc
+++ b/src/tint/ast/int_literal_expression_test.cc
@@ -40,5 +40,17 @@
     EXPECT_EQ(i->suffix, IntLiteralExpression::Suffix::kU);
 }
 
+TEST_F(IntLiteralExpressionTest, SuffixStringStream) {
+    auto to_str = [](IntLiteralExpression::Suffix suffix) {
+        std::stringstream ss;
+        ss << suffix;
+        return ss.str();
+    };
+
+    EXPECT_EQ("", to_str(IntLiteralExpression::Suffix::kNone));
+    EXPECT_EQ("i", to_str(IntLiteralExpression::Suffix::kI));
+    EXPECT_EQ("u", to_str(IntLiteralExpression::Suffix::kU));
+}
+
 }  // namespace
 }  // namespace tint::ast
diff --git a/src/tint/intrinsics.def b/src/tint/intrinsics.def
index bc4bf28..28be2d9 100644
--- a/src/tint/intrinsics.def
+++ b/src/tint/intrinsics.def
@@ -118,6 +118,8 @@
 type __frexp_result
 [[display("__frexp_result_vec{N}")]] type __frexp_result_vec<N: num>
 
+type __atomic_compare_exchange_result<T>
+
 ////////////////////////////////////////////////////////////////////////////////
 // Type matchers                                                              //
 //                                                                            //
@@ -206,13 +208,12 @@
 // Matching algorithm for a single overload:                                  //
 // -----------------------------------------                                  //
 //                                                                            //
-// The goal of matching is to compare a function call's arguments in the      //
-// program source against a possibly-templated overload declaration, and      //
-// determine if the call satisfies the form and type constraints of the       //
-// overload. Currently it is impossible for a call to match more than one     //
-// overload definition. In the event that more than one overload matches, an  //
-// ICE will be raised. Note that Tint may need to support multiple-overload   //
-// resolution in the future, depending on future overload definitions.        //
+// The goal of matching is to compare a function call's arguments and any     //
+// explicitly provided template types in the program source against an        //
+// overload declaration in this file, and determine if the call satisfies     //
+// the form and type constraints of the overload. If the call matches an      //
+// overload, then the overload is added to the list of 'overload candidates'  //
+// used for overload resolution (described below).                            //
 //                                                                            //
 // Prior to matching an overload, all template types are undefined.           //
 //                                                                            //
@@ -256,11 +257,11 @@
 //       need to be checked next. If the defined type does not match the      //
 //       'match' constraint, then the overload is no longer considered.       //
 //                                                                            //
-// This algorithm is less general than the overload resolution described in   //
-// the WGSL spec.  But it makes the same decisions because the overloads      //
-// defined by WGSL are monotonic in the sense that once a template parameter  //
-// has been refined, there is never a need to backtrack and un-refine it to   //
-// match a later argument.                                                    //
+// This algorithm for matching a single overload is less general than the     //
+// algorithm described in the WGSL spec.  But it makes the same decisions     //
+// because the overloads defined by WGSL are monotonic in the sense that once //
+// a template parameter has been refined, there is never a need to backtrack  //
+// and un-refine it to match a later argument.                                //
 //                                                                            //
 // The algorithm for matching template numbers is similar to matching         //
 // template types, except numbers need to exactly match across all uses -     //
@@ -268,7 +269,24 @@
 // numbers or enumerators.                                                    //
 //                                                                            //
 //                                                                            //
-// * More examples:                                                           //
+// Overload resolution for candidate overloads                                //
+// -------------------------------------------                                //
+//                                                                            //
+// If multiple candidate overloads match a given set of arguments, then a     //
+// final overload resolution pass needs to be performed. The arguments and    //
+// overload parameter types for each candidate overload are compared,         //
+// following the algorithm described at:                                      //
+//   https://www.w3.org/TR/WGSL/#overload-resolution-section                  //
+//                                                                            //
+// If the candidate list contains a single entry, then that single candidate  //
+// is picked, and no overload resolution needs to be performed.               //
+//                                                                            //
+// If the candidate list is empty, then the call fails to resolve and an      //
+// error diagnostic is raised.                                                //
+//                                                                            //
+//                                                                            //
+// More examples                                                              //
+// -------------                                                              //
 //                                                                            //
 //   fn F()                                                                   //
 //     - Function called F.                                                   //
@@ -603,7 +621,7 @@
 [[stage("fragment", "compute")]] fn atomicOr<T: iu32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T
 [[stage("fragment", "compute")]] fn atomicXor<T: iu32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T
 [[stage("fragment", "compute")]] fn atomicExchange<T: iu32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T
-[[stage("fragment", "compute")]] fn atomicCompareExchangeWeak<T: iu32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T, T) -> vec2<T>
+[[stage("fragment", "compute")]] fn atomicCompareExchangeWeak<T: iu32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T, T) -> __atomic_compare_exchange_result<T>
 
 ////////////////////////////////////////////////////////////////////////////////
 // Type constructors                                                          //
diff --git a/src/tint/number.h b/src/tint/number.h
index 6efb023..b4c5ca4 100644
--- a/src/tint/number.h
+++ b/src/tint/number.h
@@ -19,7 +19,10 @@
 #include <functional>
 #include <limits>
 #include <ostream>
+// TODO(https://crbug.com/dawn/1379) Update cpplint and remove NOLINT
+#include <optional>  // NOLINT(build/include_order))
 
+#include "src/tint/utils/compiler_macros.h"
 #include "src/tint/utils/result.h"
 
 // Forward declaration
@@ -184,33 +187,6 @@
     return !(a == b);
 }
 
-/// Enumerator of failure reasons when converting from one number to another.
-enum class ConversionFailure {
-    kExceedsPositiveLimit,  // The value was too big (+'ve) to fit in the target type
-    kExceedsNegativeLimit,  // The value was too big (-'ve) to fit in the target type
-};
-
-/// Writes the conversion failure message to the ostream.
-/// @param out the std::ostream to write to
-/// @param failure the ConversionFailure
-/// @return the std::ostream so calls can be chained
-std::ostream& operator<<(std::ostream& out, ConversionFailure failure);
-
-/// Converts a number from one type to another, checking that the value fits in the target type.
-/// @returns the resulting value of the conversion, or a failure reason.
-template <typename TO, typename FROM>
-utils::Result<TO, ConversionFailure> CheckedConvert(Number<FROM> num) {
-    using T = decltype(UnwrapNumber<TO>() + num.value);
-    const auto value = static_cast<T>(num.value);
-    if (value > static_cast<T>(TO::kHighest)) {
-        return ConversionFailure::kExceedsPositiveLimit;
-    }
-    if (value < static_cast<T>(TO::kLowest)) {
-        return ConversionFailure::kExceedsNegativeLimit;
-    }
-    return TO(value);  // Success
-}
-
 /// The partial specification of Number for f16 type, storing the f16 value as float,
 /// and enforcing proper explicit casting.
 template <>
@@ -282,6 +258,114 @@
 /// However since C++ don't have native binary16 type, the value is stored as float.
 using f16 = Number<detail::NumberKindF16>;
 
+/// Enumerator of failure reasons when converting from one number to another.
+enum class ConversionFailure {
+    kExceedsPositiveLimit,  // The value was too big (+'ve) to fit in the target type
+    kExceedsNegativeLimit,  // The value was too big (-'ve) to fit in the target type
+};
+
+/// Writes the conversion failure message to the ostream.
+/// @param out the std::ostream to write to
+/// @param failure the ConversionFailure
+/// @return the std::ostream so calls can be chained
+std::ostream& operator<<(std::ostream& out, ConversionFailure failure);
+
+/// Converts a number from one type to another, checking that the value fits in the target type.
+/// @returns the resulting value of the conversion, or a failure reason.
+template <typename TO, typename FROM>
+utils::Result<TO, ConversionFailure> CheckedConvert(Number<FROM> num) {
+    using T = decltype(UnwrapNumber<TO>() + num.value);
+    const auto value = static_cast<T>(num.value);
+    if (value > static_cast<T>(TO::kHighest)) {
+        return ConversionFailure::kExceedsPositiveLimit;
+    }
+    if (value < static_cast<T>(TO::kLowest)) {
+        return ConversionFailure::kExceedsNegativeLimit;
+    }
+    return TO(value);  // Success
+}
+
+/// Define 'TINT_HAS_OVERFLOW_BUILTINS' if the compiler provide overflow checking builtins.
+/// If the compiler does not support these builtins, then these are emulated with algorithms
+/// described in:
+/// https://wiki.sei.cmu.edu/confluence/display/c/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow
+#if defined(__GNUC__) && __GNUC__ >= 5
+#define TINT_HAS_OVERFLOW_BUILTINS
+#elif defined(__clang__)
+#if __has_builtin(__builtin_add_overflow) && __has_builtin(__builtin_mul_overflow)
+#define TINT_HAS_OVERFLOW_BUILTINS
+#endif
+#endif
+
+/// @returns a + b, or an empty optional if the resulting value overflowed the AInt
+inline std::optional<AInt> CheckedAdd(AInt a, AInt b) {
+    int64_t result;
+#ifdef TINT_HAS_OVERFLOW_BUILTINS
+    if (__builtin_add_overflow(a.value, b.value, &result)) {
+        return {};
+    }
+#else   // TINT_HAS_OVERFLOW_BUILTINS
+    if (a.value >= 0) {
+        if (AInt::kHighest - a.value < b.value) {
+            return {};
+        }
+    } else {
+        if (b.value < AInt::kLowest - a.value) {
+            return {};
+        }
+    }
+    result = a.value + b.value;
+#endif  // TINT_HAS_OVERFLOW_BUILTINS
+    return AInt(result);
+}
+
+/// @returns a * b, or an empty optional if the resulting value overflowed the AInt
+inline std::optional<AInt> CheckedMul(AInt a, AInt b) {
+    int64_t result;
+#ifdef TINT_HAS_OVERFLOW_BUILTINS
+    if (__builtin_mul_overflow(a.value, b.value, &result)) {
+        return {};
+    }
+#else   // TINT_HAS_OVERFLOW_BUILTINS
+    if (a > 0) {
+        if (b > 0) {
+            if (a > (AInt::kHighest / b)) {
+                return {};
+            }
+        } else {
+            if (b < (AInt::kLowest / a)) {
+                return {};
+            }
+        }
+    } else {
+        if (b > 0) {
+            if (a < (AInt::kLowest / b)) {
+                return {};
+            }
+        } else {
+            if ((a != 0) && (b < (AInt::kHighest / a))) {
+                return {};
+            }
+        }
+    }
+    result = a.value * b.value;
+#endif  // TINT_HAS_OVERFLOW_BUILTINS
+    return AInt(result);
+}
+
+/// @returns a * b + c, or an empty optional if the value overflowed the AInt
+inline std::optional<AInt> CheckedMadd(AInt a, AInt b, AInt c) {
+    // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80635
+    TINT_BEGIN_DISABLE_WARNING(MAYBE_UNINITIALIZED);
+
+    if (auto mul = CheckedMul(a, b)) {
+        return CheckedAdd(mul.value(), c);
+    }
+    return {};
+
+    TINT_END_DISABLE_WARNING(MAYBE_UNINITIALIZED);
+}
+
 }  // namespace tint
 
 namespace tint::number_suffixes {
diff --git a/src/tint/number_test.cc b/src/tint/number_test.cc
index a7bb2fe..34b4d39 100644
--- a/src/tint/number_test.cc
+++ b/src/tint/number_test.cc
@@ -13,6 +13,8 @@
 // limitations under the License.
 
 #include <cmath>
+#include <tuple>
+#include <vector>
 
 #include "src/tint/program_builder.h"
 #include "src/tint/utils/compiler_macros.h"
@@ -141,6 +143,165 @@
     EXPECT_TRUE(std::isnan(f16(nan)));
 }
 
+using BinaryCheckedCase = std::tuple<std::optional<AInt>, AInt, AInt>;
+
+#undef OVERFLOW  // corecrt_math.h :(
+#define OVERFLOW \
+    {}
+
+using CheckedAddTest = testing::TestWithParam<BinaryCheckedCase>;
+TEST_P(CheckedAddTest, Test) {
+    auto expect = std::get<0>(GetParam());
+    auto a = std::get<1>(GetParam());
+    auto b = std::get<2>(GetParam());
+    EXPECT_EQ(CheckedAdd(a, b), expect) << std::hex << "0x" << a << " * 0x" << b;
+    EXPECT_EQ(CheckedAdd(b, a), expect) << std::hex << "0x" << a << " * 0x" << b;
+}
+INSTANTIATE_TEST_SUITE_P(
+    CheckedAddTest,
+    CheckedAddTest,
+    testing::ValuesIn(std::vector<BinaryCheckedCase>{
+        {AInt(0), AInt(0), AInt(0)},
+        {AInt(1), AInt(1), AInt(0)},
+        {AInt(2), AInt(1), AInt(1)},
+        {AInt(0), AInt(-1), AInt(1)},
+        {AInt(3), AInt(2), AInt(1)},
+        {AInt(-1), AInt(-2), AInt(1)},
+        {AInt(0x300), AInt(0x100), AInt(0x200)},
+        {AInt(0x100), AInt(-0x100), AInt(0x200)},
+        {AInt(AInt::kHighest), AInt(1), AInt(AInt::kHighest - 1)},
+        {AInt(AInt::kLowest), AInt(-1), AInt(AInt::kLowest + 1)},
+        {AInt(AInt::kHighest), AInt(0x7fffffff00000000ll), AInt(0x00000000ffffffffll)},
+        {AInt(AInt::kHighest), AInt(AInt::kHighest), AInt(0)},
+        {AInt(AInt::kLowest), AInt(AInt::kLowest), AInt(0)},
+        {OVERFLOW, AInt(1), AInt(AInt::kHighest)},
+        {OVERFLOW, AInt(-1), AInt(AInt::kLowest)},
+        {OVERFLOW, AInt(2), AInt(AInt::kHighest)},
+        {OVERFLOW, AInt(-2), AInt(AInt::kLowest)},
+        {OVERFLOW, AInt(10000), AInt(AInt::kHighest)},
+        {OVERFLOW, AInt(-10000), AInt(AInt::kLowest)},
+        {OVERFLOW, AInt(AInt::kHighest), AInt(AInt::kHighest)},
+        {OVERFLOW, AInt(AInt::kLowest), AInt(AInt::kLowest)},
+        ////////////////////////////////////////////////////////////////////////
+    }));
+
+using CheckedMulTest = testing::TestWithParam<BinaryCheckedCase>;
+TEST_P(CheckedMulTest, Test) {
+    auto expect = std::get<0>(GetParam());
+    auto a = std::get<1>(GetParam());
+    auto b = std::get<2>(GetParam());
+    EXPECT_EQ(CheckedMul(a, b), expect) << std::hex << "0x" << a << " * 0x" << b;
+    EXPECT_EQ(CheckedMul(b, a), expect) << std::hex << "0x" << a << " * 0x" << b;
+}
+INSTANTIATE_TEST_SUITE_P(
+    CheckedMulTest,
+    CheckedMulTest,
+    testing::ValuesIn(std::vector<BinaryCheckedCase>{
+        {AInt(0), AInt(0), AInt(0)},
+        {AInt(0), AInt(1), AInt(0)},
+        {AInt(1), AInt(1), AInt(1)},
+        {AInt(-1), AInt(-1), AInt(1)},
+        {AInt(2), AInt(2), AInt(1)},
+        {AInt(-2), AInt(-2), AInt(1)},
+        {AInt(0x20000), AInt(0x100), AInt(0x200)},
+        {AInt(-0x20000), AInt(-0x100), AInt(0x200)},
+        {AInt(0x4000000000000000ll), AInt(0x80000000ll), AInt(0x80000000ll)},
+        {AInt(0x4000000000000000ll), AInt(-0x80000000ll), AInt(-0x80000000ll)},
+        {AInt(0x1000000000000000ll), AInt(0x40000000ll), AInt(0x40000000ll)},
+        {AInt(-0x1000000000000000ll), AInt(-0x40000000ll), AInt(0x40000000ll)},
+        {AInt(0x100000000000000ll), AInt(0x1000000), AInt(0x100000000ll)},
+        {AInt(0x2000000000000000ll), AInt(0x1000000000000000ll), AInt(2)},
+        {AInt(-0x2000000000000000ll), AInt(0x1000000000000000ll), AInt(-2)},
+        {AInt(-0x2000000000000000ll), AInt(-0x1000000000000000ll), AInt(2)},
+        {AInt(-0x2000000000000000ll), AInt(0x1000000000000000ll), AInt(-2)},
+        {AInt(0x4000000000000000ll), AInt(0x1000000000000000ll), AInt(4)},
+        {AInt(-0x4000000000000000ll), AInt(0x1000000000000000ll), AInt(-4)},
+        {AInt(-0x4000000000000000ll), AInt(-0x1000000000000000ll), AInt(4)},
+        {AInt(-0x4000000000000000ll), AInt(0x1000000000000000ll), AInt(-4)},
+        {AInt(-0x8000000000000000ll), AInt(0x1000000000000000ll), AInt(-8)},
+        {AInt(-0x8000000000000000ll), AInt(-0x1000000000000000ll), AInt(8)},
+        {AInt(0), AInt(AInt::kHighest), AInt(0)},
+        {AInt(0), AInt(AInt::kLowest), AInt(0)},
+        {OVERFLOW, AInt(0x1000000000000000ll), AInt(8)},
+        {OVERFLOW, AInt(-0x1000000000000000ll), AInt(-8)},
+        {OVERFLOW, AInt(0x800000000000000ll), AInt(0x10)},
+        {OVERFLOW, AInt(0x80000000ll), AInt(0x100000000ll)},
+        {OVERFLOW, AInt(AInt::kHighest), AInt(AInt::kHighest)},
+        {OVERFLOW, AInt(AInt::kHighest), AInt(AInt::kLowest)},
+        ////////////////////////////////////////////////////////////////////////
+    }));
+
+using TernaryCheckedCase = std::tuple<std::optional<AInt>, AInt, AInt, AInt>;
+
+using CheckedMaddTest = testing::TestWithParam<TernaryCheckedCase>;
+TEST_P(CheckedMaddTest, Test) {
+    auto expect = std::get<0>(GetParam());
+    auto a = std::get<1>(GetParam());
+    auto b = std::get<2>(GetParam());
+    auto c = std::get<3>(GetParam());
+    EXPECT_EQ(CheckedMadd(a, b, c), expect)
+        << std::hex << "0x" << a << " * 0x" << b << " + 0x" << c;
+    EXPECT_EQ(CheckedMadd(b, a, c), expect)
+        << std::hex << "0x" << a << " * 0x" << b << " + 0x" << c;
+}
+INSTANTIATE_TEST_SUITE_P(
+    CheckedMaddTest,
+    CheckedMaddTest,
+    testing::ValuesIn(std::vector<TernaryCheckedCase>{
+        {AInt(0), AInt(0), AInt(0), AInt(0)},
+        {AInt(0), AInt(1), AInt(0), AInt(0)},
+        {AInt(1), AInt(1), AInt(1), AInt(0)},
+        {AInt(2), AInt(1), AInt(1), AInt(1)},
+        {AInt(0), AInt(1), AInt(-1), AInt(1)},
+        {AInt(-1), AInt(1), AInt(-2), AInt(1)},
+        {AInt(-1), AInt(-1), AInt(1), AInt(0)},
+        {AInt(2), AInt(2), AInt(1), AInt(0)},
+        {AInt(-2), AInt(-2), AInt(1), AInt(0)},
+        {AInt(0), AInt(AInt::kHighest), AInt(0), AInt(0)},
+        {AInt(0), AInt(AInt::kLowest), AInt(0), AInt(0)},
+        {AInt(3), AInt(1), AInt(2), AInt(1)},
+        {AInt(0x300), AInt(1), AInt(0x100), AInt(0x200)},
+        {AInt(0x100), AInt(1), AInt(-0x100), AInt(0x200)},
+        {AInt(0x20000), AInt(0x100), AInt(0x200), AInt(0)},
+        {AInt(-0x20000), AInt(-0x100), AInt(0x200), AInt(0)},
+        {AInt(0x4000000000000000ll), AInt(0x80000000ll), AInt(0x80000000ll), AInt(0)},
+        {AInt(0x4000000000000000ll), AInt(-0x80000000ll), AInt(-0x80000000ll), AInt(0)},
+        {AInt(0x1000000000000000ll), AInt(0x40000000ll), AInt(0x40000000ll), AInt(0)},
+        {AInt(-0x1000000000000000ll), AInt(-0x40000000ll), AInt(0x40000000ll), AInt(0)},
+        {AInt(0x100000000000000ll), AInt(0x1000000), AInt(0x100000000ll), AInt(0)},
+        {AInt(0x2000000000000000ll), AInt(0x1000000000000000ll), AInt(2), AInt(0)},
+        {AInt(-0x2000000000000000ll), AInt(0x1000000000000000ll), AInt(-2), AInt(0)},
+        {AInt(-0x2000000000000000ll), AInt(-0x1000000000000000ll), AInt(2), AInt(0)},
+        {AInt(-0x2000000000000000ll), AInt(0x1000000000000000ll), AInt(-2), AInt(0)},
+        {AInt(0x4000000000000000ll), AInt(0x1000000000000000ll), AInt(4), AInt(0)},
+        {AInt(-0x4000000000000000ll), AInt(0x1000000000000000ll), AInt(-4), AInt(0)},
+        {AInt(-0x4000000000000000ll), AInt(-0x1000000000000000ll), AInt(4), AInt(0)},
+        {AInt(-0x4000000000000000ll), AInt(0x1000000000000000ll), AInt(-4), AInt(0)},
+        {AInt(-0x8000000000000000ll), AInt(0x1000000000000000ll), AInt(-8), AInt(0)},
+        {AInt(-0x8000000000000000ll), AInt(-0x1000000000000000ll), AInt(8), AInt(0)},
+        {AInt(AInt::kHighest), AInt(1), AInt(1), AInt(AInt::kHighest - 1)},
+        {AInt(AInt::kLowest), AInt(1), AInt(-1), AInt(AInt::kLowest + 1)},
+        {AInt(AInt::kHighest), AInt(1), AInt(0x7fffffff00000000ll), AInt(0x00000000ffffffffll)},
+        {AInt(AInt::kHighest), AInt(1), AInt(AInt::kHighest), AInt(0)},
+        {AInt(AInt::kLowest), AInt(1), AInt(AInt::kLowest), AInt(0)},
+        {OVERFLOW, AInt(0x1000000000000000ll), AInt(8), AInt(0)},
+        {OVERFLOW, AInt(-0x1000000000000000ll), AInt(-8), AInt(0)},
+        {OVERFLOW, AInt(0x800000000000000ll), AInt(0x10), AInt(0)},
+        {OVERFLOW, AInt(0x80000000ll), AInt(0x100000000ll), AInt(0)},
+        {OVERFLOW, AInt(AInt::kHighest), AInt(AInt::kHighest), AInt(0)},
+        {OVERFLOW, AInt(AInt::kHighest), AInt(AInt::kLowest), AInt(0)},
+        {OVERFLOW, AInt(1), AInt(1), AInt(AInt::kHighest)},
+        {OVERFLOW, AInt(1), AInt(-1), AInt(AInt::kLowest)},
+        {OVERFLOW, AInt(1), AInt(2), AInt(AInt::kHighest)},
+        {OVERFLOW, AInt(1), AInt(-2), AInt(AInt::kLowest)},
+        {OVERFLOW, AInt(1), AInt(10000), AInt(AInt::kHighest)},
+        {OVERFLOW, AInt(1), AInt(-10000), AInt(AInt::kLowest)},
+        {OVERFLOW, AInt(1), AInt(AInt::kHighest), AInt(AInt::kHighest)},
+        {OVERFLOW, AInt(1), AInt(AInt::kLowest), AInt(AInt::kLowest)},
+        {OVERFLOW, AInt(1), AInt(AInt::kHighest), AInt(1)},
+        {OVERFLOW, AInt(1), AInt(AInt::kLowest), AInt(-1)},
+    }));
+
 TINT_END_DISABLE_WARNING(CONSTANT_OVERFLOW);
 
 }  // namespace
diff --git a/src/tint/reader/spirv/function_arithmetic_test.cc b/src/tint/reader/spirv/function_arithmetic_test.cc
index 9db49d9..0597916 100644
--- a/src/tint/reader/spirv/function_arithmetic_test.cc
+++ b/src/tint/reader/spirv/function_arithmetic_test.cc
@@ -93,10 +93,10 @@
         return "bitcast<vec2<u32>>(vec2<i32>(40i, 30i))";
     }
     if (assembly == "v2float_50_60") {
-        return "vec2<f32>(50.0, 60.0)";
+        return "vec2<f32>(50.0f, 60.0f)";
     }
     if (assembly == "v2float_60_50") {
-        return "vec2<f32>(60.0, 50.0)";
+        return "vec2<f32>(60.0f, 50.0f)";
     }
     return "bad case";
 }
@@ -253,7 +253,7 @@
     auto fe = p->function_emitter(100);
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
-    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr("let x_1 : f32 = -(50.0);"));
+    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr("let x_1 : f32 = -(50.0f);"));
 }
 
 TEST_F(SpvUnaryArithTest, FNegate_Vector) {
@@ -270,7 +270,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : vec2<f32> = -(vec2<f32>(50.0, 60.0));"));
+                HasSubstr("let x_1 : vec2<f32> = -(vec2<f32>(50.0f, 60.0f));"));
 }
 
 struct BinaryData {
@@ -394,8 +394,8 @@
                          SpvBinaryArithTest,
                          ::testing::Values(
                              // Scalar float
-                             BinaryData{"float", "float_50", "OpFAdd", "float_60", "f32", "50.0",
-                                        "+", "60.0"},  // Vector float
+                             BinaryData{"float", "float_50", "OpFAdd", "float_60", "f32", "50.0f",
+                                        "+", "60.0f"},  // Vector float
                              BinaryData{"v2float", "v2float_50_60", "OpFAdd", "v2float_60_50",
                                         "vec2<f32>", AstFor("v2float_50_60"), "+",
                                         AstFor("v2float_60_50")}));
@@ -441,8 +441,8 @@
                          SpvBinaryArithTest,
                          ::testing::Values(
                              // Scalar float
-                             BinaryData{"float", "float_50", "OpFSub", "float_60", "f32", "50.0",
-                                        "-", "60.0"},  // Vector float
+                             BinaryData{"float", "float_50", "OpFSub", "float_60", "f32", "50.0f",
+                                        "-", "60.0f"},  // Vector float
                              BinaryData{"v2float", "v2float_50_60", "OpFSub", "v2float_60_50",
                                         "vec2<f32>", AstFor("v2float_50_60"), "-",
                                         AstFor("v2float_60_50")}));
@@ -488,8 +488,8 @@
                          SpvBinaryArithTest,
                          ::testing::Values(
                              // Scalar float
-                             BinaryData{"float", "float_50", "OpFMul", "float_60", "f32", "50.0",
-                                        "*", "60.0"},  // Vector float
+                             BinaryData{"float", "float_50", "OpFMul", "float_60", "f32", "50.0f",
+                                        "*", "60.0f"},  // Vector float
                              BinaryData{"v2float", "v2float_50_60", "OpFMul", "v2float_60_50",
                                         "vec2<f32>", AstFor("v2float_50_60"), "*",
                                         AstFor("v2float_60_50")}));
@@ -576,8 +576,8 @@
                          SpvBinaryArithTest,
                          ::testing::Values(
                              // Scalar float
-                             BinaryData{"float", "float_50", "OpFDiv", "float_60", "f32", "50.0",
-                                        "/", "60.0"},  // Vector float
+                             BinaryData{"float", "float_50", "OpFDiv", "float_60", "f32", "50.0f",
+                                        "/", "60.0f"},  // Vector float
                              BinaryData{"v2float", "v2float_50_60", "OpFDiv", "v2float_60_50",
                                         "vec2<f32>", AstFor("v2float_50_60"), "/",
                                         AstFor("v2float_60_50")}));
@@ -667,8 +667,8 @@
                          SpvBinaryArithTest,
                          ::testing::Values(
                              // Scalar float
-                             BinaryData{"float", "float_50", "OpFRem", "float_60", "f32", "50.0",
-                                        "%", "60.0"},  // Vector float
+                             BinaryData{"float", "float_50", "OpFRem", "float_60", "f32", "50.0f",
+                                        "%", "60.0f"},  // Vector float
                              BinaryData{"v2float", "v2float_50_60", "OpFRem", "v2float_60_50",
                                         "vec2<f32>", AstFor("v2float_50_60"), "%",
                                         AstFor("v2float_60_50")}));
@@ -687,7 +687,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : f32 = (50.0 - (60.0 * floor((50.0 / 60.0))));"));
+                HasSubstr("let x_1 : f32 = (50.0f - (60.0f * floor((50.0f / 60.0f))));"));
 }
 
 TEST_F(SpvBinaryArithTestBasic, FMod_Vector) {
@@ -706,7 +706,7 @@
     EXPECT_THAT(
         test::ToString(p->program(), ast_body),
         HasSubstr(
-            R"(let x_1 : vec2<f32> = (vec2<f32>(50.0, 60.0) - (vec2<f32>(60.0, 50.0) * floor((vec2<f32>(50.0, 60.0) / vec2<f32>(60.0, 50.0)))));)"));
+            R"(let x_1 : vec2<f32> = (vec2<f32>(50.0f, 60.0f) - (vec2<f32>(60.0f, 50.0f) * floor((vec2<f32>(50.0f, 60.0f) / vec2<f32>(60.0f, 50.0f)))));)"));
 }
 
 TEST_F(SpvBinaryArithTestBasic, VectorTimesScalar) {
diff --git a/src/tint/reader/spirv/function_composite_test.cc b/src/tint/reader/spirv/function_composite_test.cc
index 2e15743..2b26d14 100644
--- a/src/tint/reader/spirv/function_composite_test.cc
+++ b/src/tint/reader/spirv/function_composite_test.cc
@@ -98,7 +98,7 @@
     EXPECT_THAT(test::ToString(p->program(), ast_body),
                 HasSubstr(R"(let x_1 : vec2<u32> = vec2<u32>(10u, 20u);
 let x_2 : vec2<i32> = vec2<i32>(30i, 40i);
-let x_3 : vec2<f32> = vec2<f32>(50.0, 60.0);
+let x_3 : vec2<f32> = vec2<f32>(50.0f, 60.0f);
 )"));
 }
 
@@ -117,9 +117,9 @@
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
                 HasSubstr("let x_1 : mat3x2<f32> = mat3x2<f32>("
-                          "vec2<f32>(50.0, 60.0), "
-                          "vec2<f32>(60.0, 50.0), "
-                          "vec2<f32>(70.0, 70.0));"));
+                          "vec2<f32>(50.0f, 60.0f), "
+                          "vec2<f32>(60.0f, 50.0f), "
+                          "vec2<f32>(70.0f, 70.0f));"));
 }
 
 TEST_F(SpvParserTest_Composite_Construct, Array) {
@@ -153,7 +153,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : S = S(vec2<f32>(50.0, 60.0), 5u, 30i);"));
+                HasSubstr("let x_1 : S = S(vec2<f32>(50.0f, 60.0f), 5u, 30i);"));
 }
 
 TEST_F(SpvParserTest_Composite_Construct, ConstantComposite_Struct_NoDeduplication) {
@@ -201,7 +201,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : f32 = vec2<f32>(50.0, 60.0).y;"));
+                HasSubstr("let x_1 : f32 = vec2<f32>(50.0f, 60.0f).y;"));
 }
 
 TEST_F(SpvParserTest_CompositeExtract, Vector_IndexTooBigError) {
@@ -448,8 +448,8 @@
     auto ast_body = fe.ast_body();
     auto got = test::ToString(p->program(), ast_body);
     const auto* expected =
-        R"(var x_1_1 : vec2<f32> = vec2<f32>(50.0, 60.0);
-x_1_1.y = 70.0;
+        R"(var x_1_1 : vec2<f32> = vec2<f32>(50.0f, 60.0f);
+x_1_1.y = 70.0f;
 let x_1 : vec2<f32> = x_1_1;
 return;
 )";
@@ -492,7 +492,7 @@
     auto ast_body = fe.ast_body();
     auto body_str = test::ToString(p->program(), ast_body);
     EXPECT_THAT(body_str, HasSubstr(R"(var x_2_1 : mat3x2<f32> = x_1;
-x_2_1[2u] = vec2<f32>(50.0, 60.0);
+x_2_1[2u] = vec2<f32>(50.0f, 60.0f);
 let x_2 : mat3x2<f32> = x_2_1;
 )")) << body_str;
 }
@@ -537,7 +537,7 @@
     auto ast_body = fe.ast_body();
     auto body_str = test::ToString(p->program(), ast_body);
     EXPECT_THAT(body_str, HasSubstr(R"(var x_2_1 : mat3x2<f32> = x_1;
-x_2_1[2u] = vec2<f32>(50.0, 60.0);
+x_2_1[2u] = vec2<f32>(50.0f, 60.0f);
 let x_2 : mat3x2<f32> = x_2_1;
 return;
 )")) << body_str;
@@ -713,7 +713,7 @@
     EXPECT_THAT(body_str, HasSubstr(R"(var x_38 : S_1;
 let x_1 : S_1 = x_38;
 var x_2_1 : S_1 = x_1;
-x_2_1.field1[2u][0u].y = 70.0;
+x_2_1.field1[2u][0u].y = 70.0f;
 let x_2 : S_1 = x_2_1;
 )")) << body_str;
 }
diff --git a/src/tint/reader/spirv/function_conversion_test.cc b/src/tint/reader/spirv/function_conversion_test.cc
index e2f10b2..eab0031 100644
--- a/src/tint/reader/spirv/function_conversion_test.cc
+++ b/src/tint/reader/spirv/function_conversion_test.cc
@@ -83,7 +83,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : u32 = bitcast<u32>(50.0);"));
+                HasSubstr("let x_1 : u32 = bitcast<u32>(50.0f);"));
 }
 
 TEST_F(SpvUnaryConversionTest, Bitcast_Vector) {
diff --git a/src/tint/reader/spirv/function_decl_test.cc b/src/tint/reader/spirv/function_decl_test.cc
index bced935..8af9da2 100644
--- a/src/tint/reader/spirv/function_decl_test.cc
+++ b/src/tint/reader/spirv/function_decl_test.cc
@@ -93,7 +93,7 @@
 
     auto got = test::ToString(p->program());
     std::string expect = R"(fn x_200() -> f32 {
-  return 0.0;
+  return 0.0f;
 }
 )";
     EXPECT_THAT(got, HasSubstr(expect));
diff --git a/src/tint/reader/spirv/function_glsl_std_450_test.cc b/src/tint/reader/spirv/function_glsl_std_450_test.cc
index 319332a..4836a68 100644
--- a/src/tint/reader/spirv/function_glsl_std_450_test.cc
+++ b/src/tint/reader/spirv/function_glsl_std_450_test.cc
@@ -673,7 +673,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : f32 = 1.0;")) << body;
+    EXPECT_THAT(body, HasSubstr("let x_1 : f32 = 1.0f;")) << body;
 }
 
 TEST_F(SpvParserTest, Normalize_Vector2) {
@@ -981,7 +981,7 @@
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
     const auto* expected =
-        R"(let x_1 : f32 = refract(vec2<f32>(f1, 0.0), vec2<f32>(f2, 0.0), f3).x;)";
+        R"(let x_1 : f32 = refract(vec2<f32>(f1, 0.0f), vec2<f32>(f2, 0.0f), f3).x;)";
 
     EXPECT_THAT(body, HasSubstr(expected)) << body;
 }
@@ -1019,7 +1019,7 @@
     // The %99 sum only has one use.  Ensure it is evaluated only once by
     // making a let-declaration for it, since it is the normal operand to
     // the builtin function, and code generation uses it twice.
-    const auto* expected = R"(let x_1 : f32 = select(-(x_99), x_99, ((f2 * f3) < 0.0));)";
+    const auto* expected = R"(let x_1 : f32 = select(-(x_99), x_99, ((f2 * f3) < 0.0f));)";
 
     EXPECT_THAT(body, HasSubstr(expected)) << body;
 }
@@ -1059,7 +1059,7 @@
     // The %99 sum only has one use.  Ensure it is evaluated only once by
     // making a let-declaration for it, since it is the normal operand to
     // the builtin function, and code generation uses it twice.
-    const auto* expected = R"(let x_1 : f32 = (x_98 - (2.0 * (x_99 * (x_99 * x_98))));)";
+    const auto* expected = R"(let x_1 : f32 = (x_98 - (2.0f * (x_99 * (x_99 * x_98))));)";
 
     EXPECT_THAT(body, HasSubstr(expected)) << body;
 }
diff --git a/src/tint/reader/spirv/function_logical_test.cc b/src/tint/reader/spirv/function_logical_test.cc
index ba73d5d..474af2e 100644
--- a/src/tint/reader/spirv/function_logical_test.cc
+++ b/src/tint/reader/spirv/function_logical_test.cc
@@ -112,10 +112,10 @@
         return "bitcast<vec2<u32>>(vec2<i32>(40i, 30i))";
     }
     if (assembly == "v2float_50_60") {
-        return "vec2<f32>(50.0, 60.0)";
+        return "vec2<f32>(50.0f, 60.0f)";
     }
     if (assembly == "v2float_60_50") {
-        return "vec2<f32>(60.0, 50.0)";
+        return "vec2<f32>(60.0f, 50.0f)";
     }
     return "bad case";
 }
@@ -219,7 +219,7 @@
 INSTANTIATE_TEST_SUITE_P(SpvParserTest_FOrdEqual,
                          SpvBinaryLogicalTest,
                          ::testing::Values(BinaryData{"bool", "float_50", "OpFOrdEqual", "float_60",
-                                                      "bool", "50.0", "==", "60.0"},
+                                                      "bool", "50.0f", "==", "60.0f"},
                                            BinaryData{"v2bool", "v2float_50_60", "OpFOrdEqual",
                                                       "v2float_60_50", "vec2<bool>",
                                                       AstFor("v2float_50_60"),
@@ -249,7 +249,7 @@
 INSTANTIATE_TEST_SUITE_P(SpvParserTest_FOrdNotEqual,
                          SpvBinaryLogicalTest,
                          ::testing::Values(BinaryData{"bool", "float_50", "OpFOrdNotEqual",
-                                                      "float_60", "bool", "50.0", "!=", "60.0"},
+                                                      "float_60", "bool", "50.0f", "!=", "60.0f"},
                                            BinaryData{"v2bool", "v2float_50_60", "OpFOrdNotEqual",
                                                       "v2float_60_50", "vec2<bool>",
                                                       AstFor("v2float_50_60"),
@@ -258,7 +258,7 @@
 INSTANTIATE_TEST_SUITE_P(SpvParserTest_FOrdLessThan,
                          SpvBinaryLogicalTest,
                          ::testing::Values(BinaryData{"bool", "float_50", "OpFOrdLessThan",
-                                                      "float_60", "bool", "50.0", "<", "60.0"},
+                                                      "float_60", "bool", "50.0f", "<", "60.0f"},
                                            BinaryData{"v2bool", "v2float_50_60", "OpFOrdLessThan",
                                                       "v2float_60_50", "vec2<bool>",
                                                       AstFor("v2float_50_60"), "<",
@@ -267,7 +267,7 @@
 INSTANTIATE_TEST_SUITE_P(SpvParserTest_FOrdLessThanEqual,
                          SpvBinaryLogicalTest,
                          ::testing::Values(BinaryData{"bool", "float_50", "OpFOrdLessThanEqual",
-                                                      "float_60", "bool", "50.0", "<=", "60.0"},
+                                                      "float_60", "bool", "50.0f", "<=", "60.0f"},
                                            BinaryData{"v2bool", "v2float_50_60",
                                                       "OpFOrdLessThanEqual", "v2float_60_50",
                                                       "vec2<bool>", AstFor("v2float_50_60"),
@@ -276,7 +276,7 @@
 INSTANTIATE_TEST_SUITE_P(SpvParserTest_FOrdGreaterThan,
                          SpvBinaryLogicalTest,
                          ::testing::Values(BinaryData{"bool", "float_50", "OpFOrdGreaterThan",
-                                                      "float_60", "bool", "50.0", ">", "60.0"},
+                                                      "float_60", "bool", "50.0f", ">", "60.0f"},
                                            BinaryData{"v2bool", "v2float_50_60",
                                                       "OpFOrdGreaterThan", "v2float_60_50",
                                                       "vec2<bool>", AstFor("v2float_50_60"), ">",
@@ -285,7 +285,7 @@
 INSTANTIATE_TEST_SUITE_P(SpvParserTest_FOrdGreaterThanEqual,
                          SpvBinaryLogicalTest,
                          ::testing::Values(BinaryData{"bool", "float_50", "OpFOrdGreaterThanEqual",
-                                                      "float_60", "bool", "50.0", ">=", "60.0"},
+                                                      "float_60", "bool", "50.0f", ">=", "60.0f"},
                                            BinaryData{"v2bool", "v2float_50_60",
                                                       "OpFOrdGreaterThanEqual", "v2float_60_50",
                                                       "vec2<bool>", AstFor("v2float_50_60"),
@@ -515,7 +515,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : bool = !((50.0 != 60.0));"));
+                HasSubstr("let x_1 : bool = !((50.0f != 60.0f));"));
 }
 
 TEST_F(SpvFUnordTest, FUnordEqual_Vector) {
@@ -533,7 +533,7 @@
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
                 HasSubstr("let x_1 : vec2<bool> = "
-                          "!((vec2<f32>(50.0, 60.0) != vec2<f32>(60.0, 50.0)));"));
+                          "!((vec2<f32>(50.0f, 60.0f) != vec2<f32>(60.0f, 50.0f)));"));
 }
 
 TEST_F(SpvFUnordTest, FUnordNotEqual_Scalar) {
@@ -550,7 +550,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : bool = !((50.0 == 60.0));"));
+                HasSubstr("let x_1 : bool = !((50.0f == 60.0f));"));
 }
 
 TEST_F(SpvFUnordTest, FUnordNotEqual_Vector) {
@@ -568,7 +568,7 @@
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
                 HasSubstr("let x_1 : vec2<bool> = "
-                          "!((vec2<f32>(50.0, 60.0) == vec2<f32>(60.0, 50.0)));"));
+                          "!((vec2<f32>(50.0f, 60.0f) == vec2<f32>(60.0f, 50.0f)));"));
 }
 
 TEST_F(SpvFUnordTest, FUnordLessThan_Scalar) {
@@ -585,7 +585,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : bool = !((50.0 >= 60.0));"));
+                HasSubstr("let x_1 : bool = !((50.0f >= 60.0f));"));
 }
 
 TEST_F(SpvFUnordTest, FUnordLessThan_Vector) {
@@ -603,7 +603,7 @@
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
                 HasSubstr("let x_1 : vec2<bool> = "
-                          "!((vec2<f32>(50.0, 60.0) >= vec2<f32>(60.0, 50.0)));"));
+                          "!((vec2<f32>(50.0f, 60.0f) >= vec2<f32>(60.0f, 50.0f)));"));
 }
 
 TEST_F(SpvFUnordTest, FUnordLessThanEqual_Scalar) {
@@ -620,7 +620,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : bool = !((50.0 > 60.0));"));
+                HasSubstr("let x_1 : bool = !((50.0f > 60.0f));"));
 }
 
 TEST_F(SpvFUnordTest, FUnordLessThanEqual_Vector) {
@@ -638,7 +638,7 @@
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
                 HasSubstr("let x_1 : vec2<bool> = "
-                          "!((vec2<f32>(50.0, 60.0) > vec2<f32>(60.0, 50.0)));"));
+                          "!((vec2<f32>(50.0f, 60.0f) > vec2<f32>(60.0f, 50.0f)));"));
 }
 
 TEST_F(SpvFUnordTest, FUnordGreaterThan_Scalar) {
@@ -655,7 +655,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : bool = !((50.0 <= 60.0));"));
+                HasSubstr("let x_1 : bool = !((50.0f <= 60.0f));"));
 }
 
 TEST_F(SpvFUnordTest, FUnordGreaterThan_Vector) {
@@ -673,7 +673,7 @@
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
                 HasSubstr("let x_1 : vec2<bool> = "
-                          "!((vec2<f32>(50.0, 60.0) <= vec2<f32>(60.0, 50.0)));"));
+                          "!((vec2<f32>(50.0f, 60.0f) <= vec2<f32>(60.0f, 50.0f)));"));
 }
 
 TEST_F(SpvFUnordTest, FUnordGreaterThanEqual_Scalar) {
@@ -690,7 +690,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : bool = !((50.0 < 60.0));"));
+                HasSubstr("let x_1 : bool = !((50.0f < 60.0f));"));
 }
 
 TEST_F(SpvFUnordTest, FUnordGreaterThanEqual_Vector) {
@@ -708,7 +708,7 @@
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
                 HasSubstr("let x_1 : vec2<bool> = !(("
-                          "vec2<f32>(50.0, 60.0) < vec2<f32>(60.0, 50.0)"
+                          "vec2<f32>(50.0f, 60.0f) < vec2<f32>(60.0f, 50.0f)"
                           "));"));
 }
 
@@ -762,7 +762,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : f32 = select(60.0, 50.0, true);"));
+                HasSubstr("let x_1 : f32 = select(60.0f, 50.0f, true);"));
 }
 
 TEST_F(SpvLogicalTest, Select_BoolCond_VectorParams) {
@@ -859,7 +859,8 @@
     auto fe = p->function_emitter(100);
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
-    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr("let x_1 : bool = isNan(50.0);"));
+    EXPECT_THAT(test::ToString(p->program(), ast_body),
+                HasSubstr("let x_1 : bool = isNan(50.0f);"));
 }
 
 TEST_F(SpvLogicalTest, IsNan_Vector) {
@@ -876,7 +877,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : vec2<bool> = isNan(vec2<f32>(50.0, 60.0));"));
+                HasSubstr("let x_1 : vec2<bool> = isNan(vec2<f32>(50.0f, 60.0f));"));
 }
 
 TEST_F(SpvLogicalTest, IsInf_Scalar) {
@@ -892,7 +893,8 @@
     auto fe = p->function_emitter(100);
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
-    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr("let x_1 : bool = isInf(50.0);"));
+    EXPECT_THAT(test::ToString(p->program(), ast_body),
+                HasSubstr("let x_1 : bool = isInf(50.0f);"));
 }
 
 TEST_F(SpvLogicalTest, IsInf_Vector) {
@@ -909,7 +911,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : vec2<bool> = isInf(vec2<f32>(50.0, 60.0));"));
+                HasSubstr("let x_1 : vec2<bool> = isInf(vec2<f32>(50.0f, 60.0f));"));
 }
 
 // TODO(dneto): Kernel-guarded instructions.
diff --git a/src/tint/reader/spirv/function_memory_test.cc b/src/tint/reader/spirv/function_memory_test.cc
index 58cceda..ed39e21 100644
--- a/src/tint/reader/spirv/function_memory_test.cc
+++ b/src/tint/reader/spirv/function_memory_test.cc
@@ -133,8 +133,9 @@
     auto fe = p->function_emitter(100);
     EXPECT_TRUE(fe.EmitBody());
     auto ast_body = fe.ast_body();
-    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(R"(x_1 = 42.0;
-x_1 = 0.0;
+    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(R"(x_1 = 42.0f;
+x_1 = 0.0f;
+return;
 )"));
 }
 
@@ -497,7 +498,7 @@
     EXPECT_TRUE(fe.EmitBody());
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("myvar[2u] = vec4<f32>(42.0, 42.0, 42.0, 42.0);"));
+                HasSubstr("myvar[2u] = vec4<f32>(42.0f, 42.0f, 42.0f, 42.0f);"));
 }
 
 TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_Array) {
@@ -529,7 +530,7 @@
     EXPECT_TRUE(fe.EmitBody());
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("myvar[2u] = vec4<f32>(42.0, 42.0, 42.0, 42.0);"));
+                HasSubstr("myvar[2u] = vec4<f32>(42.0f, 42.0f, 42.0f, 42.0f);"));
 }
 
 TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_Struct) {
@@ -559,7 +560,7 @@
     auto fe = p->function_emitter(100);
     EXPECT_TRUE(fe.EmitBody());
     auto ast_body = fe.ast_body();
-    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr("myvar.age = 42.0;"));
+    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr("myvar.age = 42.0f;"));
 }
 
 TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_Struct_DifferOnlyMemberName) {
@@ -601,8 +602,9 @@
     auto fe = p->function_emitter(100);
     EXPECT_TRUE(fe.EmitBody());
     auto ast_body = fe.ast_body();
-    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(R"(myvar.age = 42.0;
-myvar2.ancientness = 420.0;
+    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(R"(myvar.age = 42.0f;
+myvar2.ancientness = 420.0f;
+return;
 )"));
 }
 
@@ -706,7 +708,7 @@
     auto fe = p->function_emitter(100);
     EXPECT_TRUE(fe.EmitBody());
     auto ast_body = fe.ast_body();
-    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr("myvar.age[2u] = 42.0;"));
+    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr("myvar.age[2u] = 42.0f;"));
 }
 
 TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_Compound_Matrix_Vector) {
@@ -737,7 +739,7 @@
     auto fe = p->function_emitter(100);
     EXPECT_TRUE(fe.EmitBody());
     auto ast_body = fe.ast_body();
-    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr("myvar[2u].w = 42.0;"));
+    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr("myvar[2u].w = 42.0f;"));
 }
 
 TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_InvalidPointeeType) {
diff --git a/src/tint/reader/spirv/function_misc_test.cc b/src/tint/reader/spirv/function_misc_test.cc
index 9d40ffc..3f9c398 100644
--- a/src/tint/reader/spirv/function_misc_test.cc
+++ b/src/tint/reader/spirv/function_misc_test.cc
@@ -75,7 +75,8 @@
     EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(R"(let x_11 : bool = false;
 let x_12 : u32 = 0u;
 let x_13 : i32 = 0i;
-let x_14 : f32 = 0.0;
+let x_14 : f32 = 0.0f;
+return;
 )"));
 }
 
@@ -133,7 +134,8 @@
     EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(R"(let x_11 : bool = false;
 let x_12 : u32 = 0u;
 let x_13 : i32 = 0i;
-let x_14 : f32 = 0.0;
+let x_14 : f32 = 0.0f;
+return;
 )"));
 }
 
@@ -224,7 +226,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_11 : S = S(false, 0u, 0i, 0.0);"));
+                HasSubstr("let x_11 : S = S(false, 0u, 0i, 0.0f);"));
 }
 
 TEST_F(SpvParserTestMiscInstruction, OpNop) {
@@ -330,7 +332,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     const auto got = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(got, HasSubstr("let x_81 : f32 = (0.0 * 42.0);"));
+    EXPECT_THAT(got, HasSubstr("let x_81 : f32 = (0.0f * 42.0f);"));
 }
 
 // TODO(dneto): OpSizeof : requires Kernel (OpenCL)
diff --git a/src/tint/reader/spirv/function_var_test.cc b/src/tint/reader/spirv/function_var_test.cc
index c986af8..6371234 100644
--- a/src/tint/reader/spirv/function_var_test.cc
+++ b/src/tint/reader/spirv/function_var_test.cc
@@ -184,7 +184,7 @@
 var b : bool = false;
 var c : i32 = -1i;
 var d : u32 = 1u;
-var e : f32 = 1.5;
+var e : f32 = 1.5f;
 )"));
 }
 
@@ -212,7 +212,7 @@
     EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(R"(var a : bool = false;
 var b : i32 = 0i;
 var c : u32 = 0u;
-var d : f32 = 0.0;
+var d : f32 = 0.0f;
 )"));
 }
 
@@ -234,7 +234,7 @@
 
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("var x_200 : vec2<f32> = vec2<f32>(1.5, 2.0);"));
+                HasSubstr("var x_200 : vec2<f32> = vec2<f32>(1.5f, 2.0f);"));
 }
 
 TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_MatrixInitializer) {
@@ -261,9 +261,9 @@
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
                 HasSubstr("var x_200 : mat3x2<f32> = mat3x2<f32>("
-                          "vec2<f32>(1.5, 2.0), "
-                          "vec2<f32>(2.0, 3.0), "
-                          "vec2<f32>(3.0, 4.0));"));
+                          "vec2<f32>(1.5f, 2.0f), "
+                          "vec2<f32>(2.0f, 3.0f), "
+                          "vec2<f32>(3.0f, 4.0f));"));
 }
 
 TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_ArrayInitializer) {
@@ -382,7 +382,7 @@
 
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("var x_200 : S = S(1u, 1.5, array<u32, 2u>(1u, 2u));"));
+                HasSubstr("var x_200 : S = S(1u, 1.5f, array<u32, 2u>(1u, 2u));"));
 }
 
 TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_StructInitializer_Null) {
@@ -404,7 +404,7 @@
 
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("var x_200 : S = S(0u, 0.0, array<u32, 2u>());"));
+                HasSubstr("var x_200 : S = S(0u, 0.0f, array<u32, 2u>());"));
 }
 
 TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_Decorate_RelaxedPrecision) {
diff --git a/src/tint/reader/spirv/parser_impl_function_decl_test.cc b/src/tint/reader/spirv/parser_impl_function_decl_test.cc
index c821c85..fe6b1e0 100644
--- a/src/tint/reader/spirv/parser_impl_function_decl_test.cc
+++ b/src/tint/reader/spirv/parser_impl_function_decl_test.cc
@@ -402,7 +402,16 @@
     Program program = p->program();
     const auto program_ast = test::ToString(program);
     EXPECT_THAT(program_ast, HasSubstr(R"(fn ret_float() -> f32 {
-  return 0.0;
+  return 0.0f;
+}
+
+fn x_100_1() {
+  return;
+}
+
+@stage(fragment)
+fn x_100() {
+  x_100_1();
 }
 )")) << program_ast;
 }
diff --git a/src/tint/reader/spirv/parser_impl_handle_test.cc b/src/tint/reader/spirv/parser_impl_handle_test.cc
index 0af3cc5..84f3dcb 100644
--- a/src/tint/reader/spirv/parser_impl_handle_test.cc
+++ b/src/tint/reader/spirv/parser_impl_handle_test.cc
@@ -1648,7 +1648,7 @@
                         R"(@group(0) @binding(0) var x_10 : sampler_comparison;
 
 @group(2) @binding(1) var x_20 : texture_depth_2d;)",
-                        "textureGatherCompare(x_20, x_10, coords12, 0.200000003)"},
+                        "textureGatherCompare(x_20, x_10, coords12, 0.200000003f)"},
         // OpImageDrefGather 2DDepth ConstOffset signed
         ImageAccessCase{"%float 2D 1 0 0 1 Unknown",
                         "%result = OpImageDrefGather "
@@ -1656,7 +1656,7 @@
                         R"(@group(0) @binding(0) var x_10 : sampler_comparison;
 
 @group(2) @binding(1) var x_20 : texture_depth_2d;)",
-                        "textureGatherCompare(x_20, x_10, coords12, 0.200000003, "
+                        "textureGatherCompare(x_20, x_10, coords12, 0.200000003f, "
                         "vec2<i32>(3i, 4i))"},
         // OpImageDrefGather 2DDepth ConstOffset unsigned
         ImageAccessCase{"%float 2D 1 0 0 1 Unknown",
@@ -1666,7 +1666,7 @@
                         R"(@group(0) @binding(0) var x_10 : sampler_comparison;
 
 @group(2) @binding(1) var x_20 : texture_depth_2d;)",
-                        "textureGatherCompare(x_20, x_10, coords12, 0.200000003, "
+                        "textureGatherCompare(x_20, x_10, coords12, 0.200000003f, "
                         "vec2<i32>(vec2<u32>(3u, 4u)))"},
         // OpImageDrefGather 2DDepth Array
         ImageAccessCase{"%float 2D 1 1 0 1 Unknown",
@@ -1676,7 +1676,7 @@
 
 @group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
                         "textureGatherCompare(x_20, x_10, coords123.xy, "
-                        "i32(round(coords123.z)), 0.200000003)"},
+                        "i32(round(coords123.z)), 0.200000003f)"},
         // OpImageDrefGather 2DDepth Array ConstOffset signed
         ImageAccessCase{"%float 2D 1 1 0 1 Unknown",
                         "%result = OpImageDrefGather "
@@ -1685,7 +1685,7 @@
 
 @group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
                         "textureGatherCompare(x_20, x_10, coords123.xy, "
-                        "i32(round(coords123.z)), 0.200000003, vec2<i32>(3i, 4i))"},
+                        "i32(round(coords123.z)), 0.200000003f, vec2<i32>(3i, 4i))"},
         // OpImageDrefGather 2DDepth Array ConstOffset unsigned
         ImageAccessCase{"%float 2D 1 1 0 1 Unknown",
                         "%result = OpImageDrefGather "
@@ -1695,7 +1695,7 @@
 
 @group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
                         "textureGatherCompare(x_20, x_10, coords123.xy, "
-                        "i32(round(coords123.z)), 0.200000003, "
+                        "i32(round(coords123.z)), 0.200000003f, "
                         "vec2<i32>(vec2<u32>(3u, 4u)))"},
         // OpImageDrefGather DepthCube
         ImageAccessCase{"%float Cube 1 0 0 1 Unknown",
@@ -1704,7 +1704,7 @@
                         R"(@group(0) @binding(0) var x_10 : sampler_comparison;
 
 @group(2) @binding(1) var x_20 : texture_depth_cube;)",
-                        "textureGatherCompare(x_20, x_10, coords123, 0.200000003)"},
+                        "textureGatherCompare(x_20, x_10, coords123, 0.200000003f)"},
         // OpImageDrefGather DepthCube Array
         ImageAccessCase{"%float Cube 1 1 0 1 Unknown",
                         "%result = OpImageDrefGather "
@@ -1713,7 +1713,7 @@
 
 @group(2) @binding(1) var x_20 : texture_depth_cube_array;)",
                         "textureGatherCompare(x_20, x_10, coords1234.xyz, "
-                        "i32(round(coords1234.w)), 0.200000003)"}}));
+                        "i32(round(coords1234.w)), 0.200000003f)"}}));
 
 INSTANTIATE_TEST_SUITE_P(
     ImageSampleImplicitLod,
@@ -1764,7 +1764,7 @@
                         R"(@group(0) @binding(0) var x_10 : sampler;
 
 @group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-                        "textureSampleBias(x_20, x_10, coords12, 7.0)"},
+                        "textureSampleBias(x_20, x_10, coords12, 7.0f)"},
 
         // OpImageSampleImplicitLod arrayed with Bias
         ImageAccessCase{
@@ -1774,7 +1774,7 @@
             R"(@group(0) @binding(0) var x_10 : sampler;
 
 @group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
-            R"(textureSampleBias(x_20, x_10, coords123.xy, i32(round(coords123.z)), 7.0))"},
+            R"(textureSampleBias(x_20, x_10, coords123.xy, i32(round(coords123.z)), 7.0f))"},
 
         // OpImageSampleImplicitLod with Bias and signed ConstOffset
         ImageAccessCase{"%float 2D 0 0 0 1 Unknown",
@@ -1784,7 +1784,7 @@
                         R"(@group(0) @binding(0) var x_10 : sampler;
 
 @group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-                        R"(textureSampleBias(x_20, x_10, coords12, 7.0, vec2<i32>(3i, 4i))"},
+                        R"(textureSampleBias(x_20, x_10, coords12, 7.0f, vec2<i32>(3i, 4i))"},
 
         // OpImageSampleImplicitLod with Bias and unsigned ConstOffset
         // Convert ConstOffset to signed
@@ -1796,7 +1796,7 @@
             R"(@group(0) @binding(0) var x_10 : sampler;
 
 @group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            R"(textureSampleBias(x_20, x_10, coords12, 7.0, vec2<i32>(vec2<u32>(3u, 4u)))"},
+            R"(textureSampleBias(x_20, x_10, coords12, 7.0f, vec2<i32>(vec2<u32>(3u, 4u)))"},
         // OpImageSampleImplicitLod arrayed with Bias
         ImageAccessCase{
             "%float 2D 0 1 0 1 Unknown",
@@ -1806,7 +1806,7 @@
             R"(@group(0) @binding(0) var x_10 : sampler;
 
 @group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
-            R"(textureSampleBias(x_20, x_10, coords123.xy, i32(round(coords123.z)), 7.0, vec2<i32>(3i, 4i))"}));
+            R"(textureSampleBias(x_20, x_10, coords123.xy, i32(round(coords123.z)), 7.0f, vec2<i32>(3i, 4i))"}));
 
 INSTANTIATE_TEST_SUITE_P(
     // This test shows the use of a sampled image used with both regular
@@ -1831,8 +1831,8 @@
 @group(0) @binding(1) var x_30 : sampler_comparison;
 )",
                         R"(
-  let x_200 : vec4<f32> = vec4<f32>(textureSample(x_20, x_10, coords12), 0.0, 0.0, 0.0);
-  let x_210 : f32 = textureSampleCompare(x_20, x_30, coords12, 0.200000003);
+  let x_200 : vec4<f32> = vec4<f32>(textureSample(x_20, x_10, coords12), 0.0f, 0.0f, 0.0f);
+  let x_210 : f32 = textureSampleCompare(x_20, x_30, coords12, 0.200000003f);
 )"}));
 
 INSTANTIATE_TEST_SUITE_P(
@@ -1847,7 +1847,7 @@
 
 @group(2) @binding(1) var x_20 : texture_depth_2d;
 )",
-                        R"(textureSampleCompare(x_20, x_10, coords12, 0.200000003))"},
+                        R"(textureSampleCompare(x_20, x_10, coords12, 0.200000003f))"},
         // ImageSampleDrefImplicitLod - arrayed
         ImageAccessCase{
             "%float 2D 0 1 0 1 Unknown",
@@ -1856,7 +1856,7 @@
             R"(@group(0) @binding(0) var x_10 : sampler_comparison;
 
 @group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
-            R"(textureSampleCompare(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.200000003))"},
+            R"(textureSampleCompare(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.200000003f))"},
         // ImageSampleDrefImplicitLod with ConstOffset
         ImageAccessCase{
             "%float 2D 0 0 0 1 Unknown",
@@ -1866,7 +1866,7 @@
 
 @group(2) @binding(1) var x_20 : texture_depth_2d;
 )",
-            R"(textureSampleCompare(x_20, x_10, coords12, 0.200000003, vec2<i32>(3i, 4i)))"},
+            R"(textureSampleCompare(x_20, x_10, coords12, 0.200000003f, vec2<i32>(3i, 4i)))"},
         // ImageSampleDrefImplicitLod arrayed with ConstOffset
         ImageAccessCase{
             "%float 2D 0 1 0 1 Unknown",
@@ -1875,7 +1875,7 @@
             R"(@group(0) @binding(0) var x_10 : sampler_comparison;
 
 @group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
-            R"(textureSampleCompare(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.200000003, vec2<i32>(3i, 4i)))"}));
+            R"(textureSampleCompare(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.200000003f, vec2<i32>(3i, 4i)))"}));
 
 INSTANTIATE_TEST_SUITE_P(
     ImageSampleDrefExplicitLod,
@@ -1891,7 +1891,7 @@
 
 @group(2) @binding(1) var x_20 : texture_depth_2d;
 )",
-                        R"(textureSampleCompareLevel(x_20, x_10, coords12, 0.200000003))"},
+                        R"(textureSampleCompareLevel(x_20, x_10, coords12, 0.200000003f))"},
         // 2D array
         ImageAccessCase{
             "%float 2D 1 1 0 1 Unknown",
@@ -1900,7 +1900,7 @@
             R"(@group(0) @binding(0) var x_10 : sampler_comparison;
 
 @group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
-            R"(textureSampleCompareLevel(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.200000003))"},
+            R"(textureSampleCompareLevel(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.200000003f))"},
         // 2D, ConstOffset
         ImageAccessCase{
             "%float 2D 1 0 0 1 Unknown",
@@ -1911,7 +1911,7 @@
 
 @group(2) @binding(1) var x_20 : texture_depth_2d;
 )",
-            R"(textureSampleCompareLevel(x_20, x_10, coords12, 0.200000003, vec2<i32>(3i, 4i)))"},
+            R"(textureSampleCompareLevel(x_20, x_10, coords12, 0.200000003f, vec2<i32>(3i, 4i)))"},
         // 2D array, ConstOffset
         ImageAccessCase{
             "%float 2D 1 1 0 1 Unknown",
@@ -1921,7 +1921,7 @@
             R"(@group(0) @binding(0) var x_10 : sampler_comparison;
 
 @group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
-            R"(textureSampleCompareLevel(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.200000003, vec2<i32>(3i, 4i)))"},
+            R"(textureSampleCompareLevel(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.200000003f, vec2<i32>(3i, 4i)))"},
         // Cube
         ImageAccessCase{"%float Cube 1 0 0 1 Unknown",
                         "%result = OpImageSampleDrefExplicitLod "
@@ -1929,7 +1929,7 @@
                         R"(@group(0) @binding(0) var x_10 : sampler_comparison;
 
 @group(2) @binding(1) var x_20 : texture_depth_cube;)",
-                        R"(textureSampleCompareLevel(x_20, x_10, coords123, 0.200000003))"},
+                        R"(textureSampleCompareLevel(x_20, x_10, coords123, 0.200000003f))"},
         // Cube array
         ImageAccessCase{
             "%float Cube 1 1 0 1 Unknown",
@@ -1938,7 +1938,7 @@
             R"(@group(0) @binding(0) var x_10 : sampler_comparison;
 
 @group(2) @binding(1) var x_20 : texture_depth_cube_array;)",
-            R"(textureSampleCompareLevel(x_20, x_10, coords1234.xyz, i32(round(coords1234.w)), 0.200000003))"}));
+            R"(textureSampleCompareLevel(x_20, x_10, coords1234.xyz, i32(round(coords1234.w)), 0.200000003f))"}));
 
 INSTANTIATE_TEST_SUITE_P(
     ImageSampleExplicitLod_UsingLod,
@@ -1952,7 +1952,7 @@
                         R"(@group(0) @binding(0) var x_10 : sampler;
 
 @group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-                        R"(textureSampleLevel(x_20, x_10, coords12, 0.0))"},
+                        R"(textureSampleLevel(x_20, x_10, coords12, 0.0f))"},
 
         // OpImageSampleExplicitLod arrayed - using Lod
         ImageAccessCase{
@@ -1962,7 +1962,7 @@
             R"(@group(0) @binding(0) var x_10 : sampler;
 
 @group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
-            R"(textureSampleLevel(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.0))"},
+            R"(textureSampleLevel(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.0f))"},
 
         // OpImageSampleExplicitLod - using Lod and ConstOffset
         ImageAccessCase{"%float 2D 0 0 0 1 Unknown",
@@ -1972,7 +1972,7 @@
                         R"(@group(0) @binding(0) var x_10 : sampler;
 
 @group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-                        R"(textureSampleLevel(x_20, x_10, coords12, 0.0, vec2<i32>(3i, 4i)))"},
+                        R"(textureSampleLevel(x_20, x_10, coords12, 0.0f, vec2<i32>(3i, 4i)))"},
 
         // OpImageSampleExplicitLod - using Lod and unsigned ConstOffset
         // Convert the ConstOffset operand to signed
@@ -1984,7 +1984,7 @@
             R"(@group(0) @binding(0) var x_10 : sampler;
 
 @group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            R"(textureSampleLevel(x_20, x_10, coords12, 0.0, vec2<i32>(vec2<u32>(3u, 4u)))"},
+            R"(textureSampleLevel(x_20, x_10, coords12, 0.0f, vec2<i32>(vec2<u32>(3u, 4u)))"},
 
         // OpImageSampleExplicitLod arrayed - using Lod and ConstOffset
         ImageAccessCase{
@@ -1995,7 +1995,7 @@
             R"(@group(0) @binding(0) var x_10 : sampler;
 
 @group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
-            R"(textureSampleLevel(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.0, vec2<i32>(3i, 4i)))"}));
+            R"(textureSampleLevel(x_20, x_10, coords123.xy, i32(round(coords123.z)), 0.0f, vec2<i32>(3i, 4i)))"}));
 
 INSTANTIATE_TEST_SUITE_P(
     ImageSampleExplicitLod_UsingGrad,
@@ -2092,7 +2092,7 @@
 
 @group(2) @binding(1) var x_20 : texture_depth_2d;
 )",
-         R"(vec4<f32>(textureSampleLevel(x_20, x_10, vf12, i32(f1)), 0.0, 0.0, 0.0))"}}));
+         R"(vec4<f32>(textureSampleLevel(x_20, x_10, vf12, i32(f1)), 0.0f, 0.0f, 0.0f))"}}));
 
 /////
 // Projection sampling
@@ -2176,7 +2176,7 @@
                         R"(@group(0) @binding(0) var x_10 : sampler;
 
 @group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-                        R"(textureSampleBias(x_20, x_10, (coords123.xy / coords123.z), 7.0))"},
+                        R"(textureSampleBias(x_20, x_10, (coords123.xy / coords123.z), 7.0f))"},
 
         // OpImageSampleProjImplicitLod with Bias and signed ConstOffset
         ImageAccessCase{
@@ -2187,7 +2187,7 @@
             R"(@group(0) @binding(0) var x_10 : sampler;
 
 @group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            R"(textureSampleBias(x_20, x_10, (coords123.xy / coords123.z), 7.0, vec2<i32>(3i, 4i)))"},
+            R"(textureSampleBias(x_20, x_10, (coords123.xy / coords123.z), 7.0f, vec2<i32>(3i, 4i)))"},
 
         // OpImageSampleProjImplicitLod with Bias and unsigned ConstOffset
         // Convert ConstOffset to signed
@@ -2199,7 +2199,7 @@
             R"(@group(0) @binding(0) var x_10 : sampler;
 
 @group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            R"(textureSampleBias(x_20, x_10, (coords123.xy / coords123.z), 7.0, vec2<i32>(vec2<u32>(3u, 4u))))"}));
+            R"(textureSampleBias(x_20, x_10, (coords123.xy / coords123.z), 7.0f, vec2<i32>(vec2<u32>(3u, 4u))))"}));
 
 INSTANTIATE_TEST_SUITE_P(
     ImageSampleProjExplicitLod_Lod,
@@ -2266,7 +2266,7 @@
             // Sampling the depth texture yields an f32, but the
             // SPIR-V operation yiedls vec4<f32>, so fill out the
             // remaining components with 0.
-            R"(vec4<f32>(textureSample(x_20, x_10, (coords123.xy / coords123.z)), 0.0, 0.0, 0.0))"}));
+            R"(vec4<f32>(textureSample(x_20, x_10, (coords123.xy / coords123.z)), 0.0f, 0.0f, 0.0f))"}));
 
 INSTANTIATE_TEST_SUITE_P(
     ImageSampleProjDrefImplicitLod,
@@ -2311,7 +2311,7 @@
 
 @group(2) @binding(1) var x_20 : texture_depth_2d;
 )",
-            R"(textureSampleCompare(x_20, x_10, (coords123.xy / coords123.z), 0.200000003, 0.0))"},
+            R"(textureSampleCompare(x_20, x_10, (coords123.xy / coords123.z), 0.200000003f, 0.0f))"},
 
         // OpImageSampleProjDrefImplicitLod 2D depth-texture, Lod ConstOffset
         ImageAccessCase{
@@ -2323,7 +2323,7 @@
 
 @group(2) @binding(1) var x_20 : texture_depth_2d;
 )",
-            R"(textureSampleCompareLevel(x_20, x_10, (coords123.xy / coords123.z), 0.200000003, 0.0, vec2<i32>(3i, 4i)))"}));
+            R"(textureSampleCompareLevel(x_20, x_10, (coords123.xy / coords123.z), 0.200000003f, 0.0f, vec2<i32>(3i, 4i)))"}));
 
 /////
 // End projection sampling
@@ -2416,15 +2416,15 @@
         // Source 1 component
         {"%float 2D 0 0 0 2 R32f", "OpImageWrite %im %vi12 %f1",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32float, write>;)",
-         "textureStore(x_20, vi12, vec4<f32>(f1, 0.0, 0.0, 0.0));"},
+         "textureStore(x_20, vi12, vec4<f32>(f1, 0.0f, 0.0f, 0.0f));"},
         // Source 2 component, dest 1 component
         {"%float 2D 0 0 0 2 R32f", "OpImageWrite %im %vi12 %vf12",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32float, write>;)",
-         "textureStore(x_20, vi12, vec4<f32>(vf12, 0.0, 0.0));"},
+         "textureStore(x_20, vi12, vec4<f32>(vf12, 0.0f, 0.0f));"},
         // Source 3 component, dest 1 component
         {"%float 2D 0 0 0 2 R32f", "OpImageWrite %im %vi12 %vf123",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32float, write>;)",
-         "textureStore(x_20, vi12, vec4<f32>(vf123, 0.0));"},
+         "textureStore(x_20, vi12, vec4<f32>(vf123, 0.0f));"},
         // Source 4 component, dest 1 component
         {"%float 2D 0 0 0 2 R32f", "OpImageWrite %im %vi12 %vf1234",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32float, write>;)",
@@ -2432,11 +2432,11 @@
         // Source 2 component, dest 2 component
         {"%float 2D 0 0 0 2 Rg32f", "OpImageWrite %im %vi12 %vf12",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rg32float, write>;)",
-         "textureStore(x_20, vi12, vec4<f32>(vf12, 0.0, 0.0));"},
+         "textureStore(x_20, vi12, vec4<f32>(vf12, 0.0f, 0.0f));"},
         // Source 3 component, dest 2 component
         {"%float 2D 0 0 0 2 Rg32f", "OpImageWrite %im %vi12 %vf123",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rg32float, write>;)",
-         "textureStore(x_20, vi12, vec4<f32>(vf123, 0.0));"},
+         "textureStore(x_20, vi12, vec4<f32>(vf123, 0.0f));"},
         // Source 4 component, dest 2 component
         {"%float 2D 0 0 0 2 Rg32f", "OpImageWrite %im %vi12 %vf1234",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rg32float, write>;)",
@@ -2583,11 +2583,11 @@
         // Level of detail is injected for depth texture
         {"%float 2D 1 0 0 1 Unknown", "%99 = OpImageFetch %v4float %im %vi12",
          R"(@group(2) @binding(1) var x_20 : texture_depth_2d;)",
-         R"(let x_99 : vec4<f32> = vec4<f32>(textureLoad(x_20, vi12, 0i), 0.0, 0.0, 0.0);)"},
+         R"(let x_99 : vec4<f32> = vec4<f32>(textureLoad(x_20, vi12, 0i), 0.0f, 0.0f, 0.0f);)"},
         // OpImageFetch with extra params, on depth texture
         {"%float 2D 1 0 0 1 Unknown", "%99 = OpImageFetch %v4float %im %vi12 Lod %int_3",
          R"(@group(2) @binding(1) var x_20 : texture_depth_2d;)",
-         R"(let x_99 : vec4<f32> = vec4<f32>(textureLoad(x_20, vi12, 3i), 0.0, 0.0, 0.0);)"}}));
+         R"(let x_99 : vec4<f32> = vec4<f32>(textureLoad(x_20, vi12, 3i), 0.0f, 0.0f, 0.0f);)"}}));
 
 INSTANTIATE_TEST_SUITE_P(
     ImageFetch_Depth,
@@ -2600,7 +2600,7 @@
         // ImageFetch on depth image.
         {"%float 2D 1 0 0 1 Unknown", "%99 = OpImageFetch %v4float %im %vi12 ",
          R"(@group(2) @binding(1) var x_20 : texture_depth_2d;)",
-         R"(let x_99 : vec4<f32> = vec4<f32>(textureLoad(x_20, vi12, 0i), 0.0, 0.0, 0.0);)"}}));
+         R"(let x_99 : vec4<f32> = vec4<f32>(textureLoad(x_20, vi12, 0i), 0.0f, 0.0f, 0.0f);)"}}));
 
 INSTANTIATE_TEST_SUITE_P(
     ImageFetch_DepthMultisampled,
@@ -2613,7 +2613,7 @@
         // ImageFetch on multisampled depth image.
         {"%float 2D 1 0 1 1 Unknown", "%99 = OpImageFetch %v4float %im %vi12 Sample %i1",
          R"(@group(2) @binding(1) var x_20 : texture_depth_multisampled_2d;)",
-         R"(let x_99 : vec4<f32> = vec4<f32>(textureLoad(x_20, vi12, i1), 0.0, 0.0, 0.0);)"}}));
+         R"(let x_99 : vec4<f32> = vec4<f32>(textureLoad(x_20, vi12, i1), 0.0f, 0.0f, 0.0f);)"}}));
 
 INSTANTIATE_TEST_SUITE_P(ImageFetch_Multisampled,
                          SpvParserHandleTest_ImageAccessTest,
diff --git a/src/tint/reader/spirv/parser_impl_module_var_test.cc b/src/tint/reader/spirv/parser_impl_module_var_test.cc
index 8ef704e..622d383 100644
--- a/src/tint/reader/spirv/parser_impl_module_var_test.cc
+++ b/src/tint/reader/spirv/parser_impl_module_var_test.cc
@@ -408,7 +408,7 @@
     EXPECT_TRUE(p->BuildAndParseInternalModule());
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_THAT(module_str, HasSubstr("gl_Position.y = 0.0;")) << module_str;
+    EXPECT_THAT(module_str, HasSubstr("gl_Position.y = 0.0f;")) << module_str;
 }
 
 TEST_F(SpvModuleScopeVarParserTest, BuiltinPosition_StorePositionMember_TwoAccessChain) {
@@ -430,7 +430,7 @@
     EXPECT_TRUE(p->BuildAndParseInternalModule());
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_THAT(module_str, HasSubstr("gl_Position.y = 0.0;")) << module_str;
+    EXPECT_THAT(module_str, HasSubstr("gl_Position.y = 0.0f;")) << module_str;
 }
 
 TEST_F(SpvModuleScopeVarParserTest, BuiltinPointSize_Write1_IsErased) {
@@ -510,7 +510,7 @@
 var<private> gl_Position : vec4<f32>;
 
 fn main_1() {
-  x_900 = 1.0;
+  x_900 = 1.0f;
   return;
 }
 
@@ -681,7 +681,7 @@
 var<private> x_900 : f32;
 
 fn main_1() {
-  x_900 = 1.0;
+  x_900 = 1.0f;
   return;
 }
 
@@ -889,7 +889,7 @@
 
 var<private> x_4 : u32 = 1u;
 
-var<private> x_5 : f32 = 1.5;
+var<private> x_5 : f32 = 1.5f;
 )"));
 }
 
@@ -914,7 +914,7 @@
 
 var<private> x_3 : u32 = 0u;
 
-var<private> x_4 : f32 = 0.0;
+var<private> x_4 : f32 = 0.0f;
 )"));
 }
 
@@ -939,7 +939,7 @@
 
 var<private> x_3 : u32 = 0u;
 
-var<private> x_4 : f32 = 0.0;
+var<private> x_4 : f32 = 0.0f;
 )"));
 
     // This example module emits ok, but is not valid SPIR-V in the first place.
@@ -956,7 +956,7 @@
     ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : vec2<f32> = vec2<f32>(1.5, 2.0);"));
+    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : vec2<f32> = vec2<f32>(1.5f, 2.0f);"));
 }
 
 TEST_F(SpvModuleScopeVarParserTest, VectorBoolNullInitializer) {
@@ -1083,9 +1083,9 @@
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
     EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : mat3x2<f32> = mat3x2<f32>("
-                                      "vec2<f32>(1.5, 2.0), "
-                                      "vec2<f32>(2.0, 3.0), "
-                                      "vec2<f32>(3.0, 4.0));"));
+                                      "vec2<f32>(1.5f, 2.0f), "
+                                      "vec2<f32>(2.0f, 3.0f), "
+                                      "vec2<f32>(3.0f, 4.0f));"));
 }
 
 TEST_F(SpvModuleScopeVarParserTest, MatrixNullInitializer) {
@@ -1168,7 +1168,7 @@
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
     EXPECT_THAT(module_str,
-                HasSubstr("var<private> x_200 : S = S(1u, 1.5, array<u32, 2u>(1u, 2u));"))
+                HasSubstr("var<private> x_200 : S = S(1u, 1.5f, array<u32, 2u>(1u, 2u));"))
         << module_str;
 }
 
@@ -1181,7 +1181,7 @@
     ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : S = S(0u, 0.0, array<u32, 2u>());"))
+    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : S = S(0u, 0.0f, array<u32, 2u>());"))
         << module_str;
 }
 
@@ -1195,7 +1195,7 @@
     EXPECT_TRUE(p->error().empty());
 
     const auto module_str = test::ToString(p->program());
-    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : S = S(0u, 0.0, array<u32, 2u>());"))
+    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : S = S(0u, 0.0f, array<u32, 2u>());"))
         << module_str;
 
     // This example module emits ok, but is not valid SPIR-V in the first place.
@@ -1565,7 +1565,7 @@
     ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_THAT(module_str, HasSubstr("@id(12) override myconst : f32 = 2.5;")) << module_str;
+    EXPECT_THAT(module_str, HasSubstr("@id(12) override myconst : f32 = 2.5f;")) << module_str;
 }
 
 TEST_F(SpvModuleScopeVarParserTest, ScalarSpecConstant_DeclareConst_F32_WithoutSpecId) {
@@ -1580,7 +1580,7 @@
     ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error();
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_THAT(module_str, HasSubstr("override myconst : f32 = 2.5;")) << module_str;
+    EXPECT_THAT(module_str, HasSubstr("override myconst : f32 = 2.5f;")) << module_str;
 }
 
 TEST_F(SpvModuleScopeVarParserTest, ScalarSpecConstant_UsedInFunction) {
@@ -3854,7 +3854,7 @@
     ASSERT_TRUE(p->Parse()) << p->error() << assembly;
     EXPECT_TRUE(p->error().empty());
     const auto got = test::ToString(p->program());
-    const std::string expected = R"(var<private> x_1 : f32 = 0.0;
+    const std::string expected = R"(var<private> x_1 : f32 = 0.0f;
 
 fn main_1() {
   return;
@@ -3957,7 +3957,7 @@
 
     const auto got = test::ToString(p->program());
     const std::string expected =
-        R"(var<private> gl_Position : vec4<f32> = vec4<f32>(1.0, 2.0, 3.0, 4.0);
+        R"(var<private> gl_Position : vec4<f32> = vec4<f32>(1.0f, 2.0f, 3.0f, 4.0f);
 
 fn main_1() {
   return;
diff --git a/src/tint/resolver/array_accessor_test.cc b/src/tint/resolver/array_accessor_test.cc
index 9323b23..d9ef10e 100644
--- a/src/tint/resolver/array_accessor_test.cc
+++ b/src/tint/resolver/array_accessor_test.cc
@@ -156,19 +156,36 @@
     EXPECT_TRUE(ref->StoreType()->Is<sem::F32>());
 }
 
-TEST_F(ResolverIndexAccessorTest, Array) {
-    auto* idx = Expr(2_i);
+TEST_F(ResolverIndexAccessorTest, Array_Literal_i32) {
     Global("my_var", ty.array<f32, 3>(), ast::StorageClass::kPrivate);
-
-    auto* acc = IndexAccessor("my_var", idx);
+    auto* acc = IndexAccessor("my_var", 2_i);
     WrapInFunction(acc);
-
     EXPECT_TRUE(r()->Resolve()) << r()->error();
-
     ASSERT_NE(TypeOf(acc), nullptr);
-    ASSERT_TRUE(TypeOf(acc)->Is<sem::Reference>());
-
     auto* ref = TypeOf(acc)->As<sem::Reference>();
+    ASSERT_NE(ref, nullptr);
+    EXPECT_TRUE(ref->StoreType()->Is<sem::F32>());
+}
+
+TEST_F(ResolverIndexAccessorTest, Array_Literal_u32) {
+    Global("my_var", ty.array<f32, 3>(), ast::StorageClass::kPrivate);
+    auto* acc = IndexAccessor("my_var", 2_u);
+    WrapInFunction(acc);
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+    ASSERT_NE(TypeOf(acc), nullptr);
+    auto* ref = TypeOf(acc)->As<sem::Reference>();
+    ASSERT_NE(ref, nullptr);
+    EXPECT_TRUE(ref->StoreType()->Is<sem::F32>());
+}
+
+TEST_F(ResolverIndexAccessorTest, Array_Literal_AInt) {
+    Global("my_var", ty.array<f32, 3>(), ast::StorageClass::kPrivate);
+    auto* acc = IndexAccessor("my_var", 2_a);
+    WrapInFunction(acc);
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+    ASSERT_NE(TypeOf(acc), nullptr);
+    auto* ref = TypeOf(acc)->As<sem::Reference>();
+    ASSERT_NE(ref, nullptr);
     EXPECT_TRUE(ref->StoreType()->Is<sem::F32>());
 }
 
@@ -249,7 +266,7 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverIndexAccessorTest, EXpr_Deref_FuncGoodParent) {
+TEST_F(ResolverIndexAccessorTest, Expr_Deref_FuncGoodParent) {
     // fn func(p: ptr<function, vec4<f32>>) -> f32 {
     //     let idx: u32 = u32();
     //     let x: f32 = (*p)[idx];
@@ -265,7 +282,7 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverIndexAccessorTest, EXpr_Deref_FuncBadParent) {
+TEST_F(ResolverIndexAccessorTest, Expr_Deref_FuncBadParent) {
     // fn func(p: ptr<function, vec4<f32>>) -> f32 {
     //     let idx: u32 = u32();
     //     let x: f32 = *p[idx];
diff --git a/src/tint/resolver/assignment_validation_test.cc b/src/tint/resolver/assignment_validation_test.cc
index e9fb7bf..64363e3 100644
--- a/src/tint/resolver/assignment_validation_test.cc
+++ b/src/tint/resolver/assignment_validation_test.cc
@@ -147,29 +147,33 @@
     // var my_var : i32 = 2i;
     // 1 = my_var;
 
-    auto* var = Var("my_var", ty.i32(), ast::StorageClass::kNone, Expr(2_i));
-    WrapInFunction(var, Assign(Expr(Source{{12, 34}}, 1_i), "my_var"));
+    WrapInFunction(Var("my_var", ty.i32(), ast::StorageClass::kNone, Expr(2_i)),  //
+                   Assign(Expr(Source{{12, 34}}, 1_i), "my_var"));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), "12:34 error: cannot assign to value of type 'i32'");
 }
 
 TEST_F(ResolverAssignmentValidationTest, AssignCompatibleTypes_Pass) {
-    // var a : i32 = 2i;
-    // a = 2i
-    auto* var = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2_i));
-    WrapInFunction(var, Assign(Source{{12, 34}}, "a", 2_i));
+    // var a : i32 = 1i;
+    // a = 2i;
+    // a = 3;
+    WrapInFunction(Var("a", ty.i32(), ast::StorageClass::kNone, Expr(1_i)),  //
+                   Assign("a", 2_i),                                         //
+                   Assign("a", 3_a));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
 TEST_F(ResolverAssignmentValidationTest, AssignCompatibleTypesThroughAlias_Pass) {
-    // alias myint = i32;
-    // var a : myint = 2i;
-    // a = 2
-    auto* myint = Alias("myint", ty.i32());
-    auto* var = Var("a", ty.Of(myint), ast::StorageClass::kNone, Expr(2_i));
-    WrapInFunction(var, Assign(Source{{12, 34}}, "a", 2_i));
+    // alias myint = u32;
+    // var a : myint = 1u;
+    // a = 2u;
+    // a = 3;
+    auto* myint = Alias("myint", ty.u32());
+    WrapInFunction(Var("a", ty.Of(myint), ast::StorageClass::kNone, Expr(1_u)),  //
+                   Assign("a", 2_u),                                             //
+                   Assign("a", 3_a));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -178,9 +182,9 @@
     // var a : i32 = 2i;
     // var b : i32 = 3i;
     // a = b;
-    auto* var_a = Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2_i));
-    auto* var_b = Var("b", ty.i32(), ast::StorageClass::kNone, Expr(3_i));
-    WrapInFunction(var_a, var_b, Assign(Source{{12, 34}}, "a", "b"));
+    WrapInFunction(Var("a", ty.i32(), ast::StorageClass::kNone, Expr(2_i)),  //
+                   Var("b", ty.i32(), ast::StorageClass::kNone, Expr(3_i)),  //
+                   Assign("a", "b"));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
@@ -190,14 +194,26 @@
     // let b : ptr<function,i32> = &a;
     // *b = 2i;
     const auto func = ast::StorageClass::kFunction;
-    auto* var_a = Var("a", ty.i32(), func, Expr(2_i));
-    auto* var_b = Let("b", ty.pointer<i32>(func), AddressOf(Expr("a")));
-    WrapInFunction(var_a, var_b, Assign(Source{{12, 34}}, Deref("b"), 2_i));
+    WrapInFunction(Var("a", ty.i32(), func, Expr(2_i)),                    //
+                   Let("b", ty.pointer<i32>(func), AddressOf(Expr("a"))),  //
+                   Assign(Deref("b"), 2_i));
 
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAssignmentValidationTest, AssignToConstant_Fail) {
+TEST_F(ResolverAssignmentValidationTest, AssignMaterializedThroughPointer_Pass) {
+    // var a : i32;
+    // let b : ptr<function,i32> = &a;
+    // *b = 2;
+    const auto func = ast::StorageClass::kFunction;
+    auto* var_a = Var("a", ty.i32(), func, Expr(2_i));
+    auto* var_b = Let("b", ty.pointer<i32>(func), AddressOf(Expr("a")));
+    WrapInFunction(var_a, var_b, Assign(Deref("b"), 2_a));
+
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAssignmentValidationTest, AssignToLet_Fail) {
     // {
     //  let a : i32 = 2i;
     //  a = 2i
@@ -328,8 +344,12 @@
     // fn f() {
     //   _ = 1i;
     //   _ = 2u;
-    //   _ = 3.0;
-    //   _ = vec2<bool>();
+    //   _ = 3.0f;
+    //   _ = 4;
+    //   _ = 5.0;
+    //   _ = vec2(6);
+    //   _ = vec3(7.0);
+    //   _ = vec4<bool>();
     //   _ = tex;
     //   _ = smp;
     //   _ = &s;
@@ -354,7 +374,11 @@
     WrapInFunction(Assign(Phony(), 1_i),                                    //
                    Assign(Phony(), 2_u),                                    //
                    Assign(Phony(), 3_f),                                    //
-                   Assign(Phony(), vec2<bool>()),                           //
+                   Assign(Phony(), 4_a),                                    //
+                   Assign(Phony(), 5.0_a),                                  //
+                   Assign(Phony(), vec(nullptr, 2u, 6_a)),                  //
+                   Assign(Phony(), vec(nullptr, 3u, 7.0_a)),                //
+                   Assign(Phony(), vec4<bool>()),                           //
                    Assign(Phony(), "tex"),                                  //
                    Assign(Phony(), "smp"),                                  //
                    Assign(Phony(), AddressOf("s")),                         //
diff --git a/src/tint/resolver/intrinsic_table.cc b/src/tint/resolver/intrinsic_table.cc
index 6c19f7c..4d9ab77 100644
--- a/src/tint/resolver/intrinsic_table.cc
+++ b/src/tint/resolver/intrinsic_table.cc
@@ -722,6 +722,14 @@
     return true;
 }
 
+bool match_atomic_compare_exchange_result(const sem::Type* ty, const sem::Type*& T) {
+    if (ty->Is<Any>()) {
+        T = ty;
+        return true;
+    }
+    return false;
+}
+
 struct NameAndType {
     std::string name;
     sem::Type* type;
@@ -779,6 +787,13 @@
                         {{"sig", vec_f32}, {"exp", vec_i32}});
 }
 
+const sem::Struct* build_atomic_compare_exchange_result(MatchState& state, const sem::Type* ty) {
+    return build_struct(
+        state, "__atomic_compare_exchange_result" + ty->FriendlyName(state.builder.Symbols()),
+        {{"old_value", const_cast<sem::Type*>(ty)},
+         {"exchanged", state.builder.create<sem::Bool>()}});
+}
+
 /// ParameterInfo describes a parameter
 struct ParameterInfo {
     /// The parameter usage (parameter name in definition file)
@@ -929,6 +944,12 @@
     /// Callback function when no overloads match.
     using OnNoMatch = std::function<void(Candidates)>;
 
+    /// Sorts the candidates based on their score, with the lowest (best-ranking) scores first.
+    static inline void SortCandidates(Candidates& candidates) {
+        std::stable_sort(candidates.begin(), candidates.end(),
+                         [&](const Candidate& a, const Candidate& b) { return a.score < b.score; });
+    }
+
     /// Attempts to find a single intrinsic overload that matches the provided argument types.
     /// @param intrinsic the intrinsic being called
     /// @param intrinsic_name the name of the intrinsic
@@ -947,7 +968,7 @@
                                       TemplateState templates,
                                       OnNoMatch on_no_match) const;
 
-    /// Evaluates the overload for the provided argument types.
+    /// Evaluates the single overload for the provided argument types.
     /// @param overload the overload being considered
     /// @param args the argument types
     /// @param templates initial template state. This may contain explicitly specified template
@@ -958,6 +979,21 @@
                             const std::vector<const sem::Type*>& args,
                             TemplateState templates) const;
 
+    /// Performs overload resolution given the list of candidates, by ranking the conversions of
+    /// arguments to the each of the candidate's parameter types.
+    /// @param candidates the list of candidate overloads
+    /// @param intrinsic_name the name of the intrinsic
+    /// @param args the argument types
+    /// @param templates initial template state. This may contain explicitly specified template
+    ///                  arguments. For example `vec3<f32>()` would have the first template-type
+    ///                  template as `f32`.
+    /// @see https://www.w3.org/TR/WGSL/#overload-resolution-section
+    /// @returns the resolved Candidate.
+    Candidate ResolveCandidate(Candidates&& candidates,
+                               const char* intrinsic_name,
+                               const std::vector<const sem::Type*>& args,
+                               TemplateState templates) const;
+
     /// Match constructs a new MatchState
     /// @param templates the template state used for matcher evaluation
     /// @param overload the overload being evaluated
@@ -976,12 +1012,11 @@
                          const Candidates& candidates,
                          const char* intrinsic_name) const;
 
-    /// Raises an ICE when multiple overload candidates match, as this should never happen.
-    void ErrMultipleOverloadsMatched(size_t num_matched,
-                                     const char* intrinsic_name,
-                                     const std::vector<const sem::Type*>& args,
-                                     TemplateState templates,
-                                     Candidates candidates) const;
+    /// Raises an error when no overload is a clear winner of overload resolution
+    void ErrAmbiguousOverload(const char* intrinsic_name,
+                              const std::vector<const sem::Type*>& args,
+                              TemplateState templates,
+                              Candidates candidates) const;
 
     ProgramBuilder& builder;
     Matchers matchers;
@@ -1265,38 +1300,38 @@
                                         TemplateState templates,
                                         OnNoMatch on_no_match) const {
     size_t num_matched = 0;
+    size_t match_idx = 0;
     Candidates candidates;
     candidates.reserve(intrinsic.num_overloads);
     for (size_t overload_idx = 0; overload_idx < static_cast<size_t>(intrinsic.num_overloads);
          overload_idx++) {
         auto candidate = ScoreOverload(&intrinsic.overloads[overload_idx], args, templates);
         if (candidate.score == 0) {
+            match_idx = overload_idx;
             num_matched++;
         }
         candidates.emplace_back(std::move(candidate));
     }
 
-    // Sort the candidates with the most promising first
-    std::stable_sort(candidates.begin(), candidates.end(),
-                     [&](const Candidate& a, const Candidate& b) { return a.score < b.score; });
-
     // How many candidates matched?
-    switch (num_matched) {
-        case 0:
-            on_no_match(std::move(candidates));
-            return {};
-        case 1:
-            break;
-        default:
-            // Note: Currently the intrinsic table does not contain any overloads which may result
-            // in ambiguities, so here we call ErrMultipleOverloadsMatched() which will produce and
-            // ICE. If we end up in the situation where this is unavoidable, we'll need to perform
-            // further overload resolution as described in
-            // https://www.w3.org/TR/WGSL/#overload-resolution-section.
-            ErrMultipleOverloadsMatched(num_matched, intrinsic_name, args, templates, candidates);
+    if (num_matched == 0) {
+        // Sort the candidates with the most promising first
+        SortCandidates(candidates);
+        on_no_match(std::move(candidates));
+        return {};
     }
 
-    auto match = candidates[0];
+    Candidate match;
+
+    if (num_matched == 1) {
+        match = std::move(candidates[match_idx]);
+    } else {
+        match = ResolveCandidate(std::move(candidates), intrinsic_name, args, std::move(templates));
+        if (!match.overload) {
+            // Ambiguous overload. ResolveCandidate() will have already raised an error diagnostic.
+            return {};
+        }
+    }
 
     // Build the return type
     const sem::Type* return_type = nullptr;
@@ -1408,6 +1443,70 @@
     return Candidate{overload, templates, parameters, score};
 }
 
+Impl::Candidate Impl::ResolveCandidate(Impl::Candidates&& candidates,
+                                       const char* intrinsic_name,
+                                       const std::vector<const sem::Type*>& args,
+                                       TemplateState templates) const {
+    std::vector<uint32_t> best_ranks(args.size(), 0xffffffff);
+    size_t num_matched = 0;
+    Candidate* best = nullptr;
+    for (auto& candidate : candidates) {
+        if (candidate.score > 0) {
+            continue;  // Candidate has already been ruled out.
+        }
+        bool some_won = false;   // An argument ranked less than the 'best' overload's argument
+        bool some_lost = false;  // An argument ranked more than the 'best' overload's argument
+        for (size_t i = 0; i < args.size(); i++) {
+            auto rank = sem::Type::ConversionRank(args[i], candidate.parameters[i].type);
+            if (best_ranks[i] > rank) {
+                best_ranks[i] = rank;
+                some_won = true;
+            } else if (best_ranks[i] < rank) {
+                some_lost = true;
+            }
+        }
+        // If no arguments of this candidate ranked worse than the previous best candidate, then
+        // this candidate becomes the new best candidate.
+        // If no arguments of this candidate ranked better than the previous best candidate, then
+        // this candidate is removed from the list of matches.
+        // If neither of the above apply, then we have two candidates with no clear winner, which
+        // results in an ambiguous overload error. In this situation the loop ends with
+        // `num_matched > 1`.
+        if (some_won) {
+            // One or more arguments of this candidate ranked better than the previous best
+            // candidate's argument(s).
+            num_matched++;
+            if (!some_lost) {
+                // All arguments were at as-good or better than the previous best.
+                if (best) {
+                    // Mark the previous best candidate as no longer being in the running, by
+                    // setting its score to a non-zero value. We pick 1 as this is the closest to 0
+                    // (match) as we can get.
+                    best->score = 1;
+                    num_matched--;
+                }
+                // This candidate is the new best.
+                best = &candidate;
+            }
+        } else {
+            // No arguments ranked better than the current best.
+            // Change the score of this candidate to a non-zero value, so that it's not considered a
+            // match.
+            candidate.score = 1;
+        }
+    }
+
+    if (num_matched > 1) {
+        // Re-sort the candidates with the most promising first
+        SortCandidates(candidates);
+        // Raise an error
+        ErrAmbiguousOverload(intrinsic_name, args, templates, candidates);
+        return {};
+    }
+
+    return std::move(*best);
+}
+
 MatchState Impl::Match(TemplateState& templates,
                        const OverloadInfo* overload,
                        MatcherIndex const* matcher_indices) const {
@@ -1497,13 +1596,12 @@
     return matcher->String(this);
 }
 
-void Impl::ErrMultipleOverloadsMatched(size_t num_matched,
-                                       const char* intrinsic_name,
-                                       const std::vector<const sem::Type*>& args,
-                                       TemplateState templates,
-                                       Candidates candidates) const {
+void Impl::ErrAmbiguousOverload(const char* intrinsic_name,
+                                const std::vector<const sem::Type*>& args,
+                                TemplateState templates,
+                                Candidates candidates) const {
     std::stringstream ss;
-    ss << num_matched << " overloads matched " << intrinsic_name;
+    ss << "ambiguous overload while attempting to match " << intrinsic_name;
     for (size_t i = 0; i < std::numeric_limits<size_t>::max(); i++) {
         if (auto* ty = templates.Type(i)) {
             ss << ((i == 0) ? "<" : ", ") << ty->FriendlyName(builder.Symbols());
diff --git a/src/tint/resolver/intrinsic_table.inl b/src/tint/resolver/intrinsic_table.inl
index c518e58..7a422fb 100644
--- a/src/tint/resolver/intrinsic_table.inl
+++ b/src/tint/resolver/intrinsic_table.inl
@@ -1512,8 +1512,41 @@
   return ss.str();
 }
 
+/// TypeMatcher for 'type __atomic_compare_exchange_result'
+/// @see src/tint/intrinsics.def:121:6
+class AtomicCompareExchangeResult : public TypeMatcher {
+ public:
+  /// Checks whether the given type matches the matcher rules.
+  /// Match may define and refine the template types and numbers in state.
+  /// @param state the MatchState
+  /// @param type the type to match
+  /// @returns the canonicalized type on match, otherwise nullptr
+  const sem::Type* Match(MatchState& state,
+                         const sem::Type* type) const override;
+  /// @param state the MatchState
+  /// @return a string representation of the matcher.
+  std::string String(MatchState* state) const override;
+};
+
+const sem::Type* AtomicCompareExchangeResult::Match(MatchState& state, const sem::Type* ty) const {
+  const sem::Type* T = nullptr;
+  if (!match_atomic_compare_exchange_result(ty, T)) {
+    return nullptr;
+  }
+  T = state.Type(T);
+  if (T == nullptr) {
+    return nullptr;
+  }
+  return build_atomic_compare_exchange_result(state, T);
+}
+
+std::string AtomicCompareExchangeResult::String(MatchState* state) const {
+  const std::string T = state->TypeName();
+  return "__atomic_compare_exchange_result<" + T + ">";
+}
+
 /// TypeMatcher for 'match fiu32'
-/// @see src/tint/intrinsics.def:127:7
+/// @see src/tint/intrinsics.def:129:7
 class Fiu32 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -1551,7 +1584,7 @@
 }
 
 /// TypeMatcher for 'match fi32'
-/// @see src/tint/intrinsics.def:128:7
+/// @see src/tint/intrinsics.def:130:7
 class Fi32 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -1586,7 +1619,7 @@
 }
 
 /// TypeMatcher for 'match iu32'
-/// @see src/tint/intrinsics.def:129:7
+/// @see src/tint/intrinsics.def:131:7
 class Iu32 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -1621,7 +1654,7 @@
 }
 
 /// TypeMatcher for 'match scalar'
-/// @see src/tint/intrinsics.def:130:7
+/// @see src/tint/intrinsics.def:132:7
 class Scalar : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -1662,7 +1695,7 @@
 }
 
 /// TypeMatcher for 'match abstract_or_scalar'
-/// @see src/tint/intrinsics.def:131:7
+/// @see src/tint/intrinsics.def:133:7
 class AbstractOrScalar : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -1709,7 +1742,7 @@
 }
 
 /// TypeMatcher for 'match af_f32'
-/// @see src/tint/intrinsics.def:132:7
+/// @see src/tint/intrinsics.def:134:7
 class AfF32 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -1744,7 +1777,7 @@
 }
 
 /// TypeMatcher for 'match scalar_no_f32'
-/// @see src/tint/intrinsics.def:133:7
+/// @see src/tint/intrinsics.def:135:7
 class ScalarNoF32 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -1782,7 +1815,7 @@
 }
 
 /// TypeMatcher for 'match scalar_no_i32'
-/// @see src/tint/intrinsics.def:134:7
+/// @see src/tint/intrinsics.def:136:7
 class ScalarNoI32 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -1820,7 +1853,7 @@
 }
 
 /// TypeMatcher for 'match scalar_no_u32'
-/// @see src/tint/intrinsics.def:135:7
+/// @see src/tint/intrinsics.def:137:7
 class ScalarNoU32 : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -1858,7 +1891,7 @@
 }
 
 /// TypeMatcher for 'match scalar_no_bool'
-/// @see src/tint/intrinsics.def:136:7
+/// @see src/tint/intrinsics.def:138:7
 class ScalarNoBool : public TypeMatcher {
  public:
   /// Checks whether the given type matches the matcher rules, and returns the
@@ -1896,7 +1929,7 @@
 }
 
 /// EnumMatcher for 'match f32_texel_format'
-/// @see src/tint/intrinsics.def:147:7
+/// @see src/tint/intrinsics.def:149:7
 class F32TexelFormat : public NumberMatcher {
  public:
   /// Checks whether the given number matches the enum matcher rules.
@@ -1929,7 +1962,7 @@
 }
 
 /// EnumMatcher for 'match i32_texel_format'
-/// @see src/tint/intrinsics.def:149:7
+/// @see src/tint/intrinsics.def:151:7
 class I32TexelFormat : public NumberMatcher {
  public:
   /// Checks whether the given number matches the enum matcher rules.
@@ -1961,7 +1994,7 @@
 }
 
 /// EnumMatcher for 'match u32_texel_format'
-/// @see src/tint/intrinsics.def:151:7
+/// @see src/tint/intrinsics.def:153:7
 class U32TexelFormat : public NumberMatcher {
  public:
   /// Checks whether the given number matches the enum matcher rules.
@@ -1993,7 +2026,7 @@
 }
 
 /// EnumMatcher for 'match write_only'
-/// @see src/tint/intrinsics.def:154:7
+/// @see src/tint/intrinsics.def:156:7
 class WriteOnly : public NumberMatcher {
  public:
   /// Checks whether the given number matches the enum matcher rules.
@@ -2019,7 +2052,7 @@
 }
 
 /// EnumMatcher for 'match function_private_workgroup'
-/// @see src/tint/intrinsics.def:156:7
+/// @see src/tint/intrinsics.def:158:7
 class FunctionPrivateWorkgroup : public NumberMatcher {
  public:
   /// Checks whether the given number matches the enum matcher rules.
@@ -2049,7 +2082,7 @@
 }
 
 /// EnumMatcher for 'match workgroup_or_storage'
-/// @see src/tint/intrinsics.def:157:7
+/// @see src/tint/intrinsics.def:159:7
 class WorkgroupOrStorage : public NumberMatcher {
  public:
   /// Checks whether the given number matches the enum matcher rules.
@@ -2206,6 +2239,7 @@
   ModfResultVec ModfResultVec_;
   FrexpResult FrexpResult_;
   FrexpResultVec FrexpResultVec_;
+  AtomicCompareExchangeResult AtomicCompareExchangeResult_;
   Fiu32 Fiu32_;
   Fi32 Fi32_;
   Iu32 Iu32_;
@@ -2233,7 +2267,7 @@
   ~Matchers();
 
   /// The template types, types, and type matchers
-  TypeMatcher const* const type[58] = {
+  TypeMatcher const* const type[59] = {
     /* [0] */ &template_type_0_,
     /* [1] */ &template_type_1_,
     /* [2] */ &Bool_,
@@ -2282,16 +2316,17 @@
     /* [45] */ &ModfResultVec_,
     /* [46] */ &FrexpResult_,
     /* [47] */ &FrexpResultVec_,
-    /* [48] */ &Fiu32_,
-    /* [49] */ &Fi32_,
-    /* [50] */ &Iu32_,
-    /* [51] */ &Scalar_,
-    /* [52] */ &AbstractOrScalar_,
-    /* [53] */ &AfF32_,
-    /* [54] */ &ScalarNoF32_,
-    /* [55] */ &ScalarNoI32_,
-    /* [56] */ &ScalarNoU32_,
-    /* [57] */ &ScalarNoBool_,
+    /* [48] */ &AtomicCompareExchangeResult_,
+    /* [49] */ &Fiu32_,
+    /* [50] */ &Fi32_,
+    /* [51] */ &Iu32_,
+    /* [52] */ &Scalar_,
+    /* [53] */ &AbstractOrScalar_,
+    /* [54] */ &AfF32_,
+    /* [55] */ &ScalarNoF32_,
+    /* [56] */ &ScalarNoI32_,
+    /* [57] */ &ScalarNoU32_,
+    /* [58] */ &ScalarNoBool_,
   };
 
   /// The template numbers, and number matchers
@@ -2488,34 +2523,36 @@
   /* [170] */ 7,
   /* [171] */ 17,
   /* [172] */ 0,
-  /* [173] */ 18,
-  /* [174] */ 7,
+  /* [173] */ 48,
+  /* [174] */ 0,
   /* [175] */ 18,
-  /* [176] */ 0,
-  /* [177] */ 27,
-  /* [178] */ 7,
-  /* [179] */ 28,
+  /* [176] */ 7,
+  /* [177] */ 18,
+  /* [178] */ 0,
+  /* [179] */ 27,
   /* [180] */ 7,
-  /* [181] */ 29,
+  /* [181] */ 28,
   /* [182] */ 7,
-  /* [183] */ 19,
+  /* [183] */ 29,
   /* [184] */ 7,
-  /* [185] */ 30,
+  /* [185] */ 19,
   /* [186] */ 7,
-  /* [187] */ 31,
+  /* [187] */ 30,
   /* [188] */ 7,
-  /* [189] */ 32,
+  /* [189] */ 31,
   /* [190] */ 7,
-  /* [191] */ 25,
-  /* [192] */ 26,
-  /* [193] */ 37,
-  /* [194] */ 36,
-  /* [195] */ 35,
-  /* [196] */ 34,
-  /* [197] */ 43,
-  /* [198] */ 38,
-  /* [199] */ 44,
-  /* [200] */ 46,
+  /* [191] */ 32,
+  /* [192] */ 7,
+  /* [193] */ 25,
+  /* [194] */ 26,
+  /* [195] */ 37,
+  /* [196] */ 36,
+  /* [197] */ 35,
+  /* [198] */ 34,
+  /* [199] */ 43,
+  /* [200] */ 38,
+  /* [201] */ 44,
+  /* [202] */ 46,
 };
 
 // Assert that the MatcherIndex is big enough to index all the matchers, plus
@@ -2853,12 +2890,12 @@
   {
     /* [65] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[181],
+    /* matcher indices */ &kMatcherIndices[183],
   },
   {
     /* [66] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [67] */
@@ -2888,12 +2925,12 @@
   {
     /* [72] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[195],
+    /* matcher indices */ &kMatcherIndices[197],
   },
   {
     /* [73] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [74] */
@@ -2948,12 +2985,12 @@
   {
     /* [84] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[179],
+    /* matcher indices */ &kMatcherIndices[181],
   },
   {
     /* [85] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [86] */
@@ -3018,7 +3055,7 @@
   {
     /* [98] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [99] */
@@ -3038,12 +3075,12 @@
   {
     /* [102] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[181],
+    /* matcher indices */ &kMatcherIndices[183],
   },
   {
     /* [103] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [104] */
@@ -3068,12 +3105,12 @@
   {
     /* [108] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[195],
+    /* matcher indices */ &kMatcherIndices[197],
   },
   {
     /* [109] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[192],
+    /* matcher indices */ &kMatcherIndices[194],
   },
   {
     /* [110] */
@@ -3098,12 +3135,12 @@
   {
     /* [114] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[185],
+    /* matcher indices */ &kMatcherIndices[187],
   },
   {
     /* [115] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [116] */
@@ -3128,12 +3165,12 @@
   {
     /* [120] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[189],
+    /* matcher indices */ &kMatcherIndices[191],
   },
   {
     /* [121] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [122] */
@@ -3158,12 +3195,12 @@
   {
     /* [126] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[181],
+    /* matcher indices */ &kMatcherIndices[183],
   },
   {
     /* [127] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [128] */
@@ -3188,12 +3225,12 @@
   {
     /* [132] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[181],
+    /* matcher indices */ &kMatcherIndices[183],
   },
   {
     /* [133] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [134] */
@@ -3218,12 +3255,12 @@
   {
     /* [138] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[195],
+    /* matcher indices */ &kMatcherIndices[197],
   },
   {
     /* [139] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[192],
+    /* matcher indices */ &kMatcherIndices[194],
   },
   {
     /* [140] */
@@ -3248,12 +3285,12 @@
   {
     /* [144] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[195],
+    /* matcher indices */ &kMatcherIndices[197],
   },
   {
     /* [145] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[192],
+    /* matcher indices */ &kMatcherIndices[194],
   },
   {
     /* [146] */
@@ -3278,12 +3315,12 @@
   {
     /* [150] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[195],
+    /* matcher indices */ &kMatcherIndices[197],
   },
   {
     /* [151] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [152] */
@@ -3303,12 +3340,12 @@
   {
     /* [155] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[196],
+    /* matcher indices */ &kMatcherIndices[198],
   },
   {
     /* [156] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [157] */
@@ -3328,12 +3365,12 @@
   {
     /* [160] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[193],
+    /* matcher indices */ &kMatcherIndices[195],
   },
   {
     /* [161] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[192],
+    /* matcher indices */ &kMatcherIndices[194],
   },
   {
     /* [162] */
@@ -3353,12 +3390,12 @@
   {
     /* [165] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[189],
+    /* matcher indices */ &kMatcherIndices[191],
   },
   {
     /* [166] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [167] */
@@ -3378,12 +3415,12 @@
   {
     /* [170] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[193],
+    /* matcher indices */ &kMatcherIndices[195],
   },
   {
     /* [171] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [172] */
@@ -3403,12 +3440,12 @@
   {
     /* [175] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[185],
+    /* matcher indices */ &kMatcherIndices[187],
   },
   {
     /* [176] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [177] */
@@ -3428,12 +3465,12 @@
   {
     /* [180] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[179],
+    /* matcher indices */ &kMatcherIndices[181],
   },
   {
     /* [181] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [182] */
@@ -3453,12 +3490,12 @@
   {
     /* [185] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[195],
+    /* matcher indices */ &kMatcherIndices[197],
   },
   {
     /* [186] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[192],
+    /* matcher indices */ &kMatcherIndices[194],
   },
   {
     /* [187] */
@@ -3478,12 +3515,12 @@
   {
     /* [190] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[181],
+    /* matcher indices */ &kMatcherIndices[183],
   },
   {
     /* [191] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [192] */
@@ -3503,12 +3540,12 @@
   {
     /* [195] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[179],
+    /* matcher indices */ &kMatcherIndices[181],
   },
   {
     /* [196] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [197] */
@@ -3528,12 +3565,12 @@
   {
     /* [200] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[195],
+    /* matcher indices */ &kMatcherIndices[197],
   },
   {
     /* [201] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [202] */
@@ -3553,12 +3590,12 @@
   {
     /* [205] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[196],
+    /* matcher indices */ &kMatcherIndices[198],
   },
   {
     /* [206] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[192],
+    /* matcher indices */ &kMatcherIndices[194],
   },
   {
     /* [207] */
@@ -3578,12 +3615,12 @@
   {
     /* [210] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[187],
+    /* matcher indices */ &kMatcherIndices[189],
   },
   {
     /* [211] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [212] */
@@ -3603,12 +3640,12 @@
   {
     /* [215] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[181],
+    /* matcher indices */ &kMatcherIndices[183],
   },
   {
     /* [216] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [217] */
@@ -3628,12 +3665,12 @@
   {
     /* [220] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[185],
+    /* matcher indices */ &kMatcherIndices[187],
   },
   {
     /* [221] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [222] */
@@ -3653,12 +3690,12 @@
   {
     /* [225] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[195],
+    /* matcher indices */ &kMatcherIndices[197],
   },
   {
     /* [226] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [227] */
@@ -3688,7 +3725,7 @@
   {
     /* [232] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [233] */
@@ -3713,7 +3750,7 @@
   {
     /* [237] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [238] */
@@ -3728,12 +3765,12 @@
   {
     /* [240] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[179],
+    /* matcher indices */ &kMatcherIndices[181],
   },
   {
     /* [241] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [242] */
@@ -3753,12 +3790,12 @@
   {
     /* [245] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[193],
+    /* matcher indices */ &kMatcherIndices[195],
   },
   {
     /* [246] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[192],
+    /* matcher indices */ &kMatcherIndices[194],
   },
   {
     /* [247] */
@@ -3778,12 +3815,12 @@
   {
     /* [250] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[185],
+    /* matcher indices */ &kMatcherIndices[187],
   },
   {
     /* [251] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [252] */
@@ -3813,7 +3850,7 @@
   {
     /* [257] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [258] */
@@ -3828,12 +3865,12 @@
   {
     /* [260] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[195],
+    /* matcher indices */ &kMatcherIndices[197],
   },
   {
     /* [261] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[192],
+    /* matcher indices */ &kMatcherIndices[194],
   },
   {
     /* [262] */
@@ -3853,12 +3890,12 @@
   {
     /* [265] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[196],
+    /* matcher indices */ &kMatcherIndices[198],
   },
   {
     /* [266] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[192],
+    /* matcher indices */ &kMatcherIndices[194],
   },
   {
     /* [267] */
@@ -3878,12 +3915,12 @@
   {
     /* [270] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[189],
+    /* matcher indices */ &kMatcherIndices[191],
   },
   {
     /* [271] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [272] */
@@ -3903,12 +3940,12 @@
   {
     /* [275] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[193],
+    /* matcher indices */ &kMatcherIndices[195],
   },
   {
     /* [276] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[192],
+    /* matcher indices */ &kMatcherIndices[194],
   },
   {
     /* [277] */
@@ -3928,12 +3965,12 @@
   {
     /* [280] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[196],
+    /* matcher indices */ &kMatcherIndices[198],
   },
   {
     /* [281] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[192],
+    /* matcher indices */ &kMatcherIndices[194],
   },
   {
     /* [282] */
@@ -3953,12 +3990,12 @@
   {
     /* [285] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[195],
+    /* matcher indices */ &kMatcherIndices[197],
   },
   {
     /* [286] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[192],
+    /* matcher indices */ &kMatcherIndices[194],
   },
   {
     /* [287] */
@@ -3978,12 +4015,12 @@
   {
     /* [290] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[181],
+    /* matcher indices */ &kMatcherIndices[183],
   },
   {
     /* [291] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [292] */
@@ -4003,12 +4040,12 @@
   {
     /* [295] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[194],
+    /* matcher indices */ &kMatcherIndices[196],
   },
   {
     /* [296] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[192],
+    /* matcher indices */ &kMatcherIndices[194],
   },
   {
     /* [297] */
@@ -4023,12 +4060,12 @@
   {
     /* [299] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[196],
+    /* matcher indices */ &kMatcherIndices[198],
   },
   {
     /* [300] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[192],
+    /* matcher indices */ &kMatcherIndices[194],
   },
   {
     /* [301] */
@@ -4043,12 +4080,12 @@
   {
     /* [303] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[196],
+    /* matcher indices */ &kMatcherIndices[198],
   },
   {
     /* [304] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[192],
+    /* matcher indices */ &kMatcherIndices[194],
   },
   {
     /* [305] */
@@ -4063,12 +4100,12 @@
   {
     /* [307] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[187],
+    /* matcher indices */ &kMatcherIndices[189],
   },
   {
     /* [308] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [309] */
@@ -4083,12 +4120,12 @@
   {
     /* [311] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[194],
+    /* matcher indices */ &kMatcherIndices[196],
   },
   {
     /* [312] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[192],
+    /* matcher indices */ &kMatcherIndices[194],
   },
   {
     /* [313] */
@@ -4103,12 +4140,12 @@
   {
     /* [315] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[185],
+    /* matcher indices */ &kMatcherIndices[187],
   },
   {
     /* [316] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [317] */
@@ -4123,12 +4160,12 @@
   {
     /* [319] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[179],
+    /* matcher indices */ &kMatcherIndices[181],
   },
   {
     /* [320] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [321] */
@@ -4163,12 +4200,12 @@
   {
     /* [327] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[185],
+    /* matcher indices */ &kMatcherIndices[187],
   },
   {
     /* [328] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [329] */
@@ -4183,12 +4220,12 @@
   {
     /* [331] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[179],
+    /* matcher indices */ &kMatcherIndices[181],
   },
   {
     /* [332] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [333] */
@@ -4203,12 +4240,12 @@
   {
     /* [335] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[193],
+    /* matcher indices */ &kMatcherIndices[195],
   },
   {
     /* [336] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [337] */
@@ -4223,12 +4260,12 @@
   {
     /* [339] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[187],
+    /* matcher indices */ &kMatcherIndices[189],
   },
   {
     /* [340] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [341] */
@@ -4243,12 +4280,12 @@
   {
     /* [343] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[195],
+    /* matcher indices */ &kMatcherIndices[197],
   },
   {
     /* [344] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [345] */
@@ -4263,12 +4300,12 @@
   {
     /* [347] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[196],
+    /* matcher indices */ &kMatcherIndices[198],
   },
   {
     /* [348] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [349] */
@@ -4283,12 +4320,12 @@
   {
     /* [351] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[189],
+    /* matcher indices */ &kMatcherIndices[191],
   },
   {
     /* [352] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [353] */
@@ -4303,12 +4340,12 @@
   {
     /* [355] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[185],
+    /* matcher indices */ &kMatcherIndices[187],
   },
   {
     /* [356] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [357] */
@@ -4333,7 +4370,7 @@
   {
     /* [361] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [362] */
@@ -4343,12 +4380,12 @@
   {
     /* [363] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[181],
+    /* matcher indices */ &kMatcherIndices[183],
   },
   {
     /* [364] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [365] */
@@ -4363,12 +4400,12 @@
   {
     /* [367] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[179],
+    /* matcher indices */ &kMatcherIndices[181],
   },
   {
     /* [368] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [369] */
@@ -4383,12 +4420,12 @@
   {
     /* [371] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[196],
+    /* matcher indices */ &kMatcherIndices[198],
   },
   {
     /* [372] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [373] */
@@ -4403,12 +4440,12 @@
   {
     /* [375] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[194],
+    /* matcher indices */ &kMatcherIndices[196],
   },
   {
     /* [376] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[192],
+    /* matcher indices */ &kMatcherIndices[194],
   },
   {
     /* [377] */
@@ -4423,12 +4460,12 @@
   {
     /* [379] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[194],
+    /* matcher indices */ &kMatcherIndices[196],
   },
   {
     /* [380] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [381] */
@@ -4483,12 +4520,12 @@
   {
     /* [391] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[196],
+    /* matcher indices */ &kMatcherIndices[198],
   },
   {
     /* [392] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[192],
+    /* matcher indices */ &kMatcherIndices[194],
   },
   {
     /* [393] */
@@ -4503,12 +4540,12 @@
   {
     /* [395] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[193],
+    /* matcher indices */ &kMatcherIndices[195],
   },
   {
     /* [396] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [397] */
@@ -4563,7 +4600,7 @@
   {
     /* [407] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[195],
+    /* matcher indices */ &kMatcherIndices[197],
   },
   {
     /* [408] */
@@ -4583,12 +4620,12 @@
   {
     /* [411] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[195],
+    /* matcher indices */ &kMatcherIndices[197],
   },
   {
     /* [412] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [413] */
@@ -4603,12 +4640,12 @@
   {
     /* [415] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[196],
+    /* matcher indices */ &kMatcherIndices[198],
   },
   {
     /* [416] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [417] */
@@ -4653,7 +4690,7 @@
   {
     /* [425] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [426] */
@@ -4763,12 +4800,12 @@
   {
     /* [447] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[196],
+    /* matcher indices */ &kMatcherIndices[198],
   },
   {
     /* [448] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [449] */
@@ -4808,12 +4845,12 @@
   {
     /* [456] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[197],
+    /* matcher indices */ &kMatcherIndices[199],
   },
   {
     /* [457] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [458] */
@@ -5198,12 +5235,12 @@
   {
     /* [534] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[194],
+    /* matcher indices */ &kMatcherIndices[196],
   },
   {
     /* [535] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [536] */
@@ -5258,12 +5295,12 @@
   {
     /* [546] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[196],
+    /* matcher indices */ &kMatcherIndices[198],
   },
   {
     /* [547] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [548] */
@@ -5348,12 +5385,12 @@
   {
     /* [564] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[187],
+    /* matcher indices */ &kMatcherIndices[189],
   },
   {
     /* [565] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [566] */
@@ -5378,12 +5415,12 @@
   {
     /* [570] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[185],
+    /* matcher indices */ &kMatcherIndices[187],
   },
   {
     /* [571] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [572] */
@@ -5423,7 +5460,7 @@
   {
     /* [579] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[196],
+    /* matcher indices */ &kMatcherIndices[198],
   },
   {
     /* [580] */
@@ -5438,12 +5475,12 @@
   {
     /* [582] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[179],
+    /* matcher indices */ &kMatcherIndices[181],
   },
   {
     /* [583] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [584] */
@@ -5453,12 +5490,12 @@
   {
     /* [585] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[177],
+    /* matcher indices */ &kMatcherIndices[179],
   },
   {
     /* [586] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [587] */
@@ -5468,7 +5505,7 @@
   {
     /* [588] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[198],
+    /* matcher indices */ &kMatcherIndices[200],
   },
   {
     /* [589] */
@@ -5498,12 +5535,12 @@
   {
     /* [594] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[194],
+    /* matcher indices */ &kMatcherIndices[196],
   },
   {
     /* [595] */
     /* usage */ ParameterUsage::kSampler,
-    /* matcher indices */ &kMatcherIndices[191],
+    /* matcher indices */ &kMatcherIndices[193],
   },
   {
     /* [596] */
@@ -5713,7 +5750,7 @@
   {
     /* [637] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[196],
+    /* matcher indices */ &kMatcherIndices[198],
   },
   {
     /* [638] */
@@ -5733,7 +5770,7 @@
   {
     /* [641] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[195],
+    /* matcher indices */ &kMatcherIndices[197],
   },
   {
     /* [642] */
@@ -5753,7 +5790,7 @@
   {
     /* [645] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[194],
+    /* matcher indices */ &kMatcherIndices[196],
   },
   {
     /* [646] */
@@ -5773,7 +5810,7 @@
   {
     /* [649] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[193],
+    /* matcher indices */ &kMatcherIndices[195],
   },
   {
     /* [650] */
@@ -6163,7 +6200,7 @@
   {
     /* [727] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[197],
+    /* matcher indices */ &kMatcherIndices[199],
   },
   {
     /* [728] */
@@ -6748,7 +6785,7 @@
   {
     /* [844] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[198],
+    /* matcher indices */ &kMatcherIndices[200],
   },
   {
     /* [845] */
@@ -6758,7 +6795,7 @@
   {
     /* [846] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[193],
+    /* matcher indices */ &kMatcherIndices[195],
   },
   {
     /* [847] */
@@ -6768,17 +6805,17 @@
   {
     /* [848] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[194],
+    /* matcher indices */ &kMatcherIndices[196],
   },
   {
     /* [849] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[195],
+    /* matcher indices */ &kMatcherIndices[197],
   },
   {
     /* [850] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[196],
+    /* matcher indices */ &kMatcherIndices[198],
   },
   {
     /* [851] */
@@ -6848,12 +6885,12 @@
   {
     /* [864] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[193],
+    /* matcher indices */ &kMatcherIndices[195],
   },
   {
     /* [865] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[195],
+    /* matcher indices */ &kMatcherIndices[197],
   },
   {
     /* [866] */
@@ -6943,7 +6980,7 @@
   {
     /* [883] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[197],
+    /* matcher indices */ &kMatcherIndices[199],
   },
   {
     /* [884] */
@@ -6968,27 +7005,27 @@
   {
     /* [888] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[198],
+    /* matcher indices */ &kMatcherIndices[200],
   },
   {
     /* [889] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[193],
+    /* matcher indices */ &kMatcherIndices[195],
   },
   {
     /* [890] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[194],
+    /* matcher indices */ &kMatcherIndices[196],
   },
   {
     /* [891] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[195],
+    /* matcher indices */ &kMatcherIndices[197],
   },
   {
     /* [892] */
     /* usage */ ParameterUsage::kTexture,
-    /* matcher indices */ &kMatcherIndices[196],
+    /* matcher indices */ &kMatcherIndices[198],
   },
   {
     /* [893] */
@@ -7123,7 +7160,7 @@
   {
     /* [919] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[183],
+    /* matcher indices */ &kMatcherIndices[185],
   },
   {
     /* [920] */
@@ -7183,7 +7220,7 @@
   {
     /* [931] */
     /* usage */ ParameterUsage::kNone,
-    /* matcher indices */ &kMatcherIndices[173],
+    /* matcher indices */ &kMatcherIndices[175],
   },
   {
     /* [932] */
@@ -7486,7 +7523,7 @@
   {
     /* [1] */
     /* name */ "U",
-    /* matcher index */ 57,
+    /* matcher index */ 58,
   },
   {
     /* [2] */
@@ -7496,7 +7533,7 @@
   {
     /* [3] */
     /* name */ "U",
-    /* matcher index */ 54,
+    /* matcher index */ 55,
   },
   {
     /* [4] */
@@ -7506,7 +7543,7 @@
   {
     /* [5] */
     /* name */ "U",
-    /* matcher index */ 55,
+    /* matcher index */ 56,
   },
   {
     /* [6] */
@@ -7516,12 +7553,12 @@
   {
     /* [7] */
     /* name */ "U",
-    /* matcher index */ 56,
+    /* matcher index */ 57,
   },
   {
     /* [8] */
     /* name */ "T",
-    /* matcher index */ 48,
+    /* matcher index */ 49,
   },
   {
     /* [9] */
@@ -7531,22 +7568,22 @@
   {
     /* [10] */
     /* name */ "T",
-    /* matcher index */ 53,
+    /* matcher index */ 54,
   },
   {
     /* [11] */
     /* name */ "T",
-    /* matcher index */ 50,
+    /* matcher index */ 51,
   },
   {
     /* [12] */
     /* name */ "T",
-    /* matcher index */ 52,
+    /* matcher index */ 53,
   },
   {
     /* [13] */
     /* name */ "T",
-    /* matcher index */ 51,
+    /* matcher index */ 52,
   },
   {
     /* [14] */
@@ -7556,27 +7593,27 @@
   {
     /* [15] */
     /* name */ "T",
-    /* matcher index */ 57,
+    /* matcher index */ 58,
   },
   {
     /* [16] */
     /* name */ "T",
-    /* matcher index */ 54,
+    /* matcher index */ 55,
   },
   {
     /* [17] */
     /* name */ "T",
-    /* matcher index */ 56,
+    /* matcher index */ 57,
   },
   {
     /* [18] */
     /* name */ "T",
-    /* matcher index */ 55,
+    /* matcher index */ 56,
   },
   {
     /* [19] */
     /* name */ "T",
-    /* matcher index */ 49,
+    /* matcher index */ 50,
   },
 };
 
@@ -9952,7 +9989,7 @@
     /* template types */ &kTemplateTypes[20],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[990],
-    /* return matcher indices */ &kMatcherIndices[173],
+    /* return matcher indices */ &kMatcherIndices[175],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
   },
   {
@@ -9963,7 +10000,7 @@
     /* template types */ &kTemplateTypes[9],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[931],
-    /* return matcher indices */ &kMatcherIndices[173],
+    /* return matcher indices */ &kMatcherIndices[175],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
   },
   {
@@ -9974,7 +10011,7 @@
     /* template types */ &kTemplateTypes[10],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[928],
-    /* return matcher indices */ &kMatcherIndices[175],
+    /* return matcher indices */ &kMatcherIndices[177],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
   },
   {
@@ -9985,7 +10022,7 @@
     /* template types */ &kTemplateTypes[10],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[28],
-    /* return matcher indices */ &kMatcherIndices[175],
+    /* return matcher indices */ &kMatcherIndices[177],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
   },
   {
@@ -9996,7 +10033,7 @@
     /* template types */ &kTemplateTypes[10],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[435],
-    /* return matcher indices */ &kMatcherIndices[175],
+    /* return matcher indices */ &kMatcherIndices[177],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
   },
   {
@@ -10117,7 +10154,7 @@
     /* template types */ &kTemplateTypes[20],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[990],
-    /* return matcher indices */ &kMatcherIndices[183],
+    /* return matcher indices */ &kMatcherIndices[185],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
   },
   {
@@ -10128,7 +10165,7 @@
     /* template types */ &kTemplateTypes[9],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[919],
-    /* return matcher indices */ &kMatcherIndices[183],
+    /* return matcher indices */ &kMatcherIndices[185],
     /* flags */ OverloadFlags(OverloadFlag::kIsConstructor, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
   },
   {
@@ -10843,7 +10880,7 @@
     /* template types */ &kTemplateTypes[20],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[843],
-    /* return matcher indices */ &kMatcherIndices[200],
+    /* return matcher indices */ &kMatcherIndices[202],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
   },
   {
@@ -10865,7 +10902,7 @@
     /* template types */ &kTemplateTypes[20],
     /* template numbers */ &kTemplateNumbers[10],
     /* parameters */ &kParameters[973],
-    /* return matcher indices */ &kMatcherIndices[199],
+    /* return matcher indices */ &kMatcherIndices[201],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
   },
   {
@@ -12526,7 +12563,7 @@
     /* template types */ &kTemplateTypes[11],
     /* template numbers */ &kTemplateNumbers[9],
     /* parameters */ &kParameters[591],
-    /* return matcher indices */ &kMatcherIndices[105],
+    /* return matcher indices */ &kMatcherIndices[173],
     /* flags */ OverloadFlags(OverloadFlag::kIsBuiltin, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
   },
 };
@@ -13358,7 +13395,7 @@
   },
   {
     /* [106] */
-    /* fn atomicCompareExchangeWeak<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T, T) -> vec2<T> */
+    /* fn atomicCompareExchangeWeak<T : iu32, S : workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T, T) -> __atomic_compare_exchange_result<T> */
     /* num overloads */ 1,
     /* overloads */ &kOverloads[444],
   },
diff --git a/src/tint/resolver/intrinsic_table_test.cc b/src/tint/resolver/intrinsic_table_test.cc
index 4a5c171..9c5de1e 100644
--- a/src/tint/resolver/intrinsic_table_test.cc
+++ b/src/tint/resolver/intrinsic_table_test.cc
@@ -793,6 +793,20 @@
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
 }
 
+TEST_F(IntrinsicTableTest, OverloadResolution) {
+    // i32(abstract-int) produces candidates for both:
+    //    ctor i32(i32) -> i32
+    //    conv i32<T: scalar_no_i32>(T) -> i32
+    // The first should win overload resolution.
+    auto* ai = create<sem::AbstractInt>();
+    auto* i32 = create<sem::I32>();
+    auto result = table->Lookup(CtorConvIntrinsic::kI32, nullptr, {ai}, Source{});
+    ASSERT_NE(result, nullptr);
+    EXPECT_EQ(result->ReturnType(), i32);
+    EXPECT_EQ(result->Parameters().size(), 1u);
+    EXPECT_EQ(result->Parameters()[0]->Type(), i32);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // AbstractBinaryTests
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/src/tint/resolver/materialize_test.cc b/src/tint/resolver/materialize_test.cc
index 8f4451e..d62ece9 100644
--- a/src/tint/resolver/materialize_test.cc
+++ b/src/tint/resolver/materialize_test.cc
@@ -81,6 +81,13 @@
     // let a : target_type = abstract_expr;
     kLet,
 
+    // var a : target_type;
+    // a = abstract_expr;
+    kAssign,
+
+    // _ = abstract_expr;
+    kPhonyAssign,
+
     // fn F(v : target_type) {}
     // fn x() {
     //   F(abstract_expr);
@@ -147,6 +154,10 @@
             return o << "var";
         case Method::kLet:
             return o << "let";
+        case Method::kAssign:
+            return o << "assign";
+        case Method::kPhonyAssign:
+            return o << "phony-assign";
         case Method::kFnArg:
             return o << "fn-arg";
         case Method::kBuiltinArg:
@@ -251,6 +262,12 @@
         case Method::kLet:
             WrapInFunction(Decl(Let("a", target_ty(), abstract_expr)));
             break;
+        case Method::kAssign:
+            WrapInFunction(Decl(Var("a", target_ty(), nullptr)), Assign("a", abstract_expr));
+            break;
+        case Method::kPhonyAssign:
+            WrapInFunction(Assign(Phony(), abstract_expr));
+            break;
         case Method::kFnArg:
             Func("F", {Param("P", target_ty())}, ty.void_(), {});
             WrapInFunction(CallStmt(Call("F", abstract_expr)));
@@ -364,20 +381,20 @@
 
 /// Methods that support scalar materialization
 constexpr Method kScalarMethods[] = {
-    Method::kLet,    Method::kVar,   Method::kFnArg,  Method::kBuiltinArg,
+    Method::kLet,    Method::kVar,   Method::kAssign, Method::kFnArg,    Method::kBuiltinArg,
     Method::kReturn, Method::kArray, Method::kStruct, Method::kBinaryOp,
 };
 
 /// Methods that support vector materialization
 constexpr Method kVectorMethods[] = {
-    Method::kLet,    Method::kVar,   Method::kFnArg,  Method::kBuiltinArg,
+    Method::kLet,    Method::kVar,   Method::kAssign, Method::kFnArg,    Method::kBuiltinArg,
     Method::kReturn, Method::kArray, Method::kStruct, Method::kBinaryOp,
 };
 
 /// Methods that support matrix materialization
 constexpr Method kMatrixMethods[] = {
-    Method::kLet,   Method::kVar,    Method::kFnArg,    Method::kReturn,
-    Method::kArray, Method::kStruct, Method::kBinaryOp,
+    Method::kLet,    Method::kVar,   Method::kAssign, Method::kFnArg,
+    Method::kReturn, Method::kArray, Method::kStruct, Method::kBinaryOp,
 };
 
 /// Methods that support materialization for switch cases
@@ -388,6 +405,12 @@
     Method::kSwitchCaseWithAbstractCase,
 };
 
+/// Methods that do not materialize
+constexpr Method kNoMaterializeMethods[] = {
+    Method::kPhonyAssign,
+    // TODO(crbug.com/tint/1504): Enable once we have abstract overloads of builtins / binary ops:
+    // Method::kBuiltinArg, Method::kBinaryOp,
+};
 INSTANTIATE_TEST_SUITE_P(
     MaterializeScalar,
     MaterializeAbstractNumericToConcreteType,
@@ -486,18 +509,18 @@
                                               Types<u32, AInt>(65535_a, 65535.0),  //
                                           })));
 
-// TODO(crbug.com/tint/1504): Enable once we have abstract overloads of builtins / binary ops.
-INSTANTIATE_TEST_SUITE_P(DISABLED_NoMaterialize,
+INSTANTIATE_TEST_SUITE_P(NoMaterialize,
                          MaterializeAbstractNumericToConcreteType,
                          testing::Combine(testing::Values(Expectation::kNoMaterialize),
-                                          testing::Values(Method::kBuiltinArg, Method::kBinaryOp),
+                                          testing::ValuesIn(kNoMaterializeMethods),
                                           testing::ValuesIn(std::vector<Data>{
-                                              Types<AInt, AInt>(),        //
-                                              Types<AFloat, AFloat>(),    //
-                                              Types<AIntV, AIntV>(),      //
-                                              Types<AFloatV, AFloatV>(),  //
-                                              Types<AFloatM, AFloatM>(),  //
+                                              Types<AInt, AInt>(1_a, 1_a),            //
+                                              Types<AIntV, AIntV>(1_a, 1_a),          //
+                                              Types<AFloat, AFloat>(1.0_a, 1.0_a),    //
+                                              Types<AFloatV, AFloatV>(1.0_a, 1.0_a),  //
+                                              Types<AFloatM, AFloatM>(1.0_a, 1.0_a),  //
                                           })));
+
 INSTANTIATE_TEST_SUITE_P(InvalidConversion,
                          MaterializeAbstractNumericToConcreteType,
                          testing::Combine(testing::Values(Expectation::kInvalidConversion),
@@ -566,16 +589,16 @@
     // let a = abstract_expr;
     kLet,
 
-    // min(abstract_expr, abstract_expr);
+    // min(abstract_expr, abstract_expr)
     kBuiltinArg,
 
-    // bitcast<f32>(abstract_expr);
+    // bitcast<f32>(abstract_expr)
     kBitcastF32Arg,
 
-    // bitcast<vec3<f32>>(abstract_expr);
+    // bitcast<vec3<f32>>(abstract_expr)
     kBitcastVec3F32Arg,
 
-    // array<i32, abstract_expr>();
+    // array<i32, abstract_expr>()
     kArrayLength,
 
     // switch (abstract_expr) {
@@ -587,7 +610,10 @@
     // @workgroup_size(abstract_expr)
     // @stage(compute)
     // fn f() {}
-    kWorkgroupSize
+    kWorkgroupSize,
+
+    // arr[abstract_expr]
+    kIndex,
 };
 
 static std::ostream& operator<<(std::ostream& o, Method m) {
@@ -608,6 +634,8 @@
             return o << "switch";
         case Method::kWorkgroupSize:
             return o << "workgroup-size";
+        case Method::kIndex:
+            return o << "index";
     }
     return o << "<unknown>";
 }
@@ -692,6 +720,10 @@
             Func("f", {}, ty.void_(), {},
                  {WorkgroupSize(abstract_expr()), Stage(ast::PipelineStage::kCompute)});
             break;
+        case Method::kIndex:
+            Global("arr", ty.array<i32, 4>(), ast::StorageClass::kPrivate);
+            WrapInFunction(IndexAccessor("arr", abstract_expr()));
+            break;
     }
 
     auto check_types_and_values = [&](const sem::Expression* expr) {
@@ -756,6 +788,14 @@
     Method::kBitcastF32Arg,
 };
 
+/// Methods that support abstract-integer materialization
+/// Note: Doesn't contain kWorkgroupSize or kArrayLength as they have tighter constraints on the
+///       range of allowed integer values.
+constexpr Method kAIntMethods[] = {
+    Method::kSwitch,
+    Method::kIndex,
+};
+
 /// Methods that support vector materialization
 constexpr Method kVectorMethods[] = {
     Method::kLet,
@@ -826,16 +866,29 @@
                          Types<f32M, AFloatM>(AFloat(-kSubnormalF32), -kSubnormalF32),  //
                      })));
 
-INSTANTIATE_TEST_SUITE_P(MaterializeSwitch,
+INSTANTIATE_TEST_SUITE_P(MaterializeAInt,
                          MaterializeAbstractNumericToDefaultType,
                          testing::Combine(testing::Values(Expectation::kMaterialize),
-                                          testing::Values(Method::kSwitch),
+                                          testing::ValuesIn(kAIntMethods),
                                           testing::ValuesIn(std::vector<Data>{
                                               Types<i32, AInt>(0_a, 0.0),                        //
+                                              Types<i32, AInt>(10_a, 10.0),                      //
                                               Types<i32, AInt>(AInt(kHighestI32), kHighestI32),  //
                                               Types<i32, AInt>(AInt(kLowestI32), kLowestI32),    //
                                           })));
 
+INSTANTIATE_TEST_SUITE_P(
+    MaterializeArrayLength,
+    MaterializeAbstractNumericToDefaultType,
+    testing::Combine(testing::Values(Expectation::kMaterialize),
+                     testing::Values(Method::kArrayLength),
+                     testing::ValuesIn(std::vector<Data>{
+                         Types<i32, AInt>(1_a, 1.0),        //
+                         Types<i32, AInt>(10_a, 10.0),      //
+                         Types<i32, AInt>(1000_a, 1000.0),  //
+                         // Note: kHighestI32 cannot be used due to max-byte-size validation
+                     })));
+
 INSTANTIATE_TEST_SUITE_P(MaterializeWorkgroupSize,
                          MaterializeAbstractNumericToDefaultType,
                          testing::Combine(testing::Values(Expectation::kMaterialize),
@@ -878,10 +931,10 @@
                                               Types<f32M, AFloatM>(0.0_a, -kTooBigF32),  //
                                           })));
 
-INSTANTIATE_TEST_SUITE_P(SwitchValueCannotBeRepresented,
+INSTANTIATE_TEST_SUITE_P(AIntValueCannotBeRepresented,
                          MaterializeAbstractNumericToDefaultType,
                          testing::Combine(testing::Values(Expectation::kValueCannotBeRepresented),
-                                          testing::Values(Method::kSwitch),
+                                          testing::ValuesIn(kAIntMethods),
                                           testing::ValuesIn(std::vector<Data>{
                                               Types<i32, AInt>(0_a, kHighestI32 + 1),  //
                                               Types<i32, AInt>(0_a, kLowestI32 - 1),   //
@@ -896,6 +949,14 @@
                                               Types<i32, AInt>(0_a, kLowestI32 - 1),   //
                                           })));
 
+INSTANTIATE_TEST_SUITE_P(ArrayLengthValueCannotBeRepresented,
+                         MaterializeAbstractNumericToDefaultType,
+                         testing::Combine(testing::Values(Expectation::kValueCannotBeRepresented),
+                                          testing::Values(Method::kArrayLength),
+                                          testing::ValuesIn(std::vector<Data>{
+                                              Types<i32, AInt>(0_a, kHighestI32 + 1),  //
+                                          })));
+
 }  // namespace materialize_abstract_numeric_to_default_type
 
 }  // namespace
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index c964d8a..aa09cfa 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -1205,7 +1205,10 @@
 }
 
 sem::Expression* Resolver::IndexAccessor(const ast::IndexAccessorExpression* expr) {
-    auto* idx = sem_.Get(expr->index);
+    auto* idx = Materialize(sem_.Get(expr->index));
+    if (!idx) {
+        return nullptr;
+    }
     auto* obj = sem_.Get(expr->object);
     auto* obj_raw_ty = obj->Type();
     auto* obj_ty = obj_raw_ty->UnwrapRef();
@@ -2027,7 +2030,7 @@
     // sem::Array uses a size of 0 for a runtime-sized array.
     uint32_t count = 0;
     if (auto* count_expr = arr->count) {
-        auto* count_sem = Expression(count_expr);
+        const auto* count_sem = Materialize(Expression(count_expr));
         if (!count_sem) {
             return nullptr;
         }
@@ -2406,14 +2409,23 @@
             return false;
         }
 
-        auto* rhs = Expression(stmt->rhs);
+        const bool is_phony_assignment = stmt->lhs->Is<ast::PhonyExpression>();
+
+        const auto* rhs = Expression(stmt->rhs);
         if (!rhs) {
             return false;
         }
 
+        if (!is_phony_assignment) {
+            rhs = Materialize(rhs, lhs->Type()->UnwrapRef());
+            if (!rhs) {
+                return false;
+            }
+        }
+
         auto& behaviors = sem->Behaviors();
         behaviors = rhs->Behaviors();
-        if (!stmt->lhs->Is<ast::PhonyExpression>()) {
+        if (!is_phony_assignment) {
             behaviors.Add(lhs->Behaviors());
         }
 
diff --git a/src/tint/resolver/type_validation_test.cc b/src/tint/resolver/type_validation_test.cc
index 26b705f..ff1b57b 100644
--- a/src/tint/resolver/type_validation_test.cc
+++ b/src/tint/resolver/type_validation_test.cc
@@ -73,7 +73,7 @@
     ASSERT_NE(TypeOf(rhs), nullptr);
 }
 
-TEST_F(ResolverTypeValidationTest, GlobalConstantNoConstructor_Pass) {
+TEST_F(ResolverTypeValidationTest, GlobalOverrideNoConstructor_Pass) {
     // @id(0) override a :i32;
     Override(Source{{12, 34}}, "a", ty.i32(), nullptr, ast::AttributeList{Id(0)});
 
@@ -87,8 +87,8 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverTypeValidationTest, GlobalConstantWithStorageClass_Fail) {
-    // const<private> global_var: f32;
+TEST_F(ResolverTypeValidationTest, GlobalLetWithStorageClass_Fail) {
+    // let<private> global_var: f32;
     AST().AddGlobalVariable(create<ast::Variable>(
         Source{{12, 34}}, Symbols().Register("global_var"), ast::StorageClass::kPrivate,
         ast::Access::kUndefined, ty.f32(), true, false, Expr(1.23_f), ast::AttributeList{}));
@@ -188,6 +188,12 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
+TEST_F(ResolverTypeValidationTest, ArraySize_AIntLiteral_Pass) {
+    // var<private> a : array<f32, 4>;
+    Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 4_a)), ast::StorageClass::kPrivate);
+    EXPECT_TRUE(r()->Resolve()) << r()->error();
+}
+
 TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedLiteral_Pass) {
     // var<private> a : array<f32, 4u>;
     Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 4_u)), ast::StorageClass::kPrivate);
@@ -200,7 +206,7 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedConstant_Pass) {
+TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedLet_Pass) {
     // let size = 4u;
     // var<private> a : array<f32, size>;
     GlobalConst("size", nullptr, Expr(4_u));
@@ -208,7 +214,7 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverTypeValidationTest, ArraySize_SignedConstant_Pass) {
+TEST_F(ResolverTypeValidationTest, ArraySize_SignedLet_Pass) {
     // let size = 4i;
     // var<private> a : array<f32, size>;
     GlobalConst("size", nullptr, Expr(4_i));
@@ -216,6 +222,13 @@
     EXPECT_TRUE(r()->Resolve()) << r()->error();
 }
 
+TEST_F(ResolverTypeValidationTest, ArraySize_AIntLiteral_Zero) {
+    // var<private> a : array<f32, 0>;
+    Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 0_a)), ast::StorageClass::kPrivate);
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
+}
+
 TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedLiteral_Zero) {
     // var<private> a : array<f32, 0u>;
     Global("a", ty.array(ty.f32(), Expr(Source{{12, 34}}, 0_u)), ast::StorageClass::kPrivate);
@@ -237,7 +250,7 @@
     EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
 }
 
-TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedConstant_Zero) {
+TEST_F(ResolverTypeValidationTest, ArraySize_UnsignedLet_Zero) {
     // let size = 0u;
     // var<private> a : array<f32, size>;
     GlobalConst("size", nullptr, Expr(0_u));
@@ -246,7 +259,7 @@
     EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
 }
 
-TEST_F(ResolverTypeValidationTest, ArraySize_SignedConstant_Zero) {
+TEST_F(ResolverTypeValidationTest, ArraySize_SignedLet_Zero) {
     // let size = 0i;
     // var<private> a : array<f32, size>;
     GlobalConst("size", nullptr, Expr(0_i));
@@ -255,7 +268,7 @@
     EXPECT_EQ(r()->error(), "12:34 error: array size must be at least 1");
 }
 
-TEST_F(ResolverTypeValidationTest, ArraySize_SignedConstant_Negative) {
+TEST_F(ResolverTypeValidationTest, ArraySize_SignedLet_Negative) {
     // let size = -10i;
     // var<private> a : array<f32, size>;
     GlobalConst("size", nullptr, Expr(-10_i));
@@ -279,7 +292,7 @@
     EXPECT_EQ(r()->error(), "12:34 error: array size must be integer scalar");
 }
 
-TEST_F(ResolverTypeValidationTest, ArraySize_FloatConstant) {
+TEST_F(ResolverTypeValidationTest, ArraySize_FloatLet) {
     // let size = 10.0;
     // var<private> a : array<f32, size>;
     GlobalConst("size", nullptr, Expr(10_f));
@@ -288,7 +301,7 @@
     EXPECT_EQ(r()->error(), "12:34 error: array size must be integer scalar");
 }
 
-TEST_F(ResolverTypeValidationTest, ArraySize_IVecConstant) {
+TEST_F(ResolverTypeValidationTest, ArraySize_IVecLet) {
     // let size = vec2<i32>(100, 100);
     // var<private> a : array<f32, size>;
     GlobalConst("size", nullptr, Construct(ty.vec2<i32>(), 100_i, 100_i));
@@ -315,7 +328,7 @@
               "is 0x100000000");
 }
 
-TEST_F(ResolverTypeValidationTest, ArraySize_OverridableConstant) {
+TEST_F(ResolverTypeValidationTest, ArraySize_Overridable) {
     // override size = 10i;
     // var<private> a : array<f32, size>;
     Override("size", nullptr, Expr(10_i));
@@ -333,7 +346,7 @@
     EXPECT_EQ(r()->error(), "12:34 error: array size identifier must be a module-scope constant");
 }
 
-TEST_F(ResolverTypeValidationTest, ArraySize_FunctionConstant) {
+TEST_F(ResolverTypeValidationTest, ArraySize_FunctionLet) {
     // {
     //   let size = 10;
     //   var a : array<f32, size>;
diff --git a/src/tint/resolver/validator.cc b/src/tint/resolver/validator.cc
index fd59988..2b4d8a9 100644
--- a/src/tint/resolver/validator.cc
+++ b/src/tint/resolver/validator.cc
@@ -48,6 +48,7 @@
 #include "src/tint/ast/variable_decl_statement.h"
 #include "src/tint/ast/vector.h"
 #include "src/tint/ast/workgroup_attribute.h"
+#include "src/tint/sem/abstract_numeric.h"
 #include "src/tint/sem/array.h"
 #include "src/tint/sem/atomic.h"
 #include "src/tint/sem/call.h"
@@ -2141,7 +2142,8 @@
     if (lhs->Is<ast::PhonyExpression>()) {
         // https://www.w3.org/TR/WGSL/#phony-assignment-section
         auto* ty = rhs_ty->UnwrapRef();
-        if (!ty->IsConstructible() && !ty->IsAnyOf<sem::Pointer, sem::Texture, sem::Sampler>()) {
+        if (!ty->IsConstructible() &&
+            !ty->IsAnyOf<sem::Pointer, sem::Texture, sem::Sampler, sem::AbstractNumeric>()) {
             AddError("cannot assign '" + sem_.TypeNameOf(rhs_ty) +
                          "' to '_'. '_' can only be assigned a constructible, pointer, "
                          "texture or sampler type",
diff --git a/src/tint/sem/constant.cc b/src/tint/sem/constant.cc
index 1fa83f5..a2cfe8f 100644
--- a/src/tint/sem/constant.cc
+++ b/src/tint/sem/constant.cc
@@ -45,9 +45,9 @@
 
 bool Constant::AnyZero() const {
     return WithElements([&](auto&& vec) {
-        for (auto scalar : vec) {
-            using T = std::remove_reference_t<decltype(scalar)>;
-            if (scalar == T(0)) {
+        using T = typename std::decay_t<decltype(vec)>::value_type;
+        for (auto el : vec) {
+            if (el == T(0)) {
                 return true;
             }
         }
@@ -55,6 +55,32 @@
     });
 }
 
+bool Constant::AllZero() const {
+    return WithElements([&](auto&& vec) {
+        using T = typename std::decay_t<decltype(vec)>::value_type;
+        for (auto el : vec) {
+            if (el != T(0)) {
+                return false;
+            }
+        }
+        return true;
+    });
+}
+
+bool Constant::AllEqual(size_t start, size_t end) const {
+    return WithElements([&](auto&& vec) {
+        if (!vec.empty()) {
+            auto value = vec[start];
+            for (size_t i = start + 1; i < end; i++) {
+                if (vec[i] != value) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    });
+}
+
 const Type* Constant::CheckElemType(const sem::Type* ty, size_t num_elements) {
     diag::List diag;
     if (ty->is_abstract_or_scalar() || ty->IsAnyOf<Vector, Matrix>()) {
diff --git a/src/tint/sem/constant.h b/src/tint/sem/constant.h
index 43109f7..c99b979 100644
--- a/src/tint/sem/constant.h
+++ b/src/tint/sem/constant.h
@@ -132,6 +132,17 @@
     /// @returns true if any element is zero
     bool AnyZero() const;
 
+    /// @returns true if all elements are zero
+    bool AllZero() const;
+
+    /// @returns true if all elements are the same value
+    bool AllEqual() const { return AllEqual(0, ElementCount()); }
+
+    /// @param start the first element index
+    /// @param end one past the last element index
+    /// @returns true if all elements between `[start, end)` are the same value
+    bool AllEqual(size_t start, size_t end) const;
+
     /// @param index the index of the element
     /// @return the element at `index`, which must be of type `T`.
     template <typename T>
diff --git a/src/tint/sem/constant_test.cc b/src/tint/sem/constant_test.cc
index 345ebd8..6ad3cd6 100644
--- a/src/tint/sem/constant_test.cc
+++ b/src/tint/sem/constant_test.cc
@@ -195,5 +195,43 @@
     EXPECT_EQ(c.ElementCount(), 6u);
 }
 
+TEST_F(ConstantTest, AnyZero) {
+    auto* vec3_ai = create<Vector>(create<AbstractInt>(), 3u);
+    EXPECT_EQ(Constant(vec3_ai, {1_a, 2_a, 3_a}).AnyZero(), false);
+    EXPECT_EQ(Constant(vec3_ai, {0_a, 2_a, 3_a}).AnyZero(), true);
+    EXPECT_EQ(Constant(vec3_ai, {1_a, 0_a, 3_a}).AnyZero(), true);
+    EXPECT_EQ(Constant(vec3_ai, {1_a, 2_a, 0_a}).AnyZero(), true);
+    EXPECT_EQ(Constant(vec3_ai, {0_a, 0_a, 0_a}).AnyZero(), true);
+}
+
+TEST_F(ConstantTest, AllZero) {
+    auto* vec3_ai = create<Vector>(create<AbstractInt>(), 3u);
+    EXPECT_EQ(Constant(vec3_ai, {1_a, 2_a, 3_a}).AllZero(), false);
+    EXPECT_EQ(Constant(vec3_ai, {0_a, 2_a, 3_a}).AllZero(), false);
+    EXPECT_EQ(Constant(vec3_ai, {1_a, 0_a, 3_a}).AllZero(), false);
+    EXPECT_EQ(Constant(vec3_ai, {1_a, 2_a, 0_a}).AllZero(), false);
+    EXPECT_EQ(Constant(vec3_ai, {0_a, 0_a, 0_a}).AllZero(), true);
+}
+
+TEST_F(ConstantTest, AllEqual) {
+    auto* vec3_ai = create<Vector>(create<AbstractInt>(), 3u);
+    EXPECT_EQ(Constant(vec3_ai, {1_a, 2_a, 3_a}).AllEqual(), false);
+    EXPECT_EQ(Constant(vec3_ai, {1_a, 1_a, 3_a}).AllEqual(), false);
+    EXPECT_EQ(Constant(vec3_ai, {1_a, 3_a, 3_a}).AllEqual(), false);
+    EXPECT_EQ(Constant(vec3_ai, {1_a, 1_a, 1_a}).AllEqual(), true);
+    EXPECT_EQ(Constant(vec3_ai, {2_a, 2_a, 2_a}).AllEqual(), true);
+    EXPECT_EQ(Constant(vec3_ai, {3_a, 3_a, 3_a}).AllEqual(), true);
+}
+
+TEST_F(ConstantTest, AllEqualRange) {
+    auto* vec3_ai = create<Vector>(create<AbstractInt>(), 3u);
+    EXPECT_EQ(Constant(vec3_ai, {1_a, 2_a, 3_a}).AllEqual(1, 3), false);
+    EXPECT_EQ(Constant(vec3_ai, {1_a, 1_a, 3_a}).AllEqual(1, 3), false);
+    EXPECT_EQ(Constant(vec3_ai, {1_a, 3_a, 3_a}).AllEqual(1, 3), true);
+    EXPECT_EQ(Constant(vec3_ai, {1_a, 1_a, 1_a}).AllEqual(1, 3), true);
+    EXPECT_EQ(Constant(vec3_ai, {2_a, 2_a, 2_a}).AllEqual(1, 3), true);
+    EXPECT_EQ(Constant(vec3_ai, {2_a, 2_a, 3_a}).AllEqual(1, 3), false);
+}
+
 }  // namespace
 }  // namespace tint::sem
diff --git a/src/tint/transform/canonicalize_entry_point_io_test.cc b/src/tint/transform/canonicalize_entry_point_io_test.cc
index d3fe95a..68ccb8b 100644
--- a/src/tint/transform/canonicalize_entry_point_io_test.cc
+++ b/src/tint/transform/canonicalize_entry_point_io_test.cc
@@ -3173,7 +3173,7 @@
 fn vert_main() {
   let inner_result = vert_main_inner();
   value = inner_result;
-  vertex_point_size = 1.0;
+  vertex_point_size = 1.0f;
 }
 )";
 
@@ -3210,7 +3210,7 @@
   let inner_result = vert_main_inner();
   var wrapper_result : tint_symbol;
   wrapper_result.value = inner_result;
-  wrapper_result.vertex_point_size = 1.0;
+  wrapper_result.vertex_point_size = 1.0f;
   return wrapper_result;
 }
 )";
@@ -3252,7 +3252,7 @@
 fn vert_main() {
   let inner_result = vert_main_inner();
   pos_1 = inner_result.pos;
-  vertex_point_size = 1.0;
+  vertex_point_size = 1.0f;
 }
 )";
 
@@ -3289,7 +3289,7 @@
 fn vert_main() {
   let inner_result = vert_main_inner();
   pos_1 = inner_result.pos;
-  vertex_point_size = 1.0;
+  vertex_point_size = 1.0f;
 }
 
 struct VertOut {
@@ -3338,7 +3338,7 @@
   let inner_result = vert_main_inner();
   var wrapper_result : tint_symbol;
   wrapper_result.pos = inner_result.pos;
-  wrapper_result.vertex_point_size = 1.0;
+  wrapper_result.vertex_point_size = 1.0f;
   return wrapper_result;
 }
 )";
@@ -3380,7 +3380,7 @@
   let inner_result = vert_main_inner();
   var wrapper_result : tint_symbol;
   wrapper_result.pos = inner_result.pos;
-  wrapper_result.vertex_point_size = 1.0;
+  wrapper_result.vertex_point_size = 1.0f;
   return wrapper_result;
 }
 
@@ -3463,7 +3463,7 @@
   let inner_result = vert_main_inner(VertIn1(collide_2), VertIn2(collide_3));
   vertex_point_size_3 = inner_result.vertex_point_size;
   vertex_point_size_1_1 = inner_result.vertex_point_size_1;
-  vertex_point_size_4 = 1.0;
+  vertex_point_size_4 = 1.0f;
 }
 )";
 
@@ -3522,7 +3522,7 @@
   let inner_result = vert_main_inner(VertIn1(collide_2), VertIn2(collide_3));
   vertex_point_size_3 = inner_result.vertex_point_size;
   vertex_point_size_1_1 = inner_result.vertex_point_size_1;
-  vertex_point_size_4 = 1.0;
+  vertex_point_size_4 = 1.0f;
 }
 
 struct VertIn1 {
@@ -3616,7 +3616,7 @@
   var wrapper_result : tint_symbol_2;
   wrapper_result.vertex_point_size = inner_result.vertex_point_size;
   wrapper_result.vertex_point_size_1 = inner_result.vertex_point_size_1;
-  wrapper_result.vertex_point_size_2 = 1.0;
+  wrapper_result.vertex_point_size_2 = 1.0f;
   return wrapper_result;
 }
 )";
@@ -3679,7 +3679,7 @@
   var wrapper_result : tint_symbol_2;
   wrapper_result.vertex_point_size = inner_result.vertex_point_size;
   wrapper_result.vertex_point_size_1 = inner_result.vertex_point_size_1;
-  wrapper_result.vertex_point_size_2 = 1.0;
+  wrapper_result.vertex_point_size_2 = 1.0f;
   return wrapper_result;
 }
 
@@ -3768,7 +3768,7 @@
   var wrapper_result : tint_symbol_2;
   wrapper_result.vertex_point_size = inner_result.vertex_point_size;
   wrapper_result.vertex_point_size_1 = inner_result.vertex_point_size_1;
-  wrapper_result.vertex_point_size_2 = 1.0;
+  wrapper_result.vertex_point_size_2 = 1.0f;
   return wrapper_result;
 }
 )";
@@ -3831,7 +3831,7 @@
   var wrapper_result : tint_symbol_2;
   wrapper_result.vertex_point_size = inner_result.vertex_point_size;
   wrapper_result.vertex_point_size_1 = inner_result.vertex_point_size_1;
-  wrapper_result.vertex_point_size_2 = 1.0;
+  wrapper_result.vertex_point_size_2 = 1.0f;
   return wrapper_result;
 }
 
@@ -3953,7 +3953,7 @@
   let inner_result = vertex_main(bitcast<u32>(gl_VertexID), bitcast<u32>(gl_InstanceID));
   gl_Position = inner_result;
   gl_Position.y = -(gl_Position.y);
-  gl_Position.z = ((2.0 * gl_Position.z) - gl_Position.w);
+  gl_Position.z = ((2.0f * gl_Position.z) - gl_Position.w);
 }
 )";
 
diff --git a/src/tint/transform/combine_samplers_test.cc b/src/tint/transform/combine_samplers_test.cc
index 1d84859..cad3109 100644
--- a/src/tint/transform/combine_samplers_test.cc
+++ b/src/tint/transform/combine_samplers_test.cc
@@ -872,7 +872,7 @@
 @group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_comparison_sampler : sampler_comparison;
 
 fn f(t_s : texture_depth_2d, coords : vec2<f32>) -> f32 {
-  return textureSampleCompare(t_s, placeholder_comparison_sampler, coords, 5.0);
+  return textureSampleCompare(t_s, placeholder_comparison_sampler, coords, 5.0f);
 }
 
 @group(0) @binding(0) @internal(disable_validation__binding_point_collision) var tex_samp : texture_depth_2d;
@@ -912,7 +912,7 @@
 @group(0) @binding(0) @internal(disable_validation__binding_point_collision) var placeholder_comparison_sampler : sampler_comparison;
 
 fn f(t_s : texture_depth_2d, coords : vec2<f32>) -> f32 {
-  return textureSampleCompare(t_s, placeholder_comparison_sampler, coords, 5.0);
+  return textureSampleCompare(t_s, placeholder_comparison_sampler, coords, 5.0f);
 }
 )";
 
diff --git a/src/tint/transform/decompose_memory_access.cc b/src/tint/transform/decompose_memory_access.cc
index 775cc05..a90a6e2 100644
--- a/src/tint/transform/decompose_memory_access.cc
+++ b/src/tint/transform/decompose_memory_access.cc
@@ -644,14 +644,34 @@
                     << el_ty->TypeInfo().name;
             }
 
-            auto* ret_ty = CreateASTTypeFor(ctx, intrinsic->ReturnType());
-            auto* func =
-                b.create<ast::Function>(b.Sym(), params, ret_ty, nullptr,
-                                        ast::AttributeList{
-                                            atomic,
-                                            b.Disable(ast::DisabledValidation::kFunctionHasNoBody),
-                                        },
-                                        ast::AttributeList{});
+            const ast::Type* ret_ty = nullptr;
+
+            // For intrinsics that return a struct, there is no AST node for it, so create one now.
+            if (intrinsic->Type() == sem::BuiltinType::kAtomicCompareExchangeWeak) {
+                auto* str = intrinsic->ReturnType()->As<sem::Struct>();
+                TINT_ASSERT(Transform, str && str->Declaration() == nullptr);
+
+                ast::StructMemberList ast_members;
+                ast_members.reserve(str->Members().size());
+                for (auto& m : str->Members()) {
+                    ast_members.push_back(
+                        b.Member(ctx.Clone(m->Name()), CreateASTTypeFor(ctx, m->Type())));
+                }
+
+                auto name = b.Symbols().New("atomic_compare_exchange_weak_ret_type");
+                auto* new_str = b.Structure(name, std::move(ast_members));
+                ret_ty = b.ty.Of(new_str);
+            } else {
+                ret_ty = CreateASTTypeFor(ctx, intrinsic->ReturnType());
+            }
+
+            auto* func = b.create<ast::Function>(
+                b.Symbols().New(std::string{"tint_"} + intrinsic->str()), params, ret_ty, nullptr,
+                ast::AttributeList{
+                    atomic,
+                    b.Disable(ast::DisabledValidation::kFunctionHasNoBody),
+                },
+                ast::AttributeList{});
 
             b.AST().AddFunction(func);
             return func->symbol;
@@ -753,6 +773,10 @@
                                                                          storage_class, type);
 }
 
+bool DecomposeMemoryAccess::Intrinsic::IsAtomic() const {
+    return op != Op::kLoad && op != Op::kStore;
+}
+
 DecomposeMemoryAccess::DecomposeMemoryAccess() = default;
 DecomposeMemoryAccess::~DecomposeMemoryAccess() = default;
 
diff --git a/src/tint/transform/decompose_memory_access.h b/src/tint/transform/decompose_memory_access.h
index 7a7b783..76cb23e 100644
--- a/src/tint/transform/decompose_memory_access.h
+++ b/src/tint/transform/decompose_memory_access.h
@@ -89,6 +89,9 @@
         /// @return the newly cloned object
         const Intrinsic* Clone(CloneContext* ctx) const override;
 
+        /// @return true if op is atomic
+        bool IsAtomic() const;
+
         /// The op of the intrinsic
         const Op op;
 
diff --git a/src/tint/transform/decompose_memory_access_test.cc b/src/tint/transform/decompose_memory_access_test.cc
index 22b5da4..19f8b2e 100644
--- a/src/tint/transform/decompose_memory_access_test.cc
+++ b/src/tint/transform/decompose_memory_access_test.cc
@@ -2467,95 +2467,105 @@
 @group(0) @binding(0) var<storage, read_write> sb : SB;
 
 @internal(intrinsic_atomic_store_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32)
+fn tint_atomicStore(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32)
 
 @internal(intrinsic_atomic_load_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> i32
+fn tint_atomicLoad(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> i32
 
 @internal(intrinsic_atomic_add_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+fn tint_atomicAdd(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
 
 @internal(intrinsic_atomic_sub_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+fn tint_atomicSub(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
 
 @internal(intrinsic_atomic_max_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+fn tint_atomicMax(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
 
 @internal(intrinsic_atomic_min_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+fn tint_atomicMin(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
 
 @internal(intrinsic_atomic_and_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+fn tint_atomicAnd(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
 
 @internal(intrinsic_atomic_or_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+fn tint_atomicOr(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
 
 @internal(intrinsic_atomic_xor_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+fn tint_atomicXor(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
 
 @internal(intrinsic_atomic_exchange_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+fn tint_atomicExchange(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+
+struct atomic_compare_exchange_weak_ret_type {
+  old_value : i32,
+  exchanged : bool,
+}
 
 @internal(intrinsic_atomic_compare_exchange_weak_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32, param_2 : i32) -> vec2<i32>
+fn tint_atomicCompareExchangeWeak(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32, param_2 : i32) -> atomic_compare_exchange_weak_ret_type
 
 @internal(intrinsic_atomic_store_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32)
+fn tint_atomicStore_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32)
 
 @internal(intrinsic_atomic_load_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> u32
+fn tint_atomicLoad_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> u32
 
 @internal(intrinsic_atomic_add_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+fn tint_atomicAdd_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
 
 @internal(intrinsic_atomic_sub_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+fn tint_atomicSub_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
 
 @internal(intrinsic_atomic_max_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+fn tint_atomicMax_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
 
 @internal(intrinsic_atomic_min_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+fn tint_atomicMin_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
 
 @internal(intrinsic_atomic_and_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+fn tint_atomicAnd_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
 
 @internal(intrinsic_atomic_or_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+fn tint_atomicOr_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
 
 @internal(intrinsic_atomic_xor_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+fn tint_atomicXor_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
 
 @internal(intrinsic_atomic_exchange_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+fn tint_atomicExchange_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+
+struct atomic_compare_exchange_weak_ret_type_1 {
+  old_value : u32,
+  exchanged : bool,
+}
 
 @internal(intrinsic_atomic_compare_exchange_weak_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32, param_2 : u32) -> vec2<u32>
+fn tint_atomicCompareExchangeWeak_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32, param_2 : u32) -> atomic_compare_exchange_weak_ret_type_1
 
 @stage(compute) @workgroup_size(1)
 fn main() {
-  tint_symbol(sb, 16u, 123);
-  tint_symbol_1(sb, 16u);
-  tint_symbol_2(sb, 16u, 123);
-  tint_symbol_3(sb, 16u, 123);
-  tint_symbol_4(sb, 16u, 123);
-  tint_symbol_5(sb, 16u, 123);
-  tint_symbol_6(sb, 16u, 123);
-  tint_symbol_7(sb, 16u, 123);
-  tint_symbol_8(sb, 16u, 123);
-  tint_symbol_9(sb, 16u, 123);
-  tint_symbol_10(sb, 16u, 123, 345);
-  tint_symbol_11(sb, 20u, 123u);
-  tint_symbol_12(sb, 20u);
-  tint_symbol_13(sb, 20u, 123u);
-  tint_symbol_14(sb, 20u, 123u);
-  tint_symbol_15(sb, 20u, 123u);
-  tint_symbol_16(sb, 20u, 123u);
-  tint_symbol_17(sb, 20u, 123u);
-  tint_symbol_18(sb, 20u, 123u);
-  tint_symbol_19(sb, 20u, 123u);
-  tint_symbol_20(sb, 20u, 123u);
-  tint_symbol_21(sb, 20u, 123u, 345u);
+  tint_atomicStore(sb, 16u, 123);
+  tint_atomicLoad(sb, 16u);
+  tint_atomicAdd(sb, 16u, 123);
+  tint_atomicSub(sb, 16u, 123);
+  tint_atomicMax(sb, 16u, 123);
+  tint_atomicMin(sb, 16u, 123);
+  tint_atomicAnd(sb, 16u, 123);
+  tint_atomicOr(sb, 16u, 123);
+  tint_atomicXor(sb, 16u, 123);
+  tint_atomicExchange(sb, 16u, 123);
+  tint_atomicCompareExchangeWeak(sb, 16u, 123, 345);
+  tint_atomicStore_1(sb, 20u, 123u);
+  tint_atomicLoad_1(sb, 20u);
+  tint_atomicAdd_1(sb, 20u, 123u);
+  tint_atomicSub_1(sb, 20u, 123u);
+  tint_atomicMax_1(sb, 20u, 123u);
+  tint_atomicMin_1(sb, 20u, 123u);
+  tint_atomicAnd_1(sb, 20u, 123u);
+  tint_atomicOr_1(sb, 20u, 123u);
+  tint_atomicXor_1(sb, 20u, 123u);
+  tint_atomicExchange_1(sb, 20u, 123u);
+  tint_atomicCompareExchangeWeak_1(sb, 20u, 123u, 345u);
 }
 )";
 
@@ -2604,95 +2614,105 @@
 
     auto* expect = R"(
 @internal(intrinsic_atomic_store_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32)
+fn tint_atomicStore(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32)
 
 @internal(intrinsic_atomic_load_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> i32
+fn tint_atomicLoad(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> i32
 
 @internal(intrinsic_atomic_add_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_2(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+fn tint_atomicAdd(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
 
 @internal(intrinsic_atomic_sub_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_3(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+fn tint_atomicSub(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
 
 @internal(intrinsic_atomic_max_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_4(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+fn tint_atomicMax(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
 
 @internal(intrinsic_atomic_min_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_5(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+fn tint_atomicMin(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
 
 @internal(intrinsic_atomic_and_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_6(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+fn tint_atomicAnd(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
 
 @internal(intrinsic_atomic_or_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_7(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+fn tint_atomicOr(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
 
 @internal(intrinsic_atomic_xor_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_8(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+fn tint_atomicXor(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
 
 @internal(intrinsic_atomic_exchange_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_9(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+fn tint_atomicExchange(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32) -> i32
+
+struct atomic_compare_exchange_weak_ret_type {
+  old_value : i32,
+  exchanged : bool,
+}
 
 @internal(intrinsic_atomic_compare_exchange_weak_storage_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_10(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32, param_2 : i32) -> vec2<i32>
+fn tint_atomicCompareExchangeWeak(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : i32, param_2 : i32) -> atomic_compare_exchange_weak_ret_type
 
 @internal(intrinsic_atomic_store_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_11(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32)
+fn tint_atomicStore_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32)
 
 @internal(intrinsic_atomic_load_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_12(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> u32
+fn tint_atomicLoad_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32) -> u32
 
 @internal(intrinsic_atomic_add_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_13(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+fn tint_atomicAdd_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
 
 @internal(intrinsic_atomic_sub_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_14(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+fn tint_atomicSub_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
 
 @internal(intrinsic_atomic_max_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_15(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+fn tint_atomicMax_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
 
 @internal(intrinsic_atomic_min_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_16(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+fn tint_atomicMin_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
 
 @internal(intrinsic_atomic_and_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_17(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+fn tint_atomicAnd_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
 
 @internal(intrinsic_atomic_or_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_18(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+fn tint_atomicOr_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
 
 @internal(intrinsic_atomic_xor_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_19(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+fn tint_atomicXor_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
 
 @internal(intrinsic_atomic_exchange_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_20(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+fn tint_atomicExchange_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32) -> u32
+
+struct atomic_compare_exchange_weak_ret_type_1 {
+  old_value : u32,
+  exchanged : bool,
+}
 
 @internal(intrinsic_atomic_compare_exchange_weak_storage_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_21(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32, param_2 : u32) -> vec2<u32>
+fn tint_atomicCompareExchangeWeak_1(@internal(disable_validation__ignore_constructible_function_parameter) buffer : SB, offset : u32, param_1 : u32, param_2 : u32) -> atomic_compare_exchange_weak_ret_type_1
 
 @stage(compute) @workgroup_size(1)
 fn main() {
-  tint_symbol(sb, 16u, 123);
-  tint_symbol_1(sb, 16u);
-  tint_symbol_2(sb, 16u, 123);
-  tint_symbol_3(sb, 16u, 123);
-  tint_symbol_4(sb, 16u, 123);
-  tint_symbol_5(sb, 16u, 123);
-  tint_symbol_6(sb, 16u, 123);
-  tint_symbol_7(sb, 16u, 123);
-  tint_symbol_8(sb, 16u, 123);
-  tint_symbol_9(sb, 16u, 123);
-  tint_symbol_10(sb, 16u, 123, 345);
-  tint_symbol_11(sb, 20u, 123u);
-  tint_symbol_12(sb, 20u);
-  tint_symbol_13(sb, 20u, 123u);
-  tint_symbol_14(sb, 20u, 123u);
-  tint_symbol_15(sb, 20u, 123u);
-  tint_symbol_16(sb, 20u, 123u);
-  tint_symbol_17(sb, 20u, 123u);
-  tint_symbol_18(sb, 20u, 123u);
-  tint_symbol_19(sb, 20u, 123u);
-  tint_symbol_20(sb, 20u, 123u);
-  tint_symbol_21(sb, 20u, 123u, 345u);
+  tint_atomicStore(sb, 16u, 123);
+  tint_atomicLoad(sb, 16u);
+  tint_atomicAdd(sb, 16u, 123);
+  tint_atomicSub(sb, 16u, 123);
+  tint_atomicMax(sb, 16u, 123);
+  tint_atomicMin(sb, 16u, 123);
+  tint_atomicAnd(sb, 16u, 123);
+  tint_atomicOr(sb, 16u, 123);
+  tint_atomicXor(sb, 16u, 123);
+  tint_atomicExchange(sb, 16u, 123);
+  tint_atomicCompareExchangeWeak(sb, 16u, 123, 345);
+  tint_atomicStore_1(sb, 20u, 123u);
+  tint_atomicLoad_1(sb, 20u);
+  tint_atomicAdd_1(sb, 20u, 123u);
+  tint_atomicSub_1(sb, 20u, 123u);
+  tint_atomicMax_1(sb, 20u, 123u);
+  tint_atomicMin_1(sb, 20u, 123u);
+  tint_atomicAnd_1(sb, 20u, 123u);
+  tint_atomicOr_1(sb, 20u, 123u);
+  tint_atomicXor_1(sb, 20u, 123u);
+  tint_atomicExchange_1(sb, 20u, 123u);
+  tint_atomicCompareExchangeWeak_1(sb, 20u, 123u, 345u);
 }
 
 @group(0) @binding(0) var<storage, read_write> sb : SB;
diff --git a/src/tint/transform/decompose_strided_array_test.cc b/src/tint/transform/decompose_strided_array_test.cc
index 53b335e..e911df3 100644
--- a/src/tint/transform/decompose_strided_array_test.cc
+++ b/src/tint/transform/decompose_strided_array_test.cc
@@ -374,8 +374,8 @@
 @stage(compute) @workgroup_size(1i)
 fn f() {
   s.a = array<strided_arr, 4u>();
-  s.a = array<strided_arr, 4u>(strided_arr(1.0), strided_arr(2.0), strided_arr(3.0), strided_arr(4.0));
-  s.a[1i].el = 5.0;
+  s.a = array<strided_arr, 4u>(strided_arr(1.0f), strided_arr(2.0f), strided_arr(3.0f), strided_arr(4.0f));
+  s.a[1i].el = 5.0f;
 }
 )";
 
@@ -423,8 +423,8 @@
 @stage(compute) @workgroup_size(1i)
 fn f() {
   s.a = array<f32, 4u>();
-  s.a = array<f32, 4u>(1.0, 2.0, 3.0, 4.0);
-  s.a[1i] = 5.0;
+  s.a = array<f32, 4u>(1.0f, 2.0f, 3.0f, 4.0f);
+  s.a[1i] = 5.0f;
 }
 )";
 
@@ -483,8 +483,8 @@
 fn f() {
   let c = s.a;
   let d = s.a[1i].el;
-  s.a = array<strided_arr, 4u>(strided_arr(1.0), strided_arr(2.0), strided_arr(3.0), strided_arr(4.0));
-  s.a[1i].el = 5.0;
+  s.a = array<strided_arr, 4u>(strided_arr(1.0f), strided_arr(2.0f), strided_arr(3.0f), strided_arr(4.0f));
+  s.a[1i].el = 5.0f;
 }
 )";
 
@@ -546,8 +546,8 @@
   let a : ARR = s.a;
   let b : f32 = s.a[1i].el;
   s.a = ARR();
-  s.a = ARR(strided_arr(1.0), strided_arr(2.0), strided_arr(3.0), strided_arr(4.0));
-  s.a[1i].el = 5.0;
+  s.a = ARR(strided_arr(1.0f), strided_arr(2.0f), strided_arr(3.0f), strided_arr(4.0f));
+  s.a[1i].el = 5.0f;
 }
 )";
 
@@ -648,7 +648,7 @@
   let c : ARR_A = s.a[3i].el[2i];
   let d : f32 = s.a[3i].el[2i][1i].el;
   s.a = ARR_B();
-  s.a[3i].el[2i][1i].el = 5.0;
+  s.a[3i].el[2i][1i].el = 5.0f;
 }
 )";
 
diff --git a/src/tint/transform/decompose_strided_matrix_test.cc b/src/tint/transform/decompose_strided_matrix_test.cc
index 803ae73..2367cf1 100644
--- a/src/tint/transform/decompose_strided_matrix_test.cc
+++ b/src/tint/transform/decompose_strided_matrix_test.cc
@@ -376,7 +376,7 @@
 
 @stage(compute) @workgroup_size(1i)
 fn f() {
-  s.m = mat2x2_stride_32_to_arr(mat2x2<f32>(vec2<f32>(1.0, 2.0), vec2<f32>(3.0, 4.0)));
+  s.m = mat2x2_stride_32_to_arr(mat2x2<f32>(vec2<f32>(1.0f, 2.0f), vec2<f32>(3.0f, 4.0f)));
 }
 )";
 
@@ -429,7 +429,7 @@
 
 @stage(compute) @workgroup_size(1i)
 fn f() {
-  s.m[1i] = vec2<f32>(1.0, 2.0);
+  s.m[1i] = vec2<f32>(1.0f, 2.0f);
 }
 )";
 
@@ -505,8 +505,8 @@
   let x = arr_to_mat2x2_stride_32(s.m);
   let y = s.m[1i];
   let z = x[1i];
-  s.m = mat2x2_stride_32_to_arr(mat2x2<f32>(vec2<f32>(1.0, 2.0), vec2<f32>(3.0, 4.0)));
-  s.m[1i] = vec2<f32>(5.0, 6.0);
+  s.m = mat2x2_stride_32_to_arr(mat2x2<f32>(vec2<f32>(1.0f, 2.0f), vec2<f32>(3.0f, 4.0f)));
+  s.m[1i] = vec2<f32>(5.0f, 6.0f);
 }
 )";
 
@@ -613,7 +613,7 @@
 
 @stage(compute) @workgroup_size(1i)
 fn f() {
-  s.m = mat2x2<f32>(vec2<f32>(1.0, 2.0), vec2<f32>(3.0, 4.0));
+  s.m = mat2x2<f32>(vec2<f32>(1.0f, 2.0f), vec2<f32>(3.0f, 4.0f));
 }
 )";
 
diff --git a/src/tint/transform/fold_constants_test.cc b/src/tint/transform/fold_constants_test.cc
index f27ede6..54bb2e3 100644
--- a/src/tint/transform/fold_constants_test.cc
+++ b/src/tint/transform/fold_constants_test.cc
@@ -41,7 +41,7 @@
 
 var<private> b : u32 = 123u;
 
-var<private> c : f32 = 123.0;
+var<private> c : f32 = 123.0f;
 
 var<private> d : bool = true;
 
@@ -70,7 +70,7 @@
 
 var<private> b : u32 = 123u;
 
-var<private> c : f32 = 123.0;
+var<private> c : f32 = 123.0f;
 
 var<private> d : bool = true;
 
@@ -99,7 +99,7 @@
 
 var<private> b : u32 = 123u;
 
-var<private> c : f32 = 123.0;
+var<private> c : f32 = 123.0f;
 
 var<private> d : bool = true;
 
@@ -128,7 +128,7 @@
 
 var<private> b : vec3<u32> = vec3<u32>(123u);
 
-var<private> c : vec3<f32> = vec3<f32>(123.0);
+var<private> c : vec3<f32> = vec3<f32>(123.0f);
 
 var<private> d : vec3<bool> = vec3<bool>(true);
 
@@ -157,7 +157,7 @@
 
 var<private> b : vec3<u32> = vec3<u32>(123u);
 
-var<private> c : vec3<f32> = vec3<f32>(123.0);
+var<private> c : vec3<f32> = vec3<f32>(123.0f);
 
 var<private> d : vec3<bool> = vec3<bool>(true);
 
@@ -186,7 +186,7 @@
 
 var<private> b : vec3<u32> = vec3<u32>(123u);
 
-var<private> c : vec3<f32> = vec3<f32>(123.0);
+var<private> c : vec3<f32> = vec3<f32>(123.0f);
 
 var<private> d : vec3<bool> = vec3<bool>(true);
 
@@ -245,7 +245,7 @@
 fn f() {
   var a : i32 = 123i;
   var b : u32 = 123u;
-  var c : f32 = 123.0;
+  var c : f32 = 123.0f;
   var d : bool = true;
 }
 )";
@@ -269,7 +269,7 @@
 fn f() {
   var a : i32 = 123i;
   var b : u32 = 123u;
-  var c : f32 = 123.0;
+  var c : f32 = 123.0f;
   var d : bool = true;
 }
 )";
@@ -293,7 +293,7 @@
 fn f() {
   var a : i32 = 123i;
   var b : u32 = 123u;
-  var c : f32 = 123.0;
+  var c : f32 = 123.0f;
   var d : bool = true;
 }
 )";
@@ -308,7 +308,7 @@
 fn f() {
   var a : vec3<i32> = vec3<i32>(123i);
   var b : vec3<u32> = vec3<u32>(123u);
-  var c : vec3<f32> = vec3<f32>(123.0);
+  var c : vec3<f32> = vec3<f32>(123.0f);
   var d : vec3<bool> = vec3<bool>(true);
 }
 )";
@@ -317,7 +317,7 @@
 fn f() {
   var a : vec3<i32> = vec3<i32>(123i);
   var b : vec3<u32> = vec3<u32>(123u);
-  var c : vec3<f32> = vec3<f32>(123.0);
+  var c : vec3<f32> = vec3<f32>(123.0f);
   var d : vec3<bool> = vec3<bool>(true);
 }
 )";
@@ -341,7 +341,7 @@
 fn f() {
   var a : vec3<i32> = vec3<i32>(123i);
   var b : vec3<u32> = vec3<u32>(123u);
-  var c : vec3<f32> = vec3<f32>(123.0);
+  var c : vec3<f32> = vec3<f32>(123.0f);
   var d : vec3<bool> = vec3<bool>(true);
 }
 )";
@@ -365,7 +365,7 @@
 fn f() {
   var a : vec3<i32> = vec3<i32>(123i);
   var b : vec3<u32> = vec3<u32>(123u);
-  var c : vec3<f32> = vec3<f32>(123.0);
+  var c : vec3<f32> = vec3<f32>(123.0f);
   var d : vec3<bool> = vec3<bool>(true);
 }
 )";
@@ -412,7 +412,7 @@
     auto* expect = R"(
 fn f() {
   var a : f32 = f32();
-  var b : vec2<f32> = vec2<f32>(1.0, a);
+  var b : vec2<f32> = vec2<f32>(1.0f, a);
 }
 )";
 
diff --git a/src/tint/transform/manager.cc b/src/tint/transform/manager.cc
index 823474c..e5f7682 100644
--- a/src/tint/transform/manager.cc
+++ b/src/tint/transform/manager.cc
@@ -49,7 +49,7 @@
     Output out;
     for (const auto& transform : transforms_) {
         if (!transform->ShouldRun(in, data)) {
-            TINT_IF_PRINT_PROGRAM(std::cout << "Skipping " << transform->TypeInfo().name);
+            TINT_IF_PRINT_PROGRAM(std::cout << "Skipping " << transform->TypeInfo().name << std::endl);
             continue;
         }
         TINT_IF_PRINT_PROGRAM(print_program("Input to", transform.get()));
diff --git a/src/tint/transform/multiplanar_external_texture_test.cc b/src/tint/transform/multiplanar_external_texture_test.cc
index 171df8d..39c4602 100644
--- a/src/tint/transform/multiplanar_external_texture_test.cc
+++ b/src/tint/transform/multiplanar_external_texture_test.cc
@@ -249,14 +249,14 @@
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   var color : vec3<f32>;
   if ((params.numPlanes == 1u)) {
-    color = textureSampleLevel(plane0, smp, coord, 0.0).rgb;
+    color = textureSampleLevel(plane0, smp, coord, 0.0f).rgb;
   } else {
-    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0).r, textureSampleLevel(plane1, smp, coord, 0.0).rg, 1.0) * params.yuvToRgbConversionMatrix);
+    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0f).r, textureSampleLevel(plane1, smp, coord, 0.0f).rg, 1.0f) * params.yuvToRgbConversionMatrix);
   }
   color = gammaCorrection(color, params.gammaDecodeParams);
   color = (params.gamutConversionMatrix * color);
   color = gammaCorrection(color, params.gammaEncodeParams);
-  return vec4<f32>(color, 1.0);
+  return vec4<f32>(color, 1.0f);
 }
 
 @stage(fragment)
@@ -318,14 +318,14 @@
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   var color : vec3<f32>;
   if ((params.numPlanes == 1u)) {
-    color = textureSampleLevel(plane0, smp, coord, 0.0).rgb;
+    color = textureSampleLevel(plane0, smp, coord, 0.0f).rgb;
   } else {
-    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0).r, textureSampleLevel(plane1, smp, coord, 0.0).rg, 1.0) * params.yuvToRgbConversionMatrix);
+    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0f).r, textureSampleLevel(plane1, smp, coord, 0.0f).rg, 1.0f) * params.yuvToRgbConversionMatrix);
   }
   color = gammaCorrection(color, params.gammaDecodeParams);
   color = (params.gamutConversionMatrix * color);
   color = gammaCorrection(color, params.gammaEncodeParams);
-  return vec4<f32>(color, 1.0);
+  return vec4<f32>(color, 1.0f);
 }
 
 @stage(fragment)
@@ -394,12 +394,12 @@
   if ((params.numPlanes == 1u)) {
     color = textureLoad(plane0, coord, 0i).rgb;
   } else {
-    color = (vec4<f32>(textureLoad(plane0, coord, 0i).r, textureLoad(plane1, coord, 0i).rg, 1.0) * params.yuvToRgbConversionMatrix);
+    color = (vec4<f32>(textureLoad(plane0, coord, 0i).r, textureLoad(plane1, coord, 0i).rg, 1.0f) * params.yuvToRgbConversionMatrix);
   }
   color = gammaCorrection(color, params.gammaDecodeParams);
   color = (params.gamutConversionMatrix * color);
   color = gammaCorrection(color, params.gammaEncodeParams);
-  return vec4<f32>(color, 1.0);
+  return vec4<f32>(color, 1.0f);
 }
 
 @stage(fragment)
@@ -462,12 +462,12 @@
   if ((params.numPlanes == 1u)) {
     color = textureLoad(plane0, coord, 0i).rgb;
   } else {
-    color = (vec4<f32>(textureLoad(plane0, coord, 0i).r, textureLoad(plane1, coord, 0i).rg, 1.0) * params.yuvToRgbConversionMatrix);
+    color = (vec4<f32>(textureLoad(plane0, coord, 0i).r, textureLoad(plane1, coord, 0i).rg, 1.0f) * params.yuvToRgbConversionMatrix);
   }
   color = gammaCorrection(color, params.gammaDecodeParams);
   color = (params.gamutConversionMatrix * color);
   color = gammaCorrection(color, params.gammaEncodeParams);
-  return vec4<f32>(color, 1.0);
+  return vec4<f32>(color, 1.0f);
 }
 
 @stage(fragment)
@@ -536,14 +536,14 @@
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   var color : vec3<f32>;
   if ((params.numPlanes == 1u)) {
-    color = textureSampleLevel(plane0, smp, coord, 0.0).rgb;
+    color = textureSampleLevel(plane0, smp, coord, 0.0f).rgb;
   } else {
-    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0).r, textureSampleLevel(plane1, smp, coord, 0.0).rg, 1.0) * params.yuvToRgbConversionMatrix);
+    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0f).r, textureSampleLevel(plane1, smp, coord, 0.0f).rg, 1.0f) * params.yuvToRgbConversionMatrix);
   }
   color = gammaCorrection(color, params.gammaDecodeParams);
   color = (params.gamutConversionMatrix * color);
   color = gammaCorrection(color, params.gammaEncodeParams);
-  return vec4<f32>(color, 1.0);
+  return vec4<f32>(color, 1.0f);
 }
 
 fn textureLoadExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, coord : vec2<i32>, params : ExternalTextureParams) -> vec4<f32> {
@@ -551,12 +551,12 @@
   if ((params.numPlanes == 1u)) {
     color = textureLoad(plane0, coord, 0i).rgb;
   } else {
-    color = (vec4<f32>(textureLoad(plane0, coord, 0i).r, textureLoad(plane1, coord, 0i).rg, 1.0) * params.yuvToRgbConversionMatrix);
+    color = (vec4<f32>(textureLoad(plane0, coord, 0i).r, textureLoad(plane1, coord, 0i).rg, 1.0f) * params.yuvToRgbConversionMatrix);
   }
   color = gammaCorrection(color, params.gammaDecodeParams);
   color = (params.gamutConversionMatrix * color);
   color = gammaCorrection(color, params.gammaEncodeParams);
-  return vec4<f32>(color, 1.0);
+  return vec4<f32>(color, 1.0f);
 }
 
 @stage(fragment)
@@ -619,14 +619,14 @@
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   var color : vec3<f32>;
   if ((params.numPlanes == 1u)) {
-    color = textureSampleLevel(plane0, smp, coord, 0.0).rgb;
+    color = textureSampleLevel(plane0, smp, coord, 0.0f).rgb;
   } else {
-    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0).r, textureSampleLevel(plane1, smp, coord, 0.0).rg, 1.0) * params.yuvToRgbConversionMatrix);
+    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0f).r, textureSampleLevel(plane1, smp, coord, 0.0f).rg, 1.0f) * params.yuvToRgbConversionMatrix);
   }
   color = gammaCorrection(color, params.gammaDecodeParams);
   color = (params.gamutConversionMatrix * color);
   color = gammaCorrection(color, params.gammaEncodeParams);
-  return vec4<f32>(color, 1.0);
+  return vec4<f32>(color, 1.0f);
 }
 
 fn textureLoadExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, coord : vec2<i32>, params : ExternalTextureParams) -> vec4<f32> {
@@ -634,12 +634,12 @@
   if ((params.numPlanes == 1u)) {
     color = textureLoad(plane0, coord, 0i).rgb;
   } else {
-    color = (vec4<f32>(textureLoad(plane0, coord, 0i).r, textureLoad(plane1, coord, 0i).rg, 1.0) * params.yuvToRgbConversionMatrix);
+    color = (vec4<f32>(textureLoad(plane0, coord, 0i).r, textureLoad(plane1, coord, 0i).rg, 1.0f) * params.yuvToRgbConversionMatrix);
   }
   color = gammaCorrection(color, params.gammaDecodeParams);
   color = (params.gamutConversionMatrix * color);
   color = gammaCorrection(color, params.gammaEncodeParams);
-  return vec4<f32>(color, 1.0);
+  return vec4<f32>(color, 1.0f);
 }
 
 @stage(fragment)
@@ -730,14 +730,14 @@
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   var color : vec3<f32>;
   if ((params.numPlanes == 1u)) {
-    color = textureSampleLevel(plane0, smp, coord, 0.0).rgb;
+    color = textureSampleLevel(plane0, smp, coord, 0.0f).rgb;
   } else {
-    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0).r, textureSampleLevel(plane1, smp, coord, 0.0).rg, 1.0) * params.yuvToRgbConversionMatrix);
+    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0f).r, textureSampleLevel(plane1, smp, coord, 0.0f).rg, 1.0f) * params.yuvToRgbConversionMatrix);
   }
   color = gammaCorrection(color, params.gammaDecodeParams);
   color = (params.gamutConversionMatrix * color);
   color = gammaCorrection(color, params.gammaEncodeParams);
-  return vec4<f32>(color, 1.0);
+  return vec4<f32>(color, 1.0f);
 }
 
 @stage(fragment)
@@ -808,14 +808,14 @@
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   var color : vec3<f32>;
   if ((params.numPlanes == 1u)) {
-    color = textureSampleLevel(plane0, smp, coord, 0.0).rgb;
+    color = textureSampleLevel(plane0, smp, coord, 0.0f).rgb;
   } else {
-    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0).r, textureSampleLevel(plane1, smp, coord, 0.0).rg, 1.0) * params.yuvToRgbConversionMatrix);
+    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0f).r, textureSampleLevel(plane1, smp, coord, 0.0f).rg, 1.0f) * params.yuvToRgbConversionMatrix);
   }
   color = gammaCorrection(color, params.gammaDecodeParams);
   color = (params.gamutConversionMatrix * color);
   color = gammaCorrection(color, params.gammaEncodeParams);
-  return vec4<f32>(color, 1.0);
+  return vec4<f32>(color, 1.0f);
 }
 
 fn f(t : texture_2d<f32>, ext_tex_plane_1_1 : texture_2d<f32>, ext_tex_params_1 : ExternalTextureParams, s : sampler) {
@@ -895,14 +895,14 @@
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   var color : vec3<f32>;
   if ((params.numPlanes == 1u)) {
-    color = textureSampleLevel(plane0, smp, coord, 0.0).rgb;
+    color = textureSampleLevel(plane0, smp, coord, 0.0f).rgb;
   } else {
-    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0).r, textureSampleLevel(plane1, smp, coord, 0.0).rg, 1.0) * params.yuvToRgbConversionMatrix);
+    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0f).r, textureSampleLevel(plane1, smp, coord, 0.0f).rg, 1.0f) * params.yuvToRgbConversionMatrix);
   }
   color = gammaCorrection(color, params.gammaDecodeParams);
   color = (params.gamutConversionMatrix * color);
   color = gammaCorrection(color, params.gammaEncodeParams);
-  return vec4<f32>(color, 1.0);
+  return vec4<f32>(color, 1.0f);
 }
 
 fn f(t : texture_2d<f32>, ext_tex_plane_1_1 : texture_2d<f32>, ext_tex_params_1 : ExternalTextureParams, s : sampler) {
@@ -972,14 +972,14 @@
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   var color : vec3<f32>;
   if ((params.numPlanes == 1u)) {
-    color = textureSampleLevel(plane0, smp, coord, 0.0).rgb;
+    color = textureSampleLevel(plane0, smp, coord, 0.0f).rgb;
   } else {
-    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0).r, textureSampleLevel(plane1, smp, coord, 0.0).rg, 1.0) * params.yuvToRgbConversionMatrix);
+    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0f).r, textureSampleLevel(plane1, smp, coord, 0.0f).rg, 1.0f) * params.yuvToRgbConversionMatrix);
   }
   color = gammaCorrection(color, params.gammaDecodeParams);
   color = (params.gamutConversionMatrix * color);
   color = gammaCorrection(color, params.gammaEncodeParams);
-  return vec4<f32>(color, 1.0);
+  return vec4<f32>(color, 1.0f);
 }
 
 fn f(s : sampler, t : texture_2d<f32>, ext_tex_plane_1_1 : texture_2d<f32>, ext_tex_params_1 : ExternalTextureParams) {
@@ -1060,14 +1060,14 @@
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   var color : vec3<f32>;
   if ((params.numPlanes == 1u)) {
-    color = textureSampleLevel(plane0, smp, coord, 0.0).rgb;
+    color = textureSampleLevel(plane0, smp, coord, 0.0f).rgb;
   } else {
-    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0).r, textureSampleLevel(plane1, smp, coord, 0.0).rg, 1.0) * params.yuvToRgbConversionMatrix);
+    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0f).r, textureSampleLevel(plane1, smp, coord, 0.0f).rg, 1.0f) * params.yuvToRgbConversionMatrix);
   }
   color = gammaCorrection(color, params.gammaDecodeParams);
   color = (params.gamutConversionMatrix * color);
   color = gammaCorrection(color, params.gammaEncodeParams);
-  return vec4<f32>(color, 1.0);
+  return vec4<f32>(color, 1.0f);
 }
 
 fn f(t : texture_2d<f32>, ext_tex_plane_1_2 : texture_2d<f32>, ext_tex_params_2 : ExternalTextureParams, s : sampler, t2 : texture_2d<f32>, ext_tex_plane_1_3 : texture_2d<f32>, ext_tex_params_3 : ExternalTextureParams) {
@@ -1158,14 +1158,14 @@
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   var color : vec3<f32>;
   if ((params.numPlanes == 1u)) {
-    color = textureSampleLevel(plane0, smp, coord, 0.0).rgb;
+    color = textureSampleLevel(plane0, smp, coord, 0.0f).rgb;
   } else {
-    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0).r, textureSampleLevel(plane1, smp, coord, 0.0).rg, 1.0) * params.yuvToRgbConversionMatrix);
+    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0f).r, textureSampleLevel(plane1, smp, coord, 0.0f).rg, 1.0f) * params.yuvToRgbConversionMatrix);
   }
   color = gammaCorrection(color, params.gammaDecodeParams);
   color = (params.gamutConversionMatrix * color);
   color = gammaCorrection(color, params.gammaEncodeParams);
-  return vec4<f32>(color, 1.0);
+  return vec4<f32>(color, 1.0f);
 }
 
 fn f(t : texture_2d<f32>, ext_tex_plane_1_2 : texture_2d<f32>, ext_tex_params_2 : ExternalTextureParams, s : sampler, t2 : texture_2d<f32>, ext_tex_plane_1_3 : texture_2d<f32>, ext_tex_params_3 : ExternalTextureParams) {
@@ -1243,14 +1243,14 @@
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   var color : vec3<f32>;
   if ((params.numPlanes == 1u)) {
-    color = textureSampleLevel(plane0, smp, coord, 0.0).rgb;
+    color = textureSampleLevel(plane0, smp, coord, 0.0f).rgb;
   } else {
-    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0).r, textureSampleLevel(plane1, smp, coord, 0.0).rg, 1.0) * params.yuvToRgbConversionMatrix);
+    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0f).r, textureSampleLevel(plane1, smp, coord, 0.0f).rg, 1.0f) * params.yuvToRgbConversionMatrix);
   }
   color = gammaCorrection(color, params.gammaDecodeParams);
   color = (params.gamutConversionMatrix * color);
   color = gammaCorrection(color, params.gammaEncodeParams);
-  return vec4<f32>(color, 1.0);
+  return vec4<f32>(color, 1.0f);
 }
 
 fn nested(t : texture_2d<f32>, ext_tex_plane_1_1 : texture_2d<f32>, ext_tex_params_1 : ExternalTextureParams, s : sampler) {
@@ -1333,14 +1333,14 @@
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   var color : vec3<f32>;
   if ((params.numPlanes == 1u)) {
-    color = textureSampleLevel(plane0, smp, coord, 0.0).rgb;
+    color = textureSampleLevel(plane0, smp, coord, 0.0f).rgb;
   } else {
-    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0).r, textureSampleLevel(plane1, smp, coord, 0.0).rg, 1.0) * params.yuvToRgbConversionMatrix);
+    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0f).r, textureSampleLevel(plane1, smp, coord, 0.0f).rg, 1.0f) * params.yuvToRgbConversionMatrix);
   }
   color = gammaCorrection(color, params.gammaDecodeParams);
   color = (params.gamutConversionMatrix * color);
   color = gammaCorrection(color, params.gammaEncodeParams);
-  return vec4<f32>(color, 1.0);
+  return vec4<f32>(color, 1.0f);
 }
 
 fn nested(t : texture_2d<f32>, ext_tex_plane_1_1 : texture_2d<f32>, ext_tex_params_1 : ExternalTextureParams, s : sampler) {
@@ -1463,14 +1463,14 @@
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   var color : vec3<f32>;
   if ((params.numPlanes == 1u)) {
-    color = textureSampleLevel(plane0, smp, coord, 0.0).rgb;
+    color = textureSampleLevel(plane0, smp, coord, 0.0f).rgb;
   } else {
-    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0).r, textureSampleLevel(plane1, smp, coord, 0.0).rg, 1.0) * params.yuvToRgbConversionMatrix);
+    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0f).r, textureSampleLevel(plane1, smp, coord, 0.0f).rg, 1.0f) * params.yuvToRgbConversionMatrix);
   }
   color = gammaCorrection(color, params.gammaDecodeParams);
   color = (params.gamutConversionMatrix * color);
   color = gammaCorrection(color, params.gammaEncodeParams);
-  return vec4<f32>(color, 1.0);
+  return vec4<f32>(color, 1.0f);
 }
 
 fn f(t : texture_2d<f32>, ext_tex_plane_1_1 : texture_2d<f32>, ext_tex_params_1 : ExternalTextureParams, s : sampler) {
@@ -1551,14 +1551,14 @@
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   var color : vec3<f32>;
   if ((params.numPlanes == 1u)) {
-    color = textureSampleLevel(plane0, smp, coord, 0.0).rgb;
+    color = textureSampleLevel(plane0, smp, coord, 0.0f).rgb;
   } else {
-    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0).r, textureSampleLevel(plane1, smp, coord, 0.0).rg, 1.0) * params.yuvToRgbConversionMatrix);
+    color = (vec4<f32>(textureSampleLevel(plane0, smp, coord, 0.0f).r, textureSampleLevel(plane1, smp, coord, 0.0f).rg, 1.0f) * params.yuvToRgbConversionMatrix);
   }
   color = gammaCorrection(color, params.gammaDecodeParams);
   color = (params.gamutConversionMatrix * color);
   color = gammaCorrection(color, params.gammaEncodeParams);
-  return vec4<f32>(color, 1.0);
+  return vec4<f32>(color, 1.0f);
 }
 
 fn f(t : texture_2d<f32>, ext_tex_plane_1_1 : texture_2d<f32>, ext_tex_params_1 : ExternalTextureParams, s : sampler) {
diff --git a/src/tint/transform/vertex_pulling_test.cc b/src/tint/transform/vertex_pulling_test.cc
index 80768a7..ef37631 100644
--- a/src/tint/transform/vertex_pulling_test.cc
+++ b/src/tint/transform/vertex_pulling_test.cc
@@ -1172,22 +1172,22 @@
     uint8x4 = (((vec4<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]) << vec4<u32>(24u, 16u, 8u, 0u)) >> vec4<u32>(24u))).xy;
     sint8x2 = (((vec2<i32>(bitcast<i32>((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] << 16u))) << vec2<u32>(8u, 0u)) >> vec2<u32>(24u))).x;
     sint8x4 = (((vec4<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])) << vec4<u32>(24u, 16u, 8u, 0u)) >> vec4<u32>(24u))).xy;
-    unorm8x2 = vec4<f32>(unpack4x8unorm((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] & 65535u)).xy, 0.0, 1.0);
+    unorm8x2 = vec4<f32>(unpack4x8unorm((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] & 65535u)).xy, 0.0f, 1.0f);
     unorm8x4 = unpack4x8unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]).x;
-    snorm8x2 = vec3<f32>(unpack4x8snorm((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] & 65535u)).xy, 0.0);
+    snorm8x2 = vec3<f32>(unpack4x8snorm((tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)] & 65535u)).xy, 0.0f);
     snorm8x4 = unpack4x8snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]).x;
     uint16x2 = vec3<u32>(((vec2<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]) << vec2<u32>(16u, 0u)) >> vec2<u32>(16u)), 0u);
     uint16x4 = (((vec2<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)], tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]).xxyy << vec4<u32>(16u, 0u, 16u, 0u)) >> vec4<u32>(16u))).xy;
     sint16x2 = vec4<i32>(((vec2<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)])) << vec2<u32>(16u, 0u)) >> vec2<u32>(16u)), 0i, 1i);
     sint16x4 = (((vec2<i32>(bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<i32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])).xxyy << vec4<u32>(16u, 0u, 16u, 0u)) >> vec4<u32>(16u))).x;
-    unorm16x2 = vec3<f32>(unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), 0.0);
+    unorm16x2 = vec3<f32>(unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), 0.0f);
     unorm16x4 = vec4<f32>(unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16unorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])).x;
-    snorm16x2 = vec4<f32>(unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), 0.0, 1.0);
+    snorm16x2 = vec4<f32>(unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), 0.0f, 1.0f);
     snorm16x4 = vec4<f32>(unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16snorm(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])).xyz;
-    float16x2 = vec4<f32>(unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), 0.0, 1.0);
+    float16x2 = vec4<f32>(unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), 0.0f, 1.0f);
     float16x4 = vec4<f32>(unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), unpack2x16float(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])).x;
-    float32 = vec4<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), 0.0, 0.0, 1.0);
-    float32x2 = vec4<f32>(vec2<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])), 0.0, 1.0);
+    float32 = vec4<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), 0.0f, 0.0f, 1.0f);
+    float32x2 = vec4<f32>(vec2<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)])), 0.0f, 1.0f);
     float32x3 = vec3<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)])).xy;
     float32x4 = vec4<f32>(bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 17u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 18u)]), bitcast<f32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 19u)])).xyz;
     uint32 = vec3<u32>(tint_pulling_vertex_buffer_0.tint_vertex_data[(buffer_array_base_0 + 16u)], 0u, 0u);
diff --git a/src/tint/utils/compiler_macros.h b/src/tint/utils/compiler_macros.h
index ada138e..34965c6 100644
--- a/src/tint/utils/compiler_macros.h
+++ b/src/tint/utils/compiler_macros.h
@@ -20,23 +20,63 @@
 #define TINT_REQUIRE_SEMICOLON static_assert(true)
 
 #if defined(_MSC_VER)
-#define TINT_WARNING_UNREACHABLE_CODE 4702
-#define TINT_WARNING_CONSTANT_OVERFLOW 4756
+////////////////////////////////////////////////////////////////////////////////
+// MSVC
+////////////////////////////////////////////////////////////////////////////////
+#define TINT_DISABLE_WARNING_CONSTANT_OVERFLOW __pragma(warning(disable : 4756))
+#define TINT_DISABLE_WARNING_MAYBE_UNINITIALIZED /* currently no-op */
+#define TINT_DISABLE_WARNING_UNREACHABLE_CODE __pragma(warning(disable : 4702))
 
 // clang-format off
-#define TINT_BEGIN_DISABLE_WARNING(name)                        \
-    __pragma(warning(push))                                     \
-    __pragma(warning(disable:TINT_CONCAT(TINT_WARNING_, name))) \
+#define TINT_BEGIN_DISABLE_WARNING(name)     \
+    __pragma(warning(push))                  \
+    TINT_CONCAT(TINT_DISABLE_WARNING_, name) \
     TINT_REQUIRE_SEMICOLON
-#define TINT_END_DISABLE_WARNING(name)                          \
-    __pragma(warning(pop))                                      \
+#define TINT_END_DISABLE_WARNING(name)       \
+    __pragma(warning(pop))                   \
+    TINT_REQUIRE_SEMICOLON
+// clang-format on
+#elif defined(__clang__)
+////////////////////////////////////////////////////////////////////////////////
+// Clang
+////////////////////////////////////////////////////////////////////////////////
+#define TINT_DISABLE_WARNING_CONSTANT_OVERFLOW   /* currently no-op */
+#define TINT_DISABLE_WARNING_MAYBE_UNINITIALIZED /* currently no-op */
+#define TINT_DISABLE_WARNING_UNREACHABLE_CODE    /* currently no-op */
+
+// clang-format off
+#define TINT_BEGIN_DISABLE_WARNING(name)     \
+    _Pragma("clang diagnostic push")         \
+    TINT_CONCAT(TINT_DISABLE_WARNING_, name) \
+    TINT_REQUIRE_SEMICOLON
+#define TINT_END_DISABLE_WARNING(name)       \
+    _Pragma("clang diagnostic pop")          \
+    TINT_REQUIRE_SEMICOLON
+// clang-format on
+#elif defined(__GNUC__)
+////////////////////////////////////////////////////////////////////////////////
+// GCC
+////////////////////////////////////////////////////////////////////////////////
+#define TINT_DISABLE_WARNING_CONSTANT_OVERFLOW /* currently no-op */
+#define TINT_DISABLE_WARNING_MAYBE_UNINITIALIZED \
+    _Pragma("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
+#define TINT_DISABLE_WARNING_UNREACHABLE_CODE /* currently no-op */
+
+// clang-format off
+#define TINT_BEGIN_DISABLE_WARNING(name)     \
+    _Pragma("GCC diagnostic push")           \
+    TINT_CONCAT(TINT_DISABLE_WARNING_, name) \
+    TINT_REQUIRE_SEMICOLON
+#define TINT_END_DISABLE_WARNING(name)       \
+    _Pragma("GCC diagnostic pop")            \
     TINT_REQUIRE_SEMICOLON
 // clang-format on
 #else
-// clang-format off
+////////////////////////////////////////////////////////////////////////////////
+// Other
+////////////////////////////////////////////////////////////////////////////////
 #define TINT_BEGIN_DISABLE_WARNING(name) TINT_REQUIRE_SEMICOLON
 #define TINT_END_DISABLE_WARNING(name) TINT_REQUIRE_SEMICOLON
-// clang-format on
-#endif  // defined(_MSC_VER)
+#endif
 
 #endif  // SRC_TINT_UTILS_COMPILER_MACROS_H_
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
index ccce06a..eb44f14 100644
--- a/src/tint/writer/glsl/generator_impl.cc
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -32,10 +32,10 @@
 #include "src/tint/sem/atomic.h"
 #include "src/tint/sem/block_statement.h"
 #include "src/tint/sem/call.h"
+#include "src/tint/sem/constant.h"
 #include "src/tint/sem/depth_multisampled_texture.h"
 #include "src/tint/sem/depth_texture.h"
 #include "src/tint/sem/function.h"
-#include "src/tint/sem/materialize.h"
 #include "src/tint/sem/member_accessor_expression.h"
 #include "src/tint/sem/module.h"
 #include "src/tint/sem/multisampled_texture.h"
@@ -147,6 +147,18 @@
     return "unknown";
 }
 
+void PrintF32(std::ostream& out, float value) {
+    // Note: Currently inf and nan should not be constructable, but this is implemented for the day
+    // we support them.
+    if (std::isinf(value)) {
+        out << (value >= 0 ? "uintBitsToFloat(0x7f800000u)" : "uintBitsToFloat(0xff800000u)");
+    } else if (std::isnan(value)) {
+        out << "uintBitsToFloat(0x7fc00000u)";
+    } else {
+        out << FloatToString(value) << "f";
+    }
+}
+
 }  // namespace
 
 SanitizedResult::SanitizedResult() = default;
@@ -691,12 +703,7 @@
 }
 
 bool GeneratorImpl::EmitCall(std::ostream& out, const ast::CallExpression* expr) {
-    auto* sem = builder_.Sem().Get(expr);
-    if (auto* m = sem->As<sem::Materialize>()) {
-        // TODO(crbug.com/tint/1504): Just emit the constant value.
-        sem = m->Expr();
-    }
-    auto* call = sem->As<sem::Call>();
+    auto* call = builder_.Sem().Get<sem::Call>(expr);
     auto* target = call->Target();
 
     if (target->Is<sem::Function>()) {
@@ -911,39 +918,56 @@
             return true;
         }
         case sem::BuiltinType::kAtomicCompareExchangeWeak: {
-            return CallBuiltinHelper(
-                out, expr, builtin, [&](TextBuffer* b, const std::vector<std::string>& params) {
-                    {
-                        auto pre = line(b);
-                        if (!EmitTypeAndName(pre, builtin->ReturnType(), ast::StorageClass::kNone,
-                                             ast::Access::kUndefined, "result")) {
-                            return false;
-                        }
-                        pre << ";";
+            // Emit the builtin return type unique to this overload. This does not
+            // exist in the AST, so it will not be generated in Generate().
+            if (!EmitStructType(&helpers_, builtin->ReturnType()->As<sem::Struct>())) {
+                return false;
+            }
+
+            auto* dest = expr->args[0];
+            auto* compare_value = expr->args[1];
+            auto* value = expr->args[2];
+
+            std::string result = UniqueIdentifier("atomic_compare_result");
+
+            {
+                auto pre = line();
+                if (!EmitTypeAndName(pre, builtin->ReturnType(), ast::StorageClass::kNone,
+                                     ast::Access::kUndefined, result)) {
+                    return false;
+                }
+                pre << ";";
+            }
+            {
+                auto pre = line();
+                pre << result << ".old_value = atomicCompSwap";
+                {
+                    ScopedParen sp(pre);
+                    if (!EmitExpression(pre, dest)) {
+                        return false;
                     }
-                    {
-                        auto pre = line(b);
-                        pre << "result.x = atomicCompSwap";
-                        {
-                            ScopedParen sp(pre);
-                            pre << params[0];
-                            pre << ", " << params[1];
-                            pre << ", " << params[2];
-                        }
-                        pre << ";";
+                    pre << ", ";
+                    if (!EmitExpression(pre, compare_value)) {
+                        return false;
                     }
-                    {
-                        auto pre = line(b);
-                        pre << "result.y = result.x == " << params[2] << " ? ";
-                        if (TypeOf(expr->args[2])->Is<sem::U32>()) {
-                            pre << "1u : 0u;";
-                        } else {
-                            pre << "1 : 0;";
-                        }
+                    pre << ", ";
+                    if (!EmitExpression(pre, value)) {
+                        return false;
                     }
-                    line(b) << "return result;";
-                    return true;
-                });
+                }
+                pre << ";";
+            }
+            {
+                auto pre = line();
+                pre << result << ".exchanged = " << result << ".old_value == ";
+                if (!EmitExpression(pre, compare_value)) {
+                    return false;
+                }
+                pre << ";";
+            }
+
+            out << result;
+            return true;
         }
 
         case sem::BuiltinType::kAtomicAdd:
@@ -1745,34 +1769,42 @@
 }
 
 bool GeneratorImpl::EmitExpression(std::ostream& out, const ast::Expression* expr) {
-    if (auto* a = expr->As<ast::IndexAccessorExpression>()) {
-        return EmitIndexAccessor(out, a);
+    if (auto* sem = builder_.Sem().Get(expr)) {
+        if (auto constant = sem->ConstantValue()) {
+            return EmitConstant(out, constant);
+        }
     }
-    if (auto* b = expr->As<ast::BinaryExpression>()) {
-        return EmitBinary(out, b);
-    }
-    if (auto* b = expr->As<ast::BitcastExpression>()) {
-        return EmitBitcast(out, b);
-    }
-    if (auto* c = expr->As<ast::CallExpression>()) {
-        return EmitCall(out, c);
-    }
-    if (auto* i = expr->As<ast::IdentifierExpression>()) {
-        return EmitIdentifier(out, i);
-    }
-    if (auto* l = expr->As<ast::LiteralExpression>()) {
-        return EmitLiteral(out, l);
-    }
-    if (auto* m = expr->As<ast::MemberAccessorExpression>()) {
-        return EmitMemberAccessor(out, m);
-    }
-    if (auto* u = expr->As<ast::UnaryOpExpression>()) {
-        return EmitUnaryOp(out, u);
-    }
-
-    diagnostics_.add_error(diag::System::Writer,
-                           "unknown expression type: " + std::string(expr->TypeInfo().name));
-    return false;
+    return Switch(
+        expr,
+        [&](const ast::IndexAccessorExpression* a) {  //
+            return EmitIndexAccessor(out, a);
+        },
+        [&](const ast::BinaryExpression* b) {  //
+            return EmitBinary(out, b);
+        },
+        [&](const ast::BitcastExpression* b) {  //
+            return EmitBitcast(out, b);
+        },
+        [&](const ast::CallExpression* c) {  //
+            return EmitCall(out, c);
+        },
+        [&](const ast::IdentifierExpression* i) {  //
+            return EmitIdentifier(out, i);
+        },
+        [&](const ast::LiteralExpression* l) {  //
+            return EmitLiteral(out, l);
+        },
+        [&](const ast::MemberAccessorExpression* m) {  //
+            return EmitMemberAccessor(out, m);
+        },
+        [&](const ast::UnaryOpExpression* u) {  //
+            return EmitUnaryOp(out, u);
+        },
+        [&](Default) {  //
+            diagnostics_.add_error(diag::System::Writer, "unknown expression type: " +
+                                                             std::string(expr->TypeInfo().name));
+            return false;
+        });
 }
 
 bool GeneratorImpl::EmitIdentifier(std::ostream& out, const ast::IdentifierExpression* expr) {
@@ -2175,6 +2207,94 @@
     return true;
 }
 
+bool GeneratorImpl::EmitConstant(std::ostream& out, const sem::Constant& constant) {
+    auto emit_bool = [&](size_t element_idx) {
+        out << (constant.Element<AInt>(element_idx) ? "true" : "false");
+        return true;
+    };
+    auto emit_f32 = [&](size_t element_idx) {
+        PrintF32(out, static_cast<float>(constant.Element<AFloat>(element_idx)));
+        return true;
+    };
+    auto emit_i32 = [&](size_t element_idx) {
+        out << constant.Element<AInt>(element_idx).value;
+        return true;
+    };
+    auto emit_u32 = [&](size_t element_idx) {
+        out << constant.Element<AInt>(element_idx).value << "u";
+        return true;
+    };
+    auto emit_vector = [&](const sem::Vector* vec_ty, size_t start, size_t end) {
+        if (!EmitType(out, vec_ty, ast::StorageClass::kNone, ast::Access::kUndefined, "")) {
+            return false;
+        }
+
+        ScopedParen sp(out);
+
+        auto emit_els = [&](auto emit_el) {
+            if (constant.AllEqual(start, end)) {
+                return emit_el(start);
+            }
+            for (size_t i = start; i < end; i++) {
+                if (i > start) {
+                    out << ", ";
+                }
+                if (!emit_el(i)) {
+                    return false;
+                }
+            }
+            return true;
+        };
+
+        return Switch(
+            vec_ty->type(),                                         //
+            [&](const sem::Bool*) { return emit_els(emit_bool); },  //
+            [&](const sem::F32*) { return emit_els(emit_f32); },    //
+            [&](const sem::I32*) { return emit_els(emit_i32); },    //
+            [&](const sem::U32*) { return emit_els(emit_u32); },    //
+            [&](Default) {
+                diagnostics_.add_error(diag::System::Writer,
+                                       "unhandled constant vector element type: " +
+                                           builder_.FriendlyName(vec_ty->type()));
+                return false;
+            });
+    };
+    auto emit_matrix = [&](const sem::Matrix* m) {
+        if (!EmitType(out, constant.Type(), ast::StorageClass::kNone, ast::Access::kUndefined,
+                      "")) {
+            return false;
+        }
+
+        ScopedParen sp(out);
+
+        for (size_t column_idx = 0; column_idx < m->columns(); column_idx++) {
+            if (column_idx > 0) {
+                out << ", ";
+            }
+            size_t start = m->rows() * column_idx;
+            size_t end = m->rows() * (column_idx + 1);
+            if (!emit_vector(m->ColumnType(), start, end)) {
+                return false;
+            }
+        }
+        return true;
+    };
+    return Switch(
+        constant.Type(),                                                                   //
+        [&](const sem::Bool*) { return emit_bool(0); },                                    //
+        [&](const sem::F32*) { return emit_f32(0); },                                      //
+        [&](const sem::I32*) { return emit_i32(0); },                                      //
+        [&](const sem::U32*) { return emit_u32(0); },                                      //
+        [&](const sem::Vector* v) { return emit_vector(v, 0, constant.ElementCount()); },  //
+        [&](const sem::Matrix* m) { return emit_matrix(m); },                              //
+        [&](Default) {
+            diagnostics_.add_error(
+                diag::System::Writer,
+                "unhandled constant type: " + builder_.FriendlyName(constant.Type()));
+            return false;
+        });
+}
+
 bool GeneratorImpl::EmitLiteral(std::ostream& out, const ast::LiteralExpression* lit) {
     return Switch(
         lit,
@@ -2183,15 +2303,7 @@
             return true;
         },
         [&](const ast::FloatLiteralExpression* l) {
-            auto f32 = static_cast<float>(l->value);
-            if (std::isinf(f32)) {
-                out << (l->value >= 0 ? "uintBitsToFloat(0x7f800000u)"
-                                      : "uintBitsToFloat(0xff800000u)");
-            } else if (std::isnan(l->value)) {
-                out << "uintBitsToFloat(0x7fc00000u)";
-            } else {
-                out << FloatToString(f32) << "f";
-            }
+            PrintF32(out, static_cast<float>(l->value));
             return true;
         },
         [&](const ast::IntLiteralExpression* l) {
diff --git a/src/tint/writer/glsl/generator_impl.h b/src/tint/writer/glsl/generator_impl.h
index 72def34..bcf84b0 100644
--- a/src/tint/writer/glsl/generator_impl.h
+++ b/src/tint/writer/glsl/generator_impl.h
@@ -42,6 +42,7 @@
 // Forward declarations
 namespace tint::sem {
 class Call;
+class Constant;
 class Builtin;
 class TypeConstructor;
 class TypeConversion;
@@ -174,14 +175,6 @@
     /// @param builtin the semantic information for the barrier builtin
     /// @returns true if the call expression is emitted
     bool EmitBarrierCall(std::ostream& out, const sem::Builtin* builtin);
-    /// Handles generating an atomic intrinsic call for a storage buffer variable
-    /// @param out the output of the expression stream
-    /// @param expr the call expression
-    /// @param intrinsic the atomic intrinsic
-    /// @returns true if the call expression is emitted
-    bool EmitStorageAtomicCall(std::ostream& out,
-                               const ast::CallExpression* expr,
-                               const transform::DecomposeMemoryAccess::Intrinsic* intrinsic);
     /// Handles generating an atomic builtin call for a workgroup variable
     /// @param out the output of the expression stream
     /// @param expr the call expression
@@ -346,6 +339,11 @@
     /// @param stmt the statement to emit
     /// @returns true if the statement was successfully emitted
     bool EmitIf(const ast::IfStatement* stmt);
+    /// Handles a constant value
+    /// @param out the output stream
+    /// @param constant the constant value to emit
+    /// @returns true if the constant value was successfully emitted
+    bool EmitConstant(std::ostream& out, const sem::Constant& constant);
     /// Handles a literal
     /// @param out the output stream
     /// @param lit the literal to emit
diff --git a/src/tint/writer/glsl/generator_impl_binary_test.cc b/src/tint/writer/glsl/generator_impl_binary_test.cc
index 07456d9..0d019a9 100644
--- a/src/tint/writer/glsl/generator_impl_binary_test.cc
+++ b/src/tint/writer/glsl/generator_impl_binary_test.cc
@@ -134,9 +134,7 @@
 
     std::stringstream out;
     EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-    EXPECT_EQ(out.str(),
-              "(vec3(1.0f, 1.0f, 1.0f) * "
-              "1.0f)");
+    EXPECT_EQ(out.str(), "(vec3(1.0f) * 1.0f)");
 }
 
 TEST_F(GlslGeneratorImplTest_Binary, Multiply_ScalarVector) {
@@ -151,9 +149,7 @@
 
     std::stringstream out;
     EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-    EXPECT_EQ(out.str(),
-              "(1.0f * vec3(1.0f, 1.0f, "
-              "1.0f))");
+    EXPECT_EQ(out.str(), "(1.0f * vec3(1.0f))");
 }
 
 TEST_F(GlslGeneratorImplTest_Binary, Multiply_MatrixScalar) {
@@ -198,7 +194,7 @@
 
     std::stringstream out;
     EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-    EXPECT_EQ(out.str(), "(mat * vec3(1.0f, 1.0f, 1.0f))");
+    EXPECT_EQ(out.str(), "(mat * vec3(1.0f))");
 }
 
 TEST_F(GlslGeneratorImplTest_Binary, Multiply_VectorMatrix) {
@@ -213,7 +209,7 @@
 
     std::stringstream out;
     EXPECT_TRUE(gen.EmitExpression(out, expr)) << gen.error();
-    EXPECT_EQ(out.str(), "(vec3(1.0f, 1.0f, 1.0f) * mat)");
+    EXPECT_EQ(out.str(), "(vec3(1.0f) * mat)");
 }
 
 TEST_F(GlslGeneratorImplTest_Binary, Multiply_MatrixMatrix) {
diff --git a/src/tint/writer/glsl/generator_impl_builtin_test.cc b/src/tint/writer/glsl/generator_impl_builtin_test.cc
index 7942ed5..6f2c555 100644
--- a/src/tint/writer/glsl/generator_impl_builtin_test.cc
+++ b/src/tint/writer/glsl/generator_impl_builtin_test.cc
@@ -334,7 +334,7 @@
 
 
 void test_function() {
-  tint_modf(vec3(0.0f, 0.0f, 0.0f));
+  tint_modf(vec3(0.0f));
 }
 
 layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
@@ -394,7 +394,7 @@
 
 
 void test_function() {
-  tint_frexp(vec3(0.0f, 0.0f, 0.0f));
+  tint_frexp(vec3(0.0f));
 }
 
 layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
diff --git a/src/tint/writer/glsl/generator_impl_builtin_texture_test.cc b/src/tint/writer/glsl/generator_impl_builtin_texture_test.cc
index cb111ee..42db16b 100644
--- a/src/tint/writer/glsl/generator_impl_builtin_texture_test.cc
+++ b/src/tint/writer/glsl/generator_impl_builtin_texture_test.cc
@@ -193,7 +193,7 @@
         case ValidTextureOverload::kSampleGrad2dF32:
             return R"(textureGrad(tint_symbol_sampler, vec2(1.0f, 2.0f), vec2(3.0f, 4.0f), vec2(5.0f, 6.0f));)";
         case ValidTextureOverload::kSampleGrad2dOffsetF32:
-            return R"(textureGradOffset(tint_symbol_sampler, vec2(1.0f, 2.0f), vec2(3.0f, 4.0f), vec2(5.0f, 6.0f), ivec2(7, 7));)";
+            return R"(textureGradOffset(tint_symbol_sampler, vec2(1.0f, 2.0f), vec2(3.0f, 4.0f), vec2(5.0f, 6.0f), ivec2(7));)";
         case ValidTextureOverload::kSampleGrad2dArrayF32:
             return R"(textureGrad(tint_symbol_sampler, vec3(1.0f, 2.0f, float(3)), vec2(4.0f, 5.0f), vec2(6.0f, 7.0f));)";
         case ValidTextureOverload::kSampleGrad2dArrayOffsetF32:
diff --git a/src/tint/writer/glsl/generator_impl_cast_test.cc b/src/tint/writer/glsl/generator_impl_cast_test.cc
index 3666782..c4f9c05 100644
--- a/src/tint/writer/glsl/generator_impl_cast_test.cc
+++ b/src/tint/writer/glsl/generator_impl_cast_test.cc
@@ -29,7 +29,7 @@
 
     std::stringstream out;
     ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error();
-    EXPECT_EQ(out.str(), "float(1)");
+    EXPECT_EQ(out.str(), "1.0f");
 }
 
 TEST_F(GlslGeneratorImplTest_Cast, EmitExpression_Cast_Vector) {
@@ -40,7 +40,7 @@
 
     std::stringstream out;
     ASSERT_TRUE(gen.EmitExpression(out, cast)) << gen.error();
-    EXPECT_EQ(out.str(), "vec3(ivec3(1, 2, 3))");
+    EXPECT_EQ(out.str(), "vec3(1.0f, 2.0f, 3.0f)");
 }
 
 }  // namespace
diff --git a/src/tint/writer/glsl/generator_impl_constructor_test.cc b/src/tint/writer/glsl/generator_impl_constructor_test.cc
index 4fa3c0a..e70ecaf 100644
--- a/src/tint/writer/glsl/generator_impl_constructor_test.cc
+++ b/src/tint/writer/glsl/generator_impl_constructor_test.cc
@@ -67,7 +67,7 @@
     GeneratorImpl& gen = Build();
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
-    EXPECT_THAT(gen.result(), HasSubstr("float(-0.000012f)"));
+    EXPECT_THAT(gen.result(), HasSubstr("-0.000012f"));
 }
 
 TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Bool) {
@@ -76,7 +76,7 @@
     GeneratorImpl& gen = Build();
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
-    EXPECT_THAT(gen.result(), HasSubstr("bool(true)"));
+    EXPECT_THAT(gen.result(), HasSubstr("true"));
 }
 
 TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Int) {
@@ -85,7 +85,7 @@
     GeneratorImpl& gen = Build();
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
-    EXPECT_THAT(gen.result(), HasSubstr("int(-12345)"));
+    EXPECT_THAT(gen.result(), HasSubstr("-12345"));
 }
 
 TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Uint) {
@@ -94,7 +94,7 @@
     GeneratorImpl& gen = Build();
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
-    EXPECT_THAT(gen.result(), HasSubstr("uint(12345u)"));
+    EXPECT_THAT(gen.result(), HasSubstr("12345u"));
 }
 
 TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Vec) {
@@ -112,7 +112,7 @@
     GeneratorImpl& gen = Build();
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
-    EXPECT_THAT(gen.result(), HasSubstr("vec3(0.0f, 0.0f, 0.0f)"));
+    EXPECT_THAT(gen.result(), HasSubstr("vec3(0.0f)"));
 }
 
 TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Vec_SingleScalar_Float) {
@@ -168,7 +168,7 @@
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
 
-    EXPECT_THAT(gen.result(), HasSubstr("mat2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f)"));
+    EXPECT_THAT(gen.result(), HasSubstr("mat2x3(vec3(0.0f), vec3(0.0f)"));
 }
 
 TEST_F(GlslGeneratorImplTest_Constructor, EmitConstructor_Type_Array) {
diff --git a/src/tint/writer/glsl/generator_impl_function_test.cc b/src/tint/writer/glsl/generator_impl_function_test.cc
index f352415..201e757 100644
--- a/src/tint/writer/glsl/generator_impl_function_test.cc
+++ b/src/tint/writer/glsl/generator_impl_function_test.cc
@@ -222,7 +222,7 @@
 };
 
 Interface vert_main() {
-  Interface tint_symbol = Interface(vec4(0.0f, 0.0f, 0.0f, 0.0f), 0.5f, 0.25f);
+  Interface tint_symbol = Interface(vec4(0.0f), 0.5f, 0.25f);
   return tint_symbol;
 }
 
@@ -789,9 +789,9 @@
     ASSERT_TRUE(gen.Generate()) << gen.error();
     EXPECT_EQ(gen.result(), R"(#version 310 es
 
-const int width = int(2);
-const int height = int(3);
-const int depth = int(4);
+const int width = 2;
+const int height = 3;
+const int depth = 4;
 layout(local_size_x = 2, local_size_y = 3, local_size_z = 4) in;
 void main() {
   return;
@@ -816,15 +816,15 @@
     EXPECT_EQ(gen.result(), R"(#version 310 es
 
 #ifndef WGSL_SPEC_CONSTANT_7
-#define WGSL_SPEC_CONSTANT_7 int(2)
+#define WGSL_SPEC_CONSTANT_7 2
 #endif
 const int width = WGSL_SPEC_CONSTANT_7;
 #ifndef WGSL_SPEC_CONSTANT_8
-#define WGSL_SPEC_CONSTANT_8 int(3)
+#define WGSL_SPEC_CONSTANT_8 3
 #endif
 const int height = WGSL_SPEC_CONSTANT_8;
 #ifndef WGSL_SPEC_CONSTANT_9
-#define WGSL_SPEC_CONSTANT_9 int(4)
+#define WGSL_SPEC_CONSTANT_9 4
 #endif
 const int depth = WGSL_SPEC_CONSTANT_9;
 layout(local_size_x = WGSL_SPEC_CONSTANT_7, local_size_y = WGSL_SPEC_CONSTANT_8, local_size_z = WGSL_SPEC_CONSTANT_9) in;
diff --git a/src/tint/writer/glsl/generator_impl_member_accessor_test.cc b/src/tint/writer/glsl/generator_impl_member_accessor_test.cc
index 935df75..4f7fdb1 100644
--- a/src/tint/writer/glsl/generator_impl_member_accessor_test.cc
+++ b/src/tint/writer/glsl/generator_impl_member_accessor_test.cc
@@ -291,7 +291,7 @@
   mat2x3 b;
 } data;
 void tint_symbol() {
-  data.b = mat2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+  data.b = mat2x3(vec3(0.0f), vec3(0.0f));
 }
 
 void main() {
diff --git a/src/tint/writer/glsl/generator_impl_variable_decl_statement_test.cc b/src/tint/writer/glsl/generator_impl_variable_decl_statement_test.cc
index d293dba..5d95bc6 100644
--- a/src/tint/writer/glsl/generator_impl_variable_decl_statement_test.cc
+++ b/src/tint/writer/glsl/generator_impl_variable_decl_statement_test.cc
@@ -98,7 +98,7 @@
     GeneratorImpl& gen = Build();
 
     ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
-    EXPECT_EQ(gen.result(), R"(vec3 a = vec3(0.0f, 0.0f, 0.0f);
+    EXPECT_EQ(gen.result(), R"(vec3 a = vec3(0.0f);
 )");
 }
 
@@ -112,7 +112,7 @@
 
     ASSERT_TRUE(gen.EmitStatement(stmt)) << gen.error();
     EXPECT_EQ(gen.result(),
-              R"(mat2x3 a = mat2x3(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f);
+              R"(mat2x3 a = mat2x3(vec3(0.0f), vec3(0.0f));
 )");
 }
 
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index d6a5fa7..a2cac0f 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -968,7 +968,10 @@
             case ast::StorageClass::kUniform:
                 return EmitUniformBufferAccess(out, expr, intrinsic);
             case ast::StorageClass::kStorage:
-                return EmitStorageBufferAccess(out, expr, intrinsic);
+                if (!intrinsic->IsAtomic()) {
+                    return EmitStorageBufferAccess(out, expr, intrinsic);
+                }
+                break;
             default:
                 TINT_UNREACHABLE(Writer, diagnostics_)
                     << "unsupported DecomposeMemoryAccess::Intrinsic storage class:"
@@ -1445,19 +1448,10 @@
                 << static_cast<int>(intrinsic->type);
             return false;
         }
-
-        case Op::kAtomicLoad:
-        case Op::kAtomicStore:
-        case Op::kAtomicAdd:
-        case Op::kAtomicSub:
-        case Op::kAtomicMax:
-        case Op::kAtomicMin:
-        case Op::kAtomicAnd:
-        case Op::kAtomicOr:
-        case Op::kAtomicXor:
-        case Op::kAtomicExchange:
-        case Op::kAtomicCompareExchangeWeak:
-            return EmitStorageAtomicCall(out, expr, intrinsic);
+        default:
+            // Break out to error case below/
+            // Note that atomic intrinsics are generated as functions.
+            break;
     }
 
     TINT_UNREACHABLE(Writer, diagnostics_)
@@ -1465,32 +1459,127 @@
     return false;
 }
 
-bool GeneratorImpl::EmitStorageAtomicCall(
-    std::ostream& out,
-    const ast::CallExpression* expr,
+bool GeneratorImpl::EmitStorageAtomicIntrinsic(
+    const ast::Function* func,
     const transform::DecomposeMemoryAccess::Intrinsic* intrinsic) {
     using Op = transform::DecomposeMemoryAccess::Intrinsic::Op;
 
-    auto* result_ty = TypeOf(expr);
+    const sem::Function* sem_func = builder_.Sem().Get(func);
+    auto* result_ty = sem_func->ReturnType();
+    const auto& params = sem_func->Parameters();
+    const auto name = builder_.Symbols().NameFor(func->symbol);
+    auto& buf = *current_buffer_;
 
-    auto& buf = helpers_;
+    auto rmw = [&](const char* hlsl) -> bool {
+        {
+            auto fn = line(&buf);
+            if (!EmitTypeAndName(fn, result_ty, ast::StorageClass::kNone, ast::Access::kUndefined,
+                                 name)) {
+                return false;
+            }
+            fn << "(RWByteAddressBuffer buffer, uint offset, ";
+            if (!EmitTypeAndName(fn, result_ty, ast::StorageClass::kNone, ast::Access::kUndefined,
+                                 "value")) {
+                return false;
+            }
+            fn << ") {";
+        }
 
-    // generate_helper() generates a helper function that translates the
-    // DecomposeMemoryAccess::Intrinsic call into the corresponding HLSL
-    // atomic intrinsic function.
-    auto generate_helper = [&]() -> std::string {
-        auto rmw = [&](const char* wgsl, const char* hlsl) -> std::string {
-            auto name = UniqueIdentifier(wgsl);
+        buf.IncrementIndent();
+        TINT_DEFER({
+            buf.DecrementIndent();
+            line(&buf) << "}";
+            line(&buf);
+        });
+
+        {
+            auto l = line(&buf);
+            if (!EmitTypeAndName(l, result_ty, ast::StorageClass::kNone, ast::Access::kUndefined,
+                                 "original_value")) {
+                return false;
+            }
+            l << " = 0;";
+        }
+        {
+            auto l = line(&buf);
+            l << "buffer." << hlsl << "(offset, ";
+            if (intrinsic->op == Op::kAtomicSub) {
+                l << "-";
+            }
+            l << "value, original_value);";
+        }
+        line(&buf) << "return original_value;";
+        return true;
+    };
+
+    switch (intrinsic->op) {
+        case Op::kAtomicAdd:
+            return rmw("InterlockedAdd");
+
+        case Op::kAtomicSub:
+            // Use add with the operand negated.
+            return rmw("InterlockedAdd");
+
+        case Op::kAtomicMax:
+            return rmw("InterlockedMax");
+
+        case Op::kAtomicMin:
+            return rmw("InterlockedMin");
+
+        case Op::kAtomicAnd:
+            return rmw("InterlockedAnd");
+
+        case Op::kAtomicOr:
+            return rmw("InterlockedOr");
+
+        case Op::kAtomicXor:
+            return rmw("InterlockedXor");
+
+        case Op::kAtomicExchange:
+            return rmw("InterlockedExchange");
+
+        case Op::kAtomicLoad: {
+            // HLSL does not have an InterlockedLoad, so we emulate it with
+            // InterlockedOr using 0 as the OR value
             {
                 auto fn = line(&buf);
                 if (!EmitTypeAndName(fn, result_ty, ast::StorageClass::kNone,
                                      ast::Access::kUndefined, name)) {
-                    return "";
+                    return false;
                 }
-                fn << "(RWByteAddressBuffer buffer, uint offset, ";
-                if (!EmitTypeAndName(fn, result_ty, ast::StorageClass::kNone,
+                fn << "(RWByteAddressBuffer buffer, uint offset) {";
+            }
+
+            buf.IncrementIndent();
+            TINT_DEFER({
+                buf.DecrementIndent();
+                line(&buf) << "}";
+                line(&buf);
+            });
+
+            {
+                auto l = line(&buf);
+                if (!EmitTypeAndName(l, result_ty, ast::StorageClass::kNone,
                                      ast::Access::kUndefined, "value")) {
-                    return "";
+                    return false;
+                }
+                l << " = 0;";
+            }
+
+            line(&buf) << "buffer.InterlockedOr(offset, 0, value);";
+            line(&buf) << "return value;";
+            return true;
+        }
+        case Op::kAtomicStore: {
+            // HLSL does not have an InterlockedStore, so we emulate it with
+            // InterlockedExchange and discard the returned value
+            auto* value_ty = params[2]->Type()->UnwrapRef();
+            {
+                auto fn = line(&buf);
+                fn << "void " << name << "(RWByteAddressBuffer buffer, uint offset, ";
+                if (!EmitTypeAndName(fn, value_ty, ast::StorageClass::kNone,
+                                     ast::Access::kUndefined, "value")) {
+                    return false;
                 }
                 fn << ") {";
             }
@@ -1504,191 +1593,73 @@
 
             {
                 auto l = line(&buf);
-                if (!EmitTypeAndName(l, result_ty, ast::StorageClass::kNone,
-                                     ast::Access::kUndefined, "original_value")) {
-                    return "";
+                if (!EmitTypeAndName(l, value_ty, ast::StorageClass::kNone, ast::Access::kUndefined,
+                                     "ignored")) {
+                    return false;
                 }
-                l << " = 0;";
+                l << ";";
             }
+            line(&buf) << "buffer.InterlockedExchange(offset, value, ignored);";
+            return true;
+        }
+        case Op::kAtomicCompareExchangeWeak: {
+            // NOTE: We don't need to emit the return type struct here as DecomposeMemoryAccess
+            // already added it to the AST, and it should have already been emitted by now.
+            auto* value_ty = params[2]->Type()->UnwrapRef();
             {
+                auto fn = line(&buf);
+                if (!EmitTypeAndName(fn, result_ty, ast::StorageClass::kNone,
+                                     ast::Access::kUndefined, name)) {
+                    return false;
+                }
+                fn << "(RWByteAddressBuffer buffer, uint offset, ";
+                if (!EmitTypeAndName(fn, value_ty, ast::StorageClass::kNone,
+                                     ast::Access::kUndefined, "compare")) {
+                    return false;
+                }
+                fn << ", ";
+                if (!EmitTypeAndName(fn, value_ty, ast::StorageClass::kNone,
+                                     ast::Access::kUndefined, "value")) {
+                    return false;
+                }
+                fn << ") {";
+            }
+
+            buf.IncrementIndent();
+            TINT_DEFER({
+                buf.DecrementIndent();
+                line(&buf) << "}";
+                line(&buf);
+            });
+
+            {  // T result = {0};
                 auto l = line(&buf);
-                l << "buffer." << hlsl << "(offset, ";
-                if (intrinsic->op == Op::kAtomicSub) {
-                    l << "-";
+                if (!EmitTypeAndName(l, result_ty, ast::StorageClass::kNone,
+                                     ast::Access::kUndefined, "result")) {
+                    return false;
                 }
-                l << "value, original_value);";
+                l << "=";
+                if (!EmitZeroValue(l, result_ty)) {
+                    return false;
+                }
+                l << ";";
             }
-            line(&buf) << "return original_value;";
-            return name;
-        };
 
-        switch (intrinsic->op) {
-            case Op::kAtomicAdd:
-                return rmw("atomicAdd", "InterlockedAdd");
+            line(&buf) << "buffer.InterlockedCompareExchange(offset, compare, value, "
+                          "result.old_value);";
+            line(&buf) << "result.exchanged = result.old_value == compare;";
+            line(&buf) << "return result;";
 
-            case Op::kAtomicSub:
-                // Use add with the operand negated.
-                return rmw("atomicSub", "InterlockedAdd");
-
-            case Op::kAtomicMax:
-                return rmw("atomicMax", "InterlockedMax");
-
-            case Op::kAtomicMin:
-                return rmw("atomicMin", "InterlockedMin");
-
-            case Op::kAtomicAnd:
-                return rmw("atomicAnd", "InterlockedAnd");
-
-            case Op::kAtomicOr:
-                return rmw("atomicOr", "InterlockedOr");
-
-            case Op::kAtomicXor:
-                return rmw("atomicXor", "InterlockedXor");
-
-            case Op::kAtomicExchange:
-                return rmw("atomicExchange", "InterlockedExchange");
-
-            case Op::kAtomicLoad: {
-                // HLSL does not have an InterlockedLoad, so we emulate it with
-                // InterlockedOr using 0 as the OR value
-                auto name = UniqueIdentifier("atomicLoad");
-                {
-                    auto fn = line(&buf);
-                    if (!EmitTypeAndName(fn, result_ty, ast::StorageClass::kNone,
-                                         ast::Access::kUndefined, name)) {
-                        return "";
-                    }
-                    fn << "(RWByteAddressBuffer buffer, uint offset) {";
-                }
-
-                buf.IncrementIndent();
-                TINT_DEFER({
-                    buf.DecrementIndent();
-                    line(&buf) << "}";
-                    line(&buf);
-                });
-
-                {
-                    auto l = line(&buf);
-                    if (!EmitTypeAndName(l, result_ty, ast::StorageClass::kNone,
-                                         ast::Access::kUndefined, "value")) {
-                        return "";
-                    }
-                    l << " = 0;";
-                }
-
-                line(&buf) << "buffer.InterlockedOr(offset, 0, value);";
-                line(&buf) << "return value;";
-                return name;
-            }
-            case Op::kAtomicStore: {
-                // HLSL does not have an InterlockedStore, so we emulate it with
-                // InterlockedExchange and discard the returned value
-                auto* value_ty = TypeOf(expr->args[2])->UnwrapRef();
-                auto name = UniqueIdentifier("atomicStore");
-                {
-                    auto fn = line(&buf);
-                    fn << "void " << name << "(RWByteAddressBuffer buffer, uint offset, ";
-                    if (!EmitTypeAndName(fn, value_ty, ast::StorageClass::kNone,
-                                         ast::Access::kUndefined, "value")) {
-                        return "";
-                    }
-                    fn << ") {";
-                }
-
-                buf.IncrementIndent();
-                TINT_DEFER({
-                    buf.DecrementIndent();
-                    line(&buf) << "}";
-                    line(&buf);
-                });
-
-                {
-                    auto l = line(&buf);
-                    if (!EmitTypeAndName(l, value_ty, ast::StorageClass::kNone,
-                                         ast::Access::kUndefined, "ignored")) {
-                        return "";
-                    }
-                    l << ";";
-                }
-                line(&buf) << "buffer.InterlockedExchange(offset, value, ignored);";
-                return name;
-            }
-            case Op::kAtomicCompareExchangeWeak: {
-                auto* value_ty = TypeOf(expr->args[2])->UnwrapRef();
-
-                auto name = UniqueIdentifier("atomicCompareExchangeWeak");
-                {
-                    auto fn = line(&buf);
-                    if (!EmitTypeAndName(fn, result_ty, ast::StorageClass::kNone,
-                                         ast::Access::kUndefined, name)) {
-                        return "";
-                    }
-                    fn << "(RWByteAddressBuffer buffer, uint offset, ";
-                    if (!EmitTypeAndName(fn, value_ty, ast::StorageClass::kNone,
-                                         ast::Access::kUndefined, "compare")) {
-                        return "";
-                    }
-                    fn << ", ";
-                    if (!EmitTypeAndName(fn, value_ty, ast::StorageClass::kNone,
-                                         ast::Access::kUndefined, "value")) {
-                        return "";
-                    }
-                    fn << ") {";
-                }
-
-                buf.IncrementIndent();
-                TINT_DEFER({
-                    buf.DecrementIndent();
-                    line(&buf) << "}";
-                    line(&buf);
-                });
-
-                {  // T result = {0, 0};
-                    auto l = line(&buf);
-                    if (!EmitTypeAndName(l, result_ty, ast::StorageClass::kNone,
-                                         ast::Access::kUndefined, "result")) {
-                        return "";
-                    }
-                    l << " = {0, 0};";
-                }
-                line(&buf) << "buffer.InterlockedCompareExchange(offset, compare, "
-                              "value, result.x);";
-                line(&buf) << "result.y = result.x == compare;";
-                line(&buf) << "return result;";
-                return name;
-            }
-            default:
-                break;
+            return true;
         }
-        TINT_UNREACHABLE(Writer, diagnostics_)
-            << "unsupported atomic DecomposeMemoryAccess::Intrinsic::Op: "
-            << static_cast<int>(intrinsic->op);
-        return "";
-    };
-
-    auto func = utils::GetOrCreate(dma_intrinsics_, DMAIntrinsic{intrinsic->op, intrinsic->type},
-                                   generate_helper);
-    if (func.empty()) {
-        return false;
+        default:
+            break;
     }
 
-    out << func;
-    {
-        ScopedParen sp(out);
-        bool first = true;
-        for (auto* arg : expr->args) {
-            if (!first) {
-                out << ", ";
-            }
-            first = false;
-            if (!EmitExpression(out, arg)) {
-                return false;
-            }
-        }
-    }
-
-    return true;
+    TINT_UNREACHABLE(Writer, diagnostics_)
+        << "unsupported atomic DecomposeMemoryAccess::Intrinsic::Op: "
+        << static_cast<int>(intrinsic->op);
+    return false;
 }
 
 bool GeneratorImpl::EmitWorkgroupAtomicCall(std::ostream& out,
@@ -1788,6 +1759,12 @@
             return true;
         }
         case sem::BuiltinType::kAtomicCompareExchangeWeak: {
+            // Emit the builtin return type unique to this overload. This does not
+            // exist in the AST, so it will not be generated in Generate().
+            if (!EmitStructType(&helpers_, builtin->ReturnType()->As<sem::Struct>())) {
+                return false;
+            }
+
             auto* dest = expr->args[0];
             auto* compare_value = expr->args[1];
             auto* value = expr->args[2];
@@ -1807,7 +1784,7 @@
                 pre << ";";
             }
 
-            {  // InterlockedCompareExchange(dst, compare, value, result.x);
+            {  // InterlockedCompareExchange(dst, compare, value, result.old_value);
                 auto pre = line();
                 pre << "InterlockedCompareExchange";
                 {
@@ -1819,14 +1796,13 @@
                     if (!EmitExpression(pre, value)) {
                         return false;
                     }
-                    pre << ", " << result << ".x";
+                    pre << ", " << result << ".old_value";
                 }
                 pre << ";";
             }
 
-            {  // result.y = result.x == compare;
-                line() << result << ".y = " << result << ".x == " << compare << ";";
-            }
+            // result.exchanged = result.old_value == compare;
+            line() << result << ".exchanged = " << result << ".old_value == " << compare << ";";
 
             out << result;
             return true;
@@ -2740,6 +2716,17 @@
 bool GeneratorImpl::EmitFunction(const ast::Function* func) {
     auto* sem = builder_.Sem().Get(func);
 
+    // Emit storage atomic helpers
+    if (auto* intrinsic =
+            ast::GetAttribute<transform::DecomposeMemoryAccess::Intrinsic>(func->attributes)) {
+        if (intrinsic->storage_class == ast::StorageClass::kStorage && intrinsic->IsAtomic()) {
+            if (!EmitStorageAtomicIntrinsic(func, intrinsic)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     if (ast::HasAttribute<ast::InternalAttribute>(func->attributes)) {
         // An internal function. Do not emit.
         return true;
@@ -3755,13 +3742,9 @@
         ScopedIndent si(b);
         for (auto* mem : str->Members()) {
             auto mem_name = builder_.Symbols().NameFor(mem->Name());
-
             auto* ty = mem->Type();
-
             auto out = line(b);
-
             std::string pre, post;
-
             if (auto* decl = mem->Declaration()) {
                 for (auto* attr : decl->attributes) {
                     if (auto* location = attr->As<ast::LocationAttribute>()) {
@@ -3826,7 +3809,6 @@
     }
 
     line(b) << "};";
-
     return true;
 }
 
diff --git a/src/tint/writer/hlsl/generator_impl.h b/src/tint/writer/hlsl/generator_impl.h
index 86bbd7d..0e8ca4c 100644
--- a/src/tint/writer/hlsl/generator_impl.h
+++ b/src/tint/writer/hlsl/generator_impl.h
@@ -187,6 +187,12 @@
     bool EmitStorageAtomicCall(std::ostream& out,
                                const ast::CallExpression* expr,
                                const transform::DecomposeMemoryAccess::Intrinsic* intrinsic);
+    /// Handles generating the helper function for the atomic intrinsic function
+    /// @param func the function
+    /// @param intrinsic the atomic intrinsic
+    /// @returns true if the function is emitted
+    bool EmitStorageAtomicIntrinsic(const ast::Function* func,
+                                    const transform::DecomposeMemoryAccess::Intrinsic* intrinsic);
     /// Handles generating an atomic intrinsic call for a workgroup variable
     /// @param out the output of the expression stream
     /// @param expr the call expression
@@ -511,7 +517,6 @@
 
     TextBuffer helpers_;  // Helper functions emitted at the top of the output
     std::function<bool()> emit_continuing_;
-    std::unordered_map<DMAIntrinsic, std::string, DMAIntrinsic::Hasher> dma_intrinsics_;
     std::unordered_map<const sem::Matrix*, std::string> matrix_scalar_ctors_;
     std::unordered_map<const sem::Builtin*, std::string> builtins_;
     std::unordered_map<const sem::Struct*, std::string> structure_builders_;
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index 54d9164..578e78d 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -806,6 +806,12 @@
             return call("atomic_exchange_explicit", true);
 
         case sem::BuiltinType::kAtomicCompareExchangeWeak: {
+            // Emit the builtin return type unique to this overload. This does not
+            // exist in the AST, so it will not be generated in Generate().
+            if (!EmitStructType(&helpers_, builtin->ReturnType()->As<sem::Struct>())) {
+                return false;
+            }
+
             auto* ptr_ty = TypeOf(expr->args[0])->UnwrapRef()->As<sem::Pointer>();
             auto sc = ptr_ty->StorageClass();
 
@@ -816,7 +822,8 @@
                 line(&buf) << "template <typename A, typename T>";
                 {
                     auto f = line(&buf);
-                    f << "vec<T, 2> " << name << "(";
+                    auto str_name = StructName(builtin->ReturnType()->As<sem::Struct>());
+                    f << str_name << " " << name << "(";
                     if (!EmitStorageClass(f, sc)) {
                         return "";
                     }
@@ -830,12 +837,12 @@
                     line(&buf);
                 });
 
-                line(&buf) << "T prev_value = compare;";
-                line(&buf) << "bool matched = "
+                line(&buf) << "T old_value = compare;";
+                line(&buf) << "bool exchanged = "
                               "atomic_compare_exchange_weak_explicit(atomic, "
-                              "&prev_value, value, memory_order_relaxed, "
+                              "&old_value, value, memory_order_relaxed, "
                               "memory_order_relaxed);";
-                line(&buf) << "return {prev_value, matched};";
+                line(&buf) << "return {old_value, exchanged};";
                 return name;
             });
 
diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc
index e2d1fac..34d550b 100644
--- a/src/tint/writer/spirv/builder.cc
+++ b/src/tint/writer/spirv/builder.cc
@@ -3161,7 +3161,7 @@
                 return false;
             }
 
-            auto* value_sem_type = TypeOf(call->Arguments()[2]->Declaration());
+            auto* value_sem_type = call->Target()->Signature().parameters[2]->Type();
 
             auto value_type = GenerateTypeIfNeeded(value_sem_type);
             if (value_type == 0) {
@@ -3201,42 +3201,12 @@
                 return false;
             }
 
-            // zero := T(0)
-            // one := T(1)
-            uint32_t zero = 0;
-            uint32_t one = 0;
-            if (value_sem_type->Is<sem::I32>()) {
-                zero = GenerateConstantIfNeeded(ScalarConstant::I32(0u));
-                one = GenerateConstantIfNeeded(ScalarConstant::I32(1u));
-            } else if (value_sem_type->Is<sem::U32>()) {
-                zero = GenerateConstantIfNeeded(ScalarConstant::U32(0u));
-                one = GenerateConstantIfNeeded(ScalarConstant::U32(1u));
-            } else {
-                TINT_UNREACHABLE(Writer, builder_.Diagnostics())
-                    << "unsupported atomic type " << value_sem_type->TypeInfo().name;
-            }
-            if (zero == 0 || one == 0) {
-                return false;
-            }
-
-            // xchg_success := values_equal ? one : zero
-            auto xchg_success = result_op();
-            if (!push_function_inst(spv::Op::OpSelect, {
-                                                           Operand(value_type),
-                                                           xchg_success,
-                                                           values_equal,
-                                                           Operand(one),
-                                                           Operand(zero),
-                                                       })) {
-                return false;
-            }
-
-            // result := vec2<T>(original_value, xchg_success)
+            // result := __atomic_compare_exchange_result<T>(original_value, values_equal)
             return push_function_inst(spv::Op::OpCompositeConstruct, {
                                                                          result_type,
                                                                          result_id,
                                                                          original_value,
-                                                                         xchg_success,
+                                                                         values_equal,
                                                                      });
         }
         default:
diff --git a/src/tint/writer/spirv/builder_builtin_test.cc b/src/tint/writer/spirv/builder_builtin_test.cc
index 59a567a..7340074 100644
--- a/src/tint/writer/spirv/builder_builtin_test.cc
+++ b/src/tint/writer/spirv/builder_builtin_test.cc
@@ -2018,15 +2018,15 @@
 
 TEST_F(BuiltinBuilderTest, Call_AtomicCompareExchangeWeak) {
     // struct S {
-    //   u : atomic<u32>;
-    //   i : atomic<i32>;
+    //   u : atomic<u32>,
+    //   i : atomic<i32>,
     // }
     //
     // @binding(1) @group(2) var<storage, read_write> b : S;
     //
     // fn a_func() {
-    //   let u : vec2<u32> = atomicCompareExchangeWeak(&b.u, 10u);
-    //   let i : vec2<i32> = atomicCompareExchangeWeak(&b.i, 10);
+    //   let u = atomicCompareExchangeWeak(&b.u, 10u, 20u);
+    //   let i = atomicCompareExchangeWeak(&b.i, 10, 10);
     // }
     auto* s = Structure("S", {
                                  Member("u", ty.atomic<u32>()),
@@ -2040,10 +2040,10 @@
 
     Func("a_func", {}, ty.void_(),
          ast::StatementList{
-             Decl(Let("u", ty.vec2<u32>(),
+             Decl(Let("u", nullptr,
                       Call("atomicCompareExchangeWeak", AddressOf(MemberAccessor("b", "u")), 10_u,
                            20_u))),
-             Decl(Let("i", ty.vec2<i32>(),
+             Decl(Let("i", nullptr,
                       Call("atomicCompareExchangeWeak", AddressOf(MemberAccessor("b", "i")), 10_i,
                            20_i))),
          },
@@ -2062,33 +2062,29 @@
 %1 = OpVariable %2 StorageBuffer
 %7 = OpTypeVoid
 %6 = OpTypeFunction %7
-%11 = OpTypeVector %4 2
-%12 = OpConstant %4 1
-%13 = OpConstant %4 0
-%15 = OpTypePointer StorageBuffer %4
-%17 = OpConstant %4 20
-%18 = OpConstant %4 10
-%19 = OpTypeBool
-%24 = OpTypeVector %5 2
-%26 = OpTypePointer StorageBuffer %5
-%28 = OpConstant %5 20
-%29 = OpConstant %5 10
-%32 = OpConstant %5 0
-%33 = OpConstant %5 1
+%12 = OpTypeBool
+%11 = OpTypeStruct %4 %12
+%13 = OpConstant %4 1
+%14 = OpConstant %4 0
+%16 = OpTypePointer StorageBuffer %4
+%18 = OpConstant %4 20
+%19 = OpConstant %4 10
+%23 = OpTypeStruct %5 %12
+%25 = OpTypePointer StorageBuffer %5
+%27 = OpConstant %5 20
+%28 = OpConstant %5 10
 )";
     auto got_types = DumpInstructions(b.types());
     EXPECT_EQ(expected_types, got_types);
 
-    auto* expected_instructions = R"(%16 = OpAccessChain %15 %1 %13
-%20 = OpAtomicCompareExchange %4 %16 %12 %13 %13 %17 %18
-%21 = OpIEqual %19 %20 %17
-%22 = OpSelect %4 %21 %12 %13
-%10 = OpCompositeConstruct %11 %20 %22
-%27 = OpAccessChain %26 %1 %12
-%30 = OpAtomicCompareExchange %5 %27 %12 %13 %13 %28 %29
-%31 = OpIEqual %19 %30 %28
-%34 = OpSelect %5 %31 %33 %32
-%23 = OpCompositeConstruct %24 %30 %34
+    auto* expected_instructions = R"(%17 = OpAccessChain %16 %1 %14
+%20 = OpAtomicCompareExchange %4 %17 %13 %14 %14 %18 %19
+%21 = OpIEqual %12 %20 %18
+%10 = OpCompositeConstruct %11 %20 %21
+%26 = OpAccessChain %25 %1 %13
+%29 = OpAtomicCompareExchange %5 %26 %13 %14 %14 %27 %28
+%30 = OpIEqual %12 %29 %27
+%22 = OpCompositeConstruct %23 %29 %30
 OpReturn
 )";
     auto got_instructions = DumpInstructions(b.functions()[0].instructions());
diff --git a/src/tint/writer/wgsl/generator_impl.cc b/src/tint/writer/wgsl/generator_impl.cc
index 677421d..c71cd49 100644
--- a/src/tint/writer/wgsl/generator_impl.cc
+++ b/src/tint/writer/wgsl/generator_impl.cc
@@ -258,7 +258,7 @@
             return true;
         },
         [&](const ast::FloatLiteralExpression* l) {  //
-            out << FloatToBitPreservingString(static_cast<float>(l->value));
+            out << FloatToBitPreservingString(static_cast<float>(l->value)) << l->suffix;
             return true;
         },
         [&](const ast::IntLiteralExpression* l) {  //
diff --git a/src/tint/writer/wgsl/generator_impl_constructor_test.cc b/src/tint/writer/wgsl/generator_impl_constructor_test.cc
index 7a147c2..ae9f2b7 100644
--- a/src/tint/writer/wgsl/generator_impl_constructor_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_constructor_test.cc
@@ -58,7 +58,7 @@
     GeneratorImpl& gen = Build();
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
-    EXPECT_THAT(gen.result(), HasSubstr("1073741824.0"));
+    EXPECT_THAT(gen.result(), HasSubstr("1073741824.0f"));
 }
 
 TEST_F(WgslGeneratorImplTest, EmitConstructor_Type_Float) {
@@ -67,7 +67,7 @@
     GeneratorImpl& gen = Build();
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
-    EXPECT_THAT(gen.result(), HasSubstr("f32(-0.000012)"));
+    EXPECT_THAT(gen.result(), HasSubstr("f32(-0.000012f)"));
 }
 
 TEST_F(WgslGeneratorImplTest, EmitConstructor_Type_Bool) {
@@ -103,7 +103,7 @@
     GeneratorImpl& gen = Build();
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
-    EXPECT_THAT(gen.result(), HasSubstr("vec3<f32>(1.0, 2.0, 3.0)"));
+    EXPECT_THAT(gen.result(), HasSubstr("vec3<f32>(1.0f, 2.0f, 3.0f)"));
 }
 
 TEST_F(WgslGeneratorImplTest, EmitConstructor_Type_Mat) {
@@ -112,8 +112,8 @@
     GeneratorImpl& gen = Build();
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
-    EXPECT_THAT(gen.result(), HasSubstr("mat2x3<f32>(vec3<f32>(1.0, 2.0, 3.0), "
-                                        "vec3<f32>(3.0, 4.0, 5.0))"));
+    EXPECT_THAT(gen.result(), HasSubstr("mat2x3<f32>(vec3<f32>(1.0f, 2.0f, 3.0f), "
+                                        "vec3<f32>(3.0f, 4.0f, 5.0f))"));
 }
 
 TEST_F(WgslGeneratorImplTest, EmitConstructor_Type_Array) {
@@ -123,8 +123,9 @@
     GeneratorImpl& gen = Build();
 
     ASSERT_TRUE(gen.Generate()) << gen.error();
-    EXPECT_THAT(gen.result(), HasSubstr("array<vec3<f32>, 3u>(vec3<f32>(1.0, 2.0, 3.0), "
-                                        "vec3<f32>(4.0, 5.0, 6.0), vec3<f32>(7.0, 8.0, 9.0))"));
+    EXPECT_THAT(gen.result(),
+                HasSubstr("array<vec3<f32>, 3u>(vec3<f32>(1.0f, 2.0f, 3.0f), "
+                          "vec3<f32>(4.0f, 5.0f, 6.0f), vec3<f32>(7.0f, 8.0f, 9.0f))"));
 }
 
 }  // namespace
diff --git a/src/tint/writer/wgsl/generator_impl_function_test.cc b/src/tint/writer/wgsl/generator_impl_function_test.cc
index da91948..4c43a59 100644
--- a/src/tint/writer/wgsl/generator_impl_function_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_function_test.cc
@@ -139,7 +139,7 @@
     ASSERT_TRUE(gen.EmitFunction(func));
     EXPECT_EQ(gen.result(), R"(  @stage(fragment)
   fn frag_main() -> @location(1) f32 {
-    return 1.0;
+    return 1.0f;
   }
 )");
 }
diff --git a/src/tint/writer/wgsl/generator_impl_literal_test.cc b/src/tint/writer/wgsl/generator_impl_literal_test.cc
index 353bd36..dd715d4 100644
--- a/src/tint/writer/wgsl/generator_impl_literal_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_literal_test.cc
@@ -64,36 +64,37 @@
 
 INSTANTIATE_TEST_SUITE_P(Zero,
                          WgslGenerator_FloatLiteralTest,
-                         ::testing::ValuesIn(std::vector<FloatData>{{0_f, "0.0"},
-                                                                    {MakeFloat(0, 0, 0), "0.0"},
-                                                                    {MakeFloat(1, 0, 0), "-0.0"}}));
+                         ::testing::ValuesIn(std::vector<FloatData>{
+                             {0_f, "0.0f"},
+                             {MakeFloat(0, 0, 0), "0.0f"},
+                             {MakeFloat(1, 0, 0), "-0.0f"}}));
 
 INSTANTIATE_TEST_SUITE_P(Normal,
                          WgslGenerator_FloatLiteralTest,
-                         ::testing::ValuesIn(std::vector<FloatData>{{1_f, "1.0"},
-                                                                    {-1_f, "-1.0"},
-                                                                    {101.375_f, "101.375"}}));
+                         ::testing::ValuesIn(std::vector<FloatData>{{1_f, "1.0f"},
+                                                                    {-1_f, "-1.0f"},
+                                                                    {101.375_f, "101.375f"}}));
 
 INSTANTIATE_TEST_SUITE_P(Subnormal,
                          WgslGenerator_FloatLiteralTest,
                          ::testing::ValuesIn(std::vector<FloatData>{
-                             {MakeFloat(0, 0, 1), "0x1p-149"},  // Smallest
-                             {MakeFloat(1, 0, 1), "-0x1p-149"},
-                             {MakeFloat(0, 0, 2), "0x1p-148"},
-                             {MakeFloat(1, 0, 2), "-0x1p-148"},
-                             {MakeFloat(0, 0, 0x7fffff), "0x1.fffffcp-127"},   // Largest
-                             {MakeFloat(1, 0, 0x7fffff), "-0x1.fffffcp-127"},  // Largest
-                             {MakeFloat(0, 0, 0xcafebe), "0x1.2bfaf8p-127"},   // Scattered bits
-                             {MakeFloat(1, 0, 0xcafebe), "-0x1.2bfaf8p-127"},  // Scattered bits
-                             {MakeFloat(0, 0, 0xaaaaa), "0x1.55554p-130"},     // Scattered bits
-                             {MakeFloat(1, 0, 0xaaaaa), "-0x1.55554p-130"},    // Scattered bits
+                             {MakeFloat(0, 0, 1), "0x1p-149f"},  // Smallest
+                             {MakeFloat(1, 0, 1), "-0x1p-149f"},
+                             {MakeFloat(0, 0, 2), "0x1p-148f"},
+                             {MakeFloat(1, 0, 2), "-0x1p-148f"},
+                             {MakeFloat(0, 0, 0x7fffff), "0x1.fffffcp-127f"},   // Largest
+                             {MakeFloat(1, 0, 0x7fffff), "-0x1.fffffcp-127f"},  // Largest
+                             {MakeFloat(0, 0, 0xcafebe), "0x1.2bfaf8p-127f"},   // Scattered bits
+                             {MakeFloat(1, 0, 0xcafebe), "-0x1.2bfaf8p-127f"},  // Scattered bits
+                             {MakeFloat(0, 0, 0xaaaaa), "0x1.55554p-130f"},     // Scattered bits
+                             {MakeFloat(1, 0, 0xaaaaa), "-0x1.55554p-130f"},    // Scattered bits
                          }));
 
 INSTANTIATE_TEST_SUITE_P(Infinity,
                          WgslGenerator_FloatLiteralTest,
                          ::testing::ValuesIn(std::vector<FloatData>{
-                             {MakeFloat(0, 255, 0), "0x1p+128"},
-                             {MakeFloat(1, 255, 0), "-0x1p+128"}}));
+                             {MakeFloat(0, 255, 0), "0x1p+128f"},
+                             {MakeFloat(1, 255, 0), "-0x1p+128f"}}));
 
 INSTANTIATE_TEST_SUITE_P(
     // TODO(dneto): It's unclear how Infinity and NaN should be handled.
@@ -108,20 +109,20 @@
     WgslGenerator_FloatLiteralTest,
     ::testing::ValuesIn(std::vector<FloatData>{
         // LSB only.  Smallest mantissa.
-        {MakeFloat(0, 255, 1), "0x1.000002p+128"},  // Smallest mantissa
-        {MakeFloat(1, 255, 1), "-0x1.000002p+128"},
+        {MakeFloat(0, 255, 1), "0x1.000002p+128f"},  // Smallest mantissa
+        {MakeFloat(1, 255, 1), "-0x1.000002p+128f"},
         // MSB only.
-        {MakeFloat(0, 255, 0x400000), "0x1.8p+128"},
-        {MakeFloat(1, 255, 0x400000), "-0x1.8p+128"},
+        {MakeFloat(0, 255, 0x400000), "0x1.8p+128f"},
+        {MakeFloat(1, 255, 0x400000), "-0x1.8p+128f"},
         // All 1s in the mantissa.
-        {MakeFloat(0, 255, 0x7fffff), "0x1.fffffep+128"},
-        {MakeFloat(1, 255, 0x7fffff), "-0x1.fffffep+128"},
+        {MakeFloat(0, 255, 0x7fffff), "0x1.fffffep+128f"},
+        {MakeFloat(1, 255, 0x7fffff), "-0x1.fffffep+128f"},
         // Scattered bits, with 0 in top mantissa bit.
-        {MakeFloat(0, 255, 0x20101f), "0x1.40203ep+128"},
-        {MakeFloat(1, 255, 0x20101f), "-0x1.40203ep+128"},
+        {MakeFloat(0, 255, 0x20101f), "0x1.40203ep+128f"},
+        {MakeFloat(1, 255, 0x20101f), "-0x1.40203ep+128f"},
         // Scattered bits, with 1 in top mantissa bit.
-        {MakeFloat(0, 255, 0x40101f), "0x1.80203ep+128"},
-        {MakeFloat(1, 255, 0x40101f), "-0x1.80203ep+128"}}));
+        {MakeFloat(0, 255, 0x40101f), "0x1.80203ep+128f"},
+        {MakeFloat(1, 255, 0x40101f), "-0x1.80203ep+128f"}}));
 
 }  // namespace
 }  // namespace tint::writer::wgsl
diff --git a/src/tint/writer/wgsl/generator_impl_variable_test.cc b/src/tint/writer/wgsl/generator_impl_variable_test.cc
index 80f6259..83af7c2 100644
--- a/src/tint/writer/wgsl/generator_impl_variable_test.cc
+++ b/src/tint/writer/wgsl/generator_impl_variable_test.cc
@@ -107,7 +107,7 @@
 
     std::stringstream out;
     ASSERT_TRUE(gen.EmitVariable(out, v)) << gen.error();
-    EXPECT_EQ(out.str(), R"(var<private> a : f32 = 1.0;)");
+    EXPECT_EQ(out.str(), R"(var<private> a : f32 = 1.0f;)");
 }
 
 TEST_F(WgslGeneratorImplTest, EmitVariable_Const) {
@@ -118,7 +118,7 @@
 
     std::stringstream out;
     ASSERT_TRUE(gen.EmitVariable(out, v)) << gen.error();
-    EXPECT_EQ(out.str(), R"(let a : f32 = 1.0;)");
+    EXPECT_EQ(out.str(), R"(let a : f32 = 1.0f;)");
 }
 
 }  // namespace