Import Tint changes from Dawn

Changes:
  - 49bae34149a604e5ea69153979dcf07a9c2cbd7f [ir][spirv-writer] Implement binary add instructions by James Price <jrprice@google.com>
  - 5e7807f02bf79fa68910a051a868531e89cc84dc [ir][spirv-writer] Emit blocks by James Price <jrprice@google.com>
  - 577b164b9145e81992541e2f278eb0c789f0baa1 [ir][spirv-writer] Emit scalar constant values by James Price <jrprice@google.com>
  - a8236a55295a3e7c335c1ae2ab2fab3e5f4593ec tint/ir: Remove value id field. by Ben Clayton <bclayton@google.com>
  - 3731ce8f21bc737773805e53fc3ff2d330e1b5f1 tint: Use new string utilities in various places by Ben Clayton <bclayton@google.com>
  - ce1025fde36d09017ff33eec66a12f965960b738 tint/utils: Add more string utilities. by Ben Clayton <bclayton@google.com>
  - fbef69b61c50edfab5b8df5b8095ab74e48ffc9d [ir][spirv-writer] Fix MSVC build by James Price <jrprice@google.com>
  - 6c1812305823e7552cb877e3fdd47e9022c54752 [spirv-reader] Avoid name clashes with builtins by James Price <jrprice@google.com>
  - 567a53e87cef93a2f4086765920b2c401c7ff0a7 tint/utils: Add Vector::Any(), Vector::All() and predicates by Ben Clayton <bclayton@google.com>
  - 13ca70fa0886e4588dda6f744d777b65c9744714 Reland "ir/spirv-writer: Emit entry point declarations" by James Price <jrprice@google.com>
  - 81fc1098228bdbb8098b1ff6b9c52a47d86e7113 [tint] Fix unshadowing of abstract const users by James Price <jrprice@google.com>
  - e162a1adeeab1fe0c3f61cbd9879597ce9de0cbe [spirv-writer] Don't emit abstract accessor source by James Price <jrprice@google.com>
  - f91b77dd6dee2ac969bc553d73c717ae42211840 [spirv-reader] Use builtin matrix aliases by James Price <jrprice@google.com>
  - 114bae24b0fbe23632cc849e04cd1299bfea41eb [spirv-reader] Use builtin vector aliases by James Price <jrprice@google.com>
GitOrigin-RevId: 49bae34149a604e5ea69153979dcf07a9c2cbd7f
Change-Id: Id5e7d7976f7efd660d95195954e161182c280d4e
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/132380
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index 8cf3daa..92edf12 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -251,6 +251,7 @@
     "utils/hashset.h",
     "utils/map.h",
     "utils/math.h",
+    "utils/predicates.h",
     "utils/scoped_assignment.h",
     "utils/slice.h",
     "utils/string.cc",
@@ -1725,6 +1726,7 @@
       "utils/io/tmpfile_test.cc",
       "utils/map_test.cc",
       "utils/math_test.cc",
+      "utils/predicates_test.cc",
       "utils/result_test.cc",
       "utils/reverse_test.cc",
       "utils/scoped_assignment_test.cc",
@@ -1805,6 +1807,7 @@
     ]
 
     deps = [
+      ":libtint_builtins_src",
       ":libtint_spv_reader_src",
       ":libtint_wgsl_writer_src",
       "${tint_spirv_tools_dir}/:spvtools_opt",
@@ -1859,6 +1862,8 @@
 
     if (tint_build_ir) {
       sources += [
+        "writer/spirv/generator_impl_binary_test.cc",
+        "writer/spirv/generator_impl_constant_test.cc",
         "writer/spirv/generator_impl_function_test.cc",
         "writer/spirv/generator_impl_ir_test.cc",
         "writer/spirv/generator_impl_type_test.cc",
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index f47d2ce..60b9885 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -532,6 +532,7 @@
   utils/hashset.h
   utils/map.h
   utils/math.h
+  utils/predicates.h
   utils/scoped_assignment.h
   utils/slice.h
   utils/string.cc
@@ -1030,6 +1031,7 @@
     utils/hashset_test.cc
     utils/map_test.cc
     utils/math_test.cc
+    utils/predicates_test.cc
     utils/result_test.cc
     utils/reverse_test.cc
     utils/scoped_assignment_test.cc
@@ -1227,6 +1229,8 @@
 
     if(${TINT_BUILD_IR})
       list(APPEND TINT_TEST_SRCS
+        writer/spirv/generator_impl_binary_test.cc
+        writer/spirv/generator_impl_constant_test.cc
         writer/spirv/generator_impl_function_test.cc
         writer/spirv/generator_impl_ir_test.cc
         writer/spirv/generator_impl_type_test.cc
diff --git a/src/tint/cmd/helper.cc b/src/tint/cmd/helper.cc
index a00e24a..2e2398c 100644
--- a/src/tint/cmd/helper.cc
+++ b/src/tint/cmd/helper.cc
@@ -22,6 +22,8 @@
 #include "spirv-tools/libspirv.hpp"
 #endif
 
+#include "src/tint/utils/string.h"
+
 namespace tint::cmd {
 namespace {
 
@@ -34,12 +36,11 @@
 
 InputFormat InputFormatFromFilename(const std::string& filename) {
     auto input_format = InputFormat::kUnknown;
-
-    if (filename.size() > 5 && filename.substr(filename.size() - 5) == ".wgsl") {
+    if (utils::HasSuffix(filename, ".wgsl")) {
         input_format = InputFormat::kWgsl;
-    } else if (filename.size() > 4 && filename.substr(filename.size() - 4) == ".spv") {
+    } else if (utils::HasSuffix(filename, ".spv")) {
         input_format = InputFormat::kSpirvBin;
-    } else if (filename.size() > 7 && filename.substr(filename.size() - 7) == ".spvasm") {
+    } else if (utils::HasSuffix(filename, ".spvasm")) {
         input_format = InputFormat::kSpirvAsm;
     }
     return input_format;
diff --git a/src/tint/cmd/main.cc b/src/tint/cmd/main.cc
index 76a9256..e7f596a 100644
--- a/src/tint/cmd/main.cc
+++ b/src/tint/cmd/main.cc
@@ -205,47 +205,34 @@
     return Format::kUnknown;
 }
 
-#if TINT_BUILD_SPV_WRITER || TINT_BUILD_WGSL_WRITER || TINT_BUILD_MSL_WRITER || \
-    TINT_BUILD_HLSL_WRITER
-/// @param input input string
-/// @param suffix potential suffix string
-/// @returns true if input ends with the given suffix.
-bool ends_with(const std::string& input, const std::string& suffix) {
-    const auto input_len = input.size();
-    const auto suffix_len = suffix.size();
-    // Avoid integer overflow.
-    return (input_len >= suffix_len) && (input_len - suffix_len == input.rfind(suffix));
-}
-#endif
-
 /// @param filename the filename to inspect
 /// @returns the inferred format for the filename suffix
 Format infer_format(const std::string& filename) {
     (void)filename;
 
 #if TINT_BUILD_SPV_WRITER
-    if (ends_with(filename, ".spv")) {
+    if (tint::utils::HasSuffix(filename, ".spv")) {
         return Format::kSpirv;
     }
-    if (ends_with(filename, ".spvasm")) {
+    if (tint::utils::HasSuffix(filename, ".spvasm")) {
         return Format::kSpvAsm;
     }
 #endif  // TINT_BUILD_SPV_WRITER
 
 #if TINT_BUILD_WGSL_WRITER
-    if (ends_with(filename, ".wgsl")) {
+    if (tint::utils::HasSuffix(filename, ".wgsl")) {
         return Format::kWgsl;
     }
 #endif  // TINT_BUILD_WGSL_WRITER
 
 #if TINT_BUILD_MSL_WRITER
-    if (ends_with(filename, ".metal")) {
+    if (tint::utils::HasSuffix(filename, ".metal")) {
         return Format::kMsl;
     }
 #endif  // TINT_BUILD_MSL_WRITER
 
 #if TINT_BUILD_HLSL_WRITER
-    if (ends_with(filename, ".hlsl")) {
+    if (tint::utils::HasSuffix(filename, ".hlsl")) {
         return Format::kHlsl;
     }
 #endif  // TINT_BUILD_HLSL_WRITER
diff --git a/src/tint/ir/binary.cc b/src/tint/ir/binary.cc
index a6a6aa7..063924f 100644
--- a/src/tint/ir/binary.cc
+++ b/src/tint/ir/binary.cc
@@ -19,8 +19,8 @@
 
 namespace tint::ir {
 
-Binary::Binary(uint32_t identifier, Kind kind, const type::Type* ty, Value* lhs, Value* rhs)
-    : Base(identifier, ty), kind_(kind), lhs_(lhs), rhs_(rhs) {
+Binary::Binary(Kind kind, const type::Type* ty, Value* lhs, Value* rhs)
+    : Base(ty), kind_(kind), lhs_(lhs), rhs_(rhs) {
     TINT_ASSERT(IR, lhs_);
     TINT_ASSERT(IR, rhs_);
     lhs_->AddUsage(this);
diff --git a/src/tint/ir/binary.h b/src/tint/ir/binary.h
index c686797..e941b3f 100644
--- a/src/tint/ir/binary.h
+++ b/src/tint/ir/binary.h
@@ -47,12 +47,11 @@
     };
 
     /// Constructor
-    /// @param id the instruction id
     /// @param kind the kind of binary instruction
     /// @param type the result type
     /// @param lhs the lhs of the instruction
     /// @param rhs the rhs of the instruction
-    Binary(uint32_t id, Kind kind, const type::Type* type, Value* lhs, Value* rhs);
+    Binary(Kind kind, const type::Type* type, Value* lhs, Value* rhs);
     Binary(const Binary& inst) = delete;
     Binary(Binary&& inst) = delete;
     ~Binary() override;
diff --git a/src/tint/ir/bitcast.cc b/src/tint/ir/bitcast.cc
index 89eedca..455bc69 100644
--- a/src/tint/ir/bitcast.cc
+++ b/src/tint/ir/bitcast.cc
@@ -19,8 +19,7 @@
 
 namespace tint::ir {
 
-Bitcast::Bitcast(uint32_t identifier, const type::Type* ty, Value* val)
-    : Base(identifier, ty, utils::Vector{val}) {}
+Bitcast::Bitcast(const type::Type* ty, Value* val) : Base(ty, utils::Vector{val}) {}
 
 Bitcast::~Bitcast() = default;
 
diff --git a/src/tint/ir/bitcast.h b/src/tint/ir/bitcast.h
index 0443701..0ad1acb 100644
--- a/src/tint/ir/bitcast.h
+++ b/src/tint/ir/bitcast.h
@@ -24,10 +24,9 @@
 class Bitcast : public utils::Castable<Bitcast, Call> {
   public:
     /// Constructor
-    /// @param id the instruction id
     /// @param type the result type
     /// @param val the value being bitcast
-    Bitcast(uint32_t id, const type::Type* type, Value* val);
+    Bitcast(const type::Type* type, Value* val);
     Bitcast(const Bitcast& inst) = delete;
     Bitcast(Bitcast&& inst) = delete;
     ~Bitcast() override;
diff --git a/src/tint/ir/builder.cc b/src/tint/ir/builder.cc
index e826be4..fe350cd 100644
--- a/src/tint/ir/builder.cc
+++ b/src/tint/ir/builder.cc
@@ -109,7 +109,7 @@
 }
 
 Binary* Builder::CreateBinary(Binary::Kind kind, const type::Type* type, Value* lhs, Value* rhs) {
-    return ir.instructions.Create<ir::Binary>(next_inst_id(), kind, type, lhs, rhs);
+    return ir.instructions.Create<ir::Binary>(kind, type, lhs, rhs);
 }
 
 Binary* Builder::And(const type::Type* type, Value* lhs, Value* rhs) {
@@ -177,7 +177,7 @@
 }
 
 Unary* Builder::CreateUnary(Unary::Kind kind, const type::Type* type, Value* val) {
-    return ir.instructions.Create<ir::Unary>(next_inst_id(), kind, type, val);
+    return ir.instructions.Create<ir::Unary>(kind, type, val);
 }
 
 Unary* Builder::AddressOf(const type::Type* type, Value* val) {
@@ -201,7 +201,7 @@
 }
 
 ir::Bitcast* Builder::Bitcast(const type::Type* type, Value* val) {
-    return ir.instructions.Create<ir::Bitcast>(next_inst_id(), type, val);
+    return ir.instructions.Create<ir::Bitcast>(type, val);
 }
 
 ir::Discard* Builder::Discard() {
@@ -211,23 +211,23 @@
 ir::UserCall* Builder::UserCall(const type::Type* type,
                                 Symbol name,
                                 utils::VectorRef<Value*> args) {
-    return ir.instructions.Create<ir::UserCall>(next_inst_id(), type, name, std::move(args));
+    return ir.instructions.Create<ir::UserCall>(type, name, std::move(args));
 }
 
 ir::Convert* Builder::Convert(const type::Type* to,
                               const type::Type* from,
                               utils::VectorRef<Value*> args) {
-    return ir.instructions.Create<ir::Convert>(next_inst_id(), to, from, std::move(args));
+    return ir.instructions.Create<ir::Convert>(to, from, std::move(args));
 }
 
 ir::Construct* Builder::Construct(const type::Type* to, utils::VectorRef<Value*> args) {
-    return ir.instructions.Create<ir::Construct>(next_inst_id(), to, std::move(args));
+    return ir.instructions.Create<ir::Construct>(to, std::move(args));
 }
 
 ir::Builtin* Builder::Builtin(const type::Type* type,
                               builtin::Function func,
                               utils::VectorRef<Value*> args) {
-    return ir.instructions.Create<ir::Builtin>(next_inst_id(), type, func, args);
+    return ir.instructions.Create<ir::Builtin>(type, func, args);
 }
 
 ir::Store* Builder::Store(Value* to, Value* from) {
@@ -237,7 +237,7 @@
 ir::Var* Builder::Declare(const type::Type* type,
                           builtin::AddressSpace address_space,
                           builtin::Access access) {
-    return ir.instructions.Create<ir::Var>(next_inst_id(), type, address_space, access);
+    return ir.instructions.Create<ir::Var>(type, address_space, access);
 }
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/builder.h b/src/tint/ir/builder.h
index ff01e2d..87a256b 100644
--- a/src/tint/ir/builder.h
+++ b/src/tint/ir/builder.h
@@ -364,11 +364,6 @@
 
     /// The IR module.
     Module ir;
-
-  private:
-    uint32_t next_inst_id() { return next_instruction_id_++; }
-
-    uint32_t next_instruction_id_ = 1;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/builder_impl_binary_test.cc b/src/tint/ir/builder_impl_binary_test.cc
index a9a8978..ed13159 100644
--- a/src/tint/ir/builder_impl_binary_test.cc
+++ b/src/tint/ir/builder_impl_binary_test.cc
@@ -53,13 +53,13 @@
     ASSERT_TRUE(r) << Error();
     auto m = r.Move();
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = block
 %1:ref<private, u32, read_write> = var private read_write
 
 
 
-%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn2 = block
+%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn3 = block
   %2:ref<private, u32, read_write> = add %1:ref<private, u32, read_write>, 1u
   store %1:ref<private, u32, read_write>, %2:ref<private, u32, read_write>
   ret
@@ -95,13 +95,13 @@
     ASSERT_TRUE(r) << Error();
     auto m = r.Move();
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = block
 %1:ref<private, u32, read_write> = var private read_write
 
 
 
-%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn2 = block
+%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn3 = block
   %2:ref<private, u32, read_write> = sub %1:ref<private, u32, read_write>, 1u
   store %1:ref<private, u32, read_write>, %2:ref<private, u32, read_write>
   ret
@@ -137,13 +137,13 @@
     ASSERT_TRUE(r) << Error();
     auto m = r.Move();
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = block
 %1:ref<private, u32, read_write> = var private read_write
 
 
 
-%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn2 = block
+%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn3 = block
   %2:ref<private, u32, read_write> = mul %1:ref<private, u32, read_write>, 1u
   store %1:ref<private, u32, read_write>, %2:ref<private, u32, read_write>
   ret
@@ -179,13 +179,13 @@
     ASSERT_TRUE(r) << Error();
     auto m = r.Move();
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = block
 %1:ref<private, u32, read_write> = var private read_write
 
 
 
-%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn2 = block
+%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn3 = block
   %2:ref<private, u32, read_write> = div %1:ref<private, u32, read_write>, 1u
   store %1:ref<private, u32, read_write>, %2:ref<private, u32, read_write>
   ret
@@ -221,13 +221,13 @@
     ASSERT_TRUE(r) << Error();
     auto m = r.Move();
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = block
 %1:ref<private, u32, read_write> = var private read_write
 
 
 
-%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn2 = block
+%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn3 = block
   %2:ref<private, u32, read_write> = mod %1:ref<private, u32, read_write>, 1u
   store %1:ref<private, u32, read_write>, %2:ref<private, u32, read_write>
   ret
@@ -263,13 +263,13 @@
     ASSERT_TRUE(r) << Error();
     auto m = r.Move();
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = block
 %1:ref<private, bool, read_write> = var private read_write
 
 
 
-%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn2 = block
+%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn3 = block
   %2:ref<private, bool, read_write> = and %1:ref<private, bool, read_write>, false
   store %1:ref<private, bool, read_write>, %2:ref<private, bool, read_write>
   ret
@@ -305,13 +305,13 @@
     ASSERT_TRUE(r) << Error();
     auto m = r.Move();
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = block
 %1:ref<private, bool, read_write> = var private read_write
 
 
 
-%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn2 = block
+%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn3 = block
   %2:ref<private, bool, read_write> = or %1:ref<private, bool, read_write>, false
   store %1:ref<private, bool, read_write>, %2:ref<private, bool, read_write>
   ret
@@ -347,13 +347,13 @@
     ASSERT_TRUE(r) << Error();
     auto m = r.Move();
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = block
 %1:ref<private, u32, read_write> = var private read_write
 
 
 
-%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn2 = block
+%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn3 = block
   %2:ref<private, u32, read_write> = xor %1:ref<private, u32, read_write>, 1u
   store %1:ref<private, u32, read_write>, %2:ref<private, u32, read_write>
   ret
@@ -371,26 +371,26 @@
     ASSERT_TRUE(r) << Error();
     auto m = r.Move();
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func my_func():bool
-  %fn1 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = func my_func():bool
+  %fn2 = block
   ret true
 func_end
 
-%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn3 = block
+%fn3 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn4 = block
   %1:bool = call my_func
   %2:bool = var function read_write
   store %2:bool, %1:bool
-  branch %fn4
+  branch %fn5
 
-  %fn4 = if %1:bool [t: %fn5, f: %fn6, m: %fn7]
+  %fn5 = if %1:bool [t: %fn6, f: %fn7, m: %fn8]
     # true branch
-    %fn5 = block
+    %fn6 = block
     store %2:bool, false
-    branch %fn7
+    branch %fn8
 
   # if merge
-  %fn7 = block
+  %fn8 = block
   ret
 func_end
 
@@ -406,27 +406,27 @@
     ASSERT_TRUE(r) << Error();
     auto m = r.Move();
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func my_func():bool
-  %fn1 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = func my_func():bool
+  %fn2 = block
   ret true
 func_end
 
-%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn3 = block
+%fn3 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn4 = block
   %1:bool = call my_func
   %2:bool = var function read_write
   store %2:bool, %1:bool
-  branch %fn4
+  branch %fn5
 
-  %fn4 = if %1:bool [t: %fn5, f: %fn6, m: %fn7]
+  %fn5 = if %1:bool [t: %fn6, f: %fn7, m: %fn8]
     # true branch
     # false branch
-    %fn6 = block
+    %fn7 = block
     store %2:bool, true
-    branch %fn7
+    branch %fn8
 
   # if merge
-  %fn7 = block
+  %fn8 = block
   ret
 func_end
 
@@ -568,13 +568,13 @@
     ASSERT_TRUE(r) << Error();
     auto m = r.Move();
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = block
 %1:ref<private, u32, read_write> = var private read_write
 
 
 
-%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn2 = block
+%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn3 = block
   %2:ref<private, u32, read_write> = shiftl %1:ref<private, u32, read_write>, 1u
   store %1:ref<private, u32, read_write>, %2:ref<private, u32, read_write>
   ret
@@ -610,13 +610,13 @@
     ASSERT_TRUE(r) << Error();
     auto m = r.Move();
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = block
 %1:ref<private, u32, read_write> = var private read_write
 
 
 
-%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn2 = block
+%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn3 = block
   %2:ref<private, u32, read_write> = shiftr %1:ref<private, u32, read_write>, 1u
   store %1:ref<private, u32, read_write>, %2:ref<private, u32, read_write>
   ret
@@ -636,32 +636,32 @@
     ASSERT_TRUE(r) << Error();
     auto m = r.Move();
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func my_func():f32
-  %fn1 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = func my_func():f32
+  %fn2 = block
   ret 0.0f
 func_end
 
-%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn3 = block
+%fn3 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn4 = block
   %1:f32 = call my_func
   %2:bool = lt %1:f32, 2.0f
   %3:bool = var function read_write
   store %3:bool, %2:bool
-  branch %fn4
+  branch %fn5
 
-  %fn4 = if %2:bool [t: %fn5, f: %fn6, m: %fn7]
+  %fn5 = if %2:bool [t: %fn6, f: %fn7, m: %fn8]
     # true branch
-    %fn5 = block
+    %fn6 = block
     %4:f32 = call my_func
     %5:f32 = call my_func
     %6:f32 = mul 2.29999995231628417969f, %5:f32
     %7:f32 = div %4:f32, %6:f32
     %8:bool = gt 2.5f, %7:f32
     store %3:bool, %8:bool
-    branch %fn7
+    branch %fn8
 
   # if merge
-  %fn7 = block
+  %fn8 = block
   ret
 func_end
 
@@ -678,13 +678,13 @@
     ASSERT_TRUE(r) << Error();
     auto m = r.Move();
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func my_func():bool
-  %fn1 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = func my_func():bool
+  %fn2 = block
   ret true
 func_end
 
-%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn3 = block
+%fn3 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn4 = block
   %1:bool = call my_func, false
   ret
 func_end
diff --git a/src/tint/ir/builder_impl_call_test.cc b/src/tint/ir/builder_impl_call_test.cc
index 4564880..6339626 100644
--- a/src/tint/ir/builder_impl_call_test.cc
+++ b/src/tint/ir/builder_impl_call_test.cc
@@ -91,14 +91,14 @@
     auto m = r.Move();
     ASSERT_TRUE(r);
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = block
 %1:ref<private, i32, read_write> = var private read_write
 store %1:ref<private, i32, read_write>, 1i
 
 
 
-%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn2 = block
+%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn3 = block
   %2:f32 = convert i32, %1:ref<private, i32, read_write>
   ret
 func_end
@@ -115,7 +115,7 @@
     auto m = r.Move();
     ASSERT_TRUE(r);
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = block
 %1:ref<private, vec3<f32>, read_write> = var private read_write
 store %1:ref<private, vec3<f32>, read_write>, vec3<f32> 0.0f
 
@@ -134,14 +134,14 @@
     auto m = r.Move();
     ASSERT_TRUE(r);
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = block
 %1:ref<private, f32, read_write> = var private read_write
 store %1:ref<private, f32, read_write>, 1.0f
 
 
 
-%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn2 = block
+%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn3 = block
   %2:vec3<f32> = construct 2.0f, 3.0f, %1:ref<private, f32, read_write>
   ret
 func_end
diff --git a/src/tint/ir/builder_impl_materialize_test.cc b/src/tint/ir/builder_impl_materialize_test.cc
index 4f917f7..6b4ae84 100644
--- a/src/tint/ir/builder_impl_materialize_test.cc
+++ b/src/tint/ir/builder_impl_materialize_test.cc
@@ -35,8 +35,8 @@
     ASSERT_TRUE(r) << Error();
     auto m = r.Move();
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func test_function():f32
-  %fn1 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = func test_function():f32
+  %fn2 = block
   ret 2.0f
 func_end
 
diff --git a/src/tint/ir/builder_impl_store_test.cc b/src/tint/ir/builder_impl_store_test.cc
index f7cc6ab..7cd074c 100644
--- a/src/tint/ir/builder_impl_store_test.cc
+++ b/src/tint/ir/builder_impl_store_test.cc
@@ -36,13 +36,13 @@
     ASSERT_TRUE(r) << Error();
     auto m = r.Move();
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = block
 %1:ref<private, u32, read_write> = var private read_write
 
 
 
-%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn2 = block
+%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn3 = block
   store %1:ref<private, u32, read_write>, 4u
   ret
 func_end
diff --git a/src/tint/ir/builder_impl_test.cc b/src/tint/ir/builder_impl_test.cc
index 4a6e713..36eb3b6 100644
--- a/src/tint/ir/builder_impl_test.cc
+++ b/src/tint/ir/builder_impl_test.cc
@@ -42,8 +42,8 @@
     EXPECT_EQ(1u, f->start_target->inbound_branches.Length());
     EXPECT_EQ(1u, f->end_target->inbound_branches.Length());
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = func f():void
-  %fn1 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = func f():void
+  %fn2 = block
   ret
 func_end
 
@@ -89,21 +89,21 @@
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
-  branch %fn2
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  branch %fn3
 
-  %fn2 = if true [t: %fn3, f: %fn4, m: %fn5]
+  %fn3 = if true [t: %fn4, f: %fn5, m: %fn6]
     # true branch
-    %fn3 = block
-    branch %fn5
+    %fn4 = block
+    branch %fn6
 
     # false branch
-    %fn4 = block
-    branch %fn5
+    %fn5 = block
+    branch %fn6
 
   # if merge
-  %fn5 = block
+  %fn6 = block
   ret
 func_end
 
@@ -138,20 +138,20 @@
     EXPECT_EQ(2u, func->end_target->inbound_branches.Length());
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
-  branch %fn2
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  branch %fn3
 
-  %fn2 = if true [t: %fn3, f: %fn4, m: %fn5]
+  %fn3 = if true [t: %fn4, f: %fn5, m: %fn6]
     # true branch
-    %fn3 = block
+    %fn4 = block
     ret
     # false branch
-    %fn4 = block
-    branch %fn5
+    %fn5 = block
+    branch %fn6
 
   # if merge
-  %fn5 = block
+  %fn6 = block
   ret
 func_end
 
@@ -186,20 +186,20 @@
     EXPECT_EQ(2u, func->end_target->inbound_branches.Length());
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
-  branch %fn2
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  branch %fn3
 
-  %fn2 = if true [t: %fn3, f: %fn4, m: %fn5]
+  %fn3 = if true [t: %fn4, f: %fn5, m: %fn6]
     # true branch
-    %fn3 = block
-    branch %fn5
+    %fn4 = block
+    branch %fn6
 
     # false branch
-    %fn4 = block
+    %fn5 = block
     ret
   # if merge
-  %fn5 = block
+  %fn6 = block
   ret
 func_end
 
@@ -234,16 +234,16 @@
     EXPECT_EQ(2u, func->end_target->inbound_branches.Length());
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
-  branch %fn2
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  branch %fn3
 
-  %fn2 = if true [t: %fn3, f: %fn4]
+  %fn3 = if true [t: %fn4, f: %fn5]
     # true branch
-    %fn3 = block
+    %fn4 = block
     ret
     # false branch
-    %fn4 = block
+    %fn5 = block
     ret
 func_end
 
@@ -278,30 +278,30 @@
     ASSERT_NE(loop_flow->merge.target, nullptr);
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
-  branch %fn2
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  branch %fn3
 
-  %fn2 = if true [t: %fn3, f: %fn4, m: %fn5]
+  %fn3 = if true [t: %fn4, f: %fn5, m: %fn6]
     # true branch
-    %fn3 = block
-    branch %fn6
+    %fn4 = block
+    branch %fn7
 
-    %fn6 = loop [s: %fn7, m: %fn8]
+    %fn7 = loop [s: %fn8, m: %fn9]
       # loop start
-      %fn7 = block
-      branch %fn8
+      %fn8 = block
+      branch %fn9
 
     # loop merge
-    %fn8 = block
-    branch %fn5
+    %fn9 = block
+    branch %fn6
 
     # false branch
-    %fn4 = block
-    branch %fn5
+    %fn5 = block
+    branch %fn6
 
   # if merge
-  %fn5 = block
+  %fn6 = block
   ret
 func_end
 
@@ -336,17 +336,17 @@
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
-  branch %fn2
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  branch %fn3
 
-  %fn2 = loop [s: %fn3, m: %fn4]
+  %fn3 = loop [s: %fn4, m: %fn5]
     # loop start
-    %fn3 = block
-    branch %fn4
+    %fn4 = block
+    branch %fn5
 
   # loop merge
-  %fn4 = block
+  %fn5 = block
   ret
 func_end
 
@@ -395,34 +395,34 @@
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
-  branch %fn2
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  branch %fn3
 
-  %fn2 = loop [s: %fn3, c: %fn4, m: %fn5]
+  %fn3 = loop [s: %fn4, c: %fn5, m: %fn6]
     # loop start
-    %fn3 = block
-    branch %fn6
+    %fn4 = block
+    branch %fn7
 
-    %fn6 = if true [t: %fn7, f: %fn8, m: %fn9]
+    %fn7 = if true [t: %fn8, f: %fn9, m: %fn10]
       # true branch
-      %fn7 = block
-      branch %fn5
+      %fn8 = block
+      branch %fn6
 
       # false branch
-      %fn8 = block
-      branch %fn9
+      %fn9 = block
+      branch %fn10
 
     # if merge
-    %fn9 = block
-    branch %fn4
+    %fn10 = block
+    branch %fn5
 
     # loop continuing
-    %fn4 = block
-    branch %fn3
+    %fn5 = block
+    branch %fn4
 
   # loop merge
-  %fn5 = block
+  %fn6 = block
   ret
 func_end
 
@@ -471,34 +471,34 @@
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
-  branch %fn2
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  branch %fn3
 
-  %fn2 = loop [s: %fn3, c: %fn4, m: %fn5]
+  %fn3 = loop [s: %fn4, c: %fn5, m: %fn6]
     # loop start
-    %fn3 = block
-    branch %fn4
+    %fn4 = block
+    branch %fn5
 
     # loop continuing
-    %fn4 = block
-    branch %fn6
+    %fn5 = block
+    branch %fn7
 
-    %fn6 = if true [t: %fn7, f: %fn8, m: %fn9]
+    %fn7 = if true [t: %fn8, f: %fn9, m: %fn10]
       # true branch
-      %fn7 = block
-      branch %fn5
+      %fn8 = block
+      branch %fn6
 
       # false branch
-      %fn8 = block
-      branch %fn9
+      %fn9 = block
+      branch %fn10
 
     # if merge
-    %fn9 = block
-    branch %fn3
+    %fn10 = block
+    branch %fn4
 
   # loop merge
-  %fn5 = block
+  %fn6 = block
   ret
 func_end
 
@@ -547,30 +547,30 @@
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
-  branch %fn2
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  branch %fn3
 
-  %fn2 = loop [s: %fn3, c: %fn4]
+  %fn3 = loop [s: %fn4, c: %fn5]
     # loop start
-    %fn3 = block
-    branch %fn5
+    %fn4 = block
+    branch %fn6
 
-    %fn5 = if true [t: %fn6, f: %fn7, m: %fn8]
+    %fn6 = if true [t: %fn7, f: %fn8, m: %fn9]
       # true branch
-      %fn6 = block
+      %fn7 = block
       ret
       # false branch
-      %fn7 = block
-      branch %fn8
+      %fn8 = block
+      branch %fn9
 
     # if merge
-    %fn8 = block
-    branch %fn4
+    %fn9 = block
+    branch %fn5
 
     # loop continuing
-    %fn4 = block
-    branch %fn3
+    %fn5 = block
+    branch %fn4
 
 func_end
 
@@ -605,13 +605,13 @@
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
-  branch %fn2
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  branch %fn3
 
-  %fn2 = loop [s: %fn3]
+  %fn3 = loop [s: %fn4]
     # loop start
-    %fn3 = block
+    %fn4 = block
     ret
 func_end
 
@@ -668,13 +668,13 @@
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
-  branch %fn2
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  branch %fn3
 
-  %fn2 = loop [s: %fn3]
+  %fn3 = loop [s: %fn4]
     # loop start
-    %fn3 = block
+    %fn4 = block
     ret
 func_end
 
@@ -723,26 +723,26 @@
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
-  branch %fn2
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  branch %fn3
 
-  %fn2 = loop [s: %fn3, m: %fn4]
+  %fn3 = loop [s: %fn4, m: %fn5]
     # loop start
-    %fn3 = block
-    branch %fn5
+    %fn4 = block
+    branch %fn6
 
-    %fn5 = if true [t: %fn6, f: %fn7]
+    %fn6 = if true [t: %fn7, f: %fn8]
       # true branch
-      %fn6 = block
-      branch %fn4
+      %fn7 = block
+      branch %fn5
 
       # false branch
-      %fn7 = block
-      branch %fn4
+      %fn8 = block
+      branch %fn5
 
   # loop merge
-  %fn4 = block
+  %fn5 = block
   ret
 func_end
 
@@ -870,108 +870,108 @@
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
-  branch %fn2
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  branch %fn3
 
-  %fn2 = loop [s: %fn3, c: %fn4, m: %fn5]
+  %fn3 = loop [s: %fn4, c: %fn5, m: %fn6]
     # loop start
-    %fn3 = block
-    branch %fn6
+    %fn4 = block
+    branch %fn7
 
-    %fn6 = loop [s: %fn7, c: %fn8, m: %fn9]
+    %fn7 = loop [s: %fn8, c: %fn9, m: %fn10]
       # loop start
-      %fn7 = block
-      branch %fn10
+      %fn8 = block
+      branch %fn11
 
-      %fn10 = if true [t: %fn11, f: %fn12, m: %fn13]
+      %fn11 = if true [t: %fn12, f: %fn13, m: %fn14]
         # true branch
-        %fn11 = block
+        %fn12 = block
+        branch %fn10
+
+        # false branch
+        %fn13 = block
+        branch %fn14
+
+      # if merge
+      %fn14 = block
+      branch %fn15
+
+      %fn15 = if true [t: %fn16, f: %fn17, m: %fn18]
+        # true branch
+        %fn16 = block
         branch %fn9
 
         # false branch
-        %fn12 = block
-        branch %fn13
+        %fn17 = block
+        branch %fn18
 
       # if merge
-      %fn13 = block
-      branch %fn14
-
-      %fn14 = if true [t: %fn15, f: %fn16, m: %fn17]
-        # true branch
-        %fn15 = block
-        branch %fn8
-
-        # false branch
-        %fn16 = block
-        branch %fn17
-
-      # if merge
-      %fn17 = block
-      branch %fn8
+      %fn18 = block
+      branch %fn9
 
       # loop continuing
-      %fn8 = block
-      branch %fn18
+      %fn9 = block
+      branch %fn19
 
-      %fn18 = loop [s: %fn19, m: %fn20]
+      %fn19 = loop [s: %fn20, m: %fn21]
         # loop start
-        %fn19 = block
-        branch %fn20
+        %fn20 = block
+        branch %fn21
 
       # loop merge
-      %fn20 = block
-      branch %fn21
+      %fn21 = block
+      branch %fn22
 
-      %fn21 = loop [s: %fn22, c: %fn23, m: %fn24]
+      %fn22 = loop [s: %fn23, c: %fn24, m: %fn25]
         # loop start
-        %fn22 = block
-        branch %fn23
+        %fn23 = block
+        branch %fn24
 
         # loop continuing
-        %fn23 = block
-        branch %fn25
+        %fn24 = block
+        branch %fn26
 
-        %fn25 = if true [t: %fn26, f: %fn27, m: %fn28]
+        %fn26 = if true [t: %fn27, f: %fn28, m: %fn29]
           # true branch
-          %fn26 = block
-          branch %fn24
+          %fn27 = block
+          branch %fn25
 
           # false branch
-          %fn27 = block
-          branch %fn28
+          %fn28 = block
+          branch %fn29
 
         # if merge
-        %fn28 = block
-        branch %fn22
+        %fn29 = block
+        branch %fn23
 
       # loop merge
-      %fn24 = block
-      branch %fn7
+      %fn25 = block
+      branch %fn8
 
     # loop merge
-    %fn9 = block
-    branch %fn29
+    %fn10 = block
+    branch %fn30
 
-    %fn29 = if true [t: %fn30, f: %fn31, m: %fn32]
+    %fn30 = if true [t: %fn31, f: %fn32, m: %fn33]
       # true branch
-      %fn30 = block
-      branch %fn5
+      %fn31 = block
+      branch %fn6
 
       # false branch
-      %fn31 = block
-      branch %fn32
+      %fn32 = block
+      branch %fn33
 
     # if merge
-    %fn32 = block
-    branch %fn4
+    %fn33 = block
+    branch %fn5
 
     # loop continuing
-    %fn4 = block
-    branch %fn3
+    %fn5 = block
+    branch %fn4
 
   # loop merge
-  %fn5 = block
+  %fn6 = block
   ret
 func_end
 
@@ -1015,34 +1015,34 @@
     EXPECT_EQ(1u, if_flow->merge.target->inbound_branches.Length());
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
-  branch %fn2
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  branch %fn3
 
-  %fn2 = loop [s: %fn3, c: %fn4, m: %fn5]
+  %fn3 = loop [s: %fn4, c: %fn5, m: %fn6]
     # loop start
-    %fn3 = block
-    branch %fn6
+    %fn4 = block
+    branch %fn7
 
-    %fn6 = if false [t: %fn7, f: %fn8, m: %fn9]
+    %fn7 = if false [t: %fn8, f: %fn9, m: %fn10]
       # true branch
-      %fn7 = block
-      branch %fn9
+      %fn8 = block
+      branch %fn10
 
       # false branch
-      %fn8 = block
-      branch %fn5
+      %fn9 = block
+      branch %fn6
 
     # if merge
-    %fn9 = block
-    branch %fn4
+    %fn10 = block
+    branch %fn5
 
     # loop continuing
-    %fn4 = block
-    branch %fn3
+    %fn5 = block
+    branch %fn4
 
   # loop merge
-  %fn5 = block
+  %fn6 = block
   ret
 func_end
 
@@ -1086,29 +1086,29 @@
     EXPECT_EQ(1u, if_flow->merge.target->inbound_branches.Length());
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
-  branch %fn2
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  branch %fn3
 
-  %fn2 = loop [s: %fn3, m: %fn4]
+  %fn3 = loop [s: %fn4, m: %fn5]
     # loop start
-    %fn3 = block
-    branch %fn5
+    %fn4 = block
+    branch %fn6
 
-    %fn5 = if true [t: %fn6, f: %fn7, m: %fn8]
+    %fn6 = if true [t: %fn7, f: %fn8, m: %fn9]
       # true branch
-      %fn6 = block
-      branch %fn8
+      %fn7 = block
+      branch %fn9
 
       # false branch
-      %fn7 = block
-      branch %fn4
+      %fn8 = block
+      branch %fn5
 
     # if merge
-    %fn8 = block
+    %fn9 = block
     ret
   # loop merge
-  %fn4 = block
+  %fn5 = block
   ret
 func_end
 
@@ -1194,17 +1194,17 @@
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
-  branch %fn2
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  branch %fn3
 
-  %fn2 = loop [s: %fn3, m: %fn4]
+  %fn3 = loop [s: %fn4, m: %fn5]
     # loop start
-    %fn3 = block
-    branch %fn4
+    %fn4 = block
+    branch %fn5
 
   # loop merge
-  %fn4 = block
+  %fn5 = block
   ret
 func_end
 
@@ -1254,25 +1254,25 @@
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
-  branch %fn2
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  branch %fn3
 
-  %fn2 = switch 1i [c: (0i, %fn3), c: (1i, %fn4), c: (default, %fn5), m: %fn6]
+  %fn3 = switch 1i [c: (0i, %fn4), c: (1i, %fn5), c: (default, %fn6), m: %fn7]
     # case 0i
-    %fn3 = block
-    branch %fn6
+    %fn4 = block
+    branch %fn7
 
     # case 1i
-    %fn4 = block
-    branch %fn6
+    %fn5 = block
+    branch %fn7
 
     # case default
-    %fn5 = block
-    branch %fn6
+    %fn6 = block
+    branch %fn7
 
   # switch merge
-  %fn6 = block
+  %fn7 = block
   ret
 func_end
 
@@ -1319,17 +1319,17 @@
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
-  branch %fn2
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  branch %fn3
 
-  %fn2 = switch 1i [c: (0i 1i default, %fn3), m: %fn4]
+  %fn3 = switch 1i [c: (0i 1i default, %fn4), m: %fn5]
     # case 0i 1i default
-    %fn3 = block
-    branch %fn4
+    %fn4 = block
+    branch %fn5
 
   # switch merge
-  %fn4 = block
+  %fn5 = block
   ret
 func_end
 
@@ -1364,17 +1364,17 @@
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
-  branch %fn2
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  branch %fn3
 
-  %fn2 = switch 1i [c: (default, %fn3), m: %fn4]
+  %fn3 = switch 1i [c: (default, %fn4), m: %fn5]
     # case default
-    %fn3 = block
-    branch %fn4
+    %fn4 = block
+    branch %fn5
 
   # switch merge
-  %fn4 = block
+  %fn5 = block
   ret
 func_end
 
@@ -1418,21 +1418,21 @@
     EXPECT_EQ(1u, func->end_target->inbound_branches.Length());
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
-  branch %fn2
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  branch %fn3
 
-  %fn2 = switch 1i [c: (0i, %fn3), c: (default, %fn4), m: %fn5]
+  %fn3 = switch 1i [c: (0i, %fn4), c: (default, %fn5), m: %fn6]
     # case 0i
-    %fn3 = block
-    branch %fn5
+    %fn4 = block
+    branch %fn6
 
     # case default
-    %fn4 = block
-    branch %fn5
+    %fn5 = block
+    branch %fn6
 
   # switch merge
-  %fn5 = block
+  %fn6 = block
   ret
 func_end
 
@@ -1478,16 +1478,16 @@
     EXPECT_EQ(2u, func->end_target->inbound_branches.Length());
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
-  branch %fn2
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
+  branch %fn3
 
-  %fn2 = switch 1i [c: (0i, %fn3), c: (default, %fn4)]
+  %fn3 = switch 1i [c: (0i, %fn4), c: (default, %fn5)]
     # case 0i
-    %fn3 = block
+    %fn4 = block
     ret
     # case default
-    %fn4 = block
+    %fn5 = block
     ret
 func_end
 
diff --git a/src/tint/ir/builder_impl_unary_test.cc b/src/tint/ir/builder_impl_unary_test.cc
index 9d35c02..091493d 100644
--- a/src/tint/ir/builder_impl_unary_test.cc
+++ b/src/tint/ir/builder_impl_unary_test.cc
@@ -90,13 +90,13 @@
     ASSERT_TRUE(r) << Error();
     auto m = r.Move();
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = block
 %1:ref<private, i32, read_write> = var private read_write
 
 
 
-%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn2 = block
+%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn3 = block
   %2:ptr<private, i32, read_write> = addr_of %1:ref<private, i32, read_write>
   ret
 func_end
@@ -116,13 +116,13 @@
     ASSERT_TRUE(r) << Error();
     auto m = r.Move();
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = block
 %1:ref<private, i32, read_write> = var private read_write
 
 
 
-%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn2 = block
+%fn2 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn3 = block
   %2:ptr<private, i32, read_write> = addr_of %1:ref<private, i32, read_write>
   %3:i32 = indirection %2:ptr<private, i32, read_write>
   ret
diff --git a/src/tint/ir/builder_impl_var_test.cc b/src/tint/ir/builder_impl_var_test.cc
index 485684a..d68ce8a 100644
--- a/src/tint/ir/builder_impl_var_test.cc
+++ b/src/tint/ir/builder_impl_var_test.cc
@@ -33,7 +33,7 @@
     ASSERT_TRUE(r) << Error();
     auto m = r.Move();
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = block
 %1:ref<private, u32, read_write> = var private read_write
 
 
@@ -49,7 +49,7 @@
     ASSERT_TRUE(r) << Error();
     auto m = r.Move();
 
-    EXPECT_EQ(Disassemble(m), R"(%fn0 = block
+    EXPECT_EQ(Disassemble(m), R"(%fn1 = block
 %1:ref<private, u32, read_write> = var private read_write
 store %1:ref<private, u32, read_write>, 2u
 
@@ -67,8 +67,8 @@
     auto m = r.Move();
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
   %1:ref<function, u32, read_write> = var function read_write
   ret
 func_end
@@ -86,8 +86,8 @@
     auto m = r.Move();
 
     EXPECT_EQ(Disassemble(m),
-              R"(%fn0 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
-  %fn1 = block
+              R"(%fn1 = func test_function():void [@compute @workgroup_size(1, 1, 1)]
+  %fn2 = block
   %1:ref<function, u32, read_write> = var function read_write
   store %1:ref<function, u32, read_write>, 2u
   ret
diff --git a/src/tint/ir/builtin.cc b/src/tint/ir/builtin.cc
index 5e53eec..fac0793 100644
--- a/src/tint/ir/builtin.cc
+++ b/src/tint/ir/builtin.cc
@@ -23,11 +23,8 @@
 // \cond DO_NOT_DOCUMENT
 namespace tint::ir {
 
-Builtin::Builtin(uint32_t identifier,
-                 const type::Type* ty,
-                 builtin::Function func,
-                 utils::VectorRef<Value*> arguments)
-    : Base(identifier, ty, std::move(arguments)), func_(func) {}
+Builtin::Builtin(const type::Type* ty, builtin::Function func, utils::VectorRef<Value*> arguments)
+    : Base(ty, std::move(arguments)), func_(func) {}
 
 Builtin::~Builtin() = default;
 
diff --git a/src/tint/ir/builtin.h b/src/tint/ir/builtin.h
index 2106a68..9a82dba 100644
--- a/src/tint/ir/builtin.h
+++ b/src/tint/ir/builtin.h
@@ -25,14 +25,10 @@
 class Builtin : public utils::Castable<Builtin, Call> {
   public:
     /// Constructor
-    /// @param id the instruction id
     /// @param type the result type
     /// @param func the builtin function
     /// @param args the conversion arguments
-    Builtin(uint32_t id,
-            const type::Type* type,
-            builtin::Function func,
-            utils::VectorRef<Value*> args);
+    Builtin(const type::Type* type, builtin::Function func, utils::VectorRef<Value*> args);
     Builtin(const Builtin& inst) = delete;
     Builtin(Builtin&& inst) = delete;
     ~Builtin() override;
diff --git a/src/tint/ir/call.cc b/src/tint/ir/call.cc
index 9f5a8a5..d8c1af6 100644
--- a/src/tint/ir/call.cc
+++ b/src/tint/ir/call.cc
@@ -20,8 +20,8 @@
 
 namespace tint::ir {
 
-Call::Call(uint32_t identifier, const type::Type* ty, utils::VectorRef<Value*> arguments)
-    : Base(identifier, ty), args(std::move(arguments)) {
+Call::Call(const type::Type* ty, utils::VectorRef<Value*> arguments)
+    : Base(ty), args(std::move(arguments)) {
     for (auto* arg : args) {
         arg->AddUsage(this);
     }
diff --git a/src/tint/ir/call.h b/src/tint/ir/call.h
index b0f2e7c..fde193e 100644
--- a/src/tint/ir/call.h
+++ b/src/tint/ir/call.h
@@ -37,10 +37,9 @@
     /// Constructor
     Call() = delete;
     /// Constructor
-    /// @param id the instruction id
     /// @param type the result type
     /// @param args the constructor arguments
-    Call(uint32_t id, const type::Type* type, utils::VectorRef<Value*> args);
+    Call(const type::Type* type, utils::VectorRef<Value*> args);
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/construct.cc b/src/tint/ir/construct.cc
index 44c8862..18e830d 100644
--- a/src/tint/ir/construct.cc
+++ b/src/tint/ir/construct.cc
@@ -22,8 +22,8 @@
 
 namespace tint::ir {
 
-Construct::Construct(uint32_t identifier, const type::Type* ty, utils::VectorRef<Value*> arguments)
-    : Base(identifier, ty, std::move(arguments)) {}
+Construct::Construct(const type::Type* ty, utils::VectorRef<Value*> arguments)
+    : Base(ty, std::move(arguments)) {}
 
 Construct::~Construct() = default;
 
diff --git a/src/tint/ir/construct.h b/src/tint/ir/construct.h
index b1711fb..b3ed6b6 100644
--- a/src/tint/ir/construct.h
+++ b/src/tint/ir/construct.h
@@ -24,10 +24,9 @@
 class Construct : public utils::Castable<Construct, Call> {
   public:
     /// Constructor
-    /// @param id the instruction id
     /// @param type the result type
     /// @param args the constructor arguments
-    Construct(uint32_t id, const type::Type* type, utils::VectorRef<Value*> args);
+    Construct(const type::Type* type, utils::VectorRef<Value*> args);
     Construct(const Construct& inst) = delete;
     Construct(Construct&& inst) = delete;
     ~Construct() override;
diff --git a/src/tint/ir/convert.cc b/src/tint/ir/convert.cc
index 969390c..edc2fe7 100644
--- a/src/tint/ir/convert.cc
+++ b/src/tint/ir/convert.cc
@@ -19,11 +19,10 @@
 
 namespace tint::ir {
 
-Convert::Convert(uint32_t identifier,
-                 const type::Type* to_type,
+Convert::Convert(const type::Type* to_type,
                  const type::Type* from_type,
                  utils::VectorRef<Value*> arguments)
-    : Base(identifier, to_type, arguments), from_type_(from_type) {}
+    : Base(to_type, arguments), from_type_(from_type) {}
 
 Convert::~Convert() = default;
 
diff --git a/src/tint/ir/convert.h b/src/tint/ir/convert.h
index 953a5fa..9fdbc5c 100644
--- a/src/tint/ir/convert.h
+++ b/src/tint/ir/convert.h
@@ -25,12 +25,10 @@
 class Convert : public utils::Castable<Convert, Call> {
   public:
     /// Constructor
-    /// @param id the instruction id
     /// @param result_type the result type
     /// @param from_type the type being converted from
     /// @param args the conversion arguments
-    Convert(uint32_t id,
-            const type::Type* result_type,
+    Convert(const type::Type* result_type,
             const type::Type* from_type,
             utils::VectorRef<Value*> args);
     Convert(const Convert& inst) = delete;
diff --git a/src/tint/ir/disassembler.cc b/src/tint/ir/disassembler.cc
index 58fb1a0..50d11cc 100644
--- a/src/tint/ir/disassembler.cc
+++ b/src/tint/ir/disassembler.cc
@@ -41,27 +41,29 @@
 namespace {
 
 class ScopedStopNode {
+    static constexpr size_t N = 32;
+
   public:
-    ScopedStopNode(std::unordered_set<const FlowNode*>* stop_nodes, const FlowNode* node)
+    ScopedStopNode(utils::Hashset<const FlowNode*, N>& stop_nodes, const FlowNode* node)
         : stop_nodes_(stop_nodes), node_(node) {
-        stop_nodes_->insert(node_);
+        stop_nodes_.Add(node_);
     }
 
-    ~ScopedStopNode() { stop_nodes_->erase(node_); }
+    ~ScopedStopNode() { stop_nodes_.Remove(node_); }
 
   private:
-    std::unordered_set<const FlowNode*>* stop_nodes_;
+    utils::Hashset<const FlowNode*, N>& stop_nodes_;
     const FlowNode* node_;
 };
 
 class ScopedIndent {
   public:
-    explicit ScopedIndent(uint32_t* indent) : indent_(indent) { (*indent_) += 2; }
+    explicit ScopedIndent(uint32_t& indent) : indent_(indent) { indent_ += 2; }
 
-    ~ScopedIndent() { (*indent_) -= 2; }
+    ~ScopedIndent() { indent_ -= 2; }
 
   private:
-    uint32_t* indent_;
+    uint32_t& indent_;
 };
 
 }  // namespace
@@ -85,30 +87,28 @@
     }
 }
 
-size_t Disassembler::GetIdForNode(const FlowNode* node) {
+size_t Disassembler::IdOf(const FlowNode* node) {
     TINT_ASSERT(IR, node);
+    return flow_node_ids_.GetOrCreate(node, [&] { return flow_node_ids_.Count(); });
+}
 
-    auto it = flow_node_to_id_.find(node);
-    if (it != flow_node_to_id_.end()) {
-        return it->second;
-    }
-    size_t id = next_node_id_++;
-    flow_node_to_id_[node] = id;
-    return id;
+std::string_view Disassembler::IdOf(const Value* value) {
+    TINT_ASSERT(IR, value);
+    return value_ids_.GetOrCreate(value, [&] { return std::to_string(value_ids_.Count()); });
 }
 
 void Disassembler::Walk(const FlowNode* node) {
-    if ((visited_.count(node) > 0) || (stop_nodes_.count(node) > 0)) {
+    if (visited_.Contains(node) || stop_nodes_.Contains(node)) {
         return;
     }
-    visited_.insert(node);
+    visited_.Add(node);
 
     tint::Switch(
         node,
         [&](const ir::Function* f) {
             TINT_SCOPED_ASSIGNMENT(in_function_, true);
 
-            Indent() << "%fn" << GetIdForNode(f) << " = func " << f->name.Name()
+            Indent() << "%fn" << IdOf(f) << " = func " << f->name.Name()
                      << "():" << f->return_type->FriendlyName();
 
             if (f->pipeline_stage != Function::PipelineStage::kUndefined) {
@@ -136,8 +136,8 @@
             out_ << std::endl;
 
             {
-                ScopedIndent func_indent(&indent_size_);
-                ScopedStopNode scope(&stop_nodes_, f->end_target);
+                ScopedIndent func_indent(indent_size_);
+                ScopedStopNode scope(stop_nodes_, f->end_target);
                 Walk(f->start_target);
             }
             Walk(f->end_target);
@@ -148,7 +148,7 @@
                 return;
             }
 
-            Indent() << "%fn" << GetIdForNode(b) << " = block" << std::endl;
+            Indent() << "%fn" << IdOf(b) << " = block" << std::endl;
             EmitBlockInstructions(b);
 
             if (b->branch.target->Is<FunctionTerminator>()) {
@@ -157,7 +157,7 @@
                 // Nothing to do
             } else {
                 Indent() << "branch "
-                         << "%fn" << GetIdForNode(b->branch.target);
+                         << "%fn" << IdOf(b->branch.target);
             }
             if (!b->branch.args.IsEmpty()) {
                 out_ << " ";
@@ -177,7 +177,7 @@
             Walk(b->branch.target);
         },
         [&](const ir::Switch* s) {
-            Indent() << "%fn" << GetIdForNode(s) << " = switch ";
+            Indent() << "%fn" << IdOf(s) << " = switch ";
             EmitValue(s->condition);
             out_ << " [";
             for (const auto& c : s->cases) {
@@ -196,16 +196,16 @@
                         EmitValue(selector.val);
                     }
                 }
-                out_ << ", %fn" << GetIdForNode(c.start.target) << ")";
+                out_ << ", %fn" << IdOf(c.start.target) << ")";
             }
             if (s->merge.target->IsConnected()) {
-                out_ << ", m: %fn" << GetIdForNode(s->merge.target);
+                out_ << ", m: %fn" << IdOf(s->merge.target);
             }
             out_ << "]" << std::endl;
 
             {
-                ScopedIndent switch_indent(&indent_size_);
-                ScopedStopNode scope(&stop_nodes_, s->merge.target);
+                ScopedIndent switch_indent(indent_size_);
+                ScopedStopNode scope(stop_nodes_, s->merge.target);
                 for (const auto& c : s->cases) {
                     Indent() << "# case ";
                     for (const auto& selector : c.selectors) {
@@ -230,18 +230,17 @@
             }
         },
         [&](const ir::If* i) {
-            Indent() << "%fn" << GetIdForNode(i) << " = if ";
+            Indent() << "%fn" << IdOf(i) << " = if ";
             EmitValue(i->condition);
-            out_ << " [t: %fn" << GetIdForNode(i->true_.target) << ", f: %fn"
-                 << GetIdForNode(i->false_.target);
+            out_ << " [t: %fn" << IdOf(i->true_.target) << ", f: %fn" << IdOf(i->false_.target);
             if (i->merge.target->IsConnected()) {
-                out_ << ", m: %fn" << GetIdForNode(i->merge.target);
+                out_ << ", m: %fn" << IdOf(i->merge.target);
             }
             out_ << "]" << std::endl;
 
             {
-                ScopedIndent if_indent(&indent_size_);
-                ScopedStopNode scope(&stop_nodes_, i->merge.target);
+                ScopedIndent if_indent(indent_size_);
+                ScopedStopNode scope(stop_nodes_, i->merge.target);
 
                 Indent() << "# true branch" << std::endl;
                 Walk(i->true_.target);
@@ -258,22 +257,21 @@
             }
         },
         [&](const ir::Loop* l) {
-            Indent() << "%fn" << GetIdForNode(l) << " = loop [s: %fn"
-                     << GetIdForNode(l->start.target);
+            Indent() << "%fn" << IdOf(l) << " = loop [s: %fn" << IdOf(l->start.target);
 
             if (l->continuing.target->IsConnected()) {
-                out_ << ", c: %fn" << GetIdForNode(l->continuing.target);
+                out_ << ", c: %fn" << IdOf(l->continuing.target);
             }
             if (l->merge.target->IsConnected()) {
-                out_ << ", m: %fn" << GetIdForNode(l->merge.target);
+                out_ << ", m: %fn" << IdOf(l->merge.target);
             }
             out_ << "]" << std::endl;
 
             {
-                ScopedStopNode loop_scope(&stop_nodes_, l->merge.target);
-                ScopedIndent loop_indent(&indent_size_);
+                ScopedStopNode loop_scope(stop_nodes_, l->merge.target);
+                ScopedIndent loop_indent(indent_size_);
                 {
-                    ScopedStopNode inner_scope(&stop_nodes_, l->continuing.target);
+                    ScopedStopNode inner_scope(stop_nodes_, l->continuing.target);
                     Indent() << "# loop start" << std::endl;
                     Walk(l->start.target);
                 }
@@ -355,11 +353,7 @@
             emit(constant->value);
         },
         [&](const ir::Instruction* i) {
-            if (i->id == ir::Instruction::kNoID) {
-                out_ << "<no-id>";
-            } else {
-                out_ << "%" << i->id;
-            }
+            out_ << "%" << IdOf(i);
             if (i->Type() != nullptr) {
                 out_ << ":" << i->Type()->FriendlyName();
             }
diff --git a/src/tint/ir/disassembler.h b/src/tint/ir/disassembler.h
index d9182d3..2438d80 100644
--- a/src/tint/ir/disassembler.h
+++ b/src/tint/ir/disassembler.h
@@ -16,14 +16,14 @@
 #define SRC_TINT_IR_DISASSEMBLER_H_
 
 #include <string>
-#include <unordered_map>
-#include <unordered_set>
 
 #include "src/tint/ir/binary.h"
 #include "src/tint/ir/call.h"
 #include "src/tint/ir/flow_node.h"
 #include "src/tint/ir/module.h"
 #include "src/tint/ir/unary.h"
+#include "src/tint/utils/hashmap.h"
+#include "src/tint/utils/hashset.h"
 #include "src/tint/utils/string_stream.h"
 
 namespace tint::ir {
@@ -49,7 +49,9 @@
 
   private:
     utils::StringStream& Indent();
-    size_t GetIdForNode(const FlowNode* node);
+
+    size_t IdOf(const FlowNode* node);
+    std::string_view IdOf(const Value* node);
 
     void Walk(const FlowNode* node);
     void EmitInstruction(const Instruction* inst);
@@ -60,10 +62,10 @@
 
     const Module& mod_;
     utils::StringStream out_;
-    std::unordered_set<const FlowNode*> visited_;
-    std::unordered_set<const FlowNode*> stop_nodes_;
-    std::unordered_map<const FlowNode*, size_t> flow_node_to_id_;
-    size_t next_node_id_ = 0;
+    utils::Hashset<const FlowNode*, 32> visited_;
+    utils::Hashset<const FlowNode*, 32> stop_nodes_;
+    utils::Hashmap<const FlowNode*, size_t, 32> flow_node_ids_;
+    utils::Hashmap<const Value*, std::string, 32> value_ids_;
     uint32_t indent_size_ = 0;
     bool in_function_ = false;
 };
diff --git a/src/tint/ir/discard.cc b/src/tint/ir/discard.cc
index 6c7ded7..feebb07 100644
--- a/src/tint/ir/discard.cc
+++ b/src/tint/ir/discard.cc
@@ -19,7 +19,7 @@
 
 namespace tint::ir {
 
-Discard::Discard() : Base(kNoID, nullptr, utils::Empty) {}
+Discard::Discard() : Base(nullptr, utils::Empty) {}
 
 Discard::~Discard() = default;
 
diff --git a/src/tint/ir/instruction.cc b/src/tint/ir/instruction.cc
index 0cf4966..bbd4992 100644
--- a/src/tint/ir/instruction.cc
+++ b/src/tint/ir/instruction.cc
@@ -20,7 +20,7 @@
 
 Instruction::Instruction() = default;
 
-Instruction::Instruction(uint32_t identifier, const type::Type* ty) : id(identifier), type(ty) {}
+Instruction::Instruction(const type::Type* ty) : type(ty) {}
 
 Instruction::~Instruction() = default;
 
diff --git a/src/tint/ir/instruction.h b/src/tint/ir/instruction.h
index 1d4cafb..1bfde50 100644
--- a/src/tint/ir/instruction.h
+++ b/src/tint/ir/instruction.h
@@ -23,9 +23,6 @@
 /// An instruction in the IR.
 class Instruction : public utils::Castable<Instruction, Value> {
   public:
-    /// The identifier used by instructions that have no value.
-    static constexpr uint32_t kNoID = 0;
-
     Instruction(const Instruction& inst) = delete;
     Instruction(Instruction&& inst) = delete;
     /// Destructor
@@ -37,9 +34,6 @@
     /// @returns the type of the value
     const type::Type* Type() const override { return type; }
 
-    /// The instruction identifier
-    const uint32_t id = kNoID;
-
     /// The instruction type
     const type::Type* type = nullptr;
 
@@ -47,9 +41,8 @@
     /// Constructor
     Instruction();
     /// Constructor
-    /// @param id the instruction id
     /// @param type the result type
-    Instruction(uint32_t id, const type::Type* type);
+    explicit Instruction(const type::Type* type);
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/unary.cc b/src/tint/ir/unary.cc
index e3f20eb..2fd7e57 100644
--- a/src/tint/ir/unary.cc
+++ b/src/tint/ir/unary.cc
@@ -19,8 +19,7 @@
 
 namespace tint::ir {
 
-Unary::Unary(uint32_t identifier, Kind kind, const type::Type* ty, Value* val)
-    : Base(identifier, ty), kind_(kind), val_(val) {
+Unary::Unary(Kind kind, const type::Type* ty, Value* val) : Base(ty), kind_(kind), val_(val) {
     TINT_ASSERT(IR, val_);
     val_->AddUsage(this);
 }
diff --git a/src/tint/ir/unary.h b/src/tint/ir/unary.h
index 64301d4..8eb0bb4 100644
--- a/src/tint/ir/unary.h
+++ b/src/tint/ir/unary.h
@@ -32,11 +32,10 @@
     };
 
     /// Constructor
-    /// @param id the instruction id
     /// @param kind the kind of unary instruction
     /// @param type the result type
     /// @param val the lhs of the instruction
-    Unary(uint32_t id, Kind kind, const type::Type* type, Value* val);
+    Unary(Kind kind, const type::Type* type, Value* val);
     Unary(const Unary& inst) = delete;
     Unary(Unary&& inst) = delete;
     ~Unary() override;
diff --git a/src/tint/ir/user_call.cc b/src/tint/ir/user_call.cc
index e7e2e26..f718284 100644
--- a/src/tint/ir/user_call.cc
+++ b/src/tint/ir/user_call.cc
@@ -22,11 +22,8 @@
 
 namespace tint::ir {
 
-UserCall::UserCall(uint32_t identifier,
-                   const type::Type* ty,
-                   Symbol n,
-                   utils::VectorRef<Value*> arguments)
-    : Base(identifier, ty, std::move(arguments)), name(n) {}
+UserCall::UserCall(const type::Type* ty, Symbol n, utils::VectorRef<Value*> arguments)
+    : Base(ty, std::move(arguments)), name(n) {}
 
 UserCall::~UserCall() = default;
 
diff --git a/src/tint/ir/user_call.h b/src/tint/ir/user_call.h
index 1edfa8b..5ada8f8 100644
--- a/src/tint/ir/user_call.h
+++ b/src/tint/ir/user_call.h
@@ -25,11 +25,10 @@
 class UserCall : public utils::Castable<UserCall, Call> {
   public:
     /// Constructor
-    /// @param id the instruction id
     /// @param type the result type
     /// @param name the function name
     /// @param args the function arguments
-    UserCall(uint32_t id, const type::Type* type, Symbol name, utils::VectorRef<Value*> args);
+    UserCall(const type::Type* type, Symbol name, utils::VectorRef<Value*> args);
     UserCall(const UserCall& inst) = delete;
     UserCall(UserCall&& inst) = delete;
     ~UserCall() override;
diff --git a/src/tint/ir/var.cc b/src/tint/ir/var.cc
index b7ede1d..3ab97e0 100644
--- a/src/tint/ir/var.cc
+++ b/src/tint/ir/var.cc
@@ -19,11 +19,8 @@
 
 namespace tint::ir {
 
-Var::Var(uint32_t identifier,
-         const type::Type* ty,
-         builtin::AddressSpace addr_space,
-         builtin::Access acc)
-    : Base(identifier, ty), address_space(addr_space), access(acc) {}
+Var::Var(const type::Type* ty, builtin::AddressSpace addr_space, builtin::Access acc)
+    : Base(ty), address_space(addr_space), access(acc) {}
 
 Var::~Var() = default;
 
diff --git a/src/tint/ir/var.h b/src/tint/ir/var.h
index dd46f54..1387c60 100644
--- a/src/tint/ir/var.h
+++ b/src/tint/ir/var.h
@@ -26,14 +26,10 @@
 class Var : public utils::Castable<Var, Instruction> {
   public:
     /// Constructor
-    /// @param id the instruction id
     /// @param type the type
     /// @param address_space the address space of the var
     /// @param access the access mode of the var
-    Var(uint32_t id,
-        const type::Type* type,
-        builtin::AddressSpace address_space,
-        builtin::Access access);
+    Var(const type::Type* type, builtin::AddressSpace address_space, builtin::Access access);
     Var(const Var& inst) = delete;
     Var(Var&& inst) = delete;
     ~Var() override;
diff --git a/src/tint/reader/spirv/function.cc b/src/tint/reader/spirv/function.cc
index c3c12f2..6c1c9f0 100644
--- a/src/tint/reader/spirv/function.cc
+++ b/src/tint/reader/spirv/function.cc
@@ -4047,8 +4047,8 @@
                     builder_.MemberAccessor(
                         builder_.Call("refract",
                                       utils::Vector{
-                                          builder_.vec2<tint::f32>(incident.expr, 0_f),
-                                          builder_.vec2<tint::f32>(normal.expr, 0_f),
+                                          builder_.Call("vec2f", incident.expr, 0_f),
+                                          builder_.Call("vec2f", normal.expr, 0_f),
                                           eta.expr,
                                       }),
                         "x"),
@@ -4136,9 +4136,9 @@
             auto* d = idx(1, 1);
 
             // s * d, -s * b, -s * c, s * a
-            auto* r = pb.mat2x2<f32>(  //
-                pb.vec2<f32>(pb.Mul(s, d), pb.Mul(neg(s), b)),
-                pb.vec2<f32>(pb.Mul(neg(s), c), pb.Mul(s, a)));
+            auto* r = pb.Call("mat2x2f",  //
+                              pb.Call("vec2f", pb.Mul(s, d), pb.Mul(neg(s), b)),
+                              pb.Call("vec2f", pb.Mul(neg(s), c), pb.Mul(s, a)));
             return {mat.type, r};
         }
 
@@ -4156,29 +4156,29 @@
             auto* h = idx(2, 1);
             auto* i = idx(2, 2);
 
-            auto r = pb.Mul(s,               //
-                            pb.mat3x3<f32>(  //
-                                pb.vec3<f32>(
-                                    // e * i - f * h
-                                    sub_mul2(e, i, f, h),
-                                    // c * h - b * i
-                                    sub_mul2(c, h, b, i),
-                                    // b * f - c * e
-                                    sub_mul2(b, f, c, e)),
-                                pb.vec3<f32>(
-                                    // f * g - d * i
-                                    sub_mul2(f, g, d, i),
-                                    // a * i - c * g
-                                    sub_mul2(a, i, c, g),
-                                    // c * d - a * f
-                                    sub_mul2(c, d, a, f)),
-                                pb.vec3<f32>(
-                                    // d * h - e * g
-                                    sub_mul2(d, h, e, g),
-                                    // b * g - a * h
-                                    sub_mul2(b, g, a, h),
-                                    // a * e - b * d
-                                    sub_mul2(a, e, b, d))));
+            auto r = pb.Mul(s,                  //
+                            pb.Call("mat3x3f",  //
+                                    pb.Call("vec3f",
+                                            // e * i - f * h
+                                            sub_mul2(e, i, f, h),
+                                            // c * h - b * i
+                                            sub_mul2(c, h, b, i),
+                                            // b * f - c * e
+                                            sub_mul2(b, f, c, e)),
+                                    pb.Call("vec3f",
+                                            // f * g - d * i
+                                            sub_mul2(f, g, d, i),
+                                            // a * i - c * g
+                                            sub_mul2(a, i, c, g),
+                                            // c * d - a * f
+                                            sub_mul2(c, d, a, f)),
+                                    pb.Call("vec3f",
+                                            // d * h - e * g
+                                            sub_mul2(d, h, e, g),
+                                            // b * g - a * h
+                                            sub_mul2(b, g, a, h),
+                                            // a * e - b * d
+                                            sub_mul2(a, e, b, d))));
             return {mat.type, r};
         }
 
@@ -4234,44 +4234,44 @@
             auto* enfm = sub_mul2(e, n, f, m);
             auto* ejfi = sub_mul2(e, j, f, i);
 
-            auto r = pb.Mul(s,               //
-                            pb.mat4x4<f32>(  //
-                                pb.vec4<f32>(
-                                    // f * kplo - g * jpln + h * jokn
-                                    sub_add_mul3(f, kplo, g, jpln, h, jokn),
-                                    // -b * kplo + c * jpln - d * jokn
-                                    add_sub_mul3(neg(b), kplo, c, jpln, d, jokn),
-                                    // b * gpho - c * fphn + d * fogn
-                                    sub_add_mul3(b, gpho, c, fphn, d, fogn),
-                                    // -b * glhk + c * flhj - d * fkgj
-                                    add_sub_mul3(neg(b), glhk, c, flhj, d, fkgj)),
-                                pb.vec4<f32>(
-                                    // -e * kplo + g * iplm - h * iokm
-                                    add_sub_mul3(neg(e), kplo, g, iplm, h, iokm),
-                                    // a * kplo - c * iplm + d * iokm
-                                    sub_add_mul3(a, kplo, c, iplm, d, iokm),
-                                    // -a * gpho + c * ephm - d * eogm
-                                    add_sub_mul3(neg(a), gpho, c, ephm, d, eogm),
-                                    // a * glhk - c * elhi + d * ekgi
-                                    sub_add_mul3(a, glhk, c, elhi, d, ekgi)),
-                                pb.vec4<f32>(
-                                    // e * jpln - f * iplm + h * injm
-                                    sub_add_mul3(e, jpln, f, iplm, h, injm),
-                                    // -a * jpln + b * iplm - d * injm
-                                    add_sub_mul3(neg(a), jpln, b, iplm, d, injm),
-                                    // a * fphn - b * ephm + d * enfm
-                                    sub_add_mul3(a, fphn, b, ephm, d, enfm),
-                                    // -a * flhj + b * elhi - d * ejfi
-                                    add_sub_mul3(neg(a), flhj, b, elhi, d, ejfi)),
-                                pb.vec4<f32>(
-                                    // -e * jokn + f * iokm - g * injm
-                                    add_sub_mul3(neg(e), jokn, f, iokm, g, injm),
-                                    // a * jokn - b * iokm + c * injm
-                                    sub_add_mul3(a, jokn, b, iokm, c, injm),
-                                    // -a * fogn + b * eogm - c * enfm
-                                    add_sub_mul3(neg(a), fogn, b, eogm, c, enfm),
-                                    // a * fkgj - b * ekgi + c * ejfi
-                                    sub_add_mul3(a, fkgj, b, ekgi, c, ejfi))));
+            auto r = pb.Mul(s,                  //
+                            pb.Call("mat4x4f",  //
+                                    pb.Call("vec4f",
+                                            // f * kplo - g * jpln + h * jokn
+                                            sub_add_mul3(f, kplo, g, jpln, h, jokn),
+                                            // -b * kplo + c * jpln - d * jokn
+                                            add_sub_mul3(neg(b), kplo, c, jpln, d, jokn),
+                                            // b * gpho - c * fphn + d * fogn
+                                            sub_add_mul3(b, gpho, c, fphn, d, fogn),
+                                            // -b * glhk + c * flhj - d * fkgj
+                                            add_sub_mul3(neg(b), glhk, c, flhj, d, fkgj)),
+                                    pb.Call("vec4f",
+                                            // -e * kplo + g * iplm - h * iokm
+                                            add_sub_mul3(neg(e), kplo, g, iplm, h, iokm),
+                                            // a * kplo - c * iplm + d * iokm
+                                            sub_add_mul3(a, kplo, c, iplm, d, iokm),
+                                            // -a * gpho + c * ephm - d * eogm
+                                            add_sub_mul3(neg(a), gpho, c, ephm, d, eogm),
+                                            // a * glhk - c * elhi + d * ekgi
+                                            sub_add_mul3(a, glhk, c, elhi, d, ekgi)),
+                                    pb.Call("vec4f",
+                                            // e * jpln - f * iplm + h * injm
+                                            sub_add_mul3(e, jpln, f, iplm, h, injm),
+                                            // -a * jpln + b * iplm - d * injm
+                                            add_sub_mul3(neg(a), jpln, b, iplm, d, injm),
+                                            // a * fphn - b * ephm + d * enfm
+                                            sub_add_mul3(a, fphn, b, ephm, d, enfm),
+                                            // -a * flhj + b * elhi - d * ejfi
+                                            add_sub_mul3(neg(a), flhj, b, elhi, d, ejfi)),
+                                    pb.Call("vec4f",
+                                            // -e * jokn + f * iokm - g * injm
+                                            add_sub_mul3(neg(e), jokn, f, iokm, g, injm),
+                                            // a * jokn - b * iokm + c * injm
+                                            sub_add_mul3(a, jokn, b, iokm, c, injm),
+                                            // -a * fogn + b * eogm - c * enfm
+                                            add_sub_mul3(neg(a), fogn, b, eogm, c, enfm),
+                                            // a * fkgj - b * ekgi + c * ejfi
+                                            sub_add_mul3(a, fkgj, b, ekgi, c, ejfi))));
             return {mat.type, r};
         }
     }
diff --git a/src/tint/reader/spirv/function_arithmetic_test.cc b/src/tint/reader/spirv/function_arithmetic_test.cc
index 1f14633..538e410 100644
--- a/src/tint/reader/spirv/function_arithmetic_test.cc
+++ b/src/tint/reader/spirv/function_arithmetic_test.cc
@@ -81,22 +81,22 @@
 // Returns the AST dump for a given SPIR-V assembly constant.
 std::string AstFor(std::string assembly) {
     if (assembly == "v2uint_10_20") {
-        return "vec2<u32>(10u, 20u)";
+        return "vec2u(10u, 20u)";
     }
     if (assembly == "v2uint_20_10") {
-        return "vec2<u32>(20u, 10u)";
+        return "vec2u(20u, 10u)";
     }
     if (assembly == "v2int_30_40") {
-        return "vec2<i32>(30i, 40i)";
+        return "vec2i(30i, 40i)";
     }
     if (assembly == "v2int_40_30") {
-        return "vec2<i32>(40i, 30i)";
+        return "vec2i(40i, 30i)";
     }
     if (assembly == "cast_int_v2uint_10_20") {
-        return "bitcast<vec2<i32>>(vec2<u32>(10u, 20u))";
+        return "bitcast<vec2i>(vec2u(10u, 20u))";
     }
     if (assembly == "cast_uint_v2int_40_30") {
-        return "bitcast<vec2<u32>>(vec2<i32>(40i, 30i))";
+        return "bitcast<vec2u>(vec2i(40i, 30i))";
     }
     if (assembly == "v2float_50_60") {
         return "v2float_50_60";
@@ -190,7 +190,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : vec2<i32> = -(vec2<i32>(30i, 40i));"));
+                HasSubstr("let x_1 : vec2i = -(vec2i(30i, 40i));"));
 }
 
 TEST_F(SpvUnaryArithTest, SNegate_SignedVec_UnsignedVec) {
@@ -207,7 +207,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : vec2<i32> = -(bitcast<vec2<i32>>(vec2<u32>(10u, 20u)));"));
+                HasSubstr("let x_1 : vec2i = -(bitcast<vec2i>(vec2u(10u, 20u)));"));
 }
 
 TEST_F(SpvUnaryArithTest, SNegate_UnsignedVec_SignedVec) {
@@ -224,7 +224,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : vec2<u32> = bitcast<vec2<u32>>(-(vec2<i32>(30i, 40i)));"));
+                HasSubstr("let x_1 : vec2u = bitcast<vec2u>(-(vec2i(30i, 40i)));"));
 }
 
 TEST_F(SpvUnaryArithTest, SNegate_UnsignedVec_UnsignedVec) {
@@ -242,8 +242,7 @@
     auto ast_body = fe.ast_body();
     EXPECT_THAT(
         test::ToString(p->program(), ast_body),
-        HasSubstr(
-            R"(let x_1 : vec2<u32> = bitcast<vec2<u32>>(-(bitcast<vec2<i32>>(vec2<u32>(10u, 20u))));)"));
+        HasSubstr(R"(let x_1 : vec2u = bitcast<vec2u>(-(bitcast<vec2i>(vec2u(10u, 20u))));)"));
 }
 
 TEST_F(SpvUnaryArithTest, FNegate_Scalar) {
@@ -276,7 +275,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : vec2<f32> = -(v2float_50_60);"));
+                HasSubstr("let x_1 : vec2f = -(v2float_50_60);"));
 }
 
 struct BinaryData {
@@ -366,11 +365,11 @@
         // Both uint
         BinaryData{"uint", "uint_10", "OpIAdd", "uint_20", "u32", "10u", "+", "20u"},  // Both int
         BinaryData{"int", "int_30", "OpIAdd", "int_40", "i32", "30i", "+", "40i"},  // Both v2uint
-        BinaryData{"v2uint", "v2uint_10_20", "OpIAdd", "v2uint_20_10", "vec2<u32>",
+        BinaryData{"v2uint", "v2uint_10_20", "OpIAdd", "v2uint_20_10", "vec2u",
                    AstFor("v2uint_10_20"), "+", AstFor("v2uint_20_10")},
         // Both v2int
-        BinaryData{"v2int", "v2int_30_40", "OpIAdd", "v2int_40_30", "vec2<i32>",
-                   AstFor("v2int_30_40"), "+", AstFor("v2int_40_30")}));
+        BinaryData{"v2int", "v2int_30_40", "OpIAdd", "v2int_40_30", "vec2i", AstFor("v2int_30_40"),
+                   "+", AstFor("v2int_40_30")}));
 
 INSTANTIATE_TEST_SUITE_P(
     SpvParserTest_IAdd_MixedSignedness,
@@ -388,13 +387,12 @@
         BinaryDataGeneral{"int", "uint_20", "OpIAdd", "uint_10", "i32",
                           "bitcast<i32>((20u + 10u))"},
         // Mixed, returning v2uint
-        BinaryDataGeneral{
-            "v2uint", "v2int_30_40", "OpIAdd", "v2uint_10_20", "vec2<u32>",
-            R"(bitcast<vec2<u32>>((vec2<i32>(30i, 40i) + bitcast<vec2<i32>>(vec2<u32>(10u, 20u)))))"},
+        BinaryDataGeneral{"v2uint", "v2int_30_40", "OpIAdd", "v2uint_10_20", "vec2u",
+                          R"(bitcast<vec2u>((vec2i(30i, 40i) + bitcast<vec2i>(vec2u(10u, 20u)))))"},
         // Mixed, returning v2int
         BinaryDataGeneral{
-            "v2int", "v2uint_10_20", "OpIAdd", "v2int_40_30", "vec2<i32>",
-            R"(bitcast<vec2<i32>>((vec2<u32>(10u, 20u) + bitcast<vec2<u32>>(vec2<i32>(40i, 30i)))))"}));
+            "v2int", "v2uint_10_20", "OpIAdd", "v2int_40_30", "vec2i",
+            R"(bitcast<vec2i>((vec2u(10u, 20u) + bitcast<vec2u>(vec2i(40i, 30i)))))"}));
 
 INSTANTIATE_TEST_SUITE_P(SpvParserTest_FAdd,
                          SpvBinaryArithTest,
@@ -403,7 +401,7 @@
                              BinaryData{"float", "float_50", "OpFAdd", "float_60", "f32", "50.0f",
                                         "+", "60.0f"},  // Vector float
                              BinaryData{"v2float", "v2float_50_60", "OpFAdd", "v2float_60_50",
-                                        "vec2<f32>", AstFor("v2float_50_60"), "+",
+                                        "vec2f", AstFor("v2float_50_60"), "+",
                                         AstFor("v2float_60_50")}));
 
 INSTANTIATE_TEST_SUITE_P(
@@ -413,11 +411,11 @@
         // Both uint
         BinaryData{"uint", "uint_10", "OpISub", "uint_20", "u32", "10u", "-", "20u"},  // Both int
         BinaryData{"int", "int_30", "OpISub", "int_40", "i32", "30i", "-", "40i"},  // Both v2uint
-        BinaryData{"v2uint", "v2uint_10_20", "OpISub", "v2uint_20_10", "vec2<u32>",
+        BinaryData{"v2uint", "v2uint_10_20", "OpISub", "v2uint_20_10", "vec2u",
                    AstFor("v2uint_10_20"), "-", AstFor("v2uint_20_10")},
         // Both v2int
-        BinaryData{"v2int", "v2int_30_40", "OpISub", "v2int_40_30", "vec2<i32>",
-                   AstFor("v2int_30_40"), "-", AstFor("v2int_40_30")}));
+        BinaryData{"v2int", "v2int_30_40", "OpISub", "v2int_40_30", "vec2i", AstFor("v2int_30_40"),
+                   "-", AstFor("v2int_40_30")}));
 
 INSTANTIATE_TEST_SUITE_P(
     SpvParserTest_ISub_MixedSignedness,
@@ -435,13 +433,12 @@
         BinaryDataGeneral{"int", "uint_20", "OpISub", "uint_10", "i32",
                           "bitcast<i32>((20u - 10u))"},
         // Mixed, returning v2uint
-        BinaryDataGeneral{
-            "v2uint", "v2int_30_40", "OpISub", "v2uint_10_20", "vec2<u32>",
-            R"(bitcast<vec2<u32>>((vec2<i32>(30i, 40i) - bitcast<vec2<i32>>(vec2<u32>(10u, 20u)))))"},
+        BinaryDataGeneral{"v2uint", "v2int_30_40", "OpISub", "v2uint_10_20", "vec2u",
+                          R"(bitcast<vec2u>((vec2i(30i, 40i) - bitcast<vec2i>(vec2u(10u, 20u)))))"},
         // Mixed, returning v2int
         BinaryDataGeneral{
-            "v2int", "v2uint_10_20", "OpISub", "v2int_40_30", "vec2<i32>",
-            R"(bitcast<vec2<i32>>((vec2<u32>(10u, 20u) - bitcast<vec2<u32>>(vec2<i32>(40i, 30i)))))"}));
+            "v2int", "v2uint_10_20", "OpISub", "v2int_40_30", "vec2i",
+            R"(bitcast<vec2i>((vec2u(10u, 20u) - bitcast<vec2u>(vec2i(40i, 30i)))))"}));
 
 INSTANTIATE_TEST_SUITE_P(SpvParserTest_FSub,
                          SpvBinaryArithTest,
@@ -450,7 +447,7 @@
                              BinaryData{"float", "float_50", "OpFSub", "float_60", "f32", "50.0f",
                                         "-", "60.0f"},  // Vector float
                              BinaryData{"v2float", "v2float_50_60", "OpFSub", "v2float_60_50",
-                                        "vec2<f32>", AstFor("v2float_50_60"), "-",
+                                        "vec2f", AstFor("v2float_50_60"), "-",
                                         AstFor("v2float_60_50")}));
 
 INSTANTIATE_TEST_SUITE_P(
@@ -460,11 +457,11 @@
         // Both uint
         BinaryData{"uint", "uint_10", "OpIMul", "uint_20", "u32", "10u", "*", "20u"},  // Both int
         BinaryData{"int", "int_30", "OpIMul", "int_40", "i32", "30i", "*", "40i"},  // Both v2uint
-        BinaryData{"v2uint", "v2uint_10_20", "OpIMul", "v2uint_20_10", "vec2<u32>",
+        BinaryData{"v2uint", "v2uint_10_20", "OpIMul", "v2uint_20_10", "vec2u",
                    AstFor("v2uint_10_20"), "*", AstFor("v2uint_20_10")},
         // Both v2int
-        BinaryData{"v2int", "v2int_30_40", "OpIMul", "v2int_40_30", "vec2<i32>",
-                   AstFor("v2int_30_40"), "*", AstFor("v2int_40_30")}));
+        BinaryData{"v2int", "v2int_30_40", "OpIMul", "v2int_40_30", "vec2i", AstFor("v2int_30_40"),
+                   "*", AstFor("v2int_40_30")}));
 
 INSTANTIATE_TEST_SUITE_P(
     SpvParserTest_IMul_MixedSignedness,
@@ -482,13 +479,12 @@
         BinaryDataGeneral{"int", "uint_20", "OpIMul", "uint_10", "i32",
                           "bitcast<i32>((20u * 10u))"},
         // Mixed, returning v2uint
-        BinaryDataGeneral{
-            "v2uint", "v2int_30_40", "OpIMul", "v2uint_10_20", "vec2<u32>",
-            R"(bitcast<vec2<u32>>((vec2<i32>(30i, 40i) * bitcast<vec2<i32>>(vec2<u32>(10u, 20u)))))"},
+        BinaryDataGeneral{"v2uint", "v2int_30_40", "OpIMul", "v2uint_10_20", "vec2u",
+                          R"(bitcast<vec2u>((vec2i(30i, 40i) * bitcast<vec2i>(vec2u(10u, 20u)))))"},
         // Mixed, returning v2int
         BinaryDataGeneral{
-            "v2int", "v2uint_10_20", "OpIMul", "v2int_40_30", "vec2<i32>",
-            R"(bitcast<vec2<i32>>((vec2<u32>(10u, 20u) * bitcast<vec2<u32>>(vec2<i32>(40i, 30i)))))"}));
+            "v2int", "v2uint_10_20", "OpIMul", "v2int_40_30", "vec2i",
+            R"(bitcast<vec2i>((vec2u(10u, 20u) * bitcast<vec2u>(vec2i(40i, 30i)))))"}));
 
 INSTANTIATE_TEST_SUITE_P(SpvParserTest_FMul,
                          SpvBinaryArithTest,
@@ -497,7 +493,7 @@
                              BinaryData{"float", "float_50", "OpFMul", "float_60", "f32", "50.0f",
                                         "*", "60.0f"},  // Vector float
                              BinaryData{"v2float", "v2float_50_60", "OpFMul", "v2float_60_50",
-                                        "vec2<f32>", AstFor("v2float_50_60"), "*",
+                                        "vec2f", AstFor("v2float_50_60"), "*",
                                         AstFor("v2float_60_50")}));
 
 INSTANTIATE_TEST_SUITE_P(SpvParserTest_UDiv,
@@ -506,18 +502,17 @@
                              // Both uint
                              BinaryData{"uint", "uint_10", "OpUDiv", "uint_20", "u32", "10u", "/",
                                         "20u"},  // Both v2uint
-                             BinaryData{"v2uint", "v2uint_10_20", "OpUDiv", "v2uint_20_10",
-                                        "vec2<u32>", AstFor("v2uint_10_20"), "/",
-                                        AstFor("v2uint_20_10")}));
+                             BinaryData{"v2uint", "v2uint_10_20", "OpUDiv", "v2uint_20_10", "vec2u",
+                                        AstFor("v2uint_10_20"), "/", AstFor("v2uint_20_10")}));
 
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_SDiv,
-    SpvBinaryArithTest,
-    ::testing::Values(
-        // Both int
-        BinaryData{"int", "int_30", "OpSDiv", "int_40", "i32", "30i", "/", "40i"},  // Both v2int
-        BinaryData{"v2int", "v2int_30_40", "OpSDiv", "v2int_40_30", "vec2<i32>",
-                   AstFor("v2int_30_40"), "/", AstFor("v2int_40_30")}));
+INSTANTIATE_TEST_SUITE_P(SpvParserTest_SDiv,
+                         SpvBinaryArithTest,
+                         ::testing::Values(
+                             // Both int
+                             BinaryData{"int", "int_30", "OpSDiv", "int_40", "i32", "30i", "/",
+                                        "40i"},  // Both v2int
+                             BinaryData{"v2int", "v2int_30_40", "OpSDiv", "v2int_40_30", "vec2i",
+                                        AstFor("v2int_30_40"), "/", AstFor("v2int_40_30")}));
 
 INSTANTIATE_TEST_SUITE_P(
     SpvParserTest_SDiv_MixedSignednessOperands,
@@ -528,11 +523,11 @@
         // Mixed, returning int, first arg uint
         BinaryData{"int", "uint_10", "OpSDiv", "int_30", "i32", "bitcast<i32>(10u)", "/",
                    "30i"},  // Mixed, returning v2int, first arg v2uint
-        BinaryData{"v2int", "v2uint_10_20", "OpSDiv", "v2int_30_40", "vec2<i32>",
+        BinaryData{"v2int", "v2uint_10_20", "OpSDiv", "v2int_30_40", "vec2i",
                    AstFor("cast_int_v2uint_10_20"), "/", AstFor("v2int_30_40")},
         // Mixed, returning v2int, second arg v2uint
-        BinaryData{"v2int", "v2int_30_40", "OpSDiv", "v2uint_10_20", "vec2<i32>",
-                   AstFor("v2int_30_40"), "/", AstFor("cast_int_v2uint_10_20")}));
+        BinaryData{"v2int", "v2int_30_40", "OpSDiv", "v2uint_10_20", "vec2i", AstFor("v2int_30_40"),
+                   "/", AstFor("cast_int_v2uint_10_20")}));
 
 TEST_F(SpvBinaryArithTestBasic, SDiv_Scalar_UnsignedResult) {
     // The WGSL signed division operator expects both operands to be signed
@@ -574,8 +569,7 @@
     auto ast_body = fe.ast_body();
     EXPECT_THAT(
         test::ToString(p->program(), ast_body),
-        HasSubstr(
-            R"(let x_1 : vec2<u32> = bitcast<vec2<u32>>((vec2<i32>(30i, 40i) / vec2<i32>(40i, 30i)));)"));
+        HasSubstr(R"(let x_1 : vec2u = bitcast<vec2u>((vec2i(30i, 40i) / vec2i(40i, 30i)));)"));
 }
 
 INSTANTIATE_TEST_SUITE_P(SpvParserTest_FDiv,
@@ -585,7 +579,7 @@
                              BinaryData{"float", "float_50", "OpFDiv", "float_60", "f32", "50.0f",
                                         "/", "60.0f"},  // Vector float
                              BinaryData{"v2float", "v2float_50_60", "OpFDiv", "v2float_60_50",
-                                        "vec2<f32>", AstFor("v2float_50_60"), "/",
+                                        "vec2f", AstFor("v2float_50_60"), "/",
                                         AstFor("v2float_60_50")}));
 
 INSTANTIATE_TEST_SUITE_P(SpvParserTest_UMod,
@@ -594,21 +588,20 @@
                              // Both uint
                              BinaryData{"uint", "uint_10", "OpUMod", "uint_20", "u32", "10u", "%",
                                         "20u"},  // Both v2uint
-                             BinaryData{"v2uint", "v2uint_10_20", "OpUMod", "v2uint_20_10",
-                                        "vec2<u32>", AstFor("v2uint_10_20"), "%",
-                                        AstFor("v2uint_20_10")}));
+                             BinaryData{"v2uint", "v2uint_10_20", "OpUMod", "v2uint_20_10", "vec2u",
+                                        AstFor("v2uint_10_20"), "%", AstFor("v2uint_20_10")}));
 
 // Currently WGSL is missing a mapping for OpSRem
 // https://github.com/gpuweb/gpuweb/issues/702
 
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_SMod,
-    SpvBinaryArithTest,
-    ::testing::Values(
-        // Both int
-        BinaryData{"int", "int_30", "OpSMod", "int_40", "i32", "30i", "%", "40i"},  // Both v2int
-        BinaryData{"v2int", "v2int_30_40", "OpSMod", "v2int_40_30", "vec2<i32>",
-                   AstFor("v2int_30_40"), "%", AstFor("v2int_40_30")}));
+INSTANTIATE_TEST_SUITE_P(SpvParserTest_SMod,
+                         SpvBinaryArithTest,
+                         ::testing::Values(
+                             // Both int
+                             BinaryData{"int", "int_30", "OpSMod", "int_40", "i32", "30i", "%",
+                                        "40i"},  // Both v2int
+                             BinaryData{"v2int", "v2int_30_40", "OpSMod", "v2int_40_30", "vec2i",
+                                        AstFor("v2int_30_40"), "%", AstFor("v2int_40_30")}));
 
 INSTANTIATE_TEST_SUITE_P(
     SpvParserTest_SMod_MixedSignednessOperands,
@@ -619,11 +612,11 @@
         // Mixed, returning int, first arg uint
         BinaryData{"int", "uint_10", "OpSMod", "int_30", "i32", "bitcast<i32>(10u)", "%",
                    "30i"},  // Mixed, returning v2int, first arg v2uint
-        BinaryData{"v2int", "v2uint_10_20", "OpSMod", "v2int_30_40", "vec2<i32>",
+        BinaryData{"v2int", "v2uint_10_20", "OpSMod", "v2int_30_40", "vec2i",
                    AstFor("cast_int_v2uint_10_20"), "%", AstFor("v2int_30_40")},
         // Mixed, returning v2int, second arg v2uint
-        BinaryData{"v2int", "v2int_30_40", "OpSMod", "v2uint_10_20", "vec2<i32>",
-                   AstFor("v2int_30_40"), "%", AstFor("cast_int_v2uint_10_20")}));
+        BinaryData{"v2int", "v2int_30_40", "OpSMod", "v2uint_10_20", "vec2i", AstFor("v2int_30_40"),
+                   "%", AstFor("cast_int_v2uint_10_20")}));
 
 TEST_F(SpvBinaryArithTestBasic, SMod_Scalar_UnsignedResult) {
     // The WGSL signed modulus operator expects both operands to be signed
@@ -665,8 +658,7 @@
     auto ast_body = fe.ast_body();
     EXPECT_THAT(
         test::ToString(p->program(), ast_body),
-        HasSubstr(
-            R"(let x_1 : vec2<u32> = bitcast<vec2<u32>>((vec2<i32>(30i, 40i) % vec2<i32>(40i, 30i)));)"));
+        HasSubstr(R"(let x_1 : vec2u = bitcast<vec2u>((vec2i(30i, 40i) % vec2i(40i, 30i)));)"));
 }
 
 INSTANTIATE_TEST_SUITE_P(SpvParserTest_FRem,
@@ -676,7 +668,7 @@
                              BinaryData{"float", "float_50", "OpFRem", "float_60", "f32", "50.0f",
                                         "%", "60.0f"},  // Vector float
                              BinaryData{"v2float", "v2float_50_60", "OpFRem", "v2float_60_50",
-                                        "vec2<f32>", AstFor("v2float_50_60"), "%",
+                                        "vec2f", AstFor("v2float_50_60"), "%",
                                         AstFor("v2float_60_50")}));
 
 TEST_F(SpvBinaryArithTestBasic, FMod_Scalar) {
@@ -710,7 +702,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : vec2<f32> = (v2float_50_60 - (v2float_60_50 * "
+                HasSubstr("let x_1 : vec2f = (v2float_50_60 - (v2float_60_50 * "
                           "floor((v2float_50_60 / v2float_60_50))));"));
 }
 
@@ -730,7 +722,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_10 : vec2<f32> = (x_1 * x_2);"));
+                HasSubstr("let x_10 : vec2f = (x_1 * x_2);"));
 }
 
 TEST_F(SpvBinaryArithTestBasic, MatrixTimesScalar) {
@@ -749,7 +741,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_10 : mat2x2<f32> = (x_1 * x_2);"));
+                HasSubstr("let x_10 : mat2x2f = (x_1 * x_2);"));
 }
 
 TEST_F(SpvBinaryArithTestBasic, VectorTimesMatrix) {
@@ -768,7 +760,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_10 : vec2<f32> = (x_1 * x_2);"));
+                HasSubstr("let x_10 : vec2f = (x_1 * x_2);"));
 }
 
 TEST_F(SpvBinaryArithTestBasic, MatrixTimesVector) {
@@ -787,7 +779,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_10 : vec2<f32> = (x_1 * x_2);"));
+                HasSubstr("let x_10 : vec2f = (x_1 * x_2);"));
 }
 
 TEST_F(SpvBinaryArithTestBasic, MatrixTimesMatrix) {
@@ -806,7 +798,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_10 : mat2x2<f32> = (x_1 * x_2);"));
+                HasSubstr("let x_10 : mat2x2f = (x_1 * x_2);"));
 }
 
 TEST_F(SpvBinaryArithTestBasic, Dot) {
@@ -846,9 +838,9 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto got = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(got, HasSubstr("let x_3 : mat2x3<f32> = mat2x3<f32>("
-                               "vec3<f32>((x_2.x * x_1.x), (x_2.x * x_1.y), (x_2.x * x_1.z)), "
-                               "vec3<f32>((x_2.y * x_1.x), (x_2.y * x_1.y), (x_2.y * x_1.z)));"))
+    EXPECT_THAT(got, HasSubstr("let x_3 : mat2x3f = mat2x3f("
+                               "vec3f((x_2.x * x_1.x), (x_2.x * x_1.y), (x_2.x * x_1.z)), "
+                               "vec3f((x_2.y * x_1.x), (x_2.y * x_1.y), (x_2.y * x_1.z)));"))
         << got;
 }
 
@@ -912,9 +904,8 @@
                                        BuiltinData{"OpDPdyCoarse", "dpdyCoarse"},
                                        BuiltinData{"OpFwidthCoarse", "fwidthCoarse"}),
                      ::testing::Values(ArgAndTypeData{"float", "float_50", "f32"},
-                                       ArgAndTypeData{"v2float", "v2float_50_60", "vec2<f32>"},
-                                       ArgAndTypeData{"v3float", "v3float_50_60_70",
-                                                      "vec3<f32>"})));
+                                       ArgAndTypeData{"v2float", "v2float_50_60", "vec2f"},
+                                       ArgAndTypeData{"v3float", "v3float_50_60_70", "vec3f"})));
 
 TEST_F(SpvUnaryArithTest, Transpose_2x2) {
     const auto assembly = Preamble() + R"(
@@ -929,7 +920,7 @@
     ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error() << "\n" << assembly;
     auto fe = p->function_emitter(100);
     EXPECT_TRUE(fe.EmitBody()) << p->error();
-    const auto* expected = "let x_2 : mat2x2<f32> = transpose(x_1);";
+    const auto* expected = "let x_2 : mat2x2f = transpose(x_1);";
     auto ast_body = fe.ast_body();
     const auto got = test::ToString(p->program(), ast_body);
     EXPECT_THAT(got, HasSubstr(expected)) << got;
@@ -951,7 +942,7 @@
     // Note, in the AST dump mat_2_3 means 2 rows and 3 columns.
     // So the column vectors have 2 elements.
     // That is,   %m3v2float is __mat_2_3f32.
-    const auto* expected = "let x_2 : mat3x2<f32> = transpose(x_1);";
+    const auto* expected = "let x_2 : mat3x2f = transpose(x_1);";
     auto ast_body = fe.ast_body();
     const auto got = test::ToString(p->program(), ast_body);
     EXPECT_THAT(got, HasSubstr(expected)) << got;
@@ -970,7 +961,7 @@
     ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions()) << p->error() << "\n" << assembly;
     auto fe = p->function_emitter(100);
     EXPECT_TRUE(fe.EmitBody()) << p->error();
-    const auto* expected = "let x_2 : mat2x3<f32> = transpose(x_1);";
+    const auto* expected = "let x_2 : mat2x3f = transpose(x_1);";
     auto ast_body = fe.ast_body();
     const auto got = test::ToString(p->program(), ast_body);
     EXPECT_THAT(got, HasSubstr(expected)) << got;
diff --git a/src/tint/reader/spirv/function_bit_test.cc b/src/tint/reader/spirv/function_bit_test.cc
index 0401a63..741126b 100644
--- a/src/tint/reader/spirv/function_bit_test.cc
+++ b/src/tint/reader/spirv/function_bit_test.cc
@@ -69,25 +69,25 @@
 // Returns the AST dump for a given SPIR-V assembly constant.
 std::string AstFor(std::string assembly) {
     if (assembly == "v2uint_10_20") {
-        return "vec2<u32>(10u, 20u)";
+        return "vec2u(10u, 20u)";
     }
     if (assembly == "v2uint_20_10") {
-        return "vec2<u32>(20u, 10u)";
+        return "vec2u(20u, 10u)";
     }
     if (assembly == "v2int_30_40") {
-        return "vec2<i32>(30i, 40i)";
+        return "vec2i(30i, 40i)";
     }
     if (assembly == "v2int_40_30") {
-        return "vec2<i32>(40i, 30i)";
+        return "vec2i(40i, 30i)";
     }
     if (assembly == "cast_int_v2uint_10_20") {
-        return "bitcast<vec2<i32>(vec2<u32>(10u, 20u))";
+        return "bitcast<vec2i(vec2u(10u, 20u))";
     }
     if (assembly == "v2float_50_60") {
-        return "vec2<f32>(50.0, 60.0))";
+        return "vec2f(50.0, 60.0))";
     }
     if (assembly == "v2float_60_50") {
-        return "vec2<f32>(60.0, 50.0))";
+        return "vec2f(60.0, 50.0))";
     }
     return "bad case";
 }
@@ -182,10 +182,10 @@
         // int, uint -> int
         BinaryData{"int", "int_30", "OpShiftLeftLogical", "uint_20", "i32", "30i", "<<", "20u"},
         // v2uint v2uint -> v2uint
-        BinaryData{"v2uint", "v2uint_10_20", "OpShiftLeftLogical", "v2uint_20_10", "vec2<u32>",
+        BinaryData{"v2uint", "v2uint_10_20", "OpShiftLeftLogical", "v2uint_20_10", "vec2u",
                    AstFor("v2uint_10_20"), "<<", AstFor("v2uint_20_10")},
         // v2int, v2uint -> v2int
-        BinaryData{"v2int", "v2int_30_40", "OpShiftLeftLogical", "v2uint_20_10", "vec2<i32>",
+        BinaryData{"v2int", "v2int_30_40", "OpShiftLeftLogical", "v2uint_20_10", "vec2i",
                    AstFor("v2int_30_40"), "<<", AstFor("v2uint_20_10")}));
 
 INSTANTIATE_TEST_SUITE_P(
@@ -200,23 +200,22 @@
         BinaryDataGeneral{"uint", "uint_10", "OpShiftLeftLogical", "int_40", "u32",
                           "(10u << bitcast<u32>(40i))"},
         // v2uint, v2int -> v2uint
-        BinaryDataGeneral{"v2uint", "v2uint_10_20", "OpShiftLeftLogical", "v2uint_20_10",
-                          "vec2<u32>", "(vec2<u32>(10u, 20u) << vec2<u32>(20u, 10u))"},
+        BinaryDataGeneral{"v2uint", "v2uint_10_20", "OpShiftLeftLogical", "v2uint_20_10", "vec2u",
+                          "(vec2u(10u, 20u) << vec2u(20u, 10u))"},
         // v2int, v2int -> v2int
-        BinaryDataGeneral{"v2int", "v2int_30_40", "OpShiftLeftLogical", "v2int_40_30", "vec2<i32>",
-                          "(vec2<i32>(30i, 40i) << bitcast<vec2<u32>>(vec2<i32>(40i, 30i)))"}));
+        BinaryDataGeneral{"v2int", "v2int_30_40", "OpShiftLeftLogical", "v2int_40_30", "vec2i",
+                          "(vec2i(30i, 40i) << bitcast<vec2u>(vec2i(40i, 30i)))"}));
 
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_ShiftLeftLogical_BitcastResult,
-    SpvBinaryBitGeneralTest,
-    ::testing::Values(
-        // int, int -> uint
-        BinaryDataGeneral{"uint", "int_30", "OpShiftLeftLogical", "uint_10", "u32",
-                          "bitcast<u32>((30i << 10u))"},
-        // v2uint, v2int -> v2uint
-        BinaryDataGeneral{"v2uint", "v2int_30_40", "OpShiftLeftLogical", "v2uint_20_10",
-                          "vec2<u32>",
-                          "bitcast<vec2<u32>>((vec2<i32>(30i, 40i) << vec2<u32>(20u, 10u)))"}));
+INSTANTIATE_TEST_SUITE_P(SpvParserTest_ShiftLeftLogical_BitcastResult,
+                         SpvBinaryBitGeneralTest,
+                         ::testing::Values(
+                             // int, int -> uint
+                             BinaryDataGeneral{"uint", "int_30", "OpShiftLeftLogical", "uint_10",
+                                               "u32", "bitcast<u32>((30i << 10u))"},
+                             // v2uint, v2int -> v2uint
+                             BinaryDataGeneral{
+                                 "v2uint", "v2int_30_40", "OpShiftLeftLogical", "v2uint_20_10",
+                                 "vec2u", "bitcast<vec2u>((vec2i(30i, 40i) << vec2u(20u, 10u)))"}));
 
 INSTANTIATE_TEST_SUITE_P(
     SpvParserTest_ShiftRightLogical_Arg2Unsigned,
@@ -229,12 +228,12 @@
         BinaryDataGeneral{"int", "int_30", "OpShiftRightLogical", "uint_20", "i32",
                           "bitcast<i32>((bitcast<u32>(30i) >> 20u))"},
         // v2uint, v2uint -> v2uint
-        BinaryDataGeneral{"v2uint", "v2uint_10_20", "OpShiftRightLogical", "v2uint_20_10",
-                          "vec2<u32>", "(vec2<u32>(10u, 20u) >> vec2<u32>(20u, 10u))"},
+        BinaryDataGeneral{"v2uint", "v2uint_10_20", "OpShiftRightLogical", "v2uint_20_10", "vec2u",
+                          "(vec2u(10u, 20u) >> vec2u(20u, 10u))"},
         // v2int, v2uint -> v2int
         BinaryDataGeneral{
-            "v2int", "v2int_30_40", "OpShiftRightLogical", "v2uint_10_20", "vec2<i32>",
-            R"(bitcast<vec2<i32>>((bitcast<vec2<u32>>(vec2<i32>(30i, 40i)) >> vec2<u32>(10u, 20u))))"}));
+            "v2int", "v2int_30_40", "OpShiftRightLogical", "v2uint_10_20", "vec2i",
+            R"(bitcast<vec2i>((bitcast<vec2u>(vec2i(30i, 40i)) >> vec2u(10u, 20u))))"}));
 
 INSTANTIATE_TEST_SUITE_P(
     SpvParserTest_ShiftRightLogical_Arg2Signed,
@@ -247,13 +246,12 @@
         BinaryDataGeneral{"int", "int_30", "OpShiftRightLogical", "int_40", "i32",
                           "bitcast<i32>((bitcast<u32>(30i) >> bitcast<u32>(40i)))"},
         // v2uint, v2int -> v2uint
-        BinaryDataGeneral{"v2uint", "v2uint_10_20", "OpShiftRightLogical", "v2int_30_40",
-                          "vec2<u32>",
-                          "(vec2<u32>(10u, 20u) >> bitcast<vec2<u32>>(vec2<i32>(30i, 40i)))"},
+        BinaryDataGeneral{"v2uint", "v2uint_10_20", "OpShiftRightLogical", "v2int_30_40", "vec2u",
+                          "(vec2u(10u, 20u) >> bitcast<vec2u>(vec2i(30i, 40i)))"},
         // v2int, v2int -> v2int
         BinaryDataGeneral{
-            "v2int", "v2int_40_30", "OpShiftRightLogical", "v2int_30_40", "vec2<i32>",
-            R"(bitcast<vec2<i32>>((bitcast<vec2<u32>>(vec2<i32>(40i, 30i)) >> bitcast<vec2<u32>>(vec2<i32>(30i, 40i)))))"}));
+            "v2int", "v2int_40_30", "OpShiftRightLogical", "v2int_30_40", "vec2i",
+            R"(bitcast<vec2i>((bitcast<vec2u>(vec2i(40i, 30i)) >> bitcast<vec2u>(vec2i(30i, 40i)))))"}));
 
 INSTANTIATE_TEST_SUITE_P(
     SpvParserTest_ShiftRightLogical_BitcastResult,
@@ -263,9 +261,8 @@
         BinaryDataGeneral{"int", "uint_20", "OpShiftRightLogical", "uint_10", "i32",
                           "bitcast<i32>((20u >> 10u))"},
         // v2uint, v2uint -> v2int
-        BinaryDataGeneral{"v2int", "v2uint_10_20", "OpShiftRightLogical", "v2uint_20_10",
-                          "vec2<i32>",
-                          R"(bitcast<vec2<i32>>((vec2<u32>(10u, 20u) >> vec2<u32>(20u, 10u))))"}));
+        BinaryDataGeneral{"v2int", "v2uint_10_20", "OpShiftRightLogical", "v2uint_20_10", "vec2i",
+                          R"(bitcast<vec2i>((vec2u(10u, 20u) >> vec2u(20u, 10u))))"}));
 
 INSTANTIATE_TEST_SUITE_P(
     SpvParserTest_ShiftRightArithmetic_Arg2Unsigned,
@@ -279,11 +276,11 @@
                           "(30i >> 10u)"},
         // v2uint, v2uint -> v2uint
         BinaryDataGeneral{
-            "v2uint", "v2uint_10_20", "OpShiftRightArithmetic", "v2uint_20_10", "vec2<u32>",
-            R"(bitcast<vec2<u32>>((bitcast<vec2<i32>>(vec2<u32>(10u, 20u)) >> vec2<u32>(20u, 10u))))"},
+            "v2uint", "v2uint_10_20", "OpShiftRightArithmetic", "v2uint_20_10", "vec2u",
+            R"(bitcast<vec2u>((bitcast<vec2i>(vec2u(10u, 20u)) >> vec2u(20u, 10u))))"},
         // v2int, v2uint -> v2int
-        BinaryDataGeneral{"v2int", "v2int_40_30", "OpShiftRightArithmetic", "v2uint_20_10",
-                          "vec2<i32>", "(vec2<i32>(40i, 30i) >> vec2<u32>(20u, 10u))"}));
+        BinaryDataGeneral{"v2int", "v2int_40_30", "OpShiftRightArithmetic", "v2uint_20_10", "vec2i",
+                          "(vec2i(40i, 30i) >> vec2u(20u, 10u))"}));
 
 INSTANTIATE_TEST_SUITE_P(
     SpvParserTest_ShiftRightArithmetic_Arg2Signed,
@@ -297,24 +294,22 @@
                           "(30i >> bitcast<u32>(40i))"},
         // v2uint, v2int -> v2uint
         BinaryDataGeneral{
-            "v2uint", "v2uint_10_20", "OpShiftRightArithmetic", "v2int_30_40", "vec2<u32>",
-            R"(bitcast<vec2<u32>>((bitcast<vec2<i32>>(vec2<u32>(10u, 20u)) >> bitcast<vec2<u32>>(vec2<i32>(30i, 40i)))))"},
+            "v2uint", "v2uint_10_20", "OpShiftRightArithmetic", "v2int_30_40", "vec2u",
+            R"(bitcast<vec2u>((bitcast<vec2i>(vec2u(10u, 20u)) >> bitcast<vec2u>(vec2i(30i, 40i)))))"},
         // v2int, v2int -> v2int
-        BinaryDataGeneral{"v2int", "v2int_40_30", "OpShiftRightArithmetic", "v2int_30_40",
-                          "vec2<i32>",
-                          "(vec2<i32>(40i, 30i) >> bitcast<vec2<u32>>(vec2<i32>(30i, 40i)))"}));
+        BinaryDataGeneral{"v2int", "v2int_40_30", "OpShiftRightArithmetic", "v2int_30_40", "vec2i",
+                          "(vec2i(40i, 30i) >> bitcast<vec2u>(vec2i(30i, 40i)))"}));
 
-INSTANTIATE_TEST_SUITE_P(
-    SpvParserTest_ShiftRightArithmetic_BitcastResult,
-    SpvBinaryBitGeneralTest,
-    ::testing::Values(
-        // int, uint -> uint
-        BinaryDataGeneral{"uint", "int_30", "OpShiftRightArithmetic", "uint_10", "u32",
-                          "bitcast<u32>((30i >> 10u))"},
-        // v2int, v2uint -> v2uint
-        BinaryDataGeneral{"v2uint", "v2int_30_40", "OpShiftRightArithmetic", "v2uint_20_10",
-                          "vec2<u32>",
-                          "bitcast<vec2<u32>>((vec2<i32>(30i, 40i) >> vec2<u32>(20u, 10u)))"}));
+INSTANTIATE_TEST_SUITE_P(SpvParserTest_ShiftRightArithmetic_BitcastResult,
+                         SpvBinaryBitGeneralTest,
+                         ::testing::Values(
+                             // int, uint -> uint
+                             BinaryDataGeneral{"uint", "int_30", "OpShiftRightArithmetic",
+                                               "uint_10", "u32", "bitcast<u32>((30i >> 10u))"},
+                             // v2int, v2uint -> v2uint
+                             BinaryDataGeneral{
+                                 "v2uint", "v2int_30_40", "OpShiftRightArithmetic", "v2uint_20_10",
+                                 "vec2u", "bitcast<vec2u>((vec2i(30i, 40i) >> vec2u(20u, 10u)))"}));
 
 INSTANTIATE_TEST_SUITE_P(
     SpvParserTest_BitwiseAnd,
@@ -326,10 +321,10 @@
         BinaryData{"int", "int_30", "OpBitwiseAnd", "int_40", "i32", "30i", "&", "40i"},
         // TODO(crbug.com/tint/678): Resolver fails on vector bitwise operations
         // Both v2uint
-        BinaryData{"v2uint", "v2uint_10_20", "OpBitwiseAnd", "v2uint_20_10", "vec2<u32>",
+        BinaryData{"v2uint", "v2uint_10_20", "OpBitwiseAnd", "v2uint_20_10", "vec2u",
                    AstFor("v2uint_10_20"), "&", AstFor("v2uint_20_10")},
         // Both v2int
-        BinaryData{"v2int", "v2int_30_40", "OpBitwiseAnd", "v2int_40_30", "vec2<i32>",
+        BinaryData{"v2int", "v2int_30_40", "OpBitwiseAnd", "v2int_40_30", "vec2i",
                    AstFor("v2int_30_40"), "&", AstFor("v2int_40_30")}));
 
 INSTANTIATE_TEST_SUITE_P(
@@ -349,13 +344,12 @@
         BinaryDataGeneral{"int", "uint_20", "OpBitwiseAnd", "uint_10", "i32",
                           "bitcast<i32>((20u & 10u))"},
         // Mixed, returning v2uint
-        BinaryDataGeneral{
-            "v2uint", "v2int_30_40", "OpBitwiseAnd", "v2uint_10_20", "vec2<u32>",
-            R"(bitcast<vec2<u32>>((vec2<i32>(30i, 40i) & bitcast<vec2<i32>>(vec2<u32>(10u, 20u)))))"},
+        BinaryDataGeneral{"v2uint", "v2int_30_40", "OpBitwiseAnd", "v2uint_10_20", "vec2u",
+                          R"(bitcast<vec2u>((vec2i(30i, 40i) & bitcast<vec2i>(vec2u(10u, 20u)))))"},
         // Mixed, returning v2int
         BinaryDataGeneral{
-            "v2int", "v2uint_10_20", "OpBitwiseAnd", "v2int_40_30", "vec2<i32>",
-            R"(bitcast<vec2<i32>>((vec2<u32>(10u, 20u) & bitcast<vec2<u32>>(vec2<i32>(40i, 30i)))))"}));
+            "v2int", "v2uint_10_20", "OpBitwiseAnd", "v2int_40_30", "vec2i",
+            R"(bitcast<vec2i>((vec2u(10u, 20u) & bitcast<vec2u>(vec2i(40i, 30i)))))"}));
 
 INSTANTIATE_TEST_SUITE_P(
     SpvParserTest_BitwiseOr,
@@ -367,10 +361,10 @@
         BinaryData{"int", "int_30", "OpBitwiseOr", "int_40", "i32", "30i", "|", "40i"},
         // TODO(crbug.com/tint/678): Resolver fails on vector bitwise operations
         // Both v2uint
-        BinaryData{"v2uint", "v2uint_10_20", "OpBitwiseOr", "v2uint_20_10", "vec2<u32>",
+        BinaryData{"v2uint", "v2uint_10_20", "OpBitwiseOr", "v2uint_20_10", "vec2u",
                    AstFor("v2uint_10_20"), "|", AstFor("v2uint_20_10")},
         // Both v2int
-        BinaryData{"v2int", "v2int_30_40", "OpBitwiseOr", "v2int_40_30", "vec2<i32>",
+        BinaryData{"v2int", "v2int_30_40", "OpBitwiseOr", "v2int_40_30", "vec2i",
                    AstFor("v2int_30_40"), "|", AstFor("v2int_40_30")}));
 
 INSTANTIATE_TEST_SUITE_P(
@@ -390,13 +384,12 @@
         BinaryDataGeneral{"int", "uint_20", "OpBitwiseOr", "uint_10", "i32",
                           "bitcast<i32>((20u | 10u))"},
         // Mixed, returning v2uint
-        BinaryDataGeneral{
-            "v2uint", "v2int_30_40", "OpBitwiseOr", "v2uint_10_20", "vec2<u32>",
-            R"(bitcast<vec2<u32>>((vec2<i32>(30i, 40i) | bitcast<vec2<i32>>(vec2<u32>(10u, 20u)))))"},
+        BinaryDataGeneral{"v2uint", "v2int_30_40", "OpBitwiseOr", "v2uint_10_20", "vec2u",
+                          R"(bitcast<vec2u>((vec2i(30i, 40i) | bitcast<vec2i>(vec2u(10u, 20u)))))"},
         // Mixed, returning v2int
         BinaryDataGeneral{
-            "v2int", "v2uint_10_20", "OpBitwiseOr", "v2int_40_30", "vec2<i32>",
-            R"(bitcast<vec2<i32>>((vec2<u32>(10u, 20u) | bitcast<vec2<u32>>(vec2<i32>(40i, 30i)))))"}));
+            "v2int", "v2uint_10_20", "OpBitwiseOr", "v2int_40_30", "vec2i",
+            R"(bitcast<vec2i>((vec2u(10u, 20u) | bitcast<vec2u>(vec2i(40i, 30i)))))"}));
 
 INSTANTIATE_TEST_SUITE_P(
     SpvParserTest_BitwiseXor,
@@ -408,10 +401,10 @@
         BinaryData{"int", "int_30", "OpBitwiseXor", "int_40", "i32", "30i", "^", "40i"},
         // TODO(crbug.com/tint/678): Resolver fails on vector bitwise operations
         // Both v2uint
-        BinaryData{"v2uint", "v2uint_10_20", "OpBitwiseXor", "v2uint_20_10", "vec2<u32>",
+        BinaryData{"v2uint", "v2uint_10_20", "OpBitwiseXor", "v2uint_20_10", "vec2u",
                    AstFor("v2uint_10_20"), "^", AstFor("v2uint_20_10")},
         // Both v2int
-        BinaryData{"v2int", "v2int_30_40", "OpBitwiseXor", "v2int_40_30", "vec2<i32>",
+        BinaryData{"v2int", "v2int_30_40", "OpBitwiseXor", "v2int_40_30", "vec2i",
                    AstFor("v2int_30_40"), "^", AstFor("v2int_40_30")}));
 
 INSTANTIATE_TEST_SUITE_P(
@@ -431,13 +424,12 @@
         BinaryDataGeneral{"int", "uint_20", "OpBitwiseXor", "uint_10", "i32",
                           "bitcast<i32>((20u ^ 10u))"},
         // Mixed, returning v2uint
-        BinaryDataGeneral{
-            "v2uint", "v2int_30_40", "OpBitwiseXor", "v2uint_10_20", "vec2<u32>",
-            R"(bitcast<vec2<u32>>((vec2<i32>(30i, 40i) ^ bitcast<vec2<i32>>(vec2<u32>(10u, 20u)))))"},
+        BinaryDataGeneral{"v2uint", "v2int_30_40", "OpBitwiseXor", "v2uint_10_20", "vec2u",
+                          R"(bitcast<vec2u>((vec2i(30i, 40i) ^ bitcast<vec2i>(vec2u(10u, 20u)))))"},
         // Mixed, returning v2int
         BinaryDataGeneral{
-            "v2int", "v2uint_10_20", "OpBitwiseXor", "v2int_40_30", "vec2<i32>",
-            R"(bitcast<vec2<i32>>((vec2<u32>(10u, 20u) ^ bitcast<vec2<u32>>(vec2<i32>(40i, 30i)))))"}));
+            "v2int", "v2uint_10_20", "OpBitwiseXor", "v2int_40_30", "vec2i",
+            R"(bitcast<vec2i>((vec2u(10u, 20u) ^ bitcast<vec2u>(vec2i(40i, 30i)))))"}));
 
 TEST_F(SpvUnaryBitTest, Not_Int_Int) {
     const auto assembly = SimplePreamble() + R"(
@@ -521,7 +513,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = ~(vec2<i32>(30i, 40i));"));
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec2i = ~(vec2i(30i, 40i));"));
 }
 
 TEST_F(SpvUnaryBitTest, Not_SignedVec_UnsignedVec) {
@@ -538,8 +530,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body,
-                HasSubstr("let x_1 : vec2<i32> = bitcast<vec2<i32>>(~(vec2<u32>(10u, 20u)));"));
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec2i = bitcast<vec2i>(~(vec2u(10u, 20u)));"));
 }
 
 TEST_F(SpvUnaryBitTest, Not_UnsignedVec_SignedVec) {
@@ -556,8 +547,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body,
-                HasSubstr("let x_1 : vec2<u32> = bitcast<vec2<u32>>(~(vec2<i32>(30i, 40i)));"));
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec2u = bitcast<vec2u>(~(vec2i(30i, 40i)));"));
 }
 TEST_F(SpvUnaryBitTest, Not_UnsignedVec_UnsignedVec) {
     const auto assembly = SimplePreamble() + R"(
@@ -573,7 +563,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<u32> = ~(vec2<u32>(10u, 20u));"));
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec2u = ~(vec2u(10u, 20u));"));
 }
 
 std::string BitTestPreamble() {
@@ -674,7 +664,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<u32> = countOneBits(v2u1);")) << body;
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec2u = countOneBits(v2u1);")) << body;
 }
 
 TEST_F(SpvUnaryBitTest, BitCount_UintVector_IntVector) {
@@ -689,8 +679,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<u32> = bitcast<vec2<u32>>(countOneBits(v2i1));"))
-        << body;
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec2u = bitcast<vec2u>(countOneBits(v2i1));")) << body;
 }
 
 TEST_F(SpvUnaryBitTest, BitCount_IntVector_UintVector) {
@@ -705,8 +694,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = bitcast<vec2<i32>>(countOneBits(v2u1));"))
-        << body;
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec2i = bitcast<vec2i>(countOneBits(v2u1));")) << body;
 }
 
 TEST_F(SpvUnaryBitTest, BitCount_IntVector_IntVector) {
@@ -721,7 +709,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = countOneBits(v2i1);")) << body;
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec2i = countOneBits(v2i1);")) << body;
 }
 
 TEST_F(SpvUnaryBitTest, BitReverse_Uint_Uint) {
@@ -790,7 +778,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<u32> = reverseBits(v2u1);")) << body;
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec2u = reverseBits(v2u1);")) << body;
 }
 
 TEST_F(SpvUnaryBitTest, BitReverse_UintVector_IntVector) {
@@ -829,7 +817,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = reverseBits(v2i1);")) << body;
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec2i = reverseBits(v2i1);")) << body;
 }
 
 TEST_F(SpvUnaryBitTest, InsertBits_Int) {
@@ -875,9 +863,8 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(
-        body,
-        HasSubstr(R"(let x_1 : vec2<i32> = insertBits(x_28, vec2<i32>(40i, 30i), 10u, 20u);)"))
+    EXPECT_THAT(body,
+                HasSubstr(R"(let x_1 : vec2i = insertBits(x_28, vec2i(40i, 30i), 10u, 20u);)"))
         << body;
 }
 
@@ -895,8 +882,7 @@
     auto body = test::ToString(p->program(), ast_body);
     EXPECT_THAT(
         body,
-        HasSubstr(
-            R"(let x_1 : vec2<i32> = insertBits(x_28, vec2<i32>(40i, 30i), u32(10i), u32(20i));)"))
+        HasSubstr(R"(let x_1 : vec2i = insertBits(x_28, vec2i(40i, 30i), u32(10i), u32(20i));)"))
         << body;
 }
 
@@ -943,9 +929,8 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(
-        body,
-        HasSubstr(R"(let x_1 : vec2<u32> = insertBits(x_26, vec2<u32>(20u, 10u), 10u, 20u);)"))
+    EXPECT_THAT(body,
+                HasSubstr(R"(let x_1 : vec2u = insertBits(x_26, vec2u(20u, 10u), 10u, 20u);)"))
         << body;
 }
 
@@ -963,8 +948,7 @@
     auto body = test::ToString(p->program(), ast_body);
     EXPECT_THAT(
         body,
-        HasSubstr(
-            R"(let x_1 : vec2<u32> = insertBits(x_26, vec2<u32>(20u, 10u), u32(10i), u32(20i));)"))
+        HasSubstr(R"(let x_1 : vec2u = insertBits(x_26, vec2u(20u, 10u), u32(10i), u32(20i));)"))
         << body;
 }
 
@@ -1010,7 +994,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = extractBits(x_28, 10u, 20u);")) << body;
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec2i = extractBits(x_28, 10u, 20u);")) << body;
 }
 
 TEST_F(SpvUnaryBitTest, ExtractBits_IntVector_SignedOffsetAndCount) {
@@ -1025,7 +1009,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = extractBits(x_28, u32(10i), u32(20i));"))
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec2i = extractBits(x_28, u32(10i), u32(20i));"))
         << body;
 }
 
@@ -1071,7 +1055,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<u32> = extractBits(x_26, 10u, 20u);")) << body;
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec2u = extractBits(x_26, 10u, 20u);")) << body;
 }
 
 TEST_F(SpvUnaryBitTest, ExtractBits_UintVector_SignedOffsetAndCount) {
@@ -1086,7 +1070,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<u32> = extractBits(x_26, u32(10i), u32(20i));"))
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec2u = extractBits(x_26, u32(10i), u32(20i));"))
         << body;
 }
 
diff --git a/src/tint/reader/spirv/function_composite_test.cc b/src/tint/reader/spirv/function_composite_test.cc
index cf422a8..cb8bc42 100644
--- a/src/tint/reader/spirv/function_composite_test.cc
+++ b/src/tint/reader/spirv/function_composite_test.cc
@@ -96,9 +96,9 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr(R"(let x_1 : vec2<u32> = vec2<u32>(10u, 20u);
-let x_2 : vec2<i32> = vec2<i32>(30i, 40i);
-let x_3 : vec2<f32> = vec2<f32>(50.0f, 60.0f);
+                HasSubstr(R"(let x_1 : vec2u = vec2u(10u, 20u);
+let x_2 : vec2i = vec2i(30i, 40i);
+let x_3 : vec2f = vec2f(50.0f, 60.0f);
 )"));
 }
 
@@ -115,11 +115,10 @@
     auto fe = p->function_emitter(100);
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
-    EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : mat3x2<f32> = mat3x2<f32>("
-                          "vec2<f32>(50.0f, 60.0f), "
-                          "vec2<f32>(60.0f, 50.0f), "
-                          "vec2<f32>(70.0f, 70.0f));"));
+    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr("let x_1 : mat3x2f = mat3x2f("
+                                                                  "vec2f(50.0f, 60.0f), "
+                                                                  "vec2f(60.0f, 50.0f), "
+                                                                  "vec2f(70.0f, 70.0f));"));
 }
 
 TEST_F(SpvParserTest_Composite_Construct, Array) {
@@ -153,7 +152,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : S = S(vec2<f32>(50.0f, 60.0f), 5u, 30i);"));
+                HasSubstr("let x_1 : S = S(vec2f(50.0f, 60.0f), 5u, 30i);"));
 }
 
 TEST_F(SpvParserTest_Composite_Construct, ConstantComposite_Struct_NoDeduplication) {
@@ -201,7 +200,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : f32 = vec2<f32>(50.0f, 60.0f).y;"));
+                HasSubstr("let x_1 : f32 = vec2f(50.0f, 60.0f).y;"));
 }
 
 TEST_F(SpvParserTest_CompositeExtract, Vector_IndexTooBigError) {
@@ -238,8 +237,7 @@
     auto fe = p->function_emitter(100);
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
-    EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_2 : vec2<f32> = x_1[2u];"));
+    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr("let x_2 : vec2f = x_1[2u];"));
 }
 
 TEST_F(SpvParserTest_CompositeExtract, Matrix_IndexTooBigError) {
@@ -448,9 +446,9 @@
     auto ast_body = fe.ast_body();
     auto got = test::ToString(p->program(), ast_body);
     const auto* expected =
-        R"(var x_1_1 : vec2<f32> = vec2<f32>(50.0f, 60.0f);
+        R"(var x_1_1 : vec2f = vec2f(50.0f, 60.0f);
 x_1_1.y = 70.0f;
-let x_1 : vec2<f32> = x_1_1;
+let x_1 : vec2f = x_1_1;
 return;
 )";
     EXPECT_EQ(got, expected);
@@ -491,9 +489,9 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto body_str = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body_str, HasSubstr(R"(var x_2_1 : mat3x2<f32> = x_1;
-x_2_1[2u] = vec2<f32>(50.0f, 60.0f);
-let x_2 : mat3x2<f32> = x_2_1;
+    EXPECT_THAT(body_str, HasSubstr(R"(var x_2_1 : mat3x2f = x_1;
+x_2_1[2u] = vec2f(50.0f, 60.0f);
+let x_2 : mat3x2f = x_2_1;
 )")) << body_str;
 }
 
@@ -536,9 +534,9 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     auto body_str = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body_str, HasSubstr(R"(var x_2_1 : mat3x2<f32> = x_1;
-x_2_1[2u] = vec2<f32>(50.0f, 60.0f);
-let x_2 : mat3x2<f32> = x_2_1;
+    EXPECT_THAT(body_str, HasSubstr(R"(var x_2_1 : mat3x2f = x_1;
+x_2_1[2u] = vec2f(50.0f, 60.0f);
+let x_2 : mat3x2f = x_2_1;
 return;
 )")) << body_str;
 }
@@ -782,7 +780,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_10 : vec4<u32> = vec4<u32>(x_2.y, x_2.x, x_1.y, x_1.x);"));
+                HasSubstr("let x_10 : vec4u = vec4u(x_2.y, x_2.x, x_1.y, x_1.x);"));
 }
 
 TEST_F(SpvParserTest_VectorShuffle, ConstantOperands_UseBoth) {
@@ -799,12 +797,11 @@
     auto fe = p->function_emitter(100);
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
-    EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_10 : vec4<u32> = vec4<u32>("
-                          "vec2<u32>(4u, 3u).y, "
-                          "vec2<u32>(4u, 3u).x, "
-                          "vec2<u32>(3u, 4u).y, "
-                          "vec2<u32>(3u, 4u).x);"));
+    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr("let x_10 : vec4u = vec4u("
+                                                                  "vec2u(4u, 3u).y, "
+                                                                  "vec2u(4u, 3u).x, "
+                                                                  "vec2u(3u, 4u).y, "
+                                                                  "vec2u(3u, 4u).x);"));
 }
 
 TEST_F(SpvParserTest_VectorShuffle, ConstantOperands_AllOnesMapToNull) {
@@ -823,7 +820,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_10 : vec2<u32> = vec2<u32>(0u, x_1.y);"));
+                HasSubstr("let x_10 : vec2u = vec2u(0u, x_1.y);"));
 }
 
 TEST_F(SpvParserTest_VectorShuffle, FunctionScopeOperands_MixedInputOperandSizes) {
@@ -845,7 +842,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_10 : vec2<u32> = vec2<u32>(x_1.y, x_3.z);"));
+                HasSubstr("let x_10 : vec2u = vec2u(x_1.y, x_3.z);"));
 }
 
 TEST_F(SpvParserTest_VectorShuffle, IndexTooBig_IsError) {
@@ -926,9 +923,9 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     const auto got = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(got, HasSubstr(R"(var x_10_1 : vec2<u32> = x_1;
+    EXPECT_THAT(got, HasSubstr(R"(var x_10_1 : vec2u = x_1;
 x_10_1[x_3] = x_2;
-let x_10 : vec2<u32> = x_10_1;
+let x_10 : vec2u = x_10_1;
 )")) << got
      << assembly;
 }
diff --git a/src/tint/reader/spirv/function_conversion_test.cc b/src/tint/reader/spirv/function_conversion_test.cc
index 1b28190..602928a 100644
--- a/src/tint/reader/spirv/function_conversion_test.cc
+++ b/src/tint/reader/spirv/function_conversion_test.cc
@@ -100,7 +100,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : vec2<f32> = bitcast<vec2<f32>>(vec2<u32>(10u, 20u));"));
+                HasSubstr("let x_1 : vec2f = bitcast<vec2f>(vec2u(10u, 20u));"));
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertSToF_BadArg) {
@@ -245,7 +245,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : vec2<f32> = vec2<f32>(x_30);"));
+                HasSubstr("let x_1 : vec2f = vec2f(x_30);"));
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertSToF_Vector_FromUnsigned) {
@@ -263,7 +263,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : vec2<f32> = vec2<f32>(bitcast<vec2<i32>>(x_30));"));
+                HasSubstr("let x_1 : vec2f = vec2f(bitcast<vec2i>(x_30));"));
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertUToF_Scalar_BadArgType) {
@@ -348,7 +348,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : vec2<f32> = vec2<f32>(bitcast<vec2<u32>>(x_30));"));
+                HasSubstr("let x_1 : vec2f = vec2f(bitcast<vec2u>(x_30));"));
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertUToF_Vector_FromUnsigned) {
@@ -366,7 +366,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : vec2<f32> = vec2<f32>(x_30);"));
+                HasSubstr("let x_1 : vec2f = vec2f(x_30);"));
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertFToS_Scalar_BadArgType) {
@@ -451,7 +451,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : vec2<i32> = vec2<i32>(x_30);"));
+                HasSubstr("let x_1 : vec2i = vec2i(x_30);"));
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertFToS_Vector_ToUnsigned) {
@@ -469,7 +469,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : vec2<u32> = bitcast<vec2<u32>>(vec2<i32>(x_30));"));
+                HasSubstr("let x_1 : vec2u = bitcast<vec2u>(vec2i(x_30));"));
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertFToU_Scalar_BadArgType) {
@@ -568,7 +568,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : vec2<u32> = vec2<u32>(x_30);"));
+                HasSubstr("let x_1 : vec2u = vec2u(x_30);"));
 }
 
 TEST_F(SpvUnaryConversionTest, ConvertFToU_HoistedValue) {
diff --git a/src/tint/reader/spirv/function_decl_test.cc b/src/tint/reader/spirv/function_decl_test.cc
index 0eceba2..59121a3 100644
--- a/src/tint/reader/spirv/function_decl_test.cc
+++ b/src/tint/reader/spirv/function_decl_test.cc
@@ -186,7 +186,7 @@
 
     auto got = test::ToString(p->program());
     std::string expect = R"(fn x_200(x_14 : texture_2d<f32>, x_15 : sampler) {
-  let x_20 : vec4<f32> = textureSample(x_14, x_15, vec2<f32>());
+  let x_20 : vec4f = textureSample(x_14, x_15, vec2f());
   return;
 }
 )";
@@ -216,7 +216,7 @@
 
     auto got = test::ToString(p->program());
     std::string expect = R"(fn x_200(x_14 : texture_2d<f32>, x_15 : sampler) {
-  let x_20 : vec4<f32> = textureSample(x_14, x_15, vec2<f32>());
+  let x_20 : vec4f = textureSample(x_14, x_15, vec2f());
   return;
 }
 )";
diff --git a/src/tint/reader/spirv/function_glsl_std_450_test.cc b/src/tint/reader/spirv/function_glsl_std_450_test.cc
index f0e7e2a..506c8d4 100644
--- a/src/tint/reader/spirv/function_glsl_std_450_test.cc
+++ b/src/tint/reader/spirv/function_glsl_std_450_test.cc
@@ -282,8 +282,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<f32> = " + GetParam().wgsl_func + "(v2f1);"))
-        << body;
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec2f = " + GetParam().wgsl_func + "(v2f1);")) << body;
 }
 
 TEST_P(SpvParserTest_GlslStd450_Floating_FloatingFloating, Scalar) {
@@ -315,7 +314,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<f32> = " + GetParam().wgsl_func + "(v2f1, v2f2);"))
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec2f = " + GetParam().wgsl_func + "(v2f1, v2f2);"))
         << body;
 }
 
@@ -351,7 +350,7 @@
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
     EXPECT_THAT(body,
-                HasSubstr("let x_1 : vec2<f32> = " + GetParam().wgsl_func + "(v2f1, v2f2, v2f3);"))
+                HasSubstr("let x_1 : vec2f = " + GetParam().wgsl_func + "(v2f1, v2f2, v2f3);"))
         << body;
 }
 
@@ -385,7 +384,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<f32> = " + GetParam().wgsl_func + "(v2f1, v2i1);"))
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec2f = " + GetParam().wgsl_func + "(v2f1, v2i1);"))
         << body;
 }
 
@@ -403,7 +402,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec3<f32> = " + GetParam().wgsl_func + "(v3f1, v3f2);"))
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec3f = " + GetParam().wgsl_func + "(v3f1, v3f2);"))
         << body;
 }
 
@@ -542,8 +541,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = " + GetParam().wgsl_func + "(v2i1);"))
-        << body;
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec2i = " + GetParam().wgsl_func + "(v2i1);")) << body;
 }
 
 TEST_P(SpvParserTest_GlslStd450_Inting_Inting_SignednessCoercing, Vector_UnsignedArg) {
@@ -560,8 +558,8 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = " + GetParam().wgsl_func +
-                                "(bitcast<vec2<i32>>(v2u1));"))
+    EXPECT_THAT(body,
+                HasSubstr("let x_1 : vec2i = " + GetParam().wgsl_func + "(bitcast<vec2i>(v2u1));"))
         << body;
 }
 
@@ -579,8 +577,8 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<u32> = bitcast<vec2<u32>>(" + GetParam().wgsl_func +
-                                "(v2i1));"))
+    EXPECT_THAT(body,
+                HasSubstr("let x_1 : vec2u = bitcast<vec2u>(" + GetParam().wgsl_func + "(v2i1));"))
         << body;
 }
 
@@ -615,7 +613,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<i32> = " + GetParam().wgsl_func + "(v2i1, v2i2);"))
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec2i = " + GetParam().wgsl_func + "(v2i1, v2i2);"))
         << body;
 }
 
@@ -652,7 +650,7 @@
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
     EXPECT_THAT(body,
-                HasSubstr("let x_1 : vec2<i32> = " + GetParam().wgsl_func + "(v2i1, v2i2, v2i3);"))
+                HasSubstr("let x_1 : vec2i = " + GetParam().wgsl_func + "(v2i1, v2i2, v2i3);"))
         << body;
 }
 
@@ -707,8 +705,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<u32> = " + GetParam().wgsl_func + "(v2u1);"))
-        << body;
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec2u = " + GetParam().wgsl_func + "(v2u1);")) << body;
 }
 
 TEST_P(SpvParserTest_GlslStd450_Uinting_UintingUinting, Scalar) {
@@ -741,7 +738,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<u32> = " + GetParam().wgsl_func + "(v2u1, v2u2);"))
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec2u = " + GetParam().wgsl_func + "(v2u1, v2u2);"))
         << body;
 }
 
@@ -777,7 +774,7 @@
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
     EXPECT_THAT(body,
-                HasSubstr("let x_1 : vec2<u32> = " + GetParam().wgsl_func + "(v2u1, v2u2, v2u3);"))
+                HasSubstr("let x_1 : vec2u = " + GetParam().wgsl_func + "(v2u1, v2u2, v2u3);"))
         << body;
 }
 
@@ -828,7 +825,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec2<f32> = normalize(v2f1);")) << body;
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec2f = normalize(v2f1);")) << body;
 }
 
 TEST_F(SpvParserTest, Normalize_Vector3) {
@@ -844,7 +841,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec3<f32> = normalize(v3f1);")) << body;
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec3f = normalize(v3f1);")) << body;
 }
 
 TEST_F(SpvParserTest, Normalize_Vector4) {
@@ -860,7 +857,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : vec4<f32> = normalize(v4f1);")) << body;
+    EXPECT_THAT(body, HasSubstr("let x_1 : vec4f = normalize(v4f1);")) << body;
 }
 
 // Check that we convert signedness of operands and result type.
@@ -880,9 +877,7 @@
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
     EXPECT_THAT(body, HasSubstr(R"(let x_1 : u32 = bitcast<u32>(abs(bitcast<i32>(u1)));)")) << body;
-    EXPECT_THAT(
-        body,
-        HasSubstr(R"(let x_2 : vec2<u32> = bitcast<vec2<u32>>(abs(bitcast<vec2<i32>>(v2u1)));)"))
+    EXPECT_THAT(body, HasSubstr(R"(let x_2 : vec2u = bitcast<vec2u>(abs(bitcast<vec2i>(v2u1)));)"))
         << body;
 }
 
@@ -906,7 +901,7 @@
     EXPECT_THAT(
         body,
         HasSubstr(
-            R"(let x_2 : vec2<u32> = bitcast<vec2<u32>>(max(bitcast<vec2<i32>>(v2u1), bitcast<vec2<i32>>(v2u2)));)"))
+            R"(let x_2 : vec2u = bitcast<vec2u>(max(bitcast<vec2i>(v2u1), bitcast<vec2i>(v2u2)));)"))
         << body;
 }
 
@@ -930,7 +925,7 @@
     EXPECT_THAT(
         body,
         HasSubstr(
-            R"(let x_2 : vec2<u32> = bitcast<vec2<u32>>(min(bitcast<vec2<i32>>(v2u1), bitcast<vec2<i32>>(v2u2)));)"))
+            R"(let x_2 : vec2u = bitcast<vec2u>(min(bitcast<vec2i>(v2u1), bitcast<vec2i>(v2u2)));)"))
         << body;
 }
 
@@ -955,7 +950,7 @@
     EXPECT_THAT(
         body,
         HasSubstr(
-            R"(let x_2 : vec2<u32> = bitcast<vec2<u32>>(clamp(bitcast<vec2<i32>>(v2u1), v2i2, bitcast<vec2<i32>>(v2u3)));)"))
+            R"(let x_2 : vec2u = bitcast<vec2u>(clamp(bitcast<vec2i>(v2u1), v2i2, bitcast<vec2i>(v2u3)));)"))
         << body;
 }
 
@@ -979,7 +974,7 @@
     EXPECT_THAT(
         body,
         HasSubstr(
-            R"(let x_2 : vec2<i32> = bitcast<vec2<i32>>(max(bitcast<vec2<u32>>(v2i1), bitcast<vec2<u32>>(v2i2)));)"))
+            R"(let x_2 : vec2i = bitcast<vec2i>(max(bitcast<vec2u>(v2i1), bitcast<vec2u>(v2i2)));)"))
         << body;
 }
 
@@ -1003,7 +998,7 @@
     EXPECT_THAT(
         body,
         HasSubstr(
-            R"(let x_2 : vec2<i32> = bitcast<vec2<i32>>(min(bitcast<vec2<u32>>(v2i1), bitcast<vec2<u32>>(v2i2)));)"))
+            R"(let x_2 : vec2i = bitcast<vec2i>(min(bitcast<vec2u>(v2i1), bitcast<vec2u>(v2i2)));)"))
         << body;
 }
 
@@ -1028,7 +1023,7 @@
     EXPECT_THAT(
         body,
         HasSubstr(
-            R"(let x_2 : vec2<i32> = bitcast<vec2<i32>>(clamp(bitcast<vec2<u32>>(v2i1), v2u2, bitcast<vec2<u32>>(v2i3)));)"))
+            R"(let x_2 : vec2i = bitcast<vec2i>(clamp(bitcast<vec2u>(v2i1), v2u2, bitcast<vec2u>(v2i3)));)"))
         << body;
 }
 
@@ -1054,9 +1049,9 @@
     const auto body = test::ToString(p->program(), ast_body);
     EXPECT_THAT(body, HasSubstr(R"(
 let x_1 : u32 = bitcast<u32>(firstTrailingBit(i1));
-let x_2 : vec2<u32> = bitcast<vec2<u32>>(firstTrailingBit(v2i1));
+let x_2 : vec2u = bitcast<vec2u>(firstTrailingBit(v2i1));
 let x_3 : i32 = bitcast<i32>(firstTrailingBit(u1));
-let x_4 : vec2<i32> = bitcast<vec2<i32>>(firstTrailingBit(v2u1));)"))
+let x_4 : vec2i = bitcast<vec2i>(firstTrailingBit(v2u1));)"))
         << body;
 }
 
@@ -1100,13 +1095,13 @@
     const auto body = test::ToString(p->program(), ast_body);
     EXPECT_THAT(body, HasSubstr(R"(
 let x_1 : i32 = firstLeadingBit(i1);
-let x_2 : vec2<i32> = firstLeadingBit(v2i1);
+let x_2 : vec2i = firstLeadingBit(v2i1);
 let x_3 : u32 = bitcast<u32>(firstLeadingBit(i1));
-let x_4 : vec2<u32> = bitcast<vec2<u32>>(firstLeadingBit(v2i1));
+let x_4 : vec2u = bitcast<vec2u>(firstLeadingBit(v2i1));
 let x_5 : i32 = firstLeadingBit(bitcast<i32>(u1));
-let x_6 : vec2<i32> = firstLeadingBit(bitcast<vec2<i32>>(v2u1));
+let x_6 : vec2i = firstLeadingBit(bitcast<vec2i>(v2u1));
 let x_7 : u32 = bitcast<u32>(firstLeadingBit(bitcast<i32>(u1)));
-let x_8 : vec2<u32> = bitcast<vec2<u32>>(firstLeadingBit(bitcast<vec2<i32>>(v2u1)));
+let x_8 : vec2u = bitcast<vec2u>(firstLeadingBit(bitcast<vec2i>(v2u1)));
 )")) << body;
 }
 
@@ -1150,13 +1145,13 @@
     const auto body = test::ToString(p->program(), ast_body);
     EXPECT_THAT(body, HasSubstr(R"(
 let x_1 : i32 = bitcast<i32>(firstLeadingBit(bitcast<u32>(i1)));
-let x_2 : vec2<i32> = bitcast<vec2<i32>>(firstLeadingBit(bitcast<vec2<u32>>(v2i1)));
+let x_2 : vec2i = bitcast<vec2i>(firstLeadingBit(bitcast<vec2u>(v2i1)));
 let x_3 : u32 = firstLeadingBit(bitcast<u32>(i1));
-let x_4 : vec2<u32> = firstLeadingBit(bitcast<vec2<u32>>(v2i1));
+let x_4 : vec2u = firstLeadingBit(bitcast<vec2u>(v2i1));
 let x_5 : i32 = bitcast<i32>(firstLeadingBit(u1));
-let x_6 : vec2<i32> = bitcast<vec2<i32>>(firstLeadingBit(v2u1));
+let x_6 : vec2i = bitcast<vec2i>(firstLeadingBit(v2u1));
 let x_7 : u32 = firstLeadingBit(u1);
-let x_8 : vec2<u32> = firstLeadingBit(v2u1);
+let x_8 : vec2u = firstLeadingBit(v2u1);
 )")) << body;
 }
 
@@ -1219,10 +1214,10 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
-    EXPECT_THAT(body, HasSubstr("let x_1 : " +
-                                std::string(param.vec_size == 2 ? "vec2<f32>" : "vec4<f32>") +
+    EXPECT_THAT(body,
+                HasSubstr("let x_1 : " + std::string(param.vec_size == 2 ? "vec2f" : "vec4f") +
 
-                                +" = " + param.wgsl_func + "(u1);"))
+                          +" = " + param.wgsl_func + "(u1);"))
         << body;
 }
 
@@ -1247,8 +1242,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
-    const auto* expected =
-        R"(let x_1 : f32 = refract(vec2<f32>(f1, 0.0f), vec2<f32>(f2, 0.0f), f3).x;)";
+    const auto* expected = R"(let x_1 : f32 = refract(vec2f(f1, 0.0f), vec2f(f2, 0.0f), f3).x;)";
 
     EXPECT_THAT(body, HasSubstr(expected)) << body;
 }
@@ -1265,7 +1259,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
-    const auto* expected = R"(let x_1 : vec2<f32> = refract(v2f1, v2f2, f3);)";
+    const auto* expected = R"(let x_1 : vec2f = refract(v2f1, v2f2, f3);)";
 
     EXPECT_THAT(body, HasSubstr(expected)) << body;
 }
@@ -1304,7 +1298,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
-    const auto* expected = R"(let x_1 : vec2<f32> = faceForward(v2f1, v2f2, v2f3);)";
+    const auto* expected = R"(let x_1 : vec2f = faceForward(v2f1, v2f2, v2f3);)";
 
     EXPECT_THAT(body, HasSubstr(expected)) << body;
 }
@@ -1346,9 +1340,9 @@
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
     const auto* expected = R"(
-let x_98 : vec2<f32> = (v2f1 + v2f1);
-let x_99 : vec2<f32> = (v2f2 + v2f2);
-let x_1 : vec2<f32> = reflect(x_98, x_99);
+let x_98 : vec2f = (v2f1 + v2f1);
+let x_99 : vec2f = (v2f2 + v2f2);
+let x_1 : vec2f = reflect(x_98, x_99);
 )";
 
     EXPECT_THAT(body, HasSubstr(expected)) << body;
@@ -1384,7 +1378,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     const auto body = test::ToString(p->program(), ast_body);
-    const auto* expected = "let x_1 : vec2<f32> = ldexp(v2f1, vec2<i32>(v2u1));";
+    const auto* expected = "let x_1 : vec2f = ldexp(v2f1, vec2i(v2u1));";
 
     EXPECT_THAT(body, HasSubstr(expected)) << body;
 }
@@ -1426,8 +1420,8 @@
 
     std::string expected =
         "let s = (1.0f / determinant(m2x2f1));\n"
-        "let x_1 : mat2x2<f32> = mat2x2<f32>(vec2<f32>((s * m2x2f1[1u][1u]), (-(s) * "
-        "m2x2f1[0u][1u])), vec2<f32>((-(s) * m2x2f1[1u][0u]), (s * m2x2f1[0u][0u])));";
+        "let x_1 : mat2x2f = mat2x2f(vec2f((s * m2x2f1[1u][1u]), (-(s) * "
+        "m2x2f1[0u][1u])), vec2f((-(s) * m2x2f1[1u][0u]), (s * m2x2f1[0u][0u])));";
 
     EXPECT_THAT(body, HasSubstr(expected)) << body;
 }
@@ -1447,13 +1441,13 @@
 
     std::string expected =
         "let s = (1.0f / determinant(m3x3f1));\n"
-        "let x_1 : mat3x3<f32> = (s * mat3x3<f32>(vec3<f32>(((m3x3f1[1u][1u] * m3x3f1[2u][2u]) - "
+        "let x_1 : mat3x3f = (s * mat3x3f(vec3f(((m3x3f1[1u][1u] * m3x3f1[2u][2u]) - "
         "(m3x3f1[1u][2u] * m3x3f1[2u][1u])), ((m3x3f1[0u][2u] * m3x3f1[2u][1u]) - (m3x3f1[0u][1u] "
         "* m3x3f1[2u][2u])), ((m3x3f1[0u][1u] * m3x3f1[1u][2u]) - (m3x3f1[0u][2u] * "
-        "m3x3f1[1u][1u]))), vec3<f32>(((m3x3f1[1u][2u] * m3x3f1[2u][0u]) - (m3x3f1[1u][0u] * "
+        "m3x3f1[1u][1u]))), vec3f(((m3x3f1[1u][2u] * m3x3f1[2u][0u]) - (m3x3f1[1u][0u] * "
         "m3x3f1[2u][2u])), ((m3x3f1[0u][0u] * m3x3f1[2u][2u]) - (m3x3f1[0u][2u] * "
         "m3x3f1[2u][0u])), ((m3x3f1[0u][2u] * m3x3f1[1u][0u]) - (m3x3f1[0u][0u] * "
-        "m3x3f1[1u][2u]))), vec3<f32>(((m3x3f1[1u][0u] * m3x3f1[2u][1u]) - (m3x3f1[1u][1u] * "
+        "m3x3f1[1u][2u]))), vec3f(((m3x3f1[1u][0u] * m3x3f1[2u][1u]) - (m3x3f1[1u][1u] * "
         "m3x3f1[2u][0u])), ((m3x3f1[0u][1u] * m3x3f1[2u][0u]) - (m3x3f1[0u][0u] * "
         "m3x3f1[2u][1u])), ((m3x3f1[0u][0u] * m3x3f1[1u][1u]) - (m3x3f1[0u][1u] * "
         "m3x3f1[1u][0u])))));";
@@ -1476,7 +1470,7 @@
 
     std::string expected =
         "let s = (1.0f / determinant(m4x4f1));\n"
-        "let x_1 : mat4x4<f32> = (s * mat4x4<f32>(vec4<f32>((((m4x4f1[1u][1u] * ((m4x4f1[2u][2u] * "
+        "let x_1 : mat4x4f = (s * mat4x4f(vec4f((((m4x4f1[1u][1u] * ((m4x4f1[2u][2u] * "
         "m4x4f1[3u][3u]) - (m4x4f1[2u][3u] * m4x4f1[3u][2u]))) - (m4x4f1[1u][2u] * "
         "((m4x4f1[2u][1u] * m4x4f1[3u][3u]) - (m4x4f1[2u][3u] * m4x4f1[3u][1u])))) + "
         "(m4x4f1[1u][3u] * ((m4x4f1[2u][1u] * m4x4f1[3u][2u]) - (m4x4f1[2u][2u] * "
@@ -1491,7 +1485,7 @@
         "((m4x4f1[1u][2u] * m4x4f1[2u][3u]) - (m4x4f1[1u][3u] * m4x4f1[2u][2u]))) + "
         "(m4x4f1[0u][2u] * ((m4x4f1[1u][1u] * m4x4f1[2u][3u]) - (m4x4f1[1u][3u] * "
         "m4x4f1[2u][1u])))) - (m4x4f1[0u][3u] * ((m4x4f1[1u][1u] * m4x4f1[2u][2u]) - "
-        "(m4x4f1[1u][2u] * m4x4f1[2u][1u]))))), vec4<f32>((((-(m4x4f1[1u][0u]) * ((m4x4f1[2u][2u] "
+        "(m4x4f1[1u][2u] * m4x4f1[2u][1u]))))), vec4f((((-(m4x4f1[1u][0u]) * ((m4x4f1[2u][2u] "
         "* m4x4f1[3u][3u]) - (m4x4f1[2u][3u] * m4x4f1[3u][2u]))) + (m4x4f1[1u][2u] * "
         "((m4x4f1[2u][0u] * m4x4f1[3u][3u]) - (m4x4f1[2u][3u] * m4x4f1[3u][0u])))) - "
         "(m4x4f1[1u][3u] * ((m4x4f1[2u][0u] * m4x4f1[3u][2u]) - (m4x4f1[2u][2u] * "
@@ -1506,7 +1500,7 @@
         "((m4x4f1[1u][2u] * m4x4f1[2u][3u]) - (m4x4f1[1u][3u] * m4x4f1[2u][2u]))) - "
         "(m4x4f1[0u][2u] * ((m4x4f1[1u][0u] * m4x4f1[2u][3u]) - (m4x4f1[1u][3u] * "
         "m4x4f1[2u][0u])))) + (m4x4f1[0u][3u] * ((m4x4f1[1u][0u] * m4x4f1[2u][2u]) - "
-        "(m4x4f1[1u][2u] * m4x4f1[2u][0u]))))), vec4<f32>((((m4x4f1[1u][0u] * ((m4x4f1[2u][1u] * "
+        "(m4x4f1[1u][2u] * m4x4f1[2u][0u]))))), vec4f((((m4x4f1[1u][0u] * ((m4x4f1[2u][1u] * "
         "m4x4f1[3u][3u]) - (m4x4f1[2u][3u] * m4x4f1[3u][1u]))) - (m4x4f1[1u][1u] * "
         "((m4x4f1[2u][0u] * m4x4f1[3u][3u]) - (m4x4f1[2u][3u] * m4x4f1[3u][0u])))) + "
         "(m4x4f1[1u][3u] * ((m4x4f1[2u][0u] * m4x4f1[3u][1u]) - (m4x4f1[2u][1u] * "
@@ -1521,7 +1515,7 @@
         "((m4x4f1[1u][1u] * m4x4f1[2u][3u]) - (m4x4f1[1u][3u] * m4x4f1[2u][1u]))) + "
         "(m4x4f1[0u][1u] * ((m4x4f1[1u][0u] * m4x4f1[2u][3u]) - (m4x4f1[1u][3u] * "
         "m4x4f1[2u][0u])))) - (m4x4f1[0u][3u] * ((m4x4f1[1u][0u] * m4x4f1[2u][1u]) - "
-        "(m4x4f1[1u][1u] * m4x4f1[2u][0u]))))), vec4<f32>((((-(m4x4f1[1u][0u]) * ((m4x4f1[2u][1u] "
+        "(m4x4f1[1u][1u] * m4x4f1[2u][0u]))))), vec4f((((-(m4x4f1[1u][0u]) * ((m4x4f1[2u][1u] "
         "* m4x4f1[3u][2u]) - (m4x4f1[2u][2u] * m4x4f1[3u][1u]))) + (m4x4f1[1u][1u] * "
         "((m4x4f1[2u][0u] * m4x4f1[3u][2u]) - (m4x4f1[2u][2u] * m4x4f1[3u][0u])))) - "
         "(m4x4f1[1u][2u] * ((m4x4f1[2u][0u] * m4x4f1[3u][1u]) - (m4x4f1[2u][1u] * "
@@ -1557,11 +1551,11 @@
 
     std::string expected =
         "let s = (1.0f / determinant(m2x2f1));\n"
-        "let x_1 : mat2x2<f32> = mat2x2<f32>(vec2<f32>((s * m2x2f1[1u][1u]), (-(s) * "
-        "m2x2f1[0u][1u])), vec2<f32>((-(s) * m2x2f1[1u][0u]), (s * m2x2f1[0u][0u])));\n"
+        "let x_1 : mat2x2f = mat2x2f(vec2f((s * m2x2f1[1u][1u]), (-(s) * "
+        "m2x2f1[0u][1u])), vec2f((-(s) * m2x2f1[1u][0u]), (s * m2x2f1[0u][0u])));\n"
         "let s_1 = (1.0f / determinant(m2x2f1));\n"
-        "let x_2 : mat2x2<f32> = mat2x2<f32>(vec2<f32>((s_1 * m2x2f1[1u][1u]), (-(s_1) * "
-        "m2x2f1[0u][1u])), vec2<f32>((-(s_1) * m2x2f1[1u][0u]), (s_1 * m2x2f1[0u][0u])));";
+        "let x_2 : mat2x2f = mat2x2f(vec2f((s_1 * m2x2f1[1u][1u]), (-(s_1) * "
+        "m2x2f1[0u][1u])), vec2f((-(s_1) * m2x2f1[1u][0u]), (s_1 * m2x2f1[0u][0u])));";
 
     EXPECT_THAT(body, HasSubstr(expected)) << body;
 }
diff --git a/src/tint/reader/spirv/function_logical_test.cc b/src/tint/reader/spirv/function_logical_test.cc
index 2d362c4..4e62df9 100644
--- a/src/tint/reader/spirv/function_logical_test.cc
+++ b/src/tint/reader/spirv/function_logical_test.cc
@@ -77,7 +77,7 @@
         return "vec2<bool>(false, true)";
     }
     if (assembly == "v2uint_10_20") {
-        return "vec2<u32>(10u, 20u)";
+        return "vec2u(10u, 20u)";
     }
     if (assembly == "cast_uint_10") {
         return "bitcast<i32>(10u)";
@@ -86,13 +86,13 @@
         return "bitcast<i32>(20u)";
     }
     if (assembly == "cast_v2uint_10_20") {
-        return "bitcast<vec2<i32>>(vec2<u32>(10u, 20u))";
+        return "bitcast<vec2i>(vec2u(10u, 20u))";
     }
     if (assembly == "v2uint_20_10") {
-        return "vec2<u32>(20u, 10u)";
+        return "vec2u(20u, 10u)";
     }
     if (assembly == "cast_v2uint_20_10") {
-        return "bitcast<vec2<i32>>(vec2<u32>(20u, 10u))";
+        return "bitcast<vec2i>(vec2u(20u, 10u))";
     }
     if (assembly == "cast_int_30") {
         return "bitcast<u32>(30i)";
@@ -101,22 +101,22 @@
         return "bitcast<u32>(40i)";
     }
     if (assembly == "v2int_30_40") {
-        return "vec2<i32>(30i, 40i)";
+        return "vec2i(30i, 40i)";
     }
     if (assembly == "cast_v2int_30_40") {
-        return "bitcast<vec2<u32>>(vec2<i32>(30i, 40i))";
+        return "bitcast<vec2u>(vec2i(30i, 40i))";
     }
     if (assembly == "v2int_40_30") {
-        return "vec2<i32>(40i, 30i)";
+        return "vec2i(40i, 30i)";
     }
     if (assembly == "cast_v2int_40_30") {
-        return "bitcast<vec2<u32>>(vec2<i32>(40i, 30i))";
+        return "bitcast<vec2u>(vec2i(40i, 30i))";
     }
     if (assembly == "v2float_50_60") {
-        return "vec2<f32>(50.0f, 60.0f)";
+        return "vec2f(50.0f, 60.0f)";
     }
     if (assembly == "v2float_60_50") {
-        return "vec2<f32>(60.0f, 50.0f)";
+        return "vec2f(60.0f, 50.0f)";
     }
     return "bad case";
 }
@@ -534,8 +534,7 @@
     auto ast_body = fe.ast_body();
     EXPECT_THAT(
         test::ToString(p->program(), ast_body),
-        HasSubstr(
-            "let x_1 : vec2<bool> = !((vec2<f32>(50.0f, 60.0f) != vec2<f32>(60.0f, 50.0f)));"));
+        HasSubstr("let x_1 : vec2<bool> = !((vec2f(50.0f, 60.0f) != vec2f(60.0f, 50.0f)));"));
 }
 
 TEST_F(SpvFUnordTest, FUnordNotEqual_Scalar) {
@@ -570,8 +569,7 @@
     auto ast_body = fe.ast_body();
     EXPECT_THAT(
         test::ToString(p->program(), ast_body),
-        HasSubstr(
-            "let x_1 : vec2<bool> = !((vec2<f32>(50.0f, 60.0f) == vec2<f32>(60.0f, 50.0f)));"));
+        HasSubstr("let x_1 : vec2<bool> = !((vec2f(50.0f, 60.0f) == vec2f(60.0f, 50.0f)));"));
 }
 
 TEST_F(SpvFUnordTest, FUnordLessThan_Scalar) {
@@ -606,8 +604,7 @@
     auto ast_body = fe.ast_body();
     EXPECT_THAT(
         test::ToString(p->program(), ast_body),
-        HasSubstr(
-            "let x_1 : vec2<bool> = !((vec2<f32>(50.0f, 60.0f) >= vec2<f32>(60.0f, 50.0f)));"));
+        HasSubstr("let x_1 : vec2<bool> = !((vec2f(50.0f, 60.0f) >= vec2f(60.0f, 50.0f)));"));
 }
 
 TEST_F(SpvFUnordTest, FUnordLessThanEqual_Scalar) {
@@ -642,8 +639,7 @@
     auto ast_body = fe.ast_body();
     EXPECT_THAT(
         test::ToString(p->program(), ast_body),
-        HasSubstr(
-            "let x_1 : vec2<bool> = !((vec2<f32>(50.0f, 60.0f) > vec2<f32>(60.0f, 50.0f)));"));
+        HasSubstr("let x_1 : vec2<bool> = !((vec2f(50.0f, 60.0f) > vec2f(60.0f, 50.0f)));"));
 }
 
 TEST_F(SpvFUnordTest, FUnordGreaterThan_Scalar) {
@@ -678,8 +674,7 @@
     auto ast_body = fe.ast_body();
     EXPECT_THAT(
         test::ToString(p->program(), ast_body),
-        HasSubstr(
-            "let x_1 : vec2<bool> = !((vec2<f32>(50.0f, 60.0f) <= vec2<f32>(60.0f, 50.0f)));"));
+        HasSubstr("let x_1 : vec2<bool> = !((vec2f(50.0f, 60.0f) <= vec2f(60.0f, 50.0f)));"));
 }
 
 TEST_F(SpvFUnordTest, FUnordGreaterThanEqual_Scalar) {
@@ -714,8 +709,7 @@
     auto ast_body = fe.ast_body();
     EXPECT_THAT(
         test::ToString(p->program(), ast_body),
-        HasSubstr(
-            "let x_1 : vec2<bool> = !((vec2<f32>(50.0f, 60.0f) < vec2<f32>(60.0f, 50.0f)));"));
+        HasSubstr("let x_1 : vec2<bool> = !((vec2f(50.0f, 60.0f) < vec2f(60.0f, 50.0f)));"));
 }
 
 using SpvLogicalTest = SpvParserTestBase<::testing::Test>;
@@ -787,9 +781,9 @@
     auto fe = p->function_emitter(100);
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
-    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr("let x_1 : vec2<u32> = select("
-                                                                  "vec2<u32>(20u, 10u), "
-                                                                  "vec2<u32>(10u, 20u), "
+    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr("let x_1 : vec2u = select("
+                                                                  "vec2u(20u, 10u), "
+                                                                  "vec2u(10u, 20u), "
                                                                   "true);"));
 
     // Fails validation prior to SPIR-V 1.4: If the value operands are vectors,
@@ -812,9 +806,9 @@
     auto fe = p->function_emitter(100);
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
-    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr("let x_1 : vec2<u32> = select("
-                                                                  "vec2<u32>(20u, 10u), "
-                                                                  "vec2<u32>(10u, 20u), "
+    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr("let x_1 : vec2u = select("
+                                                                  "vec2u(20u, 10u), "
+                                                                  "vec2u(10u, 20u), "
                                                                   "vec2<bool>(true, false));"));
 }
 
@@ -883,7 +877,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : vec2<bool> = isNan(vec2<f32>(50.0f, 60.0f));"));
+                HasSubstr("let x_1 : vec2<bool> = isNan(vec2f(50.0f, 60.0f));"));
 }
 
 TEST_F(SpvLogicalTest, IsInf_Scalar) {
@@ -917,7 +911,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_1 : vec2<bool> = isInf(vec2<f32>(50.0f, 60.0f));"));
+                HasSubstr("let x_1 : vec2<bool> = isInf(vec2f(50.0f, 60.0f));"));
 }
 
 // TODO(dneto): Kernel-guarded instructions.
diff --git a/src/tint/reader/spirv/function_memory_test.cc b/src/tint/reader/spirv/function_memory_test.cc
index 1877725..f7b1862 100644
--- a/src/tint/reader/spirv/function_memory_test.cc
+++ b/src/tint/reader/spirv/function_memory_test.cc
@@ -498,7 +498,7 @@
     EXPECT_TRUE(fe.EmitBody());
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("myvar[2u] = vec4<f32>(42.0f, 42.0f, 42.0f, 42.0f);"));
+                HasSubstr("myvar[2u] = vec4f(42.0f, 42.0f, 42.0f, 42.0f);"));
 }
 
 TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_Array) {
@@ -530,7 +530,7 @@
     EXPECT_TRUE(fe.EmitBody());
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("myvar[2u] = vec4<f32>(42.0f, 42.0f, 42.0f, 42.0f);"));
+                HasSubstr("myvar[2u] = vec4f(42.0f, 42.0f, 42.0f, 42.0f);"));
 }
 
 TEST_F(SpvParserMemoryTest, EmitStatement_AccessChain_Struct) {
@@ -801,7 +801,7 @@
     auto p = parser(test::Assemble(assembly));
     ASSERT_TRUE(p->BuildAndParseInternalModule());
     const auto got = test::ToString(p->program());
-    const std::string expected = R"(fn x_200(x_1 : ptr<private, vec2<u32>>) {
+    const std::string expected = R"(fn x_200(x_1 : ptr<private, vec2u>) {
   let x_3 : u32 = (*(x_1)).x;
   return;
 }
diff --git a/src/tint/reader/spirv/function_misc_test.cc b/src/tint/reader/spirv/function_misc_test.cc
index 5054c67..2c984d2 100644
--- a/src/tint/reader/spirv/function_misc_test.cc
+++ b/src/tint/reader/spirv/function_misc_test.cc
@@ -104,9 +104,9 @@
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
                 HasSubstr(R"(let x_14 : vec2<bool> = vec2<bool>();
-let x_11 : vec2<u32> = vec2<u32>();
-let x_12 : vec2<i32> = vec2<i32>();
-let x_13 : vec2<f32> = vec2<f32>();
+let x_11 : vec2u = vec2u();
+let x_12 : vec2i = vec2i();
+let x_13 : vec2f = vec2f();
 )"));
 }
 
@@ -158,10 +158,9 @@
     auto fe = p->function_emitter(100);
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
-    EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr(R"(let x_11 : vec2<u32> = vec2<u32>();
-let x_12 : vec2<i32> = vec2<i32>();
-let x_13 : vec2<f32> = vec2<f32>();
+    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr(R"(let x_11 : vec2u = vec2u();
+let x_12 : vec2i = vec2i();
+let x_13 : vec2f = vec2f();
 )"));
 }
 
@@ -183,7 +182,7 @@
     EXPECT_TRUE(fe.EmitBody()) << p->error();
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("let x_11 : mat2x2<f32> = mat2x2<f32>();"));
+                HasSubstr("let x_11 : mat2x2f = mat2x2f();"));
 }
 
 TEST_F(SpvParserTestMiscInstruction, OpUndef_InFunction_Array) {
diff --git a/src/tint/reader/spirv/function_var_test.cc b/src/tint/reader/spirv/function_var_test.cc
index 681572b..fd3580b 100644
--- a/src/tint/reader/spirv/function_var_test.cc
+++ b/src/tint/reader/spirv/function_var_test.cc
@@ -235,7 +235,7 @@
 
     auto ast_body = fe.ast_body();
     EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("var x_200 : vec2<f32> = vec2<f32>(1.5f, 2.0f);"));
+                HasSubstr("var x_200 : vec2f = vec2f(1.5f, 2.0f);"));
 }
 
 TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_MatrixInitializer) {
@@ -260,11 +260,10 @@
     EXPECT_TRUE(fe.EmitFunctionVariables());
 
     auto ast_body = fe.ast_body();
-    EXPECT_THAT(test::ToString(p->program(), ast_body),
-                HasSubstr("var x_200 : mat3x2<f32> = mat3x2<f32>("
-                          "vec2<f32>(1.5f, 2.0f), "
-                          "vec2<f32>(2.0f, 3.0f), "
-                          "vec2<f32>(3.0f, 4.0f));"));
+    EXPECT_THAT(test::ToString(p->program(), ast_body), HasSubstr("var x_200 : mat3x2f = mat3x2f("
+                                                                  "vec2f(1.5f, 2.0f), "
+                                                                  "vec2f(2.0f, 3.0f), "
+                                                                  "vec2f(3.0f, 4.0f));"));
 }
 
 TEST_F(SpvParserFunctionVarTest, EmitFunctionVariables_ArrayInitializer) {
@@ -1731,14 +1730,14 @@
     auto fe = p->function_emitter(100);
     EXPECT_TRUE(fe.EmitBody()) << p->error();
 
-    const auto* expected = R"(var x_200 : vec2<i32>;
+    const auto* expected = R"(var x_200 : vec2i;
 if (true) {
-  x_200 = vec2<i32>();
+  x_200 = vec2i();
   x_200.x = 0i;
 } else {
   return;
 }
-let x_201 : vec2<i32> = x_200;
+let x_201 : vec2i = x_200;
 return;
 )";
     auto ast_body = fe.ast_body();
@@ -1774,14 +1773,14 @@
 
     auto ast_body = fe.ast_body();
     const auto got = test::ToString(p->program(), ast_body);
-    const auto* expected = R"(var x_200 : vec2<i32>;
+    const auto* expected = R"(var x_200 : vec2i;
 if (true) {
-  x_200 = vec2<i32>();
+  x_200 = vec2i();
   x_200[1i] = 3i;
 } else {
   return;
 }
-let x_201 : vec2<i32> = x_200;
+let x_201 : vec2i = x_200;
 return;
 )";
     EXPECT_EQ(got, expected) << got;
diff --git a/src/tint/reader/spirv/namer.cc b/src/tint/reader/spirv/namer.cc
index 6c73a34..05a1b52 100644
--- a/src/tint/reader/spirv/namer.cc
+++ b/src/tint/reader/spirv/namer.cc
@@ -17,6 +17,7 @@
 #include <algorithm>
 #include <unordered_set>
 
+#include "src/tint/builtin/function.h"
 #include "src/tint/debug.h"
 #include "src/tint/utils/string_stream.h"
 
@@ -175,6 +176,9 @@
     for (const auto* reserved : kWGSLReservedWords) {
         name_to_id_[std::string(reserved)] = 0;
     }
+    for (const auto* builtin_function : builtin::kFunctionStrings) {
+        name_to_id_[std::string(builtin_function)] = 0;
+    }
 }
 
 Namer::~Namer() = default;
diff --git a/src/tint/reader/spirv/namer_test.cc b/src/tint/reader/spirv/namer_test.cc
index 65a19bf..e248360 100644
--- a/src/tint/reader/spirv/namer_test.cc
+++ b/src/tint/reader/spirv/namer_test.cc
@@ -15,6 +15,7 @@
 #include "src/tint/reader/spirv/namer.h"
 
 #include "gmock/gmock.h"
+#include "src/tint/builtin/function.h"
 #include "src/tint/utils/string_stream.h"
 
 namespace tint::reader::spirv {
@@ -393,5 +394,21 @@
                              "workgroup",
                          }));
 
+using SpvNamerBuiltinFunctionTest = ::testing::TestWithParam<const char*>;
+
+TEST_P(SpvNamerBuiltinFunctionTest, BuiltinFunctionsAreUsed) {
+    bool success;
+    utils::StringStream errors;
+    FailStream fail_stream(&success, &errors);
+    Namer namer(fail_stream);
+    const std::string builtin_fn = GetParam();
+    // Since it's a builtin function, it's marked as used, and we can't register an ID.
+    EXPECT_THAT(namer.FindUnusedDerivedName(builtin_fn), Eq(builtin_fn + "_1"));
+}
+
+INSTANTIATE_TEST_SUITE_P(SpvParserTest_BuiltinFunctions,
+                         SpvNamerBuiltinFunctionTest,
+                         ::testing::ValuesIn(builtin::kFunctionStrings));
+
 }  // namespace
 }  // namespace tint::reader::spirv
diff --git a/src/tint/reader/spirv/parser_impl.h b/src/tint/reader/spirv/parser_impl.h
index e0ef655..9c28aef 100644
--- a/src/tint/reader/spirv/parser_impl.h
+++ b/src/tint/reader/spirv/parser_impl.h
@@ -586,7 +586,7 @@
         uint32_t position_member_index = 0;
         /// The member index for the PointSize builtin within the struct.
         uint32_t pointsize_member_index = 0;
-        /// The ID for the member type, which should map to vec4<f32>.
+        /// The ID for the member type, which should map to vec4f.
         uint32_t position_member_type_id = 0;
         /// The ID of the type of a pointer to the struct in the Output storage
         /// class class.
@@ -863,7 +863,7 @@
     // Bookkeeping for the gl_Position builtin.
     // In Vulkan SPIR-V, it's the 0 member of the gl_PerVertex structure.
     // But in WGSL we make a module-scope variable:
-    //    [[position]] var<in> gl_Position : vec4<f32>;
+    //    [[position]] var<in> gl_Position : vec4f;
     // The builtin variable was detected if and only if the struct_id is non-zero.
     BuiltInPositionInfo builtin_position_;
 
diff --git a/src/tint/reader/spirv/parser_impl_function_decl_test.cc b/src/tint/reader/spirv/parser_impl_function_decl_test.cc
index 1c29649..e463551 100644
--- a/src/tint/reader/spirv/parser_impl_function_decl_test.cc
+++ b/src/tint/reader/spirv/parser_impl_function_decl_test.cc
@@ -122,7 +122,7 @@
     EXPECT_THAT(program_ast, HasSubstr(R"(
 struct main_out {
   @builtin(position)
-  x_2_1 : vec4<f32>,
+  x_2_1 : vec4f,
 }
 )")) << program_ast;
 
diff --git a/src/tint/reader/spirv/parser_impl_handle_test.cc b/src/tint/reader/spirv/parser_impl_handle_test.cc
index 15a62a6..0447110 100644
--- a/src/tint/reader/spirv/parser_impl_handle_test.cc
+++ b/src/tint/reader/spirv/parser_impl_handle_test.cc
@@ -1514,7 +1514,7 @@
 
 @group(2) @binding(1) var x_20 : texture_2d<f32>;)",
                         "textureGather(1i, x_20, x_10, coords12, "
-                        "vec2<i32>(u_offsets2d))"},
+                        "vec2i(u_offsets2d))"},
         // OpImageGather 2D Array
         ImageAccessCase{"%float 2D 0 1 0 1 Unknown",
                         "%result = OpImageGather "
@@ -1543,7 +1543,7 @@
 @group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
                         "textureGather(1i, x_20, x_10, coords123.xy, "
                         "i32(round(coords123.z)), "
-                        "vec2<i32>(u_offsets2d))"},
+                        "vec2i(u_offsets2d))"},
         // OpImageGather Cube
         ImageAccessCase{"%float Cube 0 0 0 1 Unknown",
                         "%result = OpImageGather "
@@ -1586,7 +1586,7 @@
 
 @group(2) @binding(1) var x_20 : texture_depth_2d;)",
                         "textureGather(x_20, x_10, coords12, "
-                        "vec2<i32>(u_offsets2d))"},
+                        "vec2i(u_offsets2d))"},
         // OpImageGather 2DDepth Array
         ImageAccessCase{"%float 2D 1 1 0 1 Unknown",
                         "%result = OpImageGather "
@@ -1615,7 +1615,7 @@
 @group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
                         "textureGather(x_20, x_10, coords123.xy, "
                         "i32(round(coords123.z)), "
-                        "vec2<i32>(u_offsets2d))"},
+                        "vec2i(u_offsets2d))"},
         // OpImageGather DepthCube
         ImageAccessCase{"%float Cube 1 0 0 1 Unknown",
                         "%result = OpImageGather "
@@ -1664,7 +1664,7 @@
 
 @group(2) @binding(1) var x_20 : texture_depth_2d;)",
                         "textureGatherCompare(x_20, x_10, coords12, 0.20000000298023223877f, "
-                        "vec2<i32>(u_offsets2d))"},
+                        "vec2i(u_offsets2d))"},
         // OpImageDrefGather 2DDepth Array
         ImageAccessCase{"%float 2D 1 1 0 1 Unknown",
                         "%result = OpImageDrefGather "
@@ -1693,7 +1693,7 @@
 @group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
                         "textureGatherCompare(x_20, x_10, coords123.xy, "
                         "i32(round(coords123.z)), 0.20000000298023223877f, "
-                        "vec2<i32>(u_offsets2d))"},
+                        "vec2i(u_offsets2d))"},
         // OpImageDrefGather DepthCube
         ImageAccessCase{"%float Cube 1 0 0 1 Unknown",
                         "%result = OpImageDrefGather "
@@ -1792,7 +1792,7 @@
                         R"(@group(0) @binding(0) var x_10 : sampler;
 
 @group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-                        R"(textureSampleBias(x_20, x_10, coords12, 7.0f, vec2<i32>(u_offsets2d))"},
+                        R"(textureSampleBias(x_20, x_10, coords12, 7.0f, vec2i(u_offsets2d))"},
         // OpImageSampleImplicitLod arrayed with Bias
         ImageAccessCase{
             "%float 2D 0 1 0 1 Unknown",
@@ -1827,7 +1827,7 @@
 @group(0) @binding(1) var x_30 : sampler_comparison;
 )",
                         R"(
-  let x_200 : vec4<f32> = vec4<f32>(textureSample(x_20, x_10, coords12), 0.0f, 0.0f, 0.0f);
+  let x_200 : vec4f = vec4f(textureSample(x_20, x_10, coords12), 0.0f, 0.0f, 0.0f);
   let x_210 : f32 = textureSampleCompare(x_20, x_30, coords12, 0.20000000298023223877f);
 )"}));
 
@@ -1981,7 +1981,7 @@
                         R"(@group(0) @binding(0) var x_10 : sampler;
 
 @group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-                        R"(textureSampleLevel(x_20, x_10, coords12, 0.0f, vec2<i32>(u_offsets2d))"},
+                        R"(textureSampleLevel(x_20, x_10, coords12, 0.0f, vec2i(u_offsets2d))"},
 
         // OpImageSampleExplicitLod arrayed - using Lod and ConstOffset
         ImageAccessCase{
@@ -2037,7 +2037,7 @@
             R"(@group(0) @binding(0) var x_10 : sampler;
 
 @group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            R"(textureSampleGrad(x_20, x_10, coords12, vf12, vf21, vec2<i32>(u_offsets2d))"},
+            R"(textureSampleGrad(x_20, x_10, coords12, vf12, vf21, vec2i(u_offsets2d))"},
 
         // OpImageSampleExplicitLod arrayed - using Grad and ConstOffset
         ImageAccessCase{
@@ -2060,7 +2060,7 @@
             R"(@group(0) @binding(0) var x_10 : sampler;
 
 @group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
-            R"(textureSampleGrad(x_20, x_10, coords123.xy, i32(round(coords123.z)), vf12, vf21, vec2<i32>(u_offsets2d)))"}));
+            R"(textureSampleGrad(x_20, x_10, coords123.xy, i32(round(coords123.z)), vf12, vf21, vec2i(u_offsets2d)))"}));
 
 // Test crbug.com/378:
 // In WGSL, sampling from depth texture with explicit level of detail
@@ -2088,7 +2088,7 @@
 
 @group(2) @binding(1) var x_20 : texture_depth_2d;
 )",
-         R"(vec4<f32>(textureSampleLevel(x_20, x_10, vf12, i32(f1)), 0.0f, 0.0f, 0.0f))"}}));
+         R"(vec4f(textureSampleLevel(x_20, x_10, vf12, i32(f1)), 0.0f, 0.0f, 0.0f))"}}));
 
 /////
 // Projection sampling
@@ -2194,7 +2194,7 @@
             R"(@group(0) @binding(0) var x_10 : sampler;
 
 @group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-            R"(textureSampleBias(x_20, x_10, (coords123.xy / coords123.z), 7.0f, vec2<i32>(u_offsets2d)))"}));
+            R"(textureSampleBias(x_20, x_10, (coords123.xy / coords123.z), 7.0f, vec2i(u_offsets2d)))"}));
 
 INSTANTIATE_TEST_SUITE_P(
     ImageSampleProjExplicitLod_Lod,
@@ -2259,9 +2259,9 @@
 @group(2) @binding(1) var x_20 : texture_depth_2d;
 )",
             // Sampling the depth texture yields an f32, but the
-            // SPIR-V operation yiedls vec4<f32>, so fill out the
+            // SPIR-V operation yiedls vec4f, so fill out the
             // remaining components with 0.
-            R"(vec4<f32>(textureSample(x_20, x_10, (coords123.xy / coords123.z)), 0.0f, 0.0f, 0.0f))"}));
+            R"(vec4f(textureSample(x_20, x_10, (coords123.xy / coords123.z)), 0.0f, 0.0f, 0.0f))"}));
 
 INSTANTIATE_TEST_SUITE_P(
     ImageSampleProjDrefImplicitLod,
@@ -2318,7 +2318,7 @@
 
 @group(2) @binding(1) var x_20 : texture_depth_2d;
 )",
-            R"(textureSampleCompareLevel(x_20, x_10, (coords123.xy / coords123.z), 0.20000000298023223877f, 0.0f, vec2<i32>(3i, 4i)))"}));
+            R"(textureSampleCompareLevel(x_20, x_10, (coords123.xy / coords123.z), 0.20000000298023223877f, 0.0f, vec2i(3i, 4i)))"}));
 
 /////
 // End projection sampling
@@ -2411,15 +2411,15 @@
         // Source 1 component
         {"%float 2D 0 0 0 2 R32f", "OpImageWrite %im %vi12 %f1",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32float, write>;)",
-         "textureStore(x_20, vi12, vec4<f32>(f1, 0.0f, 0.0f, 0.0f));"},
+         "textureStore(x_20, vi12, vec4f(f1, 0.0f, 0.0f, 0.0f));"},
         // Source 2 component, dest 1 component
         {"%float 2D 0 0 0 2 R32f", "OpImageWrite %im %vi12 %vf12",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32float, write>;)",
-         "textureStore(x_20, vi12, vec4<f32>(vf12, 0.0f, 0.0f));"},
+         "textureStore(x_20, vi12, vec4f(vf12, 0.0f, 0.0f));"},
         // Source 3 component, dest 1 component
         {"%float 2D 0 0 0 2 R32f", "OpImageWrite %im %vi12 %vf123",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32float, write>;)",
-         "textureStore(x_20, vi12, vec4<f32>(vf123, 0.0f));"},
+         "textureStore(x_20, vi12, vec4f(vf123, 0.0f));"},
         // Source 4 component, dest 1 component
         {"%float 2D 0 0 0 2 R32f", "OpImageWrite %im %vi12 %vf1234",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32float, write>;)",
@@ -2427,11 +2427,11 @@
         // Source 2 component, dest 2 component
         {"%float 2D 0 0 0 2 Rg32f", "OpImageWrite %im %vi12 %vf12",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rg32float, write>;)",
-         "textureStore(x_20, vi12, vec4<f32>(vf12, 0.0f, 0.0f));"},
+         "textureStore(x_20, vi12, vec4f(vf12, 0.0f, 0.0f));"},
         // Source 3 component, dest 2 component
         {"%float 2D 0 0 0 2 Rg32f", "OpImageWrite %im %vi12 %vf123",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rg32float, write>;)",
-         "textureStore(x_20, vi12, vec4<f32>(vf123, 0.0f));"},
+         "textureStore(x_20, vi12, vec4f(vf123, 0.0f));"},
         // Source 4 component, dest 2 component
         {"%float 2D 0 0 0 2 Rg32f", "OpImageWrite %im %vi12 %vf1234",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rg32float, write>;)",
@@ -2451,15 +2451,15 @@
         // Source 1 component
         {"%uint 2D 0 0 0 2 R32ui", "OpImageWrite %im %vi12 %u1",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32uint, write>;)",
-         "textureStore(x_20, vi12, vec4<u32>(u1, 0u, 0u, 0u));"},
+         "textureStore(x_20, vi12, vec4u(u1, 0u, 0u, 0u));"},
         // Source 2 component, dest 1 component
         {"%uint 2D 0 0 0 2 R32ui", "OpImageWrite %im %vi12 %vu12",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32uint, write>;)",
-         "textureStore(x_20, vi12, vec4<u32>(vu12, 0u, 0u));"},
+         "textureStore(x_20, vi12, vec4u(vu12, 0u, 0u));"},
         // Source 3 component, dest 1 component
         {"%uint 2D 0 0 0 2 R32ui", "OpImageWrite %im %vi12 %vu123",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32uint, write>;)",
-         "textureStore(x_20, vi12, vec4<u32>(vu123, 0u));"},
+         "textureStore(x_20, vi12, vec4u(vu123, 0u));"},
         // Source 4 component, dest 1 component
         {"%uint 2D 0 0 0 2 R32ui", "OpImageWrite %im %vi12 %vu1234",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32uint, write>;)",
@@ -2467,11 +2467,11 @@
         // Source 2 component, dest 2 component
         {"%uint 2D 0 0 0 2 Rg32ui", "OpImageWrite %im %vi12 %vu12",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rg32uint, write>;)",
-         "textureStore(x_20, vi12, vec4<u32>(vu12, 0u, 0u));"},
+         "textureStore(x_20, vi12, vec4u(vu12, 0u, 0u));"},
         // Source 3 component, dest 2 component
         {"%uint 2D 0 0 0 2 Rg32ui", "OpImageWrite %im %vi12 %vu123",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rg32uint, write>;)",
-         "textureStore(x_20, vi12, vec4<u32>(vu123, 0u));"},
+         "textureStore(x_20, vi12, vec4u(vu123, 0u));"},
         // Source 4 component, dest 2 component
         {"%uint 2D 0 0 0 2 Rg32ui", "OpImageWrite %im %vi12 %vu1234",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rg32uint, write>;)",
@@ -2491,15 +2491,15 @@
         // Source 1 component
         {"%int 2D 0 0 0 2 R32i", "OpImageWrite %im %vi12 %i1",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32sint, write>;)",
-         "textureStore(x_20, vi12, vec4<i32>(i1, 0i, 0i, 0i));"},
+         "textureStore(x_20, vi12, vec4i(i1, 0i, 0i, 0i));"},
         // Source 2 component, dest 1 component
         {"%int 2D 0 0 0 2 R32i", "OpImageWrite %im %vi12 %vi12",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32sint, write>;)",
-         "textureStore(x_20, vi12, vec4<i32>(vi12, 0i, 0i));"},
+         "textureStore(x_20, vi12, vec4i(vi12, 0i, 0i));"},
         // Source 3 component, dest 1 component
         {"%int 2D 0 0 0 2 R32i", "OpImageWrite %im %vi12 %vi123",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32sint, write>;)",
-         "textureStore(x_20, vi12, vec4<i32>(vi123, 0i));"},
+         "textureStore(x_20, vi12, vec4i(vi123, 0i));"},
         // Source 4 component, dest 1 component
         {"%int 2D 0 0 0 2 R32i", "OpImageWrite %im %vi12 %vi1234",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32sint, write>;)",
@@ -2507,11 +2507,11 @@
         // Source 2 component, dest 2 component
         {"%int 2D 0 0 0 2 Rg32i", "OpImageWrite %im %vi12 %vi12",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rg32sint, write>;)",
-         "textureStore(x_20, vi12, vec4<i32>(vi12, 0i, 0i));"},
+         "textureStore(x_20, vi12, vec4i(vi12, 0i, 0i));"},
         // Source 3 component, dest 2 component
         {"%int 2D 0 0 0 2 Rg32i", "OpImageWrite %im %vi12 %vi123",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rg32sint, write>;)",
-         "textureStore(x_20, vi12, vec4<i32>(vi123, 0i));"},
+         "textureStore(x_20, vi12, vec4i(vi123, 0i));"},
         // Source 4 component, dest 2 component
         {"%int 2D 0 0 0 2 Rg32i", "OpImageWrite %im %vi12 %vi1234",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<rg32sint, write>;)",
@@ -2635,11 +2635,11 @@
         // Source unsigned, dest unsigned
         {"%uint 2D 0 0 0 2 R32ui", "OpImageWrite %im %vi12 %vu12",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32uint, write>;)",
-         R"(textureStore(x_20, vi12, vec4<u32>(vu12, 0u, 0u)))"},
+         R"(textureStore(x_20, vi12, vec4u(vu12, 0u, 0u)))"},
         // Source signed, dest signed
         {"%int 2D 0 0 0 2 R32i", "OpImageWrite %im %vi12 %vi12",
          R"(@group(2) @binding(1) var x_20 : texture_storage_2d<r32sint, write>;)",
-         R"(textureStore(x_20, vi12, vec4<i32>(vi12, 0i, 0i)))"}}));
+         R"(textureStore(x_20, vi12, vec4i(vi12, 0i, 0i)))"}}));
 
 INSTANTIATE_TEST_SUITE_P(
     ImageFetch_OptionalParams,
@@ -2649,20 +2649,20 @@
         // Level of detail is injected for sampled texture
         {"%float 2D 0 0 0 1 Unknown", "%99 = OpImageFetch %v4float %im %vi12",
          R"(@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-         R"(let x_99 : vec4<f32> = textureLoad(x_20, vi12, 0i);)"},
+         R"(let x_99 : vec4f = textureLoad(x_20, vi12, 0i);)"},
         // OpImageFetch with explicit level, on sampled texture
         {"%float 2D 0 0 0 1 Unknown", "%99 = OpImageFetch %v4float %im %vi12 Lod %int_3",
          R"(@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-         R"(let x_99 : vec4<f32> = textureLoad(x_20, vi12, 3i);)"},
+         R"(let x_99 : vec4f = textureLoad(x_20, vi12, 3i);)"},
         // OpImageFetch with no extra params, on depth texture
         // Level of detail is injected for depth texture
         {"%float 2D 1 0 0 1 Unknown", "%99 = OpImageFetch %v4float %im %vi12",
          R"(@group(2) @binding(1) var x_20 : texture_depth_2d;)",
-         R"(let x_99 : vec4<f32> = vec4<f32>(textureLoad(x_20, vi12, 0i), 0.0f, 0.0f, 0.0f);)"},
+         R"(let x_99 : vec4f = vec4f(textureLoad(x_20, vi12, 0i), 0.0f, 0.0f, 0.0f);)"},
         // OpImageFetch with extra params, on depth texture
         {"%float 2D 1 0 0 1 Unknown", "%99 = OpImageFetch %v4float %im %vi12 Lod %int_3",
          R"(@group(2) @binding(1) var x_20 : texture_depth_2d;)",
-         R"(let x_99 : vec4<f32> = vec4<f32>(textureLoad(x_20, vi12, 3i), 0.0f, 0.0f, 0.0f);)"}}));
+         R"(let x_99 : vec4f = vec4f(textureLoad(x_20, vi12, 3i), 0.0f, 0.0f, 0.0f);)"}}));
 
 INSTANTIATE_TEST_SUITE_P(
     ImageFetch_Depth,
@@ -2675,7 +2675,7 @@
         // ImageFetch on depth image.
         {"%float 2D 1 0 0 1 Unknown", "%99 = OpImageFetch %v4float %im %vi12 ",
          R"(@group(2) @binding(1) var x_20 : texture_depth_2d;)",
-         R"(let x_99 : vec4<f32> = vec4<f32>(textureLoad(x_20, vi12, 0i), 0.0f, 0.0f, 0.0f);)"}}));
+         R"(let x_99 : vec4f = vec4f(textureLoad(x_20, vi12, 0i), 0.0f, 0.0f, 0.0f);)"}}));
 
 INSTANTIATE_TEST_SUITE_P(
     ImageFetch_DepthMultisampled,
@@ -2688,7 +2688,7 @@
         // ImageFetch on multisampled depth image.
         {"%float 2D 1 0 1 1 Unknown", "%99 = OpImageFetch %v4float %im %vi12 Sample %i1",
          R"(@group(2) @binding(1) var x_20 : texture_depth_multisampled_2d;)",
-         R"(let x_99 : vec4<f32> = vec4<f32>(textureLoad(x_20, vi12, i1), 0.0f, 0.0f, 0.0f);)"}}));
+         R"(let x_99 : vec4f = vec4f(textureLoad(x_20, vi12, i1), 0.0f, 0.0f, 0.0f);)"}}));
 
 INSTANTIATE_TEST_SUITE_P(ImageFetch_Multisampled,
                          SpvParserHandleTest_ImageAccessTest,
@@ -2703,7 +2703,7 @@
                              {"%float 2D 0 0 1 1 Unknown",
                               "%99 = OpImageFetch %v4float %im %vi12 Sample %i1",
                               R"(@group(2) @binding(1) var x_20 : texture_multisampled_2d<f32>;)",
-                              R"(let x_99 : vec4<f32> = textureLoad(x_20, vi12, i1);)"}}));
+                              R"(let x_99 : vec4f = textureLoad(x_20, vi12, i1);)"}}));
 
 INSTANTIATE_TEST_SUITE_P(ImageFetch_Multisampled_ConvertSampleOperand,
                          SpvParserHandleTest_ImageAccessTest,
@@ -2711,7 +2711,7 @@
                              {"%float 2D 0 0 1 1 Unknown",
                               "%99 = OpImageFetch %v4float %im %vi12 Sample %u1",
                               R"(@group(2) @binding(1) var x_20 : texture_multisampled_2d<f32>;)",
-                              R"(let x_99 : vec4<f32> = textureLoad(x_20, vi12, i32(u1));)"}}));
+                              R"(let x_99 : vec4f = textureLoad(x_20, vi12, i32(u1));)"}}));
 
 INSTANTIATE_TEST_SUITE_P(ConvertResultSignedness,
                          SpvParserHandleTest_SampledImageAccessTest,
@@ -2733,11 +2733,11 @@
                              // OpImageFetch requires no conversion, float -> v4float
                              {"%float 2D 0 0 0 1 Unknown", "%99 = OpImageFetch %v4float %im %vi12",
                               R"(@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-                              R"(let x_99 : vec4<f32> = textureLoad(x_20, vi12, 0i);)"},
+                              R"(let x_99 : vec4f = textureLoad(x_20, vi12, 0i);)"},
                              // OpImageFetch requires no conversion, uint -> v4uint
                              {"%uint 2D 0 0 0 1 Unknown", "%99 = OpImageFetch %v4uint %im %vi12",
                               R"(@group(2) @binding(1) var x_20 : texture_2d<u32>;)",
-                              R"(let x_99 : vec4<u32> = textureLoad(x_20, vi12, 0i);)"},
+                              R"(let x_99 : vec4u = textureLoad(x_20, vi12, 0i);)"},
                              // OpImageFetch requires conversion, uint -> v4int
                              // is invalid SPIR-V:
                              // "Expected Image 'Sampled Type' to be the same as Result Type
@@ -2746,7 +2746,7 @@
                              // OpImageFetch requires no conversion, int -> v4int
                              {"%int 2D 0 0 0 1 Unknown", "%99 = OpImageFetch %v4int %im %vi12",
                               R"(@group(2) @binding(1) var x_20 : texture_2d<i32>;)",
-                              R"(let x_99 : vec4<i32> = textureLoad(x_20, vi12, 0i);)"},
+                              R"(let x_99 : vec4i = textureLoad(x_20, vi12, 0i);)"},
                              // OpImageFetch requires conversion, int -> v4uint
                              // is invalid SPIR-V:
                              // "Expected Image 'Sampled Type' to be the same as Result Type
@@ -2759,11 +2759,11 @@
                              // OpImageRead requires no conversion, float -> v4float
                              {"%float 2D 0 0 0 2 Rgba32f", "%99 = OpImageRead %v4float %im %vi12",
                               R"(@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-                              R"(let x_99 : vec4<f32> = textureLoad(x_20, vi12, 0i);)"},
+                              R"(let x_99 : vec4f = textureLoad(x_20, vi12, 0i);)"},
                              // OpImageRead requires no conversion, uint -> v4uint
                              {"%uint 2D 0 0 0 2 Rgba32ui", "%99 = OpImageRead %v4uint %im %vi12",
                               R"(@group(2) @binding(1) var x_20 : texture_2d<u32>;)",
-                              R"(let x_99 : vec4<u32> = textureLoad(x_20, vi12, 0i);)"},
+                              R"(let x_99 : vec4u = textureLoad(x_20, vi12, 0i);)"},
 
                              // OpImageRead requires conversion, uint -> v4int
                              // is invalid SPIR-V:
@@ -2773,7 +2773,7 @@
                              // OpImageRead requires no conversion, int -> v4int
                              {"%int 2D 0 0 0 2 Rgba32i", "%99 = OpImageRead %v4int %im %vi12",
                               R"(@group(2) @binding(1) var x_20 : texture_2d<i32>;)",
-                              R"(let x_99 : vec4<i32> = textureLoad(x_20, vi12, 0i);)"},
+                              R"(let x_99 : vec4i = textureLoad(x_20, vi12, 0i);)"},
 
                              // OpImageRead requires conversion, int -> v4uint
                              // is invalid SPIR-V:
@@ -2792,7 +2792,7 @@
                               R"(@group(0) @binding(0) var x_10 : sampler;
 
 @group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-                              R"(let x_99 : vec4<f32> = textureSample(x_20, x_10, vf12);)"}}));
+                              R"(let x_99 : vec4f = textureSample(x_20, x_10, vf12);)"}}));
 
 INSTANTIATE_TEST_SUITE_P(ImageQuerySize_NonArrayed_SignedResult,
                          // ImageQuerySize requires storage image or multisampled
@@ -2813,19 +2813,19 @@
                               "%98 = OpImageRead %v4float %im %vi12\n",  // Implicitly mark as
                                                                          // NonWritable
                               R"(@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-                              R"(let x_99 : vec2<i32> = vec2<i32>(textureDimensions(x_20))"},
+                              R"(let x_99 : vec2i = vec2i(textureDimensions(x_20))"},
                              // 3D storage image
                              {"%float 3D 0 0 0 2 Rgba32f",
                               "%99 = OpImageQuerySize %v3int %im \n"
                               "%98 = OpImageRead %v4float %im %vi123\n",  // Implicitly mark as
                                                                           // NonWritable
                               R"(@group(2) @binding(1) var x_20 : texture_3d<f32>;)",
-                              R"(let x_99 : vec3<i32> = vec3<i32>(textureDimensions(x_20));)"},
+                              R"(let x_99 : vec3i = vec3i(textureDimensions(x_20));)"},
 
                              // Multisampled
                              {"%float 2D 0 0 1 1 Unknown", "%99 = OpImageQuerySize %v2int %im \n",
                               R"(@group(2) @binding(1) var x_20 : texture_multisampled_2d<f32>;)",
-                              R"(let x_99 : vec2<i32> = vec2<i32>(textureDimensions(x_20));)"}}));
+                              R"(let x_99 : vec2i = vec2i(textureDimensions(x_20));)"}}));
 
 INSTANTIATE_TEST_SUITE_P(
     ImageQuerySize_Arrayed_SignedResult,
@@ -2841,7 +2841,7 @@
          "%99 = OpImageQuerySize %v3int %im \n"
          "%98 = OpImageRead %v4float %im %vi123\n",
          R"(@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
-         R"(let x_99 : vec3<i32> = vec3<i32>(vec3<u32>(textureDimensions(x_20), textureNumLayers(x_20)));)"}
+         R"(let x_99 : vec3i = vec3i(vec3u(textureDimensions(x_20), textureNumLayers(x_20)));)"}
         // 3D array storage image doesn't exist.
 
         // Multisampled array
@@ -2862,27 +2862,27 @@
         // 2D
         {"%float 2D 0 0 0 1 Unknown", "%99 = OpImageQuerySizeLod %v2int %im %i1\n",
          R"(@group(2) @binding(1) var x_20 : texture_2d<f32>;)",
-         R"(let x_99 : vec2<i32> = vec2<i32>(textureDimensions(x_20, i1));)"},
+         R"(let x_99 : vec2i = vec2i(textureDimensions(x_20, i1));)"},
 
         // 3D
         {"%float 3D 0 0 0 1 Unknown", "%99 = OpImageQuerySizeLod %v3int %im %i1\n",
          R"(@group(2) @binding(1) var x_20 : texture_3d<f32>;)",
-         R"(let x_99 : vec3<i32> = vec3<i32>(textureDimensions(x_20, i1));)"},
+         R"(let x_99 : vec3i = vec3i(textureDimensions(x_20, i1));)"},
 
         // Cube
         {"%float Cube 0 0 0 1 Unknown", "%99 = OpImageQuerySizeLod %v2int %im %i1\n",
          R"(@group(2) @binding(1) var x_20 : texture_cube<f32>;)",
-         R"(let x_99 : vec2<i32> = vec2<i32>(textureDimensions(x_20, i1).xy);)"},
+         R"(let x_99 : vec2i = vec2i(textureDimensions(x_20, i1).xy);)"},
 
         // Depth 2D
         {"%float 2D 1 0 0 1 Unknown", "%99 = OpImageQuerySizeLod %v2int %im %i1\n",
          R"(@group(2) @binding(1) var x_20 : texture_depth_2d;)",
-         R"(let x_99 : vec2<i32> = vec2<i32>(textureDimensions(x_20, i1));)"},
+         R"(let x_99 : vec2i = vec2i(textureDimensions(x_20, i1));)"},
 
         // Depth Cube
         {"%float Cube 1 0 0 1 Unknown", "%99 = OpImageQuerySizeLod %v2int %im %i1\n",
          R"(@group(2) @binding(1) var x_20 : texture_depth_cube;)",
-         R"(let x_99 : vec2<i32> = vec2<i32>(textureDimensions(x_20, i1).xy);)"}}));
+         R"(let x_99 : vec2i = vec2i(textureDimensions(x_20, i1).xy);)"}}));
 
 INSTANTIATE_TEST_SUITE_P(
     ImageQuerySizeLod_Arrayed_SignedResult_SignedLevel,
@@ -2897,7 +2897,7 @@
         // 2D array
         {"%float 2D 0 1 0 1 Unknown", "%99 = OpImageQuerySizeLod %v3int %im %i1\n",
          R"(@group(2) @binding(1) var x_20 : texture_2d_array<f32>;)",
-         R"(let x_99 : vec3<i32> = vec3<i32>(vec3<u32>(textureDimensions(x_20, i1), textureNumLayers(x_20)));)"},
+         R"(let x_99 : vec3i = vec3i(vec3u(textureDimensions(x_20, i1), textureNumLayers(x_20)));)"},
 
         // There is no 3D array
 
@@ -2908,12 +2908,12 @@
         // https://github.com/gpuweb/gpuweb/issues/1345
         {"%float Cube 0 1 0 1 Unknown", "%99 = OpImageQuerySizeLod %v3int %im %i1\n",
          R"(@group(2) @binding(1) var x_20 : texture_cube_array<f32>;)",
-         R"(let x_99 : vec3<i32> = vec3<i32>(vec3<u32>(textureDimensions(x_20, i1).xy, textureNumLayers(x_20)));)"},
+         R"(let x_99 : vec3i = vec3i(vec3u(textureDimensions(x_20, i1).xy, textureNumLayers(x_20)));)"},
 
         // Depth 2D array
         {"%float 2D 1 1 0 1 Unknown", "%99 = OpImageQuerySizeLod %v3int %im %i1\n",
          R"(@group(2) @binding(1) var x_20 : texture_depth_2d_array;)",
-         R"(let x_99 : vec3<i32> = vec3<i32>(vec3<u32>(textureDimensions(x_20, i1), textureNumLayers(x_20)));)"},
+         R"(let x_99 : vec3i = vec3i(vec3u(textureDimensions(x_20, i1), textureNumLayers(x_20)));)"},
 
         // Depth Cube Array
         //
@@ -2922,7 +2922,7 @@
         // https://github.com/gpuweb/gpuweb/issues/1345
         {"%float Cube 1 1 0 1 Unknown", "%99 = OpImageQuerySizeLod %v3int %im %i1\n",
          R"(@group(2) @binding(1) var x_20 : texture_depth_cube_array;)",
-         R"(let x_99 : vec3<i32> = vec3<i32>(vec3<u32>(textureDimensions(x_20, i1).xy, textureNumLayers(x_20)));)"}}));
+         R"(let x_99 : vec3i = vec3i(vec3u(textureDimensions(x_20, i1).xy, textureNumLayers(x_20)));)"}}));
 
 INSTANTIATE_TEST_SUITE_P(
     // textureDimensions accepts both signed and unsigned the level-of-detail values.
@@ -3414,12 +3414,9 @@
         {"%float 2D 0 0 0 1 Unknown",
          "%result = OpImageFetch %v4float %im %vu12",
          "",
-         {"vec2<i32>(vu12)"}},
-        {"%float 2D 0 0 0 2 R32f",
-         "%result = OpImageRead %v4float %im %vu12",
-         "",
-         {"vec2<i32>(vu12)"}},
-        {"%float 2D 0 0 0 2 R32f", "OpImageWrite %im %vu12 %vf1234", "", {"vec2<i32>(vu12)"}}}));
+         {"vec2i(vu12)"}},
+        {"%float 2D 0 0 0 2 R32f", "%result = OpImageRead %v4float %im %vu12", "", {"vec2i(vu12)"}},
+        {"%float 2D 0 0 0 2 R32f", "OpImageWrite %im %vu12 %vf1234", "", {"vec2i(vu12)"}}}));
 
 INSTANTIATE_TEST_SUITE_P(ConvertUintCoords_Arrayed,
                          // In SPIR-V, image read, fetch, and write use integer coordinates.
@@ -3429,15 +3426,15 @@
                              {"%float 2D 0 1 0 1 Unknown",
                               "%result = OpImageFetch %v4float %im %vu123",
                               "",
-                              {"vec2<i32>(vu123.xy)", "i32(vu123.z)"}},
+                              {"vec2i(vu123.xy)", "i32(vu123.z)"}},
                              {"%float 2D 0 1 0 2 R32f",
                               "%result = OpImageRead %v4float %im %vu123",
                               "",
-                              {"vec2<i32>(vu123.xy)", "i32(vu123.z)"}},
+                              {"vec2i(vu123.xy)", "i32(vu123.z)"}},
                              {"%float 2D 0 1 0 2 R32f",
                               "OpImageWrite %im %vu123 %vf1234",
                               "",
-                              {"vec2<i32>(vu123.xy)", "i32(vu123.z)"}}}));
+                              {"vec2i(vu123.xy)", "i32(vu123.z)"}}}));
 
 INSTANTIATE_TEST_SUITE_P(
     BadInstructions,
@@ -3817,9 +3814,9 @@
     EXPECT_TRUE(p->error().empty()) << p->error();
     auto ast_body = fe.ast_body();
     const auto got = test::ToString(p->program(), ast_body);
-    auto* expect = R"(var var_1 : vec4<f32>;
-let x_22 : vec4<f32> = textureSample(x_2, x_3, vec2<f32>());
-let x_26 : vec4<f32> = textureSample(x_2, x_3, vec2<f32>());
+    auto* expect = R"(var var_1 : vec4f;
+let x_22 : vec4f = textureSample(x_2, x_3, vec2f());
+let x_26 : vec4f = textureSample(x_2, x_3, vec2f());
 var_1 = (x_22 + x_26);
 return;
 )";
@@ -3893,7 +3890,7 @@
     auto* expect = R"(switch(0i) {
   default: {
     if (true) {
-      let x_24 : vec4<f32> = textureSample(var_im, var_s, vec2<f32>());
+      let x_24 : vec4f = textureSample(var_im, var_s, vec2f());
     }
   }
 }
@@ -3980,7 +3977,7 @@
 x_900 = 0.0f;
 if (true) {
   if (true) {
-    let x_18 : vec4<f32> = textureSample(x_20, x_10, x_900);
+    let x_18 : vec4f = textureSample(x_20, x_10, x_900);
   }
 }
 return;
@@ -4063,11 +4060,11 @@
     EXPECT_TRUE(p->error().empty()) << p->error();
     auto ast_body = fe.ast_body();
     const auto got = test::ToString(p->program(), ast_body);
-    auto* expect = R"(var x_900 : vec2<f32>;
-x_900 = vec2<f32>();
+    auto* expect = R"(var x_900 : vec2f;
+x_900 = vec2f();
 if (true) {
   if (true) {
-    let x_19 : vec4<f32> = textureSample(x_20, x_10, x_900.x);
+    let x_19 : vec4f = textureSample(x_20, x_10, x_900.x);
   }
 }
 return;
@@ -4138,9 +4135,9 @@
     EXPECT_TRUE(p->error().empty()) << p->error();
     auto ast_body = fe.ast_body();
     const auto got = test::ToString(p->program(), ast_body);
-    auto* expect = R"(var x_24 : vec2<f32>;
+    auto* expect = R"(var x_24 : vec2f;
 var x_26 : i32;
-x_24 = vec2<f32>(0.0f, 0.0f);
+x_24 = vec2f(0.0f, 0.0f);
 x_26 = 0i;
 loop {
   var x_27 : i32;
@@ -4151,11 +4148,11 @@
 
   continuing {
     x_27 = (x_26 + 1i);
-    x_24 = vec2<f32>(1.0f, 1.0f);
+    x_24 = vec2f(1.0f, 1.0f);
     x_26 = x_27;
   }
 }
-textureStore(Output2Texture2D, vec2<i32>(vec2<u32>(1u, 1u)), vec4<f32>(x_24, 0.0f, 0.0f));
+textureStore(Output2Texture2D, vec2i(vec2u(1u, 1u)), vec4f(x_24, 0.0f, 0.0f));
 return;
 )";
     ASSERT_EQ(expect, got);
@@ -4230,7 +4227,7 @@
   }
 }
 let x_21 : f32 = select(0.0f, x_14, (x_14 > 1.0f));
-x_1 = vec4<f32>(x_21, x_21, x_21, x_21);
+x_1 = vec4f(x_21, x_21, x_21, x_21);
 return;
 )";
     ASSERT_EQ(expect, got);
diff --git a/src/tint/reader/spirv/parser_impl_module_var_test.cc b/src/tint/reader/spirv/parser_impl_module_var_test.cc
index 2ae0435..871edf7 100644
--- a/src/tint/reader/spirv/parser_impl_module_var_test.cc
+++ b/src/tint/reader/spirv/parser_impl_module_var_test.cc
@@ -347,7 +347,7 @@
     EXPECT_TRUE(p->BuildAndParseInternalModule());
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_THAT(module_str, HasSubstr("gl_Position = vec4<f32>();")) << module_str;
+    EXPECT_THAT(module_str, HasSubstr("gl_Position = vec4f();")) << module_str;
 }
 
 TEST_F(SpvModuleScopeVarParserTest, BuiltinPosition_StorePosition_PerVertexStructOutOfOrderDecl) {
@@ -389,7 +389,7 @@
     EXPECT_TRUE(p->BuildAndParseInternalModule());
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_THAT(module_str, HasSubstr("gl_Position = vec4<f32>();")) << module_str;
+    EXPECT_THAT(module_str, HasSubstr("gl_Position = vec4f();")) << module_str;
 }
 
 TEST_F(SpvModuleScopeVarParserTest, BuiltinPosition_StorePositionMember_OneAccessChain) {
@@ -449,7 +449,7 @@
     EXPECT_TRUE(p->BuildAndParseInternalModule());
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_EQ(module_str, R"(var<private> gl_Position : vec4<f32>;
+    EXPECT_EQ(module_str, R"(var<private> gl_Position : vec4f;
 
 fn main_1() {
   return;
@@ -457,7 +457,7 @@
 
 struct main_out {
   @builtin(position)
-  gl_Position : vec4<f32>,
+  gl_Position : vec4f,
 }
 
 @vertex
@@ -507,7 +507,7 @@
     const auto module_str = test::ToString(p->program());
     EXPECT_EQ(module_str, R"(var<private> x_900 : f32;
 
-var<private> gl_Position : vec4<f32>;
+var<private> gl_Position : vec4f;
 
 fn main_1() {
   x_900 = 1.0f;
@@ -516,7 +516,7 @@
 
 struct main_out {
   @builtin(position)
-  gl_Position : vec4<f32>,
+  gl_Position : vec4f,
 }
 
 @vertex
@@ -564,7 +564,7 @@
     EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_EQ(module_str, R"(var<private> gl_Position : vec4<f32>;
+    EXPECT_EQ(module_str, R"(var<private> gl_Position : vec4f;
 
 fn main_1() {
   return;
@@ -572,7 +572,7 @@
 
 struct main_out {
   @builtin(position)
-  gl_Position : vec4<f32>,
+  gl_Position : vec4f,
 }
 
 @vertex
@@ -623,7 +623,7 @@
     EXPECT_TRUE(p->BuildAndParseInternalModule());
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_EQ(module_str, R"(var<private> x_2 : vec4<f32>;
+    EXPECT_EQ(module_str, R"(var<private> x_2 : vec4f;
 
 fn main_1() {
   return;
@@ -631,7 +631,7 @@
 
 struct main_out {
   @builtin(position)
-  x_2_1 : vec4<f32>,
+  x_2_1 : vec4f,
 }
 
 @vertex
@@ -676,7 +676,7 @@
     EXPECT_TRUE(p->BuildAndParseInternalModule());
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_EQ(module_str, R"(var<private> x_2 : vec4<f32>;
+    EXPECT_EQ(module_str, R"(var<private> x_2 : vec4f;
 
 var<private> x_900 : f32;
 
@@ -687,7 +687,7 @@
 
 struct main_out {
   @builtin(position)
-  x_2_1 : vec4<f32>,
+  x_2_1 : vec4f,
 }
 
 @vertex
@@ -735,7 +735,7 @@
     EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_EQ(module_str, R"(var<private> x_2 : vec4<f32>;
+    EXPECT_EQ(module_str, R"(var<private> x_2 : vec4f;
 
 fn main_1() {
   return;
@@ -743,7 +743,7 @@
 
 struct main_out {
   @builtin(position)
-  x_2_1 : vec4<f32>,
+  x_2_1 : vec4f,
 }
 
 @vertex
@@ -771,7 +771,7 @@
     EXPECT_TRUE(p->BuildAndParseInternalModule()) << p->error();
     EXPECT_TRUE(p->error().empty()) << p->error();
     const auto module_str = test::ToString(p->program());
-    EXPECT_EQ(module_str, R"(var<private> x_2 : vec4<f32>;
+    EXPECT_EQ(module_str, R"(var<private> x_2 : vec4f;
 
 fn main_1() {
   return;
@@ -779,7 +779,7 @@
 
 struct main_out {
   @builtin(position)
-  x_2_1 : vec4<f32>,
+  x_2_1 : vec4f,
 }
 
 @vertex
@@ -956,7 +956,7 @@
     ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : vec2<f32> = vec2<f32>(1.5f, 2.0f);"));
+    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : vec2f = vec2f(1.5f, 2.0f);"));
 }
 
 TEST_F(SpvModuleScopeVarParserTest, VectorBoolNullInitializer) {
@@ -995,7 +995,7 @@
     ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : vec2<u32> = vec2<u32>();"));
+    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : vec2u = vec2u();"));
 }
 
 TEST_F(SpvModuleScopeVarParserTest, VectorUintUndefInitializer) {
@@ -1007,7 +1007,7 @@
     ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : vec2<u32> = vec2<u32>();"));
+    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : vec2u = vec2u();"));
 
     // This example module emits ok, but is not valid SPIR-V in the first place.
     p->DeliberatelyInvalidSpirv();
@@ -1022,7 +1022,7 @@
     ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : vec2<i32> = vec2<i32>();"));
+    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : vec2i = vec2i();"));
 }
 
 TEST_F(SpvModuleScopeVarParserTest, VectorIntUndefInitializer) {
@@ -1034,7 +1034,7 @@
     ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : vec2<i32> = vec2<i32>();"));
+    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : vec2i = vec2i();"));
 
     // This example module emits ok, but is not valid SPIR-V in the first place.
     p->DeliberatelyInvalidSpirv();
@@ -1049,7 +1049,7 @@
     ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : vec2<f32> = vec2<f32>();"));
+    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : vec2f = vec2f();"));
 }
 
 TEST_F(SpvModuleScopeVarParserTest, VectorFloatUndefInitializer) {
@@ -1061,7 +1061,7 @@
     ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : vec2<f32> = vec2<f32>();"));
+    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : vec2f = vec2f();"));
 
     // This example module emits ok, but is not valid SPIR-V in the first place.
     p->DeliberatelyInvalidSpirv();
@@ -1082,10 +1082,10 @@
     ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : mat3x2<f32> = mat3x2<f32>("
-                                      "vec2<f32>(1.5f, 2.0f), "
-                                      "vec2<f32>(2.0f, 3.0f), "
-                                      "vec2<f32>(3.0f, 4.0f));"));
+    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : mat3x2f = mat3x2f("
+                                      "vec2f(1.5f, 2.0f), "
+                                      "vec2f(2.0f, 3.0f), "
+                                      "vec2f(3.0f, 4.0f));"));
 }
 
 TEST_F(SpvModuleScopeVarParserTest, MatrixNullInitializer) {
@@ -1097,7 +1097,7 @@
     ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : mat3x2<f32> = mat3x2<f32>();"));
+    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : mat3x2f = mat3x2f();"));
 }
 
 TEST_F(SpvModuleScopeVarParserTest, MatrixUndefInitializer) {
@@ -1109,7 +1109,7 @@
     ASSERT_TRUE(p->BuildAndParseInternalModuleExceptFunctions());
     EXPECT_TRUE(p->error().empty());
     const auto module_str = test::ToString(p->program());
-    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : mat3x2<f32> = mat3x2<f32>();"));
+    EXPECT_THAT(module_str, HasSubstr("var<private> x_200 : mat3x2f = mat3x2f();"));
 
     // This example module emits ok, but is not valid SPIR-V in the first place.
     p->DeliberatelyInvalidSpirv();
@@ -1290,7 +1290,7 @@
     const auto module_str = test::ToString(p->program());
     EXPECT_THAT(module_str, HasSubstr(R"(struct S {
   /* @offset(0) */
-  field0 : mat3x2<f32>,
+  field0 : mat3x2f,
 }
 
 @group(0) @binding(0) var<storage, read_write> myvar : S;
@@ -1321,7 +1321,7 @@
     const auto module_str = test::ToString(p->program());
     EXPECT_THAT(module_str, HasSubstr(R"(struct S {
   /* @offset(0) */
-  field0 : mat3x2<f32>,
+  field0 : mat3x2f,
 }
 
 @group(0) @binding(0) var<storage, read_write> myvar : S;
@@ -1353,7 +1353,7 @@
     EXPECT_THAT(module_str, HasSubstr(R"(struct S {
   /* @offset(0) */
   @stride(64) @internal(disable_validation__ignore_stride)
-  field0 : mat3x2<f32>,
+  field0 : mat3x2f,
 }
 
 @group(0) @binding(0) var<storage, read_write> myvar : S;
@@ -2529,7 +2529,7 @@
     const auto module_str = test::ToString(p->program());
     const std::string expected = R"(var<private> x_1 : i32;
 
-var<private> x_4 : vec4<f32>;
+var<private> x_4 : vec4f;
 
 fn main_1() {
   let x_2 : i32 = x_1;
@@ -2538,7 +2538,7 @@
 
 struct main_out {
   @builtin(position)
-  x_4_1 : vec4<f32>,
+  x_4_1 : vec4f,
 }
 
 @vertex
@@ -2586,7 +2586,7 @@
     const auto module_str = test::ToString(p->program());
     const std::string expected = R"(var<private> x_1 : u32;
 
-var<private> x_5 : vec4<f32>;
+var<private> x_5 : vec4f;
 
 fn main_1() {
   let x_2 : u32 = x_1;
@@ -2600,7 +2600,7 @@
 
 struct main_out {
   @builtin(position)
-  x_5_1 : vec4<f32>,
+  x_5_1 : vec4f,
 }
 
 @vertex
@@ -2628,7 +2628,7 @@
     const auto module_str = test::ToString(p->program());
     const std::string expected = R"(var<private> x_1 : i32;
 
-var<private> x_4 : vec4<f32>;
+var<private> x_4 : vec4f;
 
 fn main_1() {
   let x_14 : ptr<private, i32> = &(x_1);
@@ -2638,7 +2638,7 @@
 
 struct main_out {
   @builtin(position)
-  x_4_1 : vec4<f32>,
+  x_4_1 : vec4f,
 }
 
 @vertex
@@ -2666,7 +2666,7 @@
     const auto module_str = test::ToString(p->program());
     const std::string expected = R"(var<private> x_1 : i32;
 
-var<private> x_4 : vec4<f32>;
+var<private> x_4 : vec4f;
 
 fn main_1() {
   let x_2 : i32 = x_1;
@@ -2675,7 +2675,7 @@
 
 struct main_out {
   @builtin(position)
-  x_4_1 : vec4<f32>,
+  x_4_1 : vec4f,
 }
 
 @vertex
@@ -2702,7 +2702,7 @@
     const auto module_str = test::ToString(p->program());
     const std::string expected = R"(var<private> x_1 : u32;
 
-var<private> x_4 : vec4<f32>;
+var<private> x_4 : vec4f;
 
 fn main_1() {
   let x_2 : u32 = x_1;
@@ -2711,7 +2711,7 @@
 
 struct main_out {
   @builtin(position)
-  x_4_1 : vec4<f32>,
+  x_4_1 : vec4f,
 }
 
 @vertex
@@ -2739,7 +2739,7 @@
     const auto module_str = test::ToString(p->program());
     const std::string expected = R"(var<private> x_1 : u32;
 
-var<private> x_4 : vec4<f32>;
+var<private> x_4 : vec4f;
 
 fn main_1() {
   let x_14 : ptr<private, u32> = &(x_1);
@@ -2749,7 +2749,7 @@
 
 struct main_out {
   @builtin(position)
-  x_4_1 : vec4<f32>,
+  x_4_1 : vec4f,
 }
 
 @vertex
@@ -2777,7 +2777,7 @@
     const auto module_str = test::ToString(p->program());
     const std::string expected = R"(var<private> x_1 : u32;
 
-var<private> x_4 : vec4<f32>;
+var<private> x_4 : vec4f;
 
 fn main_1() {
   let x_2 : u32 = x_1;
@@ -2786,7 +2786,7 @@
 
 struct main_out {
   @builtin(position)
-  x_4_1 : vec4<f32>,
+  x_4_1 : vec4f,
 }
 
 @vertex
@@ -2861,7 +2861,7 @@
     const auto module_str = test::ToString(p->program());
     const std::string expected = R"(var<private> x_1 : i32;
 
-var<private> position_1 : vec4<f32>;
+var<private> position_1 : vec4f;
 
 fn main_1() {
   let x_2 : i32 = x_1;
@@ -2870,7 +2870,7 @@
 
 struct main_out {
   @builtin(position)
-  position_1_1 : vec4<f32>,
+  position_1_1 : vec4f,
 }
 
 @vertex
@@ -2898,7 +2898,7 @@
     const auto module_str = test::ToString(p->program());
     const std::string expected = R"(var<private> x_1 : i32;
 
-var<private> position_1 : vec4<f32>;
+var<private> position_1 : vec4f;
 
 fn main_1() {
   let x_14 : ptr<private, i32> = &(x_1);
@@ -2908,7 +2908,7 @@
 
 struct main_out {
   @builtin(position)
-  position_1_1 : vec4<f32>,
+  position_1_1 : vec4f,
 }
 
 @vertex
@@ -2936,7 +2936,7 @@
     const auto module_str = test::ToString(p->program());
     const std::string expected = R"(var<private> x_1 : i32;
 
-var<private> position_1 : vec4<f32>;
+var<private> position_1 : vec4f;
 
 fn main_1() {
   let x_2 : i32 = x_1;
@@ -2945,7 +2945,7 @@
 
 struct main_out {
   @builtin(position)
-  position_1_1 : vec4<f32>,
+  position_1_1 : vec4f,
 }
 
 @vertex
@@ -2995,7 +2995,7 @@
     const auto module_str = test::ToString(p->program());
     const std::string expected = R"(var<private> x_1 : u32;
 
-var<private> position_1 : vec4<f32>;
+var<private> position_1 : vec4f;
 
 fn main_1() {
   let x_2 : u32 = x_1;
@@ -3004,7 +3004,7 @@
 
 struct main_out {
   @builtin(position)
-  position_1_1 : vec4<f32>,
+  position_1_1 : vec4f,
 }
 
 @vertex
@@ -3032,7 +3032,7 @@
     const auto module_str = test::ToString(p->program());
     const std::string expected = R"(var<private> x_1 : u32;
 
-var<private> position_1 : vec4<f32>;
+var<private> position_1 : vec4f;
 
 fn main_1() {
   let x_14 : ptr<private, u32> = &(x_1);
@@ -3042,7 +3042,7 @@
 
 struct main_out {
   @builtin(position)
-  position_1_1 : vec4<f32>,
+  position_1_1 : vec4f,
 }
 
 @vertex
@@ -3070,7 +3070,7 @@
     const auto module_str = test::ToString(p->program());
     const std::string expected = R"(var<private> x_1 : u32;
 
-var<private> position_1 : vec4<f32>;
+var<private> position_1 : vec4f;
 
 fn main_1() {
   let x_2 : u32 = x_1;
@@ -3079,7 +3079,7 @@
 
 struct main_out {
   @builtin(position)
-  position_1_1 : vec4<f32>,
+  position_1_1 : vec4f,
 }
 
 @vertex
@@ -3165,10 +3165,10 @@
         return "i32";
     }
     if (spirv_type == "%v3uint") {
-        return "vec3<u32>";
+        return "vec3u";
     }
     if (spirv_type == "%v3int") {
-        return "vec3<i32>";
+        return "vec3i";
     }
     return "error";
 }
@@ -3180,11 +3180,11 @@
     if (wgsl_type == "i32") {
         return "u32";
     }
-    if (wgsl_type == "vec3<u32>") {
-        return "vec3<u32>";
+    if (wgsl_type == "vec3u") {
+        return "vec3u";
     }
-    if (wgsl_type == "vec3<i32>") {
-        return "vec3<u32>";
+    if (wgsl_type == "vec3i") {
+        return "vec3u";
     }
     return "error";
 }
@@ -3196,11 +3196,11 @@
     if (wgsl_type == "i32") {
         return "i32";
     }
-    if (wgsl_type == "vec3<u32>") {
-        return "vec3<i32>";
+    if (wgsl_type == "vec3u") {
+        return "vec3i";
     }
-    if (wgsl_type == "vec3<i32>") {
-        return "vec3<i32>";
+    if (wgsl_type == "vec3i") {
+        return "vec3i";
     }
     return "error";
 }
@@ -3746,7 +3746,7 @@
     const auto got = test::ToString(p->program());
     const std::string expected = R"(var<private> x_1 : u32;
 
-var<private> x_4 : vec4<f32>;
+var<private> x_4 : vec4f;
 
 fn main_1() {
   let x_2 : u32 = x_1;
@@ -3755,7 +3755,7 @@
 
 struct main_out {
   @builtin(position)
-  x_4_1 : vec4<f32>,
+  x_4_1 : vec4f,
 }
 
 @vertex
@@ -3795,7 +3795,7 @@
     const auto got = test::ToString(p->program());
     const std::string expected = R"(var<private> x_1 : i32;
 
-var<private> x_4 : vec4<f32>;
+var<private> x_4 : vec4f;
 
 fn main_1() {
   let x_2 : i32 = x_1;
@@ -3804,7 +3804,7 @@
 
 struct main_out {
   @builtin(position)
-  x_4_1 : vec4<f32>,
+  x_4_1 : vec4f,
 }
 
 @vertex
@@ -4043,7 +4043,7 @@
     EXPECT_TRUE(p->error().empty());
 
     const auto got = test::ToString(p->program());
-    const std::string expected = R"(var<private> gl_Position : vec4<f32>;
+    const std::string expected = R"(var<private> gl_Position : vec4f;
 
 fn main_1() {
   return;
@@ -4051,7 +4051,7 @@
 
 struct main_out {
   @builtin(position)
-  gl_Position : vec4<f32>,
+  gl_Position : vec4f,
 }
 
 @vertex
@@ -4112,7 +4112,7 @@
 
     const auto got = test::ToString(p->program());
     const std::string expected =
-        R"(var<private> gl_Position : vec4<f32> = vec4<f32>(1.0f, 2.0f, 3.0f, 4.0f);
+        R"(var<private> gl_Position : vec4f = vec4f(1.0f, 2.0f, 3.0f, 4.0f);
 
 fn main_1() {
   return;
@@ -4120,7 +4120,7 @@
 
 struct main_out {
   @builtin(position)
-  gl_Position : vec4<f32>,
+  gl_Position : vec4f,
 }
 
 @vertex
@@ -4198,7 +4198,7 @@
     const auto got = test::ToString(p->program());
     const std::string expected = R"(var<private> x_1 : array<f32, 3u>;
 
-var<private> x_2 : vec4<f32>;
+var<private> x_2 : vec4f;
 
 fn main_1() {
   return;
@@ -4206,7 +4206,7 @@
 
 struct main_out {
   @builtin(position)
-  x_2_1 : vec4<f32>,
+  x_2_1 : vec4f,
 }
 
 @vertex
@@ -4254,9 +4254,9 @@
     EXPECT_TRUE(p->error().empty());
 
     const auto got = test::ToString(p->program());
-    const std::string expected = R"(var<private> x_1 : mat2x4<f32>;
+    const std::string expected = R"(var<private> x_1 : mat2x4f;
 
-var<private> x_2 : vec4<f32>;
+var<private> x_2 : vec4f;
 
 fn main_1() {
   return;
@@ -4264,11 +4264,11 @@
 
 struct main_out {
   @builtin(position)
-  x_2_1 : vec4<f32>,
+  x_2_1 : vec4f,
 }
 
 @vertex
-fn main(@location(9) x_1_param : vec4<f32>, @location(10) x_1_param_1 : vec4<f32>) -> main_out {
+fn main(@location(9) x_1_param : vec4f, @location(10) x_1_param_1 : vec4f) -> main_out {
   x_1[0i] = x_1_param;
   x_1[1i] = x_1_param_1;
   main_1();
@@ -4318,12 +4318,12 @@
     const auto got = test::ToString(p->program());
     const std::string expected = R"(struct Communicators {
   alice : f32,
-  bob : vec4<f32>,
+  bob : vec4f,
 }
 
 var<private> x_1 : Communicators;
 
-var<private> x_2 : vec4<f32>;
+var<private> x_2 : vec4f;
 
 fn main_1() {
   return;
@@ -4331,11 +4331,11 @@
 
 struct main_out {
   @builtin(position)
-  x_2_1 : vec4<f32>,
+  x_2_1 : vec4f,
 }
 
 @vertex
-fn main(@location(9) x_1_param : f32, @location(10) x_1_param_1 : vec4<f32>) -> main_out {
+fn main(@location(9) x_1_param : f32, @location(10) x_1_param_1 : vec4f) -> main_out {
   x_1.alice = x_1_param;
   x_1.bob = x_1_param_1;
   main_1();
@@ -4380,9 +4380,9 @@
     EXPECT_TRUE(p->error().empty());
 
     const auto got = test::ToString(p->program());
-    const std::string expected = R"(var<private> x_1 : array<mat2x4<f32>, 2u>;
+    const std::string expected = R"(var<private> x_1 : array<mat2x4f, 2u>;
 
-var<private> x_2 : vec4<f32>;
+var<private> x_2 : vec4f;
 
 fn main_1() {
   return;
@@ -4390,11 +4390,11 @@
 
 struct main_out {
   @builtin(position)
-  x_2_1 : vec4<f32>,
+  x_2_1 : vec4f,
 }
 
 @vertex
-fn main(@location(7) x_1_param : vec4<f32>, @location(8) x_1_param_1 : vec4<f32>, @location(9) x_1_param_2 : vec4<f32>, @location(10) x_1_param_3 : vec4<f32>) -> main_out {
+fn main(@location(7) x_1_param : vec4f, @location(8) x_1_param_1 : vec4f, @location(9) x_1_param_2 : vec4f, @location(10) x_1_param_3 : vec4f) -> main_out {
   x_1[0i][0i] = x_1_param;
   x_1[0i][1i] = x_1_param_1;
   x_1[1i][0i] = x_1_param_2;
@@ -4443,7 +4443,7 @@
     const auto got = test::ToString(p->program());
     const std::string expected = R"(var<private> x_1 : array<f32, 3u>;
 
-var<private> x_2 : vec4<f32>;
+var<private> x_2 : vec4f;
 
 fn main_1() {
   return;
@@ -4457,7 +4457,7 @@
   @location(6)
   x_1_3 : f32,
   @builtin(position)
-  x_2_1 : vec4<f32>,
+  x_2_1 : vec4f,
 }
 
 @vertex
@@ -4502,9 +4502,9 @@
     EXPECT_TRUE(p->error().empty());
 
     const auto got = test::ToString(p->program());
-    const std::string expected = R"(var<private> x_1 : mat2x4<f32>;
+    const std::string expected = R"(var<private> x_1 : mat2x4f;
 
-var<private> x_2 : vec4<f32>;
+var<private> x_2 : vec4f;
 
 fn main_1() {
   return;
@@ -4512,11 +4512,11 @@
 
 struct main_out {
   @location(9)
-  x_1_1 : vec4<f32>,
+  x_1_1 : vec4f,
   @location(10)
-  x_1_2 : vec4<f32>,
+  x_1_2 : vec4f,
   @builtin(position)
-  x_2_1 : vec4<f32>,
+  x_2_1 : vec4f,
 }
 
 @vertex
@@ -4568,12 +4568,12 @@
     const auto got = test::ToString(p->program());
     const std::string expected = R"(struct Communicators {
   alice : f32,
-  bob : vec4<f32>,
+  bob : vec4f,
 }
 
 var<private> x_1 : Communicators;
 
-var<private> x_2 : vec4<f32>;
+var<private> x_2 : vec4f;
 
 fn main_1() {
   return;
@@ -4583,9 +4583,9 @@
   @location(9)
   x_1_1 : f32,
   @location(10)
-  x_1_2 : vec4<f32>,
+  x_1_2 : vec4f,
   @builtin(position)
-  x_2_1 : vec4<f32>,
+  x_2_1 : vec4f,
 }
 
 @vertex
@@ -4641,14 +4641,14 @@
     const auto got = test::ToString(p->program());
     const std::string expected = R"(struct Communicators {
   alice : f32,
-  bob : vec4<f32>,
+  bob : vec4f,
 }
 
 var<private> x_1 : Communicators;
 
 var<private> x_3 : Communicators;
 
-var<private> x_2 : vec4<f32>;
+var<private> x_2 : vec4f;
 
 fn main_1() {
   return;
@@ -4656,15 +4656,15 @@
 
 struct main_out {
   @builtin(position)
-  x_2_1 : vec4<f32>,
+  x_2_1 : vec4f,
   @location(9)
   x_3_1 : f32,
   @location(11)
-  x_3_2 : vec4<f32>,
+  x_3_2 : vec4f,
 }
 
 @vertex
-fn main(@location(9) x_1_param : f32, @location(11) x_1_param_1 : vec4<f32>) -> main_out {
+fn main(@location(9) x_1_param : f32, @location(11) x_1_param_1 : vec4f) -> main_out {
   x_1.alice = x_1_param;
   x_1.bob = x_1_param_1;
   main_1();
@@ -4721,17 +4721,17 @@
     const std::string expected =
         R"(var<private> x_1 : u32;
 
-var<private> x_2 : vec2<u32>;
+var<private> x_2 : vec2u;
 
 var<private> x_3 : i32;
 
-var<private> x_4 : vec2<i32>;
+var<private> x_4 : vec2i;
 
 var<private> x_5 : f32;
 
-var<private> x_6 : vec2<f32>;
+var<private> x_6 : vec2f;
 
-var<private> x_10 : vec4<f32>;
+var<private> x_10 : vec4f;
 
 fn main_1() {
   return;
@@ -4739,11 +4739,11 @@
 
 struct main_out {
   @builtin(position)
-  x_10_1 : vec4<f32>,
+  x_10_1 : vec4f,
 }
 
 @vertex
-fn main(@location(1) @interpolate(flat) x_1_param : u32, @location(2) @interpolate(flat) x_2_param : vec2<u32>, @location(3) @interpolate(flat) x_3_param : i32, @location(4) @interpolate(flat) x_4_param : vec2<i32>, @location(5) @interpolate(flat) x_5_param : f32, @location(6) @interpolate(flat) x_6_param : vec2<f32>) -> main_out {
+fn main(@location(1) @interpolate(flat) x_1_param : u32, @location(2) @interpolate(flat) x_2_param : vec2u, @location(3) @interpolate(flat) x_3_param : i32, @location(4) @interpolate(flat) x_4_param : vec2i, @location(5) @interpolate(flat) x_5_param : f32, @location(6) @interpolate(flat) x_6_param : vec2f) -> main_out {
   x_1 = x_1_param;
   x_2 = x_2_param;
   x_3 = x_3_param;
@@ -4804,17 +4804,17 @@
     const std::string expected =
         R"(var<private> x_1 : u32;
 
-var<private> x_2 : vec2<u32>;
+var<private> x_2 : vec2u;
 
 var<private> x_3 : i32;
 
-var<private> x_4 : vec2<i32>;
+var<private> x_4 : vec2i;
 
 var<private> x_5 : f32;
 
-var<private> x_6 : vec2<f32>;
+var<private> x_6 : vec2f;
 
-var<private> x_10 : vec4<f32>;
+var<private> x_10 : vec4f;
 
 fn main_1() {
   return;
@@ -4824,17 +4824,17 @@
   @location(1) @interpolate(flat)
   x_1_1 : u32,
   @location(2) @interpolate(flat)
-  x_2_1 : vec2<u32>,
+  x_2_1 : vec2u,
   @location(3) @interpolate(flat)
   x_3_1 : i32,
   @location(4) @interpolate(flat)
-  x_4_1 : vec2<i32>,
+  x_4_1 : vec2i,
   @location(5) @interpolate(flat)
   x_5_1 : f32,
   @location(6) @interpolate(flat)
-  x_6_1 : vec2<f32>,
+  x_6_1 : vec2f,
   @builtin(position)
-  x_10_1 : vec4<f32>,
+  x_10_1 : vec4f,
 }
 
 @vertex
@@ -5251,17 +5251,17 @@
     const std::string expected =
         R"(var<private> x_1 : u32;
 
-var<private> x_2 : vec2<u32>;
+var<private> x_2 : vec2u;
 
 var<private> x_3 : i32;
 
-var<private> x_4 : vec2<i32>;
+var<private> x_4 : vec2i;
 
 var<private> x_5 : f32;
 
-var<private> x_6 : vec2<f32>;
+var<private> x_6 : vec2f;
 
-var<private> x_10 : vec4<f32>;
+var<private> x_10 : vec4f;
 
 fn main_1() {
   return;
@@ -5271,17 +5271,17 @@
   @location(1) @interpolate(flat)
   x_1_1 : u32,
   @location(2) @interpolate(flat)
-  x_2_1 : vec2<u32>,
+  x_2_1 : vec2u,
   @location(3) @interpolate(flat)
   x_3_1 : i32,
   @location(4) @interpolate(flat)
-  x_4_1 : vec2<i32>,
+  x_4_1 : vec2i,
   @location(5)
   x_5_1 : f32,
   @location(6)
-  x_6_1 : vec2<f32>,
+  x_6_1 : vec2f,
   @builtin(position)
-  x_10_1 : vec4<f32>,
+  x_10_1 : vec4f,
 }
 
 @vertex
@@ -5333,22 +5333,22 @@
     const std::string expected =
         R"(var<private> x_1 : u32;
 
-var<private> x_2 : vec2<u32>;
+var<private> x_2 : vec2u;
 
 var<private> x_3 : i32;
 
-var<private> x_4 : vec2<i32>;
+var<private> x_4 : vec2i;
 
 var<private> x_5 : f32;
 
-var<private> x_6 : vec2<f32>;
+var<private> x_6 : vec2f;
 
 fn main_1() {
   return;
 }
 
 @fragment
-fn main(@location(1) @interpolate(flat) x_1_param : u32, @location(2) @interpolate(flat) x_2_param : vec2<u32>, @location(3) @interpolate(flat) x_3_param : i32, @location(4) @interpolate(flat) x_4_param : vec2<i32>, @location(5) x_5_param : f32, @location(6) x_6_param : vec2<f32>) {
+fn main(@location(1) @interpolate(flat) x_1_param : u32, @location(2) @interpolate(flat) x_2_param : vec2u, @location(3) @interpolate(flat) x_3_param : i32, @location(4) @interpolate(flat) x_4_param : vec2i, @location(5) x_5_param : f32, @location(6) x_6_param : vec2f) {
   x_1 = x_1_param;
   x_2 = x_2_param;
   x_3 = x_3_param;
diff --git a/src/tint/reader/spirv/parser_type.cc b/src/tint/reader/spirv/parser_type.cc
index bc8bd93..5abd739 100644
--- a/src/tint/reader/spirv/parser_type.cc
+++ b/src/tint/reader/spirv/parser_type.cc
@@ -14,6 +14,7 @@
 
 #include "src/tint/reader/spirv/parser_type.h"
 
+#include <sstream>
 #include <string>
 #include <unordered_map>
 #include <utility>
@@ -203,13 +204,24 @@
 Vector::Vector(const Vector&) = default;
 
 ast::Type Vector::Build(ProgramBuilder& b) const {
-    return b.ty.vec(type->Build(b), size);
+    auto prefix = "vec" + std::to_string(size);
+    return Switch(
+        type,  //
+        [&](const I32*) { return b.ty(prefix + "i"); },
+        [&](const U32*) { return b.ty(prefix + "u"); },
+        [&](const F32*) { return b.ty(prefix + "f"); },
+        [&](Default) { return b.ty.vec(type->Build(b), size); });
 }
 
 Matrix::Matrix(const Type* t, uint32_t c, uint32_t r) : type(t), columns(c), rows(r) {}
 Matrix::Matrix(const Matrix&) = default;
 
 ast::Type Matrix::Build(ProgramBuilder& b) const {
+    if (type->Is<F32>()) {
+        std::ostringstream ss;
+        ss << "mat" << columns << "x" << rows << "f";
+        return b.ty(ss.str());
+    }
     return b.ty.mat(type->Build(b), columns, rows);
 }
 
diff --git a/src/tint/transform/unshadow.cc b/src/tint/transform/unshadow.cc
index 1dd4234..19a1023 100644
--- a/src/tint/transform/unshadow.cc
+++ b/src/tint/transform/unshadow.cc
@@ -108,7 +108,7 @@
         ctx.ReplaceAll(
             [&](const ast::IdentifierExpression* ident) -> const tint::ast::IdentifierExpression* {
                 if (auto* sem_ident = sem.GetVal(ident)) {
-                    if (auto* user = sem_ident->UnwrapLoad()->As<sem::VariableUser>()) {
+                    if (auto* user = sem_ident->Unwrap()->As<sem::VariableUser>()) {
                         if (auto renamed = renamed_to.Find(user->Variable())) {
                             return b.Expr(*renamed);
                         }
diff --git a/src/tint/transform/unshadow_test.cc b/src/tint/transform/unshadow_test.cc
index 9d8541d..e60db42 100644
--- a/src/tint/transform/unshadow_test.cc
+++ b/src/tint/transform/unshadow_test.cc
@@ -779,5 +779,25 @@
     EXPECT_EQ(expect, str(got));
 }
 
+TEST_F(UnshadowTest, RenamedAbstractConstHasUsers) {
+    auto* src = R"(
+fn v() {
+  const v = 1;
+  let x = v;
+}
+)";
+
+    auto* expect = R"(
+fn v() {
+  const v_1 = 1;
+  let x = v_1;
+}
+)";
+
+    auto got = Run<Unshadow>(src);
+
+    EXPECT_EQ(expect, str(got));
+}
+
 }  // namespace
 }  // namespace tint::transform
diff --git a/src/tint/utils/predicates.h b/src/tint/utils/predicates.h
new file mode 100644
index 0000000..0769b30
--- /dev/null
+++ b/src/tint/utils/predicates.h
@@ -0,0 +1,78 @@
+// Copyright 2023 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_UTILS_PREDICATES_H_
+#define SRC_TINT_UTILS_PREDICATES_H_
+
+namespace tint::utils {
+
+/// @param value the value to compare against
+/// @return a function with the signature `bool(const T&)` which returns true if the argument is
+/// equal to
+/// @p value
+template <typename T>
+auto Eq(const T& value) {
+    return [value](const T& v) { return v == value; };
+}
+
+/// @param value the value to compare against
+/// @return a function with the signature `bool(const T&)` which returns true if the argument is not
+/// equal to @p value
+template <typename T>
+auto Ne(const T& value) {
+    return [value](const T& v) { return v != value; };
+}
+
+/// @param value the value to compare against
+/// @return a function with the signature `bool(const T&)` which returns true if the argument is
+/// greater than @p value
+template <typename T>
+auto Gt(const T& value) {
+    return [value](const T& v) { return v > value; };
+}
+
+/// @param value the value to compare against
+/// @return a function with the signature `bool(const T&)` which returns true if the argument is
+/// less than
+/// @p value
+template <typename T>
+auto Lt(const T& value) {
+    return [value](const T& v) { return v < value; };
+}
+
+/// @param value the value to compare against
+/// @return a function with the signature `bool(const T&)` which returns true if the argument is
+/// greater or equal to @p value
+template <typename T>
+auto Ge(const T& value) {
+    return [value](const T& v) { return v >= value; };
+}
+
+/// @param value the value to compare against
+/// @return a function with the signature `bool(const T&)` which returns true if the argument is
+/// less than or equal to @p value
+template <typename T>
+auto Le(const T& value) {
+    return [value](const T& v) { return v <= value; };
+}
+
+/// @param ptr the pointer
+/// @return true if the pointer argument is null.
+static inline bool IsNull(const void* ptr) {
+    return ptr == nullptr;
+}
+
+}  // namespace tint::utils
+
+#endif  // SRC_TINT_UTILS_PREDICATES_H_
diff --git a/src/tint/utils/predicates_test.cc b/src/tint/utils/predicates_test.cc
new file mode 100644
index 0000000..7733cf7
--- /dev/null
+++ b/src/tint/utils/predicates_test.cc
@@ -0,0 +1,83 @@
+// Copyright 2021 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/utils/predicates.h"
+
+#include "gtest/gtest.h"
+
+namespace tint::utils {
+namespace {
+
+TEST(PredicatesTest, Eq) {
+    auto pred = Eq(3);
+    EXPECT_FALSE(pred(1));
+    EXPECT_FALSE(pred(2));
+    EXPECT_TRUE(pred(3));
+    EXPECT_FALSE(pred(4));
+    EXPECT_FALSE(pred(5));
+}
+
+TEST(PredicatesTest, Ne) {
+    auto pred = Ne(3);
+    EXPECT_TRUE(pred(1));
+    EXPECT_TRUE(pred(2));
+    EXPECT_FALSE(pred(3));
+    EXPECT_TRUE(pred(4));
+    EXPECT_TRUE(pred(5));
+}
+
+TEST(PredicatesTest, Gt) {
+    auto pred = Gt(3);
+    EXPECT_FALSE(pred(1));
+    EXPECT_FALSE(pred(2));
+    EXPECT_FALSE(pred(3));
+    EXPECT_TRUE(pred(4));
+    EXPECT_TRUE(pred(5));
+}
+
+TEST(PredicatesTest, Lt) {
+    auto pred = Lt(3);
+    EXPECT_TRUE(pred(1));
+    EXPECT_TRUE(pred(2));
+    EXPECT_FALSE(pred(3));
+    EXPECT_FALSE(pred(4));
+    EXPECT_FALSE(pred(5));
+}
+
+TEST(PredicatesTest, Ge) {
+    auto pred = Ge(3);
+    EXPECT_FALSE(pred(1));
+    EXPECT_FALSE(pred(2));
+    EXPECT_TRUE(pred(3));
+    EXPECT_TRUE(pred(4));
+    EXPECT_TRUE(pred(5));
+}
+
+TEST(PredicatesTest, Le) {
+    auto pred = Le(3);
+    EXPECT_TRUE(pred(1));
+    EXPECT_TRUE(pred(2));
+    EXPECT_TRUE(pred(3));
+    EXPECT_FALSE(pred(4));
+    EXPECT_FALSE(pred(5));
+}
+
+TEST(PredicatesTest, IsNull) {
+    int i = 1;
+    EXPECT_TRUE(IsNull(nullptr));
+    EXPECT_FALSE(IsNull(&i));
+}
+
+}  // namespace
+}  // namespace tint::utils
diff --git a/src/tint/utils/string.h b/src/tint/utils/string.h
index 897d1a1..e731365 100644
--- a/src/tint/utils/string.h
+++ b/src/tint/utils/string.h
@@ -60,7 +60,14 @@
 /// @param prefix the prefix string
 /// @returns true iff @p str has the prefix @p prefix
 inline size_t HasPrefix(std::string_view str, std::string_view prefix) {
-    return str.compare(0, prefix.size(), prefix) == 0;
+    return str.length() >= prefix.length() && str.substr(0, prefix.length()) == prefix;
+}
+
+/// @param str the input string
+/// @param suffix the suffix string
+/// @returns true iff @p str has the suffix @p suffix
+inline size_t HasSuffix(std::string_view str, std::string_view suffix) {
+    return str.length() >= suffix.length() && str.substr(str.length() - suffix.length()) == suffix;
 }
 
 /// @param a the first string
@@ -78,6 +85,71 @@
                          utils::StringStream& ss,
                          std::string_view prefix = "");
 
+/// @param str the input string
+/// @param pred the predicate function
+/// @return @p str with characters passing the predicate function @p pred removed from the start of
+/// the string.
+template <typename PREDICATE>
+std::string_view TrimLeft(std::string_view str, PREDICATE&& pred) {
+    while (!str.empty() && pred(str.front())) {
+        str = str.substr(1);
+    }
+    return str;
+}
+
+/// @param str the input string
+/// @param pred the predicate function
+/// @return @p str with characters passing the predicate function @p pred removed from the end of
+/// the string.
+template <typename PREDICATE>
+std::string_view TrimRight(std::string_view str, PREDICATE&& pred) {
+    while (!str.empty() && pred(str.back())) {
+        str = str.substr(0, str.length() - 1);
+    }
+    return str;
+}
+
+/// @param str the input string
+/// @param prefix the prefix to trim from @p str
+/// @return @p str with the prefix removed, if @p str has the prefix.
+inline std::string_view TrimPrefix(std::string_view str, std::string_view prefix) {
+    return HasPrefix(str, prefix) ? str.substr(prefix.length()) : str;
+}
+
+/// @param str the input string
+/// @param suffix the suffix to trim from @p str
+/// @return @p str with the suffix removed, if @p str has the suffix.
+inline std::string_view TrimSuffix(std::string_view str, std::string_view suffix) {
+    return HasSuffix(str, suffix) ? str.substr(0, str.length() - suffix.length()) : str;
+}
+
+/// @param str the input string
+/// @param pred the predicate function
+/// @return @p str with characters passing the predicate function @p pred removed from the start and
+/// end of the string.
+template <typename PREDICATE>
+std::string_view Trim(std::string_view str, PREDICATE&& pred) {
+    return TrimLeft(TrimRight(str, pred), pred);
+}
+
+/// @param c the character to test
+/// @returns true if @p c is one of the following:
+/// * space (' ')
+/// * form feed ('\f')
+/// * line feed ('\n')
+/// * carriage return ('\r')
+/// * horizontal tab ('\t')
+/// * vertical tab ('\v')
+inline bool IsSpace(char c) {
+    return c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v';
+}
+
+/// @param str the input string
+/// @return @p str with all whitespace (' ') removed from the start and end of the string.
+inline std::string_view TrimSpace(std::string_view str) {
+    return Trim(str, IsSpace);
+}
+
 }  // namespace tint::utils
 
 #endif  // SRC_TINT_UTILS_STRING_H_
diff --git a/src/tint/utils/string_test.cc b/src/tint/utils/string_test.cc
index 0f351cf..9cf8370 100644
--- a/src/tint/utils/string_test.cc
+++ b/src/tint/utils/string_test.cc
@@ -47,6 +47,15 @@
     EXPECT_FALSE(HasPrefix("abc", "b"));
 }
 
+TEST(StringTest, HasSuffix) {
+    EXPECT_TRUE(HasSuffix("abc", "c"));
+    EXPECT_TRUE(HasSuffix("abc", "bc"));
+    EXPECT_TRUE(HasSuffix("abc", "abc"));
+    EXPECT_FALSE(HasSuffix("abc", "1abc"));
+    EXPECT_FALSE(HasSuffix("abc", "ac"));
+    EXPECT_FALSE(HasSuffix("abc", "b"));
+}
+
 TEST(StringTest, Distance) {
     EXPECT_EQ(Distance("hello world", "hello world"), 0u);
     EXPECT_EQ(Distance("hello world", "helloworld"), 1u);
@@ -75,5 +84,74 @@
     }
 }
 
+TEST(StringTest, TrimLeft) {
+    EXPECT_EQ(TrimLeft("hello world", [](char) { return false; }), "hello world");
+    EXPECT_EQ(TrimLeft("hello world", [](char c) { return c == 'h'; }), "ello world");
+    EXPECT_EQ(TrimLeft("hello world", [](char c) { return c == 'h' || c == 'e'; }), "llo world");
+    EXPECT_EQ(TrimLeft("hello world", [](char c) { return c == 'e'; }), "hello world");
+    EXPECT_EQ(TrimLeft("hello world", [](char) { return true; }), "");
+    EXPECT_EQ(TrimLeft("", [](char) { return false; }), "");
+    EXPECT_EQ(TrimLeft("", [](char) { return true; }), "");
+}
+
+TEST(StringTest, TrimRight) {
+    EXPECT_EQ(TrimRight("hello world", [](char) { return false; }), "hello world");
+    EXPECT_EQ(TrimRight("hello world", [](char c) { return c == 'd'; }), "hello worl");
+    EXPECT_EQ(TrimRight("hello world", [](char c) { return c == 'd' || c == 'l'; }), "hello wor");
+    EXPECT_EQ(TrimRight("hello world", [](char c) { return c == 'l'; }), "hello world");
+    EXPECT_EQ(TrimRight("hello world", [](char) { return true; }), "");
+    EXPECT_EQ(TrimRight("", [](char) { return false; }), "");
+    EXPECT_EQ(TrimRight("", [](char) { return true; }), "");
+}
+
+TEST(StringTest, TrimPrefix) {
+    EXPECT_EQ(TrimPrefix("abc", "a"), "bc");
+    EXPECT_EQ(TrimPrefix("abc", "ab"), "c");
+    EXPECT_EQ(TrimPrefix("abc", "abc"), "");
+    EXPECT_EQ(TrimPrefix("abc", "abc1"), "abc");
+    EXPECT_EQ(TrimPrefix("abc", "ac"), "abc");
+    EXPECT_EQ(TrimPrefix("abc", "b"), "abc");
+    EXPECT_EQ(TrimPrefix("abc", "c"), "abc");
+}
+
+TEST(StringTest, TrimSuffix) {
+    EXPECT_EQ(TrimSuffix("abc", "c"), "ab");
+    EXPECT_EQ(TrimSuffix("abc", "bc"), "a");
+    EXPECT_EQ(TrimSuffix("abc", "abc"), "");
+    EXPECT_EQ(TrimSuffix("abc", "1abc"), "abc");
+    EXPECT_EQ(TrimSuffix("abc", "ac"), "abc");
+    EXPECT_EQ(TrimSuffix("abc", "b"), "abc");
+    EXPECT_EQ(TrimSuffix("abc", "a"), "abc");
+}
+
+TEST(StringTest, Trim) {
+    EXPECT_EQ(Trim("hello world", [](char) { return false; }), "hello world");
+    EXPECT_EQ(Trim("hello world", [](char c) { return c == 'h'; }), "ello world");
+    EXPECT_EQ(Trim("hello world", [](char c) { return c == 'd'; }), "hello worl");
+    EXPECT_EQ(Trim("hello world", [](char c) { return c == 'h' || c == 'd'; }), "ello worl");
+    EXPECT_EQ(Trim("hello world", [](char) { return true; }), "");
+    EXPECT_EQ(Trim("", [](char) { return false; }), "");
+    EXPECT_EQ(Trim("", [](char) { return true; }), "");
+}
+
+TEST(StringTest, IsSpace) {
+    EXPECT_FALSE(IsSpace('a'));
+    EXPECT_FALSE(IsSpace('z'));
+    EXPECT_FALSE(IsSpace('\0'));
+    EXPECT_TRUE(IsSpace(' '));
+    EXPECT_TRUE(IsSpace('\f'));
+    EXPECT_TRUE(IsSpace('\n'));
+    EXPECT_TRUE(IsSpace('\r'));
+    EXPECT_TRUE(IsSpace('\t'));
+    EXPECT_TRUE(IsSpace('\v'));
+}
+
+TEST(StringTest, TrimSpace) {
+    EXPECT_EQ(TrimSpace("hello world"), "hello world");
+    EXPECT_EQ(TrimSpace(" \t hello world\v\f"), "hello world");
+    EXPECT_EQ(TrimSpace("hello \t world"), "hello \t world");
+    EXPECT_EQ(TrimSpace(""), "");
+}
+
 }  // namespace
 }  // namespace tint::utils
diff --git a/src/tint/utils/vector.h b/src/tint/utils/vector.h
index ed4fcf1..65595e7 100644
--- a/src/tint/utils/vector.h
+++ b/src/tint/utils/vector.h
@@ -322,6 +322,20 @@
         Sort([](auto& a, auto& b) { return a < b; });
     }
 
+    /// @returns true if the predicate function returns true for any of the elements of the vector
+    /// @param pred a function-like with the signature `bool(T)`
+    template <typename PREDICATE>
+    bool Any(PREDICATE&& pred) const {
+        return std::any_of(begin(), end(), std::forward<PREDICATE>(pred));
+    }
+
+    /// @returns false if the predicate function returns false for any of the elements of the vector
+    /// @param pred a function-like with the signature `bool(T)`
+    template <typename PREDICATE>
+    bool All(PREDICATE&& pred) const {
+        return std::all_of(begin(), end(), std::forward<PREDICATE>(pred));
+    }
+
     /// @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 0604732..bb70e08 100644
--- a/src/tint/utils/vector_test.cc
+++ b/src/tint/utils/vector_test.cc
@@ -20,6 +20,7 @@
 #include "gmock/gmock.h"
 
 #include "src/tint/utils/bitcast.h"
+#include "src/tint/utils/predicates.h"
 #include "src/tint/utils/string_stream.h"
 
 namespace tint::utils {
@@ -1788,6 +1789,38 @@
     EXPECT_NE((Vector{2, 1}), (Vector{1, 2}));
 }
 
+TEST(TintVectorTest, Sort) {
+    Vector vec{1, 5, 3, 4, 2};
+    vec.Sort();
+    EXPECT_THAT(vec, testing::ElementsAre(1, 2, 3, 4, 5));
+}
+
+TEST(TintVectorTest, Any) {
+    Vector vec{1, 7, 5, 9};
+    EXPECT_TRUE(vec.Any(Eq(1)));
+    EXPECT_FALSE(vec.Any(Eq(2)));
+    EXPECT_FALSE(vec.Any(Eq(3)));
+    EXPECT_FALSE(vec.Any(Eq(4)));
+    EXPECT_TRUE(vec.Any(Eq(5)));
+    EXPECT_FALSE(vec.Any(Eq(6)));
+    EXPECT_TRUE(vec.Any(Eq(7)));
+    EXPECT_FALSE(vec.Any(Eq(8)));
+    EXPECT_TRUE(vec.Any(Eq(9)));
+}
+
+TEST(TintVectorTest, All) {
+    Vector vec{1, 7, 5, 9};
+    EXPECT_FALSE(vec.All(Ne(1)));
+    EXPECT_TRUE(vec.All(Ne(2)));
+    EXPECT_TRUE(vec.All(Ne(3)));
+    EXPECT_TRUE(vec.All(Ne(4)));
+    EXPECT_FALSE(vec.All(Ne(5)));
+    EXPECT_TRUE(vec.All(Ne(6)));
+    EXPECT_FALSE(vec.All(Ne(7)));
+    EXPECT_TRUE(vec.All(Ne(8)));
+    EXPECT_FALSE(vec.All(Ne(9)));
+}
+
 TEST(TintVectorTest, ostream) {
     utils::StringStream ss;
     ss << Vector{1, 2, 3};
@@ -2005,12 +2038,6 @@
     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; });
@@ -2072,6 +2099,7 @@
     ss << vec_ref;
     EXPECT_EQ(ss.str(), "[1, 2, 3]");
 }
+
 }  // namespace
 }  // namespace tint::utils
 
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
index 85644e2..9e1fbd8 100644
--- a/src/tint/writer/glsl/generator_impl.cc
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -2338,7 +2338,7 @@
                 out << cond_buf.str() << "; ";
 
                 if (!cont_buf.lines.empty()) {
-                    out << TrimSuffix(cont_buf.lines[0].content, ";");
+                    out << utils::TrimSuffix(cont_buf.lines[0].content, ";");
                 }
             }
             out << " {";
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index e8b697b..81a07d2 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -3719,7 +3719,7 @@
                 out << cond_buf.str() << "; ";
 
                 if (!cont_buf.lines.empty()) {
-                    out << TrimSuffix(cont_buf.lines[0].content, ";");
+                    out << utils::TrimSuffix(cont_buf.lines[0].content, ";");
                 }
             }
             out << " {";
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index d955d78..f3f660a 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -2253,7 +2253,7 @@
                 out << cond_buf.str() << "; ";
 
                 if (!cont_buf.lines.empty()) {
-                    out << TrimSuffix(cont_buf.lines[0].content, ";");
+                    out << utils::TrimSuffix(cont_buf.lines[0].content, ";");
                 }
             }
             out << " {";
diff --git a/src/tint/writer/spirv/builder.cc b/src/tint/writer/spirv/builder.cc
index add10aa..ff0dc3f 100644
--- a/src/tint/writer/spirv/builder.cc
+++ b/src/tint/writer/spirv/builder.cc
@@ -1034,6 +1034,11 @@
         } else {
             break;
         }
+
+        // Stop traversing if we've hit a constant source expression.
+        if (builder_.Sem().GetVal(source)->ConstantValue()) {
+            break;
+        }
     }
 
     AccessorInfo info;
diff --git a/src/tint/writer/spirv/generator_impl_binary_test.cc b/src/tint/writer/spirv/generator_impl_binary_test.cc
new file mode 100644
index 0000000..d5cd2bb
--- /dev/null
+++ b/src/tint/writer/spirv/generator_impl_binary_test.cc
@@ -0,0 +1,123 @@
+// Copyright 2023 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/writer/spirv/test_helper_ir.h"
+
+using namespace tint::number_suffixes;  // NOLINT
+
+namespace tint::writer::spirv {
+namespace {
+
+TEST_F(SpvGeneratorImplTest, Binary_Add_I32) {
+    auto* func = CreateFunction();
+    func->name = ir.symbols.Register("foo");
+    func->return_type = ir.types.Get<type::Void>();
+    func->start_target->branch.target = func->end_target;
+
+    func->start_target->instructions.Push(CreateBinary(
+        ir::Binary::Kind::kAdd, ir.types.Get<type::I32>(), Constant(1_i), Constant(2_i)));
+
+    generator_.EmitFunction(func);
+    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%6 = OpTypeInt 32 1
+%7 = OpConstant %6 1
+%8 = OpConstant %6 2
+%1 = OpFunction %2 None %3
+%4 = OpLabel
+%5 = OpIAdd %6 %7 %8
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(SpvGeneratorImplTest, Binary_Add_U32) {
+    auto* func = CreateFunction();
+    func->name = ir.symbols.Register("foo");
+    func->return_type = ir.types.Get<type::Void>();
+    func->start_target->branch.target = func->end_target;
+
+    func->start_target->instructions.Push(CreateBinary(
+        ir::Binary::Kind::kAdd, ir.types.Get<type::U32>(), Constant(1_u), Constant(2_u)));
+
+    generator_.EmitFunction(func);
+    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%6 = OpTypeInt 32 0
+%7 = OpConstant %6 1
+%8 = OpConstant %6 2
+%1 = OpFunction %2 None %3
+%4 = OpLabel
+%5 = OpIAdd %6 %7 %8
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(SpvGeneratorImplTest, Binary_Add_F32) {
+    auto* func = CreateFunction();
+    func->name = ir.symbols.Register("foo");
+    func->return_type = ir.types.Get<type::Void>();
+    func->start_target->branch.target = func->end_target;
+
+    func->start_target->instructions.Push(CreateBinary(
+        ir::Binary::Kind::kAdd, ir.types.Get<type::F32>(), Constant(1_f), Constant(2_f)));
+
+    generator_.EmitFunction(func);
+    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%6 = OpTypeFloat 32
+%7 = OpConstant %6 1
+%8 = OpConstant %6 2
+%1 = OpFunction %2 None %3
+%4 = OpLabel
+%5 = OpFAdd %6 %7 %8
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(SpvGeneratorImplTest, Binary_Add_Chain) {
+    auto* func = CreateFunction();
+    func->name = ir.symbols.Register("foo");
+    func->return_type = ir.types.Get<type::Void>();
+    func->start_target->branch.target = func->end_target;
+
+    auto* a = CreateBinary(ir::Binary::Kind::kAdd, ir.types.Get<type::I32>(), Constant(1_i),
+                           Constant(2_i));
+    func->start_target->instructions.Push(a);
+    func->start_target->instructions.Push(
+        CreateBinary(ir::Binary::Kind::kAdd, ir.types.Get<type::I32>(), a, a));
+
+    generator_.EmitFunction(func);
+    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%6 = OpTypeInt 32 1
+%7 = OpConstant %6 1
+%8 = OpConstant %6 2
+%1 = OpFunction %2 None %3
+%4 = OpLabel
+%5 = OpIAdd %6 %7 %8
+%9 = OpIAdd %6 %5 %5
+OpReturn
+OpFunctionEnd
+)");
+}
+
+}  // namespace
+}  // namespace tint::writer::spirv
diff --git a/src/tint/writer/spirv/generator_impl_constant_test.cc b/src/tint/writer/spirv/generator_impl_constant_test.cc
new file mode 100644
index 0000000..c8d7a10
--- /dev/null
+++ b/src/tint/writer/spirv/generator_impl_constant_test.cc
@@ -0,0 +1,76 @@
+// Copyright 2023 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/writer/spirv/test_helper_ir.h"
+
+namespace tint::writer::spirv {
+namespace {
+
+TEST_F(SpvGeneratorImplTest, Type_Bool) {
+    generator_.Constant(Constant(true));
+    generator_.Constant(Constant(false));
+    EXPECT_EQ(DumpTypes(), R"(%2 = OpTypeBool
+%1 = OpConstantTrue %2
+%3 = OpConstantFalse %2
+)");
+}
+
+TEST_F(SpvGeneratorImplTest, Constant_I32) {
+    generator_.Constant(Constant(i32(42)));
+    generator_.Constant(Constant(i32(-1)));
+    EXPECT_EQ(DumpTypes(), R"(%2 = OpTypeInt 32 1
+%1 = OpConstant %2 42
+%3 = OpConstant %2 -1
+)");
+}
+
+TEST_F(SpvGeneratorImplTest, Constant_U32) {
+    generator_.Constant(Constant(u32(42)));
+    generator_.Constant(Constant(u32(4000000000)));
+    EXPECT_EQ(DumpTypes(), R"(%2 = OpTypeInt 32 0
+%1 = OpConstant %2 42
+%3 = OpConstant %2 4000000000
+)");
+}
+
+TEST_F(SpvGeneratorImplTest, Constant_F32) {
+    generator_.Constant(Constant(f32(42)));
+    generator_.Constant(Constant(f32(-1)));
+    EXPECT_EQ(DumpTypes(), R"(%2 = OpTypeFloat 32
+%1 = OpConstant %2 42
+%3 = OpConstant %2 -1
+)");
+}
+
+TEST_F(SpvGeneratorImplTest, Constant_F16) {
+    generator_.Constant(Constant(f16(42)));
+    generator_.Constant(Constant(f16(-1)));
+    EXPECT_EQ(DumpTypes(), R"(%2 = OpTypeFloat 16
+%1 = OpConstant %2 0x1.5p+5
+%3 = OpConstant %2 -0x1p+0
+)");
+}
+
+// Test that we do not emit the same constant more than once.
+TEST_F(SpvGeneratorImplTest, Constant_Deduplicate) {
+    generator_.Constant(Constant(i32(42)));
+    generator_.Constant(Constant(i32(42)));
+    generator_.Constant(Constant(i32(42)));
+    EXPECT_EQ(DumpTypes(), R"(%2 = OpTypeInt 32 1
+%1 = OpConstant %2 42
+)");
+}
+
+}  // namespace
+}  // namespace tint::writer::spirv
diff --git a/src/tint/writer/spirv/generator_impl_function_test.cc b/src/tint/writer/spirv/generator_impl_function_test.cc
index e4cb71b..a1ce3cc 100644
--- a/src/tint/writer/spirv/generator_impl_function_test.cc
+++ b/src/tint/writer/spirv/generator_impl_function_test.cc
@@ -21,6 +21,7 @@
     auto* func = CreateFunction();
     func->name = ir.symbols.Register("foo");
     func->return_type = ir.types.Get<type::Void>();
+    func->start_target->branch.target = func->end_target;
 
     generator_.EmitFunction(func);
     EXPECT_EQ(DumpModule(generator_.Module()), R"(OpName %1 "foo"
@@ -37,6 +38,7 @@
 TEST_F(SpvGeneratorImplTest, Function_DeduplicateType) {
     auto* func = CreateFunction();
     func->return_type = ir.types.Get<type::Void>();
+    func->start_target->branch.target = func->end_target;
 
     generator_.EmitFunction(func);
     generator_.EmitFunction(func);
@@ -46,5 +48,115 @@
 )");
 }
 
+TEST_F(SpvGeneratorImplTest, Function_EntryPoint_Compute) {
+    auto* func = CreateFunction();
+    func->name = ir.symbols.Register("main");
+    func->return_type = ir.types.Get<type::Void>();
+    func->pipeline_stage = ir::Function::PipelineStage::kCompute;
+    func->workgroup_size = {32, 4, 1};
+    func->start_target->branch.target = func->end_target;
+
+    generator_.EmitFunction(func);
+    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpEntryPoint GLCompute %1 "main"
+OpExecutionMode %1 LocalSize 32 4 1
+OpName %1 "main"
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%1 = OpFunction %2 None %3
+%4 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(SpvGeneratorImplTest, Function_EntryPoint_Fragment) {
+    auto* func = CreateFunction();
+    func->name = ir.symbols.Register("main");
+    func->return_type = ir.types.Get<type::Void>();
+    func->pipeline_stage = ir::Function::PipelineStage::kFragment;
+    func->start_target->branch.target = func->end_target;
+
+    generator_.EmitFunction(func);
+    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpEntryPoint Fragment %1 "main"
+OpExecutionMode %1 OriginUpperLeft
+OpName %1 "main"
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%1 = OpFunction %2 None %3
+%4 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(SpvGeneratorImplTest, Function_EntryPoint_Vertex) {
+    auto* func = CreateFunction();
+    func->name = ir.symbols.Register("main");
+    func->return_type = ir.types.Get<type::Void>();
+    func->pipeline_stage = ir::Function::PipelineStage::kVertex;
+    func->start_target->branch.target = func->end_target;
+
+    generator_.EmitFunction(func);
+    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpEntryPoint Vertex %1 "main"
+OpName %1 "main"
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%1 = OpFunction %2 None %3
+%4 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+}
+
+TEST_F(SpvGeneratorImplTest, Function_EntryPoint_Multiple) {
+    auto* f1 = CreateFunction();
+    f1->name = ir.symbols.Register("main1");
+    f1->return_type = ir.types.Get<type::Void>();
+    f1->pipeline_stage = ir::Function::PipelineStage::kCompute;
+    f1->workgroup_size = {32, 4, 1};
+    f1->start_target->branch.target = f1->end_target;
+
+    auto* f2 = CreateFunction();
+    f2->name = ir.symbols.Register("main2");
+    f2->return_type = ir.types.Get<type::Void>();
+    f2->pipeline_stage = ir::Function::PipelineStage::kCompute;
+    f2->workgroup_size = {8, 2, 16};
+    f2->start_target->branch.target = f2->end_target;
+
+    auto* f3 = CreateFunction();
+    f3->name = ir.symbols.Register("main3");
+    f3->return_type = ir.types.Get<type::Void>();
+    f3->pipeline_stage = ir::Function::PipelineStage::kFragment;
+    f3->start_target->branch.target = f3->end_target;
+
+    generator_.EmitFunction(f1);
+    generator_.EmitFunction(f2);
+    generator_.EmitFunction(f3);
+    EXPECT_EQ(DumpModule(generator_.Module()), R"(OpEntryPoint GLCompute %1 "main1"
+OpEntryPoint GLCompute %5 "main2"
+OpEntryPoint Fragment %7 "main3"
+OpExecutionMode %1 LocalSize 32 4 1
+OpExecutionMode %5 LocalSize 8 2 16
+OpExecutionMode %7 OriginUpperLeft
+OpName %1 "main1"
+OpName %5 "main2"
+OpName %7 "main3"
+%2 = OpTypeVoid
+%3 = OpTypeFunction %2
+%1 = OpFunction %2 None %3
+%4 = OpLabel
+OpReturn
+OpFunctionEnd
+%5 = OpFunction %2 None %3
+%6 = OpLabel
+OpReturn
+OpFunctionEnd
+%7 = OpFunction %2 None %3
+%8 = OpLabel
+OpReturn
+OpFunctionEnd
+)");
+}
+
 }  // namespace
 }  // namespace tint::writer::spirv
diff --git a/src/tint/writer/spirv/generator_impl_ir.cc b/src/tint/writer/spirv/generator_impl_ir.cc
index 855662a..d0d7cc0 100644
--- a/src/tint/writer/spirv/generator_impl_ir.cc
+++ b/src/tint/writer/spirv/generator_impl_ir.cc
@@ -15,6 +15,9 @@
 #include "src/tint/writer/spirv/generator_impl_ir.h"
 
 #include "spirv/unified1/spirv.h"
+#include "src/tint/ir/binary.h"
+#include "src/tint/ir/block.h"
+#include "src/tint/ir/function_terminator.h"
 #include "src/tint/ir/module.h"
 #include "src/tint/switch.h"
 #include "src/tint/type/bool.h"
@@ -55,6 +58,40 @@
     return true;
 }
 
+uint32_t GeneratorImplIr::Constant(const ir::Constant* constant) {
+    return constants_.GetOrCreate(constant, [&]() {
+        auto id = module_.NextId();
+        auto* ty = constant->Type();
+        auto* value = constant->value;
+        Switch(
+            ty,  //
+            [&](const type::Bool*) {
+                module_.PushType(
+                    value->ValueAs<bool>() ? spv::Op::OpConstantTrue : spv::Op::OpConstantFalse,
+                    {Type(ty), id});
+            },
+            [&](const type::I32*) {
+                module_.PushType(spv::Op::OpConstant, {Type(ty), id, value->ValueAs<u32>()});
+            },
+            [&](const type::U32*) {
+                module_.PushType(spv::Op::OpConstant,
+                                 {Type(ty), id, U32Operand(value->ValueAs<i32>())});
+            },
+            [&](const type::F32*) {
+                module_.PushType(spv::Op::OpConstant, {Type(ty), id, value->ValueAs<f32>()});
+            },
+            [&](const type::F16*) {
+                module_.PushType(
+                    spv::Op::OpConstant,
+                    {Type(ty), id, U32Operand(value->ValueAs<f16>().BitsRepresentation())});
+            },
+            [&](Default) {
+                TINT_ICE(Writer, diagnostics_) << "unhandled constant type: " << ty->FriendlyName();
+            });
+        return id;
+    });
+}
+
 uint32_t GeneratorImplIr::Type(const type::Type* ty) {
     return types_.GetOrCreate(ty, [&]() {
         auto id = module_.NextId();
@@ -81,6 +118,24 @@
     });
 }
 
+uint32_t GeneratorImplIr::Value(const ir::Value* value) {
+    return Switch(
+        value,  //
+        [&](const ir::Constant* constant) { return Constant(constant); },
+        [&](const ir::Instruction* inst) {
+            auto id = instructions_.Find(inst);
+            if (TINT_UNLIKELY(!id)) {
+                TINT_ICE(Writer, diagnostics_) << "missing instruction result";
+                return 0u;
+            }
+            return *id;
+        },
+        [&](Default) {
+            TINT_ICE(Writer, diagnostics_) << "unhandled value node: " << value->TypeInfo().name;
+            return 0u;
+        });
+}
+
 void GeneratorImplIr::EmitFunction(const ir::Function* func) {
     // Make an ID for the function.
     auto id = module_.NextId();
@@ -88,7 +143,10 @@
     // Emit the function name.
     module_.PushDebug(spv::Op::OpName, {id, Operand(func->name.Name())});
 
-    // TODO(jrprice): Emit OpEntryPoint and OpExecutionMode declarations if needed.
+    // Emit OpEntryPoint and OpExecutionMode declarations if needed.
+    if (func->pipeline_stage != ir::Function::PipelineStage::kUndefined) {
+        EmitEntryPoint(func, id);
+    }
 
     // Get the ID for the return type.
     auto return_type_id = Type(func->return_type);
@@ -113,15 +171,92 @@
     // Create a function that we will add instructions to.
     // TODO(jrprice): Add the parameter declarations when they are supported in the IR.
     auto entry_block = module_.NextId();
-    Function current_function_(decl, entry_block, {});
+    current_function_ = Function(decl, entry_block, {});
+    TINT_DEFER(current_function_ = Function());
 
-    // TODO(jrprice): Emit the body of the function.
-
-    // TODO(jrprice): Remove this when we start emitting OpReturn for branches to the terminator.
-    current_function_.push_inst(spv::Op::OpReturn, {});
+    // Emit the body of the function.
+    EmitBlock(func->start_target);
 
     // Add the function to the module.
     module_.PushFunction(current_function_);
 }
 
+void GeneratorImplIr::EmitEntryPoint(const ir::Function* func, uint32_t id) {
+    SpvExecutionModel stage = SpvExecutionModelMax;
+    switch (func->pipeline_stage) {
+        case ir::Function::PipelineStage::kCompute: {
+            stage = SpvExecutionModelGLCompute;
+            module_.PushExecutionMode(
+                spv::Op::OpExecutionMode,
+                {id, U32Operand(SpvExecutionModeLocalSize), func->workgroup_size->at(0),
+                 func->workgroup_size->at(1), func->workgroup_size->at(2)});
+            break;
+        }
+        case ir::Function::PipelineStage::kFragment: {
+            stage = SpvExecutionModelFragment;
+            module_.PushExecutionMode(spv::Op::OpExecutionMode,
+                                      {id, U32Operand(SpvExecutionModeOriginUpperLeft)});
+            // TODO(jrprice): Add DepthReplacing execution mode if FragDepth is used.
+            break;
+        }
+        case ir::Function::PipelineStage::kVertex: {
+            stage = SpvExecutionModelVertex;
+            break;
+        }
+        case ir::Function::PipelineStage::kUndefined:
+            TINT_ICE(Writer, diagnostics_) << "undefined pipeline stage for entry point";
+            return;
+    }
+
+    // TODO(jrprice): Add the interface list of all referenced global variables.
+    module_.PushEntryPoint(spv::Op::OpEntryPoint, {U32Operand(stage), id, func->name.Name()});
+}
+
+void GeneratorImplIr::EmitBlock(const ir::Block* block) {
+    // Emit the instructions.
+    for (auto* inst : block->instructions) {
+        auto result = Switch(
+            inst,  //
+            [&](const ir::Binary* b) { return EmitBinary(b); },
+            [&](Default) {
+                TINT_ICE(Writer, diagnostics_)
+                    << "unimplemented instruction: " << inst->TypeInfo().name;
+                return 0u;
+            });
+        instructions_.Add(inst, result);
+    }
+
+    // Handle the branch at the end of the block.
+    Switch(
+        block->branch.target,
+        [&](const ir::FunctionTerminator*) {
+            // TODO(jrprice): Handle the return value, which will be a branch argument.
+            current_function_.push_inst(spv::Op::OpReturn, {});
+        },
+        [&](Default) { TINT_ICE(Writer, diagnostics_) << "unimplemented branch target"; });
+}
+
+uint32_t GeneratorImplIr::EmitBinary(const ir::Binary* binary) {
+    auto id = module_.NextId();
+
+    // Determine the opcode.
+    spv::Op op = spv::Op::Max;
+    switch (binary->GetKind()) {
+        case ir::Binary::Kind::kAdd: {
+            op = binary->Type()->is_integer_scalar_or_vector() ? spv::Op::OpIAdd : spv::Op::OpFAdd;
+            break;
+        }
+        default: {
+            TINT_ICE(Writer, diagnostics_)
+                << "unimplemented binary instruction: " << static_cast<uint32_t>(binary->GetKind());
+        }
+    }
+
+    // Emit the instruction.
+    current_function_.push_inst(
+        op, {Type(binary->Type()), id, Value(binary->LHS()), Value(binary->RHS())});
+
+    return id;
+}
+
 }  // namespace tint::writer::spirv
diff --git a/src/tint/writer/spirv/generator_impl_ir.h b/src/tint/writer/spirv/generator_impl_ir.h
index d9aab3c..61870d4 100644
--- a/src/tint/writer/spirv/generator_impl_ir.h
+++ b/src/tint/writer/spirv/generator_impl_ir.h
@@ -17,16 +17,22 @@
 
 #include <vector>
 
+#include "src/tint/constant/value.h"
 #include "src/tint/diagnostic/diagnostic.h"
+#include "src/tint/ir/constant.h"
 #include "src/tint/utils/hashmap.h"
 #include "src/tint/utils/vector.h"
 #include "src/tint/writer/spirv/binary_writer.h"
+#include "src/tint/writer/spirv/function.h"
 #include "src/tint/writer/spirv/module.h"
 
 // Forward declarations
 namespace tint::ir {
+class Binary;
+class Block;
 class Function;
 class Module;
+class Value;
 }  // namespace tint::ir
 namespace tint::type {
 class Type;
@@ -55,15 +61,39 @@
     /// @returns the list of diagnostics raised by the generator
     diag::List Diagnostics() const { return diagnostics_; }
 
+    /// Get the result ID of the constant `constant`, emitting its instruction if necessary.
+    /// @param constant the constant to get the ID for
+    /// @returns the result ID of the constant
+    uint32_t Constant(const ir::Constant* constant);
+
     /// Get the result ID of the type `ty`, emitting a type declaration instruction if necessary.
     /// @param ty the type to get the ID for
     /// @returns the result ID of the type
     uint32_t Type(const type::Type* ty);
 
+    /// Get the result ID of the value `value`, emitting its instruction if necessary.
+    /// @param value the value to get the ID for
+    /// @returns the result ID of the value
+    uint32_t Value(const ir::Value* value);
+
     /// Emit a function.
     /// @param func the function to emit
     void EmitFunction(const ir::Function* func);
 
+    /// Emit entry point declarations for a function.
+    /// @param func the function to emit entry point declarations for
+    /// @param id the result ID of the function declaration
+    void EmitEntryPoint(const ir::Function* func, uint32_t id);
+
+    /// Emit a block.
+    /// @param block the block to emit
+    void EmitBlock(const ir::Block* block);
+
+    /// Emit a binary instruction.
+    /// @param binary the binary instruction to emit
+    /// @returns the result ID of the instruction
+    uint32_t EmitBinary(const ir::Binary* binary);
+
   private:
     const ir::Module* ir_;
     spirv::Module module_;
@@ -95,12 +125,40 @@
         }
     };
 
+    /// ConstantHasher provides a hash function for an ir::Constant pointer, hashing the value
+    /// instead of the pointer itself.
+    struct ConstantHasher {
+        /// @param c the ir::Constant pointer to create a hash for
+        /// @return the hash value
+        inline std::size_t operator()(const ir::Constant* c) const { return c->value->Hash(); }
+    };
+
+    /// ConstantEquals provides an equality function for two ir::Constant pointers, comparing their
+    /// values instead of the pointers.
+    struct ConstantEquals {
+        /// @param a the first ir::Constant pointer to compare
+        /// @param b the second ir::Constant pointer to compare
+        /// @return the hash value
+        inline bool operator()(const ir::Constant* a, const ir::Constant* b) const {
+            return a->value->Equal(b->value);
+        }
+    };
+
     /// The map of types to their result IDs.
     utils::Hashmap<const type::Type*, uint32_t, 8> types_;
 
     /// The map of function types to their result IDs.
     utils::Hashmap<FunctionType, uint32_t, 8, FunctionType::Hasher> function_types_;
 
+    /// The map of constants to their result IDs.
+    utils::Hashmap<const ir::Constant*, uint32_t, 16, ConstantHasher, ConstantEquals> constants_;
+
+    /// The map of instructions to their result IDs.
+    utils::Hashmap<const ir::Instruction*, uint32_t, 8> instructions_;
+
+    /// The current function that is being emitted.
+    Function current_function_;
+
     bool zero_init_workgroup_memory_ = false;
 };
 
diff --git a/src/tint/writer/text_generator.cc b/src/tint/writer/text_generator.cc
index 13a75d5..4b0ab6a 100644
--- a/src/tint/writer/text_generator.cc
+++ b/src/tint/writer/text_generator.cc
@@ -39,15 +39,6 @@
     return name;
 }
 
-std::string TextGenerator::TrimSuffix(std::string str, const std::string& suffix) {
-    if (str.size() >= suffix.size()) {
-        if (str.substr(str.size() - suffix.size(), suffix.size()) == suffix) {
-            return str.substr(0, str.size() - suffix.size());
-        }
-    }
-    return str;
-}
-
 TextGenerator::LineWriter::LineWriter(TextBuffer* buf) : buffer(buf) {}
 
 TextGenerator::LineWriter::LineWriter(LineWriter&& other) {
diff --git a/src/tint/writer/text_generator.h b/src/tint/writer/text_generator.h
index 04fe785..ad3eb80 100644
--- a/src/tint/writer/text_generator.h
+++ b/src/tint/writer/text_generator.h
@@ -114,12 +114,6 @@
     /// underscores.
     std::string StructName(const type::Struct* s);
 
-    /// @param str the string
-    /// @param suffix the suffix to remove
-    /// @return returns str without the provided trailing suffix string. If str
-    /// doesn't end with suffix, str is returned unchanged.
-    std::string TrimSuffix(std::string str, const std::string& suffix);
-
   protected:
     /// LineWriter is a helper that acts as a string buffer, who's content is
     /// emitted to the TextBuffer as a single line on destruction.
diff --git a/src/tint/writer/wgsl/generator_impl.cc b/src/tint/writer/wgsl/generator_impl.cc
index 2703e8d..0d626f2 100644
--- a/src/tint/writer/wgsl/generator_impl.cc
+++ b/src/tint/writer/wgsl/generator_impl.cc
@@ -818,14 +818,14 @@
                 case 0:  // No initializer
                     break;
                 case 1:  // Single line initializer statement
-                    out << TrimSuffix(init_buf.lines[0].content, ";");
+                    out << utils::TrimSuffix(init_buf.lines[0].content, ";");
                     break;
                 default:  // Block initializer statement
                     for (size_t i = 1; i < init_buf.lines.size(); i++) {
                         // Indent all by the first line
                         init_buf.lines[i].indent += current_buffer_->current_indent;
                     }
-                    out << TrimSuffix(init_buf.String(), "\n");
+                    out << utils::TrimSuffix(init_buf.String(), "\n");
                     break;
             }
 
@@ -841,14 +841,14 @@
                 case 0:  // No continuing
                     break;
                 case 1:  // Single line continuing statement
-                    out << TrimSuffix(cont_buf.lines[0].content, ";");
+                    out << utils::TrimSuffix(cont_buf.lines[0].content, ";");
                     break;
                 default:  // Block continuing statement
                     for (size_t i = 1; i < cont_buf.lines.size(); i++) {
                         // Indent all by the first line
                         cont_buf.lines[i].indent += current_buffer_->current_indent;
                     }
-                    out << TrimSuffix(cont_buf.String(), "\n");
+                    out << utils::TrimSuffix(cont_buf.String(), "\n");
                     break;
             }
         }