Import Tint changes from Dawn

Changes:
  - 205e16de63a2a642394202ab57a06ff07273064d tint/utils: Add Vector::Sort() by Ben Clayton <bclayton@google.com>
  - d205b716cac39c85255739ecae340637578537be tint/resolver: forbid spelling non-instantiable pointer t... by Ben Clayton <bclayton@google.com>
  - 31e0850560d3cd937833c3111a41750eb49e585e [ir] Convert instruction to a pointer by dan sinclair <dsinclair@chromium.org>
  - e272eaf30f35389e626d91f64a832d1195c379b9 [ir] Split Value into Temp and Constant. by dan sinclair <dsinclair@chromium.org>
  - 34afd9b6c6016df4287dc5fe5277000ce56c16f2 tint/resolver: Clean up variable validation by Ben Clayton <bclayton@google.com>
  - 75b18674bf9846de04ecd223d7d7fab7066b0c5b tint: Add Symbol inequality operator by Ben Clayton <bclayton@google.com>
  - c158e845e65cfccac35146b908b55dd668025741 tint/utils: Add Hashmap equality and hashing by Ben Clayton <bclayton@google.com>
  - 2d108ae5ed54fc4d6eefe31bac44cf6d984cd229 [ir] Make Value a pointer stored in the module. by dan sinclair <dsinclair@chromium.org>
  - 028092a84345815d2d574e4334bbf4d20b58f8f5 tint/utils: Add Hashmap::Keys(), Hashmap::Values() by Ben Clayton <bclayton@google.com>
  - 05f3ebfc6892c758670a53235183015869c8a5f5 tint/utils: Make Hashmap iterator values mutable by Ben Clayton <bclayton@google.com>
  - b200e74715e8cac7ec9ac3ca47c1367b859d3503 tint: simplify const eval binary op unit tests by Antonio Maiorano <amaiorano@google.com>
  - 775dfbf7a8df0b918ea4d9385b08d14de0518af2 tint: fix const eval of divide to emit errors on dbz and ... by Antonio Maiorano <amaiorano@google.com>
  - ec7a1d2a1b276c44b1781b99720992535ca37401 [ir] Remove the VarData from Value. by dan sinclair <dsinclair@chromium.org>
GitOrigin-RevId: 205e16de63a2a642394202ab57a06ff07273064d
Change-Id: Ifac1dd1105497bd7d95f4e023c2f3331d3c8d298
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/112360
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 06aa483..15532b2 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -656,6 +656,8 @@
     ir/builder.h
     ir/builder_impl.cc
     ir/builder_impl.h
+    ir/constant.cc
+    ir/constant.h
     ir/debug.cc
     ir/debug.h
     ir/disassembler.cc
@@ -674,6 +676,8 @@
     ir/module.h
     ir/switch.cc
     ir/switch.h
+    ir/temp.cc
+    ir/temp.h
     ir/terminator.cc
     ir/terminator.h
     ir/value.cc
@@ -1341,9 +1345,10 @@
   if (${TINT_BUILD_IR})
     list(APPEND TINT_TEST_SRCS
       ir/builder_impl_test.cc
+      ir/constant_test.cc
       ir/instruction_test.cc
+      ir/temp_test.cc
       ir/test_helper.h
-      ir/value_test.cc
     )
   endif()
 
diff --git a/src/tint/ir/block.h b/src/tint/ir/block.h
index d27fdf4..ac9028b 100644
--- a/src/tint/ir/block.h
+++ b/src/tint/ir/block.h
@@ -34,7 +34,7 @@
     const FlowNode* branch_target = nullptr;
 
     /// The instructions in the block
-    utils::Vector<Instruction, 16> instructions;
+    utils::Vector<const Instruction*, 16> instructions;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/builder.cc b/src/tint/ir/builder.cc
index 0403807..5c55b91 100644
--- a/src/tint/ir/builder.cc
+++ b/src/tint/ir/builder.cc
@@ -93,83 +93,85 @@
     to->inbound_branches.Push(from);
 }
 
-Value::Id Builder::AllocateValue() {
-    return next_value_id++;
+Temp::Id Builder::AllocateTempId() {
+    return next_temp_id++;
 }
 
-Instruction Builder::CreateInstruction(Instruction::Kind kind, Value lhs, Value rhs) {
-    return Instruction(kind, Value(AllocateValue()), lhs, rhs);
+const Instruction* Builder::CreateInstruction(Instruction::Kind kind,
+                                              const Value* lhs,
+                                              const Value* rhs) {
+    return ir.instructions.Create<ir::Instruction>(kind, Temp(), lhs, rhs);
 }
 
-Instruction Builder::And(Value lhs, Value rhs) {
+const Instruction* Builder::And(const Value* lhs, const Value* rhs) {
     return CreateInstruction(Instruction::Kind::kAnd, lhs, rhs);
 }
 
-Instruction Builder::Or(Value lhs, Value rhs) {
+const Instruction* Builder::Or(const Value* lhs, const Value* rhs) {
     return CreateInstruction(Instruction::Kind::kOr, lhs, rhs);
 }
 
-Instruction Builder::Xor(Value lhs, Value rhs) {
+const Instruction* Builder::Xor(const Value* lhs, const Value* rhs) {
     return CreateInstruction(Instruction::Kind::kXor, lhs, rhs);
 }
 
-Instruction Builder::LogicalAnd(Value lhs, Value rhs) {
+const Instruction* Builder::LogicalAnd(const Value* lhs, const Value* rhs) {
     return CreateInstruction(Instruction::Kind::kLogicalAnd, lhs, rhs);
 }
 
-Instruction Builder::LogicalOr(Value lhs, Value rhs) {
+const Instruction* Builder::LogicalOr(const Value* lhs, const Value* rhs) {
     return CreateInstruction(Instruction::Kind::kLogicalOr, lhs, rhs);
 }
 
-Instruction Builder::Equal(Value lhs, Value rhs) {
+const Instruction* Builder::Equal(const Value* lhs, const Value* rhs) {
     return CreateInstruction(Instruction::Kind::kEqual, lhs, rhs);
 }
 
-Instruction Builder::NotEqual(Value lhs, Value rhs) {
+const Instruction* Builder::NotEqual(const Value* lhs, const Value* rhs) {
     return CreateInstruction(Instruction::Kind::kNotEqual, lhs, rhs);
 }
 
-Instruction Builder::LessThan(Value lhs, Value rhs) {
+const Instruction* Builder::LessThan(const Value* lhs, const Value* rhs) {
     return CreateInstruction(Instruction::Kind::kLessThan, lhs, rhs);
 }
 
-Instruction Builder::GreaterThan(Value lhs, Value rhs) {
+const Instruction* Builder::GreaterThan(const Value* lhs, const Value* rhs) {
     return CreateInstruction(Instruction::Kind::kGreaterThan, lhs, rhs);
 }
 
-Instruction Builder::LessThanEqual(Value lhs, Value rhs) {
+const Instruction* Builder::LessThanEqual(const Value* lhs, const Value* rhs) {
     return CreateInstruction(Instruction::Kind::kLessThanEqual, lhs, rhs);
 }
 
-Instruction Builder::GreaterThanEqual(Value lhs, Value rhs) {
+const Instruction* Builder::GreaterThanEqual(const Value* lhs, const Value* rhs) {
     return CreateInstruction(Instruction::Kind::kGreaterThanEqual, lhs, rhs);
 }
 
-Instruction Builder::ShiftLeft(Value lhs, Value rhs) {
+const Instruction* Builder::ShiftLeft(const Value* lhs, const Value* rhs) {
     return CreateInstruction(Instruction::Kind::kShiftLeft, lhs, rhs);
 }
 
-Instruction Builder::ShiftRight(Value lhs, Value rhs) {
+const Instruction* Builder::ShiftRight(const Value* lhs, const Value* rhs) {
     return CreateInstruction(Instruction::Kind::kShiftRight, lhs, rhs);
 }
 
-Instruction Builder::Add(Value lhs, Value rhs) {
+const Instruction* Builder::Add(const Value* lhs, const Value* rhs) {
     return CreateInstruction(Instruction::Kind::kAdd, lhs, rhs);
 }
 
-Instruction Builder::Subtract(Value lhs, Value rhs) {
+const Instruction* Builder::Subtract(const Value* lhs, const Value* rhs) {
     return CreateInstruction(Instruction::Kind::kSubtract, lhs, rhs);
 }
 
-Instruction Builder::Multiply(Value lhs, Value rhs) {
+const Instruction* Builder::Multiply(const Value* lhs, const Value* rhs) {
     return CreateInstruction(Instruction::Kind::kMultiply, lhs, rhs);
 }
 
-Instruction Builder::Divide(Value lhs, Value rhs) {
+const Instruction* Builder::Divide(const Value* lhs, const Value* rhs) {
     return CreateInstruction(Instruction::Kind::kDivide, lhs, rhs);
 }
 
-Instruction Builder::Modulo(Value lhs, Value rhs) {
+const Instruction* Builder::Modulo(const Value* lhs, const Value* rhs) {
     return CreateInstruction(Instruction::Kind::kModulo, lhs, rhs);
 }
 
diff --git a/src/tint/ir/builder.h b/src/tint/ir/builder.h
index 6776d35..d5065b8 100644
--- a/src/tint/ir/builder.h
+++ b/src/tint/ir/builder.h
@@ -15,12 +15,14 @@
 #ifndef SRC_TINT_IR_BUILDER_H_
 #define SRC_TINT_IR_BUILDER_H_
 
+#include "src/tint/ir/constant.h"
 #include "src/tint/ir/function.h"
 #include "src/tint/ir/if.h"
 #include "src/tint/ir/instruction.h"
 #include "src/tint/ir/loop.h"
 #include "src/tint/ir/module.h"
 #include "src/tint/ir/switch.h"
+#include "src/tint/ir/temp.h"
 #include "src/tint/ir/terminator.h"
 #include "src/tint/ir/value.h"
 
@@ -83,129 +85,143 @@
     /// @param to the node to branch too
     void Branch(Block* from, FlowNode* to);
 
+    /// Creates a new Constant
+    /// @param val the constant value
+    /// @returns the new constant
+    template <typename T>
+    const ir::Constant* Constant(T val) {
+        return ir.values.Create<ir::Constant>(val);
+    }
+
+    /// Creates a new Temporary
+    /// @returns the new temporary
+    const ir::Temp* Temp() { return ir.values.Create<ir::Temp>(AllocateTempId()); }
+
     /// Creates an op for `lhs kind rhs`
     /// @param kind the kind of operation
     /// @param lhs the left-hand-side of the operation
     /// @param rhs the right-hand-side of the operation
     /// @returns the operation
-    Instruction CreateInstruction(Instruction::Kind kind, Value lhs, Value rhs);
+    const Instruction* CreateInstruction(Instruction::Kind kind,
+                                         const Value* lhs,
+                                         const Value* rhs);
 
     /// Creates an And operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    Instruction And(Value lhs, Value rhs);
+    const Instruction* And(const Value* lhs, const Value* rhs);
 
     /// Creates an Or operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    Instruction Or(Value lhs, Value rhs);
+    const Instruction* Or(const Value* lhs, const Value* rhs);
 
     /// Creates an Xor operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    Instruction Xor(Value lhs, Value rhs);
+    const Instruction* Xor(const Value* lhs, const Value* rhs);
 
     /// Creates an LogicalAnd operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    Instruction LogicalAnd(Value lhs, Value rhs);
+    const Instruction* LogicalAnd(const Value* lhs, const Value* rhs);
 
     /// Creates an LogicalOr operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    Instruction LogicalOr(Value lhs, Value rhs);
+    const Instruction* LogicalOr(const Value* lhs, const Value* rhs);
 
     /// Creates an Equal operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    Instruction Equal(Value lhs, Value rhs);
+    const Instruction* Equal(const Value* lhs, const Value* rhs);
 
     /// Creates an NotEqual operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    Instruction NotEqual(Value lhs, Value rhs);
+    const Instruction* NotEqual(const Value* lhs, const Value* rhs);
 
     /// Creates an LessThan operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    Instruction LessThan(Value lhs, Value rhs);
+    const Instruction* LessThan(const Value* lhs, const Value* rhs);
 
     /// Creates an GreaterThan operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    Instruction GreaterThan(Value lhs, Value rhs);
+    const Instruction* GreaterThan(const Value* lhs, const Value* rhs);
 
     /// Creates an LessThanEqual operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    Instruction LessThanEqual(Value lhs, Value rhs);
+    const Instruction* LessThanEqual(const Value* lhs, const Value* rhs);
 
     /// Creates an GreaterThanEqual operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    Instruction GreaterThanEqual(Value lhs, Value rhs);
+    const Instruction* GreaterThanEqual(const Value* lhs, const Value* rhs);
 
     /// Creates an ShiftLeft operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    Instruction ShiftLeft(Value lhs, Value rhs);
+    const Instruction* ShiftLeft(const Value* lhs, const Value* rhs);
 
     /// Creates an ShiftRight operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    Instruction ShiftRight(Value lhs, Value rhs);
+    const Instruction* ShiftRight(const Value* lhs, const Value* rhs);
 
     /// Creates an Add operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    Instruction Add(Value lhs, Value rhs);
+    const Instruction* Add(const Value* lhs, const Value* rhs);
 
     /// Creates an Subtract operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    Instruction Subtract(Value lhs, Value rhs);
+    const Instruction* Subtract(const Value* lhs, const Value* rhs);
 
     /// Creates an Multiply operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    Instruction Multiply(Value lhs, Value rhs);
+    const Instruction* Multiply(const Value* lhs, const Value* rhs);
 
     /// Creates an Divide operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    Instruction Divide(Value lhs, Value rhs);
+    const Instruction* Divide(const Value* lhs, const Value* rhs);
 
     /// Creates an Modulo operation
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    Instruction Modulo(Value lhs, Value rhs);
+    const Instruction* Modulo(const Value* lhs, const Value* rhs);
 
-    /// @returns a unique Value id
-    Value::Id AllocateValue();
+    /// @returns a unique temp id
+    Temp::Id AllocateTempId();
 
     /// The IR module.
     Module ir;
 
-    /// The next Value number to allocate
-    Value::Id next_value_id = 1;
+    /// The next temporary number to allocate
+    Temp::Id next_temp_id = 1;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/builder_impl.cc b/src/tint/ir/builder_impl.cc
index 3af54f9..2286bf4 100644
--- a/src/tint/ir/builder_impl.cc
+++ b/src/tint/ir/builder_impl.cc
@@ -516,7 +516,7 @@
     return true;
 }
 
-utils::Result<Value> BuilderImpl::EmitExpression(const ast::Expression* expr) {
+utils::Result<const Value*> BuilderImpl::EmitExpression(const ast::Expression* expr) {
     return tint::Switch(
         expr,
         // [&](const ast::IndexAccessorExpression* a) { return EmitIndexAccessor(a); },
@@ -551,7 +551,7 @@
         });
 }
 
-utils::Result<Value> BuilderImpl::EmitBinary(const ast::BinaryExpression* expr) {
+utils::Result<const Value*> BuilderImpl::EmitBinary(const ast::BinaryExpression* expr) {
     auto lhs = EmitExpression(expr->lhs);
     if (!lhs) {
         return utils::Failure;
@@ -562,7 +562,7 @@
         return utils::Failure;
     }
 
-    Instruction instr;
+    const Instruction* instr = nullptr;
     switch (expr->op) {
         case ast::BinaryOp::kAnd:
             instr = builder.And(lhs.Get(), rhs.Get());
@@ -623,26 +623,28 @@
             return utils::Failure;
     }
 
-    auto result = instr.Result();
     current_flow_block->instructions.Push(instr);
-    return result;
+    return utils::Result<const Value*>(instr->Result());
 }
 
-utils::Result<Value> BuilderImpl::EmitLiteral(const ast::LiteralExpression* lit) {
+utils::Result<const Value*> BuilderImpl::EmitLiteral(const ast::LiteralExpression* lit) {
     return tint::Switch(  //
         lit,
-        [&](const ast::BoolLiteralExpression* l) { return utils::Result<Value>{Value(l->value)}; },
+        [&](const ast::BoolLiteralExpression* l) {
+            return utils::Result<const Value*>(builder.Constant(l->value));
+        },
         [&](const ast::FloatLiteralExpression* l) {
             if (l->suffix == ast::FloatLiteralExpression::Suffix::kF) {
-                return utils::Result<Value>{Value(f32(static_cast<float>(l->value)))};
+                return utils::Result<const Value*>(
+                    builder.Constant(f32(static_cast<float>(l->value))));
             }
-            return utils::Result<Value>{Value(f16(static_cast<float>(l->value)))};
+            return utils::Result<const Value*>(builder.Constant(f16(static_cast<float>(l->value))));
         },
         [&](const ast::IntLiteralExpression* l) {
             if (l->suffix == ast::IntLiteralExpression::Suffix::kI) {
-                return utils::Result<Value>{Value(i32(l->value))};
+                return utils::Result<const Value*>(builder.Constant(i32(l->value)));
             }
-            return utils::Result<Value>{Value(u32(l->value))};
+            return utils::Result<const Value*>(builder.Constant(u32(l->value)));
         },
         [&](Default) {
             diagnostics_.add_warning(tint::diag::System::IR,
diff --git a/src/tint/ir/builder_impl.h b/src/tint/ir/builder_impl.h
index 60eda73..5090be7 100644
--- a/src/tint/ir/builder_impl.h
+++ b/src/tint/ir/builder_impl.h
@@ -140,7 +140,7 @@
     /// Emits an expression
     /// @param expr the expression to emit
     /// @returns true if successful, false otherwise
-    utils::Result<Value> EmitExpression(const ast::Expression* expr);
+    utils::Result<const Value*> EmitExpression(const ast::Expression* expr);
 
     /// Emits a variable
     /// @param var the variable to emit
@@ -150,12 +150,12 @@
     /// Emits a binary expression
     /// @param expr the binary expression
     /// @returns the value storing the result if successful, utils::Failure otherwise
-    utils::Result<Value> EmitBinary(const ast::BinaryExpression* expr);
+    utils::Result<const Value*> EmitBinary(const ast::BinaryExpression* expr);
 
     /// Emits a literal expression
     /// @param lit the literal to emit
     /// @returns true if successful, false otherwise
-    utils::Result<Value> EmitLiteral(const ast::LiteralExpression* lit);
+    utils::Result<const Value*> EmitLiteral(const ast::LiteralExpression* lit);
 
     /// Emits a type
     /// @param ty the type to emit
diff --git a/src/tint/ir/builder_impl_test.cc b/src/tint/ir/builder_impl_test.cc
index 6c8e496..79e6e3f 100644
--- a/src/tint/ir/builder_impl_test.cc
+++ b/src/tint/ir/builder_impl_test.cc
@@ -101,9 +101,10 @@
     EXPECT_EQ(flow->merge_target->branch_target, func->end_target);
 
     // Check condition
-    auto instr = flow->condition;
-    ASSERT_TRUE(instr.IsBool());
-    EXPECT_TRUE(instr.AsBool());
+    ASSERT_TRUE(flow->condition->Is<Constant>());
+    auto* instr = flow->condition->As<Constant>();
+    ASSERT_TRUE(instr->IsBool());
+    EXPECT_TRUE(instr->AsBool());
 }
 
 TEST_F(IR_BuilderImplTest, IfStatement_TrueReturns) {
@@ -502,9 +503,10 @@
     EXPECT_EQ(loop_flow->merge_target->branch_target, nullptr);
 
     // Check condition
-    auto instr = if_flow->condition;
-    ASSERT_TRUE(instr.IsBool());
-    EXPECT_TRUE(instr.AsBool());
+    ASSERT_TRUE(if_flow->condition->Is<Constant>());
+    auto* instr = if_flow->condition->As<Constant>();
+    ASSERT_TRUE(instr->IsBool());
+    EXPECT_TRUE(instr->AsBool());
 }
 
 TEST_F(IR_BuilderImplTest, Loop_WithOnlyReturn) {
@@ -947,9 +949,10 @@
     EXPECT_EQ(flow->merge_target->branch_target, func->end_target);
 
     // Check condition
-    auto instr = if_flow->condition;
-    ASSERT_TRUE(instr.IsBool());
-    EXPECT_FALSE(instr.AsBool());
+    ASSERT_TRUE(if_flow->condition->Is<Constant>());
+    auto* instr = if_flow->condition->As<Constant>();
+    ASSERT_TRUE(instr->IsBool());
+    EXPECT_FALSE(instr->AsBool());
 }
 
 TEST_F(IR_BuilderImplTest, While_Return) {
@@ -1071,9 +1074,10 @@
     EXPECT_EQ(flow->merge_target->branch_target, func->end_target);
 
     // Check condition
-    auto instr = if_flow->condition;
-    ASSERT_TRUE(instr.IsBool());
-    EXPECT_FALSE(instr.AsBool());
+    ASSERT_TRUE(if_flow->condition->Is<Constant>());
+    auto* instr = if_flow->condition->As<Constant>();
+    ASSERT_TRUE(instr->IsBool());
+    EXPECT_FALSE(instr->AsBool());
 }
 
 TEST_F(IR_BuilderImplTest, For_NoInitCondOrContinuing) {
@@ -1171,9 +1175,10 @@
     EXPECT_EQ(flow->merge_target->branch_target, func->end_target);
 
     // Check condition
-    auto instr = flow->condition;
-    ASSERT_TRUE(instr.IsI32());
-    EXPECT_EQ(1_i, instr.AsI32());
+    ASSERT_TRUE(flow->condition->Is<Constant>());
+    auto* instr = flow->condition->As<Constant>();
+    ASSERT_TRUE(instr->IsI32());
+    EXPECT_EQ(1_i, instr->AsI32());
 }
 
 TEST_F(IR_BuilderImplTest, Switch_OnlyDefault) {
@@ -1341,9 +1346,10 @@
     auto r = b.EmitLiteral(Expr(true));
     ASSERT_TRUE(r);
 
-    auto reg = r.Get();
-    EXPECT_TRUE(reg.IsBool());
-    EXPECT_TRUE(reg.AsBool());
+    ASSERT_TRUE(r.Get()->Is<Constant>());
+    auto* val = r.Get()->As<Constant>();
+    EXPECT_TRUE(val->IsBool());
+    EXPECT_TRUE(val->AsBool());
 }
 
 TEST_F(IR_BuilderImplTest, EmitLiteral_Bool_False) {
@@ -1351,9 +1357,10 @@
     auto r = b.EmitLiteral(Expr(false));
     ASSERT_TRUE(r);
 
-    auto reg = r.Get();
-    EXPECT_TRUE(reg.IsBool());
-    EXPECT_FALSE(reg.AsBool());
+    ASSERT_TRUE(r.Get()->Is<Constant>());
+    auto* val = r.Get()->As<Constant>();
+    EXPECT_TRUE(val->IsBool());
+    EXPECT_FALSE(val->AsBool());
 }
 
 TEST_F(IR_BuilderImplTest, EmitLiteral_F32) {
@@ -1361,9 +1368,10 @@
     auto r = b.EmitLiteral(Expr(1.2_f));
     ASSERT_TRUE(r);
 
-    auto reg = r.Get();
-    EXPECT_TRUE(reg.IsF32());
-    EXPECT_EQ(1.2_f, reg.AsF32());
+    ASSERT_TRUE(r.Get()->Is<Constant>());
+    auto* val = r.Get()->As<Constant>();
+    EXPECT_TRUE(val->IsF32());
+    EXPECT_EQ(1.2_f, val->AsF32());
 }
 
 TEST_F(IR_BuilderImplTest, EmitLiteral_F16) {
@@ -1371,9 +1379,10 @@
     auto r = b.EmitLiteral(Expr(1.2_h));
     ASSERT_TRUE(r);
 
-    auto reg = r.Get();
-    EXPECT_TRUE(reg.IsF16());
-    EXPECT_EQ(1.2_h, reg.AsF16());
+    ASSERT_TRUE(r.Get()->Is<Constant>());
+    auto* val = r.Get()->As<Constant>();
+    EXPECT_TRUE(val->IsF16());
+    EXPECT_EQ(1.2_h, val->AsF16());
 }
 
 TEST_F(IR_BuilderImplTest, EmitLiteral_I32) {
@@ -1381,9 +1390,10 @@
     auto r = b.EmitLiteral(Expr(-2_i));
     ASSERT_TRUE(r);
 
-    auto reg = r.Get();
-    EXPECT_TRUE(reg.IsI32());
-    EXPECT_EQ(-2_i, reg.AsI32());
+    ASSERT_TRUE(r.Get()->Is<Constant>());
+    auto* val = r.Get()->As<Constant>();
+    EXPECT_TRUE(val->IsI32());
+    EXPECT_EQ(-2_i, val->AsI32());
 }
 
 TEST_F(IR_BuilderImplTest, EmitLiteral_U32) {
@@ -1391,9 +1401,10 @@
     auto r = b.EmitLiteral(Expr(2_u));
     ASSERT_TRUE(r);
 
-    auto reg = r.Get();
-    EXPECT_TRUE(reg.IsU32());
-    EXPECT_EQ(2_u, reg.AsU32());
+    ASSERT_TRUE(r.Get()->Is<Constant>());
+    auto* val = r.Get()->As<Constant>();
+    EXPECT_TRUE(val->IsU32());
+    EXPECT_EQ(2_u, val->AsU32());
 }
 
 TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Add) {
diff --git a/src/tint/ir/constant.cc b/src/tint/ir/constant.cc
new file mode 100644
index 0000000..78895bf
--- /dev/null
+++ b/src/tint/ir/constant.cc
@@ -0,0 +1,56 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ir/constant.h"
+
+#include <string>
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::Constant);
+
+namespace tint::ir {
+
+Constant::Constant(f32 f) : kind_(Kind::kF32), data_(f) {}
+
+Constant::Constant(f16 f) : kind_(Kind::kF16), data_(f) {}
+
+Constant::Constant(u32 u) : kind_(Kind::kU32), data_(u) {}
+
+Constant::Constant(i32 i) : kind_(Kind::kI32), data_(i) {}
+
+Constant::Constant(bool b) : kind_(Kind::kBool), data_(b) {}
+
+Constant::~Constant() = default;
+
+std::ostream& operator<<(std::ostream& out, const Constant& r) {
+    switch (r.GetKind()) {
+        case Constant::Kind::kF32:
+            out << std::to_string(r.AsF32().value);
+            break;
+        case Constant::Kind::kF16:
+            out << std::to_string(r.AsF16().value);
+            break;
+        case Constant::Kind::kI32:
+            out << std::to_string(r.AsI32().value);
+            break;
+        case Constant::Kind::kU32:
+            out << std::to_string(r.AsU32().value);
+            break;
+        case Constant::Kind::kBool:
+            out << (r.AsBool() ? "true" : "false");
+            break;
+    }
+    return out;
+}
+
+}  // namespace tint::ir
diff --git a/src/tint/ir/constant.h b/src/tint/ir/constant.h
new file mode 100644
index 0000000..c06e121
--- /dev/null
+++ b/src/tint/ir/constant.h
@@ -0,0 +1,114 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_IR_CONSTANT_H_
+#define SRC_TINT_IR_CONSTANT_H_
+
+#include <ostream>
+#include <variant>
+
+#include "src/tint/ir/value.h"
+#include "src/tint/number.h"
+
+namespace tint::ir {
+
+/// Constant in the IR. The constant can be one of several types these include, but aren't limited
+/// to, `f32`, `u32`, `bool`. The type of the constant determines the type of data stored.
+class Constant : public Castable<Constant, Value> {
+  public:
+    /// The type of the constant
+    enum class Kind {
+        /// A f32 constant
+        kF32,
+        /// A f16 constant
+        kF16,
+        /// An i32 constant
+        kI32,
+        /// A u32 constant
+        kU32,
+        /// A boolean constant
+        kBool,
+    };
+
+    /// Constructor
+    /// @param b the `bool` constant to store in the constant
+    explicit Constant(bool b);
+
+    /// Constructor
+    /// @param f the `f32` constant to store in the constant
+    explicit Constant(f32 f);
+
+    /// Constructor
+    /// @param f the `f16` constant to store in the constant
+    explicit Constant(f16 f);
+
+    /// Constructor
+    /// @param u the `u32` constant to store in the constant
+    explicit Constant(u32 u);
+
+    /// Constructor
+    /// @param i the `i32` constant to store in the constant
+    explicit Constant(i32 i);
+
+    /// Destructor
+    ~Constant() override;
+
+    Constant(const Constant&) = delete;
+    Constant(Constant&&) = delete;
+
+    Constant& operator=(const Constant&) = delete;
+    Constant& operator=(Constant&&) = delete;
+
+    /// @returns true if this is a f32 constant
+    bool IsF32() const { return kind_ == Kind::kF32; }
+    /// @returns true if this is a f16 constant
+    bool IsF16() const { return kind_ == Kind::kF16; }
+    /// @returns true if this is an i32 constant
+    bool IsI32() const { return kind_ == Kind::kI32; }
+    /// @returns true if this is a u32 constant
+    bool IsU32() const { return kind_ == Kind::kU32; }
+    /// @returns true if this is a bool constant
+    bool IsBool() const { return kind_ == Kind::kBool; }
+
+    /// @returns the kind of constant
+    Kind GetKind() const { return kind_; }
+
+    /// @returns the constant data as a `f32`.
+    /// @note, must only be called if `IsF32()` is true
+    f32 AsF32() const { return std::get<f32>(data_); }
+    /// @returns the constant data as a `f16`.
+    /// @note, must only be called if `IsF16()` is true
+    f16 AsF16() const { return std::get<f16>(data_); }
+    /// @returns the constant data as an `i32`.
+    /// @note, must only be called if `IsI32()` is true
+    i32 AsI32() const { return std::get<i32>(data_); }
+    /// @returns the constant data as a `u32`.
+    /// @note, must only be called if `IsU32()` is true
+    u32 AsU32() const { return std::get<u32>(data_); }
+    /// @returns the constant data as a `bool`.
+    /// @note, must only be called if `IsBool()` is true
+    bool AsBool() const { return std::get<bool>(data_); }
+
+  private:
+    /// The type of data stored in this constant
+    Kind kind_;
+    /// The data stored in the constant
+    std::variant<f32, f16, u32, i32, bool> data_;
+};
+
+std::ostream& operator<<(std::ostream& out, const Constant& r);
+
+}  // namespace tint::ir
+
+#endif  // SRC_TINT_IR_CONSTANT_H_
diff --git a/src/tint/ir/constant_test.cc b/src/tint/ir/constant_test.cc
new file mode 100644
index 0000000..7b4e224
--- /dev/null
+++ b/src/tint/ir/constant_test.cc
@@ -0,0 +1,125 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <sstream>
+
+#include "src/tint/ir/test_helper.h"
+#include "src/tint/ir/value.h"
+
+namespace tint::ir {
+namespace {
+
+using namespace tint::number_suffixes;  // NOLINT
+
+using IR_ConstantTest = TestHelper;
+
+TEST_F(IR_ConstantTest, f32) {
+    auto& b = CreateEmptyBuilder();
+
+    std::stringstream str;
+
+    auto* val = b.builder.Constant(1.2_f);
+    EXPECT_EQ(1.2_f, val->AsF32());
+
+    str << *val;
+    EXPECT_EQ("1.200000", str.str());
+
+    EXPECT_TRUE(val->IsF32());
+    EXPECT_FALSE(val->IsF16());
+    EXPECT_FALSE(val->IsI32());
+    EXPECT_FALSE(val->IsU32());
+    EXPECT_FALSE(val->IsBool());
+}
+
+TEST_F(IR_ConstantTest, f16) {
+    auto& b = CreateEmptyBuilder();
+
+    std::stringstream str;
+
+    auto* val = b.builder.Constant(1.1_h);
+    EXPECT_EQ(1.1_h, val->AsF16());
+
+    str << *val;
+    EXPECT_EQ("1.099609", str.str());
+
+    EXPECT_FALSE(val->IsF32());
+    EXPECT_TRUE(val->IsF16());
+    EXPECT_FALSE(val->IsI32());
+    EXPECT_FALSE(val->IsU32());
+    EXPECT_FALSE(val->IsBool());
+}
+
+TEST_F(IR_ConstantTest, i32) {
+    auto& b = CreateEmptyBuilder();
+
+    std::stringstream str;
+
+    auto* val = b.builder.Constant(1_i);
+    EXPECT_EQ(1_i, val->AsI32());
+
+    str << *val;
+    EXPECT_EQ("1", str.str());
+
+    EXPECT_FALSE(val->IsF32());
+    EXPECT_FALSE(val->IsF16());
+    EXPECT_TRUE(val->IsI32());
+    EXPECT_FALSE(val->IsU32());
+    EXPECT_FALSE(val->IsBool());
+}
+
+TEST_F(IR_ConstantTest, u32) {
+    auto& b = CreateEmptyBuilder();
+
+    std::stringstream str;
+
+    auto* val = b.builder.Constant(2_u);
+    EXPECT_EQ(2_u, val->AsU32());
+
+    str << *val;
+    EXPECT_EQ("2", str.str());
+
+    EXPECT_FALSE(val->IsF32());
+    EXPECT_FALSE(val->IsF16());
+    EXPECT_FALSE(val->IsI32());
+    EXPECT_TRUE(val->IsU32());
+    EXPECT_FALSE(val->IsBool());
+}
+
+TEST_F(IR_ConstantTest, bool) {
+    auto& b = CreateEmptyBuilder();
+
+    std::stringstream str;
+
+    auto* val = b.builder.Constant(false);
+    EXPECT_FALSE(val->AsBool());
+
+    str << *val;
+    EXPECT_EQ("false", str.str());
+
+    str.str("");
+    val = b.builder.Constant(true);
+    EXPECT_TRUE(val->AsBool());
+
+    str << *val;
+    EXPECT_EQ("true", str.str());
+
+    EXPECT_FALSE(val->IsF32());
+    EXPECT_FALSE(val->IsF16());
+    EXPECT_FALSE(val->IsI32());
+    EXPECT_FALSE(val->IsU32());
+    EXPECT_TRUE(val->IsBool());
+}
+
+}  // namespace
+}  // namespace tint::ir
diff --git a/src/tint/ir/disassembler.cc b/src/tint/ir/disassembler.cc
index f98950f..e2749a1 100644
--- a/src/tint/ir/disassembler.cc
+++ b/src/tint/ir/disassembler.cc
@@ -62,8 +62,8 @@
 }
 
 void Disassembler::EmitBlockInstructions(const Block* b) {
-    for (const auto& instr : b->instructions) {
-        out_ << instr << std::endl;
+    for (const auto* instr : b->instructions) {
+        out_ << *instr << std::endl;
     }
 }
 
diff --git a/src/tint/ir/if.h b/src/tint/ir/if.h
index 4b2969d..905f311 100644
--- a/src/tint/ir/if.h
+++ b/src/tint/ir/if.h
@@ -45,7 +45,7 @@
     /// branches into it. (e.g. if both branches `return`)
     Block* merge_target = nullptr;
     /// Value holding the condition result
-    Value condition;
+    const Value* condition = nullptr;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/instruction.cc b/src/tint/ir/instruction.cc
index 9460f39..6c0f176 100644
--- a/src/tint/ir/instruction.cc
+++ b/src/tint/ir/instruction.cc
@@ -14,11 +14,13 @@
 
 #include "src/tint/ir/instruction.h"
 
+TINT_INSTANTIATE_TYPEINFO(tint::ir::Instruction);
+
 namespace tint::ir {
 
 Instruction::Instruction() {}
 
-Instruction::Instruction(Kind kind, Value result, Value lhs, Value rhs)
+Instruction::Instruction(Kind kind, const Value* result, const Value* lhs, const Value* rhs)
     : kind_(kind), result_(result), args_({lhs, rhs}) {}
 
 Instruction::Instruction(const Instruction&) = default;
@@ -32,9 +34,9 @@
 Instruction& Instruction::operator=(Instruction&& instr) = default;
 
 std::ostream& operator<<(std::ostream& out, const Instruction& instr) {
-    out << instr.Result() << " = ";
+    out << *(instr.Result()) << " = ";
     if (instr.HasLHS()) {
-        out << instr.LHS();
+        out << *(instr.LHS());
     }
     out << " ";
 
@@ -96,7 +98,7 @@
     }
 
     if (instr.HasRHS()) {
-        out << " " << instr.RHS();
+        out << " " << *(instr.RHS());
     }
 
     return out;
diff --git a/src/tint/ir/instruction.h b/src/tint/ir/instruction.h
index b62300c..461b1f7 100644
--- a/src/tint/ir/instruction.h
+++ b/src/tint/ir/instruction.h
@@ -17,13 +17,15 @@
 
 #include <ostream>
 
+#include "src/tint/castable.h"
+#include "src/tint/debug.h"
 #include "src/tint/ir/value.h"
 #include "src/tint/utils/vector.h"
 
 namespace tint::ir {
 
 /// An instruction in the IR.
-class Instruction {
+class Instruction : public Castable<Instruction> {
   public:
     /// The kind of instruction.
     enum class Kind {
@@ -58,7 +60,7 @@
     /// @param result the result value
     /// @param lhs the lhs of the instruction
     /// @param rhs the rhs of the instruction
-    Instruction(Kind kind, Value result, Value lhs, Value rhs);
+    Instruction(Kind kind, const Value* result, const Value* lhs, const Value* rhs);
     /// Copy constructor
     /// @param instr the instruction to copy from
     Instruction(const Instruction& instr);
@@ -81,12 +83,12 @@
     Kind GetKind() const { return kind_; }
 
     /// @returns the result value for the instruction
-    const Value& Result() const { return result_; }
+    const Value* Result() const { return result_; }
 
     /// @returns true if the instruction has a LHS
     bool HasLHS() const { return args_.Length() >= 1; }
     /// @returns the left-hand-side value for the instruction
-    const Value& LHS() const {
+    const Value* LHS() const {
         TINT_ASSERT(IR, HasLHS());
         return args_[0];
     }
@@ -94,7 +96,7 @@
     /// @returns true if the instruction has a RHS
     bool HasRHS() const { return args_.Length() >= 2; }
     /// @returns the right-hand-side value for the instruction
-    const Value& RHS() const {
+    const Value* RHS() const {
         TINT_ASSERT(IR, HasRHS());
         return args_[1];
     }
@@ -102,8 +104,8 @@
   private:
     Kind kind_;
 
-    Value result_;
-    utils::Vector<Value, 2> args_;
+    const Value* result_;
+    utils::Vector<const Value*, 2> args_;
 };
 
 std::ostream& operator<<(std::ostream& out, const Instruction&);
diff --git a/src/tint/ir/instruction_test.cc b/src/tint/ir/instruction_test.cc
index 8fdd541..0957903 100644
--- a/src/tint/ir/instruction_test.cc
+++ b/src/tint/ir/instruction_test.cc
@@ -25,468 +25,509 @@
 TEST_F(IR_InstructionTest, CreateAnd) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.And(Value(i32(4)), Value(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.And(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
-    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kAnd);
+    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kAnd);
 
-    ASSERT_TRUE(instr.Result().IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr.HasLHS());
-    auto& lhs = instr.LHS();
-    ASSERT_TRUE(lhs.IsI32());
-    EXPECT_EQ(i32(4), lhs.AsI32());
+    ASSERT_TRUE(instr->HasLHS());
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr.HasRHS());
-    auto& rhs = instr.RHS();
-    ASSERT_TRUE(rhs.IsI32());
-    EXPECT_EQ(i32(2), rhs.AsI32());
+    ASSERT_TRUE(instr->HasRHS());
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << instr;
+    str << *instr;
     EXPECT_EQ(str.str(), "%42 = 4 & 2");
 }
 
 TEST_F(IR_InstructionTest, CreateOr) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.Or(Value(i32(4)), Value(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.Or(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
-    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kOr);
+    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kOr);
 
-    ASSERT_TRUE(instr.Result().IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr.HasLHS());
-    auto& lhs = instr.LHS();
-    ASSERT_TRUE(lhs.IsI32());
-    EXPECT_EQ(i32(4), lhs.AsI32());
+    ASSERT_TRUE(instr->HasLHS());
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr.HasRHS());
-    auto& rhs = instr.RHS();
-    ASSERT_TRUE(rhs.IsI32());
-    EXPECT_EQ(i32(2), rhs.AsI32());
+    ASSERT_TRUE(instr->HasRHS());
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << instr;
+    str << *instr;
     EXPECT_EQ(str.str(), "%42 = 4 | 2");
 }
 
 TEST_F(IR_InstructionTest, CreateXor) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.Xor(Value(i32(4)), Value(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.Xor(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
-    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kXor);
+    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kXor);
 
-    ASSERT_TRUE(instr.Result().IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr.HasLHS());
-    auto& lhs = instr.LHS();
-    ASSERT_TRUE(lhs.IsI32());
-    EXPECT_EQ(i32(4), lhs.AsI32());
+    ASSERT_TRUE(instr->HasLHS());
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr.HasRHS());
-    auto& rhs = instr.RHS();
-    ASSERT_TRUE(rhs.IsI32());
-    EXPECT_EQ(i32(2), rhs.AsI32());
+    ASSERT_TRUE(instr->HasRHS());
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << instr;
+    str << *instr;
     EXPECT_EQ(str.str(), "%42 = 4 ^ 2");
 }
 
 TEST_F(IR_InstructionTest, CreateLogicalAnd) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.LogicalAnd(Value(i32(4)), Value(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr =
+        b.builder.LogicalAnd(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
-    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kLogicalAnd);
+    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kLogicalAnd);
 
-    ASSERT_TRUE(instr.Result().IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr.HasLHS());
-    auto& lhs = instr.LHS();
-    ASSERT_TRUE(lhs.IsI32());
-    EXPECT_EQ(i32(4), lhs.AsI32());
+    ASSERT_TRUE(instr->HasLHS());
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr.HasRHS());
-    auto& rhs = instr.RHS();
-    ASSERT_TRUE(rhs.IsI32());
-    EXPECT_EQ(i32(2), rhs.AsI32());
+    ASSERT_TRUE(instr->HasRHS());
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << instr;
+    str << *instr;
     EXPECT_EQ(str.str(), "%42 = 4 && 2");
 }
 
 TEST_F(IR_InstructionTest, CreateLogicalOr) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.LogicalOr(Value(i32(4)), Value(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.LogicalOr(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
-    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kLogicalOr);
+    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kLogicalOr);
 
-    ASSERT_TRUE(instr.Result().IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr.HasLHS());
-    auto& lhs = instr.LHS();
-    ASSERT_TRUE(lhs.IsI32());
-    EXPECT_EQ(i32(4), lhs.AsI32());
+    ASSERT_TRUE(instr->HasLHS());
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr.HasRHS());
-    auto& rhs = instr.RHS();
-    ASSERT_TRUE(rhs.IsI32());
-    EXPECT_EQ(i32(2), rhs.AsI32());
+    ASSERT_TRUE(instr->HasRHS());
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << instr;
+    str << *instr;
     EXPECT_EQ(str.str(), "%42 = 4 || 2");
 }
 
 TEST_F(IR_InstructionTest, CreateEqual) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.Equal(Value(i32(4)), Value(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.Equal(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
-    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kEqual);
+    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kEqual);
 
-    ASSERT_TRUE(instr.Result().IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr.HasLHS());
-    auto& lhs = instr.LHS();
-    ASSERT_TRUE(lhs.IsI32());
-    EXPECT_EQ(i32(4), lhs.AsI32());
+    ASSERT_TRUE(instr->HasLHS());
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr.HasRHS());
-    auto& rhs = instr.RHS();
-    ASSERT_TRUE(rhs.IsI32());
-    EXPECT_EQ(i32(2), rhs.AsI32());
+    ASSERT_TRUE(instr->HasRHS());
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << instr;
+    str << *instr;
     EXPECT_EQ(str.str(), "%42 = 4 == 2");
 }
 
 TEST_F(IR_InstructionTest, CreateNotEqual) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.NotEqual(Value(i32(4)), Value(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.NotEqual(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
-    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kNotEqual);
+    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kNotEqual);
 
-    ASSERT_TRUE(instr.Result().IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr.HasLHS());
-    auto& lhs = instr.LHS();
-    ASSERT_TRUE(lhs.IsI32());
-    EXPECT_EQ(i32(4), lhs.AsI32());
+    ASSERT_TRUE(instr->HasLHS());
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr.HasRHS());
-    auto& rhs = instr.RHS();
-    ASSERT_TRUE(rhs.IsI32());
-    EXPECT_EQ(i32(2), rhs.AsI32());
+    ASSERT_TRUE(instr->HasRHS());
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << instr;
+    str << *instr;
     EXPECT_EQ(str.str(), "%42 = 4 != 2");
 }
 
 TEST_F(IR_InstructionTest, CreateLessThan) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.LessThan(Value(i32(4)), Value(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.LessThan(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
-    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kLessThan);
+    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kLessThan);
 
-    ASSERT_TRUE(instr.Result().IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr.HasLHS());
-    auto& lhs = instr.LHS();
-    ASSERT_TRUE(lhs.IsI32());
-    EXPECT_EQ(i32(4), lhs.AsI32());
+    ASSERT_TRUE(instr->HasLHS());
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr.HasRHS());
-    auto& rhs = instr.RHS();
-    ASSERT_TRUE(rhs.IsI32());
-    EXPECT_EQ(i32(2), rhs.AsI32());
+    ASSERT_TRUE(instr->HasRHS());
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << instr;
+    str << *instr;
     EXPECT_EQ(str.str(), "%42 = 4 < 2");
 }
 
 TEST_F(IR_InstructionTest, CreateGreaterThan) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.GreaterThan(Value(i32(4)), Value(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr =
+        b.builder.GreaterThan(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
-    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kGreaterThan);
+    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kGreaterThan);
 
-    ASSERT_TRUE(instr.Result().IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr.HasLHS());
-    auto& lhs = instr.LHS();
-    ASSERT_TRUE(lhs.IsI32());
-    EXPECT_EQ(i32(4), lhs.AsI32());
+    ASSERT_TRUE(instr->HasLHS());
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr.HasRHS());
-    auto& rhs = instr.RHS();
-    ASSERT_TRUE(rhs.IsI32());
-    EXPECT_EQ(i32(2), rhs.AsI32());
+    ASSERT_TRUE(instr->HasRHS());
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << instr;
+    str << *instr;
     EXPECT_EQ(str.str(), "%42 = 4 > 2");
 }
 
 TEST_F(IR_InstructionTest, CreateLessThanEqual) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.LessThanEqual(Value(i32(4)), Value(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr =
+        b.builder.LessThanEqual(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
-    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kLessThanEqual);
+    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kLessThanEqual);
 
-    ASSERT_TRUE(instr.Result().IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr.HasLHS());
-    auto& lhs = instr.LHS();
-    ASSERT_TRUE(lhs.IsI32());
-    EXPECT_EQ(i32(4), lhs.AsI32());
+    ASSERT_TRUE(instr->HasLHS());
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr.HasRHS());
-    auto& rhs = instr.RHS();
-    ASSERT_TRUE(rhs.IsI32());
-    EXPECT_EQ(i32(2), rhs.AsI32());
+    ASSERT_TRUE(instr->HasRHS());
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << instr;
+    str << *instr;
     EXPECT_EQ(str.str(), "%42 = 4 <= 2");
 }
 
 TEST_F(IR_InstructionTest, CreateGreaterThanEqual) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.GreaterThanEqual(Value(i32(4)), Value(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr =
+        b.builder.GreaterThanEqual(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
-    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kGreaterThanEqual);
+    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kGreaterThanEqual);
 
-    ASSERT_TRUE(instr.Result().IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr.HasLHS());
-    auto& lhs = instr.LHS();
-    ASSERT_TRUE(lhs.IsI32());
-    EXPECT_EQ(i32(4), lhs.AsI32());
+    ASSERT_TRUE(instr->HasLHS());
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr.HasRHS());
-    auto& rhs = instr.RHS();
-    ASSERT_TRUE(rhs.IsI32());
-    EXPECT_EQ(i32(2), rhs.AsI32());
+    ASSERT_TRUE(instr->HasRHS());
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << instr;
+    str << *instr;
     EXPECT_EQ(str.str(), "%42 = 4 >= 2");
 }
 
 TEST_F(IR_InstructionTest, CreateShiftLeft) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.ShiftLeft(Value(i32(4)), Value(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.ShiftLeft(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
-    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kShiftLeft);
+    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kShiftLeft);
 
-    ASSERT_TRUE(instr.Result().IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr.HasLHS());
-    auto& lhs = instr.LHS();
-    ASSERT_TRUE(lhs.IsI32());
-    EXPECT_EQ(i32(4), lhs.AsI32());
+    ASSERT_TRUE(instr->HasLHS());
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr.HasRHS());
-    auto& rhs = instr.RHS();
-    ASSERT_TRUE(rhs.IsI32());
-    EXPECT_EQ(i32(2), rhs.AsI32());
+    ASSERT_TRUE(instr->HasRHS());
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << instr;
+    str << *instr;
     EXPECT_EQ(str.str(), "%42 = 4 << 2");
 }
 
 TEST_F(IR_InstructionTest, CreateShiftRight) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.ShiftRight(Value(i32(4)), Value(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr =
+        b.builder.ShiftRight(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
-    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kShiftRight);
+    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kShiftRight);
 
-    ASSERT_TRUE(instr.Result().IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr.HasLHS());
-    auto& lhs = instr.LHS();
-    ASSERT_TRUE(lhs.IsI32());
-    EXPECT_EQ(i32(4), lhs.AsI32());
+    ASSERT_TRUE(instr->HasLHS());
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr.HasRHS());
-    auto& rhs = instr.RHS();
-    ASSERT_TRUE(rhs.IsI32());
-    EXPECT_EQ(i32(2), rhs.AsI32());
+    ASSERT_TRUE(instr->HasRHS());
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << instr;
+    str << *instr;
     EXPECT_EQ(str.str(), "%42 = 4 >> 2");
 }
 
 TEST_F(IR_InstructionTest, CreateAdd) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.Add(Value(i32(4)), Value(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.Add(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
-    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kAdd);
+    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kAdd);
 
-    ASSERT_TRUE(instr.Result().IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr.HasLHS());
-    auto& lhs = instr.LHS();
-    ASSERT_TRUE(lhs.IsI32());
-    EXPECT_EQ(i32(4), lhs.AsI32());
+    ASSERT_TRUE(instr->HasLHS());
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr.HasRHS());
-    auto& rhs = instr.RHS();
-    ASSERT_TRUE(rhs.IsI32());
-    EXPECT_EQ(i32(2), rhs.AsI32());
+    ASSERT_TRUE(instr->HasRHS());
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << instr;
+    str << *instr;
     EXPECT_EQ(str.str(), "%42 = 4 + 2");
 }
 
 TEST_F(IR_InstructionTest, CreateSubtract) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.Subtract(Value(i32(4)), Value(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.Subtract(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
-    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kSubtract);
+    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kSubtract);
 
-    ASSERT_TRUE(instr.Result().IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr.HasLHS());
-    auto& lhs = instr.LHS();
-    ASSERT_TRUE(lhs.IsI32());
-    EXPECT_EQ(i32(4), lhs.AsI32());
+    ASSERT_TRUE(instr->HasLHS());
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr.HasRHS());
-    auto& rhs = instr.RHS();
-    ASSERT_TRUE(rhs.IsI32());
-    EXPECT_EQ(i32(2), rhs.AsI32());
+    ASSERT_TRUE(instr->HasRHS());
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << instr;
+    str << *instr;
     EXPECT_EQ(str.str(), "%42 = 4 - 2");
 }
 
 TEST_F(IR_InstructionTest, CreateMultiply) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.Multiply(Value(i32(4)), Value(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.Multiply(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
-    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kMultiply);
+    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kMultiply);
 
-    ASSERT_TRUE(instr.Result().IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr.HasLHS());
-    auto& lhs = instr.LHS();
-    ASSERT_TRUE(lhs.IsI32());
-    EXPECT_EQ(i32(4), lhs.AsI32());
+    ASSERT_TRUE(instr->HasLHS());
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr.HasRHS());
-    auto& rhs = instr.RHS();
-    ASSERT_TRUE(rhs.IsI32());
-    EXPECT_EQ(i32(2), rhs.AsI32());
+    ASSERT_TRUE(instr->HasRHS());
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << instr;
+    str << *instr;
     EXPECT_EQ(str.str(), "%42 = 4 * 2");
 }
 
 TEST_F(IR_InstructionTest, CreateDivide) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.Divide(Value(i32(4)), Value(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.Divide(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
-    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kDivide);
+    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kDivide);
 
-    ASSERT_TRUE(instr.Result().IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr.HasLHS());
-    auto& lhs = instr.LHS();
-    ASSERT_TRUE(lhs.IsI32());
-    EXPECT_EQ(i32(4), lhs.AsI32());
+    ASSERT_TRUE(instr->HasLHS());
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr.HasRHS());
-    auto& rhs = instr.RHS();
-    ASSERT_TRUE(rhs.IsI32());
-    EXPECT_EQ(i32(2), rhs.AsI32());
+    ASSERT_TRUE(instr->HasRHS());
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << instr;
+    str << *instr;
     EXPECT_EQ(str.str(), "%42 = 4 / 2");
 }
 
 TEST_F(IR_InstructionTest, CreateModulo) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.Modulo(Value(i32(4)), Value(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.Modulo(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
-    EXPECT_EQ(instr.GetKind(), Instruction::Kind::kModulo);
+    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kModulo);
 
-    ASSERT_TRUE(instr.Result().IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result().AsId());
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
-    ASSERT_TRUE(instr.HasLHS());
-    auto& lhs = instr.LHS();
-    ASSERT_TRUE(lhs.IsI32());
-    EXPECT_EQ(i32(4), lhs.AsI32());
+    ASSERT_TRUE(instr->HasLHS());
+    ASSERT_TRUE(instr->LHS()->Is<Constant>());
+    auto lhs = instr->LHS()->As<Constant>();
+    ASSERT_TRUE(lhs->IsI32());
+    EXPECT_EQ(i32(4), lhs->AsI32());
 
-    ASSERT_TRUE(instr.HasRHS());
-    auto& rhs = instr.RHS();
-    ASSERT_TRUE(rhs.IsI32());
-    EXPECT_EQ(i32(2), rhs.AsI32());
+    ASSERT_TRUE(instr->HasRHS());
+    ASSERT_TRUE(instr->RHS()->Is<Constant>());
+    auto rhs = instr->RHS()->As<Constant>();
+    ASSERT_TRUE(rhs->IsI32());
+    EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    str << instr;
+    str << *instr;
     EXPECT_EQ(str.str(), "%42 = 4 % 2");
 }
 
diff --git a/src/tint/ir/module.h b/src/tint/ir/module.h
index 4d38614..d95a13e 100644
--- a/src/tint/ir/module.h
+++ b/src/tint/ir/module.h
@@ -18,6 +18,8 @@
 #include <string>
 
 #include "src/tint/ir/function.h"
+#include "src/tint/ir/instruction.h"
+#include "src/tint/ir/value.h"
 #include "src/tint/utils/block_allocator.h"
 #include "src/tint/utils/result.h"
 #include "src/tint/utils/vector.h"
@@ -66,6 +68,10 @@
 
     /// The flow node allocator
     utils::BlockAllocator<FlowNode> flow_nodes;
+    /// The value allocator
+    utils::BlockAllocator<Value> values;
+    /// The instruction allocator
+    utils::BlockAllocator<Instruction> instructions;
 
     /// List of functions in the program
     utils::Vector<Function*, 8> functions;
diff --git a/src/tint/ir/switch.h b/src/tint/ir/switch.h
index e526fe8..3ea9c3f 100644
--- a/src/tint/ir/switch.h
+++ b/src/tint/ir/switch.h
@@ -53,7 +53,7 @@
     utils::Vector<Case, 4> cases;
 
     /// Value holding the condition result
-    Value condition;
+    const Value* condition = nullptr;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/temp.cc b/src/tint/ir/temp.cc
new file mode 100644
index 0000000..de227ee
--- /dev/null
+++ b/src/tint/ir/temp.cc
@@ -0,0 +1,32 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ir/temp.h"
+
+#include <string>
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::Temp);
+
+namespace tint::ir {
+
+Temp::Temp(Id id) : id_(id) {}
+
+Temp::~Temp() = default;
+
+std::ostream& operator<<(std::ostream& out, const Temp& r) {
+    out << "%" << std::to_string(r.AsId());
+    return out;
+}
+
+}  // namespace tint::ir
diff --git a/src/tint/ir/temp.h b/src/tint/ir/temp.h
new file mode 100644
index 0000000..b16baf2
--- /dev/null
+++ b/src/tint/ir/temp.h
@@ -0,0 +1,54 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_IR_TEMP_H_
+#define SRC_TINT_IR_TEMP_H_
+
+#include <ostream>
+
+#include "src/tint/ir/value.h"
+
+namespace tint::ir {
+
+/// Temporary value in the IR.
+class Temp : public Castable<Temp, Value> {
+  public:
+    /// A value id.
+    using Id = uint32_t;
+
+    /// Constructor
+    /// @param id the id for the value
+    explicit Temp(Id id);
+
+    /// Destructor
+    ~Temp() override;
+
+    Temp(const Temp&) = delete;
+    Temp(Temp&&) = delete;
+
+    Temp& operator=(const Temp&) = delete;
+    Temp& operator=(Temp&&) = delete;
+
+    /// @returns the value data as an `Id`.
+    Id AsId() const { return id_; }
+
+  private:
+    Id id_ = 0;
+};
+
+std::ostream& operator<<(std::ostream& out, const Temp& r);
+
+}  // namespace tint::ir
+
+#endif  // SRC_TINT_IR_TEMP_H_
diff --git a/src/tint/ir/temp_test.cc b/src/tint/ir/temp_test.cc
new file mode 100644
index 0000000..b5d817b
--- /dev/null
+++ b/src/tint/ir/temp_test.cc
@@ -0,0 +1,41 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <sstream>
+
+#include "src/tint/ir/temp.h"
+#include "src/tint/ir/test_helper.h"
+
+namespace tint::ir {
+namespace {
+
+using namespace tint::number_suffixes;  // NOLINT
+
+using IR_TempTest = TestHelper;
+
+TEST_F(IR_TempTest, id) {
+    auto& b = CreateEmptyBuilder();
+
+    std::stringstream str;
+
+    b.builder.next_temp_id = Temp::Id(4);
+    auto* val = b.builder.Temp();
+    EXPECT_EQ(4u, val->AsId());
+
+    str << *val;
+    EXPECT_EQ("%4", str.str());
+}
+
+}  // namespace
+}  // namespace tint::ir
diff --git a/src/tint/ir/value.cc b/src/tint/ir/value.cc
index bd45a98..a1431e3 100644
--- a/src/tint/ir/value.cc
+++ b/src/tint/ir/value.cc
@@ -14,61 +14,26 @@
 
 #include "src/tint/ir/value.h"
 
+#include "src/tint/ir/constant.h"
+#include "src/tint/ir/temp.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::Value);
+
 namespace tint::ir {
 
-Value::Value() : kind_(Kind::kUninitialized), data_(Id(0)) {}
-
-Value::Value(Id id) : kind_(Kind::kTemp), data_(id) {}
-
-Value::Value(f32 f) : kind_(Kind::kF32), data_(f) {}
-
-Value::Value(f16 f) : kind_(Kind::kF16), data_(f) {}
-
-Value::Value(u32 u) : kind_(Kind::kU32), data_(u) {}
-
-Value::Value(i32 i) : kind_(Kind::kI32), data_(i) {}
-
-Value::Value(bool b) : kind_(Kind::kBool), data_(b) {}
-
-Value::Value(Symbol s, Id id) : kind_(Kind::kVar), data_(VarData{s, id}) {}
+Value::Value() = default;
 
 Value::~Value() = default;
 
-Value::Value(const Value& o) = default;
+std::ostream& operator<<(std::ostream& out, const Value& v) {
+    const auto* ptr = &v;
 
-Value::Value(Value&& o) = default;
-
-Value& Value::operator=(const Value& o) = default;
-
-Value& Value::operator=(Value&& o) = default;
-
-std::ostream& operator<<(std::ostream& out, const Value& r) {
-    switch (r.GetKind()) {
-        case Value::Kind::kTemp:
-            out << "%" << std::to_string(r.AsId());
-            break;
-        case Value::Kind::kF32:
-            out << std::to_string(r.AsF32().value);
-            break;
-        case Value::Kind::kF16:
-            out << std::to_string(r.AsF16().value);
-            break;
-        case Value::Kind::kI32:
-            out << std::to_string(r.AsI32().value);
-            break;
-        case Value::Kind::kU32:
-            out << std::to_string(r.AsU32().value);
-            break;
-            // TODO(dsinclair): Emit the symbol instead of v
-        case Value::Kind::kVar:
-            out << "%v" << std::to_string(r.AsVarData().id);
-            break;
-        case Value::Kind::kBool:
-            out << (r.AsBool() ? "true" : "false");
-            break;
-        case Value::Kind::kUninitialized:
-            out << "unknown value";
-            break;
+    if (auto* c = ptr->As<Constant>()) {
+        out << *c;
+    } else if (auto* t = ptr->As<Temp>()) {
+        out << *t;
+    } else {
+        out << "Unknown value";
     }
     return out;
 }
diff --git a/src/tint/ir/value.h b/src/tint/ir/value.h
index 7c9f8b5..326931f 100644
--- a/src/tint/ir/value.h
+++ b/src/tint/ir/value.h
@@ -16,153 +16,29 @@
 #define SRC_TINT_IR_VALUE_H_
 
 #include <ostream>
-#include <variant>
 
-#include "src/tint/number.h"
-#include "src/tint/symbol.h"
+#include "src/tint/castable.h"
 
 namespace tint::ir {
 
-/// Value in the IR. The value can be one of several types these include, but aren't limited
-/// to, `f32`, `u32`, `temp`, `var`. The type of the value determines the type of data stored
-/// in the value.
-class Value {
+/// Value in the IR.
+class Value : public Castable<Value> {
   public:
-    /// A value id.
-    using Id = uint32_t;
-
-    /// The type of the value
-    enum class Kind {
-        /// A uninitialized value
-        kUninitialized,
-        /// A temporary allocated value
-        kTemp,
-        /// A f32 value
-        kF32,
-        /// A f16 value
-        kF16,
-        /// An i32 value
-        kI32,
-        /// A u32 value
-        kU32,
-        /// A variable value
-        kVar,
-        /// A boolean value
-        kBool,
-    };
-
-    /// Stores data for a given variable. There will be multiple `VarData` entries for a given `id`.
-    /// The `id` acts like a generation number (although they aren't sequential, they are
-    /// increasing). As the variable is stored too a new value will be created and the the `id`
-    /// will be incremented.
-    struct VarData {
-        /// The symbol for the variable
-        Symbol sym;
-        /// The id for the variable.
-        Id id;
-        // TODO(dsinclair): Should var type data be stored here along side the variable info?
-    };
-
-    /// Constructor
-    /// Creates a uninitialized value
-    Value();
-
-    /// Constructor
-    /// @param id the id for the value
-    explicit Value(Id id);
-
-    /// Constructor
-    /// @param s the symbol for the value
-    /// @param id the id for the value
-    Value(Symbol s, Id id);
-
-    /// Constructor
-    /// @param b the `bool` value to store in the value
-    explicit Value(bool b);
-
-    /// Constructor
-    /// @param f the `f32` value to store in the value
-    explicit Value(f32 f);
-
-    /// Constructor
-    /// @param f the `f16` value to store in the value
-    explicit Value(f16 f);
-
-    /// Constructor
-    /// @param u the `u32` value to store in the value
-    explicit Value(u32 u);
-
-    /// Constructor
-    /// @param i the `i32` value to store in the value
-    explicit Value(i32 i);
-
     /// Destructor
-    ~Value();
+    virtual ~Value();
 
-    /// Copy constructor
-    /// @param o the value to copy from
-    Value(const Value& o);
-    /// Move constructor
-    /// @param o the value to move from
-    Value(Value&& o);
+    Value(const Value&) = delete;
+    Value(Value&&) = delete;
 
-    /// Copy assign
-    /// @param o the value to copy from
-    /// @returns this
-    Value& operator=(const Value& o);
-    /// Move assign
-    /// @param o the value to move from
-    /// @returns this
-    Value& operator=(Value&& o);
+    Value& operator=(const Value&) = delete;
+    Value& operator=(Value&&) = delete;
 
-    /// @returns true if this is a temporary value
-    bool IsTemp() const { return kind_ == Kind::kTemp; }
-    /// @returns true if this is a f32 value
-    bool IsF32() const { return kind_ == Kind::kF32; }
-    /// @returns true if this is a f16 value
-    bool IsF16() const { return kind_ == Kind::kF16; }
-    /// @returns true if this is an i32 value
-    bool IsI32() const { return kind_ == Kind::kI32; }
-    /// @returns true if this is a u32 value
-    bool IsU32() const { return kind_ == Kind::kU32; }
-    /// @returns true if this is a var value
-    bool IsVar() const { return kind_ == Kind::kVar; }
-    /// @returns true if this is a bool value
-    bool IsBool() const { return kind_ == Kind::kBool; }
-
-    /// @returns the kind of value
-    Kind GetKind() const { return kind_; }
-
-    /// @returns the value data as a `f32`.
-    /// @note, must only be called if `IsF32()` is true
-    f32 AsF32() const { return std::get<f32>(data_); }
-    /// @returns the value data as a `f16`.
-    /// @note, must only be called if `IsF16()` is true
-    f16 AsF16() const { return std::get<f16>(data_); }
-    /// @returns the value data as an `i32`.
-    /// @note, must only be called if `IsI32()` is true
-    i32 AsI32() const { return std::get<i32>(data_); }
-    /// @returns the value data as a `u32`.
-    /// @note, must only be called if `IsU32()` is true
-    u32 AsU32() const { return std::get<u32>(data_); }
-    /// @returns the value data as an `Id`.
-    /// @note, must only be called if `IsTemp()` is true
-    Id AsId() const { return std::get<Id>(data_); }
-    /// @returns the value data as a `VarData` structure.
-    /// @note, must only be called if `IsVar()` is true
-    VarData AsVarData() const { return std::get<VarData>(data_); }
-    /// @returns the value data as a `bool`.
-    /// @note, must only be called if `IsBool()` is true
-    bool AsBool() const { return std::get<bool>(data_); }
-
-  private:
-    /// The type of data stored in this value
-    Kind kind_;
-    /// The data stored in the value
-    std::variant<Id, f32, f16, u32, i32, VarData, bool> data_;
+  protected:
+    /// Constructor
+    Value();
 };
 
-std::ostream& operator<<(std::ostream& out, const Value& r);
+std::ostream& operator<<(std::ostream& out, const Value& v);
 
 }  // namespace tint::ir
 
diff --git a/src/tint/ir/value_test.cc b/src/tint/ir/value_test.cc
deleted file mode 100644
index 77ee3e4..0000000
--- a/src/tint/ir/value_test.cc
+++ /dev/null
@@ -1,183 +0,0 @@
-// Copyright 2022 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sstream>
-
-#include "src/tint/ir/test_helper.h"
-#include "src/tint/ir/value.h"
-
-namespace tint::ir {
-namespace {
-
-using namespace tint::number_suffixes;  // NOLINT
-
-using IR_ValueTest = TestHelper;
-
-TEST_F(IR_ValueTest, f32) {
-    std::stringstream str;
-
-    Value val(1.2_f);
-    EXPECT_EQ(1.2_f, val.AsF32());
-
-    str << val;
-    EXPECT_EQ("1.200000", str.str());
-
-    EXPECT_TRUE(val.IsF32());
-    EXPECT_FALSE(val.IsF16());
-    EXPECT_FALSE(val.IsI32());
-    EXPECT_FALSE(val.IsU32());
-    EXPECT_FALSE(val.IsTemp());
-    EXPECT_FALSE(val.IsVar());
-    EXPECT_FALSE(val.IsBool());
-}
-
-TEST_F(IR_ValueTest, f16) {
-    std::stringstream str;
-
-    Value val(1.1_h);
-    EXPECT_EQ(1.1_h, val.AsF16());
-
-    str << val;
-    EXPECT_EQ("1.099609", str.str());
-
-    EXPECT_FALSE(val.IsF32());
-    EXPECT_TRUE(val.IsF16());
-    EXPECT_FALSE(val.IsI32());
-    EXPECT_FALSE(val.IsU32());
-    EXPECT_FALSE(val.IsTemp());
-    EXPECT_FALSE(val.IsVar());
-    EXPECT_FALSE(val.IsBool());
-}
-
-TEST_F(IR_ValueTest, i32) {
-    std::stringstream str;
-
-    Value val(1_i);
-    EXPECT_EQ(1_i, val.AsI32());
-
-    str << val;
-    EXPECT_EQ("1", str.str());
-
-    EXPECT_FALSE(val.IsF32());
-    EXPECT_FALSE(val.IsF16());
-    EXPECT_TRUE(val.IsI32());
-    EXPECT_FALSE(val.IsU32());
-    EXPECT_FALSE(val.IsTemp());
-    EXPECT_FALSE(val.IsVar());
-    EXPECT_FALSE(val.IsBool());
-}
-
-TEST_F(IR_ValueTest, u32) {
-    std::stringstream str;
-
-    Value val(2_u);
-    EXPECT_EQ(2_u, val.AsU32());
-
-    str << val;
-    EXPECT_EQ("2", str.str());
-
-    EXPECT_FALSE(val.IsF32());
-    EXPECT_FALSE(val.IsF16());
-    EXPECT_FALSE(val.IsI32());
-    EXPECT_TRUE(val.IsU32());
-    EXPECT_FALSE(val.IsTemp());
-    EXPECT_FALSE(val.IsVar());
-    EXPECT_FALSE(val.IsBool());
-}
-
-TEST_F(IR_ValueTest, id) {
-    std::stringstream str;
-
-    Value val(Value::Id(4));
-    EXPECT_EQ(4u, val.AsId());
-
-    str << val;
-    EXPECT_EQ("%4", str.str());
-
-    EXPECT_FALSE(val.IsF32());
-    EXPECT_FALSE(val.IsF16());
-    EXPECT_FALSE(val.IsI32());
-    EXPECT_FALSE(val.IsU32());
-    EXPECT_TRUE(val.IsTemp());
-    EXPECT_FALSE(val.IsVar());
-    EXPECT_FALSE(val.IsBool());
-}
-
-TEST_F(IR_ValueTest, bool) {
-    std::stringstream str;
-
-    Value val(false);
-    EXPECT_FALSE(val.AsBool());
-
-    str << val;
-    EXPECT_EQ("false", str.str());
-
-    str.str("");
-    val = Value(true);
-    EXPECT_TRUE(val.AsBool());
-
-    str << val;
-    EXPECT_EQ("true", str.str());
-
-    EXPECT_FALSE(val.IsF32());
-    EXPECT_FALSE(val.IsF16());
-    EXPECT_FALSE(val.IsI32());
-    EXPECT_FALSE(val.IsU32());
-    EXPECT_FALSE(val.IsTemp());
-    EXPECT_FALSE(val.IsVar());
-    EXPECT_TRUE(val.IsBool());
-}
-
-TEST_F(IR_ValueTest, var) {
-    std::stringstream str;
-
-    Symbol s;
-    Value val(s, 2);
-    EXPECT_EQ(2u, val.AsVarData().id);
-    EXPECT_EQ(s, val.AsVarData().sym);
-
-    str << val;
-    EXPECT_EQ("%v2", str.str());
-    str.str("");
-
-    val = Value(s, 4);
-    EXPECT_EQ(4u, val.AsVarData().id);
-    EXPECT_EQ(s, val.AsVarData().sym);
-
-    str << val;
-    EXPECT_EQ("%v4", str.str());
-
-    EXPECT_FALSE(val.IsF32());
-    EXPECT_FALSE(val.IsF16());
-    EXPECT_FALSE(val.IsI32());
-    EXPECT_FALSE(val.IsU32());
-    EXPECT_FALSE(val.IsTemp());
-    EXPECT_TRUE(val.IsVar());
-    EXPECT_FALSE(val.IsBool());
-}
-
-TEST_F(IR_ValueTest, uninitialized) {
-    Value val;
-
-    EXPECT_FALSE(val.IsF32());
-    EXPECT_FALSE(val.IsF16());
-    EXPECT_FALSE(val.IsI32());
-    EXPECT_FALSE(val.IsU32());
-    EXPECT_FALSE(val.IsTemp());
-    EXPECT_FALSE(val.IsVar());
-    EXPECT_FALSE(val.IsBool());
-}
-
-}  // namespace
-}  // namespace tint::ir
diff --git a/src/tint/resolver/address_space_layout_validation_test.cc b/src/tint/resolver/address_space_layout_validation_test.cc
index b34b889..638df21 100644
--- a/src/tint/resolver/address_space_layout_validation_test.cc
+++ b/src/tint/resolver/address_space_layout_validation_test.cc
@@ -52,7 +52,7 @@
 /* offset(5) align(1) size( 4) */   b : f32;
 /* offset(9) align(1) size( 3) */   // -- implicit struct size padding --;
 /*                             */ };
-78:90 note: see declaration of variable)");
+78:90 note: 'S' used in address space 'storage' here)");
 }
 
 TEST_F(ResolverAddressSpaceLayoutValidationTest, StorageBuffer_UnalignedMember_SuggestedFix) {
@@ -116,7 +116,7 @@
 /*           align(4) size(4) */ struct Inner {
 /* offset(0) align(4) size(4) */   scalar : i32;
 /*                            */ };
-78:90 note: see declaration of variable)");
+78:90 note: 'Outer' used in address space 'uniform' here)");
 }
 
 TEST_F(ResolverAddressSpaceLayoutValidationTest,
@@ -182,7 +182,7 @@
 /* offset(  0) align(4) size(  4) */   scalar : f32;
 /* offset(  4) align(4) size(160) */   inner : @stride(16) array<f32, 10>;
 /*                                */ };
-78:90 note: see declaration of variable)");
+78:90 note: 'Outer' used in address space 'uniform' here)");
 }
 
 TEST_F(ResolverAddressSpaceLayoutValidationTest, UniformBuffer_UnalignedMember_Array_SuggestedFix) {
@@ -253,7 +253,7 @@
 /*           align(1) size(5) */ struct Inner {
 /* offset(0) align(1) size(5) */   scalar : i32;
 /*                            */ };
-22:24 note: see declaration of variable)");
+22:24 note: 'Outer' used in address space 'uniform' here)");
 }
 
 // See https://crbug.com/tint/1344
@@ -308,7 +308,7 @@
 /* offset(12) align(1) size( 5) */   scalar : i32;
 /* offset(17) align(1) size( 3) */   // -- implicit struct size padding --;
 /*                              */ };
-22:24 note: see declaration of variable)");
+22:24 note: 'Outer' used in address space 'uniform' here)");
 }
 
 TEST_F(ResolverAddressSpaceLayoutValidationTest,
@@ -418,7 +418,7 @@
 /* offset( 0) align(4) size(40) */   inner : array<f32, 10>;
 /* offset(40) align(4) size( 4) */   scalar : i32;
 /*                              */ };
-78:90 note: see declaration of variable)");
+78:90 note: 'Outer' used in address space 'uniform' here)");
 }
 
 TEST_F(ResolverAddressSpaceLayoutValidationTest, UniformBuffer_InvalidArrayStride_Vector) {
@@ -453,7 +453,7 @@
 /* offset(80) align(4) size( 4) */   scalar : i32;
 /* offset(84) align(1) size( 4) */   // -- implicit struct size padding --;
 /*                              */ };
-78:90 note: see declaration of variable)");
+78:90 note: 'Outer' used in address space 'uniform' here)");
 }
 
 TEST_F(ResolverAddressSpaceLayoutValidationTest, UniformBuffer_InvalidArrayStride_Struct) {
@@ -495,7 +495,7 @@
 /* offset( 0) align(4) size(80) */   inner : array<ArrayElem, 10>;
 /* offset(80) align(4) size( 4) */   scalar : i32;
 /*                              */ };
-78:90 note: see declaration of variable)");
+78:90 note: 'Outer' used in address space 'uniform' here)");
 }
 
 TEST_F(ResolverAddressSpaceLayoutValidationTest, UniformBuffer_InvalidArrayStride_TopLevelArray) {
@@ -507,7 +507,7 @@
     ASSERT_FALSE(r()->Resolve());
     EXPECT_EQ(
         r()->error(),
-        R"(34:56 error: uniform storage requires that array elements be aligned to 16 bytes, but array element alignment is currently 4. Consider using a vector or struct as the element type instead.)");
+        R"(78:90 error: uniform storage requires that array elements be aligned to 16 bytes, but array element alignment is currently 4. Consider using a vector or struct as the element type instead.)");
 }
 
 TEST_F(ResolverAddressSpaceLayoutValidationTest, UniformBuffer_InvalidArrayStride_NestedArray) {
@@ -534,7 +534,7 @@
 /*            align(4) size(64) */ struct Outer {
 /* offset( 0) align(4) size(64) */   inner : array<array<f32, 4>, 4>;
 /*                              */ };
-78:90 note: see declaration of variable)");
+78:90 note: 'Outer' used in address space 'uniform' here)");
 }
 
 TEST_F(ResolverAddressSpaceLayoutValidationTest, UniformBuffer_InvalidArrayStride_SuggestedFix) {
@@ -587,7 +587,7 @@
 /* offset(5) align(1) size( 4) */   b : f32;
 /* offset(9) align(1) size( 3) */   // -- implicit struct size padding --;
 /*                             */ };
-78:90 note: see declaration of variable)");
+78:90 note: 'S' used in address space 'push_constant' here)");
 }
 
 TEST_F(ResolverAddressSpaceLayoutValidationTest, PushConstant_Aligned) {
diff --git a/src/tint/resolver/address_space_validation_test.cc b/src/tint/resolver/address_space_validation_test.cc
index 60e54df..bc6c4a4 100644
--- a/src/tint/resolver/address_space_validation_test.cc
+++ b/src/tint/resolver/address_space_validation_test.cc
@@ -27,7 +27,7 @@
 
 using ResolverAddressSpaceValidationTest = ResolverTest;
 
-TEST_F(ResolverAddressSpaceValidationTest, GlobalVariableNoAddressSpace_Fail) {
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_NoAddressSpace_Fail) {
     // var g : f32;
     GlobalVar(Source{{12, 34}}, "g", ty.f32());
 
@@ -36,8 +36,16 @@
               "12:34 error: module-scope 'var' declaration must have a address space");
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, GlobalVariableFunctionAddressSpace_Fail) {
-    // var<function> g : f32;
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_NoAddressSpace_Fail) {
+    // type g = ptr<f32>;
+    Alias("g", ty.pointer(Source{{12, 34}}, ty.f32(), ast::AddressSpace::kUndefined));
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(), "12:34 error: ptr missing address space");
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_FunctionAddressSpace_Fail) {
+    // var<private> g : f32;
     GlobalVar(Source{{12, 34}}, "g", ty.f32(), ast::AddressSpace::kFunction);
 
     EXPECT_FALSE(r()->Resolve());
@@ -45,301 +53,611 @@
               "12:34 error: module-scope 'var' must not use address space 'function'");
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, Private_RuntimeArray) {
-    GlobalVar(Source{{12, 34}}, "v", ty.array(ty.i32()), ast::AddressSpace::kPrivate);
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_Private_RuntimeArray) {
+    // var<private> v : array<i32>;
+    GlobalVar(Source{{56, 78}}, "v", ty.array(Source{{12, 34}}, ty.i32()),
+              ast::AddressSpace::kPrivate);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
               R"(12:34 error: runtime-sized arrays can only be used in the <storage> address space
-12:34 note: while instantiating 'var' v)");
+56:78 note: while instantiating 'var' v)");
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, Private_RuntimeArrayInStruct) {
-    auto* s = Structure("S", utils::Vector{Member("m", ty.array(ty.i32()))});
-    GlobalVar(Source{{12, 34}}, "v", ty.Of(s), ast::AddressSpace::kPrivate);
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_Private_RuntimeArray) {
+    // type t : ptr<private, array<i32>>;
+    Alias("t", ty.pointer(Source{{56, 78}}, ty.array(Source{{12, 34}}, ty.i32()),
+                          ast::AddressSpace::kPrivate));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
               R"(12:34 error: runtime-sized arrays can only be used in the <storage> address space
-note: while analyzing structure member S.m
-12:34 note: while instantiating 'var' v)");
+56:78 note: while instantiating ptr<private, array<i32>, read_write>)");
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, Workgroup_RuntimeArray) {
-    GlobalVar(Source{{12, 34}}, "v", ty.array(ty.i32()), ast::AddressSpace::kWorkgroup);
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_Private_RuntimeArrayInStruct) {
+    // struct S { m : array<i32> };
+    // var<private> v : S;
+    Structure("S", utils::Vector{Member(Source{{12, 34}}, "m", ty.array(ty.i32()))});
+    GlobalVar(Source{{56, 78}}, "v", ty.type_name("S"), ast::AddressSpace::kPrivate);
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              R"(error: runtime-sized arrays can only be used in the <storage> address space
+12:34 note: while analyzing structure member S.m
+56:78 note: while instantiating 'var' v)");
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_Private_RuntimeArrayInStruct) {
+    // struct S { m : array<i32> };
+    // type t = ptr<private, S>;
+    Structure("S", utils::Vector{Member(Source{{12, 34}}, "m", ty.array(ty.i32()))});
+    Alias("t", ty.pointer(ty.type_name("S"), ast::AddressSpace::kPrivate));
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              R"(error: runtime-sized arrays can only be used in the <storage> address space
+12:34 note: while analyzing structure member S.m
+note: while instantiating ptr<private, S, read_write>)");
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_Workgroup_RuntimeArray) {
+    // var<workgroup> v : array<i32>;
+    GlobalVar(Source{{56, 78}}, "v", ty.array(Source{{12, 34}}, ty.i32()),
+              ast::AddressSpace::kWorkgroup);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
               R"(12:34 error: runtime-sized arrays can only be used in the <storage> address space
-12:34 note: while instantiating 'var' v)");
+56:78 note: while instantiating 'var' v)");
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, Workgroup_RuntimeArrayInStruct) {
-    auto* s = Structure("S", utils::Vector{Member("m", ty.array(ty.i32()))});
-    GlobalVar(Source{{12, 34}}, "v", ty.Of(s), ast::AddressSpace::kWorkgroup);
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_Workgroup_RuntimeArray) {
+    // type t = ptr<workgroup, array<i32>>;
+    Alias("t", ty.pointer(ty.array(Source{{12, 34}}, ty.i32()), ast::AddressSpace::kWorkgroup));
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
               R"(12:34 error: runtime-sized arrays can only be used in the <storage> address space
-note: while analyzing structure member S.m
-12:34 note: while instantiating 'var' v)");
+note: while instantiating ptr<workgroup, array<i32>, read_write>)");
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, StorageBufferBool) {
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_Workgroup_RuntimeArrayInStruct) {
+    // struct S { m : array<i32> };
+    // var<workgroup> v : S;
+    Structure("S", utils::Vector{Member(Source{{12, 34}}, "m", ty.array(ty.i32()))});
+    GlobalVar(Source{{56, 78}}, "v", ty.type_name("S"), ast::AddressSpace::kWorkgroup);
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              R"(error: runtime-sized arrays can only be used in the <storage> address space
+12:34 note: while analyzing structure member S.m
+56:78 note: while instantiating 'var' v)");
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_Workgroup_RuntimeArrayInStruct) {
+    // struct S { m : array<i32> };
+    // type t = ptr<workgroup, S>;
+    Structure("S", utils::Vector{Member(Source{{12, 34}}, "m", ty.array(ty.i32()))});
+    Alias(Source{{56, 78}}, "t", ty.pointer(ty.type_name("S"), ast::AddressSpace::kWorkgroup));
+
+    EXPECT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              R"(error: runtime-sized arrays can only be used in the <storage> address space
+12:34 note: while analyzing structure member S.m
+note: while instantiating ptr<workgroup, S, read_write>)");
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_Storage_Bool) {
     // var<storage> g : bool;
-    GlobalVar(Source{{56, 78}}, "g", ty.bool_(), ast::AddressSpace::kStorage, Binding(0_a),
-              Group(0_a));
+    GlobalVar(Source{{56, 78}}, "g", ty.bool_(Source{{12, 34}}), ast::AddressSpace::kStorage,
+              Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
     EXPECT_EQ(
         r()->error(),
-        R"(56:78 error: Type 'bool' cannot be used in address space 'storage' as it is non-host-shareable
+        R"(12:34 error: Type 'bool' cannot be used in address space 'storage' as it is non-host-shareable
 56:78 note: while instantiating 'var' g)");
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, StorageBufferBoolAlias) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_Storage_Bool) {
+    // type t = ptr<storage, bool>;
+    Alias(Source{{56, 78}}, "t",
+          ty.pointer(ty.bool_(Source{{12, 34}}), ast::AddressSpace::kStorage));
+
+    ASSERT_FALSE(r()->Resolve());
+
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: Type 'bool' cannot be used in address space 'storage' as it is non-host-shareable
+note: while instantiating ptr<storage, bool, read>)");
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_Storage_BoolAlias) {
     // type a = bool;
-    // var<storage, read> g : a;
-    auto* a = Alias("a", ty.bool_());
-    GlobalVar(Source{{56, 78}}, "g", ty.Of(a), ast::AddressSpace::kStorage, Binding(0_a),
-              Group(0_a));
-
-    ASSERT_FALSE(r()->Resolve());
-
-    EXPECT_EQ(
-        r()->error(),
-        R"(56:78 error: Type 'bool' cannot be used in address space 'storage' as it is non-host-shareable
-56:78 note: while instantiating 'var' g)");
-}
-
-TEST_F(ResolverAddressSpaceValidationTest, StorageBufferPointer) {
-    // var<storage> g : ptr<private, f32>;
-    GlobalVar(Source{{56, 78}}, "g", ty.pointer(ty.f32(), ast::AddressSpace::kPrivate),
+    // @binding(0) @group(0) var<storage, read> g : a;
+    Alias("a", ty.bool_());
+    GlobalVar(Source{{56, 78}}, "g", ty.type_name(Source{{12, 34}}, "a"),
               ast::AddressSpace::kStorage, Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
     EXPECT_EQ(
         r()->error(),
-        R"(56:78 error: Type 'ptr<private, f32, read_write>' cannot be used in address space 'storage' as it is non-host-shareable
+        R"(12:34 error: Type 'bool' cannot be used in address space 'storage' as it is non-host-shareable
 56:78 note: while instantiating 'var' g)");
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, StorageBufferIntScalar) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_Storage_BoolAlias) {
+    // type a = bool;
+    // type t = ptr<storage, a>;
+    Alias("a", ty.bool_());
+    Alias(Source{{56, 78}}, "t",
+          ty.pointer(ty.type_name(Source{{12, 34}}, "a"), ast::AddressSpace::kStorage));
+
+    ASSERT_FALSE(r()->Resolve());
+
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: Type 'bool' cannot be used in address space 'storage' as it is non-host-shareable
+note: while instantiating ptr<storage, bool, read>)");
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_Storage_Pointer) {
+    // var<storage> g : ptr<private, f32>;
+    GlobalVar(Source{{56, 78}}, "g",
+              ty.pointer(Source{{12, 34}}, ty.f32(), ast::AddressSpace::kPrivate),
+              ast::AddressSpace::kStorage, Binding(0_a), Group(0_a));
+
+    ASSERT_FALSE(r()->Resolve());
+
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: Type 'ptr<private, f32, read_write>' cannot be used in address space 'storage' as it is non-host-shareable
+56:78 note: while instantiating 'var' g)");
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_Storage_Pointer) {
+    // type t = ptr<storage, ptr<private, f32>>;
+    Alias("t", ty.pointer(Source{{56, 78}},
+                          ty.pointer(Source{{12, 34}}, ty.f32(), ast::AddressSpace::kPrivate),
+                          ast::AddressSpace::kStorage));
+
+    ASSERT_FALSE(r()->Resolve());
+
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: Type 'ptr<private, f32, read_write>' cannot be used in address space 'storage' as it is non-host-shareable
+56:78 note: while instantiating ptr<storage, ptr<private, f32, read_write>, read>)");
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_Storage_IntScalar) {
     // var<storage> g : i32;
-    GlobalVar(Source{{56, 78}}, "g", ty.i32(), ast::AddressSpace::kStorage, Binding(0_a),
-              Group(0_a));
+    GlobalVar("g", ty.i32(), ast::AddressSpace::kStorage, Binding(0_a), Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, StorageBufferF16) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_Storage_IntScalar) {
+    // type t = ptr<storage, i32;
+    Alias("t", ty.pointer(ty.i32(), ast::AddressSpace::kStorage));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_Storage_F16) {
+    // enable f16;
     // var<storage> g : f16;
     Enable(ast::Extension::kF16);
 
-    GlobalVar("g", ty.f16(Source{{56, 78}}), ast::AddressSpace::kStorage, Binding(0_a), Group(0_a));
+    GlobalVar("g", ty.f16(), ast::AddressSpace::kStorage, Binding(0_a), Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, StorageBufferF16Alias) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_Storage_F16) {
+    // enable f16;
+    // type t = ptr<storage, f16>;
+    Enable(ast::Extension::kF16);
+
+    Alias("t", ty.pointer(ty.f16(), ast::AddressSpace::kStorage));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_Storage_F16Alias) {
+    // enable f16;
     // type a = f16;
     // var<storage, read> g : a;
     Enable(ast::Extension::kF16);
 
-    auto* a = Alias("a", ty.f16());
-    GlobalVar("g", ty.type_name(Source{{56, 78}}, a->name), ast::AddressSpace::kStorage,
-              Binding(0_a), Group(0_a));
+    Alias("a", ty.f16());
+    GlobalVar("g", ty.type_name("a"), ast::AddressSpace::kStorage, Binding(0_a), Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, StorageBufferVectorF32) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_Storage_F16Alias) {
+    // enable f16;
+    // type a = f16;
+    // type t = ptr<storage, a>;
+    Enable(ast::Extension::kF16);
+
+    Alias("a", ty.f16());
+    Alias("t", ty.pointer(ty.type_name("a"), ast::AddressSpace::kStorage));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_Storage_VectorF32) {
     // var<storage> g : vec4<f32>;
-    GlobalVar(Source{{56, 78}}, "g", ty.vec4<f32>(), ast::AddressSpace::kStorage, Binding(0_a),
-              Group(0_a));
+    GlobalVar("g", ty.vec4<f32>(), ast::AddressSpace::kStorage, Binding(0_a), Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, StorageBufferVectorF16) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_Storage_VectorF32) {
+    // type t = ptr<storage, vec4<f32>>;
+    Alias("t", ty.pointer(ty.vec4<f32>(), ast::AddressSpace::kStorage));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_Storage_VectorF16) {
     // var<storage> g : vec4<f16>;
     Enable(ast::Extension::kF16);
-    GlobalVar("g", ty.vec(Source{{56, 78}}, ty.Of<f16>(), 4u), ast::AddressSpace::kStorage,
-              Binding(0_a), Group(0_a));
+    GlobalVar("g", ty.vec(ty.f16(), 4u), ast::AddressSpace::kStorage, Binding(0_a), Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, StorageBufferArrayF32) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_Storage_VectorF16) {
+    // type t = ptr<storage, vec4<f16>>;
+    Enable(ast::Extension::kF16);
+    Alias("t", ty.pointer(ty.vec(ty.f16(), 4u), ast::AddressSpace::kStorage));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_Storage_ArrayF32) {
+    // struct S{ a : f32 };
     // var<storage, read> g : array<S, 3u>;
-    auto* s = Structure("S", utils::Vector{Member("a", ty.f32())});
-    auto* a = ty.array(ty.Of(s), 3_u);
-    GlobalVar(Source{{56, 78}}, "g", a, ast::AddressSpace::kStorage, ast::Access::kRead,
-              Binding(0_a), Group(0_a));
+    Structure("S", utils::Vector{Member("a", ty.f32())});
+    GlobalVar("g", ty.array(ty.type_name("S"), 3_u), ast::AddressSpace::kStorage,
+              ast::Access::kRead, Binding(0_a), Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, StorageBufferArrayF16) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_Storage_ArrayF32) {
+    // struct S{ a : f32 };
+    // type t = ptr<storage, array<S, 3u>>;
+    Structure("S", utils::Vector{Member("a", ty.f32())});
+    Alias("t", ty.pointer(ty.array(ty.type_name("S"), 3_u), ast::AddressSpace::kStorage));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_Storage_ArrayF16) {
+    // enable f16;
+    // struct S{ a : f16 };
     // var<storage, read> g : array<S, 3u>;
     Enable(ast::Extension::kF16);
 
-    auto* s = Structure("S", utils::Vector{Member("a", ty.f16())});
-    auto* a = ty.array(ty.Of(s), 3_u);
-    GlobalVar(Source{{56, 78}}, "g", a, ast::AddressSpace::kStorage, ast::Access::kRead,
-              Binding(0_a), Group(0_a));
+    Structure("S", utils::Vector{Member("a", ty.f16())});
+    GlobalVar("g", ty.array(ty.type_name("S"), 3_u), ast::AddressSpace::kStorage,
+              ast::Access::kRead, Binding(0_a), Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, StorageBufferStructI32) {
-    // struct S { x : i32 };
-    // var<storage, read> g : S;
-    auto* s = Structure("S", utils::Vector{Member(Source{{12, 34}}, "x", ty.i32())});
-    GlobalVar(Source{{56, 78}}, "g", ty.Of(s), ast::AddressSpace::kStorage, ast::Access::kRead,
-              Binding(0_a), Group(0_a));
-
-    ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverAddressSpaceValidationTest, StorageBufferStructI32Aliases) {
-    // struct S { x : i32 };
-    // type a1 = S;
-    // var<storage, read> g : a1;
-    auto* s = Structure("S", utils::Vector{Member(Source{{12, 34}}, "x", ty.i32())});
-    auto* a1 = Alias("a1", ty.Of(s));
-    auto* a2 = Alias("a2", ty.Of(a1));
-    GlobalVar(Source{{56, 78}}, "g", ty.Of(a2), ast::AddressSpace::kStorage, ast::Access::kRead,
-              Binding(0_a), Group(0_a));
-
-    ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverAddressSpaceValidationTest, StorageBufferStructF16) {
-    // struct S { x : f16 };
-    // var<storage, read> g : S;
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_Storage_ArrayF16) {
+    // enable f16;
+    // struct S{ a : f16 };
+    // type t = ptr<storage, read, array<S, 3u>>;
     Enable(ast::Extension::kF16);
 
-    auto* s = Structure("S", utils::Vector{Member("x", ty.f16(Source{{12, 34}}))});
-    GlobalVar("g", ty.Of(s), ast::AddressSpace::kStorage, ast::Access::kRead, Binding(0_a),
+    Structure("S", utils::Vector{Member("a", ty.f16())});
+    Alias("t", ty.pointer(ty.array(ty.type_name("S"), 3_u), ast::AddressSpace::kStorage,
+                          ast::Access::kRead));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_Storage_StructI32) {
+    // struct S { x : i32 };
+    // var<storage, read> g : S;
+    Structure("S", utils::Vector{Member("x", ty.i32())});
+    GlobalVar("g", ty.type_name("S"), ast::AddressSpace::kStorage, ast::Access::kRead, Binding(0_a),
               Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, StorageBufferStructF16Aliases) {
-    // struct S { x : f16 };
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_Storage_StructI32) {
+    // struct S { x : i32 };
+    // type t = ptr<storage, read, S>;
+    Structure("S", utils::Vector{Member("x", ty.i32())});
+    Alias("t", ty.pointer(ty.type_name("S"), ast::AddressSpace::kStorage, ast::Access::kRead));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_Storage_StructI32Aliases) {
+    // struct S { x : i32 };
     // type a1 = S;
     // var<storage, read> g : a1;
+    Structure("S", utils::Vector{Member("x", ty.i32())});
+    Alias("a1", ty.type_name("S"));
+    Alias("a2", ty.type_name("a1"));
+    GlobalVar("g", ty.type_name("a2"), ast::AddressSpace::kStorage, ast::Access::kRead,
+              Binding(0_a), Group(0_a));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_Storage_StructI32Aliases) {
+    // struct S { x : i32 };
+    // type a1 = S;
+    // type t = ptr<storage, read, a1>;
+    Structure("S", utils::Vector{Member("x", ty.i32())});
+    Alias("a1", ty.type_name("S"));
+    Alias("a2", ty.type_name("a1"));
+    Alias("t", ty.pointer(ty.type_name("a2"), ast::AddressSpace::kStorage, ast::Access::kRead));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_Storage_StructF16) {
+    // struct S { x : f16 };
+    // var<storage, read> g : S;
     Enable(ast::Extension::kF16);
 
-    auto* s = Structure("S", utils::Vector{Member("x", ty.f16(Source{{12, 34}}))});
-    auto* a1 = Alias("a1", ty.Of(s));
-    auto* a2 = Alias("a2", ty.Of(a1));
-    GlobalVar("g", ty.Of(a2), ast::AddressSpace::kStorage, ast::Access::kRead, Binding(0_a),
+    Structure("S", utils::Vector{Member("x", ty.f16())});
+    GlobalVar("g", ty.type_name("S"), ast::AddressSpace::kStorage, ast::Access::kRead, Binding(0_a),
               Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, NotStorage_AccessMode) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_Storage_StructF16) {
+    // struct S { x : f16 };
+    // type t = ptr<storage, read, S>;
+    Enable(ast::Extension::kF16);
+
+    Structure("S", utils::Vector{Member("x", ty.f16())});
+    Alias("t", ty.pointer(ty.type_name("S"), ast::AddressSpace::kStorage, ast::Access::kRead));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_Storage_StructF16Aliases) {
+    // struct S { x : f16 };
+    // type a1 = S;
+    // var<storage, read> g : a1;
+    Enable(ast::Extension::kF16);
+
+    Structure("S", utils::Vector{Member("x", ty.f16())});
+    Alias("a1", ty.type_name("S"));
+    Alias("a2", ty.type_name("a1"));
+    GlobalVar("g", ty.type_name("a2"), ast::AddressSpace::kStorage, ast::Access::kRead,
+              Binding(0_a), Group(0_a));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_Storage_StructF16Aliases) {
+    // struct S { x : f16 };
+    // type a1 = S;
+    // type t = ptr<storage, read, a1>;
+    Enable(ast::Extension::kF16);
+
+    Structure("S", utils::Vector{Member("x", ty.f16())});
+    Alias("a1", ty.type_name("S"));
+    Alias("a2", ty.type_name("a1"));
+    Alias("g", ty.pointer(ty.type_name("a2"), ast::AddressSpace::kStorage, ast::Access::kRead));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_NotStorage_AccessMode) {
     // var<private, read> g : a;
-    GlobalVar(Source{{56, 78}}, "g", ty.i32(), ast::AddressSpace::kPrivate, ast::Access::kRead);
+    GlobalVar(Source{{12, 34}}, "g", ty.i32(), ast::AddressSpace::kPrivate, ast::Access::kRead);
 
     ASSERT_FALSE(r()->Resolve());
 
     EXPECT_EQ(
         r()->error(),
-        R"(56:78 error: only variables in <storage> address space may declare an access mode)");
+        R"(12:34 error: only variables in <storage> address space may declare an access mode)");
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, Storage_ReadAccessMode) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_NotStorage_AccessMode) {
+    // type t = ptr<private, read, a>;
+    Alias("t",
+          ty.pointer(Source{{12, 34}}, ty.i32(), ast::AddressSpace::kPrivate, ast::Access::kRead));
+
+    ASSERT_FALSE(r()->Resolve());
+
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: only pointers in <storage> address space may declare an access mode)");
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_Storage_ReadAccessMode) {
     // @group(0) @binding(0) var<storage, read> a : i32;
-    GlobalVar(Source{{56, 78}}, "a", ty.i32(), ast::AddressSpace::kStorage, ast::Access::kRead,
-              Group(0_a), Binding(0_a));
+    GlobalVar("a", ty.i32(), ast::AddressSpace::kStorage, ast::Access::kRead, Group(0_a),
+              Binding(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, Storage_ReadWriteAccessMode) {
-    // @group(0) @binding(0) var<storage, read_write> a : i32;
-    GlobalVar(Source{{56, 78}}, "a", ty.i32(), ast::AddressSpace::kStorage, ast::Access::kReadWrite,
-              Group(0_a), Binding(0_a));
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_Storage_ReadAccessMode) {
+    // type t = ptr<storage, read, i32>;
+    Alias("t", ty.pointer(ty.i32(), ast::AddressSpace::kStorage, ast::Access::kRead));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, Storage_WriteAccessMode) {
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_Storage_ReadWriteAccessMode) {
     // @group(0) @binding(0) var<storage, read_write> a : i32;
-    GlobalVar(Source{{56, 78}}, "a", ty.i32(), ast::AddressSpace::kStorage, ast::Access::kWrite,
+    GlobalVar("a", ty.i32(), ast::AddressSpace::kStorage, ast::Access::kReadWrite, Group(0_a),
+              Binding(0_a));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_Storage_ReadWriteAccessMode) {
+    // type t = ptr<storage, read_write, i32>;
+    Alias("t", ty.pointer(ty.i32(), ast::AddressSpace::kStorage, ast::Access::kReadWrite));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_Storage_WriteAccessMode) {
+    // @group(0) @binding(0) var<storage, read_write> a : i32;
+    GlobalVar(Source{{12, 34}}, "a", ty.i32(), ast::AddressSpace::kStorage, ast::Access::kWrite,
               Group(0_a), Binding(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
     EXPECT_EQ(r()->error(),
-              R"(56:78 error: access mode 'write' is not valid for the 'storage' address space)");
+              R"(12:34 error: access mode 'write' is not valid for the 'storage' address space)");
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, UniformBuffer_Struct_Runtime) {
-    // struct S { m:  array<f32>; };
-    // @group(0) @binding(0) var<uniform, > svar : S;
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_Storage_WriteAccessMode) {
+    // type t = ptr<storage, read_write, i32>;
+    Alias("t",
+          ty.pointer(Source{{12, 34}}, ty.i32(), ast::AddressSpace::kStorage, ast::Access::kWrite));
 
-    auto* s = Structure(Source{{12, 34}}, "S", utils::Vector{Member("m", ty.array<i32>())});
+    ASSERT_FALSE(r()->Resolve());
 
-    GlobalVar(Source{{56, 78}}, "svar", ty.Of(s), ast::AddressSpace::kUniform, Binding(0_a),
-              Group(0_a));
+    EXPECT_EQ(r()->error(),
+              R"(12:34 error: access mode 'write' is not valid for the 'storage' address space)");
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_UniformBuffer_Struct_Runtime) {
+    // struct S { m: array<f32>; };
+    // @group(0) @binding(0) var<uniform> svar : S;
+
+    Structure("S",
+              utils::Vector{Member(Source{{56, 78}}, "m", ty.array(Source{{12, 34}}, ty.i32()))});
+
+    GlobalVar(Source{{90, 12}}, "svar", ty.type_name("S"), ast::AddressSpace::kUniform,
+              Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
-              R"(56:78 error: runtime-sized arrays can only be used in the <storage> address space
-note: while analyzing structure member S.m
-56:78 note: while instantiating 'var' svar)");
+              R"(12:34 error: runtime-sized arrays can only be used in the <storage> address space
+56:78 note: while analyzing structure member S.m
+90:12 note: while instantiating 'var' svar)");
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, UniformBufferBool) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_UniformBuffer_Struct_Runtime) {
+    // struct S { m: array<f32>; };
+    // type t = ptr<uniform, S>;
+
+    Structure("S",
+              utils::Vector{Member(Source{{56, 78}}, "m", ty.array(Source{{12, 34}}, ty.i32()))});
+
+    Alias("t", ty.pointer(Source{{90, 12}}, ty.type_name("S"), ast::AddressSpace::kUniform));
+
+    ASSERT_FALSE(r()->Resolve());
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: uniform storage requires that array elements be aligned to 16 bytes, but array element alignment is currently 4. Consider using a vector or struct as the element type instead.
+note: see layout of struct:
+/*           align(4) size(4) */ struct S {
+/* offset(0) align(4) size(4) */   m : array<i32>;
+/*                            */ };
+90:12 note: 'S' used in address space 'uniform' here)");
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_UniformBufferBool) {
     // var<uniform> g : bool;
-    GlobalVar(Source{{56, 78}}, "g", ty.bool_(), ast::AddressSpace::kUniform, Binding(0_a),
-              Group(0_a));
+    GlobalVar(Source{{56, 78}}, "g", ty.bool_(Source{{12, 34}}), ast::AddressSpace::kUniform,
+              Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
     EXPECT_EQ(
         r()->error(),
-        R"(56:78 error: Type 'bool' cannot be used in address space 'uniform' as it is non-host-shareable
+        R"(12:34 error: Type 'bool' cannot be used in address space 'uniform' as it is non-host-shareable
 56:78 note: while instantiating 'var' g)");
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, UniformBufferBoolAlias) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_UniformBufferBool) {
+    // type t = ptr<uniform, bool>;
+    Alias("t",
+          ty.pointer(Source{{56, 78}}, ty.bool_(Source{{12, 34}}), ast::AddressSpace::kUniform));
+
+    ASSERT_FALSE(r()->Resolve());
+
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: Type 'bool' cannot be used in address space 'uniform' as it is non-host-shareable
+56:78 note: while instantiating ptr<uniform, bool, read>)");
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_UniformBufferBoolAlias) {
     // type a = bool;
     // var<uniform> g : a;
-    auto* a = Alias("a", ty.bool_());
-    GlobalVar(Source{{56, 78}}, "g", ty.Of(a), ast::AddressSpace::kUniform, Binding(0_a),
-              Group(0_a));
-
-    ASSERT_FALSE(r()->Resolve());
-
-    EXPECT_EQ(
-        r()->error(),
-        R"(56:78 error: Type 'bool' cannot be used in address space 'uniform' as it is non-host-shareable
-56:78 note: while instantiating 'var' g)");
-}
-
-TEST_F(ResolverAddressSpaceValidationTest, UniformBufferPointer) {
-    // var<uniform> g : ptr<private, f32>;
-    GlobalVar(Source{{56, 78}}, "g", ty.pointer(ty.f32(), ast::AddressSpace::kPrivate),
+    Alias("a", ty.bool_());
+    GlobalVar(Source{{56, 78}}, "g", ty.type_name(Source{{12, 34}}, "a"),
               ast::AddressSpace::kUniform, Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
     EXPECT_EQ(
         r()->error(),
-        R"(56:78 error: Type 'ptr<private, f32, read_write>' cannot be used in address space 'uniform' as it is non-host-shareable
+        R"(12:34 error: Type 'bool' cannot be used in address space 'uniform' as it is non-host-shareable
 56:78 note: while instantiating 'var' g)");
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, UniformBufferIntScalar) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_UniformBufferBoolAlias) {
+    // type a = bool;
+    // type t = ptr<uniform, a>;
+    Alias("a", ty.bool_());
+    Alias("t", ty.pointer(Source{{56, 78}}, ty.type_name(Source{{12, 34}}, "a"),
+                          ast::AddressSpace::kUniform));
+
+    ASSERT_FALSE(r()->Resolve());
+
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: Type 'bool' cannot be used in address space 'uniform' as it is non-host-shareable
+56:78 note: while instantiating ptr<uniform, bool, read>)");
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_UniformPointer) {
+    // var<uniform> g : ptr<private, f32>;
+    GlobalVar(Source{{56, 78}}, "g",
+              ty.pointer(Source{{12, 34}}, ty.f32(), ast::AddressSpace::kPrivate),
+              ast::AddressSpace::kUniform, Binding(0_a), Group(0_a));
+
+    ASSERT_FALSE(r()->Resolve());
+
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: Type 'ptr<private, f32, read_write>' cannot be used in address space 'uniform' as it is non-host-shareable
+56:78 note: while instantiating 'var' g)");
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_UniformPointer) {
+    // type t = ptr<uniform, ptr<private, f32>>;
+    Alias("t", ty.pointer(Source{{56, 78}},
+                          ty.pointer(Source{{12, 34}}, ty.f32(), ast::AddressSpace::kPrivate),
+                          ast::AddressSpace::kUniform));
+
+    ASSERT_FALSE(r()->Resolve());
+
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: Type 'ptr<private, f32, read_write>' cannot be used in address space 'uniform' as it is non-host-shareable
+56:78 note: while instantiating ptr<uniform, ptr<private, f32, read_write>, read>)");
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_UniformBufferIntScalar) {
     // var<uniform> g : i32;
     GlobalVar(Source{{56, 78}}, "g", ty.i32(), ast::AddressSpace::kUniform, Binding(0_a),
               Group(0_a));
@@ -347,124 +665,241 @@
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, UniformBufferF16) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_UniformBufferIntScalar) {
+    // type t = ptr<uniform, i32>;
+    Alias("t", ty.pointer(ty.i32(), ast::AddressSpace::kUniform));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_UniformBufferF16) {
+    // enable f16;
     // var<uniform> g : f16;
     Enable(ast::Extension::kF16);
 
-    GlobalVar(Source{{56, 78}}, "g", ty.f16(), ast::AddressSpace::kUniform, Binding(0_a),
-              Group(0_a));
+    GlobalVar("g", ty.f16(), ast::AddressSpace::kUniform, Binding(0_a), Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, UniformBufferVectorF32) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_UniformBufferF16) {
+    // enable f16;
+    // type t = ptr<uniform, f16>;
+    Enable(ast::Extension::kF16);
+
+    Alias("t", ty.pointer(ty.f16(), ast::AddressSpace::kUniform));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_UniformBufferVectorF32) {
     // var<uniform> g : vec4<f32>;
-    GlobalVar(Source{{56, 78}}, "g", ty.vec4<f32>(), ast::AddressSpace::kUniform, Binding(0_a),
-              Group(0_a));
+    GlobalVar("g", ty.vec4<f32>(), ast::AddressSpace::kUniform, Binding(0_a), Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, UniformBufferVectorF16) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_UniformBufferVectorF32) {
+    // type t = ptr<uniform, vec4<f32>>;
+    Alias("t", ty.pointer(ty.vec4<f32>(), ast::AddressSpace::kUniform));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_UniformBufferVectorF16) {
+    // enable f16;
     // var<uniform> g : vec4<f16>;
     Enable(ast::Extension::kF16);
 
-    GlobalVar(Source{{56, 78}}, "g", ty.vec4<f16>(), ast::AddressSpace::kUniform, Binding(0_a),
-              Group(0_a));
+    GlobalVar("g", ty.vec4<f16>(), ast::AddressSpace::kUniform, Binding(0_a), Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, UniformBufferArrayF32) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_UniformBufferVectorF16) {
+    // enable f16;
+    // type t = ptr<uniform, vec4<f16>>;
+    Enable(ast::Extension::kF16);
+
+    Alias("t", ty.pointer(ty.vec4<f16>(), ast::AddressSpace::kUniform));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_UniformBufferArrayF32) {
     // struct S {
     //   @size(16) f : f32;
     // }
     // var<uniform> g : array<S, 3u>;
-    auto* s = Structure("S", utils::Vector{Member("a", ty.f32(), utils::Vector{MemberSize(16_a)})});
-    auto* a = ty.array(ty.Of(s), 3_u);
-    GlobalVar(Source{{56, 78}}, "g", a, ast::AddressSpace::kUniform, Binding(0_a), Group(0_a));
+    Structure("S", utils::Vector{Member("a", ty.f32(), utils::Vector{MemberSize(16_a)})});
+    GlobalVar("g", ty.array(ty.type_name("S"), 3_u), ast::AddressSpace::kUniform, Binding(0_a),
+              Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, UniformBufferArrayF16) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_UniformBufferArrayF32) {
+    // struct S {
+    //   @size(16) f : f32;
+    // }
+    // type t = ptr<uniform, array<S, 3u>>;
+    Structure("S", utils::Vector{Member("a", ty.f32(), utils::Vector{MemberSize(16_a)})});
+    Alias("t", ty.pointer(ty.array(ty.type_name("S"), 3_u), ast::AddressSpace::kUniform));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_UniformBufferArrayF16) {
+    // enable f16;
     // struct S {
     //   @size(16) f : f16;
     // }
     // var<uniform> g : array<S, 3u>;
     Enable(ast::Extension::kF16);
 
-    auto* s = Structure("S", utils::Vector{Member("a", ty.f16(), utils::Vector{MemberSize(16_a)})});
-    auto* a = ty.array(ty.Of(s), 3_u);
-    GlobalVar(Source{{56, 78}}, "g", a, ast::AddressSpace::kUniform, Binding(0_a), Group(0_a));
-
-    ASSERT_TRUE(r()->Resolve()) << r()->error();
-}
-
-TEST_F(ResolverAddressSpaceValidationTest, UniformBufferStructI32) {
-    // struct S { x : i32 };
-    // var<uniform> g :  S;
-    auto* s = Structure("S", utils::Vector{Member(Source{{12, 34}}, "x", ty.i32())});
-    GlobalVar(Source{{56, 78}}, "g", ty.Of(s), ast::AddressSpace::kUniform, Binding(0_a),
+    Structure("S", utils::Vector{Member("a", ty.f16(), utils::Vector{MemberSize(16_a)})});
+    GlobalVar("g", ty.array(ty.type_name("S"), 3_u), ast::AddressSpace::kUniform, Binding(0_a),
               Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, UniformBufferStructI32Aliases) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_UniformBufferArrayF16) {
+    // enable f16;
+    // struct S {
+    //   @size(16) f : f16;
+    // }
+    // type t = ptr<uniform, array<S, 3u>>;
+    Enable(ast::Extension::kF16);
+
+    Structure("S", utils::Vector{Member("a", ty.f16(), utils::Vector{MemberSize(16_a)})});
+    Alias("t", ty.pointer(ty.array(ty.type_name("S"), 3_u), ast::AddressSpace::kUniform));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_UniformBufferStructI32) {
+    // struct S { x : i32 };
+    // var<uniform> g : S;
+    Structure("S", utils::Vector{Member("x", ty.i32())});
+    GlobalVar("g", ty.type_name("S"), ast::AddressSpace::kUniform, Binding(0_a), Group(0_a));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_UniformBufferStructI32) {
+    // struct S { x : i32 };
+    // type t = ptr<uniform, S>;
+    Structure("S", utils::Vector{Member("x", ty.i32())});
+    Alias("t", ty.pointer(ty.type_name("S"), ast::AddressSpace::kUniform));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_UniformBufferStructI32Aliases) {
     // struct S { x : i32 };
     // type a1 = S;
     // var<uniform> g : a1;
-    auto* s = Structure("S", utils::Vector{Member(Source{{12, 34}}, "x", ty.i32())});
-    auto* a1 = Alias("a1", ty.Of(s));
-    GlobalVar(Source{{56, 78}}, "g", ty.Of(a1), ast::AddressSpace::kUniform, Binding(0_a),
-              Group(0_a));
+    Structure("S", utils::Vector{Member("x", ty.i32())});
+    Alias("a1", ty.type_name("S"));
+    GlobalVar("g", ty.type_name("a1"), ast::AddressSpace::kUniform, Binding(0_a), Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, UniformBufferStructF16) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_UniformBufferStructI32Aliases) {
+    // struct S { x : i32 };
+    // type a1 = S;
+    // type t = ptr<uniform, a1>;
+    Structure("S", utils::Vector{Member("x", ty.i32())});
+    Alias("a1", ty.type_name("S"));
+    Alias("t", ty.pointer(ty.type_name("a1"), ast::AddressSpace::kUniform));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_UniformBufferStructF16) {
+    // enable f16;
     // struct S { x : f16 };
-    // var<uniform> g :  S;
+    // var<uniform> g : S;
     Enable(ast::Extension::kF16);
 
-    auto* s = Structure("S", utils::Vector{Member(Source{{12, 34}}, "x", ty.f16())});
-    GlobalVar(Source{{56, 78}}, "g", ty.Of(s), ast::AddressSpace::kUniform, Binding(0_a),
-              Group(0_a));
+    Structure("S", utils::Vector{Member("x", ty.f16())});
+    GlobalVar("g", ty.type_name("S"), ast::AddressSpace::kUniform, Binding(0_a), Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, UniformBufferStructF16Aliases) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_UniformBufferStructF16) {
+    // enable f16;
+    // struct S { x : f16 };
+    // type t = ptr<uniform, S>;
+    Enable(ast::Extension::kF16);
+
+    Structure("S", utils::Vector{Member("x", ty.f16())});
+    Alias("t", ty.pointer(ty.type_name("S"), ast::AddressSpace::kUniform));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_UniformBufferStructF16Aliases) {
+    // enable f16;
     // struct S { x : f16 };
     // type a1 = S;
     // var<uniform> g : a1;
     Enable(ast::Extension::kF16);
 
-    auto* s = Structure("S", utils::Vector{Member(Source{{12, 34}}, "x", ty.f16())});
-    auto* a1 = Alias("a1", ty.Of(s));
-    GlobalVar(Source{{56, 78}}, "g", ty.Of(a1), ast::AddressSpace::kUniform, Binding(0_a),
-              Group(0_a));
+    Structure("S", utils::Vector{Member("x", ty.f16())});
+    Alias("a1", ty.type_name("S"));
+    GlobalVar("g", ty.type_name("a1"), ast::AddressSpace::kUniform, Binding(0_a), Group(0_a));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, PushConstantBool) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_UniformBufferStructF16Aliases) {
+    // enable f16;
+    // struct S { x : f16 };
+    // type a1 = S;
+    // type t = ptr<uniform, a1>;
+    Enable(ast::Extension::kF16);
+
+    Structure("S", utils::Vector{Member("x", ty.f16())});
+    Alias("a1", ty.type_name("S"));
+    Alias("t", ty.pointer(ty.type_name("a1"), ast::AddressSpace::kUniform));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_PushConstantBool) {
     // enable chromium_experimental_push_constant;
     // var<push_constant> g : bool;
     Enable(ast::Extension::kChromiumExperimentalPushConstant);
-    GlobalVar(Source{{56, 78}}, "g", ty.bool_(), ast::AddressSpace::kPushConstant);
+    GlobalVar(Source{{56, 78}}, "g", ty.bool_(Source{{12, 34}}), ast::AddressSpace::kPushConstant);
 
     ASSERT_FALSE(r()->Resolve());
     EXPECT_EQ(
         r()->error(),
-        R"(56:78 error: Type 'bool' cannot be used in address space 'push_constant' as it is non-host-shareable
+        R"(12:34 error: Type 'bool' cannot be used in address space 'push_constant' as it is non-host-shareable
 56:78 note: while instantiating 'var' g)");
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, PushConstantF16) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_PushConstantBool) {
     // enable chromium_experimental_push_constant;
+    // type t = ptr<push_constant, bool>;
+    Enable(ast::Extension::kChromiumExperimentalPushConstant);
+    Alias(Source{{56, 78}}, "t",
+          ty.pointer(ty.bool_(Source{{12, 34}}), ast::AddressSpace::kPushConstant));
+
+    ASSERT_FALSE(r()->Resolve());
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: Type 'bool' cannot be used in address space 'push_constant' as it is non-host-shareable
+note: while instantiating ptr<push_constant, bool, read_write>)");
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_PushConstantF16) {
     // enable f16;
+    // enable chromium_experimental_push_constant;
     // var<push_constant> g : f16;
     Enable(ast::Extension::kF16);
     Enable(ast::Extension::kChromiumExperimentalPushConstant);
@@ -472,25 +907,53 @@
 
     ASSERT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
-              "56:78 error: using f16 types in 'push_constant' address space is not "
-              "implemented yet");
+              "error: using f16 types in 'push_constant' address space is not implemented yet");
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, PushConstantPointer) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_PushConstantF16) {
+    // enable f16;
+    // enable chromium_experimental_push_constant;
+    // type t = ptr<push_constant, f16>;
+    Enable(ast::Extension::kF16);
+    Enable(ast::Extension::kChromiumExperimentalPushConstant);
+    Alias("t", ty.pointer(ty.f16(Source{{56, 78}}), ast::AddressSpace::kPushConstant));
+
+    ASSERT_FALSE(r()->Resolve());
+    EXPECT_EQ(r()->error(),
+              "error: using f16 types in 'push_constant' address space is not implemented yet");
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_PushConstantPointer) {
     // enable chromium_experimental_push_constant;
     // var<push_constant> g : ptr<private, f32>;
     Enable(ast::Extension::kChromiumExperimentalPushConstant);
-    GlobalVar(Source{{56, 78}}, "g", ty.pointer(ty.f32(), ast::AddressSpace::kPrivate),
+    GlobalVar(Source{{56, 78}}, "g",
+              ty.pointer(Source{{12, 34}}, ty.f32(), ast::AddressSpace::kPrivate),
               ast::AddressSpace::kPushConstant);
 
     ASSERT_FALSE(r()->Resolve());
     EXPECT_EQ(
         r()->error(),
-        R"(56:78 error: Type 'ptr<private, f32, read_write>' cannot be used in address space 'push_constant' as it is non-host-shareable
+        R"(12:34 error: Type 'ptr<private, f32, read_write>' cannot be used in address space 'push_constant' as it is non-host-shareable
 56:78 note: while instantiating 'var' g)");
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, PushConstantIntScalar) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_PushConstantPointer) {
+    // enable chromium_experimental_push_constant;
+    // type t = ptr<push_constant, ptr<private, f32>>;
+    Enable(ast::Extension::kChromiumExperimentalPushConstant);
+    Alias(Source{{56, 78}}, "t",
+          ty.pointer(ty.pointer(Source{{12, 34}}, ty.f32(), ast::AddressSpace::kPrivate),
+                     ast::AddressSpace::kPushConstant));
+
+    ASSERT_FALSE(r()->Resolve());
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: Type 'ptr<private, f32, read_write>' cannot be used in address space 'push_constant' as it is non-host-shareable
+note: while instantiating ptr<push_constant, ptr<private, f32, read_write>, read_write>)");
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_PushConstantIntScalar) {
     // enable chromium_experimental_push_constant;
     // var<push_constant> g : i32;
     Enable(ast::Extension::kChromiumExperimentalPushConstant);
@@ -499,7 +962,16 @@
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, PushConstantVectorF32) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_PushConstantIntScalar) {
+    // enable chromium_experimental_push_constant;
+    // type t = ptr<push_constant, i32>;
+    Enable(ast::Extension::kChromiumExperimentalPushConstant);
+    Alias("t", ty.pointer(ty.i32(), ast::AddressSpace::kPushConstant));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_PushConstantVectorF32) {
     // enable chromium_experimental_push_constant;
     // var<push_constant> g : vec4<f32>;
     Enable(ast::Extension::kChromiumExperimentalPushConstant);
@@ -508,29 +980,35 @@
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, PushConstantArrayF32) {
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_PushConstantVectorF32) {
     // enable chromium_experimental_push_constant;
-    // struct S { a : f32}
-    // var<push_constant> g : array<S, 3u>;
+    // var<push_constant> g : vec4<f32>;
     Enable(ast::Extension::kChromiumExperimentalPushConstant);
-    auto* s = Structure("S", utils::Vector{Member("a", ty.f32())});
-    auto* a = ty.array(ty.Of(s), 3_u);
-    GlobalVar("g", a, ast::AddressSpace::kPushConstant);
+    Alias("t", ty.pointer(ty.vec4<f32>(), ast::AddressSpace::kPushConstant));
 
     ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
-TEST_F(ResolverAddressSpaceValidationTest, PushConstantWithInitializer) {
+TEST_F(ResolverAddressSpaceValidationTest, GlobalVariable_PushConstantArrayF32) {
     // enable chromium_experimental_push_constant;
-    // var<push_constant> a : u32 = 0u;
+    // struct S { a : f32}
+    // var<push_constant> g : array<S, 3u>;
     Enable(ast::Extension::kChromiumExperimentalPushConstant);
-    GlobalVar(Source{{1u, 2u}}, "a", ty.u32(), ast::AddressSpace::kPushConstant,
-              Expr(Source{{3u, 4u}}, u32(0)));
+    Structure("S", utils::Vector{Member("a", ty.f32())});
+    GlobalVar("g", ty.array(ty.type_name("S"), 3_u), ast::AddressSpace::kPushConstant);
 
-    ASSERT_FALSE(r()->Resolve());
-    EXPECT_EQ(
-        r()->error(),
-        R"(1:2 error: var of address space 'push_constant' cannot have an initializer. var initializers are only supported for the address spacees 'private' and 'function')");
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
+}
+
+TEST_F(ResolverAddressSpaceValidationTest, PointerAlias_PushConstantArrayF32) {
+    // enable chromium_experimental_push_constant;
+    // struct S { a : f32}
+    // type t = ptr<push_constant, array<S, 3u>>;
+    Enable(ast::Extension::kChromiumExperimentalPushConstant);
+    Structure("S", utils::Vector{Member("a", ty.f32())});
+    Alias("t", ty.pointer(ty.array(ty.type_name("S"), 3_u), ast::AddressSpace::kPushConstant));
+
+    ASSERT_TRUE(r()->Resolve()) << r()->error();
 }
 
 }  // namespace
diff --git a/src/tint/resolver/atomics_validation_test.cc b/src/tint/resolver/atomics_validation_test.cc
index 08bdf13..8b79941 100644
--- a/src/tint/resolver/atomics_validation_test.cc
+++ b/src/tint/resolver/atomics_validation_test.cc
@@ -40,7 +40,7 @@
 }
 
 TEST_F(ResolverAtomicValidationTest, AddressSpace_Storage_Struct) {
-    auto* s = Structure("s", utils::Vector{Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))});
+    auto* s = Structure("s", utils::Vector{Member(Source{{12, 34}}, "a", ty.atomic(ty.i32()))});
     GlobalVar("g", ty.Of(s), ast::AddressSpace::kStorage, ast::Access::kReadWrite, Group(0_a),
               Binding(0_a));
 
@@ -55,31 +55,28 @@
 }
 
 TEST_F(ResolverAtomicValidationTest, InvalidAddressSpace_Simple) {
-    GlobalVar("a", ty.atomic(Source{{12, 34}}, ty.i32()), ast::AddressSpace::kPrivate);
+    GlobalVar(Source{{12, 34}}, "a", ty.atomic(ty.i32()), ast::AddressSpace::kPrivate);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
-              "12:34 error: atomic variables must have <storage> or <workgroup> "
-              "address space");
+              "12:34 error: atomic variables must have <storage> or <workgroup> address space");
 }
 
 TEST_F(ResolverAtomicValidationTest, InvalidAddressSpace_Array) {
-    GlobalVar("a", ty.atomic(Source{{12, 34}}, ty.i32()), ast::AddressSpace::kPrivate);
+    GlobalVar(Source{{12, 34}}, "a", ty.atomic(ty.i32()), ast::AddressSpace::kPrivate);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
-              "12:34 error: atomic variables must have <storage> or <workgroup> "
-              "address space");
+              "12:34 error: atomic variables must have <storage> or <workgroup> address space");
 }
 
 TEST_F(ResolverAtomicValidationTest, InvalidAddressSpace_Struct) {
-    auto* s = Structure("s", utils::Vector{Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))});
-    GlobalVar("g", ty.Of(s), ast::AddressSpace::kPrivate);
+    auto* s = Structure("s", utils::Vector{Member("a", ty.atomic(ty.i32()))});
+    GlobalVar(Source{{56, 78}}, "g", ty.Of(s), ast::AddressSpace::kPrivate);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
-              "error: atomic variables must have <storage> or <workgroup> "
-              "address space\n"
+              "56:78 error: atomic variables must have <storage> or <workgroup> address space\n"
               "note: atomic sub-type of 's' is declared here");
 }
 
@@ -91,12 +88,11 @@
     auto* Inner =
         Structure("Inner", utils::Vector{Member("m", ty.atomic(Source{{12, 34}}, ty.i32()))});
     auto* Outer = Structure("Outer", utils::Vector{Member("m", ty.Of(Inner))});
-    GlobalVar("g", ty.Of(Outer), ast::AddressSpace::kPrivate);
+    GlobalVar(Source{{56, 78}}, "g", ty.Of(Outer), ast::AddressSpace::kPrivate);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
-              "error: atomic variables must have <storage> or <workgroup> "
-              "address space\n"
+              "56:78 error: atomic variables must have <storage> or <workgroup> address space\n"
               "note: atomic sub-type of 'Outer' is declared here");
 }
 
@@ -108,13 +104,12 @@
     auto* Inner =
         Structure("Inner", utils::Vector{Member(Source{{12, 34}}, "m", ty.atomic(ty.i32()))});
     auto* Outer = Structure("Outer", utils::Vector{Member("m", ty.Of(Inner))});
-    GlobalVar("g", ty.Of(Outer), ast::AddressSpace::kPrivate);
+    GlobalVar(Source{{56, 78}}, "g", ty.Of(Outer), ast::AddressSpace::kPrivate);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
-              "error: atomic variables must have <storage> or <workgroup> "
-              "address space\n"
-              "12:34 note: atomic sub-type of 'Outer' is declared here");
+              R"(56:78 error: atomic variables must have <storage> or <workgroup> address space
+12:34 note: atomic sub-type of 'Outer' is declared here)");
 }
 
 TEST_F(ResolverAtomicValidationTest, InvalidAddressSpace_ArrayOfArray) {
@@ -127,8 +122,7 @@
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
-              "error: atomic variables must have <storage> or <workgroup> "
-              "address space");
+              "56:78 error: atomic variables must have <storage> or <workgroup> address space");
 }
 
 TEST_F(ResolverAtomicValidationTest, InvalidAddressSpace_ArrayOfStruct) {
@@ -137,14 +131,13 @@
     // };
     // var<private> v: array<S, 5u>;
 
-    auto* s = Structure("S", utils::Vector{Member("m", ty.atomic<u32>())});
+    auto* s = Structure("S", utils::Vector{Member(Source{{12, 34}}, "m", ty.atomic<u32>())});
     GlobalVar(Source{{56, 78}}, "v", ty.array(ty.Of(s), 5_u), ast::AddressSpace::kPrivate);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
-              "error: atomic variables must have <storage> or <workgroup> "
-              "address space\n"
-              "note: atomic sub-type of 'array<S, 5>' is declared here");
+              R"(56:78 error: atomic variables must have <storage> or <workgroup> address space
+12:34 note: atomic sub-type of 'array<S, 5>' is declared here)");
 }
 
 TEST_F(ResolverAtomicValidationTest, InvalidAddressSpace_ArrayOfStructOfArray) {
@@ -154,16 +147,14 @@
     // };
     // var<private> v: array<S, 5u>;
 
-    auto* atomic_array =
-        Alias(Source{{12, 34}}, "AtomicArray", ty.atomic(Source{{12, 34}}, ty.i32()));
-    auto* s = Structure("S", utils::Vector{Member("m", ty.Of(atomic_array))});
+    auto* atomic_array = Alias("AtomicArray", ty.atomic(ty.i32()));
+    auto* s = Structure("S", utils::Vector{Member(Source{{12, 34}}, "m", ty.Of(atomic_array))});
     GlobalVar(Source{{56, 78}}, "v", ty.array(ty.Of(s), 5_u), ast::AddressSpace::kPrivate);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
-              "error: atomic variables must have <storage> or <workgroup> "
-              "address space\n"
-              "note: atomic sub-type of 'array<S, 5>' is declared here");
+              R"(56:78 error: atomic variables must have <storage> or <workgroup> address space
+12:34 note: atomic sub-type of 'array<S, 5>' is declared here)");
 }
 
 TEST_F(ResolverAtomicValidationTest, InvalidAddressSpace_Complex) {
@@ -181,19 +172,18 @@
     // struct S0 { x: S1; };
     // var<private> g : S0;
 
-    auto* atomic_array =
-        Alias(Source{{12, 34}}, "AtomicArray", ty.atomic(Source{{12, 34}}, ty.i32()));
+    auto* atomic_array = Alias("AtomicArray", ty.atomic(ty.i32()));
     auto* array_i32_4 = ty.array(ty.i32(), 4_u);
     auto* array_atomic_u32_8 = ty.array(ty.atomic(ty.u32()), 8_u);
     auto* array_atomic_i32_4 = ty.array(ty.atomic(ty.i32()), 4_u);
 
     auto* s6 = Structure("S6", utils::Vector{Member("x", array_i32_4)});
-    auto* s5 = Structure("S5", utils::Vector{Member("x", ty.Of(s6)),             //
-                                             Member("y", ty.Of(atomic_array)),   //
-                                             Member("z", array_atomic_u32_8)});  //
-    auto* s4 = Structure("S4", utils::Vector{Member("x", ty.Of(s6)),             //
-                                             Member("y", ty.Of(s5)),             //
-                                             Member("z", array_atomic_i32_4)});  //
+    auto* s5 = Structure("S5", utils::Vector{Member("x", ty.Of(s6)),                              //
+                                             Member(Source{{12, 34}}, "y", ty.Of(atomic_array)),  //
+                                             Member("z", array_atomic_u32_8)});                   //
+    auto* s4 = Structure("S4", utils::Vector{Member("x", ty.Of(s6)),                              //
+                                             Member("y", ty.Of(s5)),                              //
+                                             Member("z", array_atomic_i32_4)});                   //
     auto* s3 = Structure("S3", utils::Vector{Member("x", ty.Of(s4))});
     auto* s2 = Structure("S2", utils::Vector{Member("x", ty.Of(s3))});
     auto* s1 = Structure("S1", utils::Vector{Member("x", ty.Of(s2))});
@@ -202,33 +192,32 @@
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
-              "error: atomic variables must have <storage> or <workgroup> "
-              "address space\n"
-              "note: atomic sub-type of 'S0' is declared here");
+              R"(56:78 error: atomic variables must have <storage> or <workgroup> address space
+12:34 note: atomic sub-type of 'S0' is declared here)");
 }
 
 TEST_F(ResolverAtomicValidationTest, Struct_AccessMode_Read) {
-    auto* s = Structure("s", utils::Vector{Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))});
+    auto* s = Structure("s", utils::Vector{Member(Source{{12, 34}}, "a", ty.atomic(ty.i32()))});
     GlobalVar(Source{{56, 78}}, "g", ty.Of(s), ast::AddressSpace::kStorage, ast::Access::kRead,
               Group(0_a), Binding(0_a));
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "error: atomic variables in <storage> address space must have read_write "
-              "access mode\n"
-              "note: atomic sub-type of 's' is declared here");
+    EXPECT_EQ(
+        r()->error(),
+        R"(56:78 error: atomic variables in <storage> address space must have read_write access mode
+12:34 note: atomic sub-type of 's' is declared here)");
 }
 
 TEST_F(ResolverAtomicValidationTest, InvalidAccessMode_Struct) {
-    auto* s = Structure("s", utils::Vector{Member("a", ty.atomic(Source{{12, 34}}, ty.i32()))});
+    auto* s = Structure("s", utils::Vector{Member(Source{{12, 34}}, "a", ty.atomic(ty.i32()))});
     GlobalVar(Source{{56, 78}}, "g", ty.Of(s), ast::AddressSpace::kStorage, ast::Access::kRead,
               Group(0_a), Binding(0_a));
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "error: atomic variables in <storage> address space must have read_write "
-              "access mode\n"
-              "note: atomic sub-type of 's' is declared here");
+    EXPECT_EQ(
+        r()->error(),
+        R"(56:78 error: atomic variables in <storage> address space must have read_write access mode
+12:34 note: atomic sub-type of 's' is declared here)");
 }
 
 TEST_F(ResolverAtomicValidationTest, InvalidAccessMode_StructOfStruct) {
@@ -237,16 +226,16 @@
     // var<storage, read> g : Outer;
 
     auto* Inner =
-        Structure("Inner", utils::Vector{Member("m", ty.atomic(Source{{12, 34}}, ty.i32()))});
+        Structure("Inner", utils::Vector{Member(Source{{12, 34}}, "m", ty.atomic(ty.i32()))});
     auto* Outer = Structure("Outer", utils::Vector{Member("m", ty.Of(Inner))});
     GlobalVar(Source{{56, 78}}, "g", ty.Of(Outer), ast::AddressSpace::kStorage, ast::Access::kRead,
               Group(0_a), Binding(0_a));
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "error: atomic variables in <storage> address space must have read_write "
-              "access mode\n"
-              "note: atomic sub-type of 'Outer' is declared here");
+    EXPECT_EQ(
+        r()->error(),
+        R"(56:78 error: atomic variables in <storage> address space must have read_write access mode
+12:34 note: atomic sub-type of 'Outer' is declared here)");
 }
 
 TEST_F(ResolverAtomicValidationTest, InvalidAccessMode_StructOfStructOfArray) {
@@ -261,10 +250,10 @@
               Group(0_a), Binding(0_a));
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "error: atomic variables in <storage> address space must have "
-              "read_write access mode\n"
-              "12:34 note: atomic sub-type of 'Outer' is declared here");
+    EXPECT_EQ(
+        r()->error(),
+        R"(56:78 error: atomic variables in <storage> address space must have read_write access mode
+12:34 note: atomic sub-type of 'Outer' is declared here)");
 }
 
 TEST_F(ResolverAtomicValidationTest, InvalidAccessMode_Complex) {
@@ -282,31 +271,30 @@
     // struct S0 { x: S1; };
     // var<storage, read> g : S0;
 
-    auto* atomic_array =
-        Alias(Source{{12, 34}}, "AtomicArray", ty.atomic(Source{{12, 34}}, ty.i32()));
+    auto* atomic_array = Alias("AtomicArray", ty.atomic(ty.i32()));
     auto* array_i32_4 = ty.array(ty.i32(), 4_u);
     auto* array_atomic_u32_8 = ty.array(ty.atomic(ty.u32()), 8_u);
     auto* array_atomic_i32_4 = ty.array(ty.atomic(ty.i32()), 4_u);
 
     auto* s6 = Structure("S6", utils::Vector{Member("x", array_i32_4)});
-    auto* s5 = Structure("S5", utils::Vector{Member("x", ty.Of(s6)),             //
-                                             Member("y", ty.Of(atomic_array)),   //
-                                             Member("z", array_atomic_u32_8)});  //
-    auto* s4 = Structure("S4", utils::Vector{Member("x", ty.Of(s6)),             //
-                                             Member("y", ty.Of(s5)),             //
-                                             Member("z", array_atomic_i32_4)});  //
+    auto* s5 = Structure("S5", utils::Vector{Member("x", ty.Of(s6)),                              //
+                                             Member(Source{{56, 78}}, "y", ty.Of(atomic_array)),  //
+                                             Member("z", array_atomic_u32_8)});                   //
+    auto* s4 = Structure("S4", utils::Vector{Member("x", ty.Of(s6)),                              //
+                                             Member("y", ty.Of(s5)),                              //
+                                             Member("z", array_atomic_i32_4)});                   //
     auto* s3 = Structure("S3", utils::Vector{Member("x", ty.Of(s4))});
     auto* s2 = Structure("S2", utils::Vector{Member("x", ty.Of(s3))});
     auto* s1 = Structure("S1", utils::Vector{Member("x", ty.Of(s2))});
     auto* s0 = Structure("S0", utils::Vector{Member("x", ty.Of(s1))});
-    GlobalVar(Source{{56, 78}}, "g", ty.Of(s0), ast::AddressSpace::kStorage, ast::Access::kRead,
+    GlobalVar(Source{{12, 34}}, "g", ty.Of(s0), ast::AddressSpace::kStorage, ast::Access::kRead,
               Group(0_a), Binding(0_a));
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(),
-              "error: atomic variables in <storage> address space must have "
-              "read_write access mode\n"
-              "note: atomic sub-type of 'S0' is declared here");
+    EXPECT_EQ(
+        r()->error(),
+        R"(12:34 error: atomic variables in <storage> address space must have read_write access mode
+56:78 note: atomic sub-type of 'S0' is declared here)");
 }
 
 TEST_F(ResolverAtomicValidationTest, Local) {
diff --git a/src/tint/resolver/const_eval.cc b/src/tint/resolver/const_eval.cc
index b7674dc..7195824 100644
--- a/src/tint/resolver/const_eval.cc
+++ b/src/tint/resolver/const_eval.cc
@@ -779,24 +779,22 @@
         }
     } else {
         using T = UnwrapNumber<NumberT>;
-        auto divide_values = [](T lhs, T rhs) {
-            if constexpr (std::is_integral_v<T>) {
-                // For integers, lhs / 0 returns lhs
-                if (rhs == 0) {
-                    return lhs;
-                }
-
-                if constexpr (std::is_signed_v<T>) {
-                    // For signed integers, for lhs / -1, return lhs if lhs is the
-                    // most negative value
-                    if (rhs == -1 && lhs == std::numeric_limits<T>::min()) {
-                        return lhs;
-                    }
-                }
+        auto lhs = a.value;
+        auto rhs = b.value;
+        if (rhs == 0) {
+            // For integers (as for floats), lhs / 0 is an error
+            AddError(OverflowErrorMessage(a, "/", b), source);
+            return utils::Failure;
+        }
+        if constexpr (std::is_signed_v<T>) {
+            // For signed integers, lhs / -1 where lhs is the
+            // most negative value is an error
+            if (rhs == -1 && lhs == std::numeric_limits<T>::min()) {
+                AddError(OverflowErrorMessage(a, "/", b), source);
+                return utils::Failure;
             }
-            return lhs / rhs;
-        };
-        result = divide_values(a.value, b.value);
+        }
+        result = lhs / rhs;
     }
     return result;
 }
diff --git a/src/tint/resolver/const_eval_binary_op_test.cc b/src/tint/resolver/const_eval_binary_op_test.cc
index a35214a..2a13360 100644
--- a/src/tint/resolver/const_eval_binary_op_test.cc
+++ b/src/tint/resolver/const_eval_binary_op_test.cc
@@ -419,36 +419,31 @@
 
 template <typename T>
 std::vector<Case> OpDivIntCases() {
-    std::vector<Case> r = {
-        C(Val(T{0}), Val(T{1}), Val(T{0})),
-        C(Val(T{1}), Val(T{1}), Val(T{1})),
-        C(Val(T{1}), Val(T{1}), Val(T{1})),
-        C(Val(T{2}), Val(T{1}), Val(T{2})),
-        C(Val(T{4}), Val(T{2}), Val(T{2})),
-        C(Val(T::Highest()), Val(T{1}), Val(T::Highest())),
-        C(Val(T::Lowest()), Val(T{1}), Val(T::Lowest())),
-        C(Val(T::Highest()), Val(T::Highest()), Val(T{1})),
-        C(Val(T{0}), Val(T::Highest()), Val(T{0})),
-        C(Val(T{0}), Val(T::Lowest()), Val(T{0})),
-    };
-    ConcatIntoIf<!IsAbstract<T> && IsIntegral<T>>(  //
-        r, std::vector<Case>{
-               // e1, when e2 is zero.
-               C(T{123}, T{0}, T{123}),
-           });
-    ConcatIntoIf<!IsAbstract<T> && IsSignedIntegral<T>>(  //
-        r, std::vector<Case>{
-               // e1, when e1 is the most negative value in T, and e2 is -1.
-               C(T::Smallest(), T{-1}, T::Smallest()),
-           });
-
     auto error_msg = [](auto a, auto b) {
         return "12:34 error: " + OverflowErrorMessage(a, "/", b);
     };
-    ConcatIntoIf<IsAbstract<T>>(  //
+
+    std::vector<Case> r = {
+        C(T{0}, T{1}, T{0}),
+        C(T{1}, T{1}, T{1}),
+        C(T{1}, T{1}, T{1}),
+        C(T{2}, T{1}, T{2}),
+        C(T{4}, T{2}, T{2}),
+        C(T::Highest(), T{1}, T::Highest()),
+        C(T::Lowest(), T{1}, T::Lowest()),
+        C(T::Highest(), T::Highest(), T{1}),
+        C(T{0}, T::Highest(), T{0}),
+
+        // Divide by zero
+        E(T{123}, T{0}, error_msg(T{123}, T{0})),
+        E(T::Highest(), T{0}, error_msg(T::Highest(), T{0})),
+        E(T::Lowest(), T{0}, error_msg(T::Lowest(), T{0})),
+    };
+
+    // Error on most negative divided by -1
+    ConcatIntoIf<IsSignedIntegral<T>>(  //
         r, std::vector<Case>{
-               // Most negative value divided by -1
-               E(AInt::Lowest(), -1_a, error_msg(AInt::Lowest(), -1_a)),
+               E(T::Lowest(), T{-1}, error_msg(T::Lowest(), T{-1})),
            });
     return r;
 }
@@ -459,16 +454,16 @@
         return "12:34 error: " + OverflowErrorMessage(a, "/", b);
     };
     std::vector<Case> r = {
-        C(Val(T{0}), Val(T{1}), Val(T{0})),
-        C(Val(T{1}), Val(T{1}), Val(T{1})),
-        C(Val(T{1}), Val(T{1}), Val(T{1})),
-        C(Val(T{2}), Val(T{1}), Val(T{2})),
-        C(Val(T{4}), Val(T{2}), Val(T{2})),
-        C(Val(T::Highest()), Val(T{1}), Val(T::Highest())),
-        C(Val(T::Lowest()), Val(T{1}), Val(T::Lowest())),
-        C(Val(T::Highest()), Val(T::Highest()), Val(T{1})),
-        C(Val(T{0}), Val(T::Highest()), Val(T{0})),
-        C(Val(T{0}), Val(T::Lowest()), Val(-T{0})),
+        C(T{0}, T{1}, T{0}),
+        C(T{1}, T{1}, T{1}),
+        C(T{1}, T{1}, T{1}),
+        C(T{2}, T{1}, T{2}),
+        C(T{4}, T{2}, T{2}),
+        C(T::Highest(), T{1}, T::Highest()),
+        C(T::Lowest(), T{1}, T::Lowest()),
+        C(T::Highest(), T::Highest(), T{1}),
+        C(T{0}, T::Highest(), T{0}),
+        C(T{0}, T::Lowest(), -T{0}),
 
         // Divide by zero
         E(T{123}, T{0}, error_msg(T{123}, T{0})),
@@ -493,10 +488,10 @@
 template <typename T, bool equals>
 std::vector<Case> OpEqualCases() {
     return {
-        C(Val(T{0}), Val(T{0}), Val(true == equals)),
-        C(Val(T{0}), Val(T{1}), Val(false == equals)),
-        C(Val(T{1}), Val(T{0}), Val(false == equals)),
-        C(Val(T{1}), Val(T{1}), Val(true == equals)),
+        C(T{0}, T{0}, true == equals),
+        C(T{0}, T{1}, false == equals),
+        C(T{1}, T{0}, false == equals),
+        C(T{1}, T{1}, true == equals),
         C(Vec(T{0}, T{0}), Vec(T{0}, T{0}), Vec(true == equals, true == equals)),
         C(Vec(T{1}, T{0}), Vec(T{0}, T{1}), Vec(false == equals, false == equals)),
         C(Vec(T{1}, T{1}), Vec(T{0}, T{1}), Vec(false == equals, true == equals)),
@@ -530,10 +525,10 @@
 template <typename T, bool less_than>
 std::vector<Case> OpLessThanCases() {
     return {
-        C(Val(T{0}), Val(T{0}), Val(false == less_than)),
-        C(Val(T{0}), Val(T{1}), Val(true == less_than)),
-        C(Val(T{1}), Val(T{0}), Val(false == less_than)),
-        C(Val(T{1}), Val(T{1}), Val(false == less_than)),
+        C(T{0}, T{0}, false == less_than),
+        C(T{0}, T{1}, true == less_than),
+        C(T{1}, T{0}, false == less_than),
+        C(T{1}, T{1}, false == less_than),
         C(Vec(T{0}, T{0}), Vec(T{0}, T{0}), Vec(false == less_than, false == less_than)),
         C(Vec(T{0}, T{0}), Vec(T{1}, T{1}), Vec(true == less_than, true == less_than)),
         C(Vec(T{1}, T{1}), Vec(T{0}, T{0}), Vec(false == less_than, false == less_than)),
@@ -566,10 +561,10 @@
 template <typename T, bool greater_than>
 std::vector<Case> OpGreaterThanCases() {
     return {
-        C(Val(T{0}), Val(T{0}), Val(false == greater_than)),
-        C(Val(T{0}), Val(T{1}), Val(false == greater_than)),
-        C(Val(T{1}), Val(T{0}), Val(true == greater_than)),
-        C(Val(T{1}), Val(T{1}), Val(false == greater_than)),
+        C(T{0}, T{0}, false == greater_than),
+        C(T{0}, T{1}, false == greater_than),
+        C(T{1}, T{0}, true == greater_than),
+        C(T{1}, T{1}, false == greater_than),
         C(Vec(T{0}, T{0}), Vec(T{0}, T{0}), Vec(false == greater_than, false == greater_than)),
         C(Vec(T{1}, T{1}), Vec(T{0}, T{0}), Vec(true == greater_than, true == greater_than)),
         C(Vec(T{0}, T{0}), Vec(T{1}, T{1}), Vec(false == greater_than, false == greater_than)),
diff --git a/src/tint/resolver/host_shareable_validation_test.cc b/src/tint/resolver/host_shareable_validation_test.cc
index 97519cd..325595c 100644
--- a/src/tint/resolver/host_shareable_validation_test.cc
+++ b/src/tint/resolver/host_shareable_validation_test.cc
@@ -26,49 +26,52 @@
 using ResolverHostShareableValidationTest = ResolverTest;
 
 TEST_F(ResolverHostShareableValidationTest, BoolMember) {
-    auto* s = Structure("S", utils::Vector{Member(Source{{12, 34}}, "x", ty.bool_())});
+    auto* s =
+        Structure("S", utils::Vector{Member(Source{{56, 78}}, "x", ty.bool_(Source{{12, 34}}))});
 
-    GlobalVar(Source{{56, 78}}, "g", ty.Of(s), ast::AddressSpace::kStorage, ast::Access::kRead,
+    GlobalVar(Source{{90, 12}}, "g", ty.Of(s), ast::AddressSpace::kStorage, ast::Access::kRead,
               Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
     EXPECT_EQ(
         r()->error(),
-        R"(56:78 error: Type 'bool' cannot be used in address space 'storage' as it is non-host-shareable
-12:34 note: while analyzing structure member S.x
-56:78 note: while instantiating 'var' g)");
+        R"(12:34 error: Type 'bool' cannot be used in address space 'storage' as it is non-host-shareable
+56:78 note: while analyzing structure member S.x
+90:12 note: while instantiating 'var' g)");
 }
 
 TEST_F(ResolverHostShareableValidationTest, BoolVectorMember) {
-    auto* s = Structure("S", utils::Vector{Member(Source{{12, 34}}, "x", ty.vec3<bool>())});
+    auto* s = Structure(
+        "S", utils::Vector{Member(Source{{56, 78}}, "x", ty.vec3<bool>(Source{{12, 34}}))});
 
-    GlobalVar(Source{{56, 78}}, "g", ty.Of(s), ast::AddressSpace::kStorage, ast::Access::kRead,
+    GlobalVar(Source{{90, 12}}, "g", ty.Of(s), ast::AddressSpace::kStorage, ast::Access::kRead,
               Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
     EXPECT_EQ(
         r()->error(),
-        R"(56:78 error: Type 'vec3<bool>' cannot be used in address space 'storage' as it is non-host-shareable
-12:34 note: while analyzing structure member S.x
-56:78 note: while instantiating 'var' g)");
+        R"(12:34 error: Type 'vec3<bool>' cannot be used in address space 'storage' as it is non-host-shareable
+56:78 note: while analyzing structure member S.x
+90:12 note: while instantiating 'var' g)");
 }
 
 TEST_F(ResolverHostShareableValidationTest, Aliases) {
-    auto* a1 = Alias("a1", ty.bool_());
-    auto* s = Structure("S", utils::Vector{Member(Source{{12, 34}}, "x", ty.Of(a1))});
+    Alias("a1", ty.bool_());
+    auto* s = Structure(
+        "S", utils::Vector{Member(Source{{56, 78}}, "x", ty.type_name(Source{{12, 34}}, "a1"))});
     auto* a2 = Alias("a2", ty.Of(s));
-    GlobalVar(Source{{56, 78}}, "g", ty.Of(a2), ast::AddressSpace::kStorage, ast::Access::kRead,
+    GlobalVar(Source{{90, 12}}, "g", ty.Of(a2), ast::AddressSpace::kStorage, ast::Access::kRead,
               Binding(0_a), Group(0_a));
 
     ASSERT_FALSE(r()->Resolve());
 
     EXPECT_EQ(
         r()->error(),
-        R"(56:78 error: Type 'bool' cannot be used in address space 'storage' as it is non-host-shareable
-12:34 note: while analyzing structure member S.x
-56:78 note: while instantiating 'var' g)");
+        R"(12:34 error: Type 'bool' cannot be used in address space 'storage' as it is non-host-shareable
+56:78 note: while analyzing structure member S.x
+90:12 note: while instantiating 'var' g)");
 }
 
 TEST_F(ResolverHostShareableValidationTest, NestedStructures) {
@@ -85,7 +88,7 @@
 
     EXPECT_EQ(
         r()->error(),
-        R"(9:10 error: Type 'bool' cannot be used in address space 'storage' as it is non-host-shareable
+        R"(error: Type 'bool' cannot be used in address space 'storage' as it is non-host-shareable
 1:2 note: while analyzing structure member I1.x
 3:4 note: while analyzing structure member I2.y
 5:6 note: while analyzing structure member I3.z
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index 0fdd78c..cf55a1d 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -103,7 +103,11 @@
       const_eval_(*builder),
       intrinsic_table_(IntrinsicTable::Create(*builder)),
       sem_(builder, dependencies_),
-      validator_(builder, sem_) {}
+      validator_(builder,
+                 sem_,
+                 enabled_extensions_,
+                 atomic_composite_info_,
+                 valid_type_storage_layouts_) {}
 
 Resolver::~Resolver() = default;
 
@@ -258,7 +262,18 @@
                 if (access == ast::Access::kUndefined) {
                     access = DefaultAccessForAddressSpace(t->address_space);
                 }
-                return builder_->create<sem::Pointer>(el, t->address_space, access);
+                auto ptr = builder_->create<sem::Pointer>(el, t->address_space, access);
+                if (!ptr) {
+                    return nullptr;
+                }
+                if (!validator_.Pointer(t, ptr)) {
+                    return nullptr;
+                }
+                if (!ApplyAddressSpaceUsageToType(t->address_space, el, t->type->source)) {
+                    AddNote("while instantiating " + builder_->FriendlyName(ptr), t->source);
+                    return nullptr;
+                }
+                return ptr;
             }
             return nullptr;
         },
@@ -621,7 +636,8 @@
 
     auto* var_ty = builder_->create<sem::Reference>(storage_ty, address_space, access);
 
-    if (!ApplyAddressSpaceUsageToType(address_space, var_ty, var->source)) {
+    if (!ApplyAddressSpaceUsageToType(address_space, var_ty,
+                                      var->type ? var->type->source : var->source)) {
         AddNote("while instantiating 'var' " + builder_->Symbols().NameFor(var->symbol),
                 var->source);
         return nullptr;
@@ -723,7 +739,7 @@
         return nullptr;
     }
 
-    if (!ApplyAddressSpaceUsageToType(ast::AddressSpace::kNone, ty, param->source)) {
+    if (!ApplyAddressSpaceUsageToType(ast::AddressSpace::kNone, ty, param->type->source)) {
         add_note();
         return nullptr;
     }
@@ -891,13 +907,7 @@
         return nullptr;
     }
 
-    if (!validator_.GlobalVariable(sem, override_ids_, atomic_composite_info_)) {
-        return nullptr;
-    }
-
-    // TODO(bclayton): Call this at the end of resolve on all uniform and storage
-    // referenced structs
-    if (!validator_.AddressSpaceLayout(sem, enabled_extensions_, valid_type_storage_layouts_)) {
+    if (!validator_.GlobalVariable(sem, override_ids_)) {
         return nullptr;
     }
 
@@ -2300,7 +2310,7 @@
         current_function_->AddDirectCall(call);
     }
 
-    if (!validator_.RequiredExtensionForBuiltinFunction(call, enabled_extensions_)) {
+    if (!validator_.RequiredExtensionForBuiltinFunction(call)) {
         return nullptr;
     }
 
@@ -3564,12 +3574,14 @@
         str->AddUsage(address_space);
 
         for (auto* member : str->Members()) {
-            if (!ApplyAddressSpaceUsageToType(address_space, const_cast<sem::Type*>(member->Type()),
-                                              usage)) {
+            auto decl = member->Declaration();
+            if (decl &&
+                !ApplyAddressSpaceUsageToType(address_space, const_cast<sem::Type*>(member->Type()),
+                                              decl->type->source)) {
                 std::stringstream err;
                 err << "while analyzing structure member " << sem_.TypeNameOf(str) << "."
-                    << builder_->Symbols().NameFor(member->Declaration()->symbol);
-                AddNote(err.str(), member->Declaration()->source);
+                    << builder_->Symbols().NameFor(decl->symbol);
+                AddNote(err.str(), decl->source);
                 return false;
             }
         }
diff --git a/src/tint/resolver/resolver.h b/src/tint/resolver/resolver.h
index 0b31fe1..abc7633 100644
--- a/src/tint/resolver/resolver.h
+++ b/src/tint/resolver/resolver.h
@@ -111,8 +111,6 @@
     const Validator* GetValidatorForTesting() const { return &validator_; }
 
   private:
-    Validator::ValidTypeStorageLayouts valid_type_storage_layouts_;
-
     /// Resolves the program, without creating final the semantic nodes.
     /// @returns true on success, false on error
     bool ResolveInternal();
@@ -471,6 +469,7 @@
     sem::CompoundStatement* current_compound_statement_ = nullptr;
     uint32_t current_scoping_depth_ = 0;
     utils::UniqueVector<const sem::GlobalVariable*, 4>* resolved_overrides_ = nullptr;
+    utils::Hashset<TypeAndAddressSpace, 8> valid_type_storage_layouts_;
 };
 
 }  // namespace tint::resolver
diff --git a/src/tint/resolver/resolver_test_helper.h b/src/tint/resolver/resolver_test_helper.h
index d44c9d8..68a7017 100644
--- a/src/tint/resolver/resolver_test_helper.h
+++ b/src/tint/resolver/resolver_test_helper.h
@@ -608,7 +608,7 @@
     /// @return a new AST alias type
     static inline const ast::Type* AST(ProgramBuilder& b) {
         return b.create<ast::Pointer>(DataType<T>::AST(b), ast::AddressSpace::kPrivate,
-                                      ast::Access::kReadWrite);
+                                      ast::Access::kUndefined);
     }
     /// @param b the ProgramBuilder
     /// @return the semantic aliased type
diff --git a/src/tint/resolver/type_validation_test.cc b/src/tint/resolver/type_validation_test.cc
index 59367d0..fe1aa05 100644
--- a/src/tint/resolver/type_validation_test.cc
+++ b/src/tint/resolver/type_validation_test.cc
@@ -321,12 +321,11 @@
 
 TEST_F(ResolverTypeValidationTest, ArraySize_OverElementCountLimit) {
     // var<private> a : array<f32, 65536>;
-    GlobalVar(Source{{1, 2}}, "a",
-              ty.array(Source{{12, 34}}, ty.f32(), Expr(Source{{12, 34}}, 65536_a)),
+    GlobalVar(Source{{56, 78}}, "a", ty.array(Source{{12, 34}}, ty.f32(), Expr(65536_a)),
               ast::AddressSpace::kPrivate);
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), R"(1:2 error: array count (65536) must be less than 65536
-1:2 note: while instantiating 'var' a)");
+    EXPECT_EQ(r()->error(), R"(12:34 error: array count (65536) must be less than 65536
+56:78 note: while instantiating 'var' a)");
 }
 
 TEST_F(ResolverTypeValidationTest, ArraySize_StorageBufferLargeArray) {
@@ -569,7 +568,7 @@
     /// @vertex
     // fn func() { var a : array<i32>; }
 
-    auto* var = Var(Source{{12, 34}}, "a", ty.array<i32>());
+    auto* var = Var(Source{{56, 78}}, "a", ty.array(Source{{12, 34}}, ty.i32()));
 
     Func("func", utils::Empty, ty.void_(),
          utils::Vector{
@@ -582,7 +581,7 @@
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
               R"(12:34 error: runtime-sized arrays can only be used in the <storage> address space
-12:34 note: while instantiating 'var' a)");
+56:78 note: while instantiating 'var' a)");
 }
 
 TEST_F(ResolverTypeValidationTest, Struct_Member_VectorNoType) {
@@ -731,23 +730,24 @@
 }
 
 TEST_F(ResolverTypeValidationTest, RuntimeArrayAsGlobalVariable) {
-    GlobalVar(Source{{56, 78}}, "g", ty.array<i32>(), ast::AddressSpace::kPrivate);
+    GlobalVar(Source{{56, 78}}, "g", ty.array(Source{{12, 34}}, ty.i32()),
+              ast::AddressSpace::kPrivate);
 
     ASSERT_FALSE(r()->Resolve());
 
     EXPECT_EQ(r()->error(),
-              R"(56:78 error: runtime-sized arrays can only be used in the <storage> address space
+              R"(12:34 error: runtime-sized arrays can only be used in the <storage> address space
 56:78 note: while instantiating 'var' g)");
 }
 
 TEST_F(ResolverTypeValidationTest, RuntimeArrayAsLocalVariable) {
-    auto* v = Var(Source{{56, 78}}, "g", ty.array<i32>());
+    auto* v = Var(Source{{56, 78}}, "g", ty.array(Source{{12, 34}}, ty.i32()));
     WrapInFunction(v);
 
     ASSERT_FALSE(r()->Resolve());
 
     EXPECT_EQ(r()->error(),
-              R"(56:78 error: runtime-sized arrays can only be used in the <storage> address space
+              R"(12:34 error: runtime-sized arrays can only be used in the <storage> address space
 56:78 note: while instantiating 'var' g)");
 }
 
@@ -755,7 +755,7 @@
     // fn func(a : array<u32>) {}
     // @vertex fn main() {}
 
-    auto* param = Param(Source{{12, 34}}, "a", ty.array<i32>());
+    auto* param = Param(Source{{56, 78}}, "a", ty.array(Source{{12, 34}}, ty.i32()));
 
     Func("func", utils::Vector{param}, ty.void_(),
          utils::Vector{
@@ -773,14 +773,14 @@
     EXPECT_FALSE(r()->Resolve()) << r()->error();
     EXPECT_EQ(r()->error(),
               R"(12:34 error: runtime-sized arrays can only be used in the <storage> address space
-12:34 note: while instantiating parameter a)");
+56:78 note: while instantiating parameter a)");
 }
 
-TEST_F(ResolverTypeValidationTest, PtrToRuntimeArrayAsParameter_Fail) {
+TEST_F(ResolverTypeValidationTest, PtrToRuntimeArrayAsPointerParameter_Fail) {
     // fn func(a : ptr<workgroup, array<u32>>) {}
 
-    auto* param =
-        Param(Source{{12, 34}}, "a", ty.pointer(ty.array<i32>(), ast::AddressSpace::kWorkgroup));
+    auto* param = Param("a", ty.pointer(Source{{56, 78}}, ty.array(Source{{12, 34}}, ty.i32()),
+                                        ast::AddressSpace::kWorkgroup));
 
     Func("func", utils::Vector{param}, ty.void_(),
          utils::Vector{
@@ -790,7 +790,23 @@
     EXPECT_FALSE(r()->Resolve()) << r()->error();
     EXPECT_EQ(r()->error(),
               R"(12:34 error: runtime-sized arrays can only be used in the <storage> address space
-12:34 note: while instantiating parameter a)");
+56:78 note: while instantiating ptr<workgroup, array<i32>, read_write>)");
+}
+
+TEST_F(ResolverTypeValidationTest, PtrToRuntimeArrayAsParameter_Fail) {
+    // fn func(a : ptr<workgroup, array<u32>>) {}
+
+    auto* param = Param(Source{{56, 78}}, "a", ty.array(Source{{12, 34}}, ty.i32()));
+
+    Func("func", utils::Vector{param}, ty.void_(),
+         utils::Vector{
+             Return(),
+         });
+
+    EXPECT_FALSE(r()->Resolve()) << r()->error();
+    EXPECT_EQ(r()->error(),
+              R"(12:34 error: runtime-sized arrays can only be used in the <storage> address space
+56:78 note: while instantiating parameter a)");
 }
 
 TEST_F(ResolverTypeValidationTest, AliasRuntimeArrayIsNotLast_Fail) {
diff --git a/src/tint/resolver/uniformity.cc b/src/tint/resolver/uniformity.cc
index ffedaf6..b47be22 100644
--- a/src/tint/resolver/uniformity.cc
+++ b/src/tint/resolver/uniformity.cc
@@ -673,7 +673,7 @@
                 }
 
                 // Set each variable's exit node as its value in the outer scope.
-                for (auto& v : info.var_exit_nodes) {
+                for (auto v : info.var_exit_nodes) {
                     current_function_->variables.Set(v.key, v.value);
                 }
 
@@ -726,7 +726,7 @@
                 cfx->AddEdge(cf);
 
                 // Add edges from variable loop input nodes to their values at the end of the loop.
-                for (auto& v : info.var_in_nodes) {
+                for (auto v : info.var_in_nodes) {
                     auto* in_node = v.value;
                     auto* out_node = current_function_->variables.Get(v.key);
                     if (out_node != in_node) {
diff --git a/src/tint/resolver/validator.cc b/src/tint/resolver/validator.cc
index 70d9f8e..70e1de1 100644
--- a/src/tint/resolver/validator.cc
+++ b/src/tint/resolver/validator.cc
@@ -153,8 +153,18 @@
 
 }  // namespace
 
-Validator::Validator(ProgramBuilder* builder, SemHelper& sem)
-    : symbols_(builder->Symbols()), diagnostics_(builder->Diagnostics()), sem_(sem) {}
+Validator::Validator(
+    ProgramBuilder* builder,
+    SemHelper& sem,
+    const ast::Extensions& enabled_extensions,
+    const utils::Hashmap<const sem::Type*, const Source*, 8>& atomic_composite_info,
+    utils::Hashset<TypeAndAddressSpace, 8>& valid_type_storage_layouts)
+    : symbols_(builder->Symbols()),
+      diagnostics_(builder->Diagnostics()),
+      sem_(sem),
+      enabled_extensions_(enabled_extensions),
+      atomic_composite_info_(atomic_composite_info),
+      valid_type_storage_layouts_(valid_type_storage_layouts) {}
 
 Validator::~Validator() = default;
 
@@ -259,6 +269,28 @@
     return true;
 }
 
+bool Validator::Pointer(const ast::Pointer* a, const sem::Pointer* s) const {
+    if (s->AddressSpace() == ast::AddressSpace::kUndefined) {
+        AddError("ptr missing address space", a->source);
+        return false;
+    }
+
+    if (a->access != ast::Access::kUndefined) {
+        // https://www.w3.org/TR/WGSL/#access-mode-defaults
+        // When writing a variable declaration or a pointer type in WGSL source:
+        // * For the storage address space, the access mode is optional, and defaults to read.
+        // * For other address spaces, the access mode must not be written.
+        if (a->address_space != ast::AddressSpace::kStorage) {
+            AddError("only pointers in <storage> address space may declare an access mode",
+                     a->source);
+            return false;
+        }
+    }
+
+    return CheckTypeAccessAddressSpace(s->StoreType(), s->Access(), s->AddressSpace(), utils::Empty,
+                                       a->source);
+}
+
 bool Validator::StorageTexture(const ast::StorageTexture* t) const {
     switch (t->access) {
         case ast::Access::kWrite:
@@ -345,11 +377,10 @@
             default:
                 // https://gpuweb.github.io/gpuweb/wgsl/#var-and-let
                 // Optionally has an initializer expression, if the variable is in the private or
-                // function address spacees.
+                // function address spaces.
                 AddError("var of address space '" + utils::ToString(address_space) +
-                             "' cannot have an initializer. var initializers are only "
-                             "supported for the address spacees "
-                             "'private' and 'function'",
+                             "' cannot have an initializer. var initializers are only supported "
+                             "for the address spaces 'private' and 'function'",
                          v->source);
                 return false;
         }
@@ -360,8 +391,7 @@
 
 bool Validator::AddressSpaceLayout(const sem::Type* store_ty,
                                    ast::AddressSpace address_space,
-                                   Source source,
-                                   ValidTypeStorageLayouts& layouts) const {
+                                   Source source) const {
     // https://gpuweb.github.io/gpuweb/wgsl/#storage-class-layout-constraints
 
     auto is_uniform_struct_or_array = [address_space](const sem::Type* ty) {
@@ -386,8 +416,8 @@
         return symbols_.NameFor(sm->Declaration()->symbol);
     };
 
-    // Cache result of type + address space pair.
-    if (!layouts.emplace(store_ty, address_space).second) {
+    // Only validate the [type + address space] once
+    if (!valid_type_storage_layouts_.Add(TypeAndAddressSpace{store_ty, address_space})) {
         return true;
     }
 
@@ -395,6 +425,12 @@
         return true;
     }
 
+    auto note_usage = [&] {
+        AddNote("'" + store_ty->FriendlyName(symbols_) + "' used in address space '" +
+                    utils::ToString(address_space) + "' here",
+                source);
+    };
+
     // Among three host-shareable address spaces, f16 is supported in "uniform" and
     // "storage" address space, but not "push_constant" address space yet.
     if (Is<sem::F16>(sem::Type::DeepestElementOf(store_ty)) &&
@@ -409,10 +445,10 @@
             uint32_t required_align = required_alignment_of(m->Type());
 
             // Recurse into the member type.
-            if (!AddressSpaceLayout(m->Type(), address_space, m->Declaration()->type->source,
-                                    layouts)) {
+            if (!AddressSpaceLayout(m->Type(), address_space, m->Declaration()->type->source)) {
                 AddNote("see layout of struct:\n" + str->Layout(symbols_),
                         str->Declaration()->source);
+                note_usage();
                 return false;
             }
 
@@ -435,6 +471,7 @@
                             member_str->Declaration()->source);
                 }
 
+                note_usage();
                 return false;
             }
 
@@ -460,6 +497,7 @@
                     AddNote("and layout of previous member struct:\n" +
                                 prev_member_str->Layout(symbols_),
                             prev_member_str->Declaration()->source);
+                    note_usage();
                     return false;
                 }
             }
@@ -472,7 +510,7 @@
         // TODO(crbug.com/tint/1388): Ideally we'd pass the source for nested element type here, but
         // we can't easily get that from the semantic node. We should consider recursing through the
         // AST type nodes instead.
-        if (!AddressSpaceLayout(arr->ElemType(), address_space, source, layouts)) {
+        if (!AddressSpaceLayout(arr->ElemType(), address_space, source)) {
             return false;
         }
 
@@ -484,21 +522,16 @@
                 // shader author can resolve the issue.
                 std::string hint;
                 if (arr->ElemType()->is_scalar()) {
-                    hint =
-                        "Consider using a vector or struct as the element type "
-                        "instead.";
+                    hint = "Consider using a vector or struct as the element type instead.";
                 } else if (auto* vec = arr->ElemType()->As<sem::Vector>();
                            vec && vec->type()->Size() == 4) {
                     hint = "Consider using a vec4 instead.";
                 } else if (arr->ElemType()->Is<sem::Struct>()) {
-                    hint =
-                        "Consider using the @size attribute on the last struct "
-                        "member.";
+                    hint = "Consider using the @size attribute on the last struct member.";
                 } else {
                     hint =
-                        "Consider wrapping the element type in a struct and using "
-                        "the "
-                        "@size attribute.";
+                        "Consider wrapping the element type in a struct and using the @size "
+                        "attribute.";
                 }
                 AddError(
                     "uniform storage requires that array elements be aligned to 16 "
@@ -513,42 +546,6 @@
     return true;
 }
 
-bool Validator::AddressSpaceLayout(const sem::Variable* var,
-                                   const ast::Extensions& enabled_extensions,
-                                   ValidTypeStorageLayouts& layouts) const {
-    if (var->AddressSpace() == ast::AddressSpace::kPushConstant &&
-        !enabled_extensions.Contains(ast::Extension::kChromiumExperimentalPushConstant) &&
-        IsValidationEnabled(var->Declaration()->attributes,
-                            ast::DisabledValidation::kIgnoreAddressSpace)) {
-        AddError(
-            "use of variable address space 'push_constant' requires enabling extension "
-            "'chromium_experimental_push_constant'",
-            var->Declaration()->source);
-        return false;
-    }
-
-    if (auto* str = var->Type()->UnwrapRef()->As<sem::Struct>()) {
-        // Check the structure has a declaration. Builtins like modf() and frexp() return untypeable
-        // structures, and so they have no declaration. Just skip validation for these.
-        if (auto* str_decl = str->Declaration()) {
-            if (!AddressSpaceLayout(str, var->AddressSpace(), str_decl->source, layouts)) {
-                AddNote("see declaration of variable", var->Declaration()->source);
-                return false;
-            }
-        }
-    } else {
-        Source source = var->Declaration()->source;
-        if (var->Declaration()->type) {
-            source = var->Declaration()->type->source;
-        }
-        if (!AddressSpaceLayout(var->Type()->UnwrapRef(), var->AddressSpace(), source, layouts)) {
-            return false;
-        }
-    }
-
-    return true;
-}
-
 bool Validator::LocalVariable(const sem::Variable* local) const {
     auto* decl = local->Declaration();
     if (IsArrayWithOverrideCount(local->Type())) {
@@ -581,8 +578,7 @@
 
 bool Validator::GlobalVariable(
     const sem::GlobalVariable* global,
-    const utils::Hashmap<OverrideId, const sem::Variable*, 8>& override_ids,
-    const utils::Hashmap<const sem::Type*, const Source*, 8>& atomic_composite_info) const {
+    const utils::Hashmap<OverrideId, const sem::Variable*, 8>& override_ids) const {
     auto* decl = global->Declaration();
     if (global->AddressSpace() != ast::AddressSpace::kWorkgroup &&
         IsArrayWithOverrideCount(global->Type())) {
@@ -592,7 +588,7 @@
     }
     bool ok = Switch(
         decl,  //
-        [&](const ast::Var* var) {
+        [&](const ast::Var*) {
             if (auto* init = global->Initializer();
                 init && init->Stage() > sem::EvaluationStage::kOverride) {
                 AddError("module-scope 'var' initializer must be a constant or override-expression",
@@ -620,29 +616,6 @@
                 }
             }
 
-            // https://gpuweb.github.io/gpuweb/wgsl/#variable-declaration
-            // The access mode always has a default, and except for variables in the storage address
-            // space, must not be written.
-            if (var->declared_access != ast::Access::kUndefined) {
-                if (global->AddressSpace() == ast::AddressSpace::kStorage) {
-                    // The access mode for the storage address space can only be 'read' or
-                    // 'read_write'.
-                    if (var->declared_access == ast::Access::kWrite) {
-                        AddError("access mode 'write' is not valid for the 'storage' address space",
-                                 decl->source);
-                        return false;
-                    }
-                } else {
-                    AddError("only variables in <storage> address space may declare an access mode",
-                             decl->source);
-                    return false;
-                }
-            }
-
-            if (!AtomicVariable(global, atomic_composite_info)) {
-                return false;
-            }
-
             return Var(global);
         },
         [&](const ast::Override*) { return Override(global, override_ids); },
@@ -698,65 +671,41 @@
     return true;
 }
 
-// https://gpuweb.github.io/gpuweb/wgsl/#atomic-types
-// Atomic types may only be instantiated by variables in the workgroup storage class or by storage
-// buffer variables with a read_write access mode.
-bool Validator::AtomicVariable(
-    const sem::Variable* var,
-    const utils::Hashmap<const sem::Type*, const Source*, 8>& atomic_composite_info) const {
-    auto address_space = var->AddressSpace();
-    auto* decl = var->Declaration();
-    auto access = var->Access();
-    auto* type = var->Type()->UnwrapRef();
-    auto source = decl->type ? decl->type->source : decl->source;
-
-    if (type->Is<sem::Atomic>()) {
-        if (address_space != ast::AddressSpace::kWorkgroup &&
-            address_space != ast::AddressSpace::kStorage) {
-            AddError("atomic variables must have <storage> or <workgroup> address space", source);
-            return false;
-        }
-    } else if (type->IsAnyOf<sem::Struct, sem::Array>()) {
-        if (auto found = atomic_composite_info.Find(type)) {
-            if (address_space != ast::AddressSpace::kStorage &&
-                address_space != ast::AddressSpace::kWorkgroup) {
-                AddError("atomic variables must have <storage> or <workgroup> address space",
-                         source);
-                AddNote("atomic sub-type of '" + sem_.TypeNameOf(type) + "' is declared here",
-                        **found);
-                return false;
-            } else if (address_space == ast::AddressSpace::kStorage &&
-                       access != ast::Access::kReadWrite) {
-                AddError(
-                    "atomic variables in <storage> address space must have read_write "
-                    "access mode",
-                    source);
-                AddNote("atomic sub-type of '" + sem_.TypeNameOf(type) + "' is declared here",
-                        **found);
-                return false;
-            }
-        }
-    }
-
-    return true;
-}
-
 bool Validator::Var(const sem::Variable* v) const {
     auto* var = v->Declaration()->As<ast::Var>();
-    auto* storage_ty = v->Type()->UnwrapRef();
+    auto* store_ty = v->Type()->UnwrapRef();
 
-    if (!IsStorable(storage_ty)) {
-        AddError(sem_.TypeNameOf(storage_ty) + " cannot be used as the type of a var", var->source);
+    if (!IsStorable(store_ty)) {
+        AddError(sem_.TypeNameOf(store_ty) + " cannot be used as the type of a var", var->source);
         return false;
     }
 
-    if (storage_ty->is_handle() && var->declared_address_space != ast::AddressSpace::kNone) {
-        // https://gpuweb.github.io/gpuweb/wgsl/#module-scope-variables
-        // If the store type is a texture type or a sampler type, then the variable declaration must
-        // not have a address space attribute. The address space will always be handle.
-        AddError(
-            "variables of type '" + sem_.TypeNameOf(storage_ty) + "' must not have a address space",
-            var->source);
+    if (store_ty->is_handle()) {
+        if (var->declared_address_space != ast::AddressSpace::kNone) {
+            // https://gpuweb.github.io/gpuweb/wgsl/#module-scope-variables
+            // If the store type is a texture type or a sampler type, then the variable declaration
+            // must not have a address space attribute. The address space will always be handle.
+            AddError("variables of type '" + sem_.TypeNameOf(store_ty) +
+                         "' must not have a address space",
+                     var->source);
+            return false;
+        }
+    }
+
+    if (var->declared_access != ast::Access::kUndefined) {
+        // https://www.w3.org/TR/WGSL/#access-mode-defaults
+        // When writing a variable declaration or a pointer type in WGSL source:
+        // * For the storage address space, the access mode is optional, and defaults to read.
+        // * For other address spaces, the access mode must not be written.
+        if (var->declared_address_space != ast::AddressSpace::kStorage) {
+            AddError("only variables in <storage> address space may declare an access mode",
+                     var->source);
+            return false;
+        }
+    }
+
+    if (!CheckTypeAccessAddressSpace(v->Type()->UnwrapRef(), v->Access(), v->AddressSpace(),
+                                     var->attributes, var->source)) {
         return false;
     }
 
@@ -1642,9 +1591,7 @@
            check_arg_is_constexpr(sem::ParameterUsage::kComponent, 0, 3);
 }
 
-bool Validator::RequiredExtensionForBuiltinFunction(
-    const sem::Call* call,
-    const ast::Extensions& enabled_extensions) const {
+bool Validator::RequiredExtensionForBuiltinFunction(const sem::Call* call) const {
     const auto* builtin = call->Target()->As<sem::Builtin>();
     if (!builtin) {
         return true;
@@ -1655,7 +1602,7 @@
         return true;
     }
 
-    if (!enabled_extensions.Contains(extension)) {
+    if (!enabled_extensions_.Contains(extension)) {
         AddError("cannot call built-in function '" + std::string(builtin->str()) +
                      "' without extension " + utils::ToString(extension),
                  call->Declaration()->source);
@@ -2451,4 +2398,69 @@
     return vec_type.FriendlyName(symbols_);
 }
 
+bool Validator::CheckTypeAccessAddressSpace(
+    const sem::Type* store_ty,
+    ast::Access access,
+    ast::AddressSpace address_space,
+    const utils::VectorRef<const tint::ast::Attribute*> attributes,
+    const Source& source) const {
+    if (!AddressSpaceLayout(store_ty, address_space, source)) {
+        return false;
+    }
+
+    if (address_space == ast::AddressSpace::kPushConstant &&
+        !enabled_extensions_.Contains(ast::Extension::kChromiumExperimentalPushConstant) &&
+        IsValidationEnabled(attributes, ast::DisabledValidation::kIgnoreAddressSpace)) {
+        AddError(
+            "use of variable address space 'push_constant' requires enabling extension "
+            "'chromium_experimental_push_constant'",
+            source);
+        return false;
+    }
+
+    if (address_space == ast::AddressSpace::kStorage && access == ast::Access::kWrite) {
+        // The access mode for the storage address space can only be 'read' or
+        // 'read_write'.
+        AddError("access mode 'write' is not valid for the 'storage' address space", source);
+        return false;
+    }
+
+    auto atomic_error = [&]() -> const char* {
+        if (address_space != ast::AddressSpace::kStorage &&
+            address_space != ast::AddressSpace::kWorkgroup) {
+            return "atomic variables must have <storage> or <workgroup> address space";
+        }
+        if (address_space == ast::AddressSpace::kStorage && access != ast::Access::kReadWrite) {
+            return "atomic variables in <storage> address space must have read_write access "
+                   "mode";
+        }
+        return nullptr;
+    };
+
+    auto check_sub_atomics = [&] {
+        if (auto atomic_use = atomic_composite_info_.Get(store_ty)) {
+            if (auto* err = atomic_error()) {
+                AddError(err, source);
+                AddNote("atomic sub-type of '" + sem_.TypeNameOf(store_ty) + "' is declared here",
+                        **atomic_use);
+                return false;
+            }
+        }
+        return true;
+    };
+
+    return Switch(
+        store_ty,  //
+        [&](const sem::Atomic*) {
+            if (auto* err = atomic_error()) {
+                AddError(err, source);
+                return false;
+            }
+            return true;
+        },
+        [&](const sem::Struct*) { return check_sub_atomics(); },  //
+        [&](const sem::Array*) { return check_sub_atomics(); },   //
+        [&](Default) { return true; });
+}
+
 }  // namespace tint::resolver
diff --git a/src/tint/resolver/validator.h b/src/tint/resolver/validator.h
index 500d056..0f183bb 100644
--- a/src/tint/resolver/validator.h
+++ b/src/tint/resolver/validator.h
@@ -24,6 +24,7 @@
 #include "src/tint/resolver/sem_helper.h"
 #include "src/tint/sem/evaluation_stage.h"
 #include "src/tint/source.h"
+#include "src/tint/utils/hash.h"
 #include "src/tint/utils/hashmap.h"
 #include "src/tint/utils/vector.h"
 
@@ -66,19 +67,38 @@
 
 namespace tint::resolver {
 
+/// TypeAndAddressSpace is a pair of type and address space
+struct TypeAndAddressSpace {
+    /// The type
+    const sem::Type* type;
+    /// The address space
+    ast::AddressSpace address_space;
+
+    /// Equality operator
+    /// @param other the other TypeAndAddressSpace to compare this TypeAndAddressSpace to
+    /// @returns true if the type and address space of this TypeAndAddressSpace is equal to @p other
+    bool operator==(const TypeAndAddressSpace& other) const {
+        return type == other.type && address_space == other.address_space;
+    }
+};
+
 /// Validation logic for various ast nodes. The validations in general should
 /// be shallow and depend on the resolver to call on children. The validations
 /// also assume that sem changes have already been made. The validation checks
 /// should not alter the AST or SEM trees.
 class Validator {
   public:
-    /// The valid type storage layouts typedef
-    using ValidTypeStorageLayouts = std::set<std::pair<const sem::Type*, ast::AddressSpace>>;
-
     /// Constructor
     /// @param builder the program builder
     /// @param helper the SEM helper to validate with
-    Validator(ProgramBuilder* builder, SemHelper& helper);
+    /// @param enabled_extensions all the extensions declared in current module
+    /// @param atomic_composite_info atomic composite info of the module
+    /// @param valid_type_storage_layouts a set of validated type layouts by address space
+    Validator(ProgramBuilder* builder,
+              SemHelper& helper,
+              const ast::Extensions& enabled_extensions,
+              const utils::Hashmap<const sem::Type*, const Source*, 8>& atomic_composite_info,
+              utils::Hashset<TypeAndAddressSpace, 8>& valid_type_storage_layouts);
     ~Validator();
 
     /// Adds the given error message to the diagnostics
@@ -143,19 +163,17 @@
                               uint32_t el_size,
                               uint32_t el_align) const;
 
-    /// Validates an atomic
-    /// @param a the atomic ast node to validate
+    /// Validates an atomic type
+    /// @param a the atomic ast node
     /// @param s the atomic sem node
     /// @returns true on success, false otherwise.
     bool Atomic(const ast::Atomic* a, const sem::Atomic* s) const;
 
-    /// Validates an atoic variable
-    /// @param var the variable to validate
-    /// @param atomic_composite_info store atomic information
+    /// Validates a pointer type
+    /// @param a the pointer ast node
+    /// @param s the pointer sem node
     /// @returns true on success, false otherwise.
-    bool AtomicVariable(
-        const sem::Variable* var,
-        const utils::Hashmap<const sem::Type*, const Source*, 8>& atomic_composite_info) const;
+    bool Pointer(const ast::Pointer* a, const sem::Pointer* s) const;
 
     /// Validates an assignment
     /// @param a the assignment statement
@@ -238,12 +256,10 @@
     /// Validates a global variable
     /// @param var the global variable to validate
     /// @param override_id the set of override ids in the module
-    /// @param atomic_composite_info atomic composite info in the module
     /// @returns true on success, false otherwise
     bool GlobalVariable(
         const sem::GlobalVariable* var,
-        const utils::Hashmap<OverrideId, const sem::Variable*, 8>& override_id,
-        const utils::Hashmap<const sem::Type*, const Source*, 8>& atomic_composite_info) const;
+        const utils::Hashmap<OverrideId, const sem::Variable*, 8>& override_id) const;
 
     /// Validates a break-if statement
     /// @param stmt the statement to validate
@@ -423,10 +439,8 @@
 
     /// Validates an optional builtin function and its required extension.
     /// @param call the builtin call to validate
-    /// @param enabled_extensions all the extensions declared in current module
     /// @returns true on success, false otherwise
-    bool RequiredExtensionForBuiltinFunction(const sem::Call* call,
-                                             const ast::Extensions& enabled_extensions) const;
+    bool RequiredExtensionForBuiltinFunction(const sem::Call* call) const;
 
     /// Validates there are no duplicate attributes
     /// @param attributes the list of attributes to validate
@@ -437,21 +451,8 @@
     /// @param type the type to validate
     /// @param sc the address space
     /// @param source the source of the type
-    /// @param layouts previously validated storage layouts
     /// @returns true on success, false otherwise
-    bool AddressSpaceLayout(const sem::Type* type,
-                            ast::AddressSpace sc,
-                            Source source,
-                            ValidTypeStorageLayouts& layouts) const;
-
-    /// Validates a address space layout
-    /// @param var the variable to validate
-    /// @param layouts previously validated storage layouts
-    /// @param enabled_extensions all the extensions declared in current module
-    /// @returns true on success, false otherwise.
-    bool AddressSpaceLayout(const sem::Variable* var,
-                            const ast::Extensions& enabled_extensions,
-                            ValidTypeStorageLayouts& layouts) const;
+    bool AddressSpaceLayout(const sem::Type* type, ast::AddressSpace sc, Source source) const;
 
     /// @returns true if the attribute list contains a
     /// ast::DisableValidationAttribute with the validation mode equal to
@@ -497,11 +498,41 @@
     /// @return pretty string representation
     std::string VectorPretty(uint32_t size, const sem::Type* element_type) const;
 
+    /// Raises an error if combination of @p store_ty, @p access and @p address_space are not valid
+    /// for a `var` or `ptr` declaration.
+    /// @param store_ty the store type of the var or pointer
+    /// @param access the var or pointer access
+    /// @param address_space the var or pointer address space
+    /// @param source the source for the error
+    /// @returns true on success, false if an error was raised.
+    bool CheckTypeAccessAddressSpace(const sem::Type* store_ty,
+                                     ast::Access access,
+                                     ast::AddressSpace address_space,
+                                     const utils::VectorRef<const tint::ast::Attribute*> attributes,
+                                     const Source& source) const;
     SymbolTable& symbols_;
     diag::List& diagnostics_;
     SemHelper& sem_;
+    const ast::Extensions& enabled_extensions_;
+    const utils::Hashmap<const sem::Type*, const Source*, 8>& atomic_composite_info_;
+    utils::Hashset<TypeAndAddressSpace, 8>& valid_type_storage_layouts_;
 };
 
 }  // namespace tint::resolver
 
+namespace std {
+
+/// Custom std::hash specialization for tint::resolver::TypeAndAddressSpace.
+template <>
+class hash<tint::resolver::TypeAndAddressSpace> {
+  public:
+    /// @param tas the TypeAndAddressSpace
+    /// @return the hash value
+    inline std::size_t operator()(const tint::resolver::TypeAndAddressSpace& tas) const {
+        return tint::utils::Hash(tas.type, tas.address_space);
+    }
+};
+
+}  // namespace std
+
 #endif  // SRC_TINT_RESOLVER_VALIDATOR_H_
diff --git a/src/tint/resolver/variable_validation_test.cc b/src/tint/resolver/variable_validation_test.cc
index 964c8d9..cf35e4c 100644
--- a/src/tint/resolver/variable_validation_test.cc
+++ b/src/tint/resolver/variable_validation_test.cc
@@ -321,16 +321,16 @@
 
 TEST_F(ResolverVariableValidationTest, NonConstructibleType_RuntimeArray) {
     auto* s = Structure("S", utils::Vector{
-                                 Member(Source{{56, 78}}, "m", ty.array(ty.i32())),
+                                 Member(Source{{12, 34}}, "m", ty.array<i32>()),
                              });
-    auto* v = Var(Source{{12, 34}}, "v", ty.Of(s));
+    auto* v = Var(Source{{56, 78}}, "v", ty.Of(s));
     WrapInFunction(v);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(),
-              R"(12:34 error: runtime-sized arrays can only be used in the <storage> address space
-56:78 note: while analyzing structure member S.m
-12:34 note: while instantiating 'var' v)");
+              R"(error: runtime-sized arrays can only be used in the <storage> address space
+12:34 note: while analyzing structure member S.m
+56:78 note: while instantiating 'var' v)");
 }
 
 TEST_F(ResolverVariableValidationTest, NonConstructibleType_Struct_WithAtomic) {
@@ -365,7 +365,7 @@
     EXPECT_EQ(r()->error(),
               "12:34 error: var of address space 'workgroup' cannot have "
               "an initializer. var initializers are only supported for the "
-              "address spacees 'private' and 'function'");
+              "address spaces 'private' and 'function'");
 }
 
 TEST_F(ResolverVariableValidationTest, VectorConstNoType) {
@@ -486,5 +486,18 @@
 56:78 note: consider changing 'const' to 'let')");
 }
 
+TEST_F(ResolverVariableValidationTest, GlobalVariable_PushConstantWithInitializer) {
+    // enable chromium_experimental_push_constant;
+    // var<push_constant> a : u32 = 0u;
+    Enable(ast::Extension::kChromiumExperimentalPushConstant);
+    GlobalVar(Source{{1u, 2u}}, "a", ty.u32(), ast::AddressSpace::kPushConstant,
+              Expr(Source{{3u, 4u}}, u32(0)));
+
+    ASSERT_FALSE(r()->Resolve());
+    EXPECT_EQ(
+        r()->error(),
+        R"(1:2 error: var of address space 'push_constant' cannot have an initializer. var initializers are only supported for the address spaces 'private' and 'function')");
+}
+
 }  // namespace
 }  // namespace tint::resolver
diff --git a/src/tint/symbol.cc b/src/tint/symbol.cc
index 9adc1aa..32177f6 100644
--- a/src/tint/symbol.cc
+++ b/src/tint/symbol.cc
@@ -42,6 +42,11 @@
     return val_ == other.val_;
 }
 
+bool Symbol::operator!=(const Symbol& other) const {
+    TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Symbol, program_id_, other.program_id_);
+    return val_ != other.val_;
+}
+
 bool Symbol::operator<(const Symbol& other) const {
     TINT_ASSERT_PROGRAM_IDS_EQUAL_IF_VALID(Symbol, program_id_, other.program_id_);
     return val_ < other.val_;
diff --git a/src/tint/symbol.h b/src/tint/symbol.h
index fc0a0df..c093673 100644
--- a/src/tint/symbol.h
+++ b/src/tint/symbol.h
@@ -63,11 +63,16 @@
     /// @returns teh symbol after doing the move
     Symbol& operator=(Symbol&& o);
 
-    /// Comparison operator
+    /// Equality operator
     /// @param o the other symbol
     /// @returns true if the symbols are the same
     bool operator==(const Symbol& o) const;
 
+    /// Inequality operator
+    /// @param o the other symbol
+    /// @returns true if the symbols are the different
+    bool operator!=(const Symbol& o) const;
+
     /// Less-than operator
     /// @param o the other symbol
     /// @returns true if this symbol is ordered before symbol `o`
diff --git a/src/tint/symbol_test.cc b/src/tint/symbol_test.cc
index 22135b5..db8e321 100644
--- a/src/tint/symbol_test.cc
+++ b/src/tint/symbol_test.cc
@@ -43,8 +43,11 @@
     Symbol sym3(1, program_id);
 
     EXPECT_TRUE(sym1 == sym3);
+    EXPECT_FALSE(sym1 != sym3);
     EXPECT_FALSE(sym1 == sym2);
+    EXPECT_TRUE(sym1 != sym2);
     EXPECT_FALSE(sym3 == sym2);
+    EXPECT_TRUE(sym3 != sym2);
 }
 
 }  // namespace
diff --git a/src/tint/transform/decompose_memory_access.cc b/src/tint/transform/decompose_memory_access.cc
index 3be550c..944797a 100644
--- a/src/tint/transform/decompose_memory_access.cc
+++ b/src/tint/transform/decompose_memory_access.cc
@@ -466,6 +466,9 @@
                     const sem::VariableUser* var_user) {
         auto address_space = var_user->Variable()->AddressSpace();
         auto access = var_user->Variable()->Access();
+        if (address_space != ast::AddressSpace::kStorage) {
+            access = ast::Access::kUndefined;
+        }
         return utils::GetOrCreate(
             load_funcs, LoadStoreKey{address_space, access, buf_ty, el_ty}, [&] {
                 utils::Vector params{
@@ -562,6 +565,9 @@
                      const sem::VariableUser* var_user) {
         auto address_space = var_user->Variable()->AddressSpace();
         auto access = var_user->Variable()->Access();
+        if (address_space != ast::AddressSpace::kStorage) {
+            access = ast::Access::kUndefined;
+        }
         return utils::GetOrCreate(
             store_funcs, LoadStoreKey{address_space, access, buf_ty, el_ty}, [&] {
                 utils::Vector params{
@@ -670,7 +676,11 @@
                       const sem::Builtin* intrinsic,
                       const sem::VariableUser* var_user) {
         auto op = intrinsic->Type();
+        auto address_space = var_user->Variable()->AddressSpace();
         auto access = var_user->Variable()->Access();
+        if (address_space != ast::AddressSpace::kStorage) {
+            access = ast::Access::kUndefined;
+        }
         return utils::GetOrCreate(atomic_funcs, AtomicKey{access, buf_ty, el_ty, op}, [&] {
             // The first parameter to all WGSL atomics is the expression to the
             // atomic. This is replaced with two parameters: the buffer and offset.
diff --git a/src/tint/transform/decompose_memory_access_test.cc b/src/tint/transform/decompose_memory_access_test.cc
index ac798e0..4c2cd11 100644
--- a/src/tint/transform/decompose_memory_access_test.cc
+++ b/src/tint/transform/decompose_memory_access_test.cc
@@ -804,126 +804,126 @@
 @group(0) @binding(0) var<uniform> ub : UB;
 
 @internal(intrinsic_load_uniform_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> f32
+fn tint_symbol(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> f32
 
 @internal(intrinsic_load_uniform_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_1(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> i32
+fn tint_symbol_1(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> i32
 
 @internal(intrinsic_load_uniform_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_2(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> u32
+fn tint_symbol_2(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> u32
 
 @internal(intrinsic_load_uniform_f16) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_3(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> f16
+fn tint_symbol_3(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> f16
 
 @internal(intrinsic_load_uniform_vec2_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_4(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec2<f32>
+fn tint_symbol_4(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec2<f32>
 
 @internal(intrinsic_load_uniform_vec2_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_5(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec2<i32>
+fn tint_symbol_5(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec2<i32>
 
 @internal(intrinsic_load_uniform_vec2_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_6(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec2<u32>
+fn tint_symbol_6(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec2<u32>
 
 @internal(intrinsic_load_uniform_vec2_f16) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_7(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec2<f16>
+fn tint_symbol_7(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec2<f16>
 
 @internal(intrinsic_load_uniform_vec3_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_8(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec3<f32>
+fn tint_symbol_8(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec3<f32>
 
 @internal(intrinsic_load_uniform_vec3_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_9(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec3<i32>
+fn tint_symbol_9(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec3<i32>
 
 @internal(intrinsic_load_uniform_vec3_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_10(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec3<u32>
+fn tint_symbol_10(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec3<u32>
 
 @internal(intrinsic_load_uniform_vec3_f16) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_11(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec3<f16>
+fn tint_symbol_11(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec3<f16>
 
 @internal(intrinsic_load_uniform_vec4_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_12(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec4<f32>
+fn tint_symbol_12(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec4<f32>
 
 @internal(intrinsic_load_uniform_vec4_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_13(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec4<i32>
+fn tint_symbol_13(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec4<i32>
 
 @internal(intrinsic_load_uniform_vec4_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_14(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec4<u32>
+fn tint_symbol_14(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec4<u32>
 
 @internal(intrinsic_load_uniform_vec4_f16) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_15(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec4<f16>
+fn tint_symbol_15(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec4<f16>
 
-fn tint_symbol_16(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat2x2<f32> {
+fn tint_symbol_16(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat2x2<f32> {
   return mat2x2<f32>(tint_symbol_4(buffer, (offset + 0u)), tint_symbol_4(buffer, (offset + 8u)));
 }
 
-fn tint_symbol_17(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat2x3<f32> {
+fn tint_symbol_17(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat2x3<f32> {
   return mat2x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)));
 }
 
-fn tint_symbol_18(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat2x4<f32> {
+fn tint_symbol_18(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat2x4<f32> {
   return mat2x4<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 16u)));
 }
 
-fn tint_symbol_19(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat3x2<f32> {
+fn tint_symbol_19(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat3x2<f32> {
   return mat3x2<f32>(tint_symbol_4(buffer, (offset + 0u)), tint_symbol_4(buffer, (offset + 8u)), tint_symbol_4(buffer, (offset + 16u)));
 }
 
-fn tint_symbol_20(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat3x3<f32> {
+fn tint_symbol_20(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat3x3<f32> {
   return mat3x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)), tint_symbol_8(buffer, (offset + 32u)));
 }
 
-fn tint_symbol_21(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat3x4<f32> {
+fn tint_symbol_21(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat3x4<f32> {
   return mat3x4<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 16u)), tint_symbol_12(buffer, (offset + 32u)));
 }
 
-fn tint_symbol_22(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat4x2<f32> {
+fn tint_symbol_22(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat4x2<f32> {
   return mat4x2<f32>(tint_symbol_4(buffer, (offset + 0u)), tint_symbol_4(buffer, (offset + 8u)), tint_symbol_4(buffer, (offset + 16u)), tint_symbol_4(buffer, (offset + 24u)));
 }
 
-fn tint_symbol_23(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat4x3<f32> {
+fn tint_symbol_23(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat4x3<f32> {
   return mat4x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)), tint_symbol_8(buffer, (offset + 32u)), tint_symbol_8(buffer, (offset + 48u)));
 }
 
-fn tint_symbol_24(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat4x4<f32> {
+fn tint_symbol_24(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat4x4<f32> {
   return mat4x4<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 16u)), tint_symbol_12(buffer, (offset + 32u)), tint_symbol_12(buffer, (offset + 48u)));
 }
 
-fn tint_symbol_25(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat2x2<f16> {
+fn tint_symbol_25(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat2x2<f16> {
   return mat2x2<f16>(tint_symbol_7(buffer, (offset + 0u)), tint_symbol_7(buffer, (offset + 4u)));
 }
 
-fn tint_symbol_26(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat2x3<f16> {
+fn tint_symbol_26(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat2x3<f16> {
   return mat2x3<f16>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 8u)));
 }
 
-fn tint_symbol_27(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat2x4<f16> {
+fn tint_symbol_27(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat2x4<f16> {
   return mat2x4<f16>(tint_symbol_15(buffer, (offset + 0u)), tint_symbol_15(buffer, (offset + 8u)));
 }
 
-fn tint_symbol_28(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat3x2<f16> {
+fn tint_symbol_28(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat3x2<f16> {
   return mat3x2<f16>(tint_symbol_7(buffer, (offset + 0u)), tint_symbol_7(buffer, (offset + 4u)), tint_symbol_7(buffer, (offset + 8u)));
 }
 
-fn tint_symbol_29(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat3x3<f16> {
+fn tint_symbol_29(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat3x3<f16> {
   return mat3x3<f16>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 8u)), tint_symbol_11(buffer, (offset + 16u)));
 }
 
-fn tint_symbol_30(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat3x4<f16> {
+fn tint_symbol_30(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat3x4<f16> {
   return mat3x4<f16>(tint_symbol_15(buffer, (offset + 0u)), tint_symbol_15(buffer, (offset + 8u)), tint_symbol_15(buffer, (offset + 16u)));
 }
 
-fn tint_symbol_31(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat4x2<f16> {
+fn tint_symbol_31(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat4x2<f16> {
   return mat4x2<f16>(tint_symbol_7(buffer, (offset + 0u)), tint_symbol_7(buffer, (offset + 4u)), tint_symbol_7(buffer, (offset + 8u)), tint_symbol_7(buffer, (offset + 12u)));
 }
 
-fn tint_symbol_32(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat4x3<f16> {
+fn tint_symbol_32(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat4x3<f16> {
   return mat4x3<f16>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 8u)), tint_symbol_11(buffer, (offset + 16u)), tint_symbol_11(buffer, (offset + 24u)));
 }
 
-fn tint_symbol_33(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat4x4<f16> {
+fn tint_symbol_33(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat4x4<f16> {
   return mat4x4<f16>(tint_symbol_15(buffer, (offset + 0u)), tint_symbol_15(buffer, (offset + 8u)), tint_symbol_15(buffer, (offset + 16u)), tint_symbol_15(buffer, (offset + 24u)));
 }
 
-fn tint_symbol_34(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> array<vec3<f32>, 2u> {
+fn tint_symbol_34(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> array<vec3<f32>, 2u> {
   var arr : array<vec3<f32>, 2u>;
   for(var i = 0u; (i < 2u); i = (i + 1u)) {
     arr[i] = tint_symbol_8(buffer, (offset + (i * 16u)));
@@ -931,7 +931,7 @@
   return arr;
 }
 
-fn tint_symbol_35(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> array<mat4x2<f16>, 2u> {
+fn tint_symbol_35(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> array<mat4x2<f16>, 2u> {
   var arr_1 : array<mat4x2<f16>, 2u>;
   for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
     arr_1[i_1] = tint_symbol_31(buffer, (offset + (i_1 * 16u)));
@@ -1075,126 +1075,126 @@
 enable f16;
 
 @internal(intrinsic_load_uniform_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> f32
+fn tint_symbol(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> f32
 
 @internal(intrinsic_load_uniform_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_1(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> i32
+fn tint_symbol_1(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> i32
 
 @internal(intrinsic_load_uniform_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_2(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> u32
+fn tint_symbol_2(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> u32
 
 @internal(intrinsic_load_uniform_f16) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_3(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> f16
+fn tint_symbol_3(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> f16
 
 @internal(intrinsic_load_uniform_vec2_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_4(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec2<f32>
+fn tint_symbol_4(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec2<f32>
 
 @internal(intrinsic_load_uniform_vec2_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_5(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec2<i32>
+fn tint_symbol_5(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec2<i32>
 
 @internal(intrinsic_load_uniform_vec2_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_6(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec2<u32>
+fn tint_symbol_6(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec2<u32>
 
 @internal(intrinsic_load_uniform_vec2_f16) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_7(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec2<f16>
+fn tint_symbol_7(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec2<f16>
 
 @internal(intrinsic_load_uniform_vec3_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_8(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec3<f32>
+fn tint_symbol_8(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec3<f32>
 
 @internal(intrinsic_load_uniform_vec3_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_9(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec3<i32>
+fn tint_symbol_9(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec3<i32>
 
 @internal(intrinsic_load_uniform_vec3_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_10(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec3<u32>
+fn tint_symbol_10(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec3<u32>
 
 @internal(intrinsic_load_uniform_vec3_f16) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_11(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec3<f16>
+fn tint_symbol_11(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec3<f16>
 
 @internal(intrinsic_load_uniform_vec4_f32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_12(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec4<f32>
+fn tint_symbol_12(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec4<f32>
 
 @internal(intrinsic_load_uniform_vec4_i32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_13(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec4<i32>
+fn tint_symbol_13(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec4<i32>
 
 @internal(intrinsic_load_uniform_vec4_u32) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_14(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec4<u32>
+fn tint_symbol_14(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec4<u32>
 
 @internal(intrinsic_load_uniform_vec4_f16) @internal(disable_validation__function_has_no_body)
-fn tint_symbol_15(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> vec4<f16>
+fn tint_symbol_15(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> vec4<f16>
 
-fn tint_symbol_16(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat2x2<f32> {
+fn tint_symbol_16(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat2x2<f32> {
   return mat2x2<f32>(tint_symbol_4(buffer, (offset + 0u)), tint_symbol_4(buffer, (offset + 8u)));
 }
 
-fn tint_symbol_17(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat2x3<f32> {
+fn tint_symbol_17(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat2x3<f32> {
   return mat2x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)));
 }
 
-fn tint_symbol_18(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat2x4<f32> {
+fn tint_symbol_18(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat2x4<f32> {
   return mat2x4<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 16u)));
 }
 
-fn tint_symbol_19(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat3x2<f32> {
+fn tint_symbol_19(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat3x2<f32> {
   return mat3x2<f32>(tint_symbol_4(buffer, (offset + 0u)), tint_symbol_4(buffer, (offset + 8u)), tint_symbol_4(buffer, (offset + 16u)));
 }
 
-fn tint_symbol_20(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat3x3<f32> {
+fn tint_symbol_20(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat3x3<f32> {
   return mat3x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)), tint_symbol_8(buffer, (offset + 32u)));
 }
 
-fn tint_symbol_21(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat3x4<f32> {
+fn tint_symbol_21(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat3x4<f32> {
   return mat3x4<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 16u)), tint_symbol_12(buffer, (offset + 32u)));
 }
 
-fn tint_symbol_22(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat4x2<f32> {
+fn tint_symbol_22(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat4x2<f32> {
   return mat4x2<f32>(tint_symbol_4(buffer, (offset + 0u)), tint_symbol_4(buffer, (offset + 8u)), tint_symbol_4(buffer, (offset + 16u)), tint_symbol_4(buffer, (offset + 24u)));
 }
 
-fn tint_symbol_23(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat4x3<f32> {
+fn tint_symbol_23(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat4x3<f32> {
   return mat4x3<f32>(tint_symbol_8(buffer, (offset + 0u)), tint_symbol_8(buffer, (offset + 16u)), tint_symbol_8(buffer, (offset + 32u)), tint_symbol_8(buffer, (offset + 48u)));
 }
 
-fn tint_symbol_24(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat4x4<f32> {
+fn tint_symbol_24(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat4x4<f32> {
   return mat4x4<f32>(tint_symbol_12(buffer, (offset + 0u)), tint_symbol_12(buffer, (offset + 16u)), tint_symbol_12(buffer, (offset + 32u)), tint_symbol_12(buffer, (offset + 48u)));
 }
 
-fn tint_symbol_25(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat2x2<f16> {
+fn tint_symbol_25(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat2x2<f16> {
   return mat2x2<f16>(tint_symbol_7(buffer, (offset + 0u)), tint_symbol_7(buffer, (offset + 4u)));
 }
 
-fn tint_symbol_26(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat2x3<f16> {
+fn tint_symbol_26(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat2x3<f16> {
   return mat2x3<f16>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 8u)));
 }
 
-fn tint_symbol_27(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat2x4<f16> {
+fn tint_symbol_27(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat2x4<f16> {
   return mat2x4<f16>(tint_symbol_15(buffer, (offset + 0u)), tint_symbol_15(buffer, (offset + 8u)));
 }
 
-fn tint_symbol_28(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat3x2<f16> {
+fn tint_symbol_28(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat3x2<f16> {
   return mat3x2<f16>(tint_symbol_7(buffer, (offset + 0u)), tint_symbol_7(buffer, (offset + 4u)), tint_symbol_7(buffer, (offset + 8u)));
 }
 
-fn tint_symbol_29(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat3x3<f16> {
+fn tint_symbol_29(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat3x3<f16> {
   return mat3x3<f16>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 8u)), tint_symbol_11(buffer, (offset + 16u)));
 }
 
-fn tint_symbol_30(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat3x4<f16> {
+fn tint_symbol_30(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat3x4<f16> {
   return mat3x4<f16>(tint_symbol_15(buffer, (offset + 0u)), tint_symbol_15(buffer, (offset + 8u)), tint_symbol_15(buffer, (offset + 16u)));
 }
 
-fn tint_symbol_31(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat4x2<f16> {
+fn tint_symbol_31(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat4x2<f16> {
   return mat4x2<f16>(tint_symbol_7(buffer, (offset + 0u)), tint_symbol_7(buffer, (offset + 4u)), tint_symbol_7(buffer, (offset + 8u)), tint_symbol_7(buffer, (offset + 12u)));
 }
 
-fn tint_symbol_32(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat4x3<f16> {
+fn tint_symbol_32(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat4x3<f16> {
   return mat4x3<f16>(tint_symbol_11(buffer, (offset + 0u)), tint_symbol_11(buffer, (offset + 8u)), tint_symbol_11(buffer, (offset + 16u)), tint_symbol_11(buffer, (offset + 24u)));
 }
 
-fn tint_symbol_33(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> mat4x4<f16> {
+fn tint_symbol_33(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> mat4x4<f16> {
   return mat4x4<f16>(tint_symbol_15(buffer, (offset + 0u)), tint_symbol_15(buffer, (offset + 8u)), tint_symbol_15(buffer, (offset + 16u)), tint_symbol_15(buffer, (offset + 24u)));
 }
 
-fn tint_symbol_34(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> array<vec3<f32>, 2u> {
+fn tint_symbol_34(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> array<vec3<f32>, 2u> {
   var arr : array<vec3<f32>, 2u>;
   for(var i = 0u; (i < 2u); i = (i + 1u)) {
     arr[i] = tint_symbol_8(buffer, (offset + (i * 16u)));
@@ -1202,7 +1202,7 @@
   return arr;
 }
 
-fn tint_symbol_35(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB, read>, offset : u32) -> array<mat4x2<f16>, 2u> {
+fn tint_symbol_35(@internal(disable_validation__function_parameter) buffer : ptr<uniform, UB>, offset : u32) -> array<mat4x2<f16>, 2u> {
   var arr_1 : array<mat4x2<f16>, 2u>;
   for(var i_1 = 0u; (i_1 < 2u); i_1 = (i_1 + 1u)) {
     arr_1[i_1] = tint_symbol_31(buffer, (offset + (i_1 * 16u)));
diff --git a/src/tint/transform/module_scope_var_to_entry_point_param.cc b/src/tint/transform/module_scope_var_to_entry_point_param.cc
index 8a69bd3..9c9c42f 100644
--- a/src/tint/transform/module_scope_var_to_entry_point_param.cc
+++ b/src/tint/transform/module_scope_var_to_entry_point_param.cc
@@ -139,7 +139,7 @@
             }
             case ast::AddressSpace::kStorage:
             case ast::AddressSpace::kUniform: {
-                // Variables into the Storage and Uniform address spacees are redeclared as entry
+                // Variables into the Storage and Uniform address spaces are redeclared as entry
                 // point parameters with a pointer type.
                 auto attributes = ctx.Clone(var->Declaration()->attributes);
                 attributes.Push(ctx.dst->Disable(ast::DisabledValidation::kEntryPointParameter));
@@ -192,7 +192,7 @@
                 [[fallthrough]];
             }
             case ast::AddressSpace::kPrivate: {
-                // Variables in the Private and Workgroup address spacees are redeclared at function
+                // Variables in the Private and Workgroup address spaces are redeclared at function
                 // scope. Disable address space validation on this variable.
                 auto* disable_validation =
                     ctx.dst->Disable(ast::DisabledValidation::kIgnoreAddressSpace);
@@ -497,7 +497,7 @@
             }
         }
 
-        // Now remove all module-scope variables with these address spacees.
+        // Now remove all module-scope variables with these address spaces.
         for (auto* var_ast : ctx.src->AST().GlobalVariables()) {
             auto* var_sem = ctx.src->Sem().Get(var_ast);
             if (var_sem->AddressSpace() != ast::AddressSpace::kNone) {
diff --git a/src/tint/utils/hashmap.h b/src/tint/utils/hashmap.h
index 19a5abe..6a5f3c5 100644
--- a/src/tint/utils/hashmap.h
+++ b/src/tint/utils/hashmap.h
@@ -182,6 +182,55 @@
     /// @returns a reference to the entry that is equal to the given value.
     ConstReference Find(const Key& key) const { return ConstReference(*this, key); }
 
+    /// @returns the keys of the map as a vector.
+    /// @note the order of the returned vector is non-deterministic between compilers.
+    template <size_t N2 = N>
+    Vector<Key, N2> Keys() const {
+        Vector<Key, N2> out;
+        out.Reserve(this->Count());
+        for (auto it : *this) {
+            out.Push(it.key);
+        }
+        return out;
+    }
+
+    /// @returns the values of the map as a vector
+    /// @note the order of the returned vector is non-deterministic between compilers.
+    template <size_t N2 = N>
+    Vector<Value, N2> Values() const {
+        Vector<Value, N2> out;
+        out.Reserve(this->Count());
+        for (auto it : *this) {
+            out.Push(it.value);
+        }
+        return out;
+    }
+
+    /// Equality operator
+    /// @param other the other Hashmap to compare this Hashmap to
+    /// @returns true if this Hashmap has the same key and value pairs as @p other
+    template <typename K, typename V, size_t N2>
+    bool operator==(const Hashmap<K, V, N2>& other) const {
+        if (this->Count() != other.Count()) {
+            return false;
+        }
+        for (auto it : *this) {
+            auto other_val = other.Find(it.key);
+            if (!other_val || it.value != *other_val) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /// Inequality operator
+    /// @param other the other Hashmap to compare this Hashmap to
+    /// @returns false if this Hashmap has the same key and value pairs as @p other
+    template <typename K, typename V, size_t N2>
+    bool operator!=(const Hashmap<K, V, N2>& other) const {
+        return !(*this == other);
+    }
+
   private:
     Value* Lookup(const Key& key) {
         if (auto [found, index] = this->IndexOf(key); found) {
@@ -198,6 +247,22 @@
     }
 };
 
+/// Hasher specialization for Hashmap
+template <typename K, typename V, size_t N, typename HASH, typename EQUAL>
+struct Hasher<Hashmap<K, V, N, HASH, EQUAL>> {
+    /// @param map the Hashmap to hash
+    /// @returns a hash of the map
+    size_t operator()(const Hashmap<K, V, N, HASH, EQUAL>& map) const {
+        auto hash = Hash(map.Count());
+        for (auto it : map) {
+            // Use an XOR to ensure that the non-deterministic ordering of the map still produces
+            // the same hash value for the same entries.
+            hash ^= Hash(it.key) * 31 + Hash(it.value);
+        }
+        return hash;
+    }
+};
+
 }  // namespace tint::utils
 
 #endif  // SRC_TINT_UTILS_HASHMAP_H_
diff --git a/src/tint/utils/hashmap_base.h b/src/tint/utils/hashmap_base.h
index ee52dad..cd4ef6a 100644
--- a/src/tint/utils/hashmap_base.h
+++ b/src/tint/utils/hashmap_base.h
@@ -70,6 +70,27 @@
     }
 };
 
+/// KeyValueRef is a pair of references to a key and value.
+/// #key is always a const reference.
+/// #value is always a const reference if @tparam VALUE_IS_CONST is true, otherwise a non-const
+/// reference.
+template <typename KEY, typename VALUE, bool VALUE_IS_CONST>
+struct KeyValueRef {
+    /// The reference to key type
+    using KeyRef = const KEY&;
+    /// The reference to value type
+    using ValueRef = std::conditional_t<VALUE_IS_CONST, const VALUE&, VALUE&>;
+
+    /// The reference to the key
+    KeyRef key;
+
+    /// The reference to the value
+    ValueRef value;
+
+    /// @returns a KeyValue<KEY, VALUE> with the referenced key and value
+    operator KeyValue<KEY, VALUE>() const { return {key, value}; }
+};
+
 /// Writes the KeyValue to the std::ostream.
 /// @param out the std::ostream to write to
 /// @param key_value the KeyValue to write
@@ -97,9 +118,19 @@
     /// The entry type for the map.
     /// This is:
     /// - Key when Value is void (used by Hashset)
-    /// - KeyValue<Key, Value> when Value is void (used by Hashmap)
+    /// - KeyValue<Key, Value> when Value is not void (used by Hashmap)
     using Entry = std::conditional_t<ValueIsVoid, Key, KeyValue<Key, Value>>;
 
+    /// A reference to an entry in the map.
+    /// This is:
+    /// - const Key& when Value is void (used by Hashset)
+    /// - KeyValueRef<Key, Value> when Value is not void (used by Hashmap)
+    template <bool IS_CONST>
+    using EntryRef = std::conditional_t<
+        ValueIsVoid,
+        const Key&,
+        KeyValueRef<Key, std::conditional_t<ValueIsVoid, bool, Value>, IS_CONST>>;
+
     /// STL-friendly alias to Entry. Used by gmock.
     using value_type = Entry;
 
@@ -154,17 +185,25 @@
   public:
     /// Iterator for entries in the map.
     /// Iterators are invalidated if the map is modified.
-    class Iterator {
+    template <bool IS_CONST>
+    class IteratorT {
       public:
         /// @returns the value pointed to by this iterator
-        const Entry* operator->() const { return &current->entry.value(); }
+        EntryRef<IS_CONST> operator->() const { return *this; }
 
         /// @returns a reference to the value at the iterator
-        const Entry& operator*() const { return current->entry.value(); }
+        EntryRef<IS_CONST> operator*() const {
+            auto& ref = current->entry.value();
+            if constexpr (ValueIsVoid) {
+                return ref;
+            } else {
+                return {ref.key, ref.value};
+            }
+        }
 
         /// Increments the iterator
         /// @returns this iterator
-        Iterator& operator++() {
+        IteratorT& operator++() {
             if (current == end) {
                 return *this;
             }
@@ -176,18 +215,20 @@
         /// Equality operator
         /// @param other the other iterator to compare this iterator to
         /// @returns true if this iterator is equal to other
-        bool operator==(const Iterator& other) const { return current == other.current; }
+        bool operator==(const IteratorT& other) const { return current == other.current; }
 
         /// Inequality operator
         /// @param other the other iterator to compare this iterator to
         /// @returns true if this iterator is not equal to other
-        bool operator!=(const Iterator& other) const { return current != other.current; }
+        bool operator!=(const IteratorT& other) const { return current != other.current; }
 
       private:
         /// Friend class
         friend class HashmapBase;
 
-        Iterator(const Slot* c, const Slot* e) : current(c), end(e) { SkipToNextValue(); }
+        using SLOT = std::conditional_t<IS_CONST, const Slot, Slot>;
+
+        IteratorT(SLOT* c, SLOT* e) : current(c), end(e) { SkipToNextValue(); }
 
         /// Moves the iterator forward, stopping at the next slot that is not empty.
         void SkipToNextValue() {
@@ -196,10 +237,16 @@
             }
         }
 
-        const Slot* current;  /// The slot the iterator is pointing to
-        const Slot* end;      /// One past the last slot in the map
+        SLOT* current;  /// The slot the iterator is pointing to
+        SLOT* end;      /// One past the last slot in the map
     };
 
+    /// An immutable key and mutable value iterator
+    using Iterator = IteratorT</*IS_CONST*/ false>;
+
+    /// An immutable key and value iterator
+    using ConstIterator = IteratorT</*IS_CONST*/ true>;
+
     /// Constructor
     HashmapBase() { slots_.Resize(kMinSlots); }
 
@@ -322,11 +369,17 @@
     /// @returns a monotonic counter which is incremented whenever the map is mutated.
     size_t Generation() const { return generation_; }
 
+    /// @returns an immutable iterator to the start of the map.
+    ConstIterator begin() const { return ConstIterator{slots_.begin(), slots_.end()}; }
+
+    /// @returns an immutable iterator to the end of the map.
+    ConstIterator end() const { return ConstIterator{slots_.end(), slots_.end()}; }
+
     /// @returns an iterator to the start of the map.
-    Iterator begin() const { return Iterator{slots_.begin(), slots_.end()}; }
+    Iterator begin() { return Iterator{slots_.begin(), slots_.end()}; }
 
     /// @returns an iterator to the end of the map.
-    Iterator end() const { return Iterator{slots_.end(), slots_.end()}; }
+    Iterator end() { return Iterator{slots_.end(), slots_.end()}; }
 
     /// A debug function for checking that the map is in good health.
     /// Asserts if the map is corrupted.
diff --git a/src/tint/utils/hashmap_test.cc b/src/tint/utils/hashmap_test.cc
index 34a93e6..5e97aef 100644
--- a/src/tint/utils/hashmap_test.cc
+++ b/src/tint/utils/hashmap_test.cc
@@ -167,6 +167,32 @@
                                                    Entry{3, "three"}, Entry{4, "four"}));
 }
 
+TEST(Hashmap, MutableIterator) {
+    using Map = Hashmap<int, std::string, 8>;
+    using Entry = typename Map::Entry;
+    Map map;
+    map.Add(1, "one");
+    map.Add(4, "four");
+    map.Add(3, "three");
+    map.Add(2, "two");
+    for (auto pair : map) {
+        pair.value += "!";
+    }
+    EXPECT_THAT(map, testing::UnorderedElementsAre(Entry{1, "one!"}, Entry{2, "two!"},
+                                                   Entry{3, "three!"}, Entry{4, "four!"}));
+}
+
+TEST(Hashmap, KeysValues) {
+    using Map = Hashmap<int, std::string, 8>;
+    Map map;
+    map.Add(1, "one");
+    map.Add(4, "four");
+    map.Add(3, "three");
+    map.Add(2, "two");
+    EXPECT_THAT(map.Keys(), testing::UnorderedElementsAre(1, 2, 3, 4));
+    EXPECT_THAT(map.Values(), testing::UnorderedElementsAre("one", "two", "three", "four"));
+}
+
 TEST(Hashmap, AddMany) {
     Hashmap<int, std::string, 8> map;
     for (size_t i = 0; i < kPrimes.size(); i++) {
@@ -329,5 +355,61 @@
     }
 }
 
+TEST(Hashmap, EqualitySameSize) {
+    Hashmap<int, std::string, 8> a;
+    Hashmap<int, std::string, 8> b;
+    EXPECT_EQ(a, b);
+    a.Add(1, "one");
+    EXPECT_NE(a, b);
+    b.Add(2, "two");
+    EXPECT_NE(a, b);
+    a.Add(2, "two");
+    EXPECT_NE(a, b);
+    b.Add(1, "one");
+    EXPECT_EQ(a, b);
+}
+
+TEST(Hashmap, EqualityDifferentSize) {
+    Hashmap<int, std::string, 8> a;
+    Hashmap<int, std::string, 4> b;
+    EXPECT_EQ(a, b);
+    a.Add(1, "one");
+    EXPECT_NE(a, b);
+    b.Add(2, "two");
+    EXPECT_NE(a, b);
+    a.Add(2, "two");
+    EXPECT_NE(a, b);
+    b.Add(1, "one");
+    EXPECT_EQ(a, b);
+}
+
+TEST(Hashmap, HashSameSize) {
+    Hashmap<int, std::string, 8> a;
+    Hashmap<int, std::string, 8> b;
+    EXPECT_EQ(Hash(a), Hash(b));
+    a.Add(1, "one");
+    EXPECT_NE(Hash(a), Hash(b));
+    b.Add(2, "two");
+    EXPECT_NE(Hash(a), Hash(b));
+    a.Add(2, "two");
+    EXPECT_NE(Hash(a), Hash(b));
+    b.Add(1, "one");
+    EXPECT_EQ(Hash(a), Hash(b));
+}
+
+TEST(Hashmap, HashDifferentSize) {
+    Hashmap<int, std::string, 8> a;
+    Hashmap<int, std::string, 4> b;
+    EXPECT_EQ(Hash(a), Hash(b));
+    a.Add(1, "one");
+    EXPECT_NE(Hash(a), Hash(b));
+    b.Add(2, "two");
+    EXPECT_NE(Hash(a), Hash(b));
+    a.Add(2, "two");
+    EXPECT_NE(Hash(a), Hash(b));
+    b.Add(1, "one");
+    EXPECT_EQ(Hash(a), Hash(b));
+}
+
 }  // namespace
 }  // namespace tint::utils
diff --git a/src/tint/utils/vector.h b/src/tint/utils/vector.h
index ecee170..cefd536 100644
--- a/src/tint/utils/vector.h
+++ b/src/tint/utils/vector.h
@@ -423,6 +423,19 @@
         return val;
     }
 
+    /// Sort sorts the vector in-place using the predicate function @p pred
+    /// @param pred a function that has the signature `bool(const T& a, const T& b)` which returns
+    /// true if `a` is ordered before `b`.
+    template <typename PREDICATE>
+    void Sort(PREDICATE&& pred) {
+        std::sort(begin(), end(), std::forward<PREDICATE>(pred));
+    }
+
+    /// Sort sorts the vector in-place using `T::operator<()`
+    void Sort() {
+        Sort([](auto& a, auto& b) { return a < b; });
+    }
+
     /// @returns true if the vector is empty.
     bool IsEmpty() const { return impl_.slice.len == 0; }
 
diff --git a/src/tint/utils/vector_test.cc b/src/tint/utils/vector_test.cc
index 9a9bb4b..8e6fa7a 100644
--- a/src/tint/utils/vector_test.cc
+++ b/src/tint/utils/vector_test.cc
@@ -17,7 +17,7 @@
 #include <string>
 #include <tuple>
 
-#include "gtest/gtest.h"
+#include "gmock/gmock.h"
 
 #include "src/tint/utils/bitcast.h"
 
@@ -2009,6 +2009,18 @@
     EXPECT_EQ(vec_ref[1], "two");
 }
 
+TEST(TintVectorRefTest, Sort) {
+    Vector vec{1, 5, 3, 4, 2};
+    vec.Sort();
+    EXPECT_THAT(vec, testing::ElementsAre(1, 2, 3, 4, 5));
+}
+
+TEST(TintVectorRefTest, SortPredicate) {
+    Vector vec{1, 5, 3, 4, 2};
+    vec.Sort([](int a, int b) { return b < a; });
+    EXPECT_THAT(vec, testing::ElementsAre(5, 4, 3, 2, 1));
+}
+
 TEST(TintVectorRefTest, ConstIndex) {
     Vector<std::string, 2> vec{"one", "two"};
     const VectorRef<std::string> vec_ref(vec);