diff --git a/CMakeLists.txt b/CMakeLists.txt
index ceee9a56..5f0aae0b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -7,11 +7,13 @@ option(DYNARMIC_USE_SYSTEM_BOOST "Use the system boost libraries" ON)
 # Compiler flags
 if (NOT MSVC)
     add_compile_options(--std=c++14 -Wall -Werror -Wextra -pedantic -Wfatal-errors -Wno-unused-parameter -Wno-missing-braces)
+    add_compile_options(-DBOOST_SYSTEM_NO_DEPRECATED)
     if (ARCHITECTURE_x86_64)
         add_compile_options(-msse4.1)
     endif()
 else()
     add_compile_options(/W3 /MP /Zi /Zo /EHsc /WX)
+    add_compile_options(/DBOOST_SYSTEM_NO_DEPRECATED)
 endif()
 
 # This function should be passed a list of all files in a target. It will automatically generate
diff --git a/src/backend_x64/emit_x64.cpp b/src/backend_x64/emit_x64.cpp
index 5de7839c..3e824b79 100644
--- a/src/backend_x64/emit_x64.cpp
+++ b/src/backend_x64/emit_x64.cpp
@@ -6,6 +6,7 @@
 
 #include <map>
 #include <unordered_map>
+#include <common/bit_util.h>
 
 #include "backend_x64/emit_x64.h"
 #include "common/x64/abi.h"
@@ -30,14 +31,24 @@ static OpArg MJitStateCpsr() {
 }
 
 static IR::Inst* FindUseWithOpcode(IR::Inst* inst, IR::Opcode opcode) {
-    // Gets first found use.
-    auto uses = inst->GetUses();
-    auto iter = std::find_if(uses.begin(), uses.end(), [opcode](const auto& use){ return use->GetOpcode() == opcode; });
-    ASSERT(std::count_if(uses.begin(), uses.end(), [opcode](const auto& use){ return use->GetOpcode() == opcode; }) <= 1);
-    return iter == uses.end() ? nullptr : reinterpret_cast<IR::Inst*>(iter->get());
+    switch (opcode) {
+        case IR::Opcode::GetCarryFromOp:
+            return inst->carry_inst;
+        case IR::Opcode::GetOverflowFromOp:
+            return inst->overflow_inst;
+        default:
+            break;
+    }
+
+    ASSERT_MSG(false, "unreachable");
+    return nullptr;
 }
 
-CodePtr EmitX64::Emit(const Arm::LocationDescriptor descriptor, const Dynarmic::IR::Block& block) {
+static void EraseInstruction(IR::Block& block, IR::Inst* inst) {
+    block.instructions.erase(block.instructions.iterator_to(*inst));
+}
+
+CodePtr EmitX64::Emit(const Arm::LocationDescriptor descriptor, Dynarmic::IR::Block& block) {
     inhibit_emission.clear();
     reg_alloc.Reset();
 
@@ -47,22 +58,21 @@ CodePtr EmitX64::Emit(const Arm::LocationDescriptor descriptor, const Dynarmic::
 
     EmitCondPrelude(block.cond, block.cond_failed, block.location);
 
-    for (const auto& value : block.instructions) {
-        if (inhibit_emission.count(value.get()) != 0)
-            continue;
+    for (auto iter = block.instructions.begin(); iter != block.instructions.end(); ++iter) {
+        IR::Inst* inst = &*iter;
 
         // Call the relevant Emit* member function.
-        switch (value->GetOpcode()) {
+        switch (inst->GetOpcode()) {
 
-#define OPCODE(name, type, ...)                   \
-            case IR::Opcode::name:                \
-                EmitX64::Emit##name(value.get()); \
+#define OPCODE(name, type, ...)                    \
+            case IR::Opcode::name:                 \
+                EmitX64::Emit##name(block, inst);  \
                 break;
 #include "frontend/ir/opcodes.inc"
 #undef OPCODE
 
             default:
-                ASSERT_MSG(false, "Invalid opcode %zu", static_cast<size_t>(value->GetOpcode()));
+                ASSERT_MSG(false, "Invalid opcode %zu", static_cast<size_t>(inst->GetOpcode()));
                 break;
         }
 
@@ -77,153 +87,129 @@ CodePtr EmitX64::Emit(const Arm::LocationDescriptor descriptor, const Dynarmic::
     return code_ptr;
 }
 
-void EmitX64::EmitImmU1(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::ImmU1*>(value_);
-
-    X64Reg result = reg_alloc.DefRegister(value);
-
-    code->MOV(32, R(result), Imm32(value->value));
+void EmitX64::EmitGetRegister(IR::Block&, IR::Inst* inst) {
+    Arm::Reg reg = inst->GetArg(0).GetRegRef();
+    X64Reg result = reg_alloc.DefRegister(inst);
+    code->MOV(32, R(result), MJitStateReg(reg));
 }
 
-void EmitX64::EmitImmU8(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::ImmU8*>(value_);
-
-    X64Reg result = reg_alloc.DefRegister(value);
-
-    code->MOV(32, R(result), Imm32(value->value));
+void EmitX64::EmitSetRegister(IR::Block&, IR::Inst* inst) {
+    Arm::Reg reg = inst->GetArg(0).GetRegRef();
+    IR::Value arg = inst->GetArg(1);
+    if (arg.IsImmediate()) {
+        code->MOV(32, MJitStateReg(reg), Imm32(arg.GetU32()));
+    } else {
+        X64Reg to_store = reg_alloc.UseRegister(arg.GetInst());
+        code->MOV(32, MJitStateReg(reg), R(to_store));
+    }
 }
 
-void EmitX64::EmitImmU32(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::ImmU32*>(value_);
-
-    X64Reg result = reg_alloc.DefRegister(value);
-
-    code->MOV(32, R(result), Imm32(value->value));
-}
-
-void EmitX64::EmitImmRegRef(IR::Value*) {
-    return; // No need to do anything.
-}
-
-void EmitX64::EmitGetRegister(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-    auto regref = reinterpret_cast<IR::ImmRegRef*>(value->GetArg(0).get());
-
-    X64Reg result = reg_alloc.DefRegister(value);
-
-    code->MOV(32, R(result), MJitStateReg(regref->value));
-}
-
-void EmitX64::EmitSetRegister(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-    auto regref = reinterpret_cast<IR::ImmRegRef*>(value->GetArg(0).get());
-
-    X64Reg to_store = reg_alloc.UseRegister(value->GetArg(1).get());
-
-    code->MOV(32, MJitStateReg(regref->value), R(to_store));
-}
-
-void EmitX64::EmitGetNFlag(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
-    X64Reg result = reg_alloc.DefRegister(value);
-
-    // TODO: Flag optimization
-
+void EmitX64::EmitGetNFlag(IR::Block&, IR::Inst* inst) {
+    X64Reg result = reg_alloc.DefRegister(inst);
     code->MOV(32, R(result), MJitStateCpsr());
     code->SHR(32, R(result), Imm8(31));
 }
 
-void EmitX64::EmitSetNFlag(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
+void EmitX64::EmitSetNFlag(IR::Block&, IR::Inst* inst) {
+    constexpr size_t flag_bit = 31;
+    constexpr u32 flag_mask = 1u << flag_bit;
+    IR::Value arg = inst->GetArg(0);
+    if (arg.IsImmediate()) {
+        if (arg.GetU1()) {
+            code->OR(32, MJitStateCpsr(), Imm32(flag_mask));
+        } else {
+            code->AND(32, MJitStateCpsr(), Imm32(~flag_mask));
+        }
+    } else {
+        X64Reg to_store = reg_alloc.UseScratchRegister(arg.GetInst());
 
-    X64Reg to_store = reg_alloc.UseScratchRegister(value->GetArg(0).get());
-
-    // TODO: Flag optimization
-
-    code->SHL(32, R(to_store), Imm8(31));
-    code->AND(32, MJitStateCpsr(), Imm32(~static_cast<u32>(1 << 31)));
-    code->OR(32, MJitStateCpsr(), R(to_store));
+        code->SHL(32, R(to_store), Imm8(flag_bit));
+        code->AND(32, MJitStateCpsr(), Imm32(~flag_mask));
+        code->OR(32, MJitStateCpsr(), R(to_store));
+    }
 }
 
-void EmitX64::EmitGetZFlag(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
-    X64Reg result = reg_alloc.DefRegister(value);
-
-    // TODO: Flag optimization
-
+void EmitX64::EmitGetZFlag(IR::Block&, IR::Inst* inst) {
+    X64Reg result = reg_alloc.DefRegister(inst);
     code->MOV(32, R(result), MJitStateCpsr());
     code->SHR(32, R(result), Imm8(30));
     code->AND(32, R(result), Imm32(1));
 }
 
-void EmitX64::EmitSetZFlag(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
+void EmitX64::EmitSetZFlag(IR::Block&, IR::Inst* inst) {
+    constexpr size_t flag_bit = 30;
+    constexpr u32 flag_mask = 1u << flag_bit;
+    IR::Value arg = inst->GetArg(0);
+    if (arg.IsImmediate()) {
+        if (arg.GetU1()) {
+            code->OR(32, MJitStateCpsr(), Imm32(flag_mask));
+        } else {
+            code->AND(32, MJitStateCpsr(), Imm32(~flag_mask));
+        }
+    } else {
+        X64Reg to_store = reg_alloc.UseScratchRegister(arg.GetInst());
 
-    X64Reg to_store = reg_alloc.UseScratchRegister(value->GetArg(0).get());
-
-    // TODO: Flag optimization
-
-    code->SHL(32, R(to_store), Imm8(30));
-    code->AND(32, MJitStateCpsr(), Imm32(~static_cast<u32>(1 << 30)));
-    code->OR(32, MJitStateCpsr(), R(to_store));
+        code->SHL(32, R(to_store), Imm8(flag_bit));
+        code->AND(32, MJitStateCpsr(), Imm32(~flag_mask));
+        code->OR(32, MJitStateCpsr(), R(to_store));
+    }
 }
 
-void EmitX64::EmitGetCFlag(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
-    X64Reg result = reg_alloc.DefRegister(value);
-
-    // TODO: Flag optimization
-
+void EmitX64::EmitGetCFlag(IR::Block&, IR::Inst* inst) {
+    X64Reg result = reg_alloc.DefRegister(inst);
     code->MOV(32, R(result), MJitStateCpsr());
     code->SHR(32, R(result), Imm8(29));
     code->AND(32, R(result), Imm32(1));
 }
 
-void EmitX64::EmitSetCFlag(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
+void EmitX64::EmitSetCFlag(IR::Block&, IR::Inst* inst) {
+    constexpr size_t flag_bit = 29;
+    constexpr u32 flag_mask = 1u << flag_bit;
+    IR::Value arg = inst->GetArg(0);
+    if (arg.IsImmediate()) {
+        if (arg.GetU1()) {
+            code->OR(32, MJitStateCpsr(), Imm32(flag_mask));
+        } else {
+            code->AND(32, MJitStateCpsr(), Imm32(~flag_mask));
+        }
+    } else {
+        X64Reg to_store = reg_alloc.UseScratchRegister(arg.GetInst());
 
-    X64Reg to_store = reg_alloc.UseScratchRegister(value->GetArg(0).get());
-
-    // TODO: Flag optimization
-
-    code->SHL(32, R(to_store), Imm8(29));
-    code->AND(32, MJitStateCpsr(), Imm32(~static_cast<u32>(1 << 29)));
-    code->OR(32, MJitStateCpsr(), R(to_store));
+        code->SHL(32, R(to_store), Imm8(flag_bit));
+        code->AND(32, MJitStateCpsr(), Imm32(~flag_mask));
+        code->OR(32, MJitStateCpsr(), R(to_store));
+    }
 }
 
-void EmitX64::EmitGetVFlag(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
-    X64Reg result = reg_alloc.DefRegister(value);
-
-    // TODO: Flag optimization
-
+void EmitX64::EmitGetVFlag(IR::Block&, IR::Inst* inst) {
+    X64Reg result = reg_alloc.DefRegister(inst);
     code->MOV(32, R(result), MJitStateCpsr());
     code->SHR(32, R(result), Imm8(28));
     code->AND(32, R(result), Imm32(1));
 }
 
-void EmitX64::EmitSetVFlag(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
+void EmitX64::EmitSetVFlag(IR::Block&, IR::Inst* inst) {
+    constexpr size_t flag_bit = 28;
+    constexpr u32 flag_mask = 1u << flag_bit;
+    IR::Value arg = inst->GetArg(0);
+    if (arg.IsImmediate()) {
+        if (arg.GetU1()) {
+            code->OR(32, MJitStateCpsr(), Imm32(flag_mask));
+        } else {
+            code->AND(32, MJitStateCpsr(), Imm32(~flag_mask));
+        }
+    } else {
+        X64Reg to_store = reg_alloc.UseScratchRegister(arg.GetInst());
 
-    X64Reg to_store = reg_alloc.UseScratchRegister(value->GetArg(0).get());
-
-    // TODO: Flag optimization
-
-    code->SHL(32, R(to_store), Imm8(28));
-    code->AND(32, MJitStateCpsr(), Imm32(~static_cast<u32>(1 << 28)));
-    code->OR(32, MJitStateCpsr(), R(to_store));
+        code->SHL(32, R(to_store), Imm8(flag_bit));
+        code->AND(32, MJitStateCpsr(), Imm32(~flag_mask));
+        code->OR(32, MJitStateCpsr(), R(to_store));
+    }
 }
 
-void EmitX64::EmitBXWritePC(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
-    X64Reg new_pc = reg_alloc.UseScratchRegister(value->GetArg(0).get());
-    X64Reg tmp1 = reg_alloc.ScratchRegister();
-    X64Reg tmp2 = reg_alloc.ScratchRegister();
+void EmitX64::EmitBXWritePC(IR::Block&, IR::Inst* inst) {
+    const u32 T_bit = 1 << 5;
+    auto arg = inst->GetArg(0);
 
     // Pseudocode:
     // if (new_pc & 1) {
@@ -234,66 +220,74 @@ void EmitX64::EmitBXWritePC(IR::Value* value_) {
     //    cpsr.T = false;
     // }
 
-    code->MOV(32, R(tmp1), MJitStateCpsr());
-    code->MOV(32, R(tmp2), R(tmp1));
-    code->AND(32, R(tmp2), Imm32(~(1 << 5)));      // CPSR.T = 0
-    code->OR(32, R(tmp1), Imm32(1 << 5));          // CPSR.T = 1
-    code->TEST(8, R(new_pc), Imm8(1));
-    code->CMOVcc(32, tmp1, R(tmp2), CC_E);         // CPSR.T = pc & 1
-    code->MOV(32, MJitStateCpsr(), R(tmp1));
-    code->LEA(32, tmp2, MComplex(new_pc, new_pc, 1, 0));
-    code->OR(32, R(tmp2), Imm32(0xFFFFFFFC));      // tmp2 = pc & 1 ? 0xFFFFFFFE : 0xFFFFFFFC
-    code->AND(32, R(new_pc), R(tmp2));
-    code->MOV(32, MJitStateReg(Arm::Reg::PC), R(new_pc));
+    if (arg.IsImmediate()) {
+        u32 new_pc = arg.GetU32();
+        if (Common::Bit<0>(new_pc)) {
+            new_pc &= 0xFFFFFFFE;
+            code->MOV(32, MJitStateReg(Arm::Reg::PC), Imm32(new_pc));
+            code->OR(32, MJitStateCpsr(), Imm32(T_bit));
+        } else {
+            new_pc &= 0xFFFFFFFC;
+            code->MOV(32, MJitStateReg(Arm::Reg::PC), Imm32(new_pc));
+            code->AND(32, MJitStateCpsr(), Imm32(~T_bit));
+        }
+    } else {
+        X64Reg new_pc = reg_alloc.UseScratchRegister(arg.GetInst());
+        X64Reg tmp1 = reg_alloc.ScratchRegister();
+        X64Reg tmp2 = reg_alloc.ScratchRegister();
+
+        code->MOV(32, R(tmp1), MJitStateCpsr());
+        code->MOV(32, R(tmp2), R(tmp1));
+        code->AND(32, R(tmp2), Imm32(~T_bit));         // CPSR.T = 0
+        code->OR(32, R(tmp1), Imm32(T_bit));           // CPSR.T = 1
+        code->TEST(8, R(new_pc), Imm8(1));
+        code->CMOVcc(32, tmp1, R(tmp2), CC_E);         // CPSR.T = pc & 1
+        code->MOV(32, MJitStateCpsr(), R(tmp1));
+        code->LEA(32, tmp2, MComplex(new_pc, new_pc, 1, 0));
+        code->OR(32, R(tmp2), Imm32(0xFFFFFFFC));      // tmp2 = pc & 1 ? 0xFFFFFFFE : 0xFFFFFFFC
+        code->AND(32, R(new_pc), R(tmp2));
+        code->MOV(32, MJitStateReg(Arm::Reg::PC), R(new_pc));
+    }
 }
 
-void EmitX64::EmitCallSupervisor(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
+void EmitX64::EmitCallSupervisor(IR::Block&, IR::Inst* inst) {
+    auto imm32 = inst->GetArg(0);
 
-    auto imm32 = value->GetArg(0).get();
     reg_alloc.HostCall(nullptr, imm32);
 
     code->ABI_CallFunction(reinterpret_cast<void*>(cb.CallSVC));
 }
 
-void EmitX64::EmitGetCarryFromOp(IR::Value*) {
+void EmitX64::EmitGetCarryFromOp(IR::Block&, IR::Inst*) {
     ASSERT_MSG(0, "should never happen");
 }
 
-void EmitX64::EmitGetOverflowFromOp(IR::Value*) {
+void EmitX64::EmitGetOverflowFromOp(IR::Block&, IR::Inst*) {
     ASSERT_MSG(0, "should never happen");
 }
 
-void EmitX64::EmitLeastSignificantHalf(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
+void EmitX64::EmitLeastSignificantHalf(IR::Block&, IR::Inst* inst) {
     // TODO: Optimize
 
-    reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
+    reg_alloc.UseDefRegister(inst->GetArg(0), inst);
 }
 
-void EmitX64::EmitLeastSignificantByte(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
+void EmitX64::EmitLeastSignificantByte(IR::Block&, IR::Inst* inst) {
     // TODO: Optimize
 
-    reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
+    reg_alloc.UseDefRegister(inst->GetArg(0), inst);
 }
 
-void EmitX64::EmitMostSignificantBit(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
-    X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
+void EmitX64::EmitMostSignificantBit(IR::Block&, IR::Inst* inst) {
+    X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
 
     // TODO: Flag optimization
 
     code->SHR(32, R(result), Imm8(31));
 }
 
-void EmitX64::EmitIsZero(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
-    X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
+void EmitX64::EmitIsZero(IR::Block&, IR::Inst* inst) {
+    X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
 
     // TODO: Flag optimization
 
@@ -302,427 +296,577 @@ void EmitX64::EmitIsZero(IR::Value* value_) {
     code->MOVZX(32, 8, result, R(result));
 }
 
-void EmitX64::EmitLogicalShiftLeft(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-    auto carry_inst = FindUseWithOpcode(value, IR::Opcode::GetCarryFromOp);
+void EmitX64::EmitLogicalShiftLeft(IR::Block& block, IR::Inst* inst) {
+    auto carry_inst = FindUseWithOpcode(inst, IR::Opcode::GetCarryFromOp);
 
     // TODO: Consider using BMI2 instructions like SHLX when arm-in-host flags is implemented.
 
     if (!carry_inst) {
-        X64Reg shift = reg_alloc.UseRegister(value->GetArg(1).get(), {HostLoc::RCX});
-        X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
-        X64Reg zero = reg_alloc.ScratchRegister();
-        reg_alloc.DecrementRemainingUses(value->GetArg(2).get());
+        if (!inst->GetArg(2).IsImmediate()) {
+            // TODO: Remove redundant argument.
+            reg_alloc.DecrementRemainingUses(inst->GetArg(2).GetInst());
+        }
 
-        // The 32-bit x64 SHL instruction masks the shift count by 0x1F before performing the shift.
-        // ARM differs from the behaviour: It does not mask the count, so shifts above 31 result in zeros.
+        auto shift_arg = inst->GetArg(1);
 
-        code->SHL(32, R(result), R(shift));
-        code->XOR(32, R(zero), R(zero));
-        code->CMP(8, R(shift), Imm8(32));
-        code->CMOVcc(32, result, R(zero), CC_NB);
+        if (shift_arg.IsImmediate()) {
+            X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
+            u8 shift = shift_arg.GetU8();
+
+            if (shift <= 31) {
+                code->SHL(32, R(result), Imm8(shift));
+            } else {
+                code->XOR(32, R(result), R(result));
+            }
+        } else {
+            X64Reg shift = reg_alloc.UseRegister(shift_arg.GetInst(), {HostLoc::RCX});
+            X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
+            X64Reg zero = reg_alloc.ScratchRegister();
+
+            // The 32-bit x64 SHL instruction masks the shift count by 0x1F before performing the shift.
+            // ARM differs from the behaviour: It does not mask the count, so shifts above 31 result in zeros.
+
+            code->SHL(32, R(result), R(shift));
+            code->XOR(32, R(zero), R(zero));
+            code->CMP(8, R(shift), Imm8(32));
+            code->CMOVcc(32, result, R(zero), CC_NB);
+        }
     } else {
-        inhibit_emission.insert(carry_inst);
+        EraseInstruction(block, carry_inst);
+        reg_alloc.DecrementRemainingUses(inst);
 
-        X64Reg shift = reg_alloc.UseRegister(value->GetArg(1).get(), {HostLoc::RCX});
-        X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
-        X64Reg carry = reg_alloc.UseDefRegister(value->GetArg(2).get(), carry_inst);
+        auto shift_arg = inst->GetArg(1);
 
-        reg_alloc.DecrementRemainingUses(value);
+        if (shift_arg.IsImmediate()) {
+            u8 shift = shift_arg.GetU8();
+            X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
+            X64Reg carry = reg_alloc.UseDefRegister(inst->GetArg(2), carry_inst);
 
-        // TODO: Optimize this.
+            if (shift == 0) {
+                // There is nothing more to do.
+            } else if (shift < 32) {
+                code->BT(32, R(carry), Imm8(0));
+                code->SHL(32, R(result), Imm8(shift));
+                code->SETcc(CC_C, R(carry));
+            } else if (shift > 32) {
+                code->XOR(32, R(result), R(result));
+                code->XOR(32, R(carry), R(carry));
+            } else {
+                code->MOV(32, R(carry), R(result));
+                code->XOR(32, R(result), R(result));
+                code->AND(32, R(carry), Imm32(1));
+            }
+        } else {
+            X64Reg shift = reg_alloc.UseRegister(shift_arg.GetInst(), {HostLoc::RCX});
+            X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
+            X64Reg carry = reg_alloc.UseDefRegister(inst->GetArg(2), carry_inst);
 
-        code->CMP(8, R(shift), Imm8(32));
-        auto Rs_gt32 = code->J_CC(CC_A);
-        auto Rs_eq32 = code->J_CC(CC_E);
-        // if (Rs & 0xFF < 32) {
-        code->BT(32, R(carry), Imm8(0)); // Set the carry flag for correct behaviour in the case when Rs & 0xFF == 0
-        code->SHL(32, R(result), R(shift));
-        code->SETcc(CC_C, R(carry));
-        auto jmp_to_end_1 = code->J();
-        // } else if (Rs & 0xFF > 32) {
-        code->SetJumpTarget(Rs_gt32);
-        code->XOR(32, R(result), R(result));
-        code->XOR(32, R(carry), R(carry));
-        auto jmp_to_end_2 = code->J();
-        // } else if (Rs & 0xFF == 32) {
-        code->SetJumpTarget(Rs_eq32);
-        code->MOV(32, R(carry), R(result));
-        code->AND(32, R(carry), Imm8(1));
-        code->XOR(32, R(result), R(result));
-        // }
-        code->SetJumpTarget(jmp_to_end_1);
-        code->SetJumpTarget(jmp_to_end_2);
+            // TODO: Optimize this.
+
+            code->CMP(8, R(shift), Imm8(32));
+            auto Rs_gt32 = code->J_CC(CC_A);
+            auto Rs_eq32 = code->J_CC(CC_E);
+            // if (Rs & 0xFF < 32) {
+            code->BT(32, R(carry), Imm8(0)); // Set the carry flag for correct behaviour in the case when Rs & 0xFF == 0
+            code->SHL(32, R(result), R(shift));
+            code->SETcc(CC_C, R(carry));
+            auto jmp_to_end_1 = code->J();
+            // } else if (Rs & 0xFF > 32) {
+            code->SetJumpTarget(Rs_gt32);
+            code->XOR(32, R(result), R(result));
+            code->XOR(32, R(carry), R(carry));
+            auto jmp_to_end_2 = code->J();
+            // } else if (Rs & 0xFF == 32) {
+            code->SetJumpTarget(Rs_eq32);
+            code->MOV(32, R(carry), R(result));
+            code->AND(32, R(carry), Imm8(1));
+            code->XOR(32, R(result), R(result));
+            // }
+            code->SetJumpTarget(jmp_to_end_1);
+            code->SetJumpTarget(jmp_to_end_2);
+        }
     }
 }
 
-void EmitX64::EmitLogicalShiftRight(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-    auto carry_inst = FindUseWithOpcode(value, IR::Opcode::GetCarryFromOp);
+void EmitX64::EmitLogicalShiftRight(IR::Block& block, IR::Inst* inst) {
+    auto carry_inst = FindUseWithOpcode(inst, IR::Opcode::GetCarryFromOp);
 
     if (!carry_inst) {
-        X64Reg shift = reg_alloc.UseRegister(value->GetArg(1).get(), {HostLoc::RCX});
-        X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
-        X64Reg zero = reg_alloc.ScratchRegister();
-        reg_alloc.DecrementRemainingUses(value->GetArg(2).get());
+        if (!inst->GetArg(2).IsImmediate()) {
+            // TODO: Remove redundant argument.
+            reg_alloc.DecrementRemainingUses(inst->GetArg(2).GetInst());
+        }
 
-        // The 32-bit x64 SHR instruction masks the shift count by 0x1F before performing the shift.
-        // ARM differs from the behaviour: It does not mask the count, so shifts above 31 result in zeros.
+        auto shift_arg = inst->GetArg(1);
 
-        code->SHR(32, R(result), R(shift));
-        code->XOR(32, R(zero), R(zero));
-        code->CMP(8, R(shift), Imm8(32));
-        code->CMOVcc(32, result, R(zero), CC_NB);
+        if (shift_arg.IsImmediate()) {
+            X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
+            u8 shift = shift_arg.GetU8();
+
+            if (shift <= 31) {
+                code->SHR(32, R(result), Imm8(shift));
+            } else {
+                code->XOR(32, R(result), R(result));
+            }
+        } else {
+            X64Reg shift = reg_alloc.UseRegister(shift_arg.GetInst(), {HostLoc::RCX});
+            X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
+            X64Reg zero = reg_alloc.ScratchRegister();
+
+            // The 32-bit x64 SHR instruction masks the shift count by 0x1F before performing the shift.
+            // ARM differs from the behaviour: It does not mask the count, so shifts above 31 result in zeros.
+
+            code->SHR(32, R(result), R(shift));
+            code->XOR(32, R(zero), R(zero));
+            code->CMP(8, R(shift), Imm8(32));
+            code->CMOVcc(32, result, R(zero), CC_NB);
+        }
     } else {
-        inhibit_emission.insert(carry_inst);
+        EraseInstruction(block, carry_inst);
+        reg_alloc.DecrementRemainingUses(inst);
 
-        X64Reg shift = reg_alloc.UseRegister(value->GetArg(1).get(), {HostLoc::RCX});
-        X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
-        X64Reg carry = reg_alloc.UseDefRegister(value->GetArg(2).get(), carry_inst);
+        auto shift_arg = inst->GetArg(1);
 
-        reg_alloc.DecrementRemainingUses(value);
+        if (shift_arg.IsImmediate()) {
+            u8 shift = shift_arg.GetU8();
+            X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
+            X64Reg carry = reg_alloc.UseDefRegister(inst->GetArg(2), carry_inst);
 
-        // TODO: Optimize this.
+            if (shift == 0) {
+                // There is nothing more to do.
+            } else if (shift < 32) {
+                code->SHR(32, R(result), Imm8(shift));
+                code->SETcc(CC_C, R(carry));
+            } else if (shift == 32) {
+                code->BT(32, R(result), Imm8(31));
+                code->SETcc(CC_C, R(carry));
+                code->MOV(32, R(result), Imm32(0));
+            } else {
+                code->XOR(32, R(result), R(result));
+                code->XOR(32, R(carry), R(carry));
+            }
+        } else {
+            X64Reg shift = reg_alloc.UseRegister(shift_arg.GetInst(), {HostLoc::RCX});
+            X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
+            X64Reg carry = reg_alloc.UseDefRegister(inst->GetArg(2), carry_inst);
 
-        code->CMP(8, R(shift), Imm8(32));
-        auto Rs_gt32 = code->J_CC(CC_A);
-        auto Rs_eq32 = code->J_CC(CC_E);
-        // if (Rs & 0xFF == 0) goto end;
-        code->TEST(8, R(shift), R(shift));
-        auto Rs_zero = code->J_CC(CC_Z);
-        // if (Rs & 0xFF < 32) {
-        code->SHR(32, R(result), R(shift));
-        code->SETcc(CC_C, R(carry));
-        auto jmp_to_end_1 = code->J();
-        // } else if (Rs & 0xFF > 32) {
-        code->SetJumpTarget(Rs_gt32);
-        code->MOV(32, R(result), Imm32(0));
-        code->MOV(8, R(carry), Imm8(0));
-        auto jmp_to_end_2 = code->J();
-        // } else if (Rs & 0xFF == 32) {
-        code->SetJumpTarget(Rs_eq32);
-        code->BT(32, R(result), Imm8(31));
-        code->SETcc(CC_C, R(carry));
-        code->MOV(32, R(result), Imm32(0));
-        // }
-        code->SetJumpTarget(jmp_to_end_1);
-        code->SetJumpTarget(jmp_to_end_2);
-        code->SetJumpTarget(Rs_zero);
+            // TODO: Optimize this.
+
+            code->CMP(8, R(shift), Imm8(32));
+            auto Rs_gt32 = code->J_CC(CC_A);
+            auto Rs_eq32 = code->J_CC(CC_E);
+            // if (Rs & 0xFF == 0) goto end;
+            code->TEST(8, R(shift), R(shift));
+            auto Rs_zero = code->J_CC(CC_Z);
+            // if (Rs & 0xFF < 32) {
+            code->SHR(32, R(result), R(shift));
+            code->SETcc(CC_C, R(carry));
+            auto jmp_to_end_1 = code->J();
+            // } else if (Rs & 0xFF > 32) {
+            code->SetJumpTarget(Rs_gt32);
+            code->XOR(32, R(result), R(result));
+            code->XOR(32, R(carry), R(carry));
+            auto jmp_to_end_2 = code->J();
+            // } else if (Rs & 0xFF == 32) {
+            code->SetJumpTarget(Rs_eq32);
+            code->BT(32, R(result), Imm8(31));
+            code->SETcc(CC_C, R(carry));
+            code->MOV(32, R(result), Imm32(0));
+            // }
+            code->SetJumpTarget(jmp_to_end_1);
+            code->SetJumpTarget(jmp_to_end_2);
+            code->SetJumpTarget(Rs_zero);
+        }
     }
 }
 
-void EmitX64::EmitArithmeticShiftRight(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-    auto carry_inst = FindUseWithOpcode(value, IR::Opcode::GetCarryFromOp);
+void EmitX64::EmitArithmeticShiftRight(IR::Block& block, IR::Inst* inst) {
+    auto carry_inst = FindUseWithOpcode(inst, IR::Opcode::GetCarryFromOp);
 
     if (!carry_inst) {
-        X64Reg shift = reg_alloc.UseScratchRegister(value->GetArg(1).get(), {HostLoc::RCX});
-        X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
-        X64Reg const31 = reg_alloc.ScratchRegister();
-        reg_alloc.DecrementRemainingUses(value->GetArg(2).get());
+        if (!inst->GetArg(2).IsImmediate()) {
+            // TODO: Remove redundant argument.
+            reg_alloc.DecrementRemainingUses(inst->GetArg(2).GetInst());
+        }
 
-        // The 32-bit x64 SAR instruction masks the shift count by 0x1F before performing the shift.
-        // ARM differs from the behaviour: It does not mask the count.
+        auto shift_arg = inst->GetArg(1);
 
-        // We note that all shift values above 31 have the same behaviour as 31 does, so we saturate `shift` to 31.
-        code->MOV(32, R(const31), Imm32(31));
-        code->MOVZX(32, 8, shift, R(shift));
-        code->CMP(32, R(shift), Imm32(31));
-        code->CMOVcc(32, shift, R(const31), CC_G);
-        code->SAR(32, R(result), R(shift));
+        if (shift_arg.IsImmediate()) {
+            u32 shift = shift_arg.GetU8();
+            X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
+
+            code->SAR(32, R(result), Imm32(shift < 31 ? shift : 31));
+        } else {
+            X64Reg shift = reg_alloc.UseScratchRegister(shift_arg.GetInst(), {HostLoc::RCX});
+            X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
+            X64Reg const31 = reg_alloc.ScratchRegister();
+
+            // The 32-bit x64 SAR instruction masks the shift count by 0x1F before performing the shift.
+            // ARM differs from the behaviour: It does not mask the count.
+
+            // We note that all shift values above 31 have the same behaviour as 31 does, so we saturate `shift` to 31.
+            code->MOV(32, R(const31), Imm32(31));
+            code->MOVZX(32, 8, shift, R(shift));
+            code->CMP(32, R(shift), Imm32(31));
+            code->CMOVcc(32, shift, R(const31), CC_G);
+            code->SAR(32, R(result), R(shift));
+        }
     } else {
-        inhibit_emission.insert(carry_inst);
+        EraseInstruction(block, carry_inst);
+        reg_alloc.DecrementRemainingUses(inst);
 
-        X64Reg shift = reg_alloc.UseRegister(value->GetArg(1).get(), {HostLoc::RCX});
-        X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
-        X64Reg carry = reg_alloc.UseDefRegister(value->GetArg(2).get(), carry_inst);
+        auto shift_arg = inst->GetArg(1);
 
-        reg_alloc.DecrementRemainingUses(value);
+        if (shift_arg.IsImmediate()) {
+            u8 shift = shift_arg.GetU8();
+            X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
+            X64Reg carry = reg_alloc.UseDefRegister(inst->GetArg(2), carry_inst);
 
-        // TODO: Optimize this.
+            if (shift == 0) {
+                // There is nothing more to do.
+            } else if (shift <= 31) {
+                code->SAR(32, R(result), Imm8(shift));
+                code->SETcc(CC_C, R(carry));
+            } else {
+                code->SAR(32, R(result), Imm8(31));
+                code->BT(32, R(result), Imm8(31));
+                code->SETcc(CC_C, R(carry));
+            }
+        } else {
+            X64Reg shift = reg_alloc.UseRegister(shift_arg.GetInst(), {HostLoc::RCX});
+            X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
+            X64Reg carry = reg_alloc.UseDefRegister(inst->GetArg(2), carry_inst);
 
-        code->CMP(8, R(shift), Imm8(31));
-        auto Rs_gt31 = code->J_CC(CC_A);
-        // if (Rs & 0xFF == 0) goto end;
-        code->TEST(8, R(shift), R(shift));
-        auto Rs_zero = code->J_CC(CC_Z);
-        // if (Rs & 0xFF <= 31) {
-        code->SAR(32, R(result), R(CL));
-        code->SETcc(CC_C, R(carry));
-        auto jmp_to_end = code->J();
-        // } else if (Rs & 0xFF > 31) {
-        code->SetJumpTarget(Rs_gt31);
-        code->SAR(32, R(result), Imm8(31)); // Verified.
-        code->BT(32, R(result), Imm8(31));
-        code->SETcc(CC_C, R(carry));
-        // }
-        code->SetJumpTarget(jmp_to_end);
-        code->SetJumpTarget(Rs_zero);
+            // TODO: Optimize this.
+
+            code->CMP(8, R(shift), Imm8(31));
+            auto Rs_gt31 = code->J_CC(CC_A);
+            // if (Rs & 0xFF == 0) goto end;
+            code->TEST(8, R(shift), R(shift));
+            auto Rs_zero = code->J_CC(CC_Z);
+            // if (Rs & 0xFF <= 31) {
+            code->SAR(32, R(result), R(shift));
+            code->SETcc(CC_C, R(carry));
+            auto jmp_to_end = code->J();
+            // } else if (Rs & 0xFF > 31) {
+            code->SetJumpTarget(Rs_gt31);
+            code->SAR(32, R(result), Imm8(31)); // Verified.
+            code->BT(32, R(result), Imm8(31));
+            code->SETcc(CC_C, R(carry));
+            // }
+            code->SetJumpTarget(jmp_to_end);
+            code->SetJumpTarget(Rs_zero);
+        }
     }
 }
 
-void EmitX64::EmitRotateRight(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-    auto carry_inst = FindUseWithOpcode(value, IR::Opcode::GetCarryFromOp);
+void EmitX64::EmitRotateRight(IR::Block& block, IR::Inst* inst) {
+    auto carry_inst = FindUseWithOpcode(inst, IR::Opcode::GetCarryFromOp);
 
     if (!carry_inst) {
-        X64Reg shift = reg_alloc.UseRegister(value->GetArg(1).get(), {HostLoc::RCX});
-        X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
-        reg_alloc.DecrementRemainingUses(value->GetArg(2).get());
+        if (!inst->GetArg(2).IsImmediate()) {
+            // TODO: Remove redundant argument.
+            reg_alloc.DecrementRemainingUses(inst->GetArg(2).GetInst());
+        }
 
-        // x64 ROR instruction does (shift & 0x1F) for us.
-        code->ROR(32, R(result), R(shift));
+        auto shift_arg = inst->GetArg(1);
+
+        if (shift_arg.IsImmediate()) {
+            u8 shift = shift_arg.GetU8();
+            X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
+
+            code->ROR(32, R(result), Imm8(shift & 0x1F));
+        } else {
+            X64Reg shift = reg_alloc.UseRegister(shift_arg.GetInst(), {HostLoc::RCX});
+            X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
+
+            // x64 ROR instruction does (shift & 0x1F) for us.
+            code->ROR(32, R(result), R(shift));
+        }
     } else {
-        inhibit_emission.insert(carry_inst);
+        EraseInstruction(block, carry_inst);
+        reg_alloc.DecrementRemainingUses(inst);
 
-        X64Reg shift = reg_alloc.UseScratchRegister(value->GetArg(1).get(), {HostLoc::RCX});
-        X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
-        X64Reg carry = reg_alloc.UseDefRegister(value->GetArg(2).get(), carry_inst);
+        auto shift_arg = inst->GetArg(1);
 
-        reg_alloc.DecrementRemainingUses(value);
+        if (shift_arg.IsImmediate()) {
+            u8 shift = shift_arg.GetU8();
+            X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
+            X64Reg carry = reg_alloc.UseDefRegister(inst->GetArg(2), carry_inst);
 
-        // TODO: Optimize
+            if (shift == 0) {
+                // There is nothing more to do.
+            } else if ((shift & 0x1F) == 0) {
+                code->BT(32, R(result), Imm8(31));
+                code->SETcc(CC_C, R(carry));
+            } else {
+                code->ROR(32, R(result), Imm8(shift));
+                code->SETcc(CC_C, R(carry));
+            }
+        } else {
+            X64Reg shift = reg_alloc.UseScratchRegister(shift_arg.GetInst(), {HostLoc::RCX});
+            X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
+            X64Reg carry = reg_alloc.UseDefRegister(inst->GetArg(2), carry_inst);
 
-        // if (Rs & 0xFF == 0) goto end;
-        code->TEST(8, R(shift), R(shift));
-        auto Rs_zero = code->J_CC(CC_Z);
+            // TODO: Optimize
 
-        code->AND(32, R(shift), Imm8(0x1F));
-        auto zero_1F = code->J_CC(CC_Z);
-        // if (Rs & 0x1F != 0) {
-        code->ROR(32, R(result), R(shift));
-        code->SETcc(CC_C, R(carry));
-        auto jmp_to_end = code->J();
-        // } else {
-        code->SetJumpTarget(zero_1F);
-        code->BT(32, R(result), Imm8(31));
-        code->SETcc(CC_C, R(carry));
-        // }
-        code->SetJumpTarget(jmp_to_end);
-        code->SetJumpTarget(Rs_zero);
+            // if (Rs & 0xFF == 0) goto end;
+            code->TEST(8, R(shift), R(shift));
+            auto Rs_zero = code->J_CC(CC_Z);
+
+            code->AND(32, R(shift), Imm8(0x1F));
+            auto zero_1F = code->J_CC(CC_Z);
+            // if (Rs & 0x1F != 0) {
+            code->ROR(32, R(result), R(shift));
+            code->SETcc(CC_C, R(carry));
+            auto jmp_to_end = code->J();
+            // } else {
+            code->SetJumpTarget(zero_1F);
+            code->BT(32, R(result), Imm8(31));
+            code->SETcc(CC_C, R(carry));
+            // }
+            code->SetJumpTarget(jmp_to_end);
+            code->SetJumpTarget(Rs_zero);
+        }
     }
 }
 
-void EmitX64::EmitAddWithCarry(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-    auto carry_inst = FindUseWithOpcode(value, IR::Opcode::GetCarryFromOp);
-    auto overflow_inst = FindUseWithOpcode(value, IR::Opcode::GetOverflowFromOp);
+static X64Reg DoCarry(RegAlloc& reg_alloc, const IR::Value& carry_in, IR::Inst* carry_out) {
+    if (carry_in.IsImmediate()) {
+        return carry_out ? reg_alloc.DefRegister(carry_out) : INVALID_REG;
+    } else {
+        IR::Inst* in = carry_in.GetInst();
+        return carry_out ? reg_alloc.UseDefRegister(in, carry_out) : reg_alloc.UseRegister(in);
+    }
+}
 
-    X64Reg addend = reg_alloc.UseRegister(value->GetArg(1).get());
-    X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
-    X64Reg carry = carry_inst
-                   ? reg_alloc.UseDefRegister(value->GetArg(2).get(), carry_inst)
-                   : reg_alloc.UseRegister(value->GetArg(2).get());
-    X64Reg overflow = overflow_inst
-                      ? reg_alloc.DefRegister(overflow_inst)
-                      : X64Reg::INVALID_REG;
+void EmitX64::EmitAddWithCarry(IR::Block& block, IR::Inst* inst) {
+    auto carry_inst = FindUseWithOpcode(inst, IR::Opcode::GetCarryFromOp);
+    auto overflow_inst = FindUseWithOpcode(inst, IR::Opcode::GetOverflowFromOp);
+
+    IR::Value a = inst->GetArg(0);
+    IR::Value b = inst->GetArg(1);
+    IR::Value carry_in = inst->GetArg(2);
+
+    X64Reg result = reg_alloc.UseDefRegister(a, inst);
+    X64Reg carry = DoCarry(reg_alloc, carry_in, carry_inst);
+    X64Reg overflow = overflow_inst ? reg_alloc.DefRegister(overflow_inst) : INVALID_REG;
 
     // TODO: Consider using LEA.
 
-    code->BT(32, R(carry), Imm8(0)); // Sets x64 CF appropriately.
-    code->ADC(32, R(result), R(addend));
+    OpArg op_arg = b.IsImmediate()
+                    ? Imm32(b.GetU32())
+                    : R(reg_alloc.UseRegister(b.GetInst()));
+
+    if (carry_in.IsImmediate()) {
+        if (carry_in.GetU1()) {
+            code->STC();
+            code->ADC(32, R(result), op_arg);
+        } else {
+            code->ADD(32, R(result), op_arg);
+        }
+    } else {
+        code->BT(32, R(carry), Imm8(0));
+        code->ADC(32, R(result), op_arg);
+    }
 
     if (carry_inst) {
-        inhibit_emission.insert(carry_inst);
-        reg_alloc.DecrementRemainingUses(value);
+        EraseInstruction(block, carry_inst);
+        reg_alloc.DecrementRemainingUses(inst);
         code->SETcc(Gen::CC_C, R(carry));
     }
     if (overflow_inst) {
-        inhibit_emission.insert(overflow_inst);
-        reg_alloc.DecrementRemainingUses(value);
+        EraseInstruction(block, overflow_inst);
+        reg_alloc.DecrementRemainingUses(inst);
         code->SETcc(Gen::CC_O, R(overflow));
     }
 }
 
-void EmitX64::EmitSubWithCarry(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-    auto carry_inst = FindUseWithOpcode(value, IR::Opcode::GetCarryFromOp);
-    auto overflow_inst = FindUseWithOpcode(value, IR::Opcode::GetOverflowFromOp);
+void EmitX64::EmitSubWithCarry(IR::Block& block, IR::Inst* inst) {
+    auto carry_inst = FindUseWithOpcode(inst, IR::Opcode::GetCarryFromOp);
+    auto overflow_inst = FindUseWithOpcode(inst, IR::Opcode::GetOverflowFromOp);
 
-    X64Reg addend = reg_alloc.UseRegister(value->GetArg(1).get());
-    X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
-    X64Reg carry = carry_inst
-                   ? reg_alloc.UseDefRegister(value->GetArg(2).get(), carry_inst)
-                   : reg_alloc.UseRegister(value->GetArg(2).get());
-    X64Reg overflow = overflow_inst
-                      ? reg_alloc.DefRegister(overflow_inst)
-                      : X64Reg::INVALID_REG;
+    IR::Value a = inst->GetArg(0);
+    IR::Value b = inst->GetArg(1);
+    IR::Value carry_in = inst->GetArg(2);
+
+    X64Reg result = reg_alloc.UseDefRegister(a, inst);
+    X64Reg carry = DoCarry(reg_alloc, carry_in, carry_inst);
+    X64Reg overflow = overflow_inst ? reg_alloc.DefRegister(overflow_inst) : INVALID_REG;
 
     // TODO: Consider using LEA.
-    // TODO: Optimize case when result isn't used but flags are (use a CMP instruction instead).
+    // TODO: Optimize CMP case.
     // Note that x64 CF is inverse of what the ARM carry flag is here.
 
-    code->BT(32, R(carry), Imm8(0));
-    code->CMC();
-    code->SBB(32, R(result), R(addend));
+    OpArg op_arg = b.IsImmediate()
+                   ? Imm32(b.GetU32())
+                   : R(reg_alloc.UseRegister(b.GetInst()));
+
+    if (carry_in.IsImmediate()) {
+        if (carry_in.GetU1()) {
+            code->SUB(32, R(result), op_arg);
+        } else {
+            code->STC();
+            code->SBB(32, R(result), op_arg);
+        }
+    } else {
+        code->BT(32, R(carry), Imm8(0));
+        code->CMC();
+        code->SBB(32, R(result), op_arg);
+    }
 
     if (carry_inst) {
-        inhibit_emission.insert(carry_inst);
-        reg_alloc.DecrementRemainingUses(value);
+        EraseInstruction(block, carry_inst);
+        reg_alloc.DecrementRemainingUses(inst);
         code->SETcc(Gen::CC_NC, R(carry));
     }
     if (overflow_inst) {
-        inhibit_emission.insert(overflow_inst);
-        reg_alloc.DecrementRemainingUses(value);
+        EraseInstruction(block, overflow_inst);
+        reg_alloc.DecrementRemainingUses(inst);
         code->SETcc(Gen::CC_O, R(overflow));
     }
 }
 
-void EmitX64::EmitAnd(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
+void EmitX64::EmitAnd(IR::Block&, IR::Inst* inst) {
+    IR::Value a = inst->GetArg(0);
+    IR::Value b = inst->GetArg(1);
 
-    X64Reg andend = reg_alloc.UseRegister(value->GetArg(1).get());
-    X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
+    X64Reg result = reg_alloc.UseDefRegister(a, inst);
+    OpArg op_arg = b.IsImmediate()
+                   ? Imm32(b.GetU32())
+                   : R(reg_alloc.UseRegister(b.GetInst()));
 
-    code->AND(32, R(result), R(andend));
+    code->AND(32, R(result), op_arg);
 }
 
-void EmitX64::EmitEor(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
+void EmitX64::EmitEor(IR::Block&, IR::Inst* inst) {
+    IR::Value a = inst->GetArg(0);
+    IR::Value b = inst->GetArg(1);
 
-    X64Reg eorend = reg_alloc.UseRegister(value->GetArg(1).get());
-    X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
+    X64Reg result = reg_alloc.UseDefRegister(a, inst);
+    OpArg op_arg = b.IsImmediate()
+                   ? Imm32(b.GetU32())
+                   : R(reg_alloc.UseRegister(b.GetInst()));
 
-    code->XOR(32, R(result), R(eorend));
+    code->XOR(32, R(result), op_arg);
 }
 
-void EmitX64::EmitOr(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
+void EmitX64::EmitOr(IR::Block&, IR::Inst* inst) {
+    IR::Value a = inst->GetArg(0);
+    IR::Value b = inst->GetArg(1);
 
-    X64Reg orend = reg_alloc.UseRegister(value->GetArg(1).get());
-    X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
+    X64Reg result = reg_alloc.UseDefRegister(a, inst);
+    OpArg op_arg = b.IsImmediate()
+                   ? Imm32(b.GetU32())
+                   : R(reg_alloc.UseRegister(b.GetInst()));
 
-    code->OR(32, R(result), R(orend));
+    code->OR(32, R(result), op_arg);
 }
 
-void EmitX64::EmitNot(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
+void EmitX64::EmitNot(IR::Block&, IR::Inst* inst) {
+    IR::Value a = inst->GetArg(0);
 
-    X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
+    if (a.IsImmediate()) {
+        X64Reg result = reg_alloc.DefRegister(inst);
 
-    code->NOT(32, R(result));
+        code->MOV(32, R(result), Imm32(~a.GetU32()));
+    } else {
+        X64Reg result = reg_alloc.UseDefRegister(a.GetInst(), inst);
+
+        code->NOT(32, R(result));
+    }
 }
 
-void EmitX64::EmitSignExtendHalfToWord(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
+void EmitX64::EmitSignExtendHalfToWord(IR::Block&, IR::Inst* inst) {
     // TODO: Remove unnecessary mov that may occur here
-    X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
+    X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
 
     code->MOVSX(32, 16, result, R(result));
 }
 
-void EmitX64::EmitSignExtendByteToWord(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
+void EmitX64::EmitSignExtendByteToWord(IR::Block&, IR::Inst* inst) {
     // TODO: Remove unnecessary mov that may occur here
-    X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
+    X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
 
     code->MOVSX(32, 8, result, R(result));
 }
 
-void EmitX64::EmitZeroExtendHalfToWord(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
+void EmitX64::EmitZeroExtendHalfToWord(IR::Block&, IR::Inst* inst) {
     // TODO: Remove unnecessary mov that may occur here
-    X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
+    X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
 
     code->MOVZX(32, 16, result, R(result));
 }
 
-void EmitX64::EmitZeroExtendByteToWord(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
+void EmitX64::EmitZeroExtendByteToWord(IR::Block&, IR::Inst* inst) {
     // TODO: Remove unnecessary mov that may occur here
-    X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
+    X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
 
     code->MOVZX(32, 8, result, R(result));
 }
 
-void EmitX64::EmitByteReverseWord(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
-    X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
+void EmitX64::EmitByteReverseWord(IR::Block&, IR::Inst* inst) {
+    X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
 
     code->BSWAP(32, result);
 }
 
-void EmitX64::EmitByteReverseHalf(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
-    X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
+void EmitX64::EmitByteReverseHalf(IR::Block&, IR::Inst* inst) {
+    X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
 
     code->ROL(16, R(result), Imm8(8));
 }
 
-void EmitX64::EmitByteReverseDual(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
-    X64Reg result = reg_alloc.UseDefRegister(value->GetArg(0).get(), value);
+void EmitX64::EmitByteReverseDual(IR::Block&, IR::Inst* inst) {
+    X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst);
 
     code->BSWAP(64, result);
 }
 
-void EmitX64::EmitReadMemory8(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
-    reg_alloc.HostCall(value, value->GetArg(0).get());
+void EmitX64::EmitReadMemory8(IR::Block&, IR::Inst* inst) {
+    reg_alloc.HostCall(inst, inst->GetArg(0));
 
     code->ABI_CallFunction(reinterpret_cast<void*>(cb.MemoryRead8));
 }
 
-void EmitX64::EmitReadMemory16(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
-    reg_alloc.HostCall(value, value->GetArg(0).get());
+void EmitX64::EmitReadMemory16(IR::Block&, IR::Inst* inst) {
+    reg_alloc.HostCall(inst, inst->GetArg(0));
 
     code->ABI_CallFunction(reinterpret_cast<void*>(cb.MemoryRead16));
 }
 
-void EmitX64::EmitReadMemory32(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
-    reg_alloc.HostCall(value, value->GetArg(0).get());
+void EmitX64::EmitReadMemory32(IR::Block&, IR::Inst* inst) {
+    reg_alloc.HostCall(inst, inst->GetArg(0));
 
     code->ABI_CallFunction(reinterpret_cast<void*>(cb.MemoryRead32));
 }
 
-void EmitX64::EmitReadMemory64(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
-    reg_alloc.HostCall(value, value->GetArg(0).get());
+void EmitX64::EmitReadMemory64(IR::Block&, IR::Inst* inst) {
+    reg_alloc.HostCall(inst, inst->GetArg(0));
 
     code->ABI_CallFunction(reinterpret_cast<void*>(cb.MemoryRead64));
 }
 
-void EmitX64::EmitWriteMemory8(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
-    reg_alloc.HostCall(nullptr, value->GetArg(0).get(), value->GetArg(1).get());
+void EmitX64::EmitWriteMemory8(IR::Block&, IR::Inst* inst) {
+    reg_alloc.HostCall(nullptr, inst->GetArg(0), inst->GetArg(1));
 
     code->ABI_CallFunction(reinterpret_cast<void*>(cb.MemoryWrite8));
 }
 
-void EmitX64::EmitWriteMemory16(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
-    reg_alloc.HostCall(nullptr, value->GetArg(0).get(), value->GetArg(1).get());
+void EmitX64::EmitWriteMemory16(IR::Block&, IR::Inst* inst) {
+    reg_alloc.HostCall(nullptr, inst->GetArg(0), inst->GetArg(1));
 
     code->ABI_CallFunction(reinterpret_cast<void*>(cb.MemoryWrite16));
 }
 
-void EmitX64::EmitWriteMemory32(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
-    reg_alloc.HostCall(nullptr, value->GetArg(0).get(), value->GetArg(1).get());
+void EmitX64::EmitWriteMemory32(IR::Block&, IR::Inst* inst) {
+    reg_alloc.HostCall(nullptr, inst->GetArg(0), inst->GetArg(1));
 
     code->ABI_CallFunction(reinterpret_cast<void*>(cb.MemoryWrite32));
 }
 
-void EmitX64::EmitWriteMemory64(IR::Value* value_) {
-    auto value = reinterpret_cast<IR::Inst*>(value_);
-
-    reg_alloc.HostCall(nullptr, value->GetArg(0).get(), value->GetArg(1).get());
+void EmitX64::EmitWriteMemory64(IR::Block&, IR::Inst* inst) {
+    reg_alloc.HostCall(nullptr, inst->GetArg(0), inst->GetArg(1));
 
     code->ABI_CallFunction(reinterpret_cast<void*>(cb.MemoryWrite64));
 }
diff --git a/src/backend_x64/emit_x64.h b/src/backend_x64/emit_x64.h
index b0d2d5f9..7eeb96dd 100644
--- a/src/backend_x64/emit_x64.h
+++ b/src/backend_x64/emit_x64.h
@@ -23,7 +23,7 @@ public:
     EmitX64(Gen::XEmitter* code, Routines* routines, UserCallbacks cb, Jit* jit_interface)
             : reg_alloc(code), code(code), routines(routines), cb(cb), jit_interface(jit_interface) {}
 
-    CodePtr Emit(const Arm::LocationDescriptor descriptor, const IR::Block& ir);
+    CodePtr Emit(const Arm::LocationDescriptor descriptor, IR::Block& ir);
 
     CodePtr GetBasicBlock(Arm::LocationDescriptor descriptor) {
         auto iter = basic_blocks.find(descriptor);
@@ -34,53 +34,49 @@ public:
 
 private:
     // Microinstruction emitters
-    void EmitImmU1(IR::Value* value);
-    void EmitImmU8(IR::Value* value);
-    void EmitImmU32(IR::Value* value);
-    void EmitImmRegRef(IR::Value* value);
-    void EmitGetRegister(IR::Value* value);
-    void EmitSetRegister(IR::Value* value);
-    void EmitGetNFlag(IR::Value* value);
-    void EmitSetNFlag(IR::Value* value);
-    void EmitGetZFlag(IR::Value* value);
-    void EmitSetZFlag(IR::Value* value);
-    void EmitGetCFlag(IR::Value* value);
-    void EmitSetCFlag(IR::Value* value);
-    void EmitGetVFlag(IR::Value* value);
-    void EmitSetVFlag(IR::Value* value);
-    void EmitBXWritePC(IR::Value* value);
-    void EmitCallSupervisor(IR::Value* value);
-    void EmitGetCarryFromOp(IR::Value* value);
-    void EmitGetOverflowFromOp(IR::Value* value);
-    void EmitLeastSignificantHalf(IR::Value* value);
-    void EmitLeastSignificantByte(IR::Value* value);
-    void EmitMostSignificantBit(IR::Value* value);
-    void EmitIsZero(IR::Value* value);
-    void EmitLogicalShiftLeft(IR::Value* value);
-    void EmitLogicalShiftRight(IR::Value* value);
-    void EmitArithmeticShiftRight(IR::Value* value);
-    void EmitRotateRight(IR::Value* value);
-    void EmitAddWithCarry(IR::Value* value);
-    void EmitSubWithCarry(IR::Value* value);
-    void EmitAnd(IR::Value* value);
-    void EmitEor(IR::Value* value);
-    void EmitOr(IR::Value* value);
-    void EmitNot(IR::Value* value);
-    void EmitSignExtendHalfToWord(IR::Value* value);
-    void EmitSignExtendByteToWord(IR::Value* value);
-    void EmitZeroExtendHalfToWord(IR::Value* value);
-    void EmitZeroExtendByteToWord(IR::Value* value);
-    void EmitByteReverseWord(IR::Value* value);
-    void EmitByteReverseHalf(IR::Value* value);
-    void EmitByteReverseDual(IR::Value* value);
-    void EmitReadMemory8(IR::Value* value);
-    void EmitReadMemory16(IR::Value* value);
-    void EmitReadMemory32(IR::Value* value);
-    void EmitReadMemory64(IR::Value* value);
-    void EmitWriteMemory8(IR::Value* value);
-    void EmitWriteMemory16(IR::Value* value);
-    void EmitWriteMemory32(IR::Value* value);
-    void EmitWriteMemory64(IR::Value* value);
+    void EmitGetRegister(IR::Block& block, IR::Inst* inst);
+    void EmitSetRegister(IR::Block& block, IR::Inst* inst);
+    void EmitGetNFlag(IR::Block& block, IR::Inst* inst);
+    void EmitSetNFlag(IR::Block& block, IR::Inst* inst);
+    void EmitGetZFlag(IR::Block& block, IR::Inst* inst);
+    void EmitSetZFlag(IR::Block& block, IR::Inst* inst);
+    void EmitGetCFlag(IR::Block& block, IR::Inst* inst);
+    void EmitSetCFlag(IR::Block& block, IR::Inst* inst);
+    void EmitGetVFlag(IR::Block& block, IR::Inst* inst);
+    void EmitSetVFlag(IR::Block& block, IR::Inst* inst);
+    void EmitBXWritePC(IR::Block& block, IR::Inst* inst);
+    void EmitCallSupervisor(IR::Block& block, IR::Inst* inst);
+    void EmitGetCarryFromOp(IR::Block& block, IR::Inst* inst);
+    void EmitGetOverflowFromOp(IR::Block& block, IR::Inst* inst);
+    void EmitLeastSignificantHalf(IR::Block& block, IR::Inst* inst);
+    void EmitLeastSignificantByte(IR::Block& block, IR::Inst* inst);
+    void EmitMostSignificantBit(IR::Block& block, IR::Inst* inst);
+    void EmitIsZero(IR::Block& block, IR::Inst* inst);
+    void EmitLogicalShiftLeft(IR::Block& block, IR::Inst* inst);
+    void EmitLogicalShiftRight(IR::Block& block, IR::Inst* inst);
+    void EmitArithmeticShiftRight(IR::Block& block, IR::Inst* inst);
+    void EmitRotateRight(IR::Block& block, IR::Inst* inst);
+    void EmitAddWithCarry(IR::Block& block, IR::Inst* inst);
+    void EmitSubWithCarry(IR::Block& block, IR::Inst* inst);
+    void EmitAnd(IR::Block& block, IR::Inst* inst);
+    void EmitEor(IR::Block& block, IR::Inst* inst);
+    void EmitOr(IR::Block& block, IR::Inst* inst);
+    void EmitNot(IR::Block& block, IR::Inst* inst);
+    void EmitSignExtendHalfToWord(IR::Block& block, IR::Inst* inst);
+    void EmitSignExtendByteToWord(IR::Block& block, IR::Inst* inst);
+    void EmitZeroExtendHalfToWord(IR::Block& block, IR::Inst* inst);
+    void EmitZeroExtendByteToWord(IR::Block& block, IR::Inst* inst);
+    void EmitByteReverseWord(IR::Block& block, IR::Inst* inst);
+    void EmitByteReverseHalf(IR::Block& block, IR::Inst* inst);
+    void EmitByteReverseDual(IR::Block& block, IR::Inst* inst);
+    void EmitReadMemory8(IR::Block& block, IR::Inst* inst);
+    void EmitReadMemory16(IR::Block& block, IR::Inst* inst);
+    void EmitReadMemory32(IR::Block& block, IR::Inst* inst);
+    void EmitReadMemory64(IR::Block& block, IR::Inst* inst);
+    void EmitWriteMemory8(IR::Block& block, IR::Inst* inst);
+    void EmitWriteMemory16(IR::Block& block, IR::Inst* inst);
+    void EmitWriteMemory32(IR::Block& block, IR::Inst* inst);
+    void EmitWriteMemory64(IR::Block& block, IR::Inst* inst);
 
     // Helpers
     void EmitAddCycles(size_t cycles);
diff --git a/src/backend_x64/reg_alloc.cpp b/src/backend_x64/reg_alloc.cpp
index a5659099..971d237f 100644
--- a/src/backend_x64/reg_alloc.cpp
+++ b/src/backend_x64/reg_alloc.cpp
@@ -14,36 +14,22 @@
 namespace Dynarmic {
 namespace BackendX64 {
 
-// TODO: Just turn this into a function that indexes a std::array.
-const static std::map<HostLoc, Gen::X64Reg> hostloc_to_x64 = {
-    { HostLoc::RAX, Gen::RAX },
-    { HostLoc::RBX, Gen::RBX },
-    { HostLoc::RCX, Gen::RCX },
-    { HostLoc::RDX, Gen::RDX },
-    { HostLoc::RSI, Gen::RSI },
-    { HostLoc::RDI, Gen::RDI },
-    { HostLoc::RBP, Gen::RBP },
-    { HostLoc::RSP, Gen::RSP },
-    { HostLoc::R8,  Gen::R8  },
-    { HostLoc::R9,  Gen::R9  },
-    { HostLoc::R10, Gen::R10 },
-    { HostLoc::R11, Gen::R11 },
-    { HostLoc::R12, Gen::R12 },
-    { HostLoc::R13, Gen::R13 },
-    { HostLoc::R14, Gen::R14 },
-};
+static Gen::X64Reg HostLocToX64(HostLoc loc) {
+    DEBUG_ASSERT(HostLocIsRegister(loc));
+    // HostLoc is ordered such that the numbers line up.
+    return static_cast<Gen::X64Reg>(loc);
+}
 
 static Gen::OpArg SpillToOpArg(HostLoc loc) {
-    ASSERT(HostLocIsSpill(loc));
+    DEBUG_ASSERT(HostLocIsSpill(loc));
 
     size_t i = static_cast<size_t>(loc) - static_cast<size_t>(HostLoc::FirstSpill);
     return Gen::MDisp(Gen::R15, static_cast<int>(offsetof(JitState, Spill) + i * sizeof(u32)));
 }
 
-Gen::X64Reg RegAlloc::DefRegister(IR::Value* def_value, std::initializer_list<HostLoc> desired_locations) {
-    ASSERT(std::all_of(desired_locations.begin(), desired_locations.end(), HostLocIsRegister));
-    ASSERT_MSG(remaining_uses.find(def_value) == remaining_uses.end(), "def_value has already been defined");
-    ASSERT_MSG(ValueLocations(def_value).empty(), "def_value has already been defined");
+Gen::X64Reg RegAlloc::DefRegister(IR::Inst* def_inst, std::initializer_list<HostLoc> desired_locations) {
+    DEBUG_ASSERT(std::all_of(desired_locations.begin(), desired_locations.end(), HostLocIsRegister));
+    DEBUG_ASSERT_MSG(ValueLocations(def_inst).empty(), "def_inst has already been defined");
 
     HostLoc location = SelectARegister(desired_locations);
 
@@ -52,43 +38,54 @@ Gen::X64Reg RegAlloc::DefRegister(IR::Value* def_value, std::initializer_list<Ho
     }
 
     // Update state
-    hostloc_state[location] = HostLocState::Def;
-    hostloc_to_value[location] = def_value;
-    remaining_uses[def_value] = def_value->NumUses();
+    hostloc_state[static_cast<size_t>(location)] = HostLocState::Def;
+    hostloc_to_inst[static_cast<size_t>(location)] = def_inst;
 
-    return hostloc_to_x64.at(location);
+    return HostLocToX64(location);
 }
 
-Gen::X64Reg RegAlloc::UseDefRegister(IR::Value* use_value, IR::Value* def_value, std::initializer_list<HostLoc> desired_locations) {
-    ASSERT(std::all_of(desired_locations.begin(), desired_locations.end(), HostLocIsRegister));
-    ASSERT_MSG(remaining_uses.find(def_value) == remaining_uses.end(), "def_value has already been defined");
-    ASSERT_MSG(ValueLocations(def_value).empty(), "def_value has already been defined");
-    ASSERT_MSG(remaining_uses.find(use_value) != remaining_uses.end(), "use_value has not been defined");
-    ASSERT_MSG(!ValueLocations(use_value).empty(), "use_value has not been defined");
+Gen::X64Reg RegAlloc::UseDefRegister(IR::Value use_value, IR::Inst* def_inst, std::initializer_list<HostLoc> desired_locations) {
+    if (!use_value.IsImmediate()) {
+        return UseDefRegister(use_value.GetInst(), def_inst, desired_locations);
+    }
 
-    // TODO: Optimize the case when this is the last use_value use.
-    Gen::X64Reg use_reg = UseRegister(use_value);
-    Gen::X64Reg def_reg = DefRegister(def_value, desired_locations);
+    return LoadImmediateIntoRegister(use_value, DefRegister(def_inst, desired_locations));
+}
+
+Gen::X64Reg RegAlloc::UseDefRegister(IR::Inst* use_inst, IR::Inst* def_inst, std::initializer_list<HostLoc> desired_locations) {
+    DEBUG_ASSERT(std::all_of(desired_locations.begin(), desired_locations.end(), HostLocIsRegister));
+    DEBUG_ASSERT_MSG(ValueLocations(def_inst).empty(), "def_inst has already been defined");
+    DEBUG_ASSERT_MSG(!ValueLocations(use_inst).empty(), "use_inst has not been defined");
+
+    // TODO: Optimize the case when this is the last use_inst use.
+    Gen::X64Reg use_reg = UseRegister(use_inst);
+    Gen::X64Reg def_reg = DefRegister(def_inst, desired_locations);
     code->MOV(32, Gen::R(def_reg), Gen::R(use_reg));
     return def_reg;
 }
 
-Gen::X64Reg RegAlloc::UseRegister(IR::Value* use_value, std::initializer_list<HostLoc> desired_locations) {
-    ASSERT(std::all_of(desired_locations.begin(), desired_locations.end(), HostLocIsRegister));
-    ASSERT_MSG(remaining_uses.find(use_value) != remaining_uses.end(), "use_value has not been defined");
-    ASSERT_MSG(!ValueLocations(use_value).empty(), "use_value has not been defined");
-    ASSERT_MSG(remaining_uses[use_value] != 0, "use_value ran out of uses. (Use-d an IR::Value* too many times)");
+Gen::X64Reg RegAlloc::UseRegister(IR::Value use_value, std::initializer_list<HostLoc> desired_locations) {
+    if (!use_value.IsImmediate()) {
+        return UseRegister(use_value.GetInst(), desired_locations);
+    }
 
-    HostLoc current_location = ValueLocations(use_value).front();
+    return LoadImmediateIntoRegister(use_value, ScratchRegister(desired_locations));
+}
+
+Gen::X64Reg RegAlloc::UseRegister(IR::Inst* use_inst, std::initializer_list<HostLoc> desired_locations) {
+    DEBUG_ASSERT(std::all_of(desired_locations.begin(), desired_locations.end(), HostLocIsRegister));
+    DEBUG_ASSERT_MSG(!ValueLocations(use_inst).empty(), "use_inst has not been defined");
+
+    HostLoc current_location = ValueLocations(use_inst).front();
     auto iter = std::find(desired_locations.begin(), desired_locations.end(), current_location);
     if (iter != desired_locations.end()) {
-        ASSERT(hostloc_state[current_location] == HostLocState::Idle || hostloc_state[current_location] == HostLocState::Use);
+        ASSERT(hostloc_state[static_cast<size_t>(current_location)] == HostLocState::Idle || hostloc_state[static_cast<size_t>(current_location)] == HostLocState::Use);
 
         // Update state
-        hostloc_state[current_location] = HostLocState::Use;
-        remaining_uses[use_value]--;
+        hostloc_state[static_cast<size_t>(current_location)] = HostLocState::Use;
+        DecrementRemainingUses(use_inst);
 
-        return hostloc_to_x64.at(current_location);
+        return HostLocToX64(current_location);
     }
 
     HostLoc new_location = SelectARegister(desired_locations);
@@ -98,33 +95,40 @@ Gen::X64Reg RegAlloc::UseRegister(IR::Value* use_value, std::initializer_list<Ho
             SpillRegister(new_location);
         }
 
-        code->MOV(32, Gen::R(hostloc_to_x64.at(new_location)), SpillToOpArg(current_location));
+        code->MOV(32, Gen::R(HostLocToX64(new_location)), SpillToOpArg(current_location));
 
-        hostloc_state[new_location] = HostLocState::Use;
-        std::swap(hostloc_to_value[new_location], hostloc_to_value[current_location]);
-        remaining_uses[use_value]--;
+        hostloc_state[static_cast<size_t>(new_location)] = HostLocState::Use;
+        std::swap(hostloc_to_inst[static_cast<size_t>(new_location)], hostloc_to_inst[static_cast<size_t>(current_location)]);
+        DecrementRemainingUses(use_inst);
     } else if (HostLocIsRegister(current_location)) {
-        ASSERT(hostloc_state[current_location] == HostLocState::Idle);
+        ASSERT(hostloc_state[static_cast<size_t>(current_location)] == HostLocState::Idle);
 
-        code->XCHG(32, Gen::R(hostloc_to_x64.at(new_location)), Gen::R(hostloc_to_x64.at(current_location)));
+        code->XCHG(32, Gen::R(HostLocToX64(new_location)), Gen::R(HostLocToX64(current_location)));
 
-        hostloc_state[new_location] = HostLocState::Use;
-        std::swap(hostloc_to_value[new_location], hostloc_to_value[current_location]);
-        remaining_uses[use_value]--;
+        hostloc_state[static_cast<size_t>(new_location)] = HostLocState::Use;
+        std::swap(hostloc_to_inst[static_cast<size_t>(new_location)], hostloc_to_inst[static_cast<size_t>(current_location)]);
+        DecrementRemainingUses(use_inst);
     } else {
         ASSERT_MSG(0, "Invalid current_location");
     }
 
-    return hostloc_to_x64.at(new_location);
+    return HostLocToX64(new_location);
 }
 
-Gen::X64Reg RegAlloc::UseScratchRegister(IR::Value* use_value, std::initializer_list<HostLoc> desired_locations) {
-    ASSERT(std::all_of(desired_locations.begin(), desired_locations.end(), HostLocIsRegister));
-    ASSERT_MSG(remaining_uses.find(use_value) != remaining_uses.end(), "use_value has not been defined");
-    ASSERT_MSG(!ValueLocations(use_value).empty(), "use_value has not been defined");
-    ASSERT_MSG(remaining_uses[use_value] != 0, "use_value ran out of uses. (Use-d an IR::Value* too many times)");
+Gen::X64Reg RegAlloc::UseScratchRegister(IR::Value use_value, std::initializer_list<HostLoc> desired_locations) {
+    if (!use_value.IsImmediate()) {
+        return UseScratchRegister(use_value.GetInst(), desired_locations);
+    }
 
-    HostLoc current_location = ValueLocations(use_value).front();
+    return LoadImmediateIntoRegister(use_value, ScratchRegister(desired_locations));
+}
+
+Gen::X64Reg RegAlloc::UseScratchRegister(IR::Inst* use_inst, std::initializer_list<HostLoc> desired_locations) {
+    DEBUG_ASSERT(std::all_of(desired_locations.begin(), desired_locations.end(), HostLocIsRegister));
+    DEBUG_ASSERT_MSG(!ValueLocations(use_inst).empty(), "use_inst has not been defined");
+    ASSERT_MSG(use_inst->use_count != 0, "use_inst ran out of uses. (Use-d an IR::Inst* too many times)");
+
+    HostLoc current_location = ValueLocations(use_inst).front();
     HostLoc new_location = SelectARegister(desired_locations);
 
     if (HostLocIsSpill(current_location)) {
@@ -132,34 +136,34 @@ Gen::X64Reg RegAlloc::UseScratchRegister(IR::Value* use_value, std::initializer_
             SpillRegister(new_location);
         }
 
-        code->MOV(32, Gen::R(hostloc_to_x64.at(new_location)), SpillToOpArg(current_location));
+        code->MOV(32, Gen::R(HostLocToX64(new_location)), SpillToOpArg(current_location));
 
-        hostloc_state[new_location] = HostLocState::Scratch;
-        remaining_uses[use_value]--;
+        hostloc_state[static_cast<size_t>(new_location)] = HostLocState::Scratch;
+        DecrementRemainingUses(use_inst);
     } else if (HostLocIsRegister(current_location)) {
-        ASSERT(hostloc_state[current_location] == HostLocState::Idle);
+        ASSERT(hostloc_state[static_cast<size_t>(current_location)] == HostLocState::Idle);
 
         if (IsRegisterOccupied(new_location)) {
             SpillRegister(new_location);
             if (current_location != new_location) {
-                code->MOV(32, Gen::R(hostloc_to_x64.at(new_location)), Gen::R(hostloc_to_x64.at(current_location)));
+                code->MOV(32, Gen::R(HostLocToX64(new_location)), Gen::R(HostLocToX64(current_location)));
             }
         } else {
-            code->MOV(32, Gen::R(hostloc_to_x64.at(new_location)), Gen::R(hostloc_to_x64.at(current_location)));
+            code->MOV(32, Gen::R(HostLocToX64(new_location)), Gen::R(HostLocToX64(current_location)));
         }
 
-        hostloc_state[new_location] = HostLocState::Scratch;
-        remaining_uses[use_value]--;
+        hostloc_state[static_cast<size_t>(new_location)] = HostLocState::Scratch;
+        DecrementRemainingUses(use_inst);
     } else {
         ASSERT_MSG(0, "Invalid current_location");
     }
 
-    return hostloc_to_x64.at(new_location);
+    return HostLocToX64(new_location);
 }
 
 
 Gen::X64Reg RegAlloc::ScratchRegister(std::initializer_list<HostLoc> desired_locations) {
-    ASSERT(std::all_of(desired_locations.begin(), desired_locations.end(), HostLocIsRegister));
+    DEBUG_ASSERT(std::all_of(desired_locations.begin(), desired_locations.end(), HostLocIsRegister));
 
     HostLoc location = SelectARegister(desired_locations);
 
@@ -168,12 +172,32 @@ Gen::X64Reg RegAlloc::ScratchRegister(std::initializer_list<HostLoc> desired_loc
     }
 
     // Update state
-    hostloc_state[location] = HostLocState::Scratch;
+    hostloc_state[static_cast<size_t>(location)] = HostLocState::Scratch;
 
-    return hostloc_to_x64.at(location);
+    return HostLocToX64(location);
 }
 
-void RegAlloc::HostCall(IR::Value* result_def, IR::Value* arg0_use, IR::Value* arg1_use, IR::Value* arg2_use, IR::Value* arg3_use) {
+Gen::X64Reg RegAlloc::LoadImmediateIntoRegister(IR::Value imm, Gen::X64Reg reg) {
+    ASSERT_MSG(imm.IsImmediate(), "imm is not an immediate");
+
+    switch (imm.GetType()) {
+        case IR::Type::U1:
+            code->MOV(32, R(reg), Gen::Imm32(imm.GetU1()));
+            break;
+        case IR::Type::U8:
+            code->MOV(32, R(reg), Gen::Imm32(imm.GetU8()));
+            break;
+        case IR::Type::U32:
+            code->MOV(32, R(reg), Gen::Imm32(imm.GetU32()));
+            break;
+        default:
+            ASSERT_MSG(false, "This should never happen.");
+    }
+
+    return reg;
+}
+
+void RegAlloc::HostCall(IR::Inst* result_def, IR::Value arg0_use, IR::Value arg1_use, IR::Value arg2_use, IR::Value arg3_use) {
     constexpr HostLoc AbiReturn = HostLoc::RAX;
 #ifdef _WIN32
     constexpr std::array<HostLoc, 4> AbiArgs = { HostLoc::RCX, HostLoc::RDX, HostLoc::R8, HostLoc::R9 };
@@ -185,7 +209,7 @@ void RegAlloc::HostCall(IR::Value* result_def, IR::Value* arg0_use, IR::Value* a
     constexpr std::array<HostLoc, 4> OtherCallerSave = { HostLoc::R8, HostLoc::R9, HostLoc::R10, HostLoc::R11 };
 #endif
 
-    const std::array<IR::Value*, 4> args = {arg0_use, arg1_use, arg2_use, arg3_use};
+    const std::array<IR::Value*, 4> args = {&arg0_use, &arg1_use, &arg2_use, &arg3_use};
 
     // TODO: This works but almost certainly leads to suboptimal generated code.
 
@@ -200,8 +224,8 @@ void RegAlloc::HostCall(IR::Value* result_def, IR::Value* arg0_use, IR::Value* a
     }
 
     for (size_t i = 0; i < AbiArgs.size(); i++) {
-        if (args[i]) {
-            UseScratchRegister(args[i], {AbiArgs[i]});
+        if (!args[i]->IsEmpty()) {
+            UseScratchRegister(*args[i], {AbiArgs[i]});
         } else {
             ScratchRegister({AbiArgs[i]});
         }
@@ -231,36 +255,36 @@ HostLoc RegAlloc::SelectARegister(std::initializer_list<HostLoc> desired_locatio
     return candidates.front();
 }
 
-std::vector<HostLoc> RegAlloc::ValueLocations(IR::Value* value) const {
+std::vector<HostLoc> RegAlloc::ValueLocations(IR::Inst* value) const {
     std::vector<HostLoc> locations;
 
-    for (const auto& iter : hostloc_to_value)
-        if (iter.second == value)
-            locations.emplace_back(iter.first);
+    for (size_t i = 0; i < HostLocCount; i++)
+        if (hostloc_to_inst[i] == value)
+            locations.emplace_back(static_cast<HostLoc>(i));
 
     return locations;
 }
 
 bool RegAlloc::IsRegisterOccupied(HostLoc loc) const {
-    return hostloc_to_value.find(loc) != hostloc_to_value.end() && hostloc_to_value.at(loc) != nullptr;
+    return hostloc_to_inst.at(static_cast<size_t>(loc)) != nullptr;
 }
 
 bool RegAlloc::IsRegisterAllocated(HostLoc loc) const {
-    return hostloc_state.find(loc) != hostloc_state.end() && hostloc_state.at(loc) != HostLocState::Idle;
+    return hostloc_state.at(static_cast<size_t>(loc)) != HostLocState::Idle;
 }
 
 void RegAlloc::SpillRegister(HostLoc loc) {
     ASSERT_MSG(HostLocIsRegister(loc), "Only registers can be spilled");
-    ASSERT_MSG(hostloc_state[loc] == HostLocState::Idle, "Allocated registers cannot be spilled");
+    ASSERT_MSG(hostloc_state[static_cast<size_t>(loc)] == HostLocState::Idle, "Allocated registers cannot be spilled");
     ASSERT_MSG(IsRegisterOccupied(loc), "There is no need to spill unoccupied registers");
     ASSERT_MSG(!IsRegisterAllocated(loc), "Registers that have been allocated must not be spilt");
 
     HostLoc new_loc = FindFreeSpill();
 
-    code->MOV(32, SpillToOpArg(new_loc), Gen::R(hostloc_to_x64.at(loc)));
+    code->MOV(32, SpillToOpArg(new_loc), Gen::R(HostLocToX64(loc)));
 
-    hostloc_to_value[new_loc] = hostloc_to_value[loc];
-    hostloc_to_value[loc] = nullptr;
+    hostloc_to_inst[static_cast<size_t>(new_loc)] = hostloc_to_inst[static_cast<size_t>(loc)];
+    hostloc_to_inst[static_cast<size_t>(loc)] = nullptr;
 }
 
 HostLoc RegAlloc::FindFreeSpill() const {
@@ -272,27 +296,25 @@ HostLoc RegAlloc::FindFreeSpill() const {
 }
 
 void RegAlloc::EndOfAllocScope() {
-    hostloc_state.clear();
+    hostloc_state.fill(HostLocState::Idle);
 
-    for (auto& iter : hostloc_to_value)
-        if (iter.second && remaining_uses[iter.second] == 0)
-            iter.second = nullptr;
+    for (auto& iter : hostloc_to_inst)
+        if (iter && iter->use_count == 0)
+            iter = nullptr;
 }
 
-void RegAlloc::DecrementRemainingUses(IR::Value* value) {
-    ASSERT_MSG(remaining_uses.find(value) != remaining_uses.end(), "value does not exist");
-    ASSERT_MSG(remaining_uses[value] > 0, "value doesn't have any remaining uses");
-    remaining_uses[value]--;
+void RegAlloc::DecrementRemainingUses(IR::Inst* value) {
+    ASSERT_MSG(value->use_count > 0, "value doesn't have any remaining uses");
+    value->use_count--;
 }
 
 void RegAlloc::AssertNoMoreUses() {
-    ASSERT(std::all_of(hostloc_to_value.begin(), hostloc_to_value.end(), [](const auto& pair){ return !pair.second; }));
+    ASSERT(std::all_of(hostloc_to_inst.begin(), hostloc_to_inst.end(), [](const auto& inst){ return !inst; }));
 }
 
 void RegAlloc::Reset() {
-    hostloc_to_value.clear();
-    hostloc_state.clear();
-    remaining_uses.clear();
+    hostloc_to_inst.fill(nullptr);
+    hostloc_state.fill(HostLocState::Idle);
 }
 
 } // namespace BackendX64
diff --git a/src/backend_x64/reg_alloc.h b/src/backend_x64/reg_alloc.h
index 8d9bed42..bf4f58eb 100644
--- a/src/backend_x64/reg_alloc.h
+++ b/src/backend_x64/reg_alloc.h
@@ -17,11 +17,14 @@ namespace Dynarmic {
 namespace BackendX64 {
 
 enum class HostLoc {
-    RAX, RBX, RCX, RDX, RSI, RDI, RBP, RSP, R8, R9, R10, R11, R12, R13, R14,
+    // Ordering of the registers is intentional. See also: HostLocToX64.
+    RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8, R9, R10, R11, R12, R13, R14,
     CF, PF, AF, ZF, SF, OF,
     FirstSpill,
 };
 
+constexpr size_t HostLocCount = static_cast<size_t>(HostLoc::FirstSpill) + SpillCount;
+
 enum class HostLocState {
     Idle, Def, Use, Scratch
 };
@@ -66,22 +69,26 @@ public:
     RegAlloc(Gen::XEmitter* code) : code(code) {}
 
     /// Late-def
-    Gen::X64Reg DefRegister(IR::Value* def_value, std::initializer_list<HostLoc> desired_locations = hostloc_any_register);
+    Gen::X64Reg DefRegister(IR::Inst* def_inst, std::initializer_list<HostLoc> desired_locations = hostloc_any_register);
     /// Early-use, Late-def
-    Gen::X64Reg UseDefRegister(IR::Value* use_value, IR::Value* def_value, std::initializer_list<HostLoc> desired_locations = hostloc_any_register);
+    Gen::X64Reg UseDefRegister(IR::Value use_value, IR::Inst* def_inst, std::initializer_list<HostLoc> desired_locations = hostloc_any_register);
+    Gen::X64Reg UseDefRegister(IR::Inst* use_inst, IR::Inst* def_inst, std::initializer_list<HostLoc> desired_locations = hostloc_any_register);
     /// Early-use
-    Gen::X64Reg UseRegister(IR::Value* use_value, std::initializer_list<HostLoc> desired_locations = hostloc_any_register);
+    Gen::X64Reg UseRegister(IR::Value use_value, std::initializer_list<HostLoc> desired_locations = hostloc_any_register);
+    Gen::X64Reg UseRegister(IR::Inst* use_inst, std::initializer_list<HostLoc> desired_locations = hostloc_any_register);
     /// Early-use, Destroyed
-    Gen::X64Reg UseScratchRegister(IR::Value* use_value, std::initializer_list<HostLoc> desired_locations = hostloc_any_register);
+    Gen::X64Reg UseScratchRegister(IR::Value use_value, std::initializer_list<HostLoc> desired_locations = hostloc_any_register);
+    Gen::X64Reg UseScratchRegister(IR::Inst* use_inst, std::initializer_list<HostLoc> desired_locations = hostloc_any_register);
     /// Early-def, Late-use, single-use
     Gen::X64Reg ScratchRegister(std::initializer_list<HostLoc> desired_locations = hostloc_any_register);
+    Gen::X64Reg LoadImmediateIntoRegister(IR::Value imm, Gen::X64Reg reg);
 
     /// Late-def for result register, Early-use for all arguments, Each value is placed into registers according to host ABI.
-    void HostCall(IR::Value* result_def = nullptr, IR::Value* arg0_use = nullptr, IR::Value* arg1_use = nullptr, IR::Value* arg2_use = nullptr, IR::Value* arg3_use = nullptr);
+    void HostCall(IR::Inst* result_def = nullptr, IR::Value arg0_use = {}, IR::Value arg1_use = {}, IR::Value arg2_use = {}, IR::Value arg3_use = {});
 
     // TODO: Values in host flags
 
-    void DecrementRemainingUses(IR::Value* value);
+    void DecrementRemainingUses(IR::Inst* value);
 
     void EndOfAllocScope();
 
@@ -91,7 +98,7 @@ public:
 
 private:
     HostLoc SelectARegister(std::initializer_list<HostLoc> desired_locations) const;
-    std::vector<HostLoc> ValueLocations(IR::Value* value) const;
+    std::vector<HostLoc> ValueLocations(IR::Inst* value) const;
     bool IsRegisterOccupied(HostLoc loc) const;
     bool IsRegisterAllocated(HostLoc loc) const;
 
@@ -100,10 +107,9 @@ private:
 
     Gen::XEmitter* code = nullptr;
 
-    using mapping_map_t = std::map<HostLoc, IR::Value*>;
-    mapping_map_t hostloc_to_value;
-    std::map<HostLoc, HostLocState> hostloc_state;
-    std::map<IR::Value*, size_t> remaining_uses;
+    using mapping_map_t = std::array<IR::Inst*, HostLocCount>;
+    mapping_map_t hostloc_to_inst;
+    std::array<HostLocState, HostLocCount> hostloc_state;
 };
 
 } // namespace BackendX64
diff --git a/src/common/assert.h b/src/common/assert.h
index 2bf77da6..b9c954b1 100644
--- a/src/common/assert.h
+++ b/src/common/assert.h
@@ -45,7 +45,7 @@ static void assert_noinline_call(const Fn& fn) {
 #define DEBUG_ASSERT_MSG(_a_, ...) ASSERT_MSG(_a_, __VA_ARGS__)
 #else // not debug
 #define DEBUG_ASSERT(_a_)
-#define DEBUG_ASSERT_MSG(_a_, _desc_, ...)
+#define DEBUG_ASSERT_MSG(_a_, ...)
 #endif
 
 #define UNIMPLEMENTED() DEBUG_ASSERT_MSG(false, "Unimplemented code!")
diff --git a/src/frontend/ir/ir.cpp b/src/frontend/ir/ir.cpp
index 3bb69132..f3a6d10b 100644
--- a/src/frontend/ir/ir.cpp
+++ b/src/frontend/ir/ir.cpp
@@ -52,94 +52,69 @@ const char* GetNameOf(Opcode op) {
 
 // Value class member definitions
 
-void Value::ReplaceUsesWith(ValuePtr replacement) {
-    while (!uses.empty()) {
-        auto use = uses.front();
-        use.use_owner.lock()->ReplaceUseOfXWithY(use.value.lock(), replacement);
-    }
-}
-
-std::vector<ValuePtr> Value::GetUses() const {
-    std::vector<ValuePtr> result(uses.size());
-    std::transform(uses.begin(), uses.end(), result.begin(), [](const auto& use){ return use.use_owner.lock(); });
-    return result;
-}
-
-void Value::AddUse(ValuePtr owner) {
-    // There can be multiple uses from the same owner.
-    uses.push_back({ shared_from_this(), owner });
-}
-
-void Value::RemoveUse(ValuePtr owner) {
-    // Remove only one use.
-    auto iter = std::find_if(uses.begin(), uses.end(), [&owner](auto use) { return use.use_owner.lock() == owner; });
-    ASSERT_MSG(iter != uses.end(), "RemoveUse without associated AddUse. Bug in use management code.");
-    uses.erase(iter);
-}
-
-void Value::ReplaceUseOfXWithY(ValuePtr x, ValuePtr y) {
-    // This should never be called. Use management is incorrect if this is ever called.
-    ASSERT_MSG(false, "This Value type doesn't use any values. Bug in use management code.");
-}
-
-void Value::AssertValid() {
-    ASSERT(std::all_of(uses.begin(), uses.end(), [](const auto& use) { return !use.use_owner.expired(); }));
+Type Value::GetType() const {
+    return IsImmediate() ? type : inner.inst->GetType();
 }
 
 // Inst class member definitions
 
-Inst::Inst(Opcode op_) : Value(op_) {
-    args.resize(GetNumArgsOf(op));
+Value Inst::GetArg(size_t index) const {
+    DEBUG_ASSERT(index < GetNumArgsOf(op));
+    DEBUG_ASSERT(!args[index].IsEmpty());
+
+    return args[index];
 }
 
-void Inst::SetArg(size_t index, ValuePtr value) {
-    auto this_ = shared_from_this();
+void Inst::SetArg(size_t index, Value value) {
+    DEBUG_ASSERT(index < GetNumArgsOf(op));
+    DEBUG_ASSERT(value.GetType() == GetArgTypeOf(op, index));
 
-    if (auto prev_value = args.at(index).lock()) {
-        prev_value->RemoveUse(this_);
+    if (!args[index].IsImmediate()) {
+        UndoUse(args[index]);
+    }
+    if (!value.IsImmediate()) {
+        Use(value);
     }
 
-    ASSERT(value->GetType() == GetArgTypeOf(op, index));
-    args.at(index) = value;
-
-    value->AddUse(this_);
-}
-
-ValuePtr Inst::GetArg(size_t index) const {
-    ASSERT_MSG(!args.at(index).expired(), "This should never happen. All Values should be owned by a MicroBlock.");
-    return args.at(index).lock();
+    args[index] = value;
 }
 
 void Inst::Invalidate() {
-    AssertValid();
-    ASSERT(!HasUses());
-
-    auto this_ = shared_from_this();
-    for (auto& arg : args) {
-        arg.lock()->RemoveUse(this_);
-    }
-}
-
-void Inst::AssertValid() {
-    ASSERT(std::all_of(args.begin(), args.end(), [](const auto& arg) { return !arg.expired(); }));
-    Value::AssertValid();
-}
-
-void Inst::ReplaceUseOfXWithY(ValuePtr x, ValuePtr y) {
-    bool has_use = false;
-    auto this_ = shared_from_this();
-
-    // Note that there may be multiple uses of x.
-    for (auto& arg : args) {
-        if (arg.lock() == x) {
-            arg = y;
-            has_use = true;
-            x->RemoveUse(this_);
-            y->AddUse(this_);
+    for (auto& value : args) {
+        if (!value.IsImmediate()) {
+            UndoUse(value);
         }
     }
+}
 
-    ASSERT_MSG(has_use, "This Inst doesn't have x. Bug in use management code.");
+void Inst::Use(Value& value) {
+    value.GetInst()->use_count++;
+
+    switch (op){
+        case Opcode::GetCarryFromOp:
+            value.GetInst()->carry_inst = this;
+            break;
+        case Opcode::GetOverflowFromOp:
+            value.GetInst()->overflow_inst = this;
+            break;
+        default:
+            break;
+    }
+}
+
+void Inst::UndoUse(Value& value) {
+    value.GetInst()->use_count--;
+
+    switch (op){
+        case Opcode::GetCarryFromOp:
+            value.GetInst()->carry_inst = nullptr;
+            break;
+        case Opcode::GetOverflowFromOp:
+            value.GetInst()->overflow_inst = nullptr;
+            break;
+        default:
+            break;
+    }
 }
 
 std::string DumpBlock(const IR::Block& block) {
@@ -160,65 +135,48 @@ std::string DumpBlock(const IR::Block& block) {
     }
     ret += "\n";
 
-    std::map<IR::Value*, size_t> value_to_index;
+    std::map<const IR::Inst*, size_t> inst_to_index;
     size_t index = 0;
 
-    const auto arg_to_string = [&value_to_index](IR::ValuePtr arg) -> std::string {
-        if (!arg) {
+    const auto arg_to_string = [&inst_to_index](const IR::Value& arg) -> std::string {
+        if (arg.IsEmpty()) {
             return "<null>";
+        } else if (!arg.IsImmediate()) {
+            return Common::StringFromFormat("%%%zu", inst_to_index.at(arg.GetInst()));
         }
-        switch (arg->GetOpcode()) {
-            case Opcode::ImmU1: {
-                auto inst = reinterpret_cast<ImmU1*>(arg.get());
-                return Common::StringFromFormat("#%s", inst->value ? "1" : "0");
-            }
-            case Opcode::ImmU8: {
-                auto inst = reinterpret_cast<ImmU8*>(arg.get());
-                return Common::StringFromFormat("#%u", inst->value);
-            }
-            case Opcode::ImmU32: {
-                auto inst = reinterpret_cast<ImmU32*>(arg.get());
-                return Common::StringFromFormat("#%#x", inst->value);
-            }
-            case Opcode::ImmRegRef: {
-                auto inst = reinterpret_cast<ImmRegRef*>(arg.get());
-                return Arm::RegToString(inst->value);
-            }
-            default: {
-                return Common::StringFromFormat("%%%zu", value_to_index.at(arg.get()));
-            }
+        switch (arg.GetType()) {
+            case Type::U1:
+                return Common::StringFromFormat("#%s", arg.GetU1() ? "1" : "0");
+            case Type::U8:
+                return Common::StringFromFormat("#%u", arg.GetU8());
+            case Type::U32:
+                return Common::StringFromFormat("#%#x", arg.GetU32());
+            case Type::RegRef:
+                return Arm::RegToString(arg.GetRegRef());
+            default:
+                return "<unknown immediate type>";
         }
     };
 
-    for (const auto& inst_ptr : block.instructions) {
-        const Opcode op = inst_ptr->GetOpcode();
-        switch (op) {
-            case Opcode::ImmU1:
-            case Opcode::ImmU8:
-            case Opcode::ImmU32:
-            case Opcode::ImmRegRef:
-                break;
-            default: {
-                if (GetTypeOf(op) != Type::Void) {
-                    ret += Common::StringFromFormat("%%%-5zu = ", index);
-                } else {
-                    ret += "         "; // '%00000 = ' -> 1 + 5 + 3 = 9 spaces
-                }
+    for (auto inst = block.instructions.begin(); inst != block.instructions.end(); ++inst) {
+        const Opcode op = inst->GetOpcode();
 
-                ret += GetNameOf(op);
-
-                const size_t arg_count = GetNumArgsOf(op);
-                const auto inst = reinterpret_cast<Inst*>(inst_ptr.get());
-                for (size_t arg_index = 0; arg_index < arg_count; arg_index++) {
-                    ret += arg_index != 0 ? ", " : " ";
-                    ret += arg_to_string(inst->GetArg(arg_index));
-                }
-
-                ret += "\n";
-                value_to_index[inst_ptr.get()] = index++;
-                break;
-            }
+        if (GetTypeOf(op) != Type::Void) {
+            ret += Common::StringFromFormat("%%%-5zu = ", index);
+        } else {
+            ret += "         "; // '%00000 = ' -> 1 + 5 + 3 = 9 spaces
         }
+
+        ret += GetNameOf(op);
+
+        const size_t arg_count = GetNumArgsOf(op);
+        for (size_t arg_index = 0; arg_index < arg_count; arg_index++) {
+            ret += arg_index != 0 ? ", " : " ";
+            ret += arg_to_string(inst->GetArg(arg_index));
+        }
+
+        ret += "\n";
+        inst_to_index.at(&*inst) = index++;
     }
 
     return ret;
diff --git a/src/frontend/ir/ir.h b/src/frontend/ir/ir.h
index 8e9f8d3f..50d10e2c 100644
--- a/src/frontend/ir/ir.h
+++ b/src/frontend/ir/ir.h
@@ -10,9 +10,12 @@
 #include <memory>
 #include <vector>
 
-#include <boost/variant.hpp>
+#include <boost/pool/pool.hpp>
+#include <boost/intrusive/list.hpp>
 #include <boost/optional.hpp>
+#include <boost/variant.hpp>
 
+#include "common/assert.h"
 #include "common/common_types.h"
 #include "frontend/arm_types.h"
 #include "frontend/ir/opcodes.h"
@@ -47,22 +50,91 @@ const char* GetNameOf(Opcode op);
 
 // Type declarations
 
-/// Base class for microinstructions to derive from.
+/**
+ * A representation of a microinstruction. A single ARM/Thumb instruction may be
+ * converted into zero or more microinstructions.
+ */
 
-class Value;
-using ValuePtr = std::shared_ptr<Value>;
-using ValueWeakPtr = std::weak_ptr<Value>;
+struct Value;
+class Inst;
 
-class Value : public std::enable_shared_from_this<Value> {
+struct Value final {
 public:
-    virtual ~Value() = default;
+    Value() : type(Type::Void) {}
 
-    bool HasUses() const { return !uses.empty(); }
-    bool HasOneUse() const { return uses.size() == 1; }
-    bool HasManyUses() const { return uses.size() > 1; }
+    explicit Value(Inst* value) : type(Type::Opaque) {
+        inner.inst = value;
+    }
 
-    /// Replace all uses of this Value with `replacement`.
-    void ReplaceUsesWith(ValuePtr replacement);
+    explicit Value(Arm::Reg value) : type(Type::RegRef) {
+        inner.imm_regref = value;
+    }
+
+    explicit Value(bool value) : type(Type::U1) {
+        inner.imm_u1 = value;
+    }
+
+    explicit Value(u8 value) : type(Type::U8) {
+        inner.imm_u8 = value;
+    }
+
+    explicit Value(u32 value) : type(Type::U32) {
+        inner.imm_u32 = value;
+    }
+
+    bool IsEmpty() const {
+        return type == Type::Void;
+    }
+
+    bool IsImmediate() const {
+        return type != Type::Opaque;
+    }
+
+    Type GetType() const;
+
+    Inst* GetInst() const {
+        DEBUG_ASSERT(type == Type::Opaque);
+        return inner.inst;
+    }
+
+    Arm::Reg GetRegRef() const {
+        DEBUG_ASSERT(type == Type::RegRef);
+        return inner.imm_regref;
+    }
+
+    bool GetU1() const {
+        DEBUG_ASSERT(type == Type::U1);
+        return inner.imm_u1;
+    }
+
+    u8 GetU8() const {
+        DEBUG_ASSERT(type == Type::U8);
+        return inner.imm_u8;
+    }
+
+    u32 GetU32() const {
+        DEBUG_ASSERT(type == Type::U32);
+        return inner.imm_u32;
+    }
+
+private:
+    Type type;
+
+    union {
+        Inst* inst; // type == Type::Opaque
+        Arm::Reg imm_regref;
+        bool imm_u1;
+        u8 imm_u8;
+        u32 imm_u32;
+    } inner;
+};
+
+using InstListLinkMode = boost::intrusive::link_mode<boost::intrusive::normal_link>;
+class Inst final : public boost::intrusive::list_base_hook<InstListLinkMode> {
+public:
+    Inst(Opcode op) : op(op) {}
+
+    bool HasUses() const { return use_count > 0; }
 
     /// Get the microop this microinstruction represents.
     Opcode GetOpcode() const { return op; }
@@ -70,99 +142,22 @@ public:
     Type GetType() const { return GetTypeOf(op); }
     /// Get the number of arguments this instruction has.
     size_t NumArgs() const { return GetNumArgsOf(op); }
-    /// Get the number of uses this instruction has.
-    size_t NumUses() const { return uses.size(); }
 
-    std::vector<ValuePtr> GetUses() const;
+    Value GetArg(size_t index) const;
+    void SetArg(size_t index, Value value);
 
-    /// Prepare this Value for removal from the instruction stream.
-    virtual void Invalidate() {}
-    /// Assert that this Value is valid.
-    virtual void AssertValid();
+    void Invalidate();
 
-    intptr_t GetTag() const { return tag; }
-    void SetTag(intptr_t tag_) { tag = tag_; }
-
-protected:
-    friend class Inst;
-
-    explicit Value(Opcode op_) : op(op_) {}
-
-    void AddUse(ValuePtr owner);
-    void RemoveUse(ValuePtr owner);
-    virtual void ReplaceUseOfXWithY(ValuePtr x, ValuePtr y);
+    size_t use_count = 0;
+    Inst* carry_inst = nullptr;
+    Inst* overflow_inst = nullptr;
 
 private:
+    void Use(Value& value);
+    void UndoUse(Value& value);
+
     Opcode op;
-
-    struct Use {
-        /// The instruction which is being used.
-        ValueWeakPtr value;
-        /// The instruction which is using `value`.
-        ValueWeakPtr use_owner;
-    };
-    std::list<Use> uses;
-
-    intptr_t tag = 0;
-};
-
-/// Representation of a u1 immediate.
-class ImmU1 final : public Value {
-public:
-    explicit ImmU1(bool value_) : Value(Opcode::ImmU1), value(value_) {}
-    ~ImmU1() override = default;
-
-    const bool value; ///< Literal value to load
-};
-
-/// Representation of a u8 immediate.
-class ImmU8 final : public Value {
-public:
-    explicit ImmU8(u8 value_) : Value(Opcode::ImmU8), value(value_) {}
-    ~ImmU8() override = default;
-
-    const u8 value; ///< Literal value to load
-};
-
-/// Representation of a u32 immediate.
-class ImmU32 final : public Value {
-public:
-    explicit ImmU32(u32 value_) : Value(Opcode::ImmU32), value(value_) {}
-    ~ImmU32() override = default;
-
-    const u32 value; ///< Literal value to load
-};
-
-/// Representation of a GPR reference.
-class ImmRegRef final : public Value {
-public:
-    explicit ImmRegRef(Arm::Reg value_) : Value(Opcode::ImmRegRef), value(value_) {}
-    ~ImmRegRef() override = default;
-
-    const Arm::Reg value; ///< Literal value to load
-};
-
-/**
- * A representation of a microinstruction. A single ARM/Thumb instruction may be
- * converted into zero or more microinstructions.
- */
-class Inst final : public Value {
-public:
-    explicit Inst(Opcode op);
-    ~Inst() override = default;
-
-    /// Set argument number `index` to `value`.
-    void SetArg(size_t index, ValuePtr value);
-    /// Get argument number `index`.
-    ValuePtr GetArg(size_t index) const;
-
-    void Invalidate() override;
-    void AssertValid() override;
-protected:
-    void ReplaceUseOfXWithY(ValuePtr x, ValuePtr y) override;
-
-private:
-    std::vector<ValueWeakPtr> args;
+    std::array<Value, 3> args;
 };
 
 namespace Term {
@@ -261,7 +256,9 @@ public:
     boost::optional<Arm::LocationDescriptor> cond_failed = {};
 
     /// List of instructions in this block.
-    std::list<ValuePtr> instructions;
+    boost::intrusive::list<Inst, InstListLinkMode> instructions;
+    /// Memory pool for instruction list
+    std::unique_ptr<boost::pool<>> instruction_alloc_pool = std::make_unique<boost::pool<>>(sizeof(Inst));
     /// Terminal instruction of this block.
     Terminal terminal = Term::Invalid{};
 
diff --git a/src/frontend/ir/ir_emitter.cpp b/src/frontend/ir/ir_emitter.cpp
index 8c8df66d..1b57fd89 100644
--- a/src/frontend/ir/ir_emitter.cpp
+++ b/src/frontend/ir/ir_emitter.cpp
@@ -24,138 +24,132 @@ u32 IREmitter::AlignPC(size_t alignment) {
     return static_cast<u32>(pc - pc % alignment);
 }
 
-IR::ValuePtr IREmitter::Imm1(bool value) {
-    auto imm1 = std::make_shared<IR::ImmU1>(value);
-    AddToBlock(imm1);
-    return imm1;
+IR::Value IREmitter::Imm1(bool imm1) {
+    return IR::Value(imm1);
 }
 
-IR::ValuePtr IREmitter::Imm8(u8 i) {
-    auto imm8 = std::make_shared<IR::ImmU8>(i);
-    AddToBlock(imm8);
-    return imm8;
+IR::Value IREmitter::Imm8(u8 imm8) {
+    return IR::Value(imm8);
 }
 
-IR::ValuePtr IREmitter::Imm32(u32 i) {
-    auto imm32 = std::make_shared<IR::ImmU32>(i);
-    AddToBlock(imm32);
-    return imm32;
+IR::Value IREmitter::Imm32(u32 imm32) {
+    return IR::Value(imm32);
 }
 
-IR::ValuePtr IREmitter::GetRegister(Reg reg) {
+IR::Value IREmitter::GetRegister(Reg reg) {
     if (reg == Reg::PC) {
         return Imm32(PC());
     }
-    return Inst(IR::Opcode::GetRegister, { RegRef(reg) });
+    return Inst(IR::Opcode::GetRegister, { IR::Value(reg) });
 }
 
-void IREmitter::SetRegister(const Reg reg, IR::ValuePtr value) {
+void IREmitter::SetRegister(const Reg reg, const IR::Value& value) {
     ASSERT(reg != Reg::PC);
-    Inst(IR::Opcode::SetRegister, { RegRef(reg), value });
+    Inst(IR::Opcode::SetRegister, { IR::Value(reg), value });
 }
 
-void IREmitter::ALUWritePC(IR::ValuePtr value) {
+void IREmitter::ALUWritePC(const IR::Value& value) {
     // This behaviour is ARM version-dependent.
     // The below implementation is for ARMv6k
     BranchWritePC(value);
 }
 
-void IREmitter::BranchWritePC(IR::ValuePtr value) {
+void IREmitter::BranchWritePC(const IR::Value& value) {
     if (!current_location.TFlag) {
         auto new_pc = And(value, Imm32(0xFFFFFFFC));
-        Inst(IR::Opcode::SetRegister, { RegRef(Reg::PC), new_pc });
+        Inst(IR::Opcode::SetRegister, { IR::Value(Reg::PC), new_pc });
     } else {
         auto new_pc = And(value, Imm32(0xFFFFFFFE));
-        Inst(IR::Opcode::SetRegister, { RegRef(Reg::PC), new_pc });
+        Inst(IR::Opcode::SetRegister, { IR::Value(Reg::PC), new_pc });
     }
 }
 
-void IREmitter::BXWritePC(IR::ValuePtr value) {
+void IREmitter::BXWritePC(const IR::Value& value) {
     Inst(IR::Opcode::BXWritePC, {value});
 }
 
-void IREmitter::LoadWritePC(IR::ValuePtr value) {
+void IREmitter::LoadWritePC(const IR::Value& value) {
     // This behaviour is ARM version-dependent.
     // The below implementation is for ARMv6k
     BXWritePC(value);
 }
 
-void IREmitter::CallSupervisor(IR::ValuePtr value) {
+void IREmitter::CallSupervisor(const IR::Value& value) {
     Inst(IR::Opcode::CallSupervisor, {value});
 }
 
-IR::ValuePtr IREmitter::GetCFlag() {
+IR::Value IREmitter::GetCFlag() {
     return Inst(IR::Opcode::GetCFlag, {});
 }
 
-void IREmitter::SetNFlag(IR::ValuePtr value) {
+void IREmitter::SetNFlag(const IR::Value& value) {
     Inst(IR::Opcode::SetNFlag, {value});
 }
 
-void IREmitter::SetZFlag(IR::ValuePtr value) {
+void IREmitter::SetZFlag(const IR::Value& value) {
     Inst(IR::Opcode::SetZFlag, {value});
 }
 
-void IREmitter::SetCFlag(IR::ValuePtr value) {
+void IREmitter::SetCFlag(const IR::Value& value) {
     Inst(IR::Opcode::SetCFlag, {value});
 }
 
-void IREmitter::SetVFlag(IR::ValuePtr value) {
+void IREmitter::SetVFlag(const IR::Value& value) {
     Inst(IR::Opcode::SetVFlag, {value});
 }
 
-IR::ValuePtr IREmitter::LeastSignificantHalf(IR::ValuePtr value) {
+IR::Value IREmitter::LeastSignificantHalf(const IR::Value& value) {
     return Inst(IR::Opcode::LeastSignificantHalf, {value});
 }
 
-IR::ValuePtr IREmitter::LeastSignificantByte(IR::ValuePtr value) {
+IR::Value IREmitter::LeastSignificantByte(const IR::Value& value) {
     return Inst(IR::Opcode::LeastSignificantByte, {value});
 }
 
-IR::ValuePtr IREmitter::MostSignificantBit(IR::ValuePtr value) {
+IR::Value IREmitter::MostSignificantBit(const IR::Value& value) {
     return Inst(IR::Opcode::MostSignificantBit, {value});
 }
 
-IR::ValuePtr IREmitter::IsZero(IR::ValuePtr value) {
+IR::Value IREmitter::IsZero(const IR::Value& value) {
     return Inst(IR::Opcode::IsZero, {value});
 }
 
-IREmitter::ResultAndCarry IREmitter::LogicalShiftLeft(IR::ValuePtr value_in, IR::ValuePtr shift_amount, IR::ValuePtr carry_in) {
+IREmitter::ResultAndCarry IREmitter::LogicalShiftLeft(const IR::Value& value_in, const IR::Value& shift_amount, const IR::Value& carry_in) {
     auto result = Inst(IR::Opcode::LogicalShiftLeft, {value_in, shift_amount, carry_in});
     auto carry_out = Inst(IR::Opcode::GetCarryFromOp, {result});
     return {result, carry_out};
 }
 
-IREmitter::ResultAndCarry IREmitter::LogicalShiftRight(IR::ValuePtr value_in, IR::ValuePtr shift_amount, IR::ValuePtr carry_in) {
+IREmitter::ResultAndCarry IREmitter::LogicalShiftRight(const IR::Value& value_in, const IR::Value& shift_amount, const IR::Value& carry_in) {
     auto result = Inst(IR::Opcode::LogicalShiftRight, {value_in, shift_amount, carry_in});
     auto carry_out = Inst(IR::Opcode::GetCarryFromOp, {result});
     return {result, carry_out};
 }
 
-IREmitter::ResultAndCarry IREmitter::ArithmeticShiftRight(IR::ValuePtr value_in, IR::ValuePtr shift_amount, IR::ValuePtr carry_in) {
+IREmitter::ResultAndCarry IREmitter::ArithmeticShiftRight(const IR::Value& value_in, const IR::Value& shift_amount, const IR::Value& carry_in) {
     auto result = Inst(IR::Opcode::ArithmeticShiftRight, {value_in, shift_amount, carry_in});
     auto carry_out = Inst(IR::Opcode::GetCarryFromOp, {result});
     return {result, carry_out};
 }
 
-IREmitter::ResultAndCarry IREmitter::RotateRight(IR::ValuePtr value_in, IR::ValuePtr shift_amount, IR::ValuePtr carry_in) {
+IREmitter::ResultAndCarry IREmitter::RotateRight(const IR::Value& value_in, const IR::Value& shift_amount, const IR::Value& carry_in) {
     auto result = Inst(IR::Opcode::RotateRight, {value_in, shift_amount, carry_in});
     auto carry_out = Inst(IR::Opcode::GetCarryFromOp, {result});
     return {result, carry_out};
 }
 
-IREmitter::ResultAndCarryAndOverflow IREmitter::AddWithCarry(IR::ValuePtr a, IR::ValuePtr b, IR::ValuePtr carry_in) {
+IREmitter::ResultAndCarryAndOverflow IREmitter::AddWithCarry(const IR::Value& a, const IR::Value& b, const IR::Value& carry_in) {
     auto result = Inst(IR::Opcode::AddWithCarry, {a, b, carry_in});
     auto carry_out = Inst(IR::Opcode::GetCarryFromOp, {result});
     auto overflow = Inst(IR::Opcode::GetOverflowFromOp, {result});
     return {result, carry_out, overflow};
 }
 
-IR::ValuePtr IREmitter::Add(IR::ValuePtr a, IR::ValuePtr b) {
+IR::Value IREmitter::Add(const IR::Value& a, const IR::Value& b) {
     return Inst(IR::Opcode::AddWithCarry, {a, b, Imm1(0)});
 }
 
-IREmitter::ResultAndCarryAndOverflow IREmitter::SubWithCarry(IR::ValuePtr a, IR::ValuePtr b, IR::ValuePtr carry_in) {
+IREmitter::ResultAndCarryAndOverflow IREmitter::SubWithCarry(const IR::Value& a, const IR::Value& b, const IR::Value& carry_in) {
     // This is equivalent to AddWithCarry(a, Not(b), carry_in).
     auto result = Inst(IR::Opcode::SubWithCarry, {a, b, carry_in});
     auto carry_out = Inst(IR::Opcode::GetCarryFromOp, {result});
@@ -163,96 +157,102 @@ IREmitter::ResultAndCarryAndOverflow IREmitter::SubWithCarry(IR::ValuePtr a, IR:
     return {result, carry_out, overflow};
 }
 
-IR::ValuePtr IREmitter::Sub(IR::ValuePtr a, IR::ValuePtr b) {
+IR::Value IREmitter::Sub(const IR::Value& a, const IR::Value& b) {
     return Inst(IR::Opcode::SubWithCarry, {a, b, Imm1(1)});
 }
 
-IR::ValuePtr IREmitter::And(IR::ValuePtr a, IR::ValuePtr b) {
+IR::Value IREmitter::And(const IR::Value& a, const IR::Value& b) {
     return Inst(IR::Opcode::And, {a, b});
 }
 
-IR::ValuePtr IREmitter::Eor(IR::ValuePtr a, IR::ValuePtr b) {
+IR::Value IREmitter::Eor(const IR::Value& a, const IR::Value& b) {
     return Inst(IR::Opcode::Eor, {a, b});
 }
 
-IR::ValuePtr IREmitter::Or(IR::ValuePtr a, IR::ValuePtr b) {
+IR::Value IREmitter::Or(const IR::Value& a, const IR::Value& b) {
     return Inst(IR::Opcode::Or, {a, b});
 }
 
-IR::ValuePtr IREmitter::Not(IR::ValuePtr a) {
+IR::Value IREmitter::Not(const IR::Value& a) {
     return Inst(IR::Opcode::Not, {a});
 }
 
-IR::ValuePtr IREmitter::SignExtendHalfToWord(IR::ValuePtr a) {
+IR::Value IREmitter::SignExtendHalfToWord(const IR::Value& a) {
     return Inst(IR::Opcode::SignExtendHalfToWord, {a});
 }
 
-IR::ValuePtr IREmitter::SignExtendByteToWord(IR::ValuePtr a) {
+IR::Value IREmitter::SignExtendByteToWord(const IR::Value& a) {
     return Inst(IR::Opcode::SignExtendByteToWord, {a});
 }
 
-IR::ValuePtr IREmitter::ZeroExtendHalfToWord(IR::ValuePtr a) {
+IR::Value IREmitter::ZeroExtendHalfToWord(const IR::Value& a) {
     return Inst(IR::Opcode::ZeroExtendHalfToWord, {a});
 }
 
-IR::ValuePtr IREmitter::ZeroExtendByteToWord(IR::ValuePtr a) {
+IR::Value IREmitter::ZeroExtendByteToWord(const IR::Value& a) {
     return Inst(IR::Opcode::ZeroExtendByteToWord, {a});
 }
 
-IR::ValuePtr IREmitter::ByteReverseWord(IR::ValuePtr a) {
+IR::Value IREmitter::ByteReverseWord(const IR::Value& a) {
     return Inst(IR::Opcode::ByteReverseWord, {a});
 }
 
-IR::ValuePtr IREmitter::ByteReverseHalf(IR::ValuePtr a) {
+IR::Value IREmitter::ByteReverseHalf(const IR::Value& a) {
     return Inst(IR::Opcode::ByteReverseHalf, {a});
 }
 
-IR::ValuePtr IREmitter::ByteReverseDual(IR::ValuePtr a) {
+IR::Value IREmitter::ByteReverseDual(const IR::Value& a) {
     return Inst(IR::Opcode::ByteReverseDual, {a});
 }
 
-IR::ValuePtr IREmitter::ReadMemory8(IR::ValuePtr vaddr) {
+IR::Value IREmitter::ReadMemory8(const IR::Value& vaddr) {
     return Inst(IR::Opcode::ReadMemory8, {vaddr});
 }
 
-IR::ValuePtr IREmitter::ReadMemory16(IR::ValuePtr vaddr) {
+IR::Value IREmitter::ReadMemory16(const IR::Value& vaddr) {
     auto value = Inst(IR::Opcode::ReadMemory16, {vaddr});
     return current_location.EFlag ? ByteReverseHalf(value) : value;
 }
 
-IR::ValuePtr IREmitter::ReadMemory32(IR::ValuePtr vaddr) {
+IR::Value IREmitter::ReadMemory32(const IR::Value& vaddr) {
     auto value = Inst(IR::Opcode::ReadMemory32, {vaddr});
     return current_location.EFlag ? ByteReverseWord(value) : value;
 }
 
-IR::ValuePtr IREmitter::ReadMemory64(IR::ValuePtr vaddr) {
+IR::Value IREmitter::ReadMemory64(const IR::Value& vaddr) {
     auto value = Inst(IR::Opcode::ReadMemory64, {vaddr});
     return current_location.EFlag ? ByteReverseDual(value) : value;
 }
 
-void IREmitter::WriteMemory8(IR::ValuePtr vaddr, IR::ValuePtr value) {
+void IREmitter::WriteMemory8(const IR::Value& vaddr, const IR::Value& value) {
     Inst(IR::Opcode::WriteMemory8, {vaddr, value});
 }
 
-void IREmitter::WriteMemory16(IR::ValuePtr vaddr, IR::ValuePtr value) {
+void IREmitter::WriteMemory16(const IR::Value& vaddr, const IR::Value& value) {
     if (current_location.EFlag) {
-        value = ByteReverseHalf(value);
+        auto v = ByteReverseHalf(value);
+        Inst(IR::Opcode::WriteMemory16, {vaddr, v});
+    } else {
+        Inst(IR::Opcode::WriteMemory16, {vaddr, value});
     }
-    Inst(IR::Opcode::WriteMemory16, {vaddr, value});
 }
 
-void IREmitter::WriteMemory32(IR::ValuePtr vaddr, IR::ValuePtr value) {
+void IREmitter::WriteMemory32(const IR::Value& vaddr, const IR::Value& value) {
     if (current_location.EFlag) {
-        value = ByteReverseWord(value);
+        auto v = ByteReverseWord(value);
+        Inst(IR::Opcode::WriteMemory32, {vaddr, v});
+    } else {
+        Inst(IR::Opcode::WriteMemory32, {vaddr, value});
     }
-    Inst(IR::Opcode::WriteMemory32, {vaddr, value});
 }
 
-void IREmitter::WriteMemory64(IR::ValuePtr vaddr, IR::ValuePtr value) {
+void IREmitter::WriteMemory64(const IR::Value& vaddr, const IR::Value& value) {
     if (current_location.EFlag) {
-        value = ByteReverseDual(value);
+        auto v = ByteReverseDual(value);
+        Inst(IR::Opcode::WriteMemory64, {vaddr, v});
+    } else {
+        Inst(IR::Opcode::WriteMemory64, {vaddr, value});
     }
-    Inst(IR::Opcode::WriteMemory64, {vaddr, value});
 }
 
 void IREmitter::SetTerm(const IR::Terminal& terminal) {
@@ -260,28 +260,18 @@ void IREmitter::SetTerm(const IR::Terminal& terminal) {
     block.terminal = terminal;
 }
 
-IR::ValuePtr IREmitter::Inst(IR::Opcode op, std::initializer_list<IR::ValuePtr> args) {
-    auto inst = std::make_shared<IR::Inst>(op);
-    assert(args.size() == inst->NumArgs());
+IR::Value IREmitter::Inst(IR::Opcode op, std::initializer_list<IR::Value> args) {
+    IR::Inst* inst = new(block.instruction_alloc_pool->malloc()) IR::Inst(op);
+    DEBUG_ASSERT(args.size() == inst->NumArgs());
 
     std::for_each(args.begin(), args.end(), [&inst, op, index = size_t(0)](const auto& v) mutable {
-        assert(IR::GetArgTypeOf(op, index) == v->GetType());
+        DEBUG_ASSERT(IR::GetArgTypeOf(op, index) == v.GetType());
         inst->SetArg(index, v);
         index++;
     });
 
-    AddToBlock(inst);
-    return inst;
-}
-
-IR::ValuePtr IREmitter::RegRef(Reg reg) {
-    auto regref = std::make_shared<IR::ImmRegRef>(reg);
-    AddToBlock(regref);
-    return regref;
-}
-
-void IREmitter::AddToBlock(IR::ValuePtr value) {
-    block.instructions.emplace_back(value);
+    block.instructions.push_back(*inst);
+    return IR::Value(inst);
 }
 
 } // namespace Arm
diff --git a/src/frontend/ir/ir_emitter.h b/src/frontend/ir/ir_emitter.h
index 6efccb17..165cc058 100644
--- a/src/frontend/ir/ir_emitter.h
+++ b/src/frontend/ir/ir_emitter.h
@@ -21,79 +21,77 @@ public:
     LocationDescriptor current_location;
 
     struct ResultAndCarry {
-        IR::ValuePtr result;
-        IR::ValuePtr carry;
+        IR::Value result;
+        IR::Value carry;
     };
 
     struct ResultAndCarryAndOverflow {
-        IR::ValuePtr result;
-        IR::ValuePtr carry;
-        IR::ValuePtr overflow;
+        IR::Value result;
+        IR::Value carry;
+        IR::Value overflow;
     };
 
     void Unimplemented();
     u32 PC();
     u32 AlignPC(size_t alignment);
 
-    IR::ValuePtr Imm1(bool value);
-    IR::ValuePtr Imm8(u8 value);
-    IR::ValuePtr Imm32(u32 value);
+    IR::Value Imm1(bool value);
+    IR::Value Imm8(u8 value);
+    IR::Value Imm32(u32 value);
 
-    IR::ValuePtr GetRegister(Reg source_reg);
-    void SetRegister(const Reg dest_reg, IR::ValuePtr value);
+    IR::Value GetRegister(Reg source_reg);
+    void SetRegister(const Reg dest_reg, const IR::Value& value);
 
-    void ALUWritePC(IR::ValuePtr value);
-    void BranchWritePC(IR::ValuePtr value);
-    void BXWritePC(IR::ValuePtr value);
-    void LoadWritePC(IR::ValuePtr value);
-    void CallSupervisor(IR::ValuePtr value);
+    void ALUWritePC(const IR::Value& value);
+    void BranchWritePC(const IR::Value& value);
+    void BXWritePC(const IR::Value& value);
+    void LoadWritePC(const IR::Value& value);
+    void CallSupervisor(const IR::Value& value);
 
-    IR::ValuePtr GetCFlag();
-    void SetNFlag(IR::ValuePtr value);
-    void SetZFlag(IR::ValuePtr value);
-    void SetCFlag(IR::ValuePtr value);
-    void SetVFlag(IR::ValuePtr value);
+    IR::Value GetCFlag();
+    void SetNFlag(const IR::Value& value);
+    void SetZFlag(const IR::Value& value);
+    void SetCFlag(const IR::Value& value);
+    void SetVFlag(const IR::Value& value);
 
-    IR::ValuePtr LeastSignificantHalf(IR::ValuePtr value);
-    IR::ValuePtr LeastSignificantByte(IR::ValuePtr value);
-    IR::ValuePtr MostSignificantBit(IR::ValuePtr value);
-    IR::ValuePtr IsZero(IR::ValuePtr value);
+    IR::Value LeastSignificantHalf(const IR::Value& value);
+    IR::Value LeastSignificantByte(const IR::Value& value);
+    IR::Value MostSignificantBit(const IR::Value& value);
+    IR::Value IsZero(const IR::Value& value);
 
-    ResultAndCarry LogicalShiftLeft(IR::ValuePtr value_in, IR::ValuePtr shift_amount, IR::ValuePtr carry_in);
-    ResultAndCarry LogicalShiftRight(IR::ValuePtr value_in, IR::ValuePtr shift_amount, IR::ValuePtr carry_in);
-    ResultAndCarry ArithmeticShiftRight(IR::ValuePtr value_in, IR::ValuePtr shift_amount, IR::ValuePtr carry_in);
-    ResultAndCarry RotateRight(IR::ValuePtr value_in, IR::ValuePtr shift_amount, IR::ValuePtr carry_in);
-    ResultAndCarryAndOverflow AddWithCarry(IR::ValuePtr a, IR::ValuePtr b, IR::ValuePtr carry_in);
-    IR::ValuePtr Add(IR::ValuePtr a, IR::ValuePtr b);
-    ResultAndCarryAndOverflow SubWithCarry(IR::ValuePtr a, IR::ValuePtr b, IR::ValuePtr carry_in);
-    IR::ValuePtr Sub(IR::ValuePtr a, IR::ValuePtr b);
-    IR::ValuePtr And(IR::ValuePtr a, IR::ValuePtr b);
-    IR::ValuePtr Eor(IR::ValuePtr a, IR::ValuePtr b);
-    IR::ValuePtr Or(IR::ValuePtr a, IR::ValuePtr b);
-    IR::ValuePtr Not(IR::ValuePtr a);
-    IR::ValuePtr SignExtendHalfToWord(IR::ValuePtr a);
-    IR::ValuePtr SignExtendByteToWord(IR::ValuePtr a);
-    IR::ValuePtr ZeroExtendHalfToWord(IR::ValuePtr a);
-    IR::ValuePtr ZeroExtendByteToWord(IR::ValuePtr a);
-    IR::ValuePtr ByteReverseWord(IR::ValuePtr a);
-    IR::ValuePtr ByteReverseHalf(IR::ValuePtr a);
-    IR::ValuePtr ByteReverseDual(IR::ValuePtr a);
+    ResultAndCarry LogicalShiftLeft(const IR::Value& value_in, const IR::Value& shift_amount, const IR::Value& carry_in);
+    ResultAndCarry LogicalShiftRight(const IR::Value& value_in, const IR::Value& shift_amount, const IR::Value& carry_in);
+    ResultAndCarry ArithmeticShiftRight(const IR::Value& value_in, const IR::Value& shift_amount, const IR::Value& carry_in);
+    ResultAndCarry RotateRight(const IR::Value& value_in, const IR::Value& shift_amount, const IR::Value& carry_in);
+    ResultAndCarryAndOverflow AddWithCarry(const IR::Value& a, const IR::Value& b, const IR::Value& carry_in);
+    IR::Value Add(const IR::Value& a, const IR::Value& b);
+    ResultAndCarryAndOverflow SubWithCarry(const IR::Value& a, const IR::Value& b, const IR::Value& carry_in);
+    IR::Value Sub(const IR::Value& a, const IR::Value& b);
+    IR::Value And(const IR::Value& a, const IR::Value& b);
+    IR::Value Eor(const IR::Value& a, const IR::Value& b);
+    IR::Value Or(const IR::Value& a, const IR::Value& b);
+    IR::Value Not(const IR::Value& a);
+    IR::Value SignExtendHalfToWord(const IR::Value& a);
+    IR::Value SignExtendByteToWord(const IR::Value& a);
+    IR::Value ZeroExtendHalfToWord(const IR::Value& a);
+    IR::Value ZeroExtendByteToWord(const IR::Value& a);
+    IR::Value ByteReverseWord(const IR::Value& a);
+    IR::Value ByteReverseHalf(const IR::Value& a);
+    IR::Value ByteReverseDual(const IR::Value& a);
 
-    IR::ValuePtr ReadMemory8(IR::ValuePtr vaddr);
-    IR::ValuePtr ReadMemory16(IR::ValuePtr vaddr);
-    IR::ValuePtr ReadMemory32(IR::ValuePtr vaddr);
-    IR::ValuePtr ReadMemory64(IR::ValuePtr vaddr);
-    void WriteMemory8(IR::ValuePtr vaddr, IR::ValuePtr value);
-    void WriteMemory16(IR::ValuePtr vaddr, IR::ValuePtr value);
-    void WriteMemory32(IR::ValuePtr vaddr, IR::ValuePtr value);
-    void WriteMemory64(IR::ValuePtr vaddr, IR::ValuePtr value);
+    IR::Value ReadMemory8(const IR::Value& vaddr);
+    IR::Value ReadMemory16(const IR::Value& vaddr);
+    IR::Value ReadMemory32(const IR::Value& vaddr);
+    IR::Value ReadMemory64(const IR::Value& vaddr);
+    void WriteMemory8(const IR::Value& vaddr, const IR::Value& value);
+    void WriteMemory16(const IR::Value& vaddr, const IR::Value& value);
+    void WriteMemory32(const IR::Value& vaddr, const IR::Value& value);
+    void WriteMemory64(const IR::Value& vaddr, const IR::Value& value);
 
     void SetTerm(const IR::Terminal& terminal);
 
 private:
-    IR::ValuePtr Inst(IR::Opcode op, std::initializer_list<IR::ValuePtr> args);
-    IR::ValuePtr RegRef(Reg reg);
-    void AddToBlock(IR::ValuePtr value);
+    IR::Value Inst(IR::Opcode op, std::initializer_list<IR::Value> args);
 };
 
 } // namespace Arm
diff --git a/src/frontend/ir/opcodes.inc b/src/frontend/ir/opcodes.inc
index ada4ce67..9f440384 100644
--- a/src/frontend/ir/opcodes.inc
+++ b/src/frontend/ir/opcodes.inc
@@ -1,11 +1,5 @@
 //     opcode name,             return type,    arg1 type,      arg2 type,      arg3 type,      ...
 
-// Immediate values
-OPCODE(ImmU1,                   T::U1,                                                          )
-OPCODE(ImmU8,                   T::U8,                                                          )
-OPCODE(ImmU32,                  T::U32,                                                         )
-OPCODE(ImmRegRef,               T::RegRef,                                                      )
-
 // ARM Context getters/setters
 OPCODE(GetRegister,             T::U32,         T::RegRef                                       )
 OPCODE(SetRegister,             T::Void,        T::RegRef,      T::U32                          )
diff --git a/src/frontend/translate/translate_arm.cpp b/src/frontend/translate/translate_arm.cpp
index 36b709b9..3d158ac2 100644
--- a/src/frontend/translate/translate_arm.cpp
+++ b/src/frontend/translate/translate_arm.cpp
@@ -340,7 +340,7 @@ IR::Block TranslateArm(LocationDescriptor descriptor, MemoryRead32FuncType memor
         visitor.ir.block.cond_failed = { visitor.ir.current_location };
     }
 
-    return visitor.ir.block;
+    return std::move(visitor.ir.block);
 }
 
 } // namespace Arm
diff --git a/src/frontend/translate/translate_thumb.cpp b/src/frontend/translate/translate_thumb.cpp
index 072065b7..babe816d 100644
--- a/src/frontend/translate/translate_thumb.cpp
+++ b/src/frontend/translate/translate_thumb.cpp
@@ -888,7 +888,7 @@ IR::Block TranslateThumb(LocationDescriptor descriptor, MemoryRead32FuncType mem
         visitor.ir.block.cycle_count++;
     }
 
-    return visitor.ir.block;
+    return std::move(visitor.ir.block);
 }
 
 } // namespace Arm
diff --git a/src/ir_opt/dead_code_elimination_pass.cpp b/src/ir_opt/dead_code_elimination_pass.cpp
index 1fee4b50..c3ffdb04 100644
--- a/src/ir_opt/dead_code_elimination_pass.cpp
+++ b/src/ir_opt/dead_code_elimination_pass.cpp
@@ -26,8 +26,8 @@ void DeadCodeElimination(IR::Block& block) {
     auto iter = block.instructions.end();
     do {
         --iter;
-        if (!(*iter)->HasUses() && is_side_effect_free((*iter)->GetOpcode())) {
-            (*iter)->Invalidate();
+        if (!iter->HasUses() && is_side_effect_free(iter->GetOpcode())) {
+            iter->Invalidate();
             iter = block.instructions.erase(iter);
         }
     } while (iter != block.instructions.begin());
diff --git a/src/ir_opt/get_set_elimination_pass.cpp b/src/ir_opt/get_set_elimination_pass.cpp
index 5b573e4a..0ca26337 100644
--- a/src/ir_opt/get_set_elimination_pass.cpp
+++ b/src/ir_opt/get_set_elimination_pass.cpp
@@ -12,6 +12,7 @@ namespace Dynarmic {
 namespace Optimization {
 
 void GetSetElimination(IR::Block& block) {
+#if 0
     using Iterator = decltype(block.instructions.begin());
     struct RegisterInfo {
         IR::ValuePtr register_value = nullptr;
@@ -102,6 +103,7 @@ void GetSetElimination(IR::Block& block) {
                 break;
         }
     }
+#endif
 }
 
 } // namespace Optimization
diff --git a/src/ir_opt/verification_pass.cpp b/src/ir_opt/verification_pass.cpp
index 85fb3799..8668a619 100644
--- a/src/ir_opt/verification_pass.cpp
+++ b/src/ir_opt/verification_pass.cpp
@@ -12,9 +12,11 @@ namespace Dynarmic {
 namespace Optimization {
 
 void VerificationPass(const IR::Block& block) {
+#if 0
     for (const auto& inst : block.instructions) {
         inst->AssertValid();
     }
+#endif
 }
 
 } // namespace Optimization