Convert ir::Constant over to use a constant::Value

This CL updaets the ir::Constant to store a constant::Value instead of the specific numbers
themselves.

Bug: tint:1718
Change-Id: I66b0a9643893b6079399daf61ee39ac5811e1eaf
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/114362
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
diff --git a/src/tint/cmd/main.cc b/src/tint/cmd/main.cc
index ae2e2e2..fd67617 100644
--- a/src/tint/cmd/main.cc
+++ b/src/tint/cmd/main.cc
@@ -1344,8 +1344,8 @@
         } else {
             auto mod = result.Move();
             if (options.dump_ir) {
-                tint::ir::Disassembler d;
-                std::cout << d.Disassemble(mod) << std::endl;
+                tint::ir::Disassembler d(mod);
+                std::cout << d.Disassemble() << std::endl;
             }
             if (options.dump_ir_graph) {
                 auto graph = tint::ir::Debug::AsDotGraph(&mod);
diff --git a/src/tint/ir/binary.cc b/src/tint/ir/binary.cc
index 1a35b61..ccb679b 100644
--- a/src/tint/ir/binary.cc
+++ b/src/tint/ir/binary.cc
@@ -28,9 +28,9 @@
 
 Binary::~Binary() = default;
 
-std::ostream& Binary::ToString(std::ostream& out) const {
-    Result()->ToString(out) << " = ";
-    lhs_->ToString(out) << " ";
+std::ostream& Binary::ToString(std::ostream& out, const SymbolTable& st) const {
+    Result()->ToString(out, st) << " = ";
+    lhs_->ToString(out, st) << " ";
 
     switch (GetKind()) {
         case Binary::Kind::kAdd:
@@ -89,7 +89,7 @@
             break;
     }
     out << " ";
-    rhs_->ToString(out);
+    rhs_->ToString(out, st);
 
     return out;
 }
diff --git a/src/tint/ir/binary.h b/src/tint/ir/binary.h
index 755dd9c..0f7fe57 100644
--- a/src/tint/ir/binary.h
+++ b/src/tint/ir/binary.h
@@ -20,6 +20,7 @@
 #include "src/tint/castable.h"
 #include "src/tint/ir/instruction.h"
 #include "src/tint/ir/value.h"
+#include "src/tint/symbol_table.h"
 
 namespace tint::ir {
 
@@ -79,8 +80,9 @@
 
     /// Write the instruction to the given stream
     /// @param out the stream to write to
+    /// @param st the symbol table
     /// @returns the stream
-    std::ostream& ToString(std::ostream& out) const override;
+    std::ostream& ToString(std::ostream& out, const SymbolTable& st) const override;
 
   private:
     Kind kind_;
diff --git a/src/tint/ir/binary_test.cc b/src/tint/ir/binary_test.cc
index 81d0ada..f90661b 100644
--- a/src/tint/ir/binary_test.cc
+++ b/src/tint/ir/binary_test.cc
@@ -20,13 +20,15 @@
 namespace tint::ir {
 namespace {
 
+using namespace tint::number_suffixes;  // NOLINT
+                                        //
 using IR_InstructionTest = TestHelper;
 
 TEST_F(IR_InstructionTest, CreateAnd) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.And(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+    const auto* instr = b.builder.And(b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kAnd);
 
@@ -34,17 +36,17 @@
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
-    auto lhs = instr->LHS()->As<Constant>();
-    ASSERT_TRUE(lhs->IsI32());
-    EXPECT_EQ(i32(4), lhs->AsI32());
+    auto lhs = instr->LHS()->As<Constant>()->value;
+    ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
+    auto rhs = instr->RHS()->As<Constant>()->value;
+    ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 & 2");
 }
 
@@ -52,7 +54,7 @@
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.Or(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+    const auto* instr = b.builder.Or(b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kOr);
 
@@ -60,17 +62,17 @@
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
-    auto lhs = instr->LHS()->As<Constant>();
-    ASSERT_TRUE(lhs->IsI32());
-    EXPECT_EQ(i32(4), lhs->AsI32());
+    auto lhs = instr->LHS()->As<Constant>()->value;
+    ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
+    auto rhs = instr->RHS()->As<Constant>()->value;
+    ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 | 2");
 }
 
@@ -78,7 +80,7 @@
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.Xor(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+    const auto* instr = b.builder.Xor(b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kXor);
 
@@ -86,17 +88,17 @@
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
-    auto lhs = instr->LHS()->As<Constant>();
-    ASSERT_TRUE(lhs->IsI32());
-    EXPECT_EQ(i32(4), lhs->AsI32());
+    auto lhs = instr->LHS()->As<Constant>()->value;
+    ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
+    auto rhs = instr->RHS()->As<Constant>()->value;
+    ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 ^ 2");
 }
 
@@ -104,8 +106,7 @@
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr =
-        b.builder.LogicalAnd(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+    const auto* instr = b.builder.LogicalAnd(b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kLogicalAnd);
 
@@ -113,17 +114,17 @@
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
-    auto lhs = instr->LHS()->As<Constant>();
-    ASSERT_TRUE(lhs->IsI32());
-    EXPECT_EQ(i32(4), lhs->AsI32());
+    auto lhs = instr->LHS()->As<Constant>()->value;
+    ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
+    auto rhs = instr->RHS()->As<Constant>()->value;
+    ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 && 2");
 }
 
@@ -131,7 +132,7 @@
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.LogicalOr(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+    const auto* instr = b.builder.LogicalOr(b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kLogicalOr);
 
@@ -139,17 +140,17 @@
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
-    auto lhs = instr->LHS()->As<Constant>();
-    ASSERT_TRUE(lhs->IsI32());
-    EXPECT_EQ(i32(4), lhs->AsI32());
+    auto lhs = instr->LHS()->As<Constant>()->value;
+    ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
+    auto rhs = instr->RHS()->As<Constant>()->value;
+    ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 || 2");
 }
 
@@ -157,7 +158,7 @@
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.Equal(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+    const auto* instr = b.builder.Equal(b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kEqual);
 
@@ -165,17 +166,17 @@
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
-    auto lhs = instr->LHS()->As<Constant>();
-    ASSERT_TRUE(lhs->IsI32());
-    EXPECT_EQ(i32(4), lhs->AsI32());
+    auto lhs = instr->LHS()->As<Constant>()->value;
+    ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
+    auto rhs = instr->RHS()->As<Constant>()->value;
+    ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 == 2");
 }
 
@@ -183,7 +184,7 @@
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.NotEqual(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+    const auto* instr = b.builder.NotEqual(b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kNotEqual);
 
@@ -191,17 +192,17 @@
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
-    auto lhs = instr->LHS()->As<Constant>();
-    ASSERT_TRUE(lhs->IsI32());
-    EXPECT_EQ(i32(4), lhs->AsI32());
+    auto lhs = instr->LHS()->As<Constant>()->value;
+    ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
+    auto rhs = instr->RHS()->As<Constant>()->value;
+    ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 != 2");
 }
 
@@ -209,7 +210,7 @@
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.LessThan(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+    const auto* instr = b.builder.LessThan(b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kLessThan);
 
@@ -217,17 +218,17 @@
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
-    auto lhs = instr->LHS()->As<Constant>();
-    ASSERT_TRUE(lhs->IsI32());
-    EXPECT_EQ(i32(4), lhs->AsI32());
+    auto lhs = instr->LHS()->As<Constant>()->value;
+    ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
+    auto rhs = instr->RHS()->As<Constant>()->value;
+    ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 < 2");
 }
 
@@ -235,8 +236,7 @@
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr =
-        b.builder.GreaterThan(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+    const auto* instr = b.builder.GreaterThan(b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kGreaterThan);
 
@@ -244,17 +244,17 @@
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
-    auto lhs = instr->LHS()->As<Constant>();
-    ASSERT_TRUE(lhs->IsI32());
-    EXPECT_EQ(i32(4), lhs->AsI32());
+    auto lhs = instr->LHS()->As<Constant>()->value;
+    ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
+    auto rhs = instr->RHS()->As<Constant>()->value;
+    ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 > 2");
 }
 
@@ -262,8 +262,7 @@
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr =
-        b.builder.LessThanEqual(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+    const auto* instr = b.builder.LessThanEqual(b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kLessThanEqual);
 
@@ -271,17 +270,17 @@
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
-    auto lhs = instr->LHS()->As<Constant>();
-    ASSERT_TRUE(lhs->IsI32());
-    EXPECT_EQ(i32(4), lhs->AsI32());
+    auto lhs = instr->LHS()->As<Constant>()->value;
+    ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
+    auto rhs = instr->RHS()->As<Constant>()->value;
+    ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 <= 2");
 }
 
@@ -290,7 +289,7 @@
 
     b.builder.next_temp_id = Temp::Id(42);
     const auto* instr =
-        b.builder.GreaterThanEqual(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+        b.builder.GreaterThanEqual(b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kGreaterThanEqual);
 
@@ -298,17 +297,17 @@
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
-    auto lhs = instr->LHS()->As<Constant>();
-    ASSERT_TRUE(lhs->IsI32());
-    EXPECT_EQ(i32(4), lhs->AsI32());
+    auto lhs = instr->LHS()->As<Constant>()->value;
+    ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
+    auto rhs = instr->RHS()->As<Constant>()->value;
+    ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 >= 2");
 }
 
@@ -316,7 +315,7 @@
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.ShiftLeft(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+    const auto* instr = b.builder.ShiftLeft(b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kShiftLeft);
 
@@ -324,17 +323,17 @@
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
-    auto lhs = instr->LHS()->As<Constant>();
-    ASSERT_TRUE(lhs->IsI32());
-    EXPECT_EQ(i32(4), lhs->AsI32());
+    auto lhs = instr->LHS()->As<Constant>()->value;
+    ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
+    auto rhs = instr->RHS()->As<Constant>()->value;
+    ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 << 2");
 }
 
@@ -342,8 +341,7 @@
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr =
-        b.builder.ShiftRight(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+    const auto* instr = b.builder.ShiftRight(b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kShiftRight);
 
@@ -351,17 +349,17 @@
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
-    auto lhs = instr->LHS()->As<Constant>();
-    ASSERT_TRUE(lhs->IsI32());
-    EXPECT_EQ(i32(4), lhs->AsI32());
+    auto lhs = instr->LHS()->As<Constant>()->value;
+    ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
+    auto rhs = instr->RHS()->As<Constant>()->value;
+    ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 >> 2");
 }
 
@@ -369,7 +367,7 @@
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.Add(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+    const auto* instr = b.builder.Add(b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kAdd);
 
@@ -377,17 +375,17 @@
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
-    auto lhs = instr->LHS()->As<Constant>();
-    ASSERT_TRUE(lhs->IsI32());
-    EXPECT_EQ(i32(4), lhs->AsI32());
+    auto lhs = instr->LHS()->As<Constant>()->value;
+    ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
+    auto rhs = instr->RHS()->As<Constant>()->value;
+    ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 + 2");
 }
 
@@ -395,7 +393,7 @@
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.Subtract(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+    const auto* instr = b.builder.Subtract(b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kSubtract);
 
@@ -403,17 +401,17 @@
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
-    auto lhs = instr->LHS()->As<Constant>();
-    ASSERT_TRUE(lhs->IsI32());
-    EXPECT_EQ(i32(4), lhs->AsI32());
+    auto lhs = instr->LHS()->As<Constant>()->value;
+    ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
+    auto rhs = instr->RHS()->As<Constant>()->value;
+    ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 - 2");
 }
 
@@ -421,7 +419,7 @@
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.Multiply(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+    const auto* instr = b.builder.Multiply(b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kMultiply);
 
@@ -429,17 +427,17 @@
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
-    auto lhs = instr->LHS()->As<Constant>();
-    ASSERT_TRUE(lhs->IsI32());
-    EXPECT_EQ(i32(4), lhs->AsI32());
+    auto lhs = instr->LHS()->As<Constant>()->value;
+    ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
+    auto rhs = instr->RHS()->As<Constant>()->value;
+    ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 * 2");
 }
 
@@ -447,7 +445,7 @@
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.Divide(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+    const auto* instr = b.builder.Divide(b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kDivide);
 
@@ -455,17 +453,17 @@
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
-    auto lhs = instr->LHS()->As<Constant>();
-    ASSERT_TRUE(lhs->IsI32());
-    EXPECT_EQ(i32(4), lhs->AsI32());
+    auto lhs = instr->LHS()->As<Constant>()->value;
+    ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
+    auto rhs = instr->RHS()->As<Constant>()->value;
+    ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 / 2");
 }
 
@@ -473,7 +471,7 @@
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.Modulo(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
+    const auto* instr = b.builder.Modulo(b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kModulo);
 
@@ -481,17 +479,17 @@
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
-    auto lhs = instr->LHS()->As<Constant>();
-    ASSERT_TRUE(lhs->IsI32());
-    EXPECT_EQ(i32(4), lhs->AsI32());
+    auto lhs = instr->LHS()->As<Constant>()->value;
+    ASSERT_TRUE(lhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, lhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
+    auto rhs = instr->RHS()->As<Constant>()->value;
+    ASSERT_TRUE(rhs->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(2_i, rhs->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 % 2");
 }
 
diff --git a/src/tint/ir/builder.h b/src/tint/ir/builder.h
index ceabfa6b..ca21b8d 100644
--- a/src/tint/ir/builder.h
+++ b/src/tint/ir/builder.h
@@ -15,6 +15,9 @@
 #ifndef SRC_TINT_IR_BUILDER_H_
 #define SRC_TINT_IR_BUILDER_H_
 
+#include <utility>
+
+#include "src/tint/constant/scalar.h"
 #include "src/tint/ir/binary.h"
 #include "src/tint/ir/constant.h"
 #include "src/tint/ir/function.h"
@@ -25,6 +28,11 @@
 #include "src/tint/ir/temp.h"
 #include "src/tint/ir/terminator.h"
 #include "src/tint/ir/value.h"
+#include "src/tint/type/bool.h"
+#include "src/tint/type/f16.h"
+#include "src/tint/type/f32.h"
+#include "src/tint/type/i32.h"
+#include "src/tint/type/u32.h"
 
 // Forward Declarations
 namespace tint {
@@ -85,14 +93,56 @@
     /// @param to the node to branch too
     void Branch(Block* from, FlowNode* to);
 
-    /// Creates a new Constant
+    /// Creates a constant::Value
+    /// @param args the arguments
+    /// @returns the new constant value
+    template <typename T, typename... ARGS>
+    traits::EnableIf<traits::IsTypeOrDerived<T, constant::Value>, const T>* create(ARGS&&... args) {
+        return ir.constants.Create<T>(std::forward<ARGS>(args)...);
+    }
+
+    /// Creates a new ir::Constant
     /// @param val the constant value
     /// @returns the new constant
-    template <typename T>
-    const ir::Constant* Constant(T val) {
+    const ir::Constant* Constant(const constant::Value* val) {
         return ir.values.Create<ir::Constant>(val);
     }
 
+    /// Creates a ir::Constant for an i32 Scalar
+    /// @param v the value
+    /// @returns the new constant
+    const ir::Constant* Constant(i32 v) {
+        return Constant(create<constant::Scalar<i32>>(ir.types.Get<type::I32>(), v));
+    }
+
+    /// Creates a ir::Constant for a u32 Scalar
+    /// @param v the value
+    /// @returns the new constant
+    const ir::Constant* Constant(u32 v) {
+        return Constant(create<constant::Scalar<u32>>(ir.types.Get<type::U32>(), v));
+    }
+
+    /// Creates a ir::Constant for a f32 Scalar
+    /// @param v the value
+    /// @returns the new constant
+    const ir::Constant* Constant(f32 v) {
+        return Constant(create<constant::Scalar<f32>>(ir.types.Get<type::F32>(), v));
+    }
+
+    /// Creates a ir::Constant for a f16 Scalar
+    /// @param v the value
+    /// @returns the new constant
+    const ir::Constant* Constant(f16 v) {
+        return Constant(create<constant::Scalar<f16>>(ir.types.Get<type::F16>(), v));
+    }
+
+    /// Creates a ir::Constant for a bool Scalar
+    /// @param v the value
+    /// @returns the new constant
+    const ir::Constant* Constant(bool v) {
+        return Constant(create<constant::Scalar<bool>>(ir.types.Get<type::Bool>(), v));
+    }
+
     /// Creates a new Temporary
     /// @returns the new temporary
     const ir::Temp* Temp() { return ir.values.Create<ir::Temp>(AllocateTempId()); }
diff --git a/src/tint/ir/builder_impl.cc b/src/tint/ir/builder_impl.cc
index 042a7d5..afa56f5 100644
--- a/src/tint/ir/builder_impl.cc
+++ b/src/tint/ir/builder_impl.cc
@@ -41,6 +41,7 @@
 #include "src/tint/ir/switch.h"
 #include "src/tint/ir/terminator.h"
 #include "src/tint/program.h"
+#include "src/tint/sem/expression.h"
 #include "src/tint/sem/module.h"
 
 namespace tint::ir {
@@ -628,30 +629,24 @@
 }
 
 utils::Result<const Value*> BuilderImpl::EmitLiteral(const ast::LiteralExpression* lit) {
-    return tint::Switch(  //
-        lit,
-        [&](const ast::BoolLiteralExpression* l) {
-            return utils::Result<const Value*>(builder.Constant(l->value));
-        },
-        [&](const ast::FloatLiteralExpression* l) {
-            if (l->suffix == ast::FloatLiteralExpression::Suffix::kF) {
-                return utils::Result<const Value*>(
-                    builder.Constant(f32(static_cast<float>(l->value))));
-            }
-            return utils::Result<const Value*>(builder.Constant(f16(static_cast<float>(l->value))));
-        },
-        [&](const ast::IntLiteralExpression* l) {
-            if (l->suffix == ast::IntLiteralExpression::Suffix::kI) {
-                return utils::Result<const Value*>(builder.Constant(i32(l->value)));
-            }
-            return utils::Result<const Value*>(builder.Constant(u32(l->value)));
-        },
-        [&](Default) {
-            diagnostics_.add_warning(tint::diag::System::IR,
-                                     "unknown literal type: " + std::string(lit->TypeInfo().name),
-                                     lit->source);
-            return utils::Failure;
-        });
+    auto* sem = builder.ir.program->Sem().Get(lit);
+    if (!sem) {
+        diagnostics_.add_error(
+            tint::diag::System::IR,
+            "Failed to get semantic information for node " + std::string(lit->TypeInfo().name),
+            lit->source);
+        return utils::Failure;
+    }
+
+    auto* cv = sem->ConstantValue();
+    if (!cv) {
+        diagnostics_.add_error(
+            tint::diag::System::IR,
+            "Failed to get constant value for node " + std::string(lit->TypeInfo().name),
+            lit->source);
+        return utils::Failure;
+    }
+    return utils::Result<const Value*>(builder.Constant(cv));
 }
 
 bool BuilderImpl::EmitType(const ast::Type* ty) {
diff --git a/src/tint/ir/builder_impl_test.cc b/src/tint/ir/builder_impl_test.cc
index 79e6e3f..0600a15 100644
--- a/src/tint/ir/builder_impl_test.cc
+++ b/src/tint/ir/builder_impl_test.cc
@@ -102,9 +102,9 @@
 
     // Check condition
     ASSERT_TRUE(flow->condition->Is<Constant>());
-    auto* instr = flow->condition->As<Constant>();
-    ASSERT_TRUE(instr->IsBool());
-    EXPECT_TRUE(instr->AsBool());
+    auto* instr = flow->condition->As<Constant>()->value;
+    ASSERT_TRUE(instr->Is<constant::Scalar<bool>>());
+    EXPECT_TRUE(instr->As<constant::Scalar<bool>>()->ValueAs<bool>());
 }
 
 TEST_F(IR_BuilderImplTest, IfStatement_TrueReturns) {
@@ -504,9 +504,9 @@
 
     // Check condition
     ASSERT_TRUE(if_flow->condition->Is<Constant>());
-    auto* instr = if_flow->condition->As<Constant>();
-    ASSERT_TRUE(instr->IsBool());
-    EXPECT_TRUE(instr->AsBool());
+    auto* instr = if_flow->condition->As<Constant>()->value;
+    ASSERT_TRUE(instr->Is<constant::Scalar<bool>>());
+    EXPECT_TRUE(instr->As<constant::Scalar<bool>>()->ValueAs<bool>());
 }
 
 TEST_F(IR_BuilderImplTest, Loop_WithOnlyReturn) {
@@ -950,9 +950,9 @@
 
     // Check condition
     ASSERT_TRUE(if_flow->condition->Is<Constant>());
-    auto* instr = if_flow->condition->As<Constant>();
-    ASSERT_TRUE(instr->IsBool());
-    EXPECT_FALSE(instr->AsBool());
+    auto* instr = if_flow->condition->As<Constant>()->value;
+    ASSERT_TRUE(instr->Is<constant::Scalar<bool>>());
+    EXPECT_FALSE(instr->As<constant::Scalar<bool>>()->ValueAs<bool>());
 }
 
 TEST_F(IR_BuilderImplTest, While_Return) {
@@ -1075,9 +1075,9 @@
 
     // Check condition
     ASSERT_TRUE(if_flow->condition->Is<Constant>());
-    auto* instr = if_flow->condition->As<Constant>();
-    ASSERT_TRUE(instr->IsBool());
-    EXPECT_FALSE(instr->AsBool());
+    auto* instr = if_flow->condition->As<Constant>()->value;
+    ASSERT_TRUE(instr->Is<constant::Scalar<bool>>());
+    EXPECT_FALSE(instr->As<constant::Scalar<bool>>()->ValueAs<bool>());
 }
 
 TEST_F(IR_BuilderImplTest, For_NoInitCondOrContinuing) {
@@ -1176,9 +1176,9 @@
 
     // Check condition
     ASSERT_TRUE(flow->condition->Is<Constant>());
-    auto* instr = flow->condition->As<Constant>();
-    ASSERT_TRUE(instr->IsI32());
-    EXPECT_EQ(1_i, instr->AsI32());
+    auto* instr = flow->condition->As<Constant>()->value;
+    ASSERT_TRUE(instr->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(1_i, instr->As<constant::Scalar<i32>>()->ValueAs<i32>());
 }
 
 TEST_F(IR_BuilderImplTest, Switch_OnlyDefault) {
@@ -1342,283 +1342,378 @@
 }
 
 TEST_F(IR_BuilderImplTest, EmitLiteral_Bool_True) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitLiteral(Expr(true));
-    ASSERT_TRUE(r);
+    auto* expr = Expr(true);
+    GlobalVar("a", ty.bool_(), ast::AddressSpace::kPrivate, expr);
+
+    auto& b = CreateBuilder();
+    auto r = b.EmitLiteral(expr);
+    ASSERT_TRUE(r) << b.error();
 
     ASSERT_TRUE(r.Get()->Is<Constant>());
-    auto* val = r.Get()->As<Constant>();
-    EXPECT_TRUE(val->IsBool());
-    EXPECT_TRUE(val->AsBool());
+    auto* val = r.Get()->As<Constant>()->value;
+    EXPECT_TRUE(val->Is<constant::Scalar<bool>>());
+    EXPECT_TRUE(val->As<constant::Scalar<bool>>()->ValueAs<bool>());
 }
 
 TEST_F(IR_BuilderImplTest, EmitLiteral_Bool_False) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitLiteral(Expr(false));
-    ASSERT_TRUE(r);
+    auto* expr = Expr(false);
+    GlobalVar("a", ty.bool_(), ast::AddressSpace::kPrivate, expr);
+
+    auto& b = CreateBuilder();
+    auto r = b.EmitLiteral(expr);
+    ASSERT_TRUE(r) << b.error();
 
     ASSERT_TRUE(r.Get()->Is<Constant>());
-    auto* val = r.Get()->As<Constant>();
-    EXPECT_TRUE(val->IsBool());
-    EXPECT_FALSE(val->AsBool());
+    auto* val = r.Get()->As<Constant>()->value;
+    EXPECT_TRUE(val->Is<constant::Scalar<bool>>());
+    EXPECT_FALSE(val->As<constant::Scalar<bool>>()->ValueAs<bool>());
 }
 
 TEST_F(IR_BuilderImplTest, EmitLiteral_F32) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitLiteral(Expr(1.2_f));
-    ASSERT_TRUE(r);
+    auto* expr = Expr(1.2_f);
+    GlobalVar("a", ty.f32(), ast::AddressSpace::kPrivate, expr);
+
+    auto& b = CreateBuilder();
+    auto r = b.EmitLiteral(expr);
+    ASSERT_TRUE(r) << b.error();
 
     ASSERT_TRUE(r.Get()->Is<Constant>());
-    auto* val = r.Get()->As<Constant>();
-    EXPECT_TRUE(val->IsF32());
-    EXPECT_EQ(1.2_f, val->AsF32());
+    auto* val = r.Get()->As<Constant>()->value;
+    EXPECT_TRUE(val->Is<constant::Scalar<f32>>());
+    EXPECT_EQ(1.2_f, val->As<constant::Scalar<f32>>()->ValueAs<f32>());
 }
 
 TEST_F(IR_BuilderImplTest, EmitLiteral_F16) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitLiteral(Expr(1.2_h));
-    ASSERT_TRUE(r);
+    Enable(ast::Extension::kF16);
+    auto* expr = Expr(1.2_h);
+    GlobalVar("a", ty.f16(), ast::AddressSpace::kPrivate, expr);
+
+    auto& b = CreateBuilder();
+    auto r = b.EmitLiteral(expr);
+    ASSERT_TRUE(r) << b.error();
 
     ASSERT_TRUE(r.Get()->Is<Constant>());
-    auto* val = r.Get()->As<Constant>();
-    EXPECT_TRUE(val->IsF16());
-    EXPECT_EQ(1.2_h, val->AsF16());
+    auto* val = r.Get()->As<Constant>()->value;
+    EXPECT_TRUE(val->Is<constant::Scalar<f16>>());
+    EXPECT_EQ(1.2_h, val->As<constant::Scalar<f16>>()->ValueAs<f32>());
 }
 
 TEST_F(IR_BuilderImplTest, EmitLiteral_I32) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitLiteral(Expr(-2_i));
-    ASSERT_TRUE(r);
+    auto* expr = Expr(-2_i);
+    GlobalVar("a", ty.i32(), ast::AddressSpace::kPrivate, expr);
+
+    auto& b = CreateBuilder();
+    auto r = b.EmitLiteral(expr);
+    ASSERT_TRUE(r) << b.error();
 
     ASSERT_TRUE(r.Get()->Is<Constant>());
-    auto* val = r.Get()->As<Constant>();
-    EXPECT_TRUE(val->IsI32());
-    EXPECT_EQ(-2_i, val->AsI32());
+    auto* val = r.Get()->As<Constant>()->value;
+    EXPECT_TRUE(val->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(-2_i, val->As<constant::Scalar<i32>>()->ValueAs<f32>());
 }
 
 TEST_F(IR_BuilderImplTest, EmitLiteral_U32) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitLiteral(Expr(2_u));
-    ASSERT_TRUE(r);
+    auto* expr = Expr(2_u);
+    GlobalVar("a", ty.u32(), ast::AddressSpace::kPrivate, expr);
+
+    auto& b = CreateBuilder();
+    auto r = b.EmitLiteral(expr);
+    ASSERT_TRUE(r) << b.error();
 
     ASSERT_TRUE(r.Get()->Is<Constant>());
-    auto* val = r.Get()->As<Constant>();
-    EXPECT_TRUE(val->IsU32());
-    EXPECT_EQ(2_u, val->AsU32());
+    auto* val = r.Get()->As<Constant>()->value;
+    EXPECT_TRUE(val->Is<constant::Scalar<u32>>());
+    EXPECT_EQ(2_u, val->As<constant::Scalar<u32>>()->ValueAs<f32>());
 }
 
 TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Add) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitExpression(Add(3_u, 4_u));
-    ASSERT_TRUE(r);
+    auto* expr = Add(3_u, 4_u);
+    WrapInFunction(expr);
 
-    Disassembler d;
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
     d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 + 4
 )");
 }
 
 TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Subtract) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitExpression(Sub(3_u, 4_u));
-    ASSERT_TRUE(r);
+    auto* expr = Sub(3_u, 4_u);
+    WrapInFunction(expr);
 
-    Disassembler d;
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
     d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 - 4
 )");
 }
 
 TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Multiply) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitExpression(Mul(3_u, 4_u));
-    ASSERT_TRUE(r);
+    auto* expr = Mul(3_u, 4_u);
+    WrapInFunction(expr);
 
-    Disassembler d;
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
     d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 * 4
 )");
 }
 
 TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Div) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitExpression(Div(3_u, 4_u));
-    ASSERT_TRUE(r);
+    auto* expr = Div(3_u, 4_u);
+    WrapInFunction(expr);
 
-    Disassembler d;
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
     d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 / 4
 )");
 }
 
 TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Modulo) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitExpression(Mod(3_u, 4_u));
-    ASSERT_TRUE(r);
+    auto* expr = Mod(3_u, 4_u);
+    WrapInFunction(expr);
 
-    Disassembler d;
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
     d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 % 4
 )");
 }
 
 TEST_F(IR_BuilderImplTest, EmitExpression_Binary_And) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitExpression(And(3_u, 4_u));
-    ASSERT_TRUE(r);
+    auto* expr = And(3_u, 4_u);
+    WrapInFunction(expr);
 
-    Disassembler d;
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
     d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 & 4
 )");
 }
 
 TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Or) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitExpression(Or(3_u, 4_u));
-    ASSERT_TRUE(r);
+    auto* expr = Or(3_u, 4_u);
+    WrapInFunction(expr);
 
-    Disassembler d;
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
     d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 | 4
 )");
 }
 
 TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Xor) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitExpression(Xor(3_u, 4_u));
-    ASSERT_TRUE(r);
+    auto* expr = Xor(3_u, 4_u);
+    WrapInFunction(expr);
 
-    Disassembler d;
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
     d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 ^ 4
 )");
 }
 
 TEST_F(IR_BuilderImplTest, EmitExpression_Binary_LogicalAnd) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitExpression(LogicalAnd(3_u, 4_u));
-    ASSERT_TRUE(r);
+    auto* expr = LogicalAnd(true, false);
+    WrapInFunction(expr);
 
-    Disassembler d;
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
     d.EmitBlockInstructions(b.current_flow_block);
-    EXPECT_EQ(d.AsString(), R"(%1 = 3 && 4
+    EXPECT_EQ(d.AsString(), R"(%1 = true && false
 )");
 }
 
 TEST_F(IR_BuilderImplTest, EmitExpression_Binary_LogicalOr) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitExpression(LogicalOr(3_u, 4_u));
-    ASSERT_TRUE(r);
+    auto* expr = LogicalOr(false, true);
+    WrapInFunction(expr);
 
-    Disassembler d;
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
     d.EmitBlockInstructions(b.current_flow_block);
-    EXPECT_EQ(d.AsString(), R"(%1 = 3 || 4
+    EXPECT_EQ(d.AsString(), R"(%1 = false || true
 )");
 }
 
-TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Eqaul) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitExpression(Equal(3_u, 4_u));
-    ASSERT_TRUE(r);
+TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Equal) {
+    auto* expr = Equal(3_u, 4_u);
+    WrapInFunction(expr);
 
-    Disassembler d;
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
     d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 == 4
 )");
 }
 
 TEST_F(IR_BuilderImplTest, EmitExpression_Binary_NotEqual) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitExpression(NotEqual(3_u, 4_u));
-    ASSERT_TRUE(r);
+    auto* expr = NotEqual(3_u, 4_u);
+    WrapInFunction(expr);
 
-    Disassembler d;
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
     d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 != 4
 )");
 }
 
 TEST_F(IR_BuilderImplTest, EmitExpression_Binary_LessThan) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitExpression(LessThan(3_u, 4_u));
-    ASSERT_TRUE(r);
+    auto* expr = LessThan(3_u, 4_u);
+    WrapInFunction(expr);
 
-    Disassembler d;
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
     d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 < 4
 )");
 }
 
 TEST_F(IR_BuilderImplTest, EmitExpression_Binary_GreaterThan) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitExpression(GreaterThan(3_u, 4_u));
-    ASSERT_TRUE(r);
+    auto* expr = GreaterThan(3_u, 4_u);
+    WrapInFunction(expr);
 
-    Disassembler d;
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
     d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 > 4
 )");
 }
 
 TEST_F(IR_BuilderImplTest, EmitExpression_Binary_LessThanEqual) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitExpression(LessThanEqual(3_u, 4_u));
-    ASSERT_TRUE(r);
+    auto* expr = LessThanEqual(3_u, 4_u);
+    WrapInFunction(expr);
 
-    Disassembler d;
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
     d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 <= 4
 )");
 }
 
 TEST_F(IR_BuilderImplTest, EmitExpression_Binary_GreaterThanEqual) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitExpression(GreaterThanEqual(3_u, 4_u));
-    ASSERT_TRUE(r);
+    auto* expr = GreaterThanEqual(3_u, 4_u);
+    WrapInFunction(expr);
 
-    Disassembler d;
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
     d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 >= 4
 )");
 }
 
 TEST_F(IR_BuilderImplTest, EmitExpression_Binary_ShiftLeft) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitExpression(Shl(3_u, 4_u));
-    ASSERT_TRUE(r);
+    auto* expr = Shl(3_u, 4_u);
+    WrapInFunction(expr);
 
-    Disassembler d;
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
     d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 << 4
 )");
 }
 
 TEST_F(IR_BuilderImplTest, EmitExpression_Binary_ShiftRight) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitExpression(Shr(3_u, 4_u));
-    ASSERT_TRUE(r);
+    auto* expr = Shr(3_u, 4_u);
+    WrapInFunction(expr);
 
-    Disassembler d;
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
     d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 >> 4
 )");
 }
 
 TEST_F(IR_BuilderImplTest, EmitExpression_Binary_Compound) {
-    auto& b = CreateEmptyBuilder();
-    auto r = b.EmitExpression(LogicalOr(  //
-        LessThan(1_u, Add(Shr(3_u, 4_u), 9_u)), GreaterThan(2.5_f, Div(6.7_f, Mul(2.3_f, 5.5_f)))));
-    ASSERT_TRUE(r);
+    auto* expr = LogicalOr(LessThan(1_u, Add(Shr(3_u, 4_u), 9_u)),
+                           GreaterThan(2.5_f, Div(6.7_f, Mul(2.3_f, 5.5_f))));
+    WrapInFunction(expr);
 
-    Disassembler d;
+    auto& b = CreateBuilder();
+    InjectFlowBlock();
+    auto r = b.EmitExpression(expr);
+    ASSERT_TRUE(r) << b.error();
+
+    Disassembler d(b.builder.ir);
     d.EmitBlockInstructions(b.current_flow_block);
     EXPECT_EQ(d.AsString(), R"(%1 = 3 >> 4
 %2 = %1 + 9
 %3 = 1 < %2
-%4 = 2.300000 * 5.500000
-%5 = 6.700000 / %4
-%6 = 2.500000 > %5
+%4 = 2.3 * 5.5
+%5 = 6.7 / %4
+%6 = 2.5 > %5
 %7 = %3 || %6
 )");
 }
diff --git a/src/tint/ir/constant.cc b/src/tint/ir/constant.cc
index 0138190..98a5d3d 100644
--- a/src/tint/ir/constant.cc
+++ b/src/tint/ir/constant.cc
@@ -16,40 +16,48 @@
 
 #include <string>
 
+#include "src/tint/constant/composite.h"
+#include "src/tint/constant/scalar.h"
+#include "src/tint/constant/splat.h"
+
 TINT_INSTANTIATE_TYPEINFO(tint::ir::Constant);
 
 namespace tint::ir {
 
-Constant::Constant(f32 f) : kind_(Kind::kF32), data_(f) {}
-
-Constant::Constant(f16 f) : kind_(Kind::kF16), data_(f) {}
-
-Constant::Constant(u32 u) : kind_(Kind::kU32), data_(u) {}
-
-Constant::Constant(i32 i) : kind_(Kind::kI32), data_(i) {}
-
-Constant::Constant(bool b) : kind_(Kind::kBool), data_(b) {}
+Constant::Constant(const constant::Value* val) : value(val) {}
 
 Constant::~Constant() = default;
 
-std::ostream& Constant::ToString(std::ostream& out) const {
-    switch (GetKind()) {
-        case Constant::Kind::kF32:
-            out << std::to_string(AsF32().value);
-            break;
-        case Constant::Kind::kF16:
-            out << std::to_string(AsF16().value);
-            break;
-        case Constant::Kind::kI32:
-            out << std::to_string(AsI32().value);
-            break;
-        case Constant::Kind::kU32:
-            out << std::to_string(AsU32().value);
-            break;
-        case Constant::Kind::kBool:
-            out << (AsBool() ? "true" : "false");
-            break;
-    }
+std::ostream& Constant::ToString(std::ostream& out, const SymbolTable& st) const {
+    std::function<void(const constant::Value*)> emit = [&](const constant::Value* c) {
+        Switch(
+            c,
+            [&](const constant::Scalar<AFloat>* scalar) { out << scalar->ValueAs<AFloat>().value; },
+            [&](const constant::Scalar<AInt>* scalar) { out << scalar->ValueAs<AInt>().value; },
+            [&](const constant::Scalar<i32>* scalar) { out << scalar->ValueAs<i32>().value; },
+            [&](const constant::Scalar<u32>* scalar) { out << scalar->ValueAs<u32>().value; },
+            [&](const constant::Scalar<f32>* scalar) { out << scalar->ValueAs<f32>().value; },
+            [&](const constant::Scalar<f16>* scalar) { out << scalar->ValueAs<f16>().value; },
+            [&](const constant::Scalar<bool>* scalar) {
+                out << (scalar->ValueAs<bool>() ? "true" : "false");
+            },
+            [&](const constant::Splat* splat) {
+                out << splat->Type()->FriendlyName(st) << "(";
+                emit(splat->Index(0));
+                out << ")";
+            },
+            [&](const constant::Composite* composite) {
+                out << composite->Type()->FriendlyName(st) << "(";
+                for (const auto* elem : composite->elements) {
+                    if (elem != composite->elements[0]) {
+                        out << ", ";
+                    }
+                    emit(elem);
+                }
+                out << ")";
+            });
+    };
+    emit(value);
     return out;
 }
 
diff --git a/src/tint/ir/constant.h b/src/tint/ir/constant.h
index 23eeb02..c5ee079 100644
--- a/src/tint/ir/constant.h
+++ b/src/tint/ir/constant.h
@@ -16,100 +16,29 @@
 #define SRC_TINT_IR_CONSTANT_H_
 
 #include <ostream>
-#include <variant>
 
+#include "src/tint/constant/value.h"
 #include "src/tint/ir/value.h"
-#include "src/tint/number.h"
+#include "src/tint/symbol_table.h"
 
 namespace tint::ir {
 
-/// Constant in the IR. The constant can be one of several types these include, but aren't limited
-/// to, `f32`, `u32`, `bool`. The type of the constant determines the type of data stored.
+/// Constant in the IR.
 class Constant : public Castable<Constant, Value> {
   public:
-    /// The type of the constant
-    enum class Kind {
-        /// A f32 constant
-        kF32,
-        /// A f16 constant
-        kF16,
-        /// An i32 constant
-        kI32,
-        /// A u32 constant
-        kU32,
-        /// A boolean constant
-        kBool,
-    };
-
     /// Constructor
-    /// @param b the `bool` constant to store in the constant
-    explicit Constant(bool b);
-
-    /// Constructor
-    /// @param f the `f32` constant to store in the constant
-    explicit Constant(f32 f);
-
-    /// Constructor
-    /// @param f the `f16` constant to store in the constant
-    explicit Constant(f16 f);
-
-    /// Constructor
-    /// @param u the `u32` constant to store in the constant
-    explicit Constant(u32 u);
-
-    /// Constructor
-    /// @param i the `i32` constant to store in the constant
-    explicit Constant(i32 i);
-
-    /// Destructor
+    /// @param val the value stored in the constant
+    explicit Constant(const constant::Value* val);
     ~Constant() override;
 
-    Constant(const Constant&) = delete;
-    Constant(Constant&&) = delete;
-
-    Constant& operator=(const Constant&) = delete;
-    Constant& operator=(Constant&&) = delete;
-
-    /// @returns true if this is a f32 constant
-    bool IsF32() const { return kind_ == Kind::kF32; }
-    /// @returns true if this is a f16 constant
-    bool IsF16() const { return kind_ == Kind::kF16; }
-    /// @returns true if this is an i32 constant
-    bool IsI32() const { return kind_ == Kind::kI32; }
-    /// @returns true if this is a u32 constant
-    bool IsU32() const { return kind_ == Kind::kU32; }
-    /// @returns true if this is a bool constant
-    bool IsBool() const { return kind_ == Kind::kBool; }
-
-    /// @returns the kind of constant
-    Kind GetKind() const { return kind_; }
-
-    /// @returns the constant data as a `f32`.
-    /// @note, must only be called if `IsF32()` is true
-    f32 AsF32() const { return std::get<f32>(data_); }
-    /// @returns the constant data as a `f16`.
-    /// @note, must only be called if `IsF16()` is true
-    f16 AsF16() const { return std::get<f16>(data_); }
-    /// @returns the constant data as an `i32`.
-    /// @note, must only be called if `IsI32()` is true
-    i32 AsI32() const { return std::get<i32>(data_); }
-    /// @returns the constant data as a `u32`.
-    /// @note, must only be called if `IsU32()` is true
-    u32 AsU32() const { return std::get<u32>(data_); }
-    /// @returns the constant data as a `bool`.
-    /// @note, must only be called if `IsBool()` is true
-    bool AsBool() const { return std::get<bool>(data_); }
-
     /// Write the constant to the given stream
     /// @param out the stream to write to
+    /// @param st the symbol table
     /// @returns the stream
-    std::ostream& ToString(std::ostream& out) const override;
+    std::ostream& ToString(std::ostream& out, const SymbolTable& st) const override;
 
-  private:
-    /// The type of data stored in this constant
-    Kind kind_;
-    /// The data stored in the constant
-    std::variant<f32, f16, u32, i32, bool> data_;
+    /// The constants value
+    const constant::Value* const value;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/constant_test.cc b/src/tint/ir/constant_test.cc
index b20b424..87eec65 100644
--- a/src/tint/ir/constant_test.cc
+++ b/src/tint/ir/constant_test.cc
@@ -29,17 +29,17 @@
 
     std::stringstream str;
 
-    auto* val = b.builder.Constant(1.2_f);
-    EXPECT_EQ(1.2_f, val->AsF32());
+    auto* c = b.builder.Constant(1.2_f);
+    EXPECT_EQ(1.2_f, c->value->As<constant::Scalar<f32>>()->ValueAs<f32>());
 
-    val->ToString(str);
-    EXPECT_EQ("1.200000", str.str());
+    c->ToString(str, program->Symbols());
+    EXPECT_EQ("1.2", str.str());
 
-    EXPECT_TRUE(val->IsF32());
-    EXPECT_FALSE(val->IsF16());
-    EXPECT_FALSE(val->IsI32());
-    EXPECT_FALSE(val->IsU32());
-    EXPECT_FALSE(val->IsBool());
+    EXPECT_TRUE(c->value->Is<constant::Scalar<f32>>());
+    EXPECT_FALSE(c->value->Is<constant::Scalar<f16>>());
+    EXPECT_FALSE(c->value->Is<constant::Scalar<i32>>());
+    EXPECT_FALSE(c->value->Is<constant::Scalar<u32>>());
+    EXPECT_FALSE(c->value->Is<constant::Scalar<bool>>());
 }
 
 TEST_F(IR_ConstantTest, f16) {
@@ -47,17 +47,17 @@
 
     std::stringstream str;
 
-    auto* val = b.builder.Constant(1.1_h);
-    EXPECT_EQ(1.1_h, val->AsF16());
+    auto* c = b.builder.Constant(1.1_h);
+    EXPECT_EQ(1.1_h, c->value->As<constant::Scalar<f16>>()->ValueAs<f16>());
 
-    val->ToString(str);
-    EXPECT_EQ("1.099609", str.str());
+    c->ToString(str, program->Symbols());
+    EXPECT_EQ("1.09961", str.str());
 
-    EXPECT_FALSE(val->IsF32());
-    EXPECT_TRUE(val->IsF16());
-    EXPECT_FALSE(val->IsI32());
-    EXPECT_FALSE(val->IsU32());
-    EXPECT_FALSE(val->IsBool());
+    EXPECT_FALSE(c->value->Is<constant::Scalar<f32>>());
+    EXPECT_TRUE(c->value->Is<constant::Scalar<f16>>());
+    EXPECT_FALSE(c->value->Is<constant::Scalar<i32>>());
+    EXPECT_FALSE(c->value->Is<constant::Scalar<u32>>());
+    EXPECT_FALSE(c->value->Is<constant::Scalar<bool>>());
 }
 
 TEST_F(IR_ConstantTest, i32) {
@@ -65,17 +65,17 @@
 
     std::stringstream str;
 
-    auto* val = b.builder.Constant(1_i);
-    EXPECT_EQ(1_i, val->AsI32());
+    auto* c = b.builder.Constant(1_i);
+    EXPECT_EQ(1_i, c->value->As<constant::Scalar<i32>>()->ValueAs<i32>());
 
-    val->ToString(str);
+    c->ToString(str, program->Symbols());
     EXPECT_EQ("1", str.str());
 
-    EXPECT_FALSE(val->IsF32());
-    EXPECT_FALSE(val->IsF16());
-    EXPECT_TRUE(val->IsI32());
-    EXPECT_FALSE(val->IsU32());
-    EXPECT_FALSE(val->IsBool());
+    EXPECT_FALSE(c->value->Is<constant::Scalar<f32>>());
+    EXPECT_FALSE(c->value->Is<constant::Scalar<f16>>());
+    EXPECT_TRUE(c->value->Is<constant::Scalar<i32>>());
+    EXPECT_FALSE(c->value->Is<constant::Scalar<u32>>());
+    EXPECT_FALSE(c->value->Is<constant::Scalar<bool>>());
 }
 
 TEST_F(IR_ConstantTest, u32) {
@@ -83,17 +83,17 @@
 
     std::stringstream str;
 
-    auto* val = b.builder.Constant(2_u);
-    EXPECT_EQ(2_u, val->AsU32());
+    auto* c = b.builder.Constant(2_u);
+    EXPECT_EQ(2_u, c->value->As<constant::Scalar<u32>>()->ValueAs<u32>());
 
-    val->ToString(str);
+    c->ToString(str, program->Symbols());
     EXPECT_EQ("2", str.str());
 
-    EXPECT_FALSE(val->IsF32());
-    EXPECT_FALSE(val->IsF16());
-    EXPECT_FALSE(val->IsI32());
-    EXPECT_TRUE(val->IsU32());
-    EXPECT_FALSE(val->IsBool());
+    EXPECT_FALSE(c->value->Is<constant::Scalar<f32>>());
+    EXPECT_FALSE(c->value->Is<constant::Scalar<f16>>());
+    EXPECT_FALSE(c->value->Is<constant::Scalar<i32>>());
+    EXPECT_TRUE(c->value->Is<constant::Scalar<u32>>());
+    EXPECT_FALSE(c->value->Is<constant::Scalar<bool>>());
 }
 
 TEST_F(IR_ConstantTest, bool) {
@@ -101,24 +101,24 @@
 
     std::stringstream str;
 
-    auto* val = b.builder.Constant(false);
-    EXPECT_FALSE(val->AsBool());
+    auto* c = b.builder.Constant(false);
+    EXPECT_FALSE(c->value->As<constant::Scalar<bool>>()->ValueAs<bool>());
 
-    val->ToString(str);
+    c->ToString(str, program->Symbols());
     EXPECT_EQ("false", str.str());
 
     str.str("");
-    val = b.builder.Constant(true);
-    EXPECT_TRUE(val->AsBool());
+    c = b.builder.Constant(true);
+    EXPECT_TRUE(c->value->As<constant::Scalar<bool>>()->ValueAs<bool>());
 
-    val->ToString(str);
+    c->ToString(str, program->Symbols());
     EXPECT_EQ("true", str.str());
 
-    EXPECT_FALSE(val->IsF32());
-    EXPECT_FALSE(val->IsF16());
-    EXPECT_FALSE(val->IsI32());
-    EXPECT_FALSE(val->IsU32());
-    EXPECT_TRUE(val->IsBool());
+    EXPECT_FALSE(c->value->Is<constant::Scalar<f32>>());
+    EXPECT_FALSE(c->value->Is<constant::Scalar<f16>>());
+    EXPECT_FALSE(c->value->Is<constant::Scalar<i32>>());
+    EXPECT_FALSE(c->value->Is<constant::Scalar<u32>>());
+    EXPECT_TRUE(c->value->Is<constant::Scalar<bool>>());
 }
 
 }  // namespace
diff --git a/src/tint/ir/disassembler.cc b/src/tint/ir/disassembler.cc
index d8ee4ba..37f294c 100644
--- a/src/tint/ir/disassembler.cc
+++ b/src/tint/ir/disassembler.cc
@@ -50,7 +50,7 @@
 
 }  // namespace
 
-Disassembler::Disassembler() = default;
+Disassembler::Disassembler(const Module& mod) : mod_(mod) {}
 
 Disassembler::~Disassembler() = default;
 
@@ -63,7 +63,7 @@
 
 void Disassembler::EmitBlockInstructions(const Block* b) {
     for (const auto* instr : b->instructions) {
-        instr->ToString(out_) << std::endl;
+        instr->ToString(out_, mod_.program->Symbols()) << std::endl;
     }
 }
 
@@ -144,8 +144,8 @@
         [&](const ir::Terminator*) { Indent() << "Function end" << std::endl; });
 }
 
-std::string Disassembler::Disassemble(const Module& mod) {
-    for (const auto* func : mod.functions) {
+std::string Disassembler::Disassemble() {
+    for (const auto* func : mod_.functions) {
         Walk(func);
     }
     return out_.str();
diff --git a/src/tint/ir/disassembler.h b/src/tint/ir/disassembler.h
index e24524a..46b4ffd 100644
--- a/src/tint/ir/disassembler.h
+++ b/src/tint/ir/disassembler.h
@@ -28,13 +28,13 @@
 class Disassembler {
   public:
     /// Constructor
-    Disassembler();
+    /// @param mod the module
+    explicit Disassembler(const Module& mod);
     ~Disassembler();
 
     /// Returns the module as a string
-    /// @param mod the module to emit
     /// @returns the string representation of the module
-    std::string Disassemble(const Module& mod);
+    std::string Disassemble();
 
     /// Writes the block instructions to the stream
     /// @param b the block containing the instructions
@@ -47,6 +47,7 @@
     std::ostream& Indent();
     void Walk(const FlowNode* node);
 
+    const Module& mod_;
     std::stringstream out_;
     std::unordered_set<const FlowNode*> visited_;
     std::unordered_set<const FlowNode*> stop_nodes_;
diff --git a/src/tint/ir/instruction.h b/src/tint/ir/instruction.h
index 8b68465..82842ba 100644
--- a/src/tint/ir/instruction.h
+++ b/src/tint/ir/instruction.h
@@ -18,6 +18,7 @@
 #include <ostream>
 
 #include "src/tint/castable.h"
+#include "src/tint/symbol_table.h"
 
 namespace tint::ir {
 
@@ -34,8 +35,9 @@
 
     /// Write the instruction to the given stream
     /// @param out the stream to write to
+    /// @param st the symbol table
     /// @returns the stream
-    virtual std::ostream& ToString(std::ostream& out) const = 0;
+    virtual std::ostream& ToString(std::ostream& out, const SymbolTable& st) const = 0;
 
   protected:
     /// Constructor
diff --git a/src/tint/ir/instruction_test.cc b/src/tint/ir/instruction_test.cc
index 08302ed..c9307f0 100644
--- a/src/tint/ir/instruction_test.cc
+++ b/src/tint/ir/instruction_test.cc
@@ -44,7 +44,7 @@
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 & 2");
 }
 
@@ -70,7 +70,7 @@
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 | 2");
 }
 
@@ -96,7 +96,7 @@
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 ^ 2");
 }
 
@@ -123,7 +123,7 @@
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 && 2");
 }
 
@@ -149,7 +149,7 @@
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 || 2");
 }
 
@@ -175,7 +175,7 @@
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 == 2");
 }
 
@@ -201,7 +201,7 @@
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 != 2");
 }
 
@@ -227,7 +227,7 @@
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 < 2");
 }
 
@@ -254,7 +254,7 @@
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 > 2");
 }
 
@@ -281,7 +281,7 @@
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 <= 2");
 }
 
@@ -308,7 +308,7 @@
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 >= 2");
 }
 
@@ -334,7 +334,7 @@
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 << 2");
 }
 
@@ -361,7 +361,7 @@
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 >> 2");
 }
 
@@ -387,7 +387,7 @@
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 + 2");
 }
 
@@ -413,7 +413,7 @@
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 - 2");
 }
 
@@ -439,7 +439,7 @@
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 * 2");
 }
 
@@ -465,7 +465,7 @@
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 / 2");
 }
 
@@ -491,7 +491,7 @@
     EXPECT_EQ(i32(2), rhs->AsI32());
 
     std::stringstream str;
-    instr->ToString(str);
+    instr->ToString(str, program->Symbols());
     EXPECT_EQ(str.str(), "%42 = 4 % 2");
 }
 
diff --git a/src/tint/ir/module.h b/src/tint/ir/module.h
index d95a13e..1ed9ea9 100644
--- a/src/tint/ir/module.h
+++ b/src/tint/ir/module.h
@@ -17,9 +17,11 @@
 
 #include <string>
 
+#include "src/tint/constant/value.h"
 #include "src/tint/ir/function.h"
 #include "src/tint/ir/instruction.h"
 #include "src/tint/ir/value.h"
+#include "src/tint/type/manager.h"
 #include "src/tint/utils/block_allocator.h"
 #include "src/tint/utils/result.h"
 #include "src/tint/utils/vector.h"
@@ -68,6 +70,8 @@
 
     /// The flow node allocator
     utils::BlockAllocator<FlowNode> flow_nodes;
+    /// The constant allocator
+    utils::BlockAllocator<constant::Value> constants;
     /// The value allocator
     utils::BlockAllocator<Value> values;
     /// The instruction allocator
@@ -80,6 +84,9 @@
 
     /// The source ast::Program this module was constucted from
     const Program* program;
+
+    /// The type manager for the module
+    type::Manager types;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/temp.cc b/src/tint/ir/temp.cc
index 30efb86..afd0921 100644
--- a/src/tint/ir/temp.cc
+++ b/src/tint/ir/temp.cc
@@ -24,7 +24,7 @@
 
 Temp::~Temp() = default;
 
-std::ostream& Temp::ToString(std::ostream& out) const {
+std::ostream& Temp::ToString(std::ostream& out, const SymbolTable&) const {
     out << "%" << std::to_string(AsId());
     return out;
 }
diff --git a/src/tint/ir/temp.h b/src/tint/ir/temp.h
index c621b5c..2db81f3 100644
--- a/src/tint/ir/temp.h
+++ b/src/tint/ir/temp.h
@@ -18,6 +18,7 @@
 #include <ostream>
 
 #include "src/tint/ir/value.h"
+#include "src/tint/symbol_table.h"
 
 namespace tint::ir {
 
@@ -45,8 +46,9 @@
 
     /// Write the temp to the given stream
     /// @param out the stream to write to
+    /// @param st the symbol table
     /// @returns the stream
-    std::ostream& ToString(std::ostream& out) const override;
+    std::ostream& ToString(std::ostream& out, const SymbolTable& st) const override;
 
   private:
     Id id_ = 0;
diff --git a/src/tint/ir/temp_test.cc b/src/tint/ir/temp_test.cc
index 1dcae13..6b79a51 100644
--- a/src/tint/ir/temp_test.cc
+++ b/src/tint/ir/temp_test.cc
@@ -33,7 +33,7 @@
     auto* val = b.builder.Temp();
     EXPECT_EQ(4u, val->AsId());
 
-    val->ToString(str);
+    val->ToString(str, program->Symbols());
     EXPECT_EQ("%4", str.str());
 }
 
diff --git a/src/tint/ir/test_helper.h b/src/tint/ir/test_helper.h
index 10694ee..e3d66cf 100644
--- a/src/tint/ir/test_helper.h
+++ b/src/tint/ir/test_helper.h
@@ -21,6 +21,7 @@
 #include "gtest/gtest.h"
 #include "src/tint/ir/builder_impl.h"
 #include "src/tint/ir/disassembler.h"
+#include "src/tint/number.h"
 #include "src/tint/program_builder.h"
 
 namespace tint::ir {
@@ -41,19 +42,29 @@
         if (gen_) {
             return *gen_;
         }
-        program = std::make_unique<Program>(std::move(*this));
         diag::Formatter formatter;
+
+        program = std::make_unique<Program>(std::move(*this));
         [&]() { ASSERT_TRUE(program->IsValid()) << formatter.format(program->Diagnostics()); }();
         gen_ = std::make_unique<BuilderImpl>(program.get());
         return *gen_;
     }
 
+    /// Injects a flow block into the builder
+    /// @returns the injected block
+    ir::Block* InjectFlowBlock() {
+        auto* block = gen_->builder.CreateBlock();
+        gen_->current_flow_block = block;
+        return block;
+    }
+
     /// Creates a BuilderImpl without an originating program. This is used for testing the
     /// expressions which don't require the full builder implementation. The current flow block
     /// is initialized with an empty block.
     /// @returns the BuilderImpl for testing.
     BuilderImpl& CreateEmptyBuilder() {
-        gen_ = std::make_unique<BuilderImpl>(nullptr);
+        program = std::make_unique<Program>();
+        gen_ = std::make_unique<BuilderImpl>(program.get());
         gen_->current_flow_block = gen_->builder.CreateBlock();
         return *gen_;
     }
diff --git a/src/tint/ir/value.h b/src/tint/ir/value.h
index 9c41f52..6dffc3e 100644
--- a/src/tint/ir/value.h
+++ b/src/tint/ir/value.h
@@ -18,6 +18,7 @@
 #include <ostream>
 
 #include "src/tint/castable.h"
+#include "src/tint/symbol_table.h"
 
 namespace tint::ir {
 
@@ -35,8 +36,9 @@
 
     /// Write the value to the given stream
     /// @param out the stream to write to
+    /// @param st the symbol table
     /// @returns the stream
-    virtual std::ostream& ToString(std::ostream& out) const = 0;
+    virtual std::ostream& ToString(std::ostream& out, const SymbolTable& st) const = 0;
 
   protected:
     /// Constructor