[ir] Split Value into Temp and Constant.

This CL pulls the Temp and Constant classes out of the Value base class.

Bug: tint:1718
Change-Id: Ib7bccc7d3190ddd1c5cf493704e778dd23b5c008
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/112044
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 06aa483..15532b2 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -656,6 +656,8 @@
     ir/builder.h
     ir/builder_impl.cc
     ir/builder_impl.h
+    ir/constant.cc
+    ir/constant.h
     ir/debug.cc
     ir/debug.h
     ir/disassembler.cc
@@ -674,6 +676,8 @@
     ir/module.h
     ir/switch.cc
     ir/switch.h
+    ir/temp.cc
+    ir/temp.h
     ir/terminator.cc
     ir/terminator.h
     ir/value.cc
@@ -1341,9 +1345,10 @@
   if (${TINT_BUILD_IR})
     list(APPEND TINT_TEST_SRCS
       ir/builder_impl_test.cc
+      ir/constant_test.cc
       ir/instruction_test.cc
+      ir/temp_test.cc
       ir/test_helper.h
-      ir/value_test.cc
     )
   endif()
 
diff --git a/src/tint/ir/builder.cc b/src/tint/ir/builder.cc
index 2b5dea3..6f5e9c6 100644
--- a/src/tint/ir/builder.cc
+++ b/src/tint/ir/builder.cc
@@ -93,12 +93,12 @@
     to->inbound_branches.Push(from);
 }
 
-Value::Id Builder::AllocateValue() {
-    return next_value_id++;
+Temp::Id Builder::AllocateTempId() {
+    return next_temp_id++;
 }
 
 Instruction Builder::CreateInstruction(Instruction::Kind kind, const Value* lhs, const Value* rhs) {
-    return Instruction(kind, MkValue(AllocateValue()), lhs, rhs);
+    return Instruction(kind, Temp(), lhs, rhs);
 }
 
 Instruction Builder::And(const Value* lhs, const Value* rhs) {
diff --git a/src/tint/ir/builder.h b/src/tint/ir/builder.h
index c380980..bc30a95 100644
--- a/src/tint/ir/builder.h
+++ b/src/tint/ir/builder.h
@@ -15,12 +15,14 @@
 #ifndef SRC_TINT_IR_BUILDER_H_
 #define SRC_TINT_IR_BUILDER_H_
 
+#include "src/tint/ir/constant.h"
 #include "src/tint/ir/function.h"
 #include "src/tint/ir/if.h"
 #include "src/tint/ir/instruction.h"
 #include "src/tint/ir/loop.h"
 #include "src/tint/ir/module.h"
 #include "src/tint/ir/switch.h"
+#include "src/tint/ir/temp.h"
 #include "src/tint/ir/terminator.h"
 #include "src/tint/ir/value.h"
 
@@ -83,14 +85,18 @@
     /// @param to the node to branch too
     void Branch(Block* from, FlowNode* to);
 
-    /// Creates a new Value
-    /// @param val the value
-    /// @returns the new Value
+    /// Creates a new Constant
+    /// @param val the constant value
+    /// @returns the new constant
     template <typename T>
-    const Value* MkValue(T val) {
-        return ir.values.Create<Value>(val);
+    const ir::Constant* Constant(T val) {
+        return ir.values.Create<ir::Constant>(val);
     }
 
+    /// Creates a new Temporary
+    /// @returns the new temporary
+    const ir::Temp* Temp() { return ir.values.Create<ir::Temp>(AllocateTempId()); }
+
     /// Creates an op for `lhs kind rhs`
     /// @param kind the kind of operation
     /// @param lhs the left-hand-side of the operation
@@ -206,14 +212,14 @@
     /// @returns the operation
     Instruction Modulo(const Value* lhs, const Value* rhs);
 
-    /// @returns a unique Value id
-    Value::Id AllocateValue();
+    /// @returns a unique temp id
+    Temp::Id AllocateTempId();
 
     /// The IR module.
     Module ir;
 
-    /// The next Value number to allocate
-    Value::Id next_value_id = 1;
+    /// The next temporary number to allocate
+    Temp::Id next_temp_id = 1;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/builder_impl.cc b/src/tint/ir/builder_impl.cc
index 36bcad8..7700512 100644
--- a/src/tint/ir/builder_impl.cc
+++ b/src/tint/ir/builder_impl.cc
@@ -632,20 +632,20 @@
     return tint::Switch(  //
         lit,
         [&](const ast::BoolLiteralExpression* l) {
-            return utils::Result<const Value*>(builder.MkValue(l->value));
+            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.MkValue(f32(static_cast<float>(l->value))));
+                    builder.Constant(f32(static_cast<float>(l->value))));
             }
-            return utils::Result<const Value*>(builder.MkValue(f16(static_cast<float>(l->value))));
+            return utils::Result<const Value*>(builder.Constant(f16(static_cast<float>(l->value))));
         },
         [&](const ast::IntLiteralExpression* l) {
             if (l->suffix == ast::IntLiteralExpression::Suffix::kI) {
-                return utils::Result<const Value*>(builder.MkValue(i32(l->value)));
+                return utils::Result<const Value*>(builder.Constant(i32(l->value)));
             }
-            return utils::Result<const Value*>(builder.MkValue(u32(l->value)));
+            return utils::Result<const Value*>(builder.Constant(u32(l->value)));
         },
         [&](Default) {
             diagnostics_.add_warning(tint::diag::System::IR,
diff --git a/src/tint/ir/builder_impl_test.cc b/src/tint/ir/builder_impl_test.cc
index edfe7be..79e6e3f 100644
--- a/src/tint/ir/builder_impl_test.cc
+++ b/src/tint/ir/builder_impl_test.cc
@@ -101,7 +101,8 @@
     EXPECT_EQ(flow->merge_target->branch_target, func->end_target);
 
     // Check condition
-    auto* instr = flow->condition;
+    ASSERT_TRUE(flow->condition->Is<Constant>());
+    auto* instr = flow->condition->As<Constant>();
     ASSERT_TRUE(instr->IsBool());
     EXPECT_TRUE(instr->AsBool());
 }
@@ -502,7 +503,8 @@
     EXPECT_EQ(loop_flow->merge_target->branch_target, nullptr);
 
     // Check condition
-    auto* instr = if_flow->condition;
+    ASSERT_TRUE(if_flow->condition->Is<Constant>());
+    auto* instr = if_flow->condition->As<Constant>();
     ASSERT_TRUE(instr->IsBool());
     EXPECT_TRUE(instr->AsBool());
 }
@@ -947,7 +949,8 @@
     EXPECT_EQ(flow->merge_target->branch_target, func->end_target);
 
     // Check condition
-    auto* instr = if_flow->condition;
+    ASSERT_TRUE(if_flow->condition->Is<Constant>());
+    auto* instr = if_flow->condition->As<Constant>();
     ASSERT_TRUE(instr->IsBool());
     EXPECT_FALSE(instr->AsBool());
 }
@@ -1071,7 +1074,8 @@
     EXPECT_EQ(flow->merge_target->branch_target, func->end_target);
 
     // Check condition
-    auto* instr = if_flow->condition;
+    ASSERT_TRUE(if_flow->condition->Is<Constant>());
+    auto* instr = if_flow->condition->As<Constant>();
     ASSERT_TRUE(instr->IsBool());
     EXPECT_FALSE(instr->AsBool());
 }
@@ -1171,7 +1175,8 @@
     EXPECT_EQ(flow->merge_target->branch_target, func->end_target);
 
     // Check condition
-    auto* instr = flow->condition;
+    ASSERT_TRUE(flow->condition->Is<Constant>());
+    auto* instr = flow->condition->As<Constant>();
     ASSERT_TRUE(instr->IsI32());
     EXPECT_EQ(1_i, instr->AsI32());
 }
@@ -1341,7 +1346,8 @@
     auto r = b.EmitLiteral(Expr(true));
     ASSERT_TRUE(r);
 
-    auto* val = r.Get();
+    ASSERT_TRUE(r.Get()->Is<Constant>());
+    auto* val = r.Get()->As<Constant>();
     EXPECT_TRUE(val->IsBool());
     EXPECT_TRUE(val->AsBool());
 }
@@ -1351,7 +1357,8 @@
     auto r = b.EmitLiteral(Expr(false));
     ASSERT_TRUE(r);
 
-    auto val = r.Get();
+    ASSERT_TRUE(r.Get()->Is<Constant>());
+    auto* val = r.Get()->As<Constant>();
     EXPECT_TRUE(val->IsBool());
     EXPECT_FALSE(val->AsBool());
 }
@@ -1361,7 +1368,8 @@
     auto r = b.EmitLiteral(Expr(1.2_f));
     ASSERT_TRUE(r);
 
-    auto val = r.Get();
+    ASSERT_TRUE(r.Get()->Is<Constant>());
+    auto* val = r.Get()->As<Constant>();
     EXPECT_TRUE(val->IsF32());
     EXPECT_EQ(1.2_f, val->AsF32());
 }
@@ -1371,7 +1379,8 @@
     auto r = b.EmitLiteral(Expr(1.2_h));
     ASSERT_TRUE(r);
 
-    auto val = r.Get();
+    ASSERT_TRUE(r.Get()->Is<Constant>());
+    auto* val = r.Get()->As<Constant>();
     EXPECT_TRUE(val->IsF16());
     EXPECT_EQ(1.2_h, val->AsF16());
 }
@@ -1381,7 +1390,8 @@
     auto r = b.EmitLiteral(Expr(-2_i));
     ASSERT_TRUE(r);
 
-    auto val = r.Get();
+    ASSERT_TRUE(r.Get()->Is<Constant>());
+    auto* val = r.Get()->As<Constant>();
     EXPECT_TRUE(val->IsI32());
     EXPECT_EQ(-2_i, val->AsI32());
 }
@@ -1391,7 +1401,8 @@
     auto r = b.EmitLiteral(Expr(2_u));
     ASSERT_TRUE(r);
 
-    auto val = r.Get();
+    ASSERT_TRUE(r.Get()->Is<Constant>());
+    auto* val = r.Get()->As<Constant>();
     EXPECT_TRUE(val->IsU32());
     EXPECT_EQ(2_u, val->AsU32());
 }
diff --git a/src/tint/ir/constant.cc b/src/tint/ir/constant.cc
new file mode 100644
index 0000000..78895bf
--- /dev/null
+++ b/src/tint/ir/constant.cc
@@ -0,0 +1,56 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ir/constant.h"
+
+#include <string>
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::Constant);
+
+namespace tint::ir {
+
+Constant::Constant(f32 f) : kind_(Kind::kF32), data_(f) {}
+
+Constant::Constant(f16 f) : kind_(Kind::kF16), data_(f) {}
+
+Constant::Constant(u32 u) : kind_(Kind::kU32), data_(u) {}
+
+Constant::Constant(i32 i) : kind_(Kind::kI32), data_(i) {}
+
+Constant::Constant(bool b) : kind_(Kind::kBool), data_(b) {}
+
+Constant::~Constant() = default;
+
+std::ostream& operator<<(std::ostream& out, const Constant& r) {
+    switch (r.GetKind()) {
+        case Constant::Kind::kF32:
+            out << std::to_string(r.AsF32().value);
+            break;
+        case Constant::Kind::kF16:
+            out << std::to_string(r.AsF16().value);
+            break;
+        case Constant::Kind::kI32:
+            out << std::to_string(r.AsI32().value);
+            break;
+        case Constant::Kind::kU32:
+            out << std::to_string(r.AsU32().value);
+            break;
+        case Constant::Kind::kBool:
+            out << (r.AsBool() ? "true" : "false");
+            break;
+    }
+    return out;
+}
+
+}  // namespace tint::ir
diff --git a/src/tint/ir/constant.h b/src/tint/ir/constant.h
new file mode 100644
index 0000000..c06e121
--- /dev/null
+++ b/src/tint/ir/constant.h
@@ -0,0 +1,114 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_IR_CONSTANT_H_
+#define SRC_TINT_IR_CONSTANT_H_
+
+#include <ostream>
+#include <variant>
+
+#include "src/tint/ir/value.h"
+#include "src/tint/number.h"
+
+namespace tint::ir {
+
+/// Constant in the IR. The constant can be one of several types these include, but aren't limited
+/// to, `f32`, `u32`, `bool`. The type of the constant determines the type of data stored.
+class Constant : public Castable<Constant, Value> {
+  public:
+    /// The type of the constant
+    enum class Kind {
+        /// A f32 constant
+        kF32,
+        /// A f16 constant
+        kF16,
+        /// An i32 constant
+        kI32,
+        /// A u32 constant
+        kU32,
+        /// A boolean constant
+        kBool,
+    };
+
+    /// Constructor
+    /// @param b the `bool` constant to store in the constant
+    explicit Constant(bool b);
+
+    /// Constructor
+    /// @param f the `f32` constant to store in the constant
+    explicit Constant(f32 f);
+
+    /// Constructor
+    /// @param f the `f16` constant to store in the constant
+    explicit Constant(f16 f);
+
+    /// Constructor
+    /// @param u the `u32` constant to store in the constant
+    explicit Constant(u32 u);
+
+    /// Constructor
+    /// @param i the `i32` constant to store in the constant
+    explicit Constant(i32 i);
+
+    /// Destructor
+    ~Constant() override;
+
+    Constant(const Constant&) = delete;
+    Constant(Constant&&) = delete;
+
+    Constant& operator=(const Constant&) = delete;
+    Constant& operator=(Constant&&) = delete;
+
+    /// @returns true if this is a f32 constant
+    bool IsF32() const { return kind_ == Kind::kF32; }
+    /// @returns true if this is a f16 constant
+    bool IsF16() const { return kind_ == Kind::kF16; }
+    /// @returns true if this is an i32 constant
+    bool IsI32() const { return kind_ == Kind::kI32; }
+    /// @returns true if this is a u32 constant
+    bool IsU32() const { return kind_ == Kind::kU32; }
+    /// @returns true if this is a bool constant
+    bool IsBool() const { return kind_ == Kind::kBool; }
+
+    /// @returns the kind of constant
+    Kind GetKind() const { return kind_; }
+
+    /// @returns the constant data as a `f32`.
+    /// @note, must only be called if `IsF32()` is true
+    f32 AsF32() const { return std::get<f32>(data_); }
+    /// @returns the constant data as a `f16`.
+    /// @note, must only be called if `IsF16()` is true
+    f16 AsF16() const { return std::get<f16>(data_); }
+    /// @returns the constant data as an `i32`.
+    /// @note, must only be called if `IsI32()` is true
+    i32 AsI32() const { return std::get<i32>(data_); }
+    /// @returns the constant data as a `u32`.
+    /// @note, must only be called if `IsU32()` is true
+    u32 AsU32() const { return std::get<u32>(data_); }
+    /// @returns the constant data as a `bool`.
+    /// @note, must only be called if `IsBool()` is true
+    bool AsBool() const { return std::get<bool>(data_); }
+
+  private:
+    /// The type of data stored in this constant
+    Kind kind_;
+    /// The data stored in the constant
+    std::variant<f32, f16, u32, i32, bool> data_;
+};
+
+std::ostream& operator<<(std::ostream& out, const Constant& r);
+
+}  // namespace tint::ir
+
+#endif  // SRC_TINT_IR_CONSTANT_H_
diff --git a/src/tint/ir/constant_test.cc b/src/tint/ir/constant_test.cc
new file mode 100644
index 0000000..7b4e224
--- /dev/null
+++ b/src/tint/ir/constant_test.cc
@@ -0,0 +1,125 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <sstream>
+
+#include "src/tint/ir/test_helper.h"
+#include "src/tint/ir/value.h"
+
+namespace tint::ir {
+namespace {
+
+using namespace tint::number_suffixes;  // NOLINT
+
+using IR_ConstantTest = TestHelper;
+
+TEST_F(IR_ConstantTest, f32) {
+    auto& b = CreateEmptyBuilder();
+
+    std::stringstream str;
+
+    auto* val = b.builder.Constant(1.2_f);
+    EXPECT_EQ(1.2_f, val->AsF32());
+
+    str << *val;
+    EXPECT_EQ("1.200000", str.str());
+
+    EXPECT_TRUE(val->IsF32());
+    EXPECT_FALSE(val->IsF16());
+    EXPECT_FALSE(val->IsI32());
+    EXPECT_FALSE(val->IsU32());
+    EXPECT_FALSE(val->IsBool());
+}
+
+TEST_F(IR_ConstantTest, f16) {
+    auto& b = CreateEmptyBuilder();
+
+    std::stringstream str;
+
+    auto* val = b.builder.Constant(1.1_h);
+    EXPECT_EQ(1.1_h, val->AsF16());
+
+    str << *val;
+    EXPECT_EQ("1.099609", str.str());
+
+    EXPECT_FALSE(val->IsF32());
+    EXPECT_TRUE(val->IsF16());
+    EXPECT_FALSE(val->IsI32());
+    EXPECT_FALSE(val->IsU32());
+    EXPECT_FALSE(val->IsBool());
+}
+
+TEST_F(IR_ConstantTest, i32) {
+    auto& b = CreateEmptyBuilder();
+
+    std::stringstream str;
+
+    auto* val = b.builder.Constant(1_i);
+    EXPECT_EQ(1_i, val->AsI32());
+
+    str << *val;
+    EXPECT_EQ("1", str.str());
+
+    EXPECT_FALSE(val->IsF32());
+    EXPECT_FALSE(val->IsF16());
+    EXPECT_TRUE(val->IsI32());
+    EXPECT_FALSE(val->IsU32());
+    EXPECT_FALSE(val->IsBool());
+}
+
+TEST_F(IR_ConstantTest, u32) {
+    auto& b = CreateEmptyBuilder();
+
+    std::stringstream str;
+
+    auto* val = b.builder.Constant(2_u);
+    EXPECT_EQ(2_u, val->AsU32());
+
+    str << *val;
+    EXPECT_EQ("2", str.str());
+
+    EXPECT_FALSE(val->IsF32());
+    EXPECT_FALSE(val->IsF16());
+    EXPECT_FALSE(val->IsI32());
+    EXPECT_TRUE(val->IsU32());
+    EXPECT_FALSE(val->IsBool());
+}
+
+TEST_F(IR_ConstantTest, bool) {
+    auto& b = CreateEmptyBuilder();
+
+    std::stringstream str;
+
+    auto* val = b.builder.Constant(false);
+    EXPECT_FALSE(val->AsBool());
+
+    str << *val;
+    EXPECT_EQ("false", str.str());
+
+    str.str("");
+    val = b.builder.Constant(true);
+    EXPECT_TRUE(val->AsBool());
+
+    str << *val;
+    EXPECT_EQ("true", str.str());
+
+    EXPECT_FALSE(val->IsF32());
+    EXPECT_FALSE(val->IsF16());
+    EXPECT_FALSE(val->IsI32());
+    EXPECT_FALSE(val->IsU32());
+    EXPECT_TRUE(val->IsBool());
+}
+
+}  // namespace
+}  // namespace tint::ir
diff --git a/src/tint/ir/instruction.h b/src/tint/ir/instruction.h
index ea9dec2..1735115 100644
--- a/src/tint/ir/instruction.h
+++ b/src/tint/ir/instruction.h
@@ -17,6 +17,7 @@
 
 #include <ostream>
 
+#include "src/tint/debug.h"
 #include "src/tint/ir/value.h"
 #include "src/tint/utils/vector.h"
 
diff --git a/src/tint/ir/instruction_test.cc b/src/tint/ir/instruction_test.cc
index d93b9c7..d6c8d8a 100644
--- a/src/tint/ir/instruction_test.cc
+++ b/src/tint/ir/instruction_test.cc
@@ -25,21 +25,23 @@
 TEST_F(IR_InstructionTest, CreateAnd) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.And(b.builder.MkValue(i32(4)), b.builder.MkValue(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    auto instr = b.builder.And(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
     EXPECT_EQ(instr.GetKind(), Instruction::Kind::kAnd);
 
-    ASSERT_TRUE(instr.Result()->IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result()->AsId());
+    ASSERT_TRUE(instr.Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr.Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr.HasLHS());
-    auto lhs = instr.LHS();
+    ASSERT_TRUE(instr.LHS()->Is<Constant>());
+    auto lhs = instr.LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
     ASSERT_TRUE(instr.HasRHS());
-    auto rhs = instr.RHS();
+    ASSERT_TRUE(instr.RHS()->Is<Constant>());
+    auto rhs = instr.RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
@@ -51,21 +53,23 @@
 TEST_F(IR_InstructionTest, CreateOr) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.Or(b.builder.MkValue(i32(4)), b.builder.MkValue(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    auto instr = b.builder.Or(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
     EXPECT_EQ(instr.GetKind(), Instruction::Kind::kOr);
 
-    ASSERT_TRUE(instr.Result()->IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result()->AsId());
+    ASSERT_TRUE(instr.Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr.Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr.HasLHS());
-    auto lhs = instr.LHS();
+    ASSERT_TRUE(instr.LHS()->Is<Constant>());
+    auto lhs = instr.LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
     ASSERT_TRUE(instr.HasRHS());
-    auto rhs = instr.RHS();
+    ASSERT_TRUE(instr.RHS()->Is<Constant>());
+    auto rhs = instr.RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
@@ -77,21 +81,23 @@
 TEST_F(IR_InstructionTest, CreateXor) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.Xor(b.builder.MkValue(i32(4)), b.builder.MkValue(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    auto instr = b.builder.Xor(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
     EXPECT_EQ(instr.GetKind(), Instruction::Kind::kXor);
 
-    ASSERT_TRUE(instr.Result()->IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result()->AsId());
+    ASSERT_TRUE(instr.Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr.Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr.HasLHS());
-    auto lhs = instr.LHS();
+    ASSERT_TRUE(instr.LHS()->Is<Constant>());
+    auto lhs = instr.LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
     ASSERT_TRUE(instr.HasRHS());
-    auto rhs = instr.RHS();
+    ASSERT_TRUE(instr.RHS()->Is<Constant>());
+    auto rhs = instr.RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
@@ -103,21 +109,23 @@
 TEST_F(IR_InstructionTest, CreateLogicalAnd) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.LogicalAnd(b.builder.MkValue(i32(4)), b.builder.MkValue(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    auto instr = b.builder.LogicalAnd(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
     EXPECT_EQ(instr.GetKind(), Instruction::Kind::kLogicalAnd);
 
-    ASSERT_TRUE(instr.Result()->IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result()->AsId());
+    ASSERT_TRUE(instr.Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr.Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr.HasLHS());
-    auto lhs = instr.LHS();
+    ASSERT_TRUE(instr.LHS()->Is<Constant>());
+    auto lhs = instr.LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
     ASSERT_TRUE(instr.HasRHS());
-    auto rhs = instr.RHS();
+    ASSERT_TRUE(instr.RHS()->Is<Constant>());
+    auto rhs = instr.RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
@@ -129,21 +137,23 @@
 TEST_F(IR_InstructionTest, CreateLogicalOr) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.LogicalOr(b.builder.MkValue(i32(4)), b.builder.MkValue(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    auto instr = b.builder.LogicalOr(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
     EXPECT_EQ(instr.GetKind(), Instruction::Kind::kLogicalOr);
 
-    ASSERT_TRUE(instr.Result()->IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result()->AsId());
+    ASSERT_TRUE(instr.Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr.Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr.HasLHS());
-    auto lhs = instr.LHS();
+    ASSERT_TRUE(instr.LHS()->Is<Constant>());
+    auto lhs = instr.LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
     ASSERT_TRUE(instr.HasRHS());
-    auto rhs = instr.RHS();
+    ASSERT_TRUE(instr.RHS()->Is<Constant>());
+    auto rhs = instr.RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
@@ -155,21 +165,23 @@
 TEST_F(IR_InstructionTest, CreateEqual) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.Equal(b.builder.MkValue(i32(4)), b.builder.MkValue(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    auto instr = b.builder.Equal(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
     EXPECT_EQ(instr.GetKind(), Instruction::Kind::kEqual);
 
-    ASSERT_TRUE(instr.Result()->IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result()->AsId());
+    ASSERT_TRUE(instr.Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr.Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr.HasLHS());
-    auto lhs = instr.LHS();
+    ASSERT_TRUE(instr.LHS()->Is<Constant>());
+    auto lhs = instr.LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
     ASSERT_TRUE(instr.HasRHS());
-    auto rhs = instr.RHS();
+    ASSERT_TRUE(instr.RHS()->Is<Constant>());
+    auto rhs = instr.RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
@@ -181,21 +193,23 @@
 TEST_F(IR_InstructionTest, CreateNotEqual) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.NotEqual(b.builder.MkValue(i32(4)), b.builder.MkValue(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    auto instr = b.builder.NotEqual(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
     EXPECT_EQ(instr.GetKind(), Instruction::Kind::kNotEqual);
 
-    ASSERT_TRUE(instr.Result()->IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result()->AsId());
+    ASSERT_TRUE(instr.Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr.Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr.HasLHS());
-    auto lhs = instr.LHS();
+    ASSERT_TRUE(instr.LHS()->Is<Constant>());
+    auto lhs = instr.LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
     ASSERT_TRUE(instr.HasRHS());
-    auto rhs = instr.RHS();
+    ASSERT_TRUE(instr.RHS()->Is<Constant>());
+    auto rhs = instr.RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
@@ -207,21 +221,23 @@
 TEST_F(IR_InstructionTest, CreateLessThan) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.LessThan(b.builder.MkValue(i32(4)), b.builder.MkValue(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    auto instr = b.builder.LessThan(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
     EXPECT_EQ(instr.GetKind(), Instruction::Kind::kLessThan);
 
-    ASSERT_TRUE(instr.Result()->IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result()->AsId());
+    ASSERT_TRUE(instr.Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr.Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr.HasLHS());
-    auto lhs = instr.LHS();
+    ASSERT_TRUE(instr.LHS()->Is<Constant>());
+    auto lhs = instr.LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
     ASSERT_TRUE(instr.HasRHS());
-    auto rhs = instr.RHS();
+    ASSERT_TRUE(instr.RHS()->Is<Constant>());
+    auto rhs = instr.RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
@@ -233,21 +249,23 @@
 TEST_F(IR_InstructionTest, CreateGreaterThan) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.GreaterThan(b.builder.MkValue(i32(4)), b.builder.MkValue(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    auto instr = b.builder.GreaterThan(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
     EXPECT_EQ(instr.GetKind(), Instruction::Kind::kGreaterThan);
 
-    ASSERT_TRUE(instr.Result()->IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result()->AsId());
+    ASSERT_TRUE(instr.Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr.Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr.HasLHS());
-    auto lhs = instr.LHS();
+    ASSERT_TRUE(instr.LHS()->Is<Constant>());
+    auto lhs = instr.LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
     ASSERT_TRUE(instr.HasRHS());
-    auto rhs = instr.RHS();
+    ASSERT_TRUE(instr.RHS()->Is<Constant>());
+    auto rhs = instr.RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
@@ -259,21 +277,23 @@
 TEST_F(IR_InstructionTest, CreateLessThanEqual) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.LessThanEqual(b.builder.MkValue(i32(4)), b.builder.MkValue(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    auto instr = b.builder.LessThanEqual(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
     EXPECT_EQ(instr.GetKind(), Instruction::Kind::kLessThanEqual);
 
-    ASSERT_TRUE(instr.Result()->IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result()->AsId());
+    ASSERT_TRUE(instr.Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr.Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr.HasLHS());
-    auto lhs = instr.LHS();
+    ASSERT_TRUE(instr.LHS()->Is<Constant>());
+    auto lhs = instr.LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
     ASSERT_TRUE(instr.HasRHS());
-    auto rhs = instr.RHS();
+    ASSERT_TRUE(instr.RHS()->Is<Constant>());
+    auto rhs = instr.RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
@@ -285,21 +305,23 @@
 TEST_F(IR_InstructionTest, CreateGreaterThanEqual) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.GreaterThanEqual(b.builder.MkValue(i32(4)), b.builder.MkValue(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    auto instr = b.builder.GreaterThanEqual(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
     EXPECT_EQ(instr.GetKind(), Instruction::Kind::kGreaterThanEqual);
 
-    ASSERT_TRUE(instr.Result()->IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result()->AsId());
+    ASSERT_TRUE(instr.Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr.Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr.HasLHS());
-    auto lhs = instr.LHS();
+    ASSERT_TRUE(instr.LHS()->Is<Constant>());
+    auto lhs = instr.LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
     ASSERT_TRUE(instr.HasRHS());
-    auto rhs = instr.RHS();
+    ASSERT_TRUE(instr.RHS()->Is<Constant>());
+    auto rhs = instr.RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
@@ -311,21 +333,23 @@
 TEST_F(IR_InstructionTest, CreateShiftLeft) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.ShiftLeft(b.builder.MkValue(i32(4)), b.builder.MkValue(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    auto instr = b.builder.ShiftLeft(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
     EXPECT_EQ(instr.GetKind(), Instruction::Kind::kShiftLeft);
 
-    ASSERT_TRUE(instr.Result()->IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result()->AsId());
+    ASSERT_TRUE(instr.Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr.Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr.HasLHS());
-    auto lhs = instr.LHS();
+    ASSERT_TRUE(instr.LHS()->Is<Constant>());
+    auto lhs = instr.LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
     ASSERT_TRUE(instr.HasRHS());
-    auto rhs = instr.RHS();
+    ASSERT_TRUE(instr.RHS()->Is<Constant>());
+    auto rhs = instr.RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
@@ -337,21 +361,23 @@
 TEST_F(IR_InstructionTest, CreateShiftRight) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.ShiftRight(b.builder.MkValue(i32(4)), b.builder.MkValue(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    auto instr = b.builder.ShiftRight(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
     EXPECT_EQ(instr.GetKind(), Instruction::Kind::kShiftRight);
 
-    ASSERT_TRUE(instr.Result()->IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result()->AsId());
+    ASSERT_TRUE(instr.Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr.Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr.HasLHS());
-    auto lhs = instr.LHS();
+    ASSERT_TRUE(instr.LHS()->Is<Constant>());
+    auto lhs = instr.LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
     ASSERT_TRUE(instr.HasRHS());
-    auto rhs = instr.RHS();
+    ASSERT_TRUE(instr.RHS()->Is<Constant>());
+    auto rhs = instr.RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
@@ -363,21 +389,23 @@
 TEST_F(IR_InstructionTest, CreateAdd) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.Add(b.builder.MkValue(i32(4)), b.builder.MkValue(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    auto instr = b.builder.Add(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
     EXPECT_EQ(instr.GetKind(), Instruction::Kind::kAdd);
 
-    ASSERT_TRUE(instr.Result()->IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result()->AsId());
+    ASSERT_TRUE(instr.Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr.Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr.HasLHS());
-    auto lhs = instr.LHS();
+    ASSERT_TRUE(instr.LHS()->Is<Constant>());
+    auto lhs = instr.LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
     ASSERT_TRUE(instr.HasRHS());
-    auto rhs = instr.RHS();
+    ASSERT_TRUE(instr.RHS()->Is<Constant>());
+    auto rhs = instr.RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
@@ -389,21 +417,23 @@
 TEST_F(IR_InstructionTest, CreateSubtract) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.Subtract(b.builder.MkValue(i32(4)), b.builder.MkValue(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    auto instr = b.builder.Subtract(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
     EXPECT_EQ(instr.GetKind(), Instruction::Kind::kSubtract);
 
-    ASSERT_TRUE(instr.Result()->IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result()->AsId());
+    ASSERT_TRUE(instr.Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr.Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr.HasLHS());
-    auto lhs = instr.LHS();
+    ASSERT_TRUE(instr.LHS()->Is<Constant>());
+    auto lhs = instr.LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
     ASSERT_TRUE(instr.HasRHS());
-    auto rhs = instr.RHS();
+    ASSERT_TRUE(instr.RHS()->Is<Constant>());
+    auto rhs = instr.RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
@@ -415,21 +445,23 @@
 TEST_F(IR_InstructionTest, CreateMultiply) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.Multiply(b.builder.MkValue(i32(4)), b.builder.MkValue(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    auto instr = b.builder.Multiply(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
     EXPECT_EQ(instr.GetKind(), Instruction::Kind::kMultiply);
 
-    ASSERT_TRUE(instr.Result()->IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result()->AsId());
+    ASSERT_TRUE(instr.Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr.Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr.HasLHS());
-    auto lhs = instr.LHS();
+    ASSERT_TRUE(instr.LHS()->Is<Constant>());
+    auto lhs = instr.LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
     ASSERT_TRUE(instr.HasRHS());
-    auto rhs = instr.RHS();
+    ASSERT_TRUE(instr.RHS()->Is<Constant>());
+    auto rhs = instr.RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
@@ -441,21 +473,23 @@
 TEST_F(IR_InstructionTest, CreateDivide) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.Divide(b.builder.MkValue(i32(4)), b.builder.MkValue(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    auto instr = b.builder.Divide(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
     EXPECT_EQ(instr.GetKind(), Instruction::Kind::kDivide);
 
-    ASSERT_TRUE(instr.Result()->IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result()->AsId());
+    ASSERT_TRUE(instr.Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr.Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr.HasLHS());
-    auto lhs = instr.LHS();
+    ASSERT_TRUE(instr.LHS()->Is<Constant>());
+    auto lhs = instr.LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
     ASSERT_TRUE(instr.HasRHS());
-    auto rhs = instr.RHS();
+    ASSERT_TRUE(instr.RHS()->Is<Constant>());
+    auto rhs = instr.RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
@@ -467,21 +501,23 @@
 TEST_F(IR_InstructionTest, CreateModulo) {
     auto& b = CreateEmptyBuilder();
 
-    b.builder.next_value_id = Value::Id(42);
-    auto instr = b.builder.Modulo(b.builder.MkValue(i32(4)), b.builder.MkValue(i32(2)));
+    b.builder.next_temp_id = Temp::Id(42);
+    auto instr = b.builder.Modulo(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
 
     EXPECT_EQ(instr.GetKind(), Instruction::Kind::kModulo);
 
-    ASSERT_TRUE(instr.Result()->IsTemp());
-    EXPECT_EQ(Value::Id(42), instr.Result()->AsId());
+    ASSERT_TRUE(instr.Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr.Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr.HasLHS());
-    auto lhs = instr.LHS();
+    ASSERT_TRUE(instr.LHS()->Is<Constant>());
+    auto lhs = instr.LHS()->As<Constant>();
     ASSERT_TRUE(lhs->IsI32());
     EXPECT_EQ(i32(4), lhs->AsI32());
 
     ASSERT_TRUE(instr.HasRHS());
-    auto rhs = instr.RHS();
+    ASSERT_TRUE(instr.RHS()->Is<Constant>());
+    auto rhs = instr.RHS()->As<Constant>();
     ASSERT_TRUE(rhs->IsI32());
     EXPECT_EQ(i32(2), rhs->AsI32());
 
diff --git a/src/tint/ir/temp.cc b/src/tint/ir/temp.cc
new file mode 100644
index 0000000..de227ee
--- /dev/null
+++ b/src/tint/ir/temp.cc
@@ -0,0 +1,32 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ir/temp.h"
+
+#include <string>
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::Temp);
+
+namespace tint::ir {
+
+Temp::Temp(Id id) : id_(id) {}
+
+Temp::~Temp() = default;
+
+std::ostream& operator<<(std::ostream& out, const Temp& r) {
+    out << "%" << std::to_string(r.AsId());
+    return out;
+}
+
+}  // namespace tint::ir
diff --git a/src/tint/ir/temp.h b/src/tint/ir/temp.h
new file mode 100644
index 0000000..b16baf2
--- /dev/null
+++ b/src/tint/ir/temp.h
@@ -0,0 +1,54 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_IR_TEMP_H_
+#define SRC_TINT_IR_TEMP_H_
+
+#include <ostream>
+
+#include "src/tint/ir/value.h"
+
+namespace tint::ir {
+
+/// Temporary value in the IR.
+class Temp : public Castable<Temp, Value> {
+  public:
+    /// A value id.
+    using Id = uint32_t;
+
+    /// Constructor
+    /// @param id the id for the value
+    explicit Temp(Id id);
+
+    /// Destructor
+    ~Temp() override;
+
+    Temp(const Temp&) = delete;
+    Temp(Temp&&) = delete;
+
+    Temp& operator=(const Temp&) = delete;
+    Temp& operator=(Temp&&) = delete;
+
+    /// @returns the value data as an `Id`.
+    Id AsId() const { return id_; }
+
+  private:
+    Id id_ = 0;
+};
+
+std::ostream& operator<<(std::ostream& out, const Temp& r);
+
+}  // namespace tint::ir
+
+#endif  // SRC_TINT_IR_TEMP_H_
diff --git a/src/tint/ir/temp_test.cc b/src/tint/ir/temp_test.cc
new file mode 100644
index 0000000..b5d817b
--- /dev/null
+++ b/src/tint/ir/temp_test.cc
@@ -0,0 +1,41 @@
+// Copyright 2022 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <sstream>
+
+#include "src/tint/ir/temp.h"
+#include "src/tint/ir/test_helper.h"
+
+namespace tint::ir {
+namespace {
+
+using namespace tint::number_suffixes;  // NOLINT
+
+using IR_TempTest = TestHelper;
+
+TEST_F(IR_TempTest, id) {
+    auto& b = CreateEmptyBuilder();
+
+    std::stringstream str;
+
+    b.builder.next_temp_id = Temp::Id(4);
+    auto* val = b.builder.Temp();
+    EXPECT_EQ(4u, val->AsId());
+
+    str << *val;
+    EXPECT_EQ("%4", str.str());
+}
+
+}  // namespace
+}  // namespace tint::ir
diff --git a/src/tint/ir/value.cc b/src/tint/ir/value.cc
index 4177f63..a1431e3 100644
--- a/src/tint/ir/value.cc
+++ b/src/tint/ir/value.cc
@@ -14,50 +14,26 @@
 
 #include "src/tint/ir/value.h"
 
+#include "src/tint/ir/constant.h"
+#include "src/tint/ir/temp.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::Value);
+
 namespace tint::ir {
 
-Value::Value(Id id) : kind_(Kind::kTemp), data_(id) {}
-
-Value::Value(f32 f) : kind_(Kind::kF32), data_(f) {}
-
-Value::Value(f16 f) : kind_(Kind::kF16), data_(f) {}
-
-Value::Value(u32 u) : kind_(Kind::kU32), data_(u) {}
-
-Value::Value(i32 i) : kind_(Kind::kI32), data_(i) {}
-
-Value::Value(bool b) : kind_(Kind::kBool), data_(b) {}
+Value::Value() = default;
 
 Value::~Value() = default;
 
-Value::Value(const Value& o) = default;
+std::ostream& operator<<(std::ostream& out, const Value& v) {
+    const auto* ptr = &v;
 
-Value::Value(Value&& o) = default;
-
-Value& Value::operator=(const Value& o) = default;
-
-Value& Value::operator=(Value&& o) = default;
-
-std::ostream& operator<<(std::ostream& out, const Value& r) {
-    switch (r.GetKind()) {
-        case Value::Kind::kTemp:
-            out << "%" << std::to_string(r.AsId());
-            break;
-        case Value::Kind::kF32:
-            out << std::to_string(r.AsF32().value);
-            break;
-        case Value::Kind::kF16:
-            out << std::to_string(r.AsF16().value);
-            break;
-        case Value::Kind::kI32:
-            out << std::to_string(r.AsI32().value);
-            break;
-        case Value::Kind::kU32:
-            out << std::to_string(r.AsU32().value);
-            break;
-        case Value::Kind::kBool:
-            out << (r.AsBool() ? "true" : "false");
-            break;
+    if (auto* c = ptr->As<Constant>()) {
+        out << *c;
+    } else if (auto* t = ptr->As<Temp>()) {
+        out << *t;
+    } else {
+        out << "Unknown value";
     }
     return out;
 }
diff --git a/src/tint/ir/value.h b/src/tint/ir/value.h
index bbe8974..326931f 100644
--- a/src/tint/ir/value.h
+++ b/src/tint/ir/value.h
@@ -16,122 +16,29 @@
 #define SRC_TINT_IR_VALUE_H_
 
 #include <ostream>
-#include <variant>
 
-#include "src/tint/number.h"
+#include "src/tint/castable.h"
 
 namespace tint::ir {
 
-/// Value in the IR. The value can be one of several types these include, but aren't limited
-/// to, `f32`, `u32`, `temp`, `var`. The type of the value determines the type of data stored
-/// in the value.
-class Value {
+/// Value in the IR.
+class Value : public Castable<Value> {
   public:
-    /// A value id.
-    using Id = uint32_t;
-
-    /// The type of the value
-    enum class Kind {
-        /// A temporary allocated value
-        kTemp,
-        /// A f32 value
-        kF32,
-        /// A f16 value
-        kF16,
-        /// An i32 value
-        kI32,
-        /// A u32 value
-        kU32,
-        /// A boolean value
-        kBool,
-    };
-
-    /// Constructor
-    /// @param id the id for the value
-    explicit Value(Id id);
-
-    /// Constructor
-    /// @param b the `bool` value to store in the value
-    explicit Value(bool b);
-
-    /// Constructor
-    /// @param f the `f32` value to store in the value
-    explicit Value(f32 f);
-
-    /// Constructor
-    /// @param f the `f16` value to store in the value
-    explicit Value(f16 f);
-
-    /// Constructor
-    /// @param u the `u32` value to store in the value
-    explicit Value(u32 u);
-
-    /// Constructor
-    /// @param i the `i32` value to store in the value
-    explicit Value(i32 i);
-
     /// Destructor
-    ~Value();
+    virtual ~Value();
 
-    /// Copy constructor
-    /// @param o the value to copy from
-    Value(const Value& o);
-    /// Move constructor
-    /// @param o the value to move from
-    Value(Value&& o);
+    Value(const Value&) = delete;
+    Value(Value&&) = delete;
 
-    /// Copy assign
-    /// @param o the value to copy from
-    /// @returns this
-    Value& operator=(const Value& o);
-    /// Move assign
-    /// @param o the value to move from
-    /// @returns this
-    Value& operator=(Value&& o);
+    Value& operator=(const Value&) = delete;
+    Value& operator=(Value&&) = delete;
 
-    /// @returns true if this is a temporary value
-    bool IsTemp() const { return kind_ == Kind::kTemp; }
-    /// @returns true if this is a f32 value
-    bool IsF32() const { return kind_ == Kind::kF32; }
-    /// @returns true if this is a f16 value
-    bool IsF16() const { return kind_ == Kind::kF16; }
-    /// @returns true if this is an i32 value
-    bool IsI32() const { return kind_ == Kind::kI32; }
-    /// @returns true if this is a u32 value
-    bool IsU32() const { return kind_ == Kind::kU32; }
-    /// @returns true if this is a bool value
-    bool IsBool() const { return kind_ == Kind::kBool; }
-
-    /// @returns the kind of value
-    Kind GetKind() const { return kind_; }
-
-    /// @returns the value data as a `f32`.
-    /// @note, must only be called if `IsF32()` is true
-    f32 AsF32() const { return std::get<f32>(data_); }
-    /// @returns the value data as a `f16`.
-    /// @note, must only be called if `IsF16()` is true
-    f16 AsF16() const { return std::get<f16>(data_); }
-    /// @returns the value data as an `i32`.
-    /// @note, must only be called if `IsI32()` is true
-    i32 AsI32() const { return std::get<i32>(data_); }
-    /// @returns the value data as a `u32`.
-    /// @note, must only be called if `IsU32()` is true
-    u32 AsU32() const { return std::get<u32>(data_); }
-    /// @returns the value data as an `Id`.
-    /// @note, must only be called if `IsTemp()` is true
-    Id AsId() const { return std::get<Id>(data_); }
-    /// @returns the value data as a `bool`.
-    /// @note, must only be called if `IsBool()` is true
-    bool AsBool() const { return std::get<bool>(data_); }
-
-  private:
-    /// The type of data stored in this value
-    Kind kind_;
-    /// The data stored in the value
-    std::variant<Id, f32, f16, u32, i32, bool> data_;
+  protected:
+    /// Constructor
+    Value();
 };
 
-std::ostream& operator<<(std::ostream& out, const Value& r);
+std::ostream& operator<<(std::ostream& out, const Value& v);
 
 }  // namespace tint::ir
 
diff --git a/src/tint/ir/value_test.cc b/src/tint/ir/value_test.cc
deleted file mode 100644
index 4ffe66e..0000000
--- a/src/tint/ir/value_test.cc
+++ /dev/null
@@ -1,137 +0,0 @@
-// Copyright 2022 The Tint Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <sstream>
-
-#include "src/tint/ir/test_helper.h"
-#include "src/tint/ir/value.h"
-
-namespace tint::ir {
-namespace {
-
-using namespace tint::number_suffixes;  // NOLINT
-
-using IR_ValueTest = TestHelper;
-
-TEST_F(IR_ValueTest, f32) {
-    std::stringstream str;
-
-    Value val(1.2_f);
-    EXPECT_EQ(1.2_f, val.AsF32());
-
-    str << val;
-    EXPECT_EQ("1.200000", str.str());
-
-    EXPECT_TRUE(val.IsF32());
-    EXPECT_FALSE(val.IsF16());
-    EXPECT_FALSE(val.IsI32());
-    EXPECT_FALSE(val.IsU32());
-    EXPECT_FALSE(val.IsTemp());
-    EXPECT_FALSE(val.IsBool());
-}
-
-TEST_F(IR_ValueTest, f16) {
-    std::stringstream str;
-
-    Value val(1.1_h);
-    EXPECT_EQ(1.1_h, val.AsF16());
-
-    str << val;
-    EXPECT_EQ("1.099609", str.str());
-
-    EXPECT_FALSE(val.IsF32());
-    EXPECT_TRUE(val.IsF16());
-    EXPECT_FALSE(val.IsI32());
-    EXPECT_FALSE(val.IsU32());
-    EXPECT_FALSE(val.IsTemp());
-    EXPECT_FALSE(val.IsBool());
-}
-
-TEST_F(IR_ValueTest, i32) {
-    std::stringstream str;
-
-    Value val(1_i);
-    EXPECT_EQ(1_i, val.AsI32());
-
-    str << val;
-    EXPECT_EQ("1", str.str());
-
-    EXPECT_FALSE(val.IsF32());
-    EXPECT_FALSE(val.IsF16());
-    EXPECT_TRUE(val.IsI32());
-    EXPECT_FALSE(val.IsU32());
-    EXPECT_FALSE(val.IsTemp());
-    EXPECT_FALSE(val.IsBool());
-}
-
-TEST_F(IR_ValueTest, u32) {
-    std::stringstream str;
-
-    Value val(2_u);
-    EXPECT_EQ(2_u, val.AsU32());
-
-    str << val;
-    EXPECT_EQ("2", str.str());
-
-    EXPECT_FALSE(val.IsF32());
-    EXPECT_FALSE(val.IsF16());
-    EXPECT_FALSE(val.IsI32());
-    EXPECT_TRUE(val.IsU32());
-    EXPECT_FALSE(val.IsTemp());
-    EXPECT_FALSE(val.IsBool());
-}
-
-TEST_F(IR_ValueTest, id) {
-    std::stringstream str;
-
-    Value val(Value::Id(4));
-    EXPECT_EQ(4u, val.AsId());
-
-    str << val;
-    EXPECT_EQ("%4", str.str());
-
-    EXPECT_FALSE(val.IsF32());
-    EXPECT_FALSE(val.IsF16());
-    EXPECT_FALSE(val.IsI32());
-    EXPECT_FALSE(val.IsU32());
-    EXPECT_TRUE(val.IsTemp());
-    EXPECT_FALSE(val.IsBool());
-}
-
-TEST_F(IR_ValueTest, bool) {
-    std::stringstream str;
-
-    Value val(false);
-    EXPECT_FALSE(val.AsBool());
-
-    str << val;
-    EXPECT_EQ("false", str.str());
-
-    str.str("");
-    val = Value(true);
-    EXPECT_TRUE(val.AsBool());
-
-    str << val;
-    EXPECT_EQ("true", str.str());
-
-    EXPECT_FALSE(val.IsF32());
-    EXPECT_FALSE(val.IsF16());
-    EXPECT_FALSE(val.IsI32());
-    EXPECT_FALSE(val.IsU32());
-    EXPECT_FALSE(val.IsTemp());
-    EXPECT_TRUE(val.IsBool());
-}
-
-}  // namespace
-}  // namespace tint::ir