Import Tint changes from Dawn

Changes:
  - afd7f2aa21719374eee8a8201b2e4357b49c046d [ir] Add usage tracking to ir::Value. by dan sinclair <dsinclair@chromium.org>
  - 4cef4362b0654b52e3c1a6ee5a3133456cf0107d [ir] Add bitcast expression. by dan sinclair <dsinclair@chromium.org>
  - cf58122c58bbaa37322e60d685c2d7cb862a4f1d Minor cleanups from #114202. by dan sinclair <dsinclair@chromium.org>
  - 3e449f219449b5d7f12ed9f12a6757aee4fdb8be [ir] Add types to the ir::Value classes. by dan sinclair <dsinclair@chromium.org>
GitOrigin-RevId: afd7f2aa21719374eee8a8201b2e4357b49c046d
Change-Id: I9b85c98331f8ecd60586053e5ba456dc0822b2cb
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/116200
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index e158941..7988364 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -678,6 +678,8 @@
   list(APPEND TINT_LIB_SRCS
     ir/binary.cc
     ir/binary.h
+    ir/bitcast.cc
+    ir/bitcast.h
     ir/block.cc
     ir/block.h
     ir/builder.cc
@@ -1378,6 +1380,7 @@
   if (${TINT_BUILD_IR})
     list(APPEND TINT_TEST_SRCS
       ir/binary_test.cc
+      ir/bitcast_test.cc
       ir/builder_impl_test.cc
       ir/constant_test.cc
       ir/temp_test.cc
diff --git a/src/tint/ir/binary.cc b/src/tint/ir/binary.cc
index ccb679b..e48ca86 100644
--- a/src/tint/ir/binary.cc
+++ b/src/tint/ir/binary.cc
@@ -19,11 +19,12 @@
 
 namespace tint::ir {
 
-Binary::Binary(Kind kind, const Value* result, const Value* lhs, const Value* rhs)
-    : kind_(kind), result_(result), lhs_(lhs), rhs_(rhs) {
-    TINT_ASSERT(IR, result_);
+Binary::Binary(Kind kind, Value* result, Value* lhs, Value* rhs)
+    : Base(result), kind_(kind), lhs_(lhs), rhs_(rhs) {
     TINT_ASSERT(IR, lhs_);
     TINT_ASSERT(IR, rhs_);
+    lhs_->AddUsage(this);
+    rhs_->AddUsage(this);
 }
 
 Binary::~Binary() = default;
diff --git a/src/tint/ir/binary.h b/src/tint/ir/binary.h
index 0f7fe57..85ad9dc 100644
--- a/src/tint/ir/binary.h
+++ b/src/tint/ir/binary.h
@@ -19,8 +19,8 @@
 
 #include "src/tint/castable.h"
 #include "src/tint/ir/instruction.h"
-#include "src/tint/ir/value.h"
 #include "src/tint/symbol_table.h"
+#include "src/tint/type/type.h"
 
 namespace tint::ir {
 
@@ -58,7 +58,7 @@
     /// @param result the result value
     /// @param lhs the lhs of the instruction
     /// @param rhs the rhs of the instruction
-    Binary(Kind kind, const Value* result, const Value* lhs, const Value* rhs);
+    Binary(Kind kind, Value* result, Value* lhs, Value* rhs);
     Binary(const Binary& instr) = delete;
     Binary(Binary&& instr) = delete;
     ~Binary() override;
@@ -69,9 +69,6 @@
     /// @returns the kind of instruction
     Kind GetKind() const { return kind_; }
 
-    /// @returns the result value for the instruction
-    const Value* Result() const { return result_; }
-
     /// @returns the left-hand-side value for the instruction
     const Value* LHS() const { return lhs_; }
 
@@ -86,9 +83,8 @@
 
   private:
     Kind kind_;
-    const Value* result_ = nullptr;
-    const Value* lhs_ = nullptr;
-    const Value* rhs_ = nullptr;
+    Value* lhs_ = nullptr;
+    Value* rhs_ = nullptr;
 };
 
 std::ostream& operator<<(std::ostream& out, const Binary&);
diff --git a/src/tint/ir/binary_test.cc b/src/tint/ir/binary_test.cc
index f90661b..f234879 100644
--- a/src/tint/ir/binary_test.cc
+++ b/src/tint/ir/binary_test.cc
@@ -28,11 +28,13 @@
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.And(b.builder.Constant(4_i), b.builder.Constant(2_i));
+    const auto* instr = b.builder.And(b.builder.ir.types.Get<type::I32>(), b.builder.Constant(4_i),
+                                      b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kAnd);
 
     ASSERT_TRUE(instr->Result()->Is<Temp>());
+    ASSERT_NE(instr->Result()->Type(), nullptr);
     EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
 
     ASSERT_TRUE(instr->LHS()->Is<Constant>());
@@ -47,14 +49,15 @@
 
     std::stringstream str;
     instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 & 2");
+    EXPECT_EQ(str.str(), "%42 (i32) = 4 & 2");
 }
 
 TEST_F(IR_InstructionTest, CreateOr) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.Or(b.builder.Constant(4_i), b.builder.Constant(2_i));
+    const auto* instr = b.builder.Or(b.builder.ir.types.Get<type::I32>(), b.builder.Constant(4_i),
+                                     b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kOr);
 
@@ -73,14 +76,15 @@
 
     std::stringstream str;
     instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 | 2");
+    EXPECT_EQ(str.str(), "%42 (i32) = 4 | 2");
 }
 
 TEST_F(IR_InstructionTest, CreateXor) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.Xor(b.builder.Constant(4_i), b.builder.Constant(2_i));
+    const auto* instr = b.builder.Xor(b.builder.ir.types.Get<type::I32>(), b.builder.Constant(4_i),
+                                      b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kXor);
 
@@ -99,14 +103,15 @@
 
     std::stringstream str;
     instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 ^ 2");
+    EXPECT_EQ(str.str(), "%42 (i32) = 4 ^ 2");
 }
 
 TEST_F(IR_InstructionTest, CreateLogicalAnd) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.LogicalAnd(b.builder.Constant(4_i), b.builder.Constant(2_i));
+    const auto* instr = b.builder.LogicalAnd(b.builder.ir.types.Get<type::Bool>(),
+                                             b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kLogicalAnd);
 
@@ -125,14 +130,15 @@
 
     std::stringstream str;
     instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 && 2");
+    EXPECT_EQ(str.str(), "%42 (bool) = 4 && 2");
 }
 
 TEST_F(IR_InstructionTest, CreateLogicalOr) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.LogicalOr(b.builder.Constant(4_i), b.builder.Constant(2_i));
+    const auto* instr = b.builder.LogicalOr(b.builder.ir.types.Get<type::Bool>(),
+                                            b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kLogicalOr);
 
@@ -151,14 +157,15 @@
 
     std::stringstream str;
     instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 || 2");
+    EXPECT_EQ(str.str(), "%42 (bool) = 4 || 2");
 }
 
 TEST_F(IR_InstructionTest, CreateEqual) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.Equal(b.builder.Constant(4_i), b.builder.Constant(2_i));
+    const auto* instr = b.builder.Equal(b.builder.ir.types.Get<type::Bool>(),
+                                        b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kEqual);
 
@@ -177,14 +184,15 @@
 
     std::stringstream str;
     instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 == 2");
+    EXPECT_EQ(str.str(), "%42 (bool) = 4 == 2");
 }
 
 TEST_F(IR_InstructionTest, CreateNotEqual) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.NotEqual(b.builder.Constant(4_i), b.builder.Constant(2_i));
+    const auto* instr = b.builder.NotEqual(b.builder.ir.types.Get<type::Bool>(),
+                                           b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kNotEqual);
 
@@ -203,14 +211,15 @@
 
     std::stringstream str;
     instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 != 2");
+    EXPECT_EQ(str.str(), "%42 (bool) = 4 != 2");
 }
 
 TEST_F(IR_InstructionTest, CreateLessThan) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.LessThan(b.builder.Constant(4_i), b.builder.Constant(2_i));
+    const auto* instr = b.builder.LessThan(b.builder.ir.types.Get<type::Bool>(),
+                                           b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kLessThan);
 
@@ -229,14 +238,15 @@
 
     std::stringstream str;
     instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 < 2");
+    EXPECT_EQ(str.str(), "%42 (bool) = 4 < 2");
 }
 
 TEST_F(IR_InstructionTest, CreateGreaterThan) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.GreaterThan(b.builder.Constant(4_i), b.builder.Constant(2_i));
+    const auto* instr = b.builder.GreaterThan(b.builder.ir.types.Get<type::Bool>(),
+                                              b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kGreaterThan);
 
@@ -255,14 +265,15 @@
 
     std::stringstream str;
     instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 > 2");
+    EXPECT_EQ(str.str(), "%42 (bool) = 4 > 2");
 }
 
 TEST_F(IR_InstructionTest, CreateLessThanEqual) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.LessThanEqual(b.builder.Constant(4_i), b.builder.Constant(2_i));
+    const auto* instr = b.builder.LessThanEqual(b.builder.ir.types.Get<type::Bool>(),
+                                                b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kLessThanEqual);
 
@@ -281,15 +292,15 @@
 
     std::stringstream str;
     instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 <= 2");
+    EXPECT_EQ(str.str(), "%42 (bool) = 4 <= 2");
 }
 
 TEST_F(IR_InstructionTest, CreateGreaterThanEqual) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr =
-        b.builder.GreaterThanEqual(b.builder.Constant(4_i), b.builder.Constant(2_i));
+    const auto* instr = b.builder.GreaterThanEqual(
+        b.builder.ir.types.Get<type::Bool>(), b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kGreaterThanEqual);
 
@@ -308,14 +319,15 @@
 
     std::stringstream str;
     instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 >= 2");
+    EXPECT_EQ(str.str(), "%42 (bool) = 4 >= 2");
 }
 
 TEST_F(IR_InstructionTest, CreateShiftLeft) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.ShiftLeft(b.builder.Constant(4_i), b.builder.Constant(2_i));
+    const auto* instr = b.builder.ShiftLeft(b.builder.ir.types.Get<type::I32>(),
+                                            b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kShiftLeft);
 
@@ -334,14 +346,15 @@
 
     std::stringstream str;
     instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 << 2");
+    EXPECT_EQ(str.str(), "%42 (i32) = 4 << 2");
 }
 
 TEST_F(IR_InstructionTest, CreateShiftRight) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.ShiftRight(b.builder.Constant(4_i), b.builder.Constant(2_i));
+    const auto* instr = b.builder.ShiftRight(b.builder.ir.types.Get<type::I32>(),
+                                             b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kShiftRight);
 
@@ -360,14 +373,15 @@
 
     std::stringstream str;
     instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 >> 2");
+    EXPECT_EQ(str.str(), "%42 (i32) = 4 >> 2");
 }
 
 TEST_F(IR_InstructionTest, CreateAdd) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.Add(b.builder.Constant(4_i), b.builder.Constant(2_i));
+    const auto* instr = b.builder.Add(b.builder.ir.types.Get<type::I32>(), b.builder.Constant(4_i),
+                                      b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kAdd);
 
@@ -386,14 +400,15 @@
 
     std::stringstream str;
     instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 + 2");
+    EXPECT_EQ(str.str(), "%42 (i32) = 4 + 2");
 }
 
 TEST_F(IR_InstructionTest, CreateSubtract) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.Subtract(b.builder.Constant(4_i), b.builder.Constant(2_i));
+    const auto* instr = b.builder.Subtract(b.builder.ir.types.Get<type::I32>(),
+                                           b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kSubtract);
 
@@ -412,14 +427,15 @@
 
     std::stringstream str;
     instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 - 2");
+    EXPECT_EQ(str.str(), "%42 (i32) = 4 - 2");
 }
 
 TEST_F(IR_InstructionTest, CreateMultiply) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.Multiply(b.builder.Constant(4_i), b.builder.Constant(2_i));
+    const auto* instr = b.builder.Multiply(b.builder.ir.types.Get<type::I32>(),
+                                           b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kMultiply);
 
@@ -438,14 +454,15 @@
 
     std::stringstream str;
     instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 * 2");
+    EXPECT_EQ(str.str(), "%42 (i32) = 4 * 2");
 }
 
 TEST_F(IR_InstructionTest, CreateDivide) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.Divide(b.builder.Constant(4_i), b.builder.Constant(2_i));
+    const auto* instr = b.builder.Divide(b.builder.ir.types.Get<type::I32>(),
+                                         b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kDivide);
 
@@ -464,14 +481,15 @@
 
     std::stringstream str;
     instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 / 2");
+    EXPECT_EQ(str.str(), "%42 (i32) = 4 / 2");
 }
 
 TEST_F(IR_InstructionTest, CreateModulo) {
     auto& b = CreateEmptyBuilder();
 
     b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr = b.builder.Modulo(b.builder.Constant(4_i), b.builder.Constant(2_i));
+    const auto* instr = b.builder.Modulo(b.builder.ir.types.Get<type::I32>(),
+                                         b.builder.Constant(4_i), b.builder.Constant(2_i));
 
     EXPECT_EQ(instr->GetKind(), Binary::Kind::kModulo);
 
@@ -490,7 +508,50 @@
 
     std::stringstream str;
     instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 % 2");
+    EXPECT_EQ(str.str(), "%42 (i32) = 4 % 2");
+}
+
+TEST_F(IR_InstructionTest, Binary_Usage) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.And(b.builder.ir.types.Get<type::I32>(), b.builder.Constant(4_i),
+                                      b.builder.Constant(2_i));
+
+    EXPECT_EQ(instr->GetKind(), Binary::Kind::kAnd);
+
+    ASSERT_NE(instr->Result(), nullptr);
+    ASSERT_EQ(instr->Result()->Usage().Length(), 1);
+    EXPECT_EQ(instr->Result()->Usage()[0], instr);
+
+    ASSERT_NE(instr->LHS(), nullptr);
+    ASSERT_EQ(instr->LHS()->Usage().Length(), 1);
+    EXPECT_EQ(instr->LHS()->Usage()[0], instr);
+
+    ASSERT_NE(instr->RHS(), nullptr);
+    ASSERT_EQ(instr->RHS()->Usage().Length(), 1);
+    EXPECT_EQ(instr->RHS()->Usage()[0], instr);
+}
+
+TEST_F(IR_InstructionTest, Binary_Usage_DuplicateValue) {
+    auto& b = CreateEmptyBuilder();
+
+    auto val = b.builder.Constant(4_i);
+
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr = b.builder.And(b.builder.ir.types.Get<type::I32>(), val, val);
+
+    EXPECT_EQ(instr->GetKind(), Binary::Kind::kAnd);
+
+    ASSERT_NE(instr->Result(), nullptr);
+    ASSERT_EQ(instr->Result()->Usage().Length(), 1);
+    EXPECT_EQ(instr->Result()->Usage()[0], instr);
+
+    ASSERT_EQ(instr->LHS(), instr->RHS());
+
+    ASSERT_NE(instr->LHS(), nullptr);
+    ASSERT_EQ(instr->LHS()->Usage().Length(), 1);
+    EXPECT_EQ(instr->LHS()->Usage()[0], instr);
 }
 
 }  // namespace
diff --git a/src/tint/ir/bitcast.cc b/src/tint/ir/bitcast.cc
new file mode 100644
index 0000000..512d93a
--- /dev/null
+++ b/src/tint/ir/bitcast.cc
@@ -0,0 +1,37 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/ir/bitcast.h"
+#include "src/tint/debug.h"
+
+TINT_INSTANTIATE_TYPEINFO(tint::ir::Bitcast);
+
+namespace tint::ir {
+
+Bitcast::Bitcast(Value* result, Value* val) : Base(result), val_(val) {
+    TINT_ASSERT(IR, val_);
+    val_->AddUsage(this);
+}
+
+Bitcast::~Bitcast() = default;
+
+std::ostream& Bitcast::ToString(std::ostream& out, const SymbolTable& st) const {
+    Result()->ToString(out, st);
+    out << " = bitcast(";
+    val_->ToString(out, st);
+    out << ")";
+    return out;
+}
+
+}  // namespace tint::ir
diff --git a/src/tint/ir/bitcast.h b/src/tint/ir/bitcast.h
new file mode 100644
index 0000000..df8a05e
--- /dev/null
+++ b/src/tint/ir/bitcast.h
@@ -0,0 +1,58 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_IR_BITCAST_H_
+#define SRC_TINT_IR_BITCAST_H_
+
+#include <ostream>
+
+#include "src/tint/castable.h"
+#include "src/tint/ir/instruction.h"
+#include "src/tint/symbol_table.h"
+#include "src/tint/type/type.h"
+
+namespace tint::ir {
+
+/// A bitcast instruction in the IR.
+class Bitcast : public Castable<Bitcast, Instruction> {
+  public:
+    /// Constructor
+    /// @param result the result value
+    /// @param val the value being bitcast
+    Bitcast(Value* result, Value* val);
+    Bitcast(const Bitcast& instr) = delete;
+    Bitcast(Bitcast&& instr) = delete;
+    ~Bitcast() override;
+
+    Bitcast& operator=(const Bitcast& instr) = delete;
+    Bitcast& operator=(Bitcast&& instr) = delete;
+
+    /// @returns the left-hand-side value for the instruction
+    const Value* Val() const { return val_; }
+
+    /// 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 SymbolTable& st) const override;
+
+  private:
+    Value* val_ = nullptr;
+};
+
+std::ostream& operator<<(std::ostream& out, const Bitcast&);
+
+}  // namespace tint::ir
+
+#endif  // SRC_TINT_IR_BITCAST_H_
diff --git a/src/tint/ir/bitcast_test.cc b/src/tint/ir/bitcast_test.cc
new file mode 100644
index 0000000..91c5c4b
--- /dev/null
+++ b/src/tint/ir/bitcast_test.cc
@@ -0,0 +1,65 @@
+// Copyright 2023 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <sstream>
+
+#include "src/tint/ir/instruction.h"
+#include "src/tint/ir/test_helper.h"
+
+namespace tint::ir {
+namespace {
+
+using namespace tint::number_suffixes;  // NOLINT
+                                        //
+using IR_InstructionTest = TestHelper;
+
+TEST_F(IR_InstructionTest, Bitcast) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr =
+        b.builder.Bitcast(b.builder.ir.types.Get<type::I32>(), b.builder.Constant(4_i));
+
+    ASSERT_TRUE(instr->Result()->Is<Temp>());
+    EXPECT_EQ(Temp::Id(42), instr->Result()->As<Temp>()->AsId());
+    ASSERT_NE(instr->Result()->Type(), nullptr);
+
+    ASSERT_TRUE(instr->Val()->Is<Constant>());
+    auto val = instr->Val()->As<Constant>()->value;
+    ASSERT_TRUE(val->Is<constant::Scalar<i32>>());
+    EXPECT_EQ(4_i, val->As<constant::Scalar<i32>>()->ValueAs<i32>());
+
+    std::stringstream str;
+    instr->ToString(str, program->Symbols());
+    EXPECT_EQ(str.str(), "%42 (i32) = bitcast(4)");
+}
+
+TEST_F(IR_InstructionTest, Bitcast_Usage) {
+    auto& b = CreateEmptyBuilder();
+
+    b.builder.next_temp_id = Temp::Id(42);
+    const auto* instr =
+        b.builder.Bitcast(b.builder.ir.types.Get<type::I32>(), b.builder.Constant(4_i));
+
+    ASSERT_NE(instr->Result(), nullptr);
+    ASSERT_EQ(instr->Result()->Usage().Length(), 1);
+    EXPECT_EQ(instr->Result()->Usage()[0], instr);
+
+    ASSERT_NE(instr->Val(), nullptr);
+    ASSERT_EQ(instr->Val()->Usage().Length(), 1);
+    EXPECT_EQ(instr->Val()->Usage()[0], instr);
+}
+
+}  // namespace
+}  // namespace tint::ir
diff --git a/src/tint/ir/builder.cc b/src/tint/ir/builder.cc
index 35e4f70..e65c2fc 100644
--- a/src/tint/ir/builder.cc
+++ b/src/tint/ir/builder.cc
@@ -97,80 +97,84 @@
     return next_temp_id++;
 }
 
-const Binary* Builder::CreateBinary(Binary::Kind kind, const Value* lhs, const Value* rhs) {
-    return ir.instructions.Create<ir::Binary>(kind, Temp(), lhs, rhs);
+Binary* Builder::CreateBinary(Binary::Kind kind, const type::Type* type, Value* lhs, Value* rhs) {
+    return ir.instructions.Create<ir::Binary>(kind, Temp(type), lhs, rhs);
 }
 
-const Binary* Builder::And(const Value* lhs, const Value* rhs) {
-    return CreateBinary(Binary::Kind::kAnd, lhs, rhs);
+Binary* Builder::And(const type::Type* type, Value* lhs, Value* rhs) {
+    return CreateBinary(Binary::Kind::kAnd, type, lhs, rhs);
 }
 
-const Binary* Builder::Or(const Value* lhs, const Value* rhs) {
-    return CreateBinary(Binary::Kind::kOr, lhs, rhs);
+Binary* Builder::Or(const type::Type* type, Value* lhs, Value* rhs) {
+    return CreateBinary(Binary::Kind::kOr, type, lhs, rhs);
 }
 
-const Binary* Builder::Xor(const Value* lhs, const Value* rhs) {
-    return CreateBinary(Binary::Kind::kXor, lhs, rhs);
+Binary* Builder::Xor(const type::Type* type, Value* lhs, Value* rhs) {
+    return CreateBinary(Binary::Kind::kXor, type, lhs, rhs);
 }
 
-const Binary* Builder::LogicalAnd(const Value* lhs, const Value* rhs) {
-    return CreateBinary(Binary::Kind::kLogicalAnd, lhs, rhs);
+Binary* Builder::LogicalAnd(const type::Type* type, Value* lhs, Value* rhs) {
+    return CreateBinary(Binary::Kind::kLogicalAnd, type, lhs, rhs);
 }
 
-const Binary* Builder::LogicalOr(const Value* lhs, const Value* rhs) {
-    return CreateBinary(Binary::Kind::kLogicalOr, lhs, rhs);
+Binary* Builder::LogicalOr(const type::Type* type, Value* lhs, Value* rhs) {
+    return CreateBinary(Binary::Kind::kLogicalOr, type, lhs, rhs);
 }
 
-const Binary* Builder::Equal(const Value* lhs, const Value* rhs) {
-    return CreateBinary(Binary::Kind::kEqual, lhs, rhs);
+Binary* Builder::Equal(const type::Type* type, Value* lhs, Value* rhs) {
+    return CreateBinary(Binary::Kind::kEqual, type, lhs, rhs);
 }
 
-const Binary* Builder::NotEqual(const Value* lhs, const Value* rhs) {
-    return CreateBinary(Binary::Kind::kNotEqual, lhs, rhs);
+Binary* Builder::NotEqual(const type::Type* type, Value* lhs, Value* rhs) {
+    return CreateBinary(Binary::Kind::kNotEqual, type, lhs, rhs);
 }
 
-const Binary* Builder::LessThan(const Value* lhs, const Value* rhs) {
-    return CreateBinary(Binary::Kind::kLessThan, lhs, rhs);
+Binary* Builder::LessThan(const type::Type* type, Value* lhs, Value* rhs) {
+    return CreateBinary(Binary::Kind::kLessThan, type, lhs, rhs);
 }
 
-const Binary* Builder::GreaterThan(const Value* lhs, const Value* rhs) {
-    return CreateBinary(Binary::Kind::kGreaterThan, lhs, rhs);
+Binary* Builder::GreaterThan(const type::Type* type, Value* lhs, Value* rhs) {
+    return CreateBinary(Binary::Kind::kGreaterThan, type, lhs, rhs);
 }
 
-const Binary* Builder::LessThanEqual(const Value* lhs, const Value* rhs) {
-    return CreateBinary(Binary::Kind::kLessThanEqual, lhs, rhs);
+Binary* Builder::LessThanEqual(const type::Type* type, Value* lhs, Value* rhs) {
+    return CreateBinary(Binary::Kind::kLessThanEqual, type, lhs, rhs);
 }
 
-const Binary* Builder::GreaterThanEqual(const Value* lhs, const Value* rhs) {
-    return CreateBinary(Binary::Kind::kGreaterThanEqual, lhs, rhs);
+Binary* Builder::GreaterThanEqual(const type::Type* type, Value* lhs, Value* rhs) {
+    return CreateBinary(Binary::Kind::kGreaterThanEqual, type, lhs, rhs);
 }
 
-const Binary* Builder::ShiftLeft(const Value* lhs, const Value* rhs) {
-    return CreateBinary(Binary::Kind::kShiftLeft, lhs, rhs);
+Binary* Builder::ShiftLeft(const type::Type* type, Value* lhs, Value* rhs) {
+    return CreateBinary(Binary::Kind::kShiftLeft, type, lhs, rhs);
 }
 
-const Binary* Builder::ShiftRight(const Value* lhs, const Value* rhs) {
-    return CreateBinary(Binary::Kind::kShiftRight, lhs, rhs);
+Binary* Builder::ShiftRight(const type::Type* type, Value* lhs, Value* rhs) {
+    return CreateBinary(Binary::Kind::kShiftRight, type, lhs, rhs);
 }
 
-const Binary* Builder::Add(const Value* lhs, const Value* rhs) {
-    return CreateBinary(Binary::Kind::kAdd, lhs, rhs);
+Binary* Builder::Add(const type::Type* type, Value* lhs, Value* rhs) {
+    return CreateBinary(Binary::Kind::kAdd, type, lhs, rhs);
 }
 
-const Binary* Builder::Subtract(const Value* lhs, const Value* rhs) {
-    return CreateBinary(Binary::Kind::kSubtract, lhs, rhs);
+Binary* Builder::Subtract(const type::Type* type, Value* lhs, Value* rhs) {
+    return CreateBinary(Binary::Kind::kSubtract, type, lhs, rhs);
 }
 
-const Binary* Builder::Multiply(const Value* lhs, const Value* rhs) {
-    return CreateBinary(Binary::Kind::kMultiply, lhs, rhs);
+Binary* Builder::Multiply(const type::Type* type, Value* lhs, Value* rhs) {
+    return CreateBinary(Binary::Kind::kMultiply, type, lhs, rhs);
 }
 
-const Binary* Builder::Divide(const Value* lhs, const Value* rhs) {
-    return CreateBinary(Binary::Kind::kDivide, lhs, rhs);
+Binary* Builder::Divide(const type::Type* type, Value* lhs, Value* rhs) {
+    return CreateBinary(Binary::Kind::kDivide, type, lhs, rhs);
 }
 
-const Binary* Builder::Modulo(const Value* lhs, const Value* rhs) {
-    return CreateBinary(Binary::Kind::kModulo, lhs, rhs);
+Binary* Builder::Modulo(const type::Type* type, Value* lhs, Value* rhs) {
+    return CreateBinary(Binary::Kind::kModulo, type, lhs, rhs);
+}
+
+ir::Bitcast* Builder::Bitcast(const type::Type* type, Value* val) {
+    return ir.instructions.Create<ir::Bitcast>(Temp(type), val);
 }
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/builder.h b/src/tint/ir/builder.h
index ca21b8d..ad9e2e1 100644
--- a/src/tint/ir/builder.h
+++ b/src/tint/ir/builder.h
@@ -19,6 +19,7 @@
 
 #include "src/tint/constant/scalar.h"
 #include "src/tint/ir/binary.h"
+#include "src/tint/ir/bitcast.h"
 #include "src/tint/ir/constant.h"
 #include "src/tint/ir/function.h"
 #include "src/tint/ir/if.h"
@@ -104,163 +105,191 @@
     /// Creates a new ir::Constant
     /// @param val the constant value
     /// @returns the new constant
-    const ir::Constant* Constant(const constant::Value* val) {
+    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) {
+    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) {
+    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) {
+    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) {
+    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) {
+    ir::Constant* Constant(bool v) {
         return Constant(create<constant::Scalar<bool>>(ir.types.Get<type::Bool>(), v));
     }
 
     /// Creates a new Temporary
+    /// @param type the type of the temporary
     /// @returns the new temporary
-    const ir::Temp* Temp() { return ir.values.Create<ir::Temp>(AllocateTempId()); }
+    ir::Temp* Temp(const type::Type* type) {
+        return ir.values.Create<ir::Temp>(type, AllocateTempId());
+    }
 
     /// Creates an op for `lhs kind rhs`
     /// @param kind the kind of operation
+    /// @param type the result type of the binary expression
     /// @param lhs the left-hand-side of the operation
     /// @param rhs the right-hand-side of the operation
     /// @returns the operation
-    const Binary* CreateBinary(Binary::Kind kind, const Value* lhs, const Value* rhs);
+    Binary* CreateBinary(Binary::Kind kind, const type::Type* type, Value* lhs, Value* rhs);
 
     /// Creates an And operation
+    /// @param type the result type of the expression
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Binary* And(const Value* lhs, const Value* rhs);
+    Binary* And(const type::Type* type, Value* lhs, Value* rhs);
 
     /// Creates an Or operation
+    /// @param type the result type of the expression
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Binary* Or(const Value* lhs, const Value* rhs);
+    Binary* Or(const type::Type* type, Value* lhs, Value* rhs);
 
     /// Creates an Xor operation
+    /// @param type the result type of the expression
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Binary* Xor(const Value* lhs, const Value* rhs);
+    Binary* Xor(const type::Type* type, Value* lhs, Value* rhs);
 
     /// Creates an LogicalAnd operation
+    /// @param type the result type of the expression
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Binary* LogicalAnd(const Value* lhs, const Value* rhs);
+    Binary* LogicalAnd(const type::Type* type, Value* lhs, Value* rhs);
 
     /// Creates an LogicalOr operation
+    /// @param type the result type of the expression
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Binary* LogicalOr(const Value* lhs, const Value* rhs);
+    Binary* LogicalOr(const type::Type* type, Value* lhs, Value* rhs);
 
     /// Creates an Equal operation
+    /// @param type the result type of the expression
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Binary* Equal(const Value* lhs, const Value* rhs);
+    Binary* Equal(const type::Type* type, Value* lhs, Value* rhs);
 
     /// Creates an NotEqual operation
+    /// @param type the result type of the expression
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Binary* NotEqual(const Value* lhs, const Value* rhs);
+    Binary* NotEqual(const type::Type* type, Value* lhs, Value* rhs);
 
     /// Creates an LessThan operation
+    /// @param type the result type of the expression
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Binary* LessThan(const Value* lhs, const Value* rhs);
+    Binary* LessThan(const type::Type* type, Value* lhs, Value* rhs);
 
     /// Creates an GreaterThan operation
+    /// @param type the result type of the expression
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Binary* GreaterThan(const Value* lhs, const Value* rhs);
+    Binary* GreaterThan(const type::Type* type, Value* lhs, Value* rhs);
 
     /// Creates an LessThanEqual operation
+    /// @param type the result type of the expression
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Binary* LessThanEqual(const Value* lhs, const Value* rhs);
+    Binary* LessThanEqual(const type::Type* type, Value* lhs, Value* rhs);
 
     /// Creates an GreaterThanEqual operation
+    /// @param type the result type of the expression
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Binary* GreaterThanEqual(const Value* lhs, const Value* rhs);
+    Binary* GreaterThanEqual(const type::Type* type, Value* lhs, Value* rhs);
 
     /// Creates an ShiftLeft operation
+    /// @param type the result type of the expression
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Binary* ShiftLeft(const Value* lhs, const Value* rhs);
+    Binary* ShiftLeft(const type::Type* type, Value* lhs, Value* rhs);
 
     /// Creates an ShiftRight operation
+    /// @param type the result type of the expression
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Binary* ShiftRight(const Value* lhs, const Value* rhs);
+    Binary* ShiftRight(const type::Type* type, Value* lhs, Value* rhs);
 
     /// Creates an Add operation
+    /// @param type the result type of the expression
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Binary* Add(const Value* lhs, const Value* rhs);
+    Binary* Add(const type::Type* type, Value* lhs, Value* rhs);
 
     /// Creates an Subtract operation
+    /// @param type the result type of the expression
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Binary* Subtract(const Value* lhs, const Value* rhs);
+    Binary* Subtract(const type::Type* type, Value* lhs, Value* rhs);
 
     /// Creates an Multiply operation
+    /// @param type the result type of the expression
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Binary* Multiply(const Value* lhs, const Value* rhs);
+    Binary* Multiply(const type::Type* type, Value* lhs, Value* rhs);
 
     /// Creates an Divide operation
+    /// @param type the result type of the expression
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Binary* Divide(const Value* lhs, const Value* rhs);
+    Binary* Divide(const type::Type* type, Value* lhs, Value* rhs);
 
     /// Creates an Modulo operation
+    /// @param type the result type of the expression
     /// @param lhs the lhs of the add
     /// @param rhs the rhs of the add
     /// @returns the operation
-    const Binary* Modulo(const Value* lhs, const Value* rhs);
+    Binary* Modulo(const type::Type* type, Value* lhs, Value* rhs);
+
+    /// Creates a bitcast instruction
+    /// @param type the result type of the bitcast
+    /// @param val the value being bitcast
+    /// @returns the instruction
+    ir::Bitcast* Bitcast(const type::Type* type, Value* val);
 
     /// @returns a unique temp id
     Temp::Id AllocateTempId();
diff --git a/src/tint/ir/builder_impl.cc b/src/tint/ir/builder_impl.cc
index afa56f5..517e09b 100644
--- a/src/tint/ir/builder_impl.cc
+++ b/src/tint/ir/builder_impl.cc
@@ -16,6 +16,7 @@
 
 #include "src/tint/ast/alias.h"
 #include "src/tint/ast/binary_expression.h"
+#include "src/tint/ast/bitcast_expression.h"
 #include "src/tint/ast/block_statement.h"
 #include "src/tint/ast/bool_literal_expression.h"
 #include "src/tint/ast/break_if_statement.h"
@@ -517,12 +518,12 @@
     return true;
 }
 
-utils::Result<const Value*> BuilderImpl::EmitExpression(const ast::Expression* expr) {
+utils::Result<Value*> BuilderImpl::EmitExpression(const ast::Expression* expr) {
     return tint::Switch(
         expr,
         // [&](const ast::IndexAccessorExpression* a) { return EmitIndexAccessor(a); },
         [&](const ast::BinaryExpression* b) { return EmitBinary(b); },
-        // [&](const ast::BitcastExpression* b) { return EmitBitcast(b); },
+        [&](const ast::BitcastExpression* b) { return EmitBitcast(b); },
         // [&](const ast::CallExpression* c) { return EmitCall(c); },
         // [&](const ast::IdentifierExpression* i) { return EmitIdentifier(i); },
         [&](const ast::LiteralExpression* l) { return EmitLiteral(l); },
@@ -552,7 +553,7 @@
         });
 }
 
-utils::Result<const Value*> BuilderImpl::EmitBinary(const ast::BinaryExpression* expr) {
+utils::Result<Value*> BuilderImpl::EmitBinary(const ast::BinaryExpression* expr) {
     auto lhs = EmitExpression(expr->lhs);
     if (!lhs) {
         return utils::Failure;
@@ -563,61 +564,62 @@
         return utils::Failure;
     }
 
-    const Binary* instr = nullptr;
+    auto* sem = builder.ir.program->Sem().Get(expr);
+    Binary* instr = nullptr;
     switch (expr->op) {
         case ast::BinaryOp::kAnd:
-            instr = builder.And(lhs.Get(), rhs.Get());
+            instr = builder.And(sem->Type(), lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kOr:
-            instr = builder.Or(lhs.Get(), rhs.Get());
+            instr = builder.Or(sem->Type(), lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kXor:
-            instr = builder.Xor(lhs.Get(), rhs.Get());
+            instr = builder.Xor(sem->Type(), lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kLogicalAnd:
-            instr = builder.LogicalAnd(lhs.Get(), rhs.Get());
+            instr = builder.LogicalAnd(sem->Type(), lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kLogicalOr:
-            instr = builder.LogicalOr(lhs.Get(), rhs.Get());
+            instr = builder.LogicalOr(sem->Type(), lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kEqual:
-            instr = builder.Equal(lhs.Get(), rhs.Get());
+            instr = builder.Equal(sem->Type(), lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kNotEqual:
-            instr = builder.NotEqual(lhs.Get(), rhs.Get());
+            instr = builder.NotEqual(sem->Type(), lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kLessThan:
-            instr = builder.LessThan(lhs.Get(), rhs.Get());
+            instr = builder.LessThan(sem->Type(), lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kGreaterThan:
-            instr = builder.GreaterThan(lhs.Get(), rhs.Get());
+            instr = builder.GreaterThan(sem->Type(), lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kLessThanEqual:
-            instr = builder.LessThanEqual(lhs.Get(), rhs.Get());
+            instr = builder.LessThanEqual(sem->Type(), lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kGreaterThanEqual:
-            instr = builder.GreaterThanEqual(lhs.Get(), rhs.Get());
+            instr = builder.GreaterThanEqual(sem->Type(), lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kShiftLeft:
-            instr = builder.ShiftLeft(lhs.Get(), rhs.Get());
+            instr = builder.ShiftLeft(sem->Type(), lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kShiftRight:
-            instr = builder.ShiftRight(lhs.Get(), rhs.Get());
+            instr = builder.ShiftRight(sem->Type(), lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kAdd:
-            instr = builder.Add(lhs.Get(), rhs.Get());
+            instr = builder.Add(sem->Type(), lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kSubtract:
-            instr = builder.Subtract(lhs.Get(), rhs.Get());
+            instr = builder.Subtract(sem->Type(), lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kMultiply:
-            instr = builder.Multiply(lhs.Get(), rhs.Get());
+            instr = builder.Multiply(sem->Type(), lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kDivide:
-            instr = builder.Divide(lhs.Get(), rhs.Get());
+            instr = builder.Divide(sem->Type(), lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kModulo:
-            instr = builder.Modulo(lhs.Get(), rhs.Get());
+            instr = builder.Modulo(sem->Type(), lhs.Get(), rhs.Get());
             break;
         case ast::BinaryOp::kNone:
             TINT_ICE(IR, diagnostics_) << "missing binary operand type";
@@ -625,10 +627,23 @@
     }
 
     current_flow_block->instructions.Push(instr);
-    return utils::Result<const Value*>(instr->Result());
+    return instr->Result();
 }
 
-utils::Result<const Value*> BuilderImpl::EmitLiteral(const ast::LiteralExpression* lit) {
+utils::Result<Value*> BuilderImpl::EmitBitcast(const ast::BitcastExpression* expr) {
+    auto val = EmitExpression(expr->expr);
+    if (!val) {
+        return utils::Failure;
+    }
+
+    auto* sem = builder.ir.program->Sem().Get(expr);
+    auto* instr = builder.Bitcast(sem->Type(), val.Get());
+
+    current_flow_block->instructions.Push(instr);
+    return instr->Result();
+}
+
+utils::Result<Value*> BuilderImpl::EmitLiteral(const ast::LiteralExpression* lit) {
     auto* sem = builder.ir.program->Sem().Get(lit);
     if (!sem) {
         diagnostics_.add_error(
@@ -646,7 +661,7 @@
             lit->source);
         return utils::Failure;
     }
-    return utils::Result<const Value*>(builder.Constant(cv));
+    return builder.Constant(cv);
 }
 
 bool BuilderImpl::EmitType(const ast::Type* ty) {
diff --git a/src/tint/ir/builder_impl.h b/src/tint/ir/builder_impl.h
index 5090be7..5d6fbd7 100644
--- a/src/tint/ir/builder_impl.h
+++ b/src/tint/ir/builder_impl.h
@@ -32,6 +32,7 @@
 }  // namespace tint
 namespace tint::ast {
 class BinaryExpression;
+class BitcastExpression;
 class BlockStatement;
 class BreakIfStatement;
 class BreakStatement;
@@ -140,7 +141,7 @@
     /// Emits an expression
     /// @param expr the expression to emit
     /// @returns true if successful, false otherwise
-    utils::Result<const Value*> EmitExpression(const ast::Expression* expr);
+    utils::Result<Value*> EmitExpression(const ast::Expression* expr);
 
     /// Emits a variable
     /// @param var the variable to emit
@@ -150,12 +151,17 @@
     /// Emits a binary expression
     /// @param expr the binary expression
     /// @returns the value storing the result if successful, utils::Failure otherwise
-    utils::Result<const Value*> EmitBinary(const ast::BinaryExpression* expr);
+    utils::Result<Value*> EmitBinary(const ast::BinaryExpression* expr);
+
+    /// Emits a bitcast expression
+    /// @param expr the bitcast expression
+    /// @returns the value storing the result if successful, utils::Failure otherwise
+    utils::Result<Value*> EmitBitcast(const ast::BitcastExpression* expr);
 
     /// Emits a literal expression
     /// @param lit the literal to emit
     /// @returns true if successful, false otherwise
-    utils::Result<const Value*> EmitLiteral(const ast::LiteralExpression* lit);
+    utils::Result<Value*> EmitLiteral(const ast::LiteralExpression* lit);
 
     /// Emits a type
     /// @param ty the type to emit
diff --git a/src/tint/ir/builder_impl_test.cc b/src/tint/ir/builder_impl_test.cc
index 0600a15..41a3f4c 100644
--- a/src/tint/ir/builder_impl_test.cc
+++ b/src/tint/ir/builder_impl_test.cc
@@ -1437,7 +1437,7 @@
 
     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 (u32) = 3 + 4
 )");
 }
 
@@ -1452,7 +1452,7 @@
 
     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 (u32) = 3 - 4
 )");
 }
 
@@ -1467,7 +1467,7 @@
 
     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 (u32) = 3 * 4
 )");
 }
 
@@ -1482,7 +1482,7 @@
 
     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 (u32) = 3 / 4
 )");
 }
 
@@ -1497,7 +1497,7 @@
 
     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 (u32) = 3 % 4
 )");
 }
 
@@ -1512,7 +1512,7 @@
 
     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 (u32) = 3 & 4
 )");
 }
 
@@ -1527,7 +1527,7 @@
 
     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 (u32) = 3 | 4
 )");
 }
 
@@ -1542,7 +1542,7 @@
 
     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 (u32) = 3 ^ 4
 )");
 }
 
@@ -1557,7 +1557,7 @@
 
     Disassembler d(b.builder.ir);
     d.EmitBlockInstructions(b.current_flow_block);
-    EXPECT_EQ(d.AsString(), R"(%1 = true && false
+    EXPECT_EQ(d.AsString(), R"(%1 (bool) = true && false
 )");
 }
 
@@ -1572,7 +1572,7 @@
 
     Disassembler d(b.builder.ir);
     d.EmitBlockInstructions(b.current_flow_block);
-    EXPECT_EQ(d.AsString(), R"(%1 = false || true
+    EXPECT_EQ(d.AsString(), R"(%1 (bool) = false || true
 )");
 }
 
@@ -1587,7 +1587,7 @@
 
     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 (bool) = 3 == 4
 )");
 }
 
@@ -1602,7 +1602,7 @@
 
     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 (bool) = 3 != 4
 )");
 }
 
@@ -1617,7 +1617,7 @@
 
     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 (bool) = 3 < 4
 )");
 }
 
@@ -1632,7 +1632,7 @@
 
     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 (bool) = 3 > 4
 )");
 }
 
@@ -1647,7 +1647,7 @@
 
     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 (bool) = 3 <= 4
 )");
 }
 
@@ -1662,7 +1662,7 @@
 
     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 (bool) = 3 >= 4
 )");
 }
 
@@ -1677,7 +1677,7 @@
 
     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 (u32) = 3 << 4
 )");
 }
 
@@ -1692,7 +1692,7 @@
 
     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 (u32) = 3 >> 4
 )");
 }
 
@@ -1708,15 +1708,29 @@
 
     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.3 * 5.5
-%5 = 6.7 / %4
-%6 = 2.5 > %5
-%7 = %3 || %6
+    EXPECT_EQ(d.AsString(), R"(%1 (u32) = 3 >> 4
+%2 (u32) = %1 (u32) + 9
+%3 (bool) = 1 < %2 (u32)
+%4 (f32) = 2.3 * 5.5
+%5 (f32) = 6.7 / %4 (f32)
+%6 (bool) = 2.5 > %5 (f32)
+%7 (bool) = %3 (bool) || %6 (bool)
 )");
 }
 
+TEST_F(IR_BuilderImplTest, EmitExpression_Bitcast) {
+    auto* expr = Bitcast(ty.f32(), 3_u);
+    WrapInFunction(expr);
+
+    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 (f32) = bitcast(3)
+)");
+}
 }  // namespace
 }  // namespace tint::ir
diff --git a/src/tint/ir/constant.h b/src/tint/ir/constant.h
index c5ee079..10b41d3 100644
--- a/src/tint/ir/constant.h
+++ b/src/tint/ir/constant.h
@@ -31,6 +31,9 @@
     explicit Constant(const constant::Value* val);
     ~Constant() override;
 
+    /// @returns the type of the constant
+    const type::Type* Type() const override { return value->Type(); }
+
     /// Write the constant to the given stream
     /// @param out the stream to write to
     /// @param st the symbol table
diff --git a/src/tint/ir/instruction.cc b/src/tint/ir/instruction.cc
index e54b13f..46644dd 100644
--- a/src/tint/ir/instruction.cc
+++ b/src/tint/ir/instruction.cc
@@ -18,7 +18,10 @@
 
 namespace tint::ir {
 
-Instruction::Instruction() = default;
+Instruction::Instruction(Value* result) : result_(result) {
+    TINT_ASSERT(IR, result_);
+    result_->AddUsage(this);
+}
 
 Instruction::~Instruction() = default;
 
diff --git a/src/tint/ir/instruction.h b/src/tint/ir/instruction.h
index 82842ba..8cd9ba8 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/ir/value.h"
 #include "src/tint/symbol_table.h"
 
 namespace tint::ir {
@@ -33,6 +34,9 @@
     Instruction& operator=(const Instruction& instr) = delete;
     Instruction& operator=(Instruction&& instr) = delete;
 
+    /// @returns the result value for the instruction
+    Value* Result() const { return result_; }
+
     /// Write the instruction to the given stream
     /// @param out the stream to write to
     /// @param st the symbol table
@@ -41,7 +45,11 @@
 
   protected:
     /// Constructor
-    Instruction();
+    /// @param result the result value
+    explicit Instruction(Value* result);
+
+  private:
+    Value* result_ = nullptr;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/ir/instruction_test.cc b/src/tint/ir/instruction_test.cc
deleted file mode 100644
index c9307f0..0000000
--- a/src/tint/ir/instruction_test.cc
+++ /dev/null
@@ -1,499 +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/instruction.h"
-#include "src/tint/ir/test_helper.h"
-
-namespace tint::ir {
-namespace {
-
-using IR_BinaryTest = TestHelper;
-
-TEST_F(IR_BinaryTest, 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)));
-
-    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kAnd);
-
-    ASSERT_TRUE(instr->Result()->Is<Temp>());
-    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());
-
-    ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
-
-    std::stringstream str;
-    instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 & 2");
-}
-
-TEST_F(IR_BinaryTest, CreateOr) {
-    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)));
-
-    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kOr);
-
-    ASSERT_TRUE(instr->Result()->Is<Temp>());
-    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());
-
-    ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
-
-    std::stringstream str;
-    instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 | 2");
-}
-
-TEST_F(IR_BinaryTest, CreateXor) {
-    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)));
-
-    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kXor);
-
-    ASSERT_TRUE(instr->Result()->Is<Temp>());
-    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());
-
-    ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
-
-    std::stringstream str;
-    instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 ^ 2");
-}
-
-TEST_F(IR_BinaryTest, CreateLogicalAnd) {
-    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)));
-
-    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kLogicalAnd);
-
-    ASSERT_TRUE(instr->Result()->Is<Temp>());
-    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());
-
-    ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
-
-    std::stringstream str;
-    instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 && 2");
-}
-
-TEST_F(IR_BinaryTest, CreateLogicalOr) {
-    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)));
-
-    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kLogicalOr);
-
-    ASSERT_TRUE(instr->Result()->Is<Temp>());
-    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());
-
-    ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
-
-    std::stringstream str;
-    instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 || 2");
-}
-
-TEST_F(IR_BinaryTest, CreateEqual) {
-    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)));
-
-    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kEqual);
-
-    ASSERT_TRUE(instr->Result()->Is<Temp>());
-    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());
-
-    ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
-
-    std::stringstream str;
-    instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 == 2");
-}
-
-TEST_F(IR_BinaryTest, CreateNotEqual) {
-    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)));
-
-    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kNotEqual);
-
-    ASSERT_TRUE(instr->Result()->Is<Temp>());
-    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());
-
-    ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
-
-    std::stringstream str;
-    instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 != 2");
-}
-
-TEST_F(IR_BinaryTest, CreateLessThan) {
-    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)));
-
-    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kLessThan);
-
-    ASSERT_TRUE(instr->Result()->Is<Temp>());
-    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());
-
-    ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
-
-    std::stringstream str;
-    instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 < 2");
-}
-
-TEST_F(IR_BinaryTest, CreateGreaterThan) {
-    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)));
-
-    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kGreaterThan);
-
-    ASSERT_TRUE(instr->Result()->Is<Temp>());
-    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());
-
-    ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
-
-    std::stringstream str;
-    instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 > 2");
-}
-
-TEST_F(IR_BinaryTest, CreateLessThanEqual) {
-    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)));
-
-    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kLessThanEqual);
-
-    ASSERT_TRUE(instr->Result()->Is<Temp>());
-    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());
-
-    ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
-
-    std::stringstream str;
-    instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 <= 2");
-}
-
-TEST_F(IR_BinaryTest, CreateGreaterThanEqual) {
-    auto& b = CreateEmptyBuilder();
-
-    b.builder.next_temp_id = Temp::Id(42);
-    const auto* instr =
-        b.builder.GreaterThanEqual(b.builder.Constant(i32(4)), b.builder.Constant(i32(2)));
-
-    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kGreaterThanEqual);
-
-    ASSERT_TRUE(instr->Result()->Is<Temp>());
-    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());
-
-    ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
-
-    std::stringstream str;
-    instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 >= 2");
-}
-
-TEST_F(IR_BinaryTest, CreateShiftLeft) {
-    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)));
-
-    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kShiftLeft);
-
-    ASSERT_TRUE(instr->Result()->Is<Temp>());
-    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());
-
-    ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
-
-    std::stringstream str;
-    instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 << 2");
-}
-
-TEST_F(IR_BinaryTest, CreateShiftRight) {
-    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)));
-
-    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kShiftRight);
-
-    ASSERT_TRUE(instr->Result()->Is<Temp>());
-    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());
-
-    ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
-
-    std::stringstream str;
-    instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 >> 2");
-}
-
-TEST_F(IR_BinaryTest, CreateAdd) {
-    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)));
-
-    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kAdd);
-
-    ASSERT_TRUE(instr->Result()->Is<Temp>());
-    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());
-
-    ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
-
-    std::stringstream str;
-    instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 + 2");
-}
-
-TEST_F(IR_BinaryTest, CreateSubtract) {
-    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)));
-
-    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kSubtract);
-
-    ASSERT_TRUE(instr->Result()->Is<Temp>());
-    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());
-
-    ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
-
-    std::stringstream str;
-    instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 - 2");
-}
-
-TEST_F(IR_BinaryTest, CreateMultiply) {
-    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)));
-
-    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kMultiply);
-
-    ASSERT_TRUE(instr->Result()->Is<Temp>());
-    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());
-
-    ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
-
-    std::stringstream str;
-    instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 * 2");
-}
-
-TEST_F(IR_BinaryTest, CreateDivide) {
-    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)));
-
-    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kDivide);
-
-    ASSERT_TRUE(instr->Result()->Is<Temp>());
-    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());
-
-    ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
-
-    std::stringstream str;
-    instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 / 2");
-}
-
-TEST_F(IR_BinaryTest, CreateModulo) {
-    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)));
-
-    EXPECT_EQ(instr->GetKind(), Instruction::Kind::kModulo);
-
-    ASSERT_TRUE(instr->Result()->Is<Temp>());
-    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());
-
-    ASSERT_TRUE(instr->RHS()->Is<Constant>());
-    auto rhs = instr->RHS()->As<Constant>();
-    ASSERT_TRUE(rhs->IsI32());
-    EXPECT_EQ(i32(2), rhs->AsI32());
-
-    std::stringstream str;
-    instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 % 2");
-}
-
-}  // namespace
-}  // namespace tint::ir
diff --git a/src/tint/ir/temp.cc b/src/tint/ir/temp.cc
index afd0921..e925561 100644
--- a/src/tint/ir/temp.cc
+++ b/src/tint/ir/temp.cc
@@ -20,12 +20,12 @@
 
 namespace tint::ir {
 
-Temp::Temp(Id id) : id_(id) {}
+Temp::Temp(const type::Type* type, Id id) : type_(type), id_(id) {}
 
 Temp::~Temp() = default;
 
-std::ostream& Temp::ToString(std::ostream& out, const SymbolTable&) const {
-    out << "%" << std::to_string(AsId());
+std::ostream& Temp::ToString(std::ostream& out, const SymbolTable& st) const {
+    out << "%" << std::to_string(AsId()) << " (" << type_->FriendlyName(st) << ")";
     return out;
 }
 
diff --git a/src/tint/ir/temp.h b/src/tint/ir/temp.h
index 2db81f3..1a4a38d 100644
--- a/src/tint/ir/temp.h
+++ b/src/tint/ir/temp.h
@@ -29,8 +29,9 @@
     using Id = uint32_t;
 
     /// Constructor
+    /// @param type the type of the temporary
     /// @param id the id for the value
-    explicit Temp(Id id);
+    Temp(const type::Type* type, Id id);
 
     /// Destructor
     ~Temp() override;
@@ -44,6 +45,9 @@
     /// @returns the value data as an `Id`.
     Id AsId() const { return id_; }
 
+    /// @returns the type of the temporary
+    const type::Type* Type() const override { return type_; }
+
     /// Write the temp to the given stream
     /// @param out the stream to write to
     /// @param st the symbol table
@@ -51,6 +55,7 @@
     std::ostream& ToString(std::ostream& out, const SymbolTable& st) const override;
 
   private:
+    const type::Type* type_ = nullptr;
     Id id_ = 0;
 };
 
diff --git a/src/tint/ir/temp_test.cc b/src/tint/ir/temp_test.cc
index 6b79a51..81a4992 100644
--- a/src/tint/ir/temp_test.cc
+++ b/src/tint/ir/temp_test.cc
@@ -30,11 +30,11 @@
     std::stringstream str;
 
     b.builder.next_temp_id = Temp::Id(4);
-    auto* val = b.builder.Temp();
+    auto* val = b.builder.Temp(b.builder.ir.types.Get<type::I32>());
     EXPECT_EQ(4u, val->AsId());
 
     val->ToString(str, program->Symbols());
-    EXPECT_EQ("%4", str.str());
+    EXPECT_EQ("%4 (i32)", str.str());
 }
 
 }  // namespace
diff --git a/src/tint/ir/value.h b/src/tint/ir/value.h
index 6dffc3e..1f9619f 100644
--- a/src/tint/ir/value.h
+++ b/src/tint/ir/value.h
@@ -19,6 +19,13 @@
 
 #include "src/tint/castable.h"
 #include "src/tint/symbol_table.h"
+#include "src/tint/type/type.h"
+#include "src/tint/utils/unique_vector.h"
+
+// Forward declarations
+namespace tint::ir {
+class Instruction;
+}  // namespace tint::ir
 
 namespace tint::ir {
 
@@ -34,6 +41,17 @@
     Value& operator=(const Value&) = delete;
     Value& operator=(Value&&) = delete;
 
+    /// Adds an instruction which uses this value.
+    /// @param instr the instruction
+    void AddUsage(const Instruction* instr) { uses_.Add(instr); }
+
+    /// @returns the vector of instructions which use this value. An instruction will only be
+    /// returned once even if that instruction uses the given value multiple times.
+    utils::VectorRef<const Instruction*> Usage() const { return uses_; }
+
+    /// @returns the type of the value
+    virtual const type::Type* Type() const = 0;
+
     /// Write the value to the given stream
     /// @param out the stream to write to
     /// @param st the symbol table
@@ -43,6 +61,9 @@
   protected:
     /// Constructor
     Value();
+
+  private:
+    utils::UniqueVector<const Instruction*, 4> uses_;
 };
 
 }  // namespace tint::ir
diff --git a/src/tint/program_builder.cc b/src/tint/program_builder.cc
index 87c6a4c..a219de3 100644
--- a/src/tint/program_builder.cc
+++ b/src/tint/program_builder.cc
@@ -153,4 +153,39 @@
                 });
 }
 
+const constant::Value* ProgramBuilder::createSplatOrComposite(
+    const type::Type* type,
+    utils::VectorRef<const constant::Value*> elements) {
+    if (elements.IsEmpty()) {
+        return nullptr;
+    }
+
+    bool any_zero = false;
+    bool all_zero = true;
+    bool all_equal = true;
+    auto* first = elements.Front();
+    for (auto* el : elements) {
+        if (!el) {
+            return nullptr;
+        }
+        if (!any_zero && el->AnyZero()) {
+            any_zero = true;
+        }
+        if (all_zero && !el->AllZero()) {
+            all_zero = false;
+        }
+        if (all_equal && el != first) {
+            if (!el->Equal(first)) {
+                all_equal = false;
+            }
+        }
+    }
+    if (all_equal) {
+        return create<constant::Splat>(type, elements[0], elements.Length());
+    }
+
+    return constant_nodes_.Create<constant::Composite>(type, std::move(elements), all_zero,
+                                                       any_zero);
+}
+
 }  // namespace tint
diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h
index 4dac010..9b688f0 100644
--- a/src/tint/program_builder.h
+++ b/src/tint/program_builder.h
@@ -493,42 +493,13 @@
     /// @param type the composite type
     /// @param elements the composite elements
     /// @returns the node pointer
-    template <typename T>
-    traits::EnableIf<traits::IsTypeOrDerived<T, constant::Composite> ||
-                         traits::IsTypeOrDerived<T, constant::Splat>,
-                     const constant::Value>*
-    create(const type::Type* type, utils::VectorRef<const constant::Value*> elements) {
+    template <typename T,
+              typename = traits::EnableIf<traits::IsTypeOrDerived<T, constant::Composite> ||
+                                          traits::IsTypeOrDerived<T, constant::Splat>>>
+    const constant::Value* create(const type::Type* type,
+                                  utils::VectorRef<const constant::Value*> elements) {
         AssertNotMoved();
-        if (elements.IsEmpty()) {
-            return nullptr;
-        }
-
-        bool any_zero = false;
-        bool all_zero = true;
-        bool all_equal = true;
-        auto* first = elements.Front();
-        for (auto* el : elements) {
-            if (!el) {
-                return nullptr;
-            }
-            if (!any_zero && el->AnyZero()) {
-                any_zero = true;
-            }
-            if (all_zero && !el->AllZero()) {
-                all_zero = false;
-            }
-            if (all_equal && el != first) {
-                if (!el->Equal(first)) {
-                    all_equal = false;
-                }
-            }
-        }
-        if (all_equal) {
-            return create<constant::Splat>(type, elements[0], elements.Length());
-        }
-
-        return constant_nodes_.Create<constant::Composite>(type, std::move(elements), all_zero,
-                                                           any_zero);
+        return createSplatOrComposite(type, elements);
     }
 
     /// Constructs a splat constant.
@@ -536,9 +507,10 @@
     /// @param element the splat element
     /// @param n the number of elements
     /// @returns the node pointer
-    template <typename T>
-    traits::EnableIf<traits::IsTypeOrDerived<T, constant::Splat>, const constant::Splat>*
-    create(const type::Type* type, const constant::Value* element, size_t n) {
+    template <typename T, typename = traits::EnableIf<traits::IsTypeOrDerived<T, constant::Splat>>>
+    const constant::Splat* create(const type::Type* type,
+                                  const constant::Value* element,
+                                  size_t n) {
         AssertNotMoved();
         return constant_nodes_.Create<constant::Splat>(type, element, n);
     }
@@ -3351,6 +3323,10 @@
     void AssertNotMoved() const;
 
   private:
+    const constant::Value* createSplatOrComposite(
+        const type::Type* type,
+        utils::VectorRef<const constant::Value*> elements);
+
     ProgramID id_;
     ast::NodeID last_ast_node_id_ = ast::NodeID{static_cast<decltype(ast::NodeID::value)>(0) - 1};
     type::Manager types_;