[ir] Add bitcast expression.

This CL adds the Bitcast expression into the IR.

Bug: tint:1718
Change-Id: Ic48bd54485e9b380c94f599e683c2fbba7505787
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/116041
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Dan Sinclair <dsinclair@chromium.org>
Reviewed-by: Ben Clayton <bclayton@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_test.cc b/src/tint/ir/binary_test.cc
index d7ea7dd..05d7065 100644
--- a/src/tint/ir/binary_test.cc
+++ b/src/tint/ir/binary_test.cc
@@ -34,6 +34,7 @@
     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>());
@@ -48,7 +49,7 @@
 
     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) {
@@ -75,7 +76,7 @@
 
     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) {
@@ -102,14 +103,14 @@
 
     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.ir.types.Get<type::I32>(),
+    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);
@@ -129,14 +130,14 @@
 
     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.ir.types.Get<type::I32>(),
+    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);
@@ -156,7 +157,7 @@
 
     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) {
@@ -183,7 +184,7 @@
 
     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) {
@@ -210,7 +211,7 @@
 
     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) {
@@ -237,7 +238,7 @@
 
     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) {
@@ -264,7 +265,7 @@
 
     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) {
@@ -291,7 +292,7 @@
 
     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) {
@@ -318,7 +319,7 @@
 
     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) {
@@ -345,7 +346,7 @@
 
     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) {
@@ -372,7 +373,7 @@
 
     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) {
@@ -399,7 +400,7 @@
 
     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) {
@@ -426,7 +427,7 @@
 
     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) {
@@ -453,7 +454,7 @@
 
     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) {
@@ -480,7 +481,7 @@
 
     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) {
@@ -507,7 +508,7 @@
 
     std::stringstream str;
     instr->ToString(str, program->Symbols());
-    EXPECT_EQ(str.str(), "%42 = 4 % 2");
+    EXPECT_EQ(str.str(), "%42 (i32) = 4 % 2");
 }
 
 }  // namespace
diff --git a/src/tint/ir/bitcast.cc b/src/tint/ir/bitcast.cc
new file mode 100644
index 0000000..7ea2b7f
--- /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(const Value* result, const Value* val) : result_(result), val_(val) {
+    TINT_ASSERT(IR, result_);
+    TINT_ASSERT(IR, val_);
+}
+
+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..56b9651
--- /dev/null
+++ b/src/tint/ir/bitcast.h
@@ -0,0 +1,63 @@
+// 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/ir/value.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(const Value* result, const 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 result value for the instruction
+    const Value* Result() const { return result_; }
+
+    /// @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:
+    const Value* result_ = nullptr;
+    const 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..3b5cecd
--- /dev/null
+++ b/src/tint/ir/bitcast_test.cc
@@ -0,0 +1,49 @@
+// 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)");
+}
+
+}  // namespace
+}  // namespace tint::ir
diff --git a/src/tint/ir/builder.cc b/src/tint/ir/builder.cc
index 2c92f8c..ba0ed9e 100644
--- a/src/tint/ir/builder.cc
+++ b/src/tint/ir/builder.cc
@@ -178,4 +178,8 @@
     return CreateBinary(Binary::Kind::kModulo, type, lhs, rhs);
 }
 
+const ir::Bitcast* Builder::Bitcast(const type::Type* type, const 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 835c8c0..7f442f2 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"
@@ -287,6 +288,12 @@
     /// @returns the operation
     const Binary* Modulo(const type::Type* type, const Value* lhs, const Value* rhs);
 
+    /// Creates a bitcast instruction
+    /// @param type the result type of the bitcast
+    /// @param val the value being bitcast
+    /// @returns the instruction
+    const ir::Bitcast* Bitcast(const type::Type* type, const 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 8196f44..115b6d7 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"
@@ -522,7 +523,7 @@
         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); },
@@ -629,6 +630,19 @@
     return utils::Result<const Value*>(instr->Result());
 }
 
+utils::Result<const 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 utils::Result<const Value*>(instr->Result());
+}
+
 utils::Result<const Value*> BuilderImpl::EmitLiteral(const ast::LiteralExpression* lit) {
     auto* sem = builder.ir.program->Sem().Get(lit);
     if (!sem) {
diff --git a/src/tint/ir/builder_impl.h b/src/tint/ir/builder_impl.h
index 5090be7..35476eb 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;
@@ -152,6 +153,11 @@
     /// @returns the value storing the result if successful, utils::Failure otherwise
     utils::Result<const 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<const Value*> EmitBitcast(const ast::BitcastExpression* expr);
+
     /// Emits a literal expression
     /// @param lit the literal to emit
     /// @returns true if successful, false otherwise
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/temp.cc b/src/tint/ir/temp.cc
index 57def51..e925561 100644
--- a/src/tint/ir/temp.cc
+++ b/src/tint/ir/temp.cc
@@ -24,8 +24,8 @@
 
 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_test.cc b/src/tint/ir/temp_test.cc
index f1fc24f..81a4992 100644
--- a/src/tint/ir/temp_test.cc
+++ b/src/tint/ir/temp_test.cc
@@ -34,7 +34,7 @@
     EXPECT_EQ(4u, val->AsId());
 
     val->ToString(str, program->Symbols());
-    EXPECT_EQ("%4", str.str());
+    EXPECT_EQ("%4 (i32)", str.str());
 }
 
 }  // namespace