Import Tint changes from Dawn

Changes:
  - cae289da70bf50336dad554258aa3ed2cfce1c81 tint: Fix exactly representable check in lexer by Zhaoming Jiang <zhaoming.jiang@intel.com>
  - 0fb4e2c608f3b8235270d7286d703af152626e33 tint: f16 literal in WGSL lexer and check subnormal f32/f... by Zhaoming Jiang <zhaoming.jiang@intel.com>
  - 856d6af57ea6d854f03de6ca5aa08fcd2296acf7 tint: uniformity: detect pointers assigned to in non-unif... by Antonio Maiorano <amaiorano@google.com>
  - 0fa572ff05d2a35c75369d4107f175780dbae15a Emit deprecation warnings for @stage. by dan sinclair <dsinclair@chromium.org>
GitOrigin-RevId: cae289da70bf50336dad554258aa3ed2cfce1c81
Change-Id: I005c00e6fe5e5a974bc677fd63a1d6e02fca0904
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/93320
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/number.cc b/src/tint/number.cc
index 3ead5b0..17b005b 100644
--- a/src/tint/number.cc
+++ b/src/tint/number.cc
@@ -15,9 +15,12 @@
 #include "src/tint/number.h"
 
 #include <algorithm>
+#include <cmath>
 #include <cstring>
 #include <ostream>
 
+#include "src/tint/debug.h"
+
 namespace tint {
 
 std::ostream& operator<<(std::ostream& out, ConversionFailure failure) {
@@ -38,18 +41,165 @@
         return -std::numeric_limits<f16::type>::infinity();
     }
     // Below value must be within the finite range of a f16.
+    // Assert we use binary32 (i.e. float) as underlying type, which has 4 bytes.
+    static_assert(std::is_same<f16::type, float>());
+    const uint32_t sign_mask = 0x80000000u;      // Mask for the sign bit
+    const uint32_t exponent_mask = 0x7f800000u;  // Mask for 8 exponent bits
+
     uint32_t u32;
     memcpy(&u32, &value, 4);
-    if ((u32 & 0x7fffffffu) == 0) {  // ~sign
+
+    if ((u32 & ~sign_mask) == 0) {
         return value;                // +/- zero
     }
-    if ((u32 & 0x7f800000) == 0x7f800000) {  // exponent all 1's
+    if ((u32 & exponent_mask) == exponent_mask) {  // exponent all 1's
         return value;                        // inf or nan
     }
-    // f32 bits :  1 sign,  8 exponent, 23 mantissa
-    // f16 bits :  1 sign,  5 exponent, 10 mantissa
-    // Mask the value to preserve the sign, exponent and most-significant 10 mantissa bits.
-    u32 = u32 & 0xffffe000u;
+
+    // We are now going to quantize a f32 number into subnormal f16 and store the result value back
+    // into a f32 variable. Notice that all subnormal f16 values are just normal f32 values. Below
+    // will show that we can do this quantization by just masking out 13 or more lowest mantissa
+    // bits of the original f32 number.
+    //
+    // Note:
+    // f32 has 1 sign bit, 8 exponent bits for biased exponent (i.e. unbiased exponent + 127), and
+    // 23 mantissa bits. Binary form: s_eeeeeeee_mmmmmmmmmmmmmmmmmmmmmmm
+    // f16 has 1 sign bit, 5 exponent bits for biased exponent (i.e. unbiased exponent + 15), and
+    // 10 mantissa bits. Binary form: s_eeeee_mmmmmmmmmm
+    // The largest finite f16 number has a biased exponent of 11110 in binary, or 30 decimal, and so
+    // a unbiased exponent of 30 - 15 = 15.
+    // The smallest finite f16 number has a biased exponent of 00001 in binary, or 1 decimal, and so
+    // a unbiased exponent of 1 - 15 = -14.
+    //
+    // We may follow the argument below:
+    // 1. All normal or subnormal f16 values, range from 0x1.p-24 to 0x1.ffcp15, are exactly
+    // representable by normal f32 number.
+    //   1.1. We can denote the set of all f32 number that are exact representation of finite f16
+    //   values by `R`.
+    //   1.2. We can do the quantization by mapping a normal f32 value v (in the f16 finite range)
+    //   to a certain f32 number v' in the set R, which is the largest (by the meaning of absolute
+    //   value) one among all values in R that are no larger than v.
+    // 2. We can decide whether a given normal f32 number v is in the set R, by looking at its
+    // mantissa bits and biased exponent `e`. Recall that biased exponent e is unbiased exponent +
+    // 127, and in the range of 1 to 254 for normal f32 number.
+    //   2.1. If e >= 143, i.e. abs(v) >= 2^16 > f16::kHighest = 0x1.ffcp15, v is larger than any
+    //   finite f16 value and can not be in set R.
+    //   2.2. If 142 >= e >= 113, or f16::kHighest >= abs(v) >= f16::kSmallest = 2^-14, v falls in
+    //   the range of normal f16 values. In this case, v is in the set R iff the lowest 13 mantissa
+    //   bits are all 0. (See below for proof)
+    //     2.2.1. If we let v' be v with lowest 13 mantissa bits masked to 0, v' will be in set R
+    //     and the largest one in set R that no larger than v. Such v' is the quantized value of v.
+    //   2.3. If 112 >= e >= 103, i.e. 2^-14 > abs(v) >= f16::kSmallestSubnormal = 2^-24, v falls in
+    //   the range of subnormal f16 values. In this case, v is in the set R iff the lowest 126-e
+    //   mantissa bits are all 0. Notice that 126-e is in range 14 to 23, inclusive. (See below for
+    //   proof)
+    //     2.3.1. If we let v' be v with lowest 126-e mantissa bits masked to 0, v' will be in set R
+    //     and the largest on in set R that no larger than v. Such v' is the quantized value of v.
+    //   2.4. If 2^-24 > abs(v) > 0, i.e. 103 > e, v is smaller than any finite f16 value and not
+    //   equal to 0.0, thus can not be in set R.
+    //   2.5. If abs(v) = 0, v is in set R and is just +-0.0.
+    //
+    // Proof for 2.2:
+    // Any normal f16 number, in binary form, s_eeeee_mmmmmmmmmm, has value
+    //      (s==0?1:-1)*(1+uint(mmmmm_mmmmm)*(2^-10))*2^(uint(eeeee)-15)
+    // in which unit(bbbbb) means interprete binary pattern "bbbbb" as unsigned binary number,
+    // and we have 1 <= uint(eeeee) <= 30.
+    // This value is equal to a normal f32 number with binary
+    //      s_EEEEEEEE_mmmmmmmmmm0000000000000
+    // where uint(EEEEEEEE) = uint(eeeee) + 112, so that unbiased exponent keep unchanged
+    //      uint(EEEEEEEE) - 127 = uint(eeeee) - 15
+    // and its value is
+    //         (s==0?1:-1)*(1+uint(mmmmm_mmmmm_00000_00000_000)*(2^-23))*2^(uint(EEEEEEEE)-127)
+    //      == (s==0?1:-1)*(1+uint(mmmmm_mmmmm)*(2^-10))*2^(uint(eeeee)-15)
+    // Notice that uint(EEEEEEEE) is in range [113, 142], showing that it is a normal f32 number.
+    // So we proof that any normal f16 number can be exactly representd by a normal f32 number
+    // with biased exponent in range [113,142] and the lowest 13 mantissa bits 0.
+    // On the other hand, since mantissa bits mmmmmmmmmm are arbitrary, the value of any f32
+    // that has a biased exponent in range [113, 142] and lowest 13 mantissa bits zero is equal
+    // to a normal f16 value. Hence we proof 2.2.
+    //
+    // Proof for 2.3:
+    // Any subnormal f16 number has a binary form of s_00000_mmmmmmmmmm, and its value is
+    // (s==0?1:-1)*uint(mmmmmmmmmm)*(2^-10)*(2^-14) = (s==0?1:-1)*uint(mmmmmmmmmm)*(2^-24).
+    // We discuss on bit pattern of mantissa bits mmmmmmmmmm.
+    //   Case 1: mantissa bits has no leading zero bit, s_00000_1mmmmmmmmm
+    //      In this case the value is
+    //              (s==0?1:-1)*uint(1mmmm_mmmmm)*(2^-10)*(2^-14)
+    //          ==  (s==0?1:-1)*(uint(1_mmmmm_mmmm)*(2^-9))*(2^-15)
+    //          ==  (s==0?1:-1)*(1+uint(mmmmm_mmmm)*(2^-9))*(2^-15)
+    //          ==  (s==0?1:-1)*(1+uint(mmmmm_mmmm0_00000_00000_000)*(2^-23))*(2^-15)
+    //      which is equal to the value of normal f32 number
+    //          s_EEEEEEEE_mmmmm_mmmm0_00000_00000_000
+    //      where uint(EEEEEEEE) = -15 + 127 = 112. Hence we proof that any subnormal f16 number
+    //      with no leading zero mantissa bit can be exactly represented by a f32 number with
+    //      biased exponent 112 and the lowest 14 mantissa bits zero, and the value of any f32
+    //      number with biased exponent 112 and the lowest 14 mantissa bits zero are equal to a
+    //      subnormal f16 number with no leading zero mantissa bit.
+    //   Case 2: mantissa bits has 1 leading zero bit, s_00000_01mmmmmmmm
+    //      In this case the value is
+    //              (s==0?1:-1)*uint(01mmm_mmmmm)*(2^-10)*(2^-14)
+    //          ==  (s==0?1:-1)*(uint(01_mmmmm_mmm)*(2^-8))*(2^-16)
+    //          ==  (s==0?1:-1)*(1+uint(mmmmm_mmm)*(2^-8))*(2^-16)
+    //          ==  (s==0?1:-1)*(1+uint(mmmmm_mmm00_00000_00000_000)*(2^-23))*(2^-16)
+    //      which is equal to the value of normal f32 number
+    //          s_EEEEEEEE_mmmmm_mmm00_00000_00000_000
+    //      where uint(EEEEEEEE) = -16 + 127 = 111. Hence we proof that any subnormal f16 number
+    //      with 1 leading zero mantissa bit can be exactly represented by a f32 number with
+    //      biased exponent 111 and the lowest 15 mantissa bits zero, and the value of any f32
+    //      number with biased exponent 111 and the lowest 15 mantissa bits zero are equal to a
+    //      subnormal f16 number with 1 leading zero mantissa bit.
+    //   Case 3 to case 8: ......
+    //   Case 9: mantissa bits has 8 leading zero bit, s_00000_000000001m
+    //      In this case the value is
+    //              (s==0?1:-1)*uint(00000_0001m)*(2^-10)*(2^-14)
+    //          ==  (s==0?1:-1)*(uint(000000001_m)*(2^-1))*(2^-23)
+    //          ==  (s==0?1:-1)*(1+uint(m)*(2^-1))*(2^-23)
+    //          ==  (s==0?1:-1)*(1+uint(m0000_00000_00000_00000_000)*(2^-23))*(2^-23)
+    //      which is equal to the value of normal f32 number
+    //          s_EEEEEEEE_m0000_00000_00000_00000_000
+    //      where uint(EEEEEEEE) = -23 + 127 = 104. Hence we proof that any subnormal f16 number
+    //      with 8 leading zero mantissa bit can be exactly represented by a f32 number with
+    //      biased exponent 104 and the lowest 22 mantissa bits zero, and the value of any f32
+    //      number with biased exponent 104 and the lowest 22 mantissa bits zero are equal to a
+    //      subnormal f16 number with 8 leading zero mantissa bit.
+    //   Case 10: mantissa bits has 9 leading zero bit, s_00000_0000000001
+    //      In this case the value is just +-2^-24 = +-0x1.0p-24,
+    //      the f32 number has biased exponent 103 and all 23 mantissa bits zero.
+    //   Case 11: mantissa bits has 10 leading zero bit, s_00000_0000000000, just 0.0
+    // Concluding all these case, we proof that any subnormal f16 number with N leading zero
+    // mantissa bit can be exactly represented by a f32 number with biased exponent 112-N and the
+    // lowest 14+N mantissa bits zero, and the value of any f32 number with biased exponent 112-N (=
+    // e) and the lowest 14+N (= 126-e) mantissa bits zero are equal to a subnormal f16 number with
+    // N leading zero mantissa bit. N is in range [0, 9], so the f32 number's biased exponent e is
+    // in range [103, 112], or unbiased exponent in [-24, -15].
+
+    float abs_value = std::fabs(value);
+    if (abs_value >= kSmallest) {
+        // Value falls in the normal f16 range, quantize it to a normal f16 value by masking out
+        // lowest 13 mantissa bits.
+        u32 = u32 & ~((1u << 13) - 1);
+    } else if (abs_value >= kSmallestSubnormal) {
+        // Value should be quantized to a subnormal f16 value.
+
+        // Get the biased exponent `e` of f32 value, e.g. value 127 representing exponent 2^0.
+        uint32_t biased_exponent_original = (u32 & exponent_mask) >> 23;
+        // Since we ensure that kSmallest = 0x1f-14 > abs(value) >= kSmallestSubnormal = 0x1f-24,
+        // value will have a unbiased exponent in range -24 to -15 (inclusive), and the
+        // corresponding biased exponent in f32 is in range 103 to 112 (inclusive).
+        TINT_ASSERT(Semantic,
+                    (103 <= biased_exponent_original) && (biased_exponent_original <= 112));
+
+        // As we have proved, masking out the lowest 126-e mantissa bits of input value will result
+        // in a valid subnormal f16 value, which is exactly the required quantization result.
+        uint32_t discard_bits = 126 - biased_exponent_original;  // In range 14 to 23 (inclusive)
+        TINT_ASSERT(Semantic, (14 <= discard_bits) && (discard_bits <= 23));
+        uint32_t discard_mask = (1u << discard_bits) - 1;
+        u32 = u32 & ~discard_mask;
+    } else {
+        // value is too small that it can't even be represented as subnormal f16 number. Quantize
+        // to zero.
+        return value > 0 ? 0.0 : -0.0;
+    }
     memcpy(&value, &u32, 4);
     return value;
 }
diff --git a/src/tint/number.h b/src/tint/number.h
index b4c5ca4..7a0b13b 100644
--- a/src/tint/number.h
+++ b/src/tint/number.h
@@ -86,6 +86,10 @@
     static constexpr type kSmallest =
         std::is_integral_v<type> ? 0 : std::numeric_limits<type>::min();
 
+    /// Smallest positive subnormal value of this type, 0 for integral type.
+    static constexpr type kSmallestSubnormal =
+        std::is_integral_v<type> ? 0 : std::numeric_limits<type>::denorm_min();
+
     /// Constructor. The value is zero-initialized.
     Number() = default;
 
@@ -201,7 +205,12 @@
     static constexpr type kLowest = -65504.0f;
 
     /// Smallest positive normal value of this type.
-    static constexpr type kSmallest = 0.00006103515625f;  // 2⁻¹⁴
+    /// binary16 0_00001_0000000000, value is 2⁻¹⁴.
+    static constexpr type kSmallest = 0x1p-14f;
+
+    /// Smallest positive subnormal value of this type.
+    /// binary16 0_00000_0000000001, value is 2⁻¹⁴ * 2⁻¹⁰ = 2⁻²⁴.
+    static constexpr type kSmallestSubnormal = 0x1p-24f;
 
     /// Constructor. The value is zero-initialized.
     Number() = default;
diff --git a/src/tint/number_test.cc b/src/tint/number_test.cc
index 34b4d39..52ba4ae 100644
--- a/src/tint/number_test.cc
+++ b/src/tint/number_test.cc
@@ -52,7 +52,7 @@
 // Smallest positive normal float16 value.
 constexpr double kSmallestF16 = 0x1p-14;
 
-// Highest subnormal value for a float32.
+// Highest subnormal value for a float16.
 constexpr double kHighestF16Subnormal = 0x0.ffcp-14;
 
 constexpr double kLowestF32 = -kHighestF32;
@@ -141,6 +141,67 @@
     EXPECT_EQ(f16(inf), inf);
     EXPECT_EQ(f16(-inf), -inf);
     EXPECT_TRUE(std::isnan(f16(nan)));
+
+    // Test for subnormal quantization.
+    // The ULP is based on float rather than double or f16, since F16::Quantize take float as input.
+    constexpr float lowestPositiveNormalF16 = 0x1p-14;
+    constexpr float lowestPositiveNormalF16PlusULP = 0x1.000002p-14;
+    constexpr float lowestPositiveNormalF16MinusULP = 0x1.fffffep-15;
+    constexpr float highestPositiveSubnormalF16 = 0x0.ffcp-14;
+    constexpr float highestPositiveSubnormalF16PlusULP = 0x1.ff8002p-15;
+    constexpr float highestPositiveSubnormalF16MinusULP = 0x1.ff7ffep-15;
+    constexpr float lowestPositiveSubnormalF16 = 0x1.p-24;
+    constexpr float lowestPositiveSubnormalF16PlusULP = 0x1.000002p-24;
+    constexpr float lowestPositiveSubnormalF16MinusULP = 0x1.fffffep-25;
+
+    constexpr float highestNegativeNormalF16 = -lowestPositiveNormalF16;
+    constexpr float highestNegativeNormalF16PlusULP = -lowestPositiveNormalF16MinusULP;
+    constexpr float highestNegativeNormalF16MinusULP = -lowestPositiveNormalF16PlusULP;
+    constexpr float lowestNegativeSubnormalF16 = -highestPositiveSubnormalF16;
+    constexpr float lowestNegativeSubnormalF16PlusULP = -highestPositiveSubnormalF16MinusULP;
+    constexpr float lowestNegativeSubnormalF16MinusULP = -highestPositiveSubnormalF16PlusULP;
+    constexpr float highestNegativeSubnormalF16 = -lowestPositiveSubnormalF16;
+    constexpr float highestNegativeSubnormalF16PlusULP = -lowestPositiveSubnormalF16MinusULP;
+    constexpr float highestNegativeSubnormalF16MinusULP = -lowestPositiveSubnormalF16PlusULP;
+
+    // Value larger than or equal to lowest positive normal f16 will be quantized to normal f16.
+    EXPECT_EQ(f16(lowestPositiveNormalF16PlusULP), lowestPositiveNormalF16);
+    EXPECT_EQ(f16(lowestPositiveNormalF16), lowestPositiveNormalF16);
+    // Positive value smaller than lowest positive normal f16 but not smaller than lowest positive
+    // subnormal f16 will be quantized to subnormal f16 or zero.
+    EXPECT_EQ(f16(lowestPositiveNormalF16MinusULP), highestPositiveSubnormalF16);
+    EXPECT_EQ(f16(highestPositiveSubnormalF16PlusULP), highestPositiveSubnormalF16);
+    EXPECT_EQ(f16(highestPositiveSubnormalF16), highestPositiveSubnormalF16);
+    EXPECT_EQ(f16(highestPositiveSubnormalF16MinusULP), 0x0.ff8p-14);
+    EXPECT_EQ(f16(lowestPositiveSubnormalF16PlusULP), lowestPositiveSubnormalF16);
+    EXPECT_EQ(f16(lowestPositiveSubnormalF16), lowestPositiveSubnormalF16);
+    // Positive value smaller than lowest positive subnormal f16 will be quantized to zero.
+    EXPECT_EQ(f16(lowestPositiveSubnormalF16MinusULP), 0.0);
+    // Test the mantissa discarding, the least significant mantissa bit is 0x1p-24 = 0x0.004p-14.
+    EXPECT_EQ(f16(0x0.064p-14), 0x0.064p-14);
+    EXPECT_EQ(f16(0x0.067fecp-14), 0x0.064p-14);
+    EXPECT_EQ(f16(0x0.063ffep-14), 0x0.060p-14);
+    EXPECT_EQ(f16(0x0.008p-14), 0x0.008p-14);
+    EXPECT_EQ(f16(0x0.00bffep-14), 0x0.008p-14);
+    EXPECT_EQ(f16(0x0.007ffep-14), 0x0.004p-14);
+
+    // Vice versa for negative cases.
+    EXPECT_EQ(f16(highestNegativeNormalF16MinusULP), highestNegativeNormalF16);
+    EXPECT_EQ(f16(highestNegativeNormalF16), highestNegativeNormalF16);
+    EXPECT_EQ(f16(highestNegativeNormalF16PlusULP), lowestNegativeSubnormalF16);
+    EXPECT_EQ(f16(lowestNegativeSubnormalF16MinusULP), lowestNegativeSubnormalF16);
+    EXPECT_EQ(f16(lowestNegativeSubnormalF16), lowestNegativeSubnormalF16);
+    EXPECT_EQ(f16(lowestNegativeSubnormalF16PlusULP), -0x0.ff8p-14);
+    EXPECT_EQ(f16(highestNegativeSubnormalF16MinusULP), highestNegativeSubnormalF16);
+    EXPECT_EQ(f16(highestNegativeSubnormalF16), highestNegativeSubnormalF16);
+    EXPECT_EQ(f16(highestNegativeSubnormalF16PlusULP), 0.0);
+    // Test the mantissa discarding.
+    EXPECT_EQ(f16(-0x0.064p-14), -0x0.064p-14);
+    EXPECT_EQ(f16(-0x0.067fecp-14), -0x0.064p-14);
+    EXPECT_EQ(f16(-0x0.063ffep-14), -0x0.060p-14);
+    EXPECT_EQ(f16(-0x0.008p-14), -0x0.008p-14);
+    EXPECT_EQ(f16(-0x0.00bffep-14), -0x0.008p-14);
+    EXPECT_EQ(f16(-0x0.007ffep-14), -0x0.004p-14);
 }
 
 using BinaryCheckedCase = std::tuple<std::optional<AInt>, AInt, AInt>;
diff --git a/src/tint/reader/wgsl/lexer.cc b/src/tint/reader/wgsl/lexer.cc
index 58e3c85..7943894 100644
--- a/src/tint/reader/wgsl/lexer.cc
+++ b/src/tint/reader/wgsl/lexer.cc
@@ -343,12 +343,16 @@
     }
 
     bool has_f_suffix = false;
+    bool has_h_suffix = false;
     if (end < length() && matches(end, "f")) {
         end++;
         has_f_suffix = true;
+    } else if (end < length() && matches(end, "h")) {
+        end++;
+        has_h_suffix = true;
     }
 
-    if (!has_point && !has_exponent && !has_f_suffix) {
+    if (!has_point && !has_exponent && !has_f_suffix && !has_h_suffix) {
         // If it only has digits then it's an integer.
         return {};
     }
@@ -369,6 +373,14 @@
         }
     }
 
+    if (has_h_suffix) {
+        if (auto f = CheckedConvert<f16>(AFloat(value))) {
+            return {Token::Type::kFloatLiteral_H, source, static_cast<double>(f.Get())};
+        } else {
+            return {Token::Type::kError, source, "value cannot be represented as 'f16'"};
+        }
+    }
+
     if (value == HUGE_VAL || -value == HUGE_VAL) {
         return {Token::Type::kError, source, "value cannot be represented as 'abstract-float'"};
     } else {
@@ -547,6 +559,7 @@
     int64_t exponent_sign = 1;
     // If the 'p' part is present, the rest of the exponent must exist.
     bool has_f_suffix = false;
+    bool has_h_suffix = false;
     if (has_exponent) {
         // Parse the rest of the exponent.
         // (+|-)?
@@ -574,12 +587,15 @@
             end++;
         }
 
-        // Parse optional 'f' suffix.  For a hex float, it can only exist
+        // Parse optional 'f' or 'h' suffix.  For a hex float, it can only exist
         // when the exponent is present. Otherwise it will look like
         // one of the mantissa digits.
         if (end < length() && matches(end, "f")) {
             has_f_suffix = true;
             end++;
+        } else if (end < length() && matches(end, "h")) {
+            has_h_suffix = true;
+            end++;
         }
 
         if (!has_exponent_digits) {
@@ -648,7 +664,7 @@
     }
 
     if (signed_exponent >= kExponentMax || (signed_exponent == kExponentMax && mantissa != 0)) {
-        std::string type = has_f_suffix ? "f32" : "abstract-float";
+        std::string type = has_f_suffix ? "f32" : (has_h_suffix ? "f16" : "abstract-float");
         return {Token::Type::kError, source, "value cannot be represented as '" + type + "'"};
     }
 
@@ -667,14 +683,106 @@
             result_f64 > static_cast<double>(f32::kHighest)) {
             return {Token::Type::kError, source, "value cannot be represented as 'f32'"};
         }
-        // Check the value can be exactly represented (low 29 mantissa bits must be 0)
-        if (result_u64 & 0x1fffffff) {
+        // Check the value can be exactly represented, i.e. only high 23 mantissa bits are valid for
+        // normal f32 values, and less for subnormal f32 values. The rest low mantissa bits must be
+        // 0.
+        int valid_mantissa_bits = 0;
+        double abs_result_f64 = std::fabs(result_f64);
+        if (abs_result_f64 >= static_cast<double>(f32::kSmallest)) {
+            // The result shall be a normal f32 value.
+            valid_mantissa_bits = 23;
+        } else if (abs_result_f64 >= static_cast<double>(f32::kSmallestSubnormal)) {
+            // The result shall be a subnormal f32 value, represented as double.
+            // The smallest positive normal f32 is f32::kSmallest = 2^-126 = 0x1.0p-126, and the
+            //   smallest positive subnormal f32 is f32::kSmallestSubnormal = 2^-149. Thus, the
+            //   value v in range 2^-126 > v >= 2^-149 must be represented as a subnormal f32
+            //   number, but is still normal double (f64) number, and has a exponent in range -127
+            //   to -149, inclusive.
+            // A value v, if 2^-126 > v >= 2^-127, its binary32 representation will have binary form
+            //   s_00000000_1xxxxxxxxxxxxxxxxxxxxxx, having mantissa of 1 leading 1 bit and 22
+            //   arbitrary bits. Since this value is represented as normal double number, the
+            //   leading 1 bit is omitted, only the highest 22 mantissia bits can be arbitrary, and
+            //   the rest lowest 40 mantissa bits of f64 number must be zero.
+            // 2^-127 > v >= 2^-128, binary32 s_00000000_01xxxxxxxxxxxxxxxxxxxxx, having mantissa of
+            //   1 leading 0 bit, 1 leading 1 bit, and 21 arbitrary bits. The f64 representation
+            //   omits the leading 0 and 1 bits, and only the highest 21 mantissia bits can be
+            //   arbitrary.
+            // 2^-128 > v >= 2^-129, binary32 s_00000000_001xxxxxxxxxxxxxxxxxxxx, 20 arbitrary bits.
+            // ...
+            // 2^-147 > v >= 2^-148, binary32 s_00000000_0000000000000000000001x, 1 arbitrary bit.
+            // 2^-148 > v >= 2^-149, binary32 s_00000000_00000000000000000000001, 0 arbitrary bit.
+
+            // signed_exponent must be in range -149 + 1023 = 874 to -127 + 1023 = 896, inclusive
+            TINT_ASSERT(Reader, (874 <= signed_exponent) && (signed_exponent <= 896));
+            int unbiased_exponent =
+                static_cast<int>(signed_exponent) - static_cast<int>(kExponentBias);
+            TINT_ASSERT(Reader, (-149 <= unbiased_exponent) && (unbiased_exponent <= -127));
+            valid_mantissa_bits = unbiased_exponent + 149;  // 0 for -149, and 22 for -127
+        } else if (abs_result_f64 != 0.0) {
+            // The result is smaller than the smallest subnormal f32 value, but not equal to zero.
+            // Such value will never be exactly represented by f32.
             return {Token::Type::kError, source, "value cannot be exactly represented as 'f32'"};
         }
+        // Check the low 52-valid_mantissa_bits mantissa bits must be 0.
+        TINT_ASSERT(Reader, (0 <= valid_mantissa_bits) && (valid_mantissa_bits <= 23));
+        if (result_u64 & ((uint64_t(1) << (52 - valid_mantissa_bits)) - 1)) {
+            return {Token::Type::kError, source, "value cannot be exactly represented as 'f32'"};
+        }
+        return {Token::Type::kFloatLiteral_F, source, result_f64};
+    } else if (has_h_suffix) {
+        // Check value fits in f16
+        if (result_f64 < static_cast<double>(f16::kLowest) ||
+            result_f64 > static_cast<double>(f16::kHighest)) {
+            return {Token::Type::kError, source, "value cannot be represented as 'f16'"};
+        }
+        // Check the value can be exactly represented, i.e. only high 10 mantissa bits are valid for
+        // normal f16 values, and less for subnormal f16 values. The rest low mantissa bits must be
+        // 0.
+        int valid_mantissa_bits = 0;
+        double abs_result_f64 = std::fabs(result_f64);
+        if (abs_result_f64 >= static_cast<double>(f16::kSmallest)) {
+            // The result shall be a normal f16 value.
+            valid_mantissa_bits = 10;
+        } else if (abs_result_f64 >= static_cast<double>(f16::kSmallestSubnormal)) {
+            // The result shall be a subnormal f16 value, represented as double.
+            // The smallest positive normal f16 is f16::kSmallest = 2^-14 = 0x1.0p-14, and the
+            //   smallest positive subnormal f16 is f16::kSmallestSubnormal = 2^-24. Thus, the value
+            //   v in range 2^-14 > v >= 2^-24 must be represented as a subnormal f16 number, but
+            //   is still normal double (f64) number, and has a exponent in range -15 to -24,
+            //   inclusive.
+            // A value v, if 2^-14 > v >= 2^-15, its binary16 representation will have binary form
+            //   s_00000_1xxxxxxxxx, having mantissa of 1 leading 1 bit and 9 arbitrary bits. Since
+            //   this value is represented as normal double number, the leading 1 bit is omitted,
+            //   only the highest 9 mantissia bits can be arbitrary, and the rest lowest 43 mantissa
+            //   bits of f64 number must be zero.
+            // 2^-15 > v >= 2^-16, binary16 s_00000_01xxxxxxxx, having mantissa of 1 leading 0 bit,
+            //   1 leading 1 bit, and 8 arbitrary bits. The f64 representation omits the leading 0
+            //   and 1 bits, and only the highest 8 mantissia bits can be arbitrary.
+            // 2^-16 > v >= 2^-17, binary16 s_00000_001xxxxxxx, 7 arbitrary bits.
+            // ...
+            // 2^-22 > v >= 2^-23, binary16 s_00000_000000001x, 1 arbitrary bits.
+            // 2^-23 > v >= 2^-24, binary16 s_00000_0000000001, 0 arbitrary bits.
+
+            // signed_exponent must be in range -24 + 1023 = 999 to -15 + 1023 = 1008, inclusive
+            TINT_ASSERT(Reader, (999 <= signed_exponent) && (signed_exponent <= 1008));
+            int unbiased_exponent =
+                static_cast<int>(signed_exponent) - static_cast<int>(kExponentBias);
+            TINT_ASSERT(Reader, (-24 <= unbiased_exponent) && (unbiased_exponent <= -15));
+            valid_mantissa_bits = unbiased_exponent + 24;  // 0 for -24, and 9 for -15
+        } else if (abs_result_f64 != 0.0) {
+            // The result is smaller than the smallest subnormal f16 value, but not equal to zero.
+            // Such value will never be exactly represented by f16.
+            return {Token::Type::kError, source, "value cannot be exactly represented as 'f16'"};
+        }
+        // Check the low 52-valid_mantissa_bits mantissa bits must be 0.
+        TINT_ASSERT(Reader, (0 <= valid_mantissa_bits) && (valid_mantissa_bits <= 10));
+        if (result_u64 & ((uint64_t(1) << (52 - valid_mantissa_bits)) - 1)) {
+            return {Token::Type::kError, source, "value cannot be exactly represented as 'f16'"};
+        }
+        return {Token::Type::kFloatLiteral_H, source, result_f64};
     }
 
-    return {has_f_suffix ? Token::Type::kFloatLiteral_F : Token::Type::kFloatLiteral, source,
-            result_f64};
+    return {Token::Type::kFloatLiteral, source, result_f64};
 }
 
 Token Lexer::build_token_from_int_if_possible(Source source, size_t start, int32_t base) {
diff --git a/src/tint/reader/wgsl/lexer_test.cc b/src/tint/reader/wgsl/lexer_test.cc
index 16ae46d..d8decba 100644
--- a/src/tint/reader/wgsl/lexer_test.cc
+++ b/src/tint/reader/wgsl/lexer_test.cc
@@ -19,6 +19,7 @@
 #include <vector>
 
 #include "gtest/gtest.h"
+#include "src/tint/number.h"
 
 namespace tint::reader::wgsl {
 namespace {
@@ -320,6 +321,8 @@
     auto t = l.next();
     if (std::string(params.input).back() == 'f') {
         EXPECT_TRUE(t.Is(Token::Type::kFloatLiteral_F));
+    } else if (std::string(params.input).back() == 'h') {
+        EXPECT_TRUE(t.Is(Token::Type::kFloatLiteral_H));
     } else {
         EXPECT_TRUE(t.Is(Token::Type::kFloatLiteral));
     }
@@ -340,6 +343,11 @@
                              FloatData{"1f", 1.0},
                              FloatData{"-0f", 0.0},
                              FloatData{"-1f", -1.0},
+                             // No decimal, with 'h' suffix
+                             FloatData{"0h", 0.0},
+                             FloatData{"1h", 1.0},
+                             FloatData{"-0h", 0.0},
+                             FloatData{"-1h", -1.0},
 
                              // Zero, with decimal.
                              FloatData{"0.0", 0.0},
@@ -354,7 +362,14 @@
                              FloatData{".0f", 0.0},
                              FloatData{"-0.0f", 0.0},
                              FloatData{"-0.f", 0.0},
-                             FloatData{"-.0", 0.0},
+                             FloatData{"-.0f", 0.0},
+                             // Zero, with decimal and 'h' suffix
+                             FloatData{"0.0h", 0.0},
+                             FloatData{"0.h", 0.0},
+                             FloatData{".0h", 0.0},
+                             FloatData{"-0.0h", 0.0},
+                             FloatData{"-0.h", 0.0},
+                             FloatData{"-.0h", 0.0},
 
                              // Non-zero with decimal
                              FloatData{"5.7", 5.7},
@@ -370,6 +385,13 @@
                              FloatData{"-5.7f", static_cast<double>(-5.7f)},
                              FloatData{"-5.f", static_cast<double>(-5.f)},
                              FloatData{"-.7f", static_cast<double>(-.7f)},
+                             // Non-zero with decimal and 'h' suffix
+                             FloatData{"5.7h", static_cast<double>(f16::Quantize(5.7f))},
+                             FloatData{"5.h", static_cast<double>(f16::Quantize(5.f))},
+                             FloatData{".7h", static_cast<double>(f16::Quantize(.7f))},
+                             FloatData{"-5.7h", static_cast<double>(f16::Quantize(-5.7f))},
+                             FloatData{"-5.h", static_cast<double>(f16::Quantize(-5.f))},
+                             FloatData{"-.7h", static_cast<double>(f16::Quantize(-.7f))},
 
                              // No decimal, with exponent
                              FloatData{"1e5", 1e5},
@@ -381,6 +403,11 @@
                              FloatData{"1E5f", static_cast<double>(1e5f)},
                              FloatData{"1e-5f", static_cast<double>(1e-5f)},
                              FloatData{"1E-5f", static_cast<double>(1e-5f)},
+                             // No decimal, with exponent and 'h' suffix
+                             FloatData{"6e4h", static_cast<double>(f16::Quantize(6e4f))},
+                             FloatData{"6E4h", static_cast<double>(f16::Quantize(6e4f))},
+                             FloatData{"1e-5h", static_cast<double>(f16::Quantize(1e-5f))},
+                             FloatData{"1E-5h", static_cast<double>(f16::Quantize(1e-5f))},
                              // With decimal and exponents
                              FloatData{"0.2e+12", 0.2e12},
                              FloatData{"1.2e-5", 1.2e-5},
@@ -393,9 +420,16 @@
                              FloatData{"2.57e23f", static_cast<double>(2.57e23f)},
                              FloatData{"2.5e+0f", static_cast<double>(2.5f)},
                              FloatData{"2.5e-0f", static_cast<double>(2.5f)},
+                             // With decimal and exponents and 'h' suffix
+                             FloatData{"0.2e+5h", static_cast<double>(f16::Quantize(0.2e5f))},
+                             FloatData{"1.2e-5h", static_cast<double>(f16::Quantize(1.2e-5f))},
+                             FloatData{"6.55e4h", static_cast<double>(f16::Quantize(6.55e4f))},
+                             FloatData{"2.5e+0h", static_cast<double>(f16::Quantize(2.5f))},
+                             FloatData{"2.5e-0h", static_cast<double>(f16::Quantize(2.5f))},
                              // Quantization
-                             FloatData{"3.141592653589793", 3.141592653589793},   // no quantization
-                             FloatData{"3.141592653589793f", 3.1415927410125732}  // f32 quantized
+                             FloatData{"3.141592653589793", 3.141592653589793},  // no quantization
+                             FloatData{"3.141592653589793f", 3.1415927410125732},  // f32 quantized
+                             FloatData{"3.141592653589793h", 3.140625}             // f16 quantized
                              ));
 
 using FloatTest_Invalid = testing::TestWithParam<const char*>;
@@ -404,7 +438,8 @@
     Lexer l(&file);
 
     auto t = l.next();
-    EXPECT_FALSE(t.Is(Token::Type::kFloatLiteral));
+    EXPECT_FALSE(t.Is(Token::Type::kFloatLiteral) || t.Is(Token::Type::kFloatLiteral_F) ||
+                 t.Is(Token::Type::kFloatLiteral_H));
 }
 INSTANTIATE_TEST_SUITE_P(LexerTest,
                          FloatTest_Invalid,
@@ -423,9 +458,8 @@
                                          // Overflow
                                          "2.5e+256f",
                                          "-2.5e+127f",
-                                         // Magnitude smaller than smallest positive f32.
-                                         "2.5e-300f",
-                                         "-2.5e-300f",
+                                         "6.5520e+4h",
+                                         "-6.5e+12h",
                                          // Decimal exponent must immediately
                                          // follow the 'e'.
                                          "2.5e 12",
diff --git a/src/tint/reader/wgsl/parser_impl.cc b/src/tint/reader/wgsl/parser_impl.cc
index a28b798..ddd9e96 100644
--- a/src/tint/reader/wgsl/parser_impl.cc
+++ b/src/tint/reader/wgsl/parser_impl.cc
@@ -3018,6 +3018,10 @@
         return create<ast::FloatLiteralExpression>(t.source(), t.to_f64(),
                                                    ast::FloatLiteralExpression::Suffix::kF);
     }
+    if (match(Token::Type::kFloatLiteral_H)) {
+        return create<ast::FloatLiteralExpression>(t.source(), t.to_f64(),
+                                                   ast::FloatLiteralExpression::Suffix::kH);
+    }
     if (match(Token::Type::kTrue)) {
         return create<ast::BoolLiteralExpression>(t.source(), true);
     }
@@ -3251,6 +3255,7 @@
         });
     }
 
+    // TODO(crbug.com/tint/1503): Remove when deprecation period is over.
     if (t == kStageAttribute) {
         return expect_paren_block("stage attribute", [&]() -> Result {
             auto stage = expect_pipeline_stage();
@@ -3258,26 +3263,22 @@
                 return Failure::kErrored;
             }
 
-            // TODO(crbug.com/tint/1503): Enable this once all the Dawn and CTS
-            // tests are updated to use the new format so we can avoid spamming
-            // the log files.
-            if ((false)) {
-                std::string warning = "stage should use @";
-                switch (stage.value) {
-                    case ast::PipelineStage::kVertex:
-                        warning += "vertex";
-                        break;
-                    case ast::PipelineStage::kFragment:
-                        warning += "fragment";
-                        break;
-                    case ast::PipelineStage::kCompute:
-                        warning += "compute";
-                        break;
-                    case ast::PipelineStage::kNone:
-                        break;
-                }
-                deprecated(t.source(), warning);
+            std::string warning = "remove stage and use @";
+            switch (stage.value) {
+                case ast::PipelineStage::kVertex:
+                    warning += "vertex";
+                    break;
+                case ast::PipelineStage::kFragment:
+                    warning += "fragment";
+                    break;
+                case ast::PipelineStage::kCompute:
+                    warning += "compute";
+                    break;
+                case ast::PipelineStage::kNone:
+                    break;
             }
+            deprecated(t.source(), warning);
+
             return create<ast::StageAttribute>(t.source(), stage.value);
         });
     }
diff --git a/src/tint/reader/wgsl/parser_impl_const_literal_test.cc b/src/tint/reader/wgsl/parser_impl_const_literal_test.cc
index b07ec1b..16861e4 100644
--- a/src/tint/reader/wgsl/parser_impl_const_literal_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_const_literal_test.cc
@@ -151,13 +151,18 @@
     ASSERT_NE(c.value, nullptr);
     auto* literal = c->As<ast::FloatLiteralExpression>();
     ASSERT_NE(literal, nullptr);
-    EXPECT_DOUBLE_EQ(literal->value, params.expected)
+    // Use EXPECT_EQ instead of EXPECT_DOUBLE_EQ here, because EXPECT_DOUBLE_EQ use AlmostEquals(),
+    // which allows an error up to 4 ULPs.
+    EXPECT_EQ(literal->value, params.expected)
         << "\n"
         << "got:      " << std::hexfloat << literal->value << "\n"
         << "expected: " << std::hexfloat << params.expected;
     if (params.input.back() == 'f') {
         EXPECT_EQ(c->As<ast::FloatLiteralExpression>()->suffix,
                   ast::FloatLiteralExpression::Suffix::kF);
+    } else if (params.input.back() == 'h') {
+        EXPECT_EQ(c->As<ast::FloatLiteralExpression>()->suffix,
+                  ast::FloatLiteralExpression::Suffix::kH);
     } else {
         EXPECT_EQ(c->As<ast::FloatLiteralExpression>()->suffix,
                   ast::FloatLiteralExpression::Suffix::kNone);
@@ -181,6 +186,7 @@
 
                              {"234.e12", 234.e12},
                              {"234.e12f", static_cast<double>(234.e12f)},
+                             {"234.e2h", static_cast<double>(f16::Quantize(234.e2))},
 
                              // Tiny cases
                              {"1e-5000", 0.0},
@@ -189,6 +195,12 @@
                              {"-1e-5000f", 0.0},
                              {"1e-50f", 0.0},
                              {"-1e-50f", 0.0},
+                             {"1e-5000h", 0.0},
+                             {"-1e-5000h", 0.0},
+                             {"1e-50h", 0.0},
+                             {"-1e-50h", 0.0},
+                             {"1e-8h", 0.0},  // The smallest positive subnormal f16 is 5.96e-8
+                             {"-1e-8h", 0.0},
 
                              // Nearly overflow
                              {"1.e308", 1.e308},
@@ -209,6 +221,16 @@
                              {"-3.5e37f", static_cast<double>(-3.5e37f)},
                              {"3.403e37f", static_cast<double>(3.403e37f)},
                              {"-3.403e37f", static_cast<double>(-3.403e37f)},
+
+                             // Nearly overflow
+                             {"6e4h", 6e4},
+                             {"-6e4h", -6e4},
+                             {"8.0e3h", 8.0e3},
+                             {"-8.0e3h", -8.0e3},
+                             {"3.5e3h", 3.5e3},
+                             {"-3.5e3h", -3.5e3},
+                             {"3.403e3h", 3.402e3},    // Quantized
+                             {"-3.403e3h", -3.402e3},  // Quantized
                          }));
 
 const double NegInf = MakeDouble(1, 0x7FF, 0);
@@ -229,6 +251,10 @@
         {"-0x1p-1", -0x1p-1},
         {"-0x1p-2", -0x1p-2},
         {"-0x1.8p-1", -0x1.8p-1},
+        {"0x0.4p+1", 0x0.4p+1},
+        {"0x0.02p+3", 0x0.02p+3},
+        {"0x4.4p+1", 0x4.4p+1},
+        {"0x8c.02p+3", 0x8c.02p+3},
 
         // Large numbers
         {"0x1p+9", 0x1p+9},
@@ -257,6 +283,11 @@
         {"-0x1p-124f", -0x1p-124},
         {"-0x1p-125f", -0x1p-125},
 
+        {"0x1p-12h", 0x1p-12},
+        {"0x1p-13h", 0x1p-13},
+        {"-0x1p-12h", -0x1p-12},
+        {"-0x1p-13h", -0x1p-13},
+
         // Lowest non-denorm
         {"0x1p-1022", 0x1p-1022},
         {"-0x1p-1022", -0x1p-1022},
@@ -264,9 +295,14 @@
         {"0x1p-126f", 0x1p-126},
         {"-0x1p-126f", -0x1p-126},
 
+        {"0x1p-14h", 0x1p-14},
+        {"-0x1p-14h", -0x1p-14},
+
         // Denormalized values
         {"0x1p-1023", 0x1p-1023},
+        {"0x0.8p-1022", 0x0.8p-1022},
         {"0x1p-1024", 0x1p-1024},
+        {"0x0.2p-1021", 0x0.2p-1021},
         {"0x1p-1025", 0x1p-1025},
         {"0x1p-1026", 0x1p-1026},
         {"-0x1p-1023", -0x1p-1023},
@@ -277,7 +313,9 @@
         {"0x1.8p-1024", 0x1.8p-1024},
 
         {"0x1p-127f", 0x1p-127},
+        {"0x0.8p-126f", 0x0.8p-126},
         {"0x1p-128f", 0x1p-128},
+        {"0x0.2p-125f", 0x0.2p-125},
         {"0x1p-129f", 0x1p-129},
         {"0x1p-130f", 0x1p-130},
         {"-0x1p-127f", -0x1p-127},
@@ -287,13 +325,28 @@
         {"0x1.8p-127f", 0x1.8p-127},
         {"0x1.8p-128f", 0x1.8p-128},
 
+        {"0x1p-15h", 0x1p-15},
+        {"0x0.8p-14h", 0x0.8p-14},
+        {"0x1p-16h", 0x1p-16},
+        {"0x0.2p-13h", 0x0.2p-13},
+        {"0x1p-17h", 0x1p-17},
+        {"0x1p-18h", 0x1p-18},
+        {"-0x1p-15h", -0x1p-15},
+        {"-0x1p-16h", -0x1p-16},
+        {"-0x1p-17h", -0x1p-17},
+        {"-0x1p-18h", -0x1p-18},
+        {"0x1.8p-15h", 0x1.8p-15},
+        {"0x1.8p-16h", 0x1.8p-16},
+
         // F64 extremities
-        {"0x1p-1074", 0x1p-1074},                              // +SmallestDenormal
-        {"0x1p-1073", 0x1p-1073},                              // +BiggerDenormal
-        {"0x1.ffffffffffffp-1027", 0x1.ffffffffffffp-1027},    // +LargestDenormal
-        {"-0x1p-1074", -0x1p-1074},                            // -SmallestDenormal
-        {"-0x1p-1073", -0x1p-1073},                            // -BiggerDenormal
-        {"-0x1.ffffffffffffp-1027", -0x1.ffffffffffffp-1027},  // -LargestDenormal
+        {"0x1p-1074", 0x1p-1074},                                // +SmallestDenormal
+        {"0x1p-1073", 0x1p-1073},                                // +BiggerDenormal
+        {"0x1.ffffffffffffep-1023", 0x1.ffffffffffffep-1023},    // +LargestDenormal
+        {"0x0.fffffffffffffp-1022", 0x0.fffffffffffffp-1022},    // +LargestDenormal
+        {"-0x1p-1074", -0x1p-1074},                              // -SmallestDenormal
+        {"-0x1p-1073", -0x1p-1073},                              // -BiggerDenormal
+        {"-0x1.ffffffffffffep-1023", -0x1.ffffffffffffep-1023},  // -LargestDenormal
+        {"-0x0.fffffffffffffp-1022", -0x0.fffffffffffffp-1022},  // -LargestDenormal
 
         {"0x0.cafebeeff000dp-1022", 0x0.cafebeeff000dp-1022},    // +Subnormal
         {"-0x0.cafebeeff000dp-1022", -0x0.cafebeeff000dp-1022},  // -Subnormal
@@ -301,21 +354,29 @@
         {"-0x1.2bfaf8p-1052", -0x1.2bfaf8p-1052},                // +Subnormal
         {"0x1.55554p-1055", 0x1.55554p-1055},                    // +Subnormal
         {"-0x1.55554p-1055", -0x1.55554p-1055},                  // -Subnormal
+        {"0x1.fffffffffffp-1027", 0x1.fffffffffffp-1027},  // +Subnormal, = 0x0.0fffffffffff8p-1022
+        {"-0x1.fffffffffffp-1027", -0x1.fffffffffffp-1027},  // -Subnormal
 
         // F32 extremities
-        {"0x1p-149", 0x1p-149},                  // +SmallestDenormal
-        {"0x1p-148", 0x1p-148},                  // +BiggerDenormal
-        {"0x1.fffffcp-127", 0x1.fffffcp-127},    // +LargestDenormal
-        {"-0x1p-149", -0x1p-149},                // -SmallestDenormal
-        {"-0x1p-148", -0x1p-148},                // -BiggerDenormal
-        {"-0x1.fffffcp-127", -0x1.fffffcp-127},  // -LargestDenormal
+        {"0x1p-149f", 0x1p-149},                  // +SmallestDenormal
+        {"0x1p-148f", 0x1p-148},                  // +BiggerDenormal
+        {"0x1.fffffcp-127f", 0x1.fffffcp-127},    // +LargestDenormal
+        {"0x0.fffffep-126f", 0x0.fffffep-126},    // +LargestDenormal
+        {"0x1.0p-126f", 0x1.0p-126},              // +SmallestNormal
+        {"0x8.0p-129f", 0x8.0p-129},              // +SmallestNormal
+        {"-0x1p-149f", -0x1p-149},                // -SmallestDenormal
+        {"-0x1p-148f", -0x1p-148},                // -BiggerDenormal
+        {"-0x1.fffffcp-127f", -0x1.fffffcp-127},  // -LargestDenormal
+        {"-0x0.fffffep-126f", -0x0.fffffep-126},  // -LargestDenormal
+        {"-0x1.0p-126f", -0x1.0p-126},            // -SmallestNormal
+        {"-0x8.0p-129f", -0x8.0p-129},            // -SmallestNormal
 
-        {"0x0.cafebp-129", 0x0.cafebp-129},      // +Subnormal
-        {"-0x0.cafebp-129", -0x0.cafebp-129},    // -Subnormal
-        {"0x1.2bfaf8p-127", 0x1.2bfaf8p-127},    // +Subnormal
-        {"-0x1.2bfaf8p-127", -0x1.2bfaf8p-127},  // -Subnormal
-        {"0x1.55554p-130", 0x1.55554p-130},      // +Subnormal
-        {"-0x1.55554p-130", -0x1.55554p-130},    // -Subnormal
+        {"0x0.cafebp-129f", 0x0.cafebp-129},      // +Subnormal
+        {"-0x0.cafebp-129f", -0x0.cafebp-129},    // -Subnormal
+        {"0x1.2bfaf8p-127f", 0x1.2bfaf8p-127},    // +Subnormal
+        {"-0x1.2bfaf8p-127f", -0x1.2bfaf8p-127},  // -Subnormal
+        {"0x1.55554p-130f", 0x1.55554p-130},      // +Subnormal
+        {"-0x1.55554p-130f", -0x1.55554p-130},    // -Subnormal
 
         // F32 exactly representable
         {"0x1.000002p+0f", 0x1.000002p+0},
@@ -324,10 +385,47 @@
         {"0x8.00003p+0f", 0x8.00003p+0},
         {"0x2.123p+0f", 0x2.123p+0},
         {"0x2.cafefp+0f", 0x2.cafefp+0},
+        {"0x0.0000fep-126f", 0x0.0000fep-126},    // Subnormal
+        {"-0x0.0000fep-126f", -0x0.0000fep-126},  // Subnormal
+        {"0x3.f8p-144f", 0x3.f8p-144},            // Subnormal
+        {"-0x3.f8p-144f", -0x3.f8p-144},          // Subnormal
+
+        // F16 extremities
+        {"0x1p-24h", 0x1p-24},            // +SmallestDenormal
+        {"0x1p-23h", 0x1p-23},            // +BiggerDenormal
+        {"0x1.ff8p-15h", 0x1.ff8p-15},    // +LargestDenormal
+        {"0x0.ffcp-14h", 0x0.ffcp-14},    // +LargestDenormal
+        {"0x1.0p-14h", 0x1.0p-14},        // +SmallestNormal
+        {"0x8.0p-17h", 0x8.0p-17},        // +SmallestNormal
+        {"-0x1p-24h", -0x1p-24},          // -SmallestDenormal
+        {"-0x1p-23h", -0x1p-23},          // -BiggerDenormal
+        {"-0x1.ff8p-15h", -0x1.ff8p-15},  // -LargestDenormal
+        {"-0x0.ffcp-14h", -0x0.ffcp-14},  // -LargestDenormal
+        {"-0x1.0p-14h", -0x1.0p-14},      // -SmallestNormal
+        {"-0x8.0p-17h", -0x8.0p-17},      // -SmallestNormal
+
+        {"0x0.a8p-19h", 0x0.a8p-19},    // +Subnormal
+        {"-0x0.a8p-19h", -0x0.a8p-19},  // -Subnormal
+        {"0x1.7ap-17h", 0x1.7ap-17},    // +Subnormal
+        {"-0x1.7ap-17h", -0x1.7ap-17},  // -Subnormal
+        {"0x1.dp-20h", 0x1.dp-20},      // +Subnormal
+        {"-0x1.dp-20h", -0x1.dp-20},    // -Subnormal
+
+        // F16 exactly representable
+        {"0x1.004p+0h", 0x1.004p+0},
+        {"0x8.02p+0h", 0x8.02p+0},
+        {"0x8.fep+0h", 0x8.fep+0},
+        {"0x8.06p+0h", 0x8.06p+0},
+        {"0x2.128p+0h", 0x2.128p+0},
+        {"0x2.ca8p+0h", 0x2.ca8p+0},
+        {"0x0.0fcp-14h", 0x0.0fcp-14},    // Subnormal
+        {"-0x0.0fcp-14h", -0x0.0fcp-14},  // Subnormal
+        {"0x3.f00p-20h", 0x3.f00p-20},    // Subnormal
+        {"-0x3.f00p-20h", -0x3.f00p-20},  // Subnormal
 
         // Underflow -> Zero
-        {"0x1p-1074", 0.0},  // Exponent underflows
-        {"-0x1p-1074", 0.0},
+        {"0x1p-1075", 0.0},  // Exponent underflows
+        {"-0x1p-1075", 0.0},
         {"0x1p-5000", 0.0},
         {"-0x1p-5000", 0.0},
         {"0x0.00000000000000000000001p-1022", 0.0},  // Fraction causes underflow
@@ -399,6 +497,16 @@
         {"-0x.8p2f", -2.0},
         {"-0x1.8p-1f", -0.75},
         {"-0x2p-2f", -0.5},  // No binary point
+
+        // Examples with a binary exponent and a 'h' suffix.
+        {"0x1.p0h", 1.0},
+        {"0x.8p2h", 2.0},
+        {"0x1.8p-1h", 0.75},
+        {"0x2p-2h", 0.5},  // No binary point
+        {"-0x1.p0h", -1.0},
+        {"-0x.8p2h", -2.0},
+        {"-0x1.8p-1h", -0.75},
+        {"-0x2p-2h", -0.5},  // No binary point
     };
 }
 INSTANTIATE_TEST_SUITE_P(ParserImplFloatLiteralTest_HexFloat,
@@ -542,6 +650,23 @@
                      })));
 
 INSTANTIATE_TEST_SUITE_P(
+    HexNaNF16,
+    ParserImplInvalidLiteralTest,
+    testing::Combine(testing::Values("1:1: value cannot be represented as 'f16'"),
+                     testing::ValuesIn(std::vector<const char*>{
+                         "0x1.8p+16h",
+                         "0x1.004p+16h",
+                         "0x1.018p+16h",
+                         "0x1.1ep+16h",
+                         "0x1.ffcp+16h",
+                         "-0x1.8p+16h",
+                         "-0x1.004p+16h",
+                         "-0x1.018p+16h",
+                         "-0x1.1ep+16h",
+                         "-0x1.ffcp+16h",
+                     })));
+
+INSTANTIATE_TEST_SUITE_P(
     HexOverflowAFloat,
     ParserImplInvalidLiteralTest,
     testing::Combine(testing::Values("1:1: value cannot be represented as 'abstract-float'"),
@@ -578,17 +703,94 @@
                      })));
 
 INSTANTIATE_TEST_SUITE_P(
+    HexOverflowF16,
+    ParserImplInvalidLiteralTest,
+    testing::Combine(testing::Values("1:1: value cannot be represented as 'f16'"),
+                     testing::ValuesIn(std::vector<const char*>{
+                         "0x1p+16h",
+                         "-0x1p+16h",
+                         "0x1.1p+16h",
+                         "-0x1.1p+16h",
+                         "0x1p+17h",
+                         "-0x1p+17h",
+                         "0x32p+15h",
+                         "-0x32p+15h",
+                         "0x32p+500h",
+                         "-0x32p+500h",
+                     })));
+
+INSTANTIATE_TEST_SUITE_P(
     HexNotExactlyRepresentableF32,
     ParserImplInvalidLiteralTest,
     testing::Combine(testing::Values("1:1: value cannot be exactly represented as 'f32'"),
                      testing::ValuesIn(std::vector<const char*>{
-                         "0x1.000001p+0f",    // Quantizes to 0x1.0p+0
-                         "0x8.0000f8p+0f",    // Quantizes to 0x8.0000fp+0
-                         "0x8.000038p+0f",    // Quantizes to 0x8.00003p+0
-                         "0x2.cafef00dp+0f",  // Quantizes to 0x2.cafefp+0
+                         "0x1.000001p+0f",            // Quantizes to 0x1.0p+0
+                         "0x1.0000008p+0f",           // Quantizes to 0x1.0p+0
+                         "0x1.0000000000001p+0f",     // Quantizes to 0x1.0p+0
+                         "0x8.0000f8p+0f",            // Quantizes to 0x8.0000fp+0
+                         "0x8.000038p+0f",            // Quantizes to 0x8.00003p+0
+                         "0x2.cafef00dp+0f",          // Quantizes to 0x2.cafefp+0
+                         "0x0.0000ffp-126f",          // Subnormal, quantizes to 0x0.0000fep-126
+                         "0x3.fcp-144f",              // Subnormal, quantizes to 0x3.f8p-144
+                         "-0x0.0000ffp-126f",         // Subnormal, quantizes to -0x0.0000fep-126
+                         "-0x3.fcp-144f",             // Subnormal, quantizes to -0x3.f8p-144
+                         "0x0.ffffffp-126f",          // Subnormal, quantizes to 0x0.fffffep-144
+                         "0x0.fffffe0000001p-126f",   // Subnormal, quantizes to 0x0.fffffep-144
+                         "-0x0.ffffffp-126f",         // Subnormal, quantizes to -0x0.fffffep-144
+                         "-0x0.fffffe0000001p-126f",  // Subnormal, quantizes to -0x0.fffffep-144
+                         "0x1.8p-149f",               // Subnormal, quantizes to 0x1.0p-149f
+                         "0x1.4p-149f",               // Subnormal, quantizes to 0x1.0p-149f
+                         "0x1.000002p-149f",          // Subnormal, quantizes to 0x1.0p-149f
+                         "0x1.0000000000001p-149f",   // Subnormal, quantizes to 0x1.0p-149f
+                         "-0x1.8p-149f",              // Subnormal, quantizes to -0x1.0p-149f
+                         "-0x1.4p-149f",              // Subnormal, quantizes to -0x1.0p-149f
+                         "-0x1.000002p-149f",         // Subnormal, quantizes to -0x1.0p-149f
+                         "-0x1.0000000000001p-149f",  // Subnormal, quantizes to -0x1.0p-149f
+                         "0x1.0p-150f",   // Smaller than the smallest subnormal, quantizes to 0.0
+                         "0x1.8p-150f",   // Smaller than the smallest subnormal, quantizes to 0.0
+                         "-0x1.0p-150f",  // Smaller than the smallest subnormal, quantizes to -0.0
+                         "-0x1.8p-150f",  // Smaller than the smallest subnormal, quantizes to -0.0
                      })));
 
 INSTANTIATE_TEST_SUITE_P(
+    HexNotExactlyRepresentableF16,
+    ParserImplInvalidLiteralTest,
+    testing::Combine(
+        testing::Values("1:1: value cannot be exactly represented as 'f16'"),
+        testing::ValuesIn(std::vector<const char*>{
+            "0x1.002p+0h",             // Quantizes to 0x1.0p+0, has 11 mantissa bits rather than 10
+            "0x1.001p+0h",             // Quantizes to 0x1.0p+0, has 12 mantissa bits rather than 10
+            "0x1.0000000000001p+0h",   // Quantizes to 0x1.0p+0, has 52 mantissa bits rather than 10
+            "0x8.0fp+0h",              // Quantizes to 0x8.0ep+0
+            "0x8.31p+0h",              // Quantizes to 0x8.30p+0
+            "0x2.ca80dp+0h",           // Quantizes to 0x2.ca8p+0
+            "0x4.ba8p+0h",             // Quantizes to 0x4.bap+0
+            "0x4.011p+0h",             // Quantizes to 0x4.01p+0
+            "0x0.0fep-14h",            // Subnormal, quantizes to 0x0.0fcp-14
+            "0x3.f8p-20h",             // Subnormal, quantizes to 0x3.f0p-20
+            "-0x0.0fep-14h",           // Subnormal, quantizes to -0x0.0fcp-14
+            "-0x3.f8p-20h",            // Subnormal, quantizes to -0x3.f0p-20
+            "0x0.ffep-14h",            // Subnormal, quantizes to 0x0.ffcp-14
+            "0x0.ffe0000000001p-14h",  // Subnormal, quantizes to 0x0.ffcp-14
+            "0x0.fffffffffffffp-14h",  // Subnormal, quantizes to 0x0.ffcp-14
+            "-0x0.ffep-14h",           // Subnormal, quantizes to -0x0.ffcp-14
+            "-0x0.ffe0000000001p-14h",  // Subnormal, quantizes to -0x0.ffcp-14
+            "-0x0.fffffffffffffp-14h",  // Subnormal, quantizes to -0x0.ffcp-14
+            "0x1.8p-24h",               // Subnormal, quantizes to 0x1.0p-24f
+            "0x1.4p-24h",               // Subnormal, quantizes to 0x1.0p-24f
+            "0x1.004p-24h",             // Subnormal, quantizes to 0x1.0p-24f
+            "0x1.0000000000001p-24h",   // Subnormal, quantizes to 0x1.0p-24f
+            "-0x1.8p-24h",              // Subnormal, quantizes to -0x1.0p-24f
+            "-0x1.4p-24h",              // Subnormal, quantizes to -0x1.0p-24f
+            "-0x1.004p-24h",            // Subnormal, quantizes to -0x1.0p-24f
+            "-0x1.0000000000001p-24h",  // Subnormal, quantizes to -0x1.0p-24f
+            "0x1.0p-25h",               // Smaller than the smallest subnormal, quantizes to 0.0
+            "0x1.8p-25h",               // Smaller than the smallest subnormal, quantizes to 0.0
+            "-0x1.0p-25h",              // Smaller than the smallest subnormal, quantizes to -0.0
+            "-0x1.8p-25h",              // Smaller than the smallest subnormal, quantizes to -0.0
+        })));
+
+INSTANTIATE_TEST_SUITE_P(
     DecOverflowAFloat,
     ParserImplInvalidLiteralTest,
     testing::Combine(testing::Values("1:1: value cannot be represented as 'abstract-float'"),
@@ -622,6 +824,25 @@
                          "-1.2e+256f",
                      })));
 
+INSTANTIATE_TEST_SUITE_P(
+    DecOverflowF16,
+    ParserImplInvalidLiteralTest,
+    testing::Combine(testing::Values("1:1: value cannot be represented as 'f16'"),
+                     testing::ValuesIn(std::vector<const char*>{
+                         "1.0e5h",
+                         "-1.0e5h",
+                         "7.0e4h",
+                         "-7.0e4h",
+                         "6.6e4h",
+                         "-6.6e4h",
+                         "6.56e4h",
+                         "-6.56e4h",
+                         "6.554e4h",
+                         "-6.554e4h",
+                         "1.2e+32h",
+                         "-1.2e+32h",
+                     })));
+
 TEST_F(ParserImplTest, ConstLiteral_FloatHighest) {
     const auto highest = std::numeric_limits<float>::max();
     const auto expected_highest = 340282346638528859811704183484516925440.0f;
@@ -636,8 +857,7 @@
     EXPECT_FALSE(p->has_error()) << p->error();
     ASSERT_NE(c.value, nullptr);
     ASSERT_TRUE(c->Is<ast::FloatLiteralExpression>());
-    EXPECT_DOUBLE_EQ(c->As<ast::FloatLiteralExpression>()->value,
-                     std::numeric_limits<float>::max());
+    EXPECT_EQ(c->As<ast::FloatLiteralExpression>()->value, std::numeric_limits<float>::max());
     EXPECT_EQ(c->As<ast::FloatLiteralExpression>()->suffix,
               ast::FloatLiteralExpression::Suffix::kNone);
     EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 42u}}));
@@ -660,8 +880,7 @@
     EXPECT_FALSE(p->has_error()) << p->error();
     ASSERT_NE(c.value, nullptr);
     ASSERT_TRUE(c->Is<ast::FloatLiteralExpression>());
-    EXPECT_DOUBLE_EQ(c->As<ast::FloatLiteralExpression>()->value,
-                     std::numeric_limits<float>::lowest());
+    EXPECT_EQ(c->As<ast::FloatLiteralExpression>()->value, std::numeric_limits<float>::lowest());
     EXPECT_EQ(c->As<ast::FloatLiteralExpression>()->suffix,
               ast::FloatLiteralExpression::Suffix::kNone);
     EXPECT_EQ(c->source.range, (Source::Range{{1u, 1u}, {1u, 43u}}));
diff --git a/src/tint/reader/wgsl/parser_impl_error_msg_test.cc b/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
index ab97d94..972e118 100644
--- a/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_error_msg_test.cc
@@ -316,8 +316,13 @@
 }
 
 TEST_F(ParserImplErrorTest, FunctionDeclStageMissingRParen) {
-    EXPECT("@stage(vertex fn f() {}",
-           R"(test.wgsl:1:15 error: expected ')' for stage attribute
+    EXPECT(
+        "@stage(vertex fn f() {}",
+        R"(test.wgsl:1:2 warning: use of deprecated language feature: remove stage and use @vertex
+@stage(vertex fn f() {}
+ ^^^^^
+
+test.wgsl:1:15 error: expected ')' for stage attribute
 @stage(vertex fn f() {}
               ^^
 )");
diff --git a/src/tint/reader/wgsl/parser_impl_function_attribute_test.cc b/src/tint/reader/wgsl/parser_impl_function_attribute_test.cc
index 44f3d78..a32e140 100644
--- a/src/tint/reader/wgsl/parser_impl_function_attribute_test.cc
+++ b/src/tint/reader/wgsl/parser_impl_function_attribute_test.cc
@@ -263,7 +263,8 @@
     EXPECT_TRUE(attr.errored);
     EXPECT_EQ(attr.value, nullptr);
     EXPECT_TRUE(p->has_error());
-    EXPECT_EQ(p->error(), "1:14: expected ')' for stage attribute");
+    EXPECT_EQ(p->error(), R"(1:1: use of deprecated language feature: remove stage and use @compute
+1:14: expected ')' for stage attribute)");
 }
 
 TEST_F(ParserImplTest, Attribute_Compute) {
diff --git a/src/tint/reader/wgsl/token.cc b/src/tint/reader/wgsl/token.cc
index 4680eee..dcd72cf 100644
--- a/src/tint/reader/wgsl/token.cc
+++ b/src/tint/reader/wgsl/token.cc
@@ -29,6 +29,8 @@
             return "abstract float literal";
         case Token::Type::kFloatLiteral_F:
             return "'f'-suffixed float literal";
+        case Token::Type::kFloatLiteral_H:
+            return "'h'-suffixed float literal";
         case Token::Type::kIntLiteral:
             return "abstract integer literal";
         case Token::Type::kIntLiteral_I:
@@ -311,6 +313,8 @@
             return std::to_string(std::get<double>(value_));
         case Type::kFloatLiteral_F:
             return std::to_string(std::get<double>(value_)) + "f";
+        case Type::kFloatLiteral_H:
+            return std::to_string(std::get<double>(value_)) + "h";
         case Type::kIntLiteral:
             return std::to_string(std::get<int64_t>(value_));
         case Type::kIntLiteral_I:
diff --git a/src/tint/reader/wgsl/token.h b/src/tint/reader/wgsl/token.h
index 0a68f9b0..9587f36 100644
--- a/src/tint/reader/wgsl/token.h
+++ b/src/tint/reader/wgsl/token.h
@@ -42,6 +42,8 @@
         kFloatLiteral,
         /// A float literal with an 'f' suffix
         kFloatLiteral_F,
+        /// A float literal with an 'h' suffix
+        kFloatLiteral_H,
         /// An integer literal with no suffix
         kIntLiteral,
         /// An integer literal with an 'i' suffix
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index 90ae4b0..b1f5a95 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -1658,10 +1658,15 @@
             return nullptr;
         },
         [&](const ast::FloatLiteralExpression* f) -> sem::Type* {
-            if (f->suffix == ast::FloatLiteralExpression::Suffix::kNone) {
-                return builder_->create<sem::AbstractFloat>();
+            switch (f->suffix) {
+                case ast::FloatLiteralExpression::Suffix::kNone:
+                    return builder_->create<sem::AbstractFloat>();
+                case ast::FloatLiteralExpression::Suffix::kF:
+                    return builder_->create<sem::F32>();
+                case ast::FloatLiteralExpression::Suffix::kH:
+                    return builder_->create<sem::F16>();
             }
-            return builder_->create<sem::F32>();
+            return nullptr;
         },
         [&](const ast::BoolLiteralExpression*) { return builder_->create<sem::Bool>(); },
         [&](Default) { return nullptr; });
@@ -1672,6 +1677,11 @@
         return nullptr;
     }
 
+    if ((ty->Is<sem::F16>()) && (!enabled_extensions_.contains(tint::ast::Extension::kF16))) {
+        AddError("f16 literal used without 'f16' extension enabled", literal->source);
+        return nullptr;
+    }
+
     auto val = EvaluateConstantValue(literal, ty);
     if (!val) {
         return nullptr;
diff --git a/src/tint/resolver/resolver_test.cc b/src/tint/resolver/resolver_test.cc
index 16725ba..38f4d49 100644
--- a/src/tint/resolver/resolver_test.cc
+++ b/src/tint/resolver/resolver_test.cc
@@ -2134,5 +2134,22 @@
                                         std::to_string(kMaxExpressionDepth)));
 }
 
+TEST_F(ResolverTest, Literal_F16WithoutExtension) {
+    // fn test() {_ = 1.23h;}
+    WrapInFunction(Ignore(Expr(f16(1.23f))));
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_THAT(r()->error(), HasSubstr("error: f16 literal used without 'f16' extension enabled"));
+}
+
+TEST_F(ResolverTest, Literal_F16WithExtension) {
+    // enable f16;
+    // fn test() {_ = 1.23h;}
+    Enable(ast::Extension::kF16);
+    WrapInFunction(Ignore(Expr(f16(1.23f))));
+
+    EXPECT_TRUE(r()->Resolve());
+}
+
 }  // namespace
 }  // namespace tint::resolver
diff --git a/src/tint/resolver/uniformity.cc b/src/tint/resolver/uniformity.cc
index 97612a4..2bbb487 100644
--- a/src/tint/resolver/uniformity.cc
+++ b/src/tint/resolver/uniformity.cc
@@ -305,7 +305,7 @@
     /// @param ast the optional AST node that this node corresponds to
     /// @returns the new node
     Node* CreateNode(std::string tag, const ast::Node* ast = nullptr) {
-        return current_function_->CreateNode(tag, ast);
+        return current_function_->CreateNode(std::move(tag), ast);
     }
 
     /// Process a function.
@@ -1248,6 +1248,10 @@
                     if (func_info->parameters[i].pointer_may_become_non_uniform) {
                         ptr_result->AddEdge(current_function_->may_be_non_uniform);
                     } else {
+                        // Add edge to the call to catch when it's called in non-uniform control
+                        // flow.
+                        ptr_result->AddEdge(call_node);
+
                         // Add edges from the resulting pointer value to any other arguments that
                         // feed it.
                         for (auto* source : func_info->parameters[i].pointer_param_output_sources) {
diff --git a/src/tint/resolver/uniformity_test.cc b/src/tint/resolver/uniformity_test.cc
index 7ed4a6b..d661099 100644
--- a/src/tint/resolver/uniformity_test.cc
+++ b/src/tint/resolver/uniformity_test.cc
@@ -4624,6 +4624,145 @@
 )");
 }
 
+TEST_F(UniformityAnalysisTest, PointerParamModifiedInNonUniformControlFlow) {
+    std::string src = R"(
+@binding(0) @group(0) var<storage, read_write> non_uniform_global : i32;
+
+fn foo(p : ptr<function, i32>) {
+  *p = 42;
+}
+
+@compute @workgroup_size(64)
+fn main() {
+  var a : i32;
+  if (non_uniform_global == 0) {
+    foo(&a);
+  }
+
+  if (a == 0) {
+    workgroupBarrier();
+  }
+}
+)";
+
+    RunTest(src, false);
+    EXPECT_EQ(error_,
+              R"(test:16:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+    workgroupBarrier();
+    ^^^^^^^^^^^^^^^^
+
+test:11:3 note: control flow depends on non-uniform value
+  if (non_uniform_global == 0) {
+  ^^
+
+test:11:7 note: reading from read_write storage buffer 'non_uniform_global' may result in a non-uniform value
+  if (non_uniform_global == 0) {
+      ^^^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, PointerParamAssumedModifiedInNonUniformControlFlow) {
+    std::string src = R"(
+@binding(0) @group(0) var<storage, read_write> non_uniform_global : i32;
+
+fn foo(p : ptr<function, i32>) {
+  // Do not modify 'p', uniformity analysis presently assumes it will be.
+}
+
+@compute @workgroup_size(64)
+fn main() {
+  var a : i32;
+  if (non_uniform_global == 0) {
+    foo(&a);
+  }
+
+  if (a == 0) {
+    workgroupBarrier();
+  }
+}
+)";
+
+    RunTest(src, false);
+    EXPECT_EQ(error_,
+              R"(test:16:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+    workgroupBarrier();
+    ^^^^^^^^^^^^^^^^
+
+test:11:3 note: control flow depends on non-uniform value
+  if (non_uniform_global == 0) {
+  ^^
+
+test:11:7 note: reading from read_write storage buffer 'non_uniform_global' may result in a non-uniform value
+  if (non_uniform_global == 0) {
+      ^^^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, PointerParamModifiedInNonUniformControlFlow_NestedCall) {
+    std::string src = R"(
+@binding(0) @group(0) var<storage, read_write> non_uniform_global : i32;
+
+fn foo2(p : ptr<function, i32>) {
+  *p = 42;
+}
+
+fn foo(p : ptr<function, i32>) {
+  foo2(p);
+}
+
+@compute @workgroup_size(64)
+fn main() {
+  var a : i32;
+  if (non_uniform_global == 0) {
+    foo(&a);
+  }
+
+  if (a == 0) {
+    workgroupBarrier();
+  }
+}
+)";
+
+    RunTest(src, false);
+    EXPECT_EQ(error_,
+              R"(test:20:5 warning: 'workgroupBarrier' must only be called from uniform control flow
+    workgroupBarrier();
+    ^^^^^^^^^^^^^^^^
+
+test:15:3 note: control flow depends on non-uniform value
+  if (non_uniform_global == 0) {
+  ^^
+
+test:15:7 note: reading from read_write storage buffer 'non_uniform_global' may result in a non-uniform value
+  if (non_uniform_global == 0) {
+      ^^^^^^^^^^^^^^^^^^
+)");
+}
+
+TEST_F(UniformityAnalysisTest, PointerParamModifiedInUniformControlFlow) {
+    std::string src = R"(
+@binding(0) @group(0) var<uniform> uniform_global : i32;
+
+fn foo(p : ptr<function, i32>) {
+  *p = 42;
+}
+
+@compute @workgroup_size(64)
+fn main() {
+  var a : i32;
+  if (uniform_global == 0) {
+    foo(&a);
+  }
+
+  if (a == 0) {
+    workgroupBarrier();
+  }
+}
+)";
+
+    RunTest(src, true);
+}
+
 TEST_F(UniformityAnalysisTest, NonUniformPointerParameterBecomesUniform_AfterUse) {
     std::string src = R"(
 @group(0) @binding(0) var<storage, read_write> non_uniform : i32;