From 2d48a7b4d0666ad16d03a22d85712617a0849046 Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Sat, 9 Jan 2021 03:30:07 -0300
Subject: shader: Initial recompiler work

---
 src/shader_recompiler/frontend/ir/attribute.cpp    |  447 ++++++++
 src/shader_recompiler/frontend/ir/attribute.h      |  242 +++++
 src/shader_recompiler/frontend/ir/basic_block.cpp  |  142 +++
 src/shader_recompiler/frontend/ir/basic_block.h    |  134 +++
 src/shader_recompiler/frontend/ir/condition.cpp    |   31 +
 src/shader_recompiler/frontend/ir/condition.h      |   60 ++
 src/shader_recompiler/frontend/ir/flow_test.cpp    |   83 ++
 src/shader_recompiler/frontend/ir/flow_test.h      |   61 ++
 src/shader_recompiler/frontend/ir/ir_emitter.cpp   |  533 ++++++++++
 src/shader_recompiler/frontend/ir/ir_emitter.h     |  123 +++
 .../frontend/ir/microinstruction.cpp               |  189 ++++
 .../frontend/ir/microinstruction.h                 |   82 ++
 src/shader_recompiler/frontend/ir/opcode.cpp       |   67 ++
 src/shader_recompiler/frontend/ir/opcode.h         |   44 +
 src/shader_recompiler/frontend/ir/opcode.inc       |  142 +++
 src/shader_recompiler/frontend/ir/pred.h           |   28 +
 src/shader_recompiler/frontend/ir/reg.h            |  314 ++++++
 src/shader_recompiler/frontend/ir/type.cpp         |   36 +
 src/shader_recompiler/frontend/ir/type.h           |   47 +
 src/shader_recompiler/frontend/ir/value.cpp        |  124 +++
 src/shader_recompiler/frontend/ir/value.h          |   98 ++
 .../frontend/maxwell/control_flow.cpp              |  531 ++++++++++
 .../frontend/maxwell/control_flow.h                |  137 +++
 src/shader_recompiler/frontend/maxwell/decode.cpp  |  149 +++
 src/shader_recompiler/frontend/maxwell/decode.h    |   14 +
 .../frontend/maxwell/instruction.h                 |   62 ++
 src/shader_recompiler/frontend/maxwell/location.h  |  106 ++
 src/shader_recompiler/frontend/maxwell/maxwell.inc |  285 +++++
 src/shader_recompiler/frontend/maxwell/opcode.cpp  |   26 +
 src/shader_recompiler/frontend/maxwell/opcode.h    |   30 +
 src/shader_recompiler/frontend/maxwell/program.cpp |   69 ++
 src/shader_recompiler/frontend/maxwell/program.h   |   39 +
 .../frontend/maxwell/termination_code.cpp          |   79 ++
 .../frontend/maxwell/termination_code.h            |   16 +
 .../frontend/maxwell/translate/impl/exit.cpp       |   15 +
 .../impl/floating_point_conversion_integer.cpp     |  133 +++
 .../impl/floating_point_multi_function.cpp         |   71 ++
 .../frontend/maxwell/translate/impl/impl.cpp       |   79 ++
 .../frontend/maxwell/translate/impl/impl.h         |  316 ++++++
 .../translate/impl/load_store_attribute.cpp        |   92 ++
 .../maxwell/translate/impl/load_store_memory.cpp   |   90 ++
 .../maxwell/translate/impl/not_implemented.cpp     | 1105 ++++++++++++++++++++
 .../maxwell/translate/impl/register_move.cpp       |   45 +
 .../frontend/maxwell/translate/translate.cpp       |   50 +
 .../frontend/maxwell/translate/translate.h         |   16 +
 45 files changed, 6582 insertions(+)
 create mode 100644 src/shader_recompiler/frontend/ir/attribute.cpp
 create mode 100644 src/shader_recompiler/frontend/ir/attribute.h
 create mode 100644 src/shader_recompiler/frontend/ir/basic_block.cpp
 create mode 100644 src/shader_recompiler/frontend/ir/basic_block.h
 create mode 100644 src/shader_recompiler/frontend/ir/condition.cpp
 create mode 100644 src/shader_recompiler/frontend/ir/condition.h
 create mode 100644 src/shader_recompiler/frontend/ir/flow_test.cpp
 create mode 100644 src/shader_recompiler/frontend/ir/flow_test.h
 create mode 100644 src/shader_recompiler/frontend/ir/ir_emitter.cpp
 create mode 100644 src/shader_recompiler/frontend/ir/ir_emitter.h
 create mode 100644 src/shader_recompiler/frontend/ir/microinstruction.cpp
 create mode 100644 src/shader_recompiler/frontend/ir/microinstruction.h
 create mode 100644 src/shader_recompiler/frontend/ir/opcode.cpp
 create mode 100644 src/shader_recompiler/frontend/ir/opcode.h
 create mode 100644 src/shader_recompiler/frontend/ir/opcode.inc
 create mode 100644 src/shader_recompiler/frontend/ir/pred.h
 create mode 100644 src/shader_recompiler/frontend/ir/reg.h
 create mode 100644 src/shader_recompiler/frontend/ir/type.cpp
 create mode 100644 src/shader_recompiler/frontend/ir/type.h
 create mode 100644 src/shader_recompiler/frontend/ir/value.cpp
 create mode 100644 src/shader_recompiler/frontend/ir/value.h
 create mode 100644 src/shader_recompiler/frontend/maxwell/control_flow.cpp
 create mode 100644 src/shader_recompiler/frontend/maxwell/control_flow.h
 create mode 100644 src/shader_recompiler/frontend/maxwell/decode.cpp
 create mode 100644 src/shader_recompiler/frontend/maxwell/decode.h
 create mode 100644 src/shader_recompiler/frontend/maxwell/instruction.h
 create mode 100644 src/shader_recompiler/frontend/maxwell/location.h
 create mode 100644 src/shader_recompiler/frontend/maxwell/maxwell.inc
 create mode 100644 src/shader_recompiler/frontend/maxwell/opcode.cpp
 create mode 100644 src/shader_recompiler/frontend/maxwell/opcode.h
 create mode 100644 src/shader_recompiler/frontend/maxwell/program.cpp
 create mode 100644 src/shader_recompiler/frontend/maxwell/program.h
 create mode 100644 src/shader_recompiler/frontend/maxwell/termination_code.cpp
 create mode 100644 src/shader_recompiler/frontend/maxwell/termination_code.h
 create mode 100644 src/shader_recompiler/frontend/maxwell/translate/impl/exit.cpp
 create mode 100644 src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_conversion_integer.cpp
 create mode 100644 src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_multi_function.cpp
 create mode 100644 src/shader_recompiler/frontend/maxwell/translate/impl/impl.cpp
 create mode 100644 src/shader_recompiler/frontend/maxwell/translate/impl/impl.h
 create mode 100644 src/shader_recompiler/frontend/maxwell/translate/impl/load_store_attribute.cpp
 create mode 100644 src/shader_recompiler/frontend/maxwell/translate/impl/load_store_memory.cpp
 create mode 100644 src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp
 create mode 100644 src/shader_recompiler/frontend/maxwell/translate/impl/register_move.cpp
 create mode 100644 src/shader_recompiler/frontend/maxwell/translate/translate.cpp
 create mode 100644 src/shader_recompiler/frontend/maxwell/translate/translate.h

(limited to 'src/shader_recompiler/frontend')

diff --git a/src/shader_recompiler/frontend/ir/attribute.cpp b/src/shader_recompiler/frontend/ir/attribute.cpp
new file mode 100644
index 0000000000..2fb7d576ff
--- /dev/null
+++ b/src/shader_recompiler/frontend/ir/attribute.cpp
@@ -0,0 +1,447 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <fmt/format.h>
+
+#include "shader_recompiler/exception.h"
+#include "shader_recompiler/frontend/ir/attribute.h"
+
+namespace Shader::IR {
+
+bool IsGeneric(Attribute attribute) noexcept {
+    return attribute >= Attribute::Generic0X && attribute <= Attribute::Generic31X;
+}
+
+int GenericAttributeIndex(Attribute attribute) {
+    if (!IsGeneric(attribute)) {
+        throw InvalidArgument("Attribute is not generic {}", attribute);
+    }
+    return (static_cast<int>(attribute) - static_cast<int>(Attribute::Generic0X)) / 4;
+}
+
+std::string NameOf(Attribute attribute) {
+    switch (attribute) {
+    case Attribute::PrimitiveId:
+        return "PrimitiveId";
+    case Attribute::Layer:
+        return "Layer";
+    case Attribute::ViewportIndex:
+        return "ViewportIndex";
+    case Attribute::PointSize:
+        return "PointSize";
+    case Attribute::PositionX:
+        return "Position.X";
+    case Attribute::PositionY:
+        return "Position.Y";
+    case Attribute::PositionZ:
+        return "Position.Z";
+    case Attribute::PositionW:
+        return "Position.W";
+    case Attribute::Generic0X:
+        return "Generic[0].X";
+    case Attribute::Generic0Y:
+        return "Generic[0].Y";
+    case Attribute::Generic0Z:
+        return "Generic[0].Z";
+    case Attribute::Generic0W:
+        return "Generic[0].W";
+    case Attribute::Generic1X:
+        return "Generic[1].X";
+    case Attribute::Generic1Y:
+        return "Generic[1].Y";
+    case Attribute::Generic1Z:
+        return "Generic[1].Z";
+    case Attribute::Generic1W:
+        return "Generic[1].W";
+    case Attribute::Generic2X:
+        return "Generic[2].X";
+    case Attribute::Generic2Y:
+        return "Generic[2].Y";
+    case Attribute::Generic2Z:
+        return "Generic[2].Z";
+    case Attribute::Generic2W:
+        return "Generic[2].W";
+    case Attribute::Generic3X:
+        return "Generic[3].X";
+    case Attribute::Generic3Y:
+        return "Generic[3].Y";
+    case Attribute::Generic3Z:
+        return "Generic[3].Z";
+    case Attribute::Generic3W:
+        return "Generic[3].W";
+    case Attribute::Generic4X:
+        return "Generic[4].X";
+    case Attribute::Generic4Y:
+        return "Generic[4].Y";
+    case Attribute::Generic4Z:
+        return "Generic[4].Z";
+    case Attribute::Generic4W:
+        return "Generic[4].W";
+    case Attribute::Generic5X:
+        return "Generic[5].X";
+    case Attribute::Generic5Y:
+        return "Generic[5].Y";
+    case Attribute::Generic5Z:
+        return "Generic[5].Z";
+    case Attribute::Generic5W:
+        return "Generic[5].W";
+    case Attribute::Generic6X:
+        return "Generic[6].X";
+    case Attribute::Generic6Y:
+        return "Generic[6].Y";
+    case Attribute::Generic6Z:
+        return "Generic[6].Z";
+    case Attribute::Generic6W:
+        return "Generic[6].W";
+    case Attribute::Generic7X:
+        return "Generic[7].X";
+    case Attribute::Generic7Y:
+        return "Generic[7].Y";
+    case Attribute::Generic7Z:
+        return "Generic[7].Z";
+    case Attribute::Generic7W:
+        return "Generic[7].W";
+    case Attribute::Generic8X:
+        return "Generic[8].X";
+    case Attribute::Generic8Y:
+        return "Generic[8].Y";
+    case Attribute::Generic8Z:
+        return "Generic[8].Z";
+    case Attribute::Generic8W:
+        return "Generic[8].W";
+    case Attribute::Generic9X:
+        return "Generic[9].X";
+    case Attribute::Generic9Y:
+        return "Generic[9].Y";
+    case Attribute::Generic9Z:
+        return "Generic[9].Z";
+    case Attribute::Generic9W:
+        return "Generic[9].W";
+    case Attribute::Generic10X:
+        return "Generic[10].X";
+    case Attribute::Generic10Y:
+        return "Generic[10].Y";
+    case Attribute::Generic10Z:
+        return "Generic[10].Z";
+    case Attribute::Generic10W:
+        return "Generic[10].W";
+    case Attribute::Generic11X:
+        return "Generic[11].X";
+    case Attribute::Generic11Y:
+        return "Generic[11].Y";
+    case Attribute::Generic11Z:
+        return "Generic[11].Z";
+    case Attribute::Generic11W:
+        return "Generic[11].W";
+    case Attribute::Generic12X:
+        return "Generic[12].X";
+    case Attribute::Generic12Y:
+        return "Generic[12].Y";
+    case Attribute::Generic12Z:
+        return "Generic[12].Z";
+    case Attribute::Generic12W:
+        return "Generic[12].W";
+    case Attribute::Generic13X:
+        return "Generic[13].X";
+    case Attribute::Generic13Y:
+        return "Generic[13].Y";
+    case Attribute::Generic13Z:
+        return "Generic[13].Z";
+    case Attribute::Generic13W:
+        return "Generic[13].W";
+    case Attribute::Generic14X:
+        return "Generic[14].X";
+    case Attribute::Generic14Y:
+        return "Generic[14].Y";
+    case Attribute::Generic14Z:
+        return "Generic[14].Z";
+    case Attribute::Generic14W:
+        return "Generic[14].W";
+    case Attribute::Generic15X:
+        return "Generic[15].X";
+    case Attribute::Generic15Y:
+        return "Generic[15].Y";
+    case Attribute::Generic15Z:
+        return "Generic[15].Z";
+    case Attribute::Generic15W:
+        return "Generic[15].W";
+    case Attribute::Generic16X:
+        return "Generic[16].X";
+    case Attribute::Generic16Y:
+        return "Generic[16].Y";
+    case Attribute::Generic16Z:
+        return "Generic[16].Z";
+    case Attribute::Generic16W:
+        return "Generic[16].W";
+    case Attribute::Generic17X:
+        return "Generic[17].X";
+    case Attribute::Generic17Y:
+        return "Generic[17].Y";
+    case Attribute::Generic17Z:
+        return "Generic[17].Z";
+    case Attribute::Generic17W:
+        return "Generic[17].W";
+    case Attribute::Generic18X:
+        return "Generic[18].X";
+    case Attribute::Generic18Y:
+        return "Generic[18].Y";
+    case Attribute::Generic18Z:
+        return "Generic[18].Z";
+    case Attribute::Generic18W:
+        return "Generic[18].W";
+    case Attribute::Generic19X:
+        return "Generic[19].X";
+    case Attribute::Generic19Y:
+        return "Generic[19].Y";
+    case Attribute::Generic19Z:
+        return "Generic[19].Z";
+    case Attribute::Generic19W:
+        return "Generic[19].W";
+    case Attribute::Generic20X:
+        return "Generic[20].X";
+    case Attribute::Generic20Y:
+        return "Generic[20].Y";
+    case Attribute::Generic20Z:
+        return "Generic[20].Z";
+    case Attribute::Generic20W:
+        return "Generic[20].W";
+    case Attribute::Generic21X:
+        return "Generic[21].X";
+    case Attribute::Generic21Y:
+        return "Generic[21].Y";
+    case Attribute::Generic21Z:
+        return "Generic[21].Z";
+    case Attribute::Generic21W:
+        return "Generic[21].W";
+    case Attribute::Generic22X:
+        return "Generic[22].X";
+    case Attribute::Generic22Y:
+        return "Generic[22].Y";
+    case Attribute::Generic22Z:
+        return "Generic[22].Z";
+    case Attribute::Generic22W:
+        return "Generic[22].W";
+    case Attribute::Generic23X:
+        return "Generic[23].X";
+    case Attribute::Generic23Y:
+        return "Generic[23].Y";
+    case Attribute::Generic23Z:
+        return "Generic[23].Z";
+    case Attribute::Generic23W:
+        return "Generic[23].W";
+    case Attribute::Generic24X:
+        return "Generic[24].X";
+    case Attribute::Generic24Y:
+        return "Generic[24].Y";
+    case Attribute::Generic24Z:
+        return "Generic[24].Z";
+    case Attribute::Generic24W:
+        return "Generic[24].W";
+    case Attribute::Generic25X:
+        return "Generic[25].X";
+    case Attribute::Generic25Y:
+        return "Generic[25].Y";
+    case Attribute::Generic25Z:
+        return "Generic[25].Z";
+    case Attribute::Generic25W:
+        return "Generic[25].W";
+    case Attribute::Generic26X:
+        return "Generic[26].X";
+    case Attribute::Generic26Y:
+        return "Generic[26].Y";
+    case Attribute::Generic26Z:
+        return "Generic[26].Z";
+    case Attribute::Generic26W:
+        return "Generic[26].W";
+    case Attribute::Generic27X:
+        return "Generic[27].X";
+    case Attribute::Generic27Y:
+        return "Generic[27].Y";
+    case Attribute::Generic27Z:
+        return "Generic[27].Z";
+    case Attribute::Generic27W:
+        return "Generic[27].W";
+    case Attribute::Generic28X:
+        return "Generic[28].X";
+    case Attribute::Generic28Y:
+        return "Generic[28].Y";
+    case Attribute::Generic28Z:
+        return "Generic[28].Z";
+    case Attribute::Generic28W:
+        return "Generic[28].W";
+    case Attribute::Generic29X:
+        return "Generic[29].X";
+    case Attribute::Generic29Y:
+        return "Generic[29].Y";
+    case Attribute::Generic29Z:
+        return "Generic[29].Z";
+    case Attribute::Generic29W:
+        return "Generic[29].W";
+    case Attribute::Generic30X:
+        return "Generic[30].X";
+    case Attribute::Generic30Y:
+        return "Generic[30].Y";
+    case Attribute::Generic30Z:
+        return "Generic[30].Z";
+    case Attribute::Generic30W:
+        return "Generic[30].W";
+    case Attribute::Generic31X:
+        return "Generic[31].X";
+    case Attribute::Generic31Y:
+        return "Generic[31].Y";
+    case Attribute::Generic31Z:
+        return "Generic[31].Z";
+    case Attribute::Generic31W:
+        return "Generic[31].W";
+    case Attribute::ColorFrontDiffuseR:
+        return "ColorFrontDiffuse.R";
+    case Attribute::ColorFrontDiffuseG:
+        return "ColorFrontDiffuse.G";
+    case Attribute::ColorFrontDiffuseB:
+        return "ColorFrontDiffuse.B";
+    case Attribute::ColorFrontDiffuseA:
+        return "ColorFrontDiffuse.A";
+    case Attribute::ColorFrontSpecularR:
+        return "ColorFrontSpecular.R";
+    case Attribute::ColorFrontSpecularG:
+        return "ColorFrontSpecular.G";
+    case Attribute::ColorFrontSpecularB:
+        return "ColorFrontSpecular.B";
+    case Attribute::ColorFrontSpecularA:
+        return "ColorFrontSpecular.A";
+    case Attribute::ColorBackDiffuseR:
+        return "ColorBackDiffuse.R";
+    case Attribute::ColorBackDiffuseG:
+        return "ColorBackDiffuse.G";
+    case Attribute::ColorBackDiffuseB:
+        return "ColorBackDiffuse.B";
+    case Attribute::ColorBackDiffuseA:
+        return "ColorBackDiffuse.A";
+    case Attribute::ColorBackSpecularR:
+        return "ColorBackSpecular.R";
+    case Attribute::ColorBackSpecularG:
+        return "ColorBackSpecular.G";
+    case Attribute::ColorBackSpecularB:
+        return "ColorBackSpecular.B";
+    case Attribute::ColorBackSpecularA:
+        return "ColorBackSpecular.A";
+    case Attribute::ClipDistance0:
+        return "ClipDistance[0]";
+    case Attribute::ClipDistance1:
+        return "ClipDistance[1]";
+    case Attribute::ClipDistance2:
+        return "ClipDistance[2]";
+    case Attribute::ClipDistance3:
+        return "ClipDistance[3]";
+    case Attribute::ClipDistance4:
+        return "ClipDistance[4]";
+    case Attribute::ClipDistance5:
+        return "ClipDistance[5]";
+    case Attribute::ClipDistance6:
+        return "ClipDistance[6]";
+    case Attribute::ClipDistance7:
+        return "ClipDistance[7]";
+    case Attribute::PointSpriteS:
+        return "PointSprite.S";
+    case Attribute::PointSpriteT:
+        return "PointSprite.T";
+    case Attribute::FogCoordinate:
+        return "FogCoordinate";
+    case Attribute::TessellationEvaluationPointU:
+        return "TessellationEvaluationPoint.U";
+    case Attribute::TessellationEvaluationPointV:
+        return "TessellationEvaluationPoint.V";
+    case Attribute::InstanceId:
+        return "InstanceId";
+    case Attribute::VertexId:
+        return "VertexId";
+    case Attribute::FixedFncTexture0S:
+        return "FixedFncTexture[0].S";
+    case Attribute::FixedFncTexture0T:
+        return "FixedFncTexture[0].T";
+    case Attribute::FixedFncTexture0R:
+        return "FixedFncTexture[0].R";
+    case Attribute::FixedFncTexture0Q:
+        return "FixedFncTexture[0].Q";
+    case Attribute::FixedFncTexture1S:
+        return "FixedFncTexture[1].S";
+    case Attribute::FixedFncTexture1T:
+        return "FixedFncTexture[1].T";
+    case Attribute::FixedFncTexture1R:
+        return "FixedFncTexture[1].R";
+    case Attribute::FixedFncTexture1Q:
+        return "FixedFncTexture[1].Q";
+    case Attribute::FixedFncTexture2S:
+        return "FixedFncTexture[2].S";
+    case Attribute::FixedFncTexture2T:
+        return "FixedFncTexture[2].T";
+    case Attribute::FixedFncTexture2R:
+        return "FixedFncTexture[2].R";
+    case Attribute::FixedFncTexture2Q:
+        return "FixedFncTexture[2].Q";
+    case Attribute::FixedFncTexture3S:
+        return "FixedFncTexture[3].S";
+    case Attribute::FixedFncTexture3T:
+        return "FixedFncTexture[3].T";
+    case Attribute::FixedFncTexture3R:
+        return "FixedFncTexture[3].R";
+    case Attribute::FixedFncTexture3Q:
+        return "FixedFncTexture[3].Q";
+    case Attribute::FixedFncTexture4S:
+        return "FixedFncTexture[4].S";
+    case Attribute::FixedFncTexture4T:
+        return "FixedFncTexture[4].T";
+    case Attribute::FixedFncTexture4R:
+        return "FixedFncTexture[4].R";
+    case Attribute::FixedFncTexture4Q:
+        return "FixedFncTexture[4].Q";
+    case Attribute::FixedFncTexture5S:
+        return "FixedFncTexture[5].S";
+    case Attribute::FixedFncTexture5T:
+        return "FixedFncTexture[5].T";
+    case Attribute::FixedFncTexture5R:
+        return "FixedFncTexture[5].R";
+    case Attribute::FixedFncTexture5Q:
+        return "FixedFncTexture[5].Q";
+    case Attribute::FixedFncTexture6S:
+        return "FixedFncTexture[6].S";
+    case Attribute::FixedFncTexture6T:
+        return "FixedFncTexture[6].T";
+    case Attribute::FixedFncTexture6R:
+        return "FixedFncTexture[6].R";
+    case Attribute::FixedFncTexture6Q:
+        return "FixedFncTexture[6].Q";
+    case Attribute::FixedFncTexture7S:
+        return "FixedFncTexture[7].S";
+    case Attribute::FixedFncTexture7T:
+        return "FixedFncTexture[7].T";
+    case Attribute::FixedFncTexture7R:
+        return "FixedFncTexture[7].R";
+    case Attribute::FixedFncTexture7Q:
+        return "FixedFncTexture[7].Q";
+    case Attribute::FixedFncTexture8S:
+        return "FixedFncTexture[8].S";
+    case Attribute::FixedFncTexture8T:
+        return "FixedFncTexture[8].T";
+    case Attribute::FixedFncTexture8R:
+        return "FixedFncTexture[8].R";
+    case Attribute::FixedFncTexture8Q:
+        return "FixedFncTexture[8].Q";
+    case Attribute::FixedFncTexture9S:
+        return "FixedFncTexture[9].S";
+    case Attribute::FixedFncTexture9T:
+        return "FixedFncTexture[9].T";
+    case Attribute::FixedFncTexture9R:
+        return "FixedFncTexture[9].R";
+    case Attribute::FixedFncTexture9Q:
+        return "FixedFncTexture[9].Q";
+    case Attribute::ViewportMask:
+        return "ViewportMask";
+    case Attribute::FrontFace:
+        return "FrontFace";
+    }
+    return fmt::format("<reserved attribute {}>", static_cast<int>(attribute));
+}
+
+} // namespace Shader::IR
\ No newline at end of file
diff --git a/src/shader_recompiler/frontend/ir/attribute.h b/src/shader_recompiler/frontend/ir/attribute.h
new file mode 100644
index 0000000000..bb2cad6afd
--- /dev/null
+++ b/src/shader_recompiler/frontend/ir/attribute.h
@@ -0,0 +1,242 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <fmt/format.h>
+
+#include "common/common_types.h"
+
+namespace Shader::IR {
+
+enum class Attribute : u64 {
+    PrimitiveId = 24,
+    Layer = 25,
+    ViewportIndex = 26,
+    PointSize = 27,
+    PositionX = 28,
+    PositionY = 29,
+    PositionZ = 30,
+    PositionW = 31,
+    Generic0X = 32,
+    Generic0Y = 33,
+    Generic0Z = 34,
+    Generic0W = 35,
+    Generic1X = 36,
+    Generic1Y = 37,
+    Generic1Z = 38,
+    Generic1W = 39,
+    Generic2X = 40,
+    Generic2Y = 41,
+    Generic2Z = 42,
+    Generic2W = 43,
+    Generic3X = 44,
+    Generic3Y = 45,
+    Generic3Z = 46,
+    Generic3W = 47,
+    Generic4X = 48,
+    Generic4Y = 49,
+    Generic4Z = 50,
+    Generic4W = 51,
+    Generic5X = 52,
+    Generic5Y = 53,
+    Generic5Z = 54,
+    Generic5W = 55,
+    Generic6X = 56,
+    Generic6Y = 57,
+    Generic6Z = 58,
+    Generic6W = 59,
+    Generic7X = 60,
+    Generic7Y = 61,
+    Generic7Z = 62,
+    Generic7W = 63,
+    Generic8X = 64,
+    Generic8Y = 65,
+    Generic8Z = 66,
+    Generic8W = 67,
+    Generic9X = 68,
+    Generic9Y = 69,
+    Generic9Z = 70,
+    Generic9W = 71,
+    Generic10X = 72,
+    Generic10Y = 73,
+    Generic10Z = 74,
+    Generic10W = 75,
+    Generic11X = 76,
+    Generic11Y = 77,
+    Generic11Z = 78,
+    Generic11W = 79,
+    Generic12X = 80,
+    Generic12Y = 81,
+    Generic12Z = 82,
+    Generic12W = 83,
+    Generic13X = 84,
+    Generic13Y = 85,
+    Generic13Z = 86,
+    Generic13W = 87,
+    Generic14X = 88,
+    Generic14Y = 89,
+    Generic14Z = 90,
+    Generic14W = 91,
+    Generic15X = 92,
+    Generic15Y = 93,
+    Generic15Z = 94,
+    Generic15W = 95,
+    Generic16X = 96,
+    Generic16Y = 97,
+    Generic16Z = 98,
+    Generic16W = 99,
+    Generic17X = 100,
+    Generic17Y = 101,
+    Generic17Z = 102,
+    Generic17W = 103,
+    Generic18X = 104,
+    Generic18Y = 105,
+    Generic18Z = 106,
+    Generic18W = 107,
+    Generic19X = 108,
+    Generic19Y = 109,
+    Generic19Z = 110,
+    Generic19W = 111,
+    Generic20X = 112,
+    Generic20Y = 113,
+    Generic20Z = 114,
+    Generic20W = 115,
+    Generic21X = 116,
+    Generic21Y = 117,
+    Generic21Z = 118,
+    Generic21W = 119,
+    Generic22X = 120,
+    Generic22Y = 121,
+    Generic22Z = 122,
+    Generic22W = 123,
+    Generic23X = 124,
+    Generic23Y = 125,
+    Generic23Z = 126,
+    Generic23W = 127,
+    Generic24X = 128,
+    Generic24Y = 129,
+    Generic24Z = 130,
+    Generic24W = 131,
+    Generic25X = 132,
+    Generic25Y = 133,
+    Generic25Z = 134,
+    Generic25W = 135,
+    Generic26X = 136,
+    Generic26Y = 137,
+    Generic26Z = 138,
+    Generic26W = 139,
+    Generic27X = 140,
+    Generic27Y = 141,
+    Generic27Z = 142,
+    Generic27W = 143,
+    Generic28X = 144,
+    Generic28Y = 145,
+    Generic28Z = 146,
+    Generic28W = 147,
+    Generic29X = 148,
+    Generic29Y = 149,
+    Generic29Z = 150,
+    Generic29W = 151,
+    Generic30X = 152,
+    Generic30Y = 153,
+    Generic30Z = 154,
+    Generic30W = 155,
+    Generic31X = 156,
+    Generic31Y = 157,
+    Generic31Z = 158,
+    Generic31W = 159,
+    ColorFrontDiffuseR = 160,
+    ColorFrontDiffuseG = 161,
+    ColorFrontDiffuseB = 162,
+    ColorFrontDiffuseA = 163,
+    ColorFrontSpecularR = 164,
+    ColorFrontSpecularG = 165,
+    ColorFrontSpecularB = 166,
+    ColorFrontSpecularA = 167,
+    ColorBackDiffuseR = 168,
+    ColorBackDiffuseG = 169,
+    ColorBackDiffuseB = 170,
+    ColorBackDiffuseA = 171,
+    ColorBackSpecularR = 172,
+    ColorBackSpecularG = 173,
+    ColorBackSpecularB = 174,
+    ColorBackSpecularA = 175,
+    ClipDistance0 = 176,
+    ClipDistance1 = 177,
+    ClipDistance2 = 178,
+    ClipDistance3 = 179,
+    ClipDistance4 = 180,
+    ClipDistance5 = 181,
+    ClipDistance6 = 182,
+    ClipDistance7 = 183,
+    PointSpriteS = 184,
+    PointSpriteT = 185,
+    FogCoordinate = 186,
+    TessellationEvaluationPointU = 188,
+    TessellationEvaluationPointV = 189,
+    InstanceId = 190,
+    VertexId = 191,
+    FixedFncTexture0S = 192,
+    FixedFncTexture0T = 193,
+    FixedFncTexture0R = 194,
+    FixedFncTexture0Q = 195,
+    FixedFncTexture1S = 196,
+    FixedFncTexture1T = 197,
+    FixedFncTexture1R = 198,
+    FixedFncTexture1Q = 199,
+    FixedFncTexture2S = 200,
+    FixedFncTexture2T = 201,
+    FixedFncTexture2R = 202,
+    FixedFncTexture2Q = 203,
+    FixedFncTexture3S = 204,
+    FixedFncTexture3T = 205,
+    FixedFncTexture3R = 206,
+    FixedFncTexture3Q = 207,
+    FixedFncTexture4S = 208,
+    FixedFncTexture4T = 209,
+    FixedFncTexture4R = 210,
+    FixedFncTexture4Q = 211,
+    FixedFncTexture5S = 212,
+    FixedFncTexture5T = 213,
+    FixedFncTexture5R = 214,
+    FixedFncTexture5Q = 215,
+    FixedFncTexture6S = 216,
+    FixedFncTexture6T = 217,
+    FixedFncTexture6R = 218,
+    FixedFncTexture6Q = 219,
+    FixedFncTexture7S = 220,
+    FixedFncTexture7T = 221,
+    FixedFncTexture7R = 222,
+    FixedFncTexture7Q = 223,
+    FixedFncTexture8S = 224,
+    FixedFncTexture8T = 225,
+    FixedFncTexture8R = 226,
+    FixedFncTexture8Q = 227,
+    FixedFncTexture9S = 228,
+    FixedFncTexture9T = 229,
+    FixedFncTexture9R = 230,
+    FixedFncTexture9Q = 231,
+    ViewportMask = 232,
+    FrontFace = 255,
+};
+
+[[nodiscard]] bool IsGeneric(Attribute attribute) noexcept;
+
+[[nodiscard]] int GenericAttributeIndex(Attribute attribute);
+
+[[nodiscard]] std::string NameOf(Attribute attribute);
+
+} // namespace Shader::IR
+
+template <>
+struct fmt::formatter<Shader::IR::Attribute> {
+    constexpr auto parse(format_parse_context& ctx) {
+        return ctx.begin();
+    }
+    template <typename FormatContext>
+    auto format(const Shader::IR::Attribute& attribute, FormatContext& ctx) {
+        return fmt::format_to(ctx.out(), "{}", Shader::IR::NameOf(attribute));
+    }
+};
diff --git a/src/shader_recompiler/frontend/ir/basic_block.cpp b/src/shader_recompiler/frontend/ir/basic_block.cpp
new file mode 100644
index 0000000000..0406726ad9
--- /dev/null
+++ b/src/shader_recompiler/frontend/ir/basic_block.cpp
@@ -0,0 +1,142 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <initializer_list>
+#include <map>
+#include <memory>
+
+#include "common/bit_cast.h"
+#include "common/common_types.h"
+#include "shader_recompiler/frontend/ir/basic_block.h"
+#include "shader_recompiler/frontend/ir/value.h"
+
+namespace Shader::IR {
+
+Block::Block(u32 begin, u32 end) : location_begin{begin}, location_end{end} {}
+
+Block::~Block() = default;
+
+void Block::AppendNewInst(Opcode op, std::initializer_list<Value> args) {
+    PrependNewInst(end(), op, args);
+}
+
+Block::iterator Block::PrependNewInst(iterator insertion_point, Opcode op,
+                                      std::initializer_list<Value> args) {
+    Inst* const inst{std::construct_at(instruction_alloc_pool.allocate(), op)};
+    const auto result_it{instructions.insert(insertion_point, *inst)};
+
+    if (inst->NumArgs() != args.size()) {
+        throw InvalidArgument("Invalid number of arguments {} in {}", args.size(), op);
+    }
+    std::ranges::for_each(args, [inst, index = size_t{0}](const Value& arg) mutable {
+        inst->SetArg(index, arg);
+        ++index;
+    });
+    return result_it;
+}
+
+u32 Block::LocationBegin() const noexcept {
+    return location_begin;
+}
+
+u32 Block::LocationEnd() const noexcept {
+    return location_end;
+}
+
+Block::InstructionList& Block::Instructions() noexcept {
+    return instructions;
+}
+
+const Block::InstructionList& Block::Instructions() const noexcept {
+    return instructions;
+}
+
+static std::string ArgToIndex(const std::map<const Block*, size_t>& block_to_index,
+                              const std::map<const Inst*, size_t>& inst_to_index,
+                              const Value& arg) {
+    if (arg.IsEmpty()) {
+        return "<null>";
+    }
+    if (arg.IsLabel()) {
+        if (const auto it{block_to_index.find(arg.Label())}; it != block_to_index.end()) {
+            return fmt::format("{{Block ${}}}", it->second);
+        }
+        return fmt::format("$<unknown block {:016x}>", reinterpret_cast<u64>(arg.Label()));
+    }
+    if (!arg.IsImmediate()) {
+        if (const auto it{inst_to_index.find(arg.Inst())}; it != inst_to_index.end()) {
+            return fmt::format("%{}", it->second);
+        }
+        return fmt::format("%<unknown inst {:016x}>", reinterpret_cast<u64>(arg.Inst()));
+    }
+    switch (arg.Type()) {
+    case Type::U1:
+        return fmt::format("#{}", arg.U1() ? '1' : '0');
+    case Type::U8:
+        return fmt::format("#{}", arg.U8());
+    case Type::U16:
+        return fmt::format("#{}", arg.U16());
+    case Type::U32:
+        return fmt::format("#{}", arg.U32());
+    case Type::U64:
+        return fmt::format("#{}", arg.U64());
+    case Type::Reg:
+        return fmt::format("{}", arg.Reg());
+    case Type::Pred:
+        return fmt::format("{}", arg.Pred());
+    case Type::Attribute:
+        return fmt::format("{}", arg.Attribute());
+    default:
+        return "<unknown immediate type>";
+    }
+}
+
+std::string DumpBlock(const Block& block) {
+    size_t inst_index{0};
+    std::map<const Inst*, size_t> inst_to_index;
+    return DumpBlock(block, {}, inst_to_index, inst_index);
+}
+
+std::string DumpBlock(const Block& block, const std::map<const Block*, size_t>& block_to_index,
+                      std::map<const Inst*, size_t>& inst_to_index, size_t& inst_index) {
+    std::string ret{"Block"};
+    if (const auto it{block_to_index.find(&block)}; it != block_to_index.end()) {
+        ret += fmt::format(" ${}", it->second);
+    }
+    ret += fmt::format(": begin={:04x} end={:04x}\n", block.LocationBegin(), block.LocationEnd());
+
+    for (const Inst& inst : block) {
+        const Opcode op{inst.Opcode()};
+        ret += fmt::format("[{:016x}] ", reinterpret_cast<u64>(&inst));
+        if (TypeOf(op) != Type::Void) {
+            ret += fmt::format("%{:<5} = {}", inst_index, op);
+        } else {
+            ret += fmt::format("         {}", op); // '%00000 = ' -> 1 + 5 + 3 = 9 spaces
+        }
+        const size_t arg_count{NumArgsOf(op)};
+        for (size_t arg_index = 0; arg_index < arg_count; ++arg_index) {
+            const Value arg{inst.Arg(arg_index)};
+            ret += arg_index != 0 ? ", " : " ";
+            ret += ArgToIndex(block_to_index, inst_to_index, arg);
+
+            const Type actual_type{arg.Type()};
+            const Type expected_type{ArgTypeOf(op, arg_index)};
+            if (!AreTypesCompatible(actual_type, expected_type)) {
+                ret += fmt::format("<type error: {} != {}>", actual_type, expected_type);
+            }
+        }
+        if (TypeOf(op) != Type::Void) {
+            ret += fmt::format(" (uses: {})\n", inst.UseCount());
+        } else {
+            ret += '\n';
+        }
+
+        inst_to_index.emplace(&inst, inst_index);
+        ++inst_index;
+    }
+    return ret;
+}
+
+} // namespace Shader::IR
diff --git a/src/shader_recompiler/frontend/ir/basic_block.h b/src/shader_recompiler/frontend/ir/basic_block.h
new file mode 100644
index 0000000000..3ed2eb9571
--- /dev/null
+++ b/src/shader_recompiler/frontend/ir/basic_block.h
@@ -0,0 +1,134 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <initializer_list>
+#include <map>
+
+#include <boost/intrusive/list.hpp>
+#include <boost/pool/pool_alloc.hpp>
+
+#include "shader_recompiler/frontend/ir/microinstruction.h"
+
+namespace Shader::IR {
+
+class Block {
+public:
+    using InstructionList = boost::intrusive::list<Inst>;
+    using size_type = InstructionList::size_type;
+    using iterator = InstructionList::iterator;
+    using const_iterator = InstructionList::const_iterator;
+    using reverse_iterator = InstructionList::reverse_iterator;
+    using const_reverse_iterator = InstructionList::const_reverse_iterator;
+
+    explicit Block(u32 begin, u32 end);
+    ~Block();
+
+    Block(const Block&) = delete;
+    Block& operator=(const Block&) = delete;
+
+    Block(Block&&) = default;
+    Block& operator=(Block&&) = default;
+
+    /// Appends a new instruction to the end of this basic block.
+    void AppendNewInst(Opcode op, std::initializer_list<Value> args);
+
+    /// Prepends a new instruction to this basic block before the insertion point.
+    iterator PrependNewInst(iterator insertion_point, Opcode op, std::initializer_list<Value> args);
+
+    /// Gets the starting location of this basic block.
+    [[nodiscard]] u32 LocationBegin() const noexcept;
+    /// Gets the end location for this basic block.
+    [[nodiscard]] u32 LocationEnd() const noexcept;
+
+    /// Gets a mutable reference to the instruction list for this basic block.
+    InstructionList& Instructions() noexcept;
+    /// Gets an immutable reference to the instruction list for this basic block.
+    const InstructionList& Instructions() const noexcept;
+
+    [[nodiscard]] bool empty() const {
+        return instructions.empty();
+    }
+    [[nodiscard]] size_type size() const {
+        return instructions.size();
+    }
+
+    [[nodiscard]] Inst& front() {
+        return instructions.front();
+    }
+    [[nodiscard]] const Inst& front() const {
+        return instructions.front();
+    }
+
+    [[nodiscard]] Inst& back() {
+        return instructions.back();
+    }
+    [[nodiscard]] const Inst& back() const {
+        return instructions.back();
+    }
+
+    [[nodiscard]] iterator begin() {
+        return instructions.begin();
+    }
+    [[nodiscard]] const_iterator begin() const {
+        return instructions.begin();
+    }
+    [[nodiscard]] iterator end() {
+        return instructions.end();
+    }
+    [[nodiscard]] const_iterator end() const {
+        return instructions.end();
+    }
+
+    [[nodiscard]] reverse_iterator rbegin() {
+        return instructions.rbegin();
+    }
+    [[nodiscard]] const_reverse_iterator rbegin() const {
+        return instructions.rbegin();
+    }
+    [[nodiscard]] reverse_iterator rend() {
+        return instructions.rend();
+    }
+    [[nodiscard]] const_reverse_iterator rend() const {
+        return instructions.rend();
+    }
+
+    [[nodiscard]] const_iterator cbegin() const {
+        return instructions.cbegin();
+    }
+    [[nodiscard]] const_iterator cend() const {
+        return instructions.cend();
+    }
+
+    [[nodiscard]] const_reverse_iterator crbegin() const {
+        return instructions.crbegin();
+    }
+    [[nodiscard]] const_reverse_iterator crend() const {
+        return instructions.crend();
+    }
+
+private:
+    /// Starting location of this block
+    u32 location_begin;
+    /// End location of this block
+    u32 location_end;
+
+    /// List of instructions in this block.
+    InstructionList instructions;
+
+    /// Memory pool for instruction list
+    boost::fast_pool_allocator<Inst, boost::default_user_allocator_malloc_free,
+                               boost::details::pool::null_mutex>
+        instruction_alloc_pool;
+};
+
+[[nodiscard]] std::string DumpBlock(const Block& block);
+
+[[nodiscard]] std::string DumpBlock(const Block& block,
+                                    const std::map<const Block*, size_t>& block_to_index,
+                                    std::map<const Inst*, size_t>& inst_to_index,
+                                    size_t& inst_index);
+
+} // namespace Shader::IR
diff --git a/src/shader_recompiler/frontend/ir/condition.cpp b/src/shader_recompiler/frontend/ir/condition.cpp
new file mode 100644
index 0000000000..edff35dc77
--- /dev/null
+++ b/src/shader_recompiler/frontend/ir/condition.cpp
@@ -0,0 +1,31 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <string>
+
+#include <fmt/format.h>
+
+#include "shader_recompiler/frontend/ir/condition.h"
+
+namespace Shader::IR {
+
+std::string NameOf(Condition condition) {
+    std::string ret;
+    if (condition.FlowTest() != FlowTest::T) {
+        ret = fmt::to_string(condition.FlowTest());
+    }
+    const auto [pred, negated]{condition.Pred()};
+    if (pred != Pred::PT || negated) {
+        if (!ret.empty()) {
+            ret += '&';
+        }
+        if (negated) {
+            ret += '!';
+        }
+        ret += fmt::to_string(pred);
+    }
+    return ret;
+}
+
+} // namespace Shader::IR
diff --git a/src/shader_recompiler/frontend/ir/condition.h b/src/shader_recompiler/frontend/ir/condition.h
new file mode 100644
index 0000000000..52737025c9
--- /dev/null
+++ b/src/shader_recompiler/frontend/ir/condition.h
@@ -0,0 +1,60 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include <compare>
+
+#include <fmt/format.h>
+
+#include "common/common_types.h"
+#include "shader_recompiler/frontend/ir/flow_test.h"
+#include "shader_recompiler/frontend/ir/pred.h"
+
+namespace Shader::IR {
+
+class Condition {
+public:
+    Condition() noexcept = default;
+
+    explicit Condition(FlowTest flow_test_, Pred pred_, bool pred_negated_ = false) noexcept
+        : flow_test{static_cast<u16>(flow_test_)}, pred{static_cast<u8>(pred_)},
+          pred_negated{pred_negated_ ? u8{1} : u8{0}} {}
+
+    explicit Condition(Pred pred_, bool pred_negated_ = false) noexcept
+        : Condition(FlowTest::T, pred_, pred_negated_) {}
+
+    Condition(bool value) : Condition(Pred::PT, !value) {}
+
+    auto operator<=>(const Condition&) const noexcept = default;
+
+    [[nodiscard]] IR::FlowTest FlowTest() const noexcept {
+        return static_cast<IR::FlowTest>(flow_test);
+    }
+
+    [[nodiscard]] std::pair<IR::Pred, bool> Pred() const noexcept {
+        return {static_cast<IR::Pred>(pred), pred_negated != 0};
+    }
+
+private:
+    u16 flow_test;
+    u8 pred;
+    u8 pred_negated;
+};
+
+std::string NameOf(Condition condition);
+
+} // namespace Shader::IR
+
+template <>
+struct fmt::formatter<Shader::IR::Condition> {
+    constexpr auto parse(format_parse_context& ctx) {
+        return ctx.begin();
+    }
+    template <typename FormatContext>
+    auto format(const Shader::IR::Condition& cond, FormatContext& ctx) {
+        return fmt::format_to(ctx.out(), "{}", Shader::IR::NameOf(cond));
+    }
+};
diff --git a/src/shader_recompiler/frontend/ir/flow_test.cpp b/src/shader_recompiler/frontend/ir/flow_test.cpp
new file mode 100644
index 0000000000..6ebb4ad893
--- /dev/null
+++ b/src/shader_recompiler/frontend/ir/flow_test.cpp
@@ -0,0 +1,83 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <string>
+
+#include <fmt/format.h>
+
+#include "shader_recompiler/frontend/ir/flow_test.h"
+
+namespace Shader::IR {
+
+std::string NameOf(FlowTest flow_test) {
+    switch (flow_test) {
+    case FlowTest::F:
+        return "F";
+    case FlowTest::LT:
+        return "LT";
+    case FlowTest::EQ:
+        return "EQ";
+    case FlowTest::LE:
+        return "LE";
+    case FlowTest::GT:
+        return "GT";
+    case FlowTest::NE:
+        return "NE";
+    case FlowTest::GE:
+        return "GE";
+    case FlowTest::NUM:
+        return "NUM";
+    case FlowTest::NaN:
+        return "NAN";
+    case FlowTest::LTU:
+        return "LTU";
+    case FlowTest::EQU:
+        return "EQU";
+    case FlowTest::LEU:
+        return "LEU";
+    case FlowTest::GTU:
+        return "GTU";
+    case FlowTest::NEU:
+        return "NEU";
+    case FlowTest::GEU:
+        return "GEU";
+    case FlowTest::T:
+        return "T";
+    case FlowTest::OFF:
+        return "OFF";
+    case FlowTest::LO:
+        return "LO";
+    case FlowTest::SFF:
+        return "SFF";
+    case FlowTest::LS:
+        return "LS";
+    case FlowTest::HI:
+        return "HI";
+    case FlowTest::SFT:
+        return "SFT";
+    case FlowTest::HS:
+        return "HS";
+    case FlowTest::OFT:
+        return "OFT";
+    case FlowTest::CSM_TA:
+        return "CSM_TA";
+    case FlowTest::CSM_TR:
+        return "CSM_TR";
+    case FlowTest::CSM_MX:
+        return "CSM_MX";
+    case FlowTest::FCSM_TA:
+        return "FCSM_TA";
+    case FlowTest::FCSM_TR:
+        return "FCSM_TR";
+    case FlowTest::FCSM_MX:
+        return "FCSM_MX";
+    case FlowTest::RLE:
+        return "RLE";
+    case FlowTest::RGT:
+        return "RGT";
+    }
+    return fmt::format("<invalid flow test {}>", static_cast<int>(flow_test));
+}
+
+} // namespace Shader::IR
diff --git a/src/shader_recompiler/frontend/ir/flow_test.h b/src/shader_recompiler/frontend/ir/flow_test.h
new file mode 100644
index 0000000000..ac883da136
--- /dev/null
+++ b/src/shader_recompiler/frontend/ir/flow_test.h
@@ -0,0 +1,61 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+
+#include <fmt/format.h>
+
+namespace Shader::IR {
+
+enum class FlowTest {
+    F,
+    LT,
+    EQ,
+    LE,
+    GT,
+    NE,
+    GE,
+    NUM,
+    NaN,
+    LTU,
+    EQU,
+    LEU,
+    GTU,
+    NEU,
+    GEU,
+    T,
+    OFF,
+    LO,
+    SFF,
+    LS,
+    HI,
+    SFT,
+    HS,
+    OFT,
+    CSM_TA,
+    CSM_TR,
+    CSM_MX,
+    FCSM_TA,
+    FCSM_TR,
+    FCSM_MX,
+    RLE,
+    RGT,
+};
+
+[[nodiscard]] std::string NameOf(FlowTest flow_test);
+
+} // namespace Shader::IR
+
+template <>
+struct fmt::formatter<Shader::IR::FlowTest> {
+    constexpr auto parse(format_parse_context& ctx) {
+        return ctx.begin();
+    }
+    template <typename FormatContext>
+    auto format(const Shader::IR::FlowTest& flow_test, FormatContext& ctx) {
+        return fmt::format_to(ctx.out(), "{}", Shader::IR::NameOf(flow_test));
+    }
+};
diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.cpp b/src/shader_recompiler/frontend/ir/ir_emitter.cpp
new file mode 100644
index 0000000000..6450e4b2c0
--- /dev/null
+++ b/src/shader_recompiler/frontend/ir/ir_emitter.cpp
@@ -0,0 +1,533 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/bit_cast.h"
+#include "shader_recompiler/frontend/ir/ir_emitter.h"
+#include "shader_recompiler/frontend/ir/value.h"
+
+namespace Shader::IR {
+
+[[noreturn]] static void ThrowInvalidType(Type type) {
+    throw InvalidArgument("Invalid type {}", type);
+}
+
+U1 IREmitter::Imm1(bool value) const {
+    return U1{Value{value}};
+}
+
+U8 IREmitter::Imm8(u8 value) const {
+    return U8{Value{value}};
+}
+
+U16 IREmitter::Imm16(u16 value) const {
+    return U16{Value{value}};
+}
+
+U32 IREmitter::Imm32(u32 value) const {
+    return U32{Value{value}};
+}
+
+U32 IREmitter::Imm32(s32 value) const {
+    return U32{Value{static_cast<u32>(value)}};
+}
+
+U32 IREmitter::Imm32(f32 value) const {
+    return U32{Value{Common::BitCast<u32>(value)}};
+}
+
+U64 IREmitter::Imm64(u64 value) const {
+    return U64{Value{value}};
+}
+
+U64 IREmitter::Imm64(f64 value) const {
+    return U64{Value{Common::BitCast<u64>(value)}};
+}
+
+void IREmitter::Branch(IR::Block* label) {
+    Inst(Opcode::Branch, label);
+}
+
+void IREmitter::BranchConditional(const U1& cond, IR::Block* true_label, IR::Block* false_label) {
+    Inst(Opcode::BranchConditional, cond, true_label, false_label);
+}
+
+void IREmitter::Exit() {
+    Inst(Opcode::Exit);
+}
+
+void IREmitter::Return() {
+    Inst(Opcode::Return);
+}
+
+void IREmitter::Unreachable() {
+    Inst(Opcode::Unreachable);
+}
+
+U32 IREmitter::GetReg(IR::Reg reg) {
+    return Inst<U32>(Opcode::GetRegister, reg);
+}
+
+void IREmitter::SetReg(IR::Reg reg, const U32& value) {
+    Inst(Opcode::SetRegister, reg, value);
+}
+
+U1 IREmitter::GetPred(IR::Pred pred, bool is_negated) {
+    const U1 value{Inst<U1>(Opcode::GetPred, pred)};
+    if (is_negated) {
+        return Inst<U1>(Opcode::LogicalNot, value);
+    } else {
+        return value;
+    }
+}
+
+void IREmitter::SetPred(IR::Pred pred, const U1& value) {
+    Inst(Opcode::SetPred, pred, value);
+}
+
+U32 IREmitter::GetCbuf(const U32& binding, const U32& byte_offset) {
+    return Inst<U32>(Opcode::GetCbuf, binding, byte_offset);
+}
+
+U1 IREmitter::GetZFlag() {
+    return Inst<U1>(Opcode::GetZFlag);
+}
+
+U1 IREmitter::GetSFlag() {
+    return Inst<U1>(Opcode::GetSFlag);
+}
+
+U1 IREmitter::GetCFlag() {
+    return Inst<U1>(Opcode::GetCFlag);
+}
+
+U1 IREmitter::GetOFlag() {
+    return Inst<U1>(Opcode::GetOFlag);
+}
+
+void IREmitter::SetZFlag(const U1& value) {
+    Inst(Opcode::SetZFlag, value);
+}
+
+void IREmitter::SetSFlag(const U1& value) {
+    Inst(Opcode::SetSFlag, value);
+}
+
+void IREmitter::SetCFlag(const U1& value) {
+    Inst(Opcode::SetCFlag, value);
+}
+
+void IREmitter::SetOFlag(const U1& value) {
+    Inst(Opcode::SetOFlag, value);
+}
+
+U32 IREmitter::GetAttribute(IR::Attribute attribute) {
+    return Inst<U32>(Opcode::GetAttribute, attribute);
+}
+
+void IREmitter::SetAttribute(IR::Attribute attribute, const U32& value) {
+    Inst(Opcode::SetAttribute, attribute, value);
+}
+
+void IREmitter::WriteGlobalU8(const U64& address, const U32& value) {
+    Inst(Opcode::WriteGlobalU8, address, value);
+}
+
+void IREmitter::WriteGlobalS8(const U64& address, const U32& value) {
+    Inst(Opcode::WriteGlobalS8, address, value);
+}
+
+void IREmitter::WriteGlobalU16(const U64& address, const U32& value) {
+    Inst(Opcode::WriteGlobalU16, address, value);
+}
+
+void IREmitter::WriteGlobalS16(const U64& address, const U32& value) {
+    Inst(Opcode::WriteGlobalS16, address, value);
+}
+
+void IREmitter::WriteGlobal32(const U64& address, const U32& value) {
+    Inst(Opcode::WriteGlobal32, address, value);
+}
+
+void IREmitter::WriteGlobal64(const U64& address, const IR::Value& vector) {
+    Inst(Opcode::WriteGlobal64, address, vector);
+}
+
+void IREmitter::WriteGlobal128(const U64& address, const IR::Value& vector) {
+    Inst(Opcode::WriteGlobal128, address, vector);
+}
+
+U1 IREmitter::GetZeroFromOp(const Value& op) {
+    return Inst<U1>(Opcode::GetZeroFromOp, op);
+}
+
+U1 IREmitter::GetSignFromOp(const Value& op) {
+    return Inst<U1>(Opcode::GetSignFromOp, op);
+}
+
+U1 IREmitter::GetCarryFromOp(const Value& op) {
+    return Inst<U1>(Opcode::GetCarryFromOp, op);
+}
+
+U1 IREmitter::GetOverflowFromOp(const Value& op) {
+    return Inst<U1>(Opcode::GetOverflowFromOp, op);
+}
+
+U16U32U64 IREmitter::FPAdd(const U16U32U64& a, const U16U32U64& b) {
+    if (a.Type() != a.Type()) {
+        throw InvalidArgument("Mismatching types {} and {}", a.Type(), b.Type());
+    }
+    switch (a.Type()) {
+    case Type::U16:
+        return Inst<U16>(Opcode::FPAdd16, a, b);
+    case Type::U32:
+        return Inst<U32>(Opcode::FPAdd32, a, b);
+    case Type::U64:
+        return Inst<U64>(Opcode::FPAdd64, a, b);
+    default:
+        ThrowInvalidType(a.Type());
+    }
+}
+
+Value IREmitter::CompositeConstruct(const UAny& e1, const UAny& e2) {
+    if (e1.Type() != e2.Type()) {
+        throw InvalidArgument("Incompatible types {} {}", e1.Type(), e2.Type());
+    }
+    return Inst(Opcode::CompositeConstruct2, e1, e2);
+}
+
+Value IREmitter::CompositeConstruct(const UAny& e1, const UAny& e2, const UAny& e3) {
+    if (e1.Type() != e2.Type() || e1.Type() != e3.Type()) {
+        throw InvalidArgument("Incompatible types {} {} {}", e1.Type(), e2.Type(), e3.Type());
+    }
+    return Inst(Opcode::CompositeConstruct3, e1, e2, e3);
+}
+
+Value IREmitter::CompositeConstruct(const UAny& e1, const UAny& e2, const UAny& e3,
+                                    const UAny& e4) {
+    if (e1.Type() != e2.Type() || e1.Type() != e3.Type() || e1.Type() != e4.Type()) {
+        throw InvalidArgument("Incompatible types {} {} {}", e1.Type(), e2.Type(), e3.Type(),
+                              e4.Type());
+    }
+    return Inst(Opcode::CompositeConstruct4, e1, e2, e3, e4);
+}
+
+UAny IREmitter::CompositeExtract(const Value& vector, size_t element) {
+    if (element >= 4) {
+        throw InvalidArgument("Out of bounds element {}", element);
+    }
+    return Inst<UAny>(Opcode::CompositeExtract, vector, Imm32(static_cast<u32>(element)));
+}
+
+U64 IREmitter::PackUint2x32(const Value& vector) {
+    return Inst<U64>(Opcode::PackUint2x32, vector);
+}
+
+Value IREmitter::UnpackUint2x32(const U64& value) {
+    return Inst<Value>(Opcode::UnpackUint2x32, value);
+}
+
+U32 IREmitter::PackFloat2x16(const Value& vector) {
+    return Inst<U32>(Opcode::PackFloat2x16, vector);
+}
+
+Value IREmitter::UnpackFloat2x16(const U32& value) {
+    return Inst<Value>(Opcode::UnpackFloat2x16, value);
+}
+
+U64 IREmitter::PackDouble2x32(const Value& vector) {
+    return Inst<U64>(Opcode::PackDouble2x32, vector);
+}
+
+Value IREmitter::UnpackDouble2x32(const U64& value) {
+    return Inst<Value>(Opcode::UnpackDouble2x32, value);
+}
+
+U16U32U64 IREmitter::FPMul(const U16U32U64& a, const U16U32U64& b) {
+    if (a.Type() != b.Type()) {
+        throw InvalidArgument("Mismatching types {} and {}", a.Type(), b.Type());
+    }
+    switch (a.Type()) {
+    case Type::U16:
+        return Inst<U16>(Opcode::FPMul16, a, b);
+    case Type::U32:
+        return Inst<U32>(Opcode::FPMul32, a, b);
+    case Type::U64:
+        return Inst<U64>(Opcode::FPMul64, a, b);
+    default:
+        ThrowInvalidType(a.Type());
+    }
+}
+
+U16U32U64 IREmitter::FPAbs(const U16U32U64& value) {
+    switch (value.Type()) {
+    case Type::U16:
+        return Inst<U16>(Opcode::FPAbs16, value);
+    case Type::U32:
+        return Inst<U32>(Opcode::FPAbs32, value);
+    case Type::U64:
+        return Inst<U64>(Opcode::FPAbs64, value);
+    default:
+        ThrowInvalidType(value.Type());
+    }
+}
+
+U16U32U64 IREmitter::FPNeg(const U16U32U64& value) {
+    switch (value.Type()) {
+    case Type::U16:
+        return Inst<U16>(Opcode::FPNeg16, value);
+    case Type::U32:
+        return Inst<U32>(Opcode::FPNeg32, value);
+    case Type::U64:
+        return Inst<U64>(Opcode::FPNeg64, value);
+    default:
+        ThrowInvalidType(value.Type());
+    }
+}
+
+U16U32U64 IREmitter::FPAbsNeg(const U16U32U64& value, bool abs, bool neg) {
+    U16U32U64 result{value};
+    if (abs) {
+        result = FPAbs(value);
+    }
+    if (neg) {
+        result = FPNeg(value);
+    }
+    return result;
+}
+
+U32 IREmitter::FPCosNotReduced(const U32& value) {
+    return Inst<U32>(Opcode::FPCosNotReduced, value);
+}
+
+U32 IREmitter::FPExp2NotReduced(const U32& value) {
+    return Inst<U32>(Opcode::FPExp2NotReduced, value);
+}
+
+U32 IREmitter::FPLog2(const U32& value) {
+    return Inst<U32>(Opcode::FPLog2, value);
+}
+
+U32U64 IREmitter::FPRecip(const U32U64& value) {
+    switch (value.Type()) {
+    case Type::U32:
+        return Inst<U32>(Opcode::FPRecip32, value);
+    case Type::U64:
+        return Inst<U64>(Opcode::FPRecip64, value);
+    default:
+        ThrowInvalidType(value.Type());
+    }
+}
+
+U32U64 IREmitter::FPRecipSqrt(const U32U64& value) {
+    switch (value.Type()) {
+    case Type::U32:
+        return Inst<U32>(Opcode::FPRecipSqrt32, value);
+    case Type::U64:
+        return Inst<U64>(Opcode::FPRecipSqrt64, value);
+    default:
+        ThrowInvalidType(value.Type());
+    }
+}
+
+U32 IREmitter::FPSinNotReduced(const U32& value) {
+    return Inst<U32>(Opcode::FPSinNotReduced, value);
+}
+
+U32 IREmitter::FPSqrt(const U32& value) {
+    return Inst<U32>(Opcode::FPSqrt, value);
+}
+
+U16U32U64 IREmitter::FPSaturate(const U16U32U64& value) {
+    switch (value.Type()) {
+    case Type::U16:
+        return Inst<U16>(Opcode::FPSaturate16, value);
+    case Type::U32:
+        return Inst<U32>(Opcode::FPSaturate32, value);
+    case Type::U64:
+        return Inst<U64>(Opcode::FPSaturate64, value);
+    default:
+        ThrowInvalidType(value.Type());
+    }
+}
+
+U16U32U64 IREmitter::FPRoundEven(const U16U32U64& value) {
+    switch (value.Type()) {
+    case Type::U16:
+        return Inst<U16>(Opcode::FPRoundEven16, value);
+    case Type::U32:
+        return Inst<U32>(Opcode::FPRoundEven32, value);
+    case Type::U64:
+        return Inst<U64>(Opcode::FPRoundEven64, value);
+    default:
+        ThrowInvalidType(value.Type());
+    }
+}
+
+U16U32U64 IREmitter::FPFloor(const U16U32U64& value) {
+    switch (value.Type()) {
+    case Type::U16:
+        return Inst<U16>(Opcode::FPFloor16, value);
+    case Type::U32:
+        return Inst<U32>(Opcode::FPFloor32, value);
+    case Type::U64:
+        return Inst<U64>(Opcode::FPFloor64, value);
+    default:
+        ThrowInvalidType(value.Type());
+    }
+}
+
+U16U32U64 IREmitter::FPCeil(const U16U32U64& value) {
+    switch (value.Type()) {
+    case Type::U16:
+        return Inst<U16>(Opcode::FPCeil16, value);
+    case Type::U32:
+        return Inst<U32>(Opcode::FPCeil32, value);
+    case Type::U64:
+        return Inst<U64>(Opcode::FPCeil64, value);
+    default:
+        ThrowInvalidType(value.Type());
+    }
+}
+
+U16U32U64 IREmitter::FPTrunc(const U16U32U64& value) {
+    switch (value.Type()) {
+    case Type::U16:
+        return Inst<U16>(Opcode::FPTrunc16, value);
+    case Type::U32:
+        return Inst<U32>(Opcode::FPTrunc32, value);
+    case Type::U64:
+        return Inst<U64>(Opcode::FPTrunc64, value);
+    default:
+        ThrowInvalidType(value.Type());
+    }
+}
+
+U1 IREmitter::LogicalOr(const U1& a, const U1& b) {
+    return Inst<U1>(Opcode::LogicalOr, a, b);
+}
+
+U1 IREmitter::LogicalAnd(const U1& a, const U1& b) {
+    return Inst<U1>(Opcode::LogicalAnd, a, b);
+}
+
+U1 IREmitter::LogicalNot(const U1& value) {
+    return Inst<U1>(Opcode::LogicalNot, value);
+}
+
+U32U64 IREmitter::ConvertFToS(size_t bitsize, const U16U32U64& value) {
+    switch (bitsize) {
+    case 16:
+        switch (value.Type()) {
+        case Type::U16:
+            return Inst<U32>(Opcode::ConvertS16F16, value);
+        case Type::U32:
+            return Inst<U32>(Opcode::ConvertS16F32, value);
+        case Type::U64:
+            return Inst<U32>(Opcode::ConvertS16F64, value);
+        default:
+            ThrowInvalidType(value.Type());
+        }
+    case 32:
+        switch (value.Type()) {
+        case Type::U16:
+            return Inst<U32>(Opcode::ConvertS32F16, value);
+        case Type::U32:
+            return Inst<U32>(Opcode::ConvertS32F32, value);
+        case Type::U64:
+            return Inst<U32>(Opcode::ConvertS32F64, value);
+        default:
+            ThrowInvalidType(value.Type());
+        }
+    case 64:
+        switch (value.Type()) {
+        case Type::U16:
+            return Inst<U64>(Opcode::ConvertS64F16, value);
+        case Type::U32:
+            return Inst<U64>(Opcode::ConvertS64F32, value);
+        case Type::U64:
+            return Inst<U64>(Opcode::ConvertS64F64, value);
+        default:
+            ThrowInvalidType(value.Type());
+        }
+    default:
+        throw InvalidArgument("Invalid destination bitsize {}", bitsize);
+    }
+}
+
+U32U64 IREmitter::ConvertFToU(size_t bitsize, const U16U32U64& value) {
+    switch (bitsize) {
+    case 16:
+        switch (value.Type()) {
+        case Type::U16:
+            return Inst<U32>(Opcode::ConvertU16F16, value);
+        case Type::U32:
+            return Inst<U32>(Opcode::ConvertU16F32, value);
+        case Type::U64:
+            return Inst<U32>(Opcode::ConvertU16F64, value);
+        default:
+            ThrowInvalidType(value.Type());
+        }
+    case 32:
+        switch (value.Type()) {
+        case Type::U16:
+            return Inst<U32>(Opcode::ConvertU32F16, value);
+        case Type::U32:
+            return Inst<U32>(Opcode::ConvertU32F32, value);
+        case Type::U64:
+            return Inst<U32>(Opcode::ConvertU32F64, value);
+        default:
+            ThrowInvalidType(value.Type());
+        }
+    case 64:
+        switch (value.Type()) {
+        case Type::U16:
+            return Inst<U64>(Opcode::ConvertU64F16, value);
+        case Type::U32:
+            return Inst<U64>(Opcode::ConvertU64F32, value);
+        case Type::U64:
+            return Inst<U64>(Opcode::ConvertU64F64, value);
+        default:
+            ThrowInvalidType(value.Type());
+        }
+    default:
+        throw InvalidArgument("Invalid destination bitsize {}", bitsize);
+    }
+}
+
+U32U64 IREmitter::ConvertFToI(size_t bitsize, bool is_signed, const U16U32U64& value) {
+    if (is_signed) {
+        return ConvertFToS(bitsize, value);
+    } else {
+        return ConvertFToU(bitsize, value);
+    }
+}
+
+U32U64 IREmitter::ConvertU(size_t bitsize, const U32U64& value) {
+    switch (bitsize) {
+    case 32:
+        switch (value.Type()) {
+        case Type::U32:
+            // Nothing to do
+            return value;
+        case Type::U64:
+            return Inst<U32>(Opcode::ConvertU32U64, value);
+        default:
+            break;
+        }
+        break;
+    case 64:
+        switch (value.Type()) {
+        case Type::U32:
+            // Nothing to do
+            return value;
+        case Type::U64:
+            return Inst<U64>(Opcode::ConvertU64U32, value);
+        default:
+            break;
+        }
+    }
+    throw NotImplementedException("Conversion from {} to {} bits", value.Type(), bitsize);
+}
+
+} // namespace Shader::IR
diff --git a/src/shader_recompiler/frontend/ir/ir_emitter.h b/src/shader_recompiler/frontend/ir/ir_emitter.h
new file mode 100644
index 0000000000..1af79f41cb
--- /dev/null
+++ b/src/shader_recompiler/frontend/ir/ir_emitter.h
@@ -0,0 +1,123 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "shader_recompiler/frontend/ir/attribute.h"
+#include "shader_recompiler/frontend/ir/basic_block.h"
+#include "shader_recompiler/frontend/ir/value.h"
+
+namespace Shader::IR {
+
+class IREmitter {
+public:
+    explicit IREmitter(Block& block_) : block{block_}, insertion_point{block.end()} {}
+
+    Block& block;
+
+    [[nodiscard]] U1 Imm1(bool value) const;
+    [[nodiscard]] U8 Imm8(u8 value) const;
+    [[nodiscard]] U16 Imm16(u16 value) const;
+    [[nodiscard]] U32 Imm32(u32 value) const;
+    [[nodiscard]] U32 Imm32(s32 value) const;
+    [[nodiscard]] U32 Imm32(f32 value) const;
+    [[nodiscard]] U64 Imm64(u64 value) const;
+    [[nodiscard]] U64 Imm64(f64 value) const;
+
+    void Branch(IR::Block* label);
+    void BranchConditional(const U1& cond, IR::Block* true_label, IR::Block* false_label);
+    void Exit();
+    void Return();
+    void Unreachable();
+
+    [[nodiscard]] U32 GetReg(IR::Reg reg);
+    void SetReg(IR::Reg reg, const U32& value);
+
+    [[nodiscard]] U1 GetPred(IR::Pred pred, bool is_negated = false);
+    void SetPred(IR::Pred pred, const U1& value);
+
+    [[nodiscard]] U32 GetCbuf(const U32& binding, const U32& byte_offset);
+
+    [[nodiscard]] U1 GetZFlag();
+    [[nodiscard]] U1 GetSFlag();
+    [[nodiscard]] U1 GetCFlag();
+    [[nodiscard]] U1 GetOFlag();
+
+    void SetZFlag(const U1& value);
+    void SetSFlag(const U1& value);
+    void SetCFlag(const U1& value);
+    void SetOFlag(const U1& value);
+
+    [[nodiscard]] U32 GetAttribute(IR::Attribute attribute);
+    void SetAttribute(IR::Attribute attribute, const U32& value);
+
+    void WriteGlobalU8(const U64& address, const U32& value);
+    void WriteGlobalS8(const U64& address, const U32& value);
+    void WriteGlobalU16(const U64& address, const U32& value);
+    void WriteGlobalS16(const U64& address, const U32& value);
+    void WriteGlobal32(const U64& address, const U32& value);
+    void WriteGlobal64(const U64& address, const IR::Value& vector);
+    void WriteGlobal128(const U64& address, const IR::Value& vector);
+
+    [[nodiscard]] U1 GetZeroFromOp(const Value& op);
+    [[nodiscard]] U1 GetSignFromOp(const Value& op);
+    [[nodiscard]] U1 GetCarryFromOp(const Value& op);
+    [[nodiscard]] U1 GetOverflowFromOp(const Value& op);
+
+    [[nodiscard]] Value CompositeConstruct(const UAny& e1, const UAny& e2);
+    [[nodiscard]] Value CompositeConstruct(const UAny& e1, const UAny& e2, const UAny& e3);
+    [[nodiscard]] Value CompositeConstruct(const UAny& e1, const UAny& e2, const UAny& e3,
+                                           const UAny& e4);
+    [[nodiscard]] UAny CompositeExtract(const Value& vector, size_t element);
+
+    [[nodiscard]] U64 PackUint2x32(const Value& vector);
+    [[nodiscard]] Value UnpackUint2x32(const U64& value);
+
+    [[nodiscard]] U32 PackFloat2x16(const Value& vector);
+    [[nodiscard]] Value UnpackFloat2x16(const U32& value);
+
+    [[nodiscard]] U64 PackDouble2x32(const Value& vector);
+    [[nodiscard]] Value UnpackDouble2x32(const U64& value);
+
+    [[nodiscard]] U16U32U64 FPAdd(const U16U32U64& a, const U16U32U64& b);
+    [[nodiscard]] U16U32U64 FPMul(const U16U32U64& a, const U16U32U64& b);
+
+    [[nodiscard]] U16U32U64 FPAbs(const U16U32U64& value);
+    [[nodiscard]] U16U32U64 FPNeg(const U16U32U64& value);
+    [[nodiscard]] U16U32U64 FPAbsNeg(const U16U32U64& value, bool abs, bool neg);
+
+    [[nodiscard]] U32 FPCosNotReduced(const U32& value);
+    [[nodiscard]] U32 FPExp2NotReduced(const U32& value);
+    [[nodiscard]] U32 FPLog2(const U32& value);
+    [[nodiscard]] U32U64 FPRecip(const U32U64& value);
+    [[nodiscard]] U32U64 FPRecipSqrt(const U32U64& value);
+    [[nodiscard]] U32 FPSinNotReduced(const U32& value);
+    [[nodiscard]] U32 FPSqrt(const U32& value);
+    [[nodiscard]] U16U32U64 FPSaturate(const U16U32U64& value);
+    [[nodiscard]] U16U32U64 FPRoundEven(const U16U32U64& value);
+    [[nodiscard]] U16U32U64 FPFloor(const U16U32U64& value);
+    [[nodiscard]] U16U32U64 FPCeil(const U16U32U64& value);
+    [[nodiscard]] U16U32U64 FPTrunc(const U16U32U64& value);
+
+    [[nodiscard]] U1 LogicalOr(const U1& a, const U1& b);
+    [[nodiscard]] U1 LogicalAnd(const U1& a, const U1& b);
+    [[nodiscard]] U1 LogicalNot(const U1& value);
+
+    [[nodiscard]] U32U64 ConvertFToS(size_t bitsize, const U16U32U64& value);
+    [[nodiscard]] U32U64 ConvertFToU(size_t bitsize, const U16U32U64& value);
+    [[nodiscard]] U32U64 ConvertFToI(size_t bitsize, bool is_signed, const U16U32U64& value);
+
+    [[nodiscard]] U32U64 ConvertU(size_t bitsize, const U32U64& value);
+
+private:
+    IR::Block::iterator insertion_point;
+
+    template <typename T = Value, typename... Args>
+    T Inst(Opcode op, Args... args) {
+        auto it{block.PrependNewInst(insertion_point, op, {Value{args}...})};
+        return T{Value{&*it}};
+    }
+};
+
+} // namespace Shader::IR
diff --git a/src/shader_recompiler/frontend/ir/microinstruction.cpp b/src/shader_recompiler/frontend/ir/microinstruction.cpp
new file mode 100644
index 0000000000..553fec3b7f
--- /dev/null
+++ b/src/shader_recompiler/frontend/ir/microinstruction.cpp
@@ -0,0 +1,189 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "shader_recompiler/exception.h"
+#include "shader_recompiler/frontend/ir/microinstruction.h"
+#include "shader_recompiler/frontend/ir/type.h"
+
+namespace Shader::IR {
+
+static void CheckPseudoInstruction(IR::Inst* inst, IR::Opcode opcode) {
+    if (inst && inst->Opcode() != opcode) {
+        throw LogicError("Invalid pseudo-instruction");
+    }
+}
+
+static void SetPseudoInstruction(IR::Inst*& dest_inst, IR::Inst* pseudo_inst) {
+    if (dest_inst) {
+        throw LogicError("Only one of each type of pseudo-op allowed");
+    }
+    dest_inst = pseudo_inst;
+}
+
+static void RemovePseudoInstruction(IR::Inst*& inst, IR::Opcode expected_opcode) {
+    if (inst->Opcode() != expected_opcode) {
+        throw LogicError("Undoing use of invalid pseudo-op");
+    }
+    inst = nullptr;
+}
+
+bool Inst::MayHaveSideEffects() const noexcept {
+    switch (op) {
+    case Opcode::SetAttribute:
+    case Opcode::SetAttributeIndexed:
+    case Opcode::WriteGlobalU8:
+    case Opcode::WriteGlobalS8:
+    case Opcode::WriteGlobalU16:
+    case Opcode::WriteGlobalS16:
+    case Opcode::WriteGlobal32:
+    case Opcode::WriteGlobal64:
+    case Opcode::WriteGlobal128:
+        return true;
+    default:
+        return false;
+    }
+}
+
+bool Inst::IsPseudoInstruction() const noexcept {
+    switch (op) {
+    case Opcode::GetZeroFromOp:
+    case Opcode::GetSignFromOp:
+    case Opcode::GetCarryFromOp:
+    case Opcode::GetOverflowFromOp:
+    case Opcode::GetZSCOFromOp:
+        return true;
+    default:
+        return false;
+    }
+}
+
+bool Inst::HasAssociatedPseudoOperation() const noexcept {
+    return zero_inst || sign_inst || carry_inst || overflow_inst || zsco_inst;
+}
+
+Inst* Inst::GetAssociatedPseudoOperation(IR::Opcode opcode) {
+    // This is faster than doing a search through the block.
+    switch (opcode) {
+    case Opcode::GetZeroFromOp:
+        CheckPseudoInstruction(zero_inst, Opcode::GetZeroFromOp);
+        return zero_inst;
+    case Opcode::GetSignFromOp:
+        CheckPseudoInstruction(sign_inst, Opcode::GetSignFromOp);
+        return sign_inst;
+    case Opcode::GetCarryFromOp:
+        CheckPseudoInstruction(carry_inst, Opcode::GetCarryFromOp);
+        return carry_inst;
+    case Opcode::GetOverflowFromOp:
+        CheckPseudoInstruction(overflow_inst, Opcode::GetOverflowFromOp);
+        return overflow_inst;
+    case Opcode::GetZSCOFromOp:
+        CheckPseudoInstruction(zsco_inst, Opcode::GetZSCOFromOp);
+        return zsco_inst;
+    default:
+        throw InvalidArgument("{} is not a pseudo-instruction", opcode);
+    }
+}
+
+size_t Inst::NumArgs() const {
+    return NumArgsOf(op);
+}
+
+IR::Type Inst::Type() const {
+    return TypeOf(op);
+}
+
+Value Inst::Arg(size_t index) const {
+    if (index >= NumArgsOf(op)) {
+        throw InvalidArgument("Out of bounds argument index {} in opcode {}", index, op);
+    }
+    return args[index];
+}
+
+void Inst::SetArg(size_t index, Value value) {
+    if (index >= NumArgsOf(op)) {
+        throw InvalidArgument("Out of bounds argument index {} in opcode {}", index, op);
+    }
+    if (!args[index].IsImmediate()) {
+        UndoUse(args[index]);
+    }
+    if (!value.IsImmediate()) {
+        Use(value);
+    }
+    args[index] = value;
+}
+
+void Inst::Invalidate() {
+    ClearArgs();
+    op = Opcode::Void;
+}
+
+void Inst::ClearArgs() {
+    for (auto& value : args) {
+        if (!value.IsImmediate()) {
+            UndoUse(value);
+        }
+        value = {};
+    }
+}
+
+void Inst::ReplaceUsesWith(Value replacement) {
+    Invalidate();
+
+    op = Opcode::Identity;
+
+    if (!replacement.IsImmediate()) {
+        Use(replacement);
+    }
+    args[0] = replacement;
+}
+
+void Inst::Use(const Value& value) {
+    ++value.Inst()->use_count;
+
+    switch (op) {
+    case Opcode::GetZeroFromOp:
+        SetPseudoInstruction(value.Inst()->zero_inst, this);
+        break;
+    case Opcode::GetSignFromOp:
+        SetPseudoInstruction(value.Inst()->sign_inst, this);
+        break;
+    case Opcode::GetCarryFromOp:
+        SetPseudoInstruction(value.Inst()->carry_inst, this);
+        break;
+    case Opcode::GetOverflowFromOp:
+        SetPseudoInstruction(value.Inst()->overflow_inst, this);
+        break;
+    case Opcode::GetZSCOFromOp:
+        SetPseudoInstruction(value.Inst()->zsco_inst, this);
+        break;
+    default:
+        break;
+    }
+}
+
+void Inst::UndoUse(const Value& value) {
+    --value.Inst()->use_count;
+
+    switch (op) {
+    case Opcode::GetZeroFromOp:
+        RemovePseudoInstruction(value.Inst()->zero_inst, Opcode::GetZeroFromOp);
+        break;
+    case Opcode::GetSignFromOp:
+        RemovePseudoInstruction(value.Inst()->sign_inst, Opcode::GetSignFromOp);
+        break;
+    case Opcode::GetCarryFromOp:
+        RemovePseudoInstruction(value.Inst()->carry_inst, Opcode::GetCarryFromOp);
+        break;
+    case Opcode::GetOverflowFromOp:
+        RemovePseudoInstruction(value.Inst()->overflow_inst, Opcode::GetOverflowFromOp);
+        break;
+    case Opcode::GetZSCOFromOp:
+        RemovePseudoInstruction(value.Inst()->zsco_inst, Opcode::GetZSCOFromOp);
+        break;
+    default:
+        break;
+    }
+}
+
+} // namespace Shader::IR
diff --git a/src/shader_recompiler/frontend/ir/microinstruction.h b/src/shader_recompiler/frontend/ir/microinstruction.h
new file mode 100644
index 0000000000..43460b9505
--- /dev/null
+++ b/src/shader_recompiler/frontend/ir/microinstruction.h
@@ -0,0 +1,82 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+
+#include <boost/intrusive/list.hpp>
+
+#include "common/common_types.h"
+#include "shader_recompiler/frontend/ir/opcode.h"
+#include "shader_recompiler/frontend/ir/type.h"
+#include "shader_recompiler/frontend/ir/value.h"
+
+namespace Shader::IR {
+
+constexpr size_t MAX_ARG_COUNT = 4;
+
+class Inst : public boost::intrusive::list_base_hook<> {
+public:
+    explicit Inst(Opcode op_) noexcept : op(op_) {}
+
+    /// Get the number of uses this instruction has.
+    [[nodiscard]] int UseCount() const noexcept {
+        return use_count;
+    }
+
+    /// Determines whether this instruction has uses or not.
+    [[nodiscard]] bool HasUses() const noexcept {
+        return use_count > 0;
+    }
+
+    /// Get the opcode this microinstruction represents.
+    [[nodiscard]] IR::Opcode Opcode() const noexcept {
+        return op;
+    }
+
+    /// Determines whether or not this instruction may have side effects.
+    [[nodiscard]] bool MayHaveSideEffects() const noexcept;
+
+    /// Determines whether or not this instruction is a pseudo-instruction.
+    /// Pseudo-instructions depend on their parent instructions for their semantics.
+    [[nodiscard]] bool IsPseudoInstruction() const noexcept;
+
+    /// Determines if there is a pseudo-operation associated with this instruction.
+    [[nodiscard]] bool HasAssociatedPseudoOperation() const noexcept;
+    /// Gets a pseudo-operation associated with this instruction
+    [[nodiscard]] Inst* GetAssociatedPseudoOperation(IR::Opcode opcode);
+
+    /// Get the number of arguments this instruction has.
+    [[nodiscard]] size_t NumArgs() const;
+
+    /// Get the type this instruction returns.
+    [[nodiscard]] IR::Type Type() const;
+
+    /// Get the value of a given argument index.
+    [[nodiscard]] Value Arg(size_t index) const;
+    /// Set the value of a given argument index.
+    void SetArg(size_t index, Value value);
+
+    void Invalidate();
+    void ClearArgs();
+
+    void ReplaceUsesWith(Value replacement);
+
+private:
+    void Use(const Value& value);
+    void UndoUse(const Value& value);
+
+    IR::Opcode op{};
+    int use_count{};
+    std::array<Value, MAX_ARG_COUNT> args{};
+    Inst* zero_inst{};
+    Inst* sign_inst{};
+    Inst* carry_inst{};
+    Inst* overflow_inst{};
+    Inst* zsco_inst{};
+    u64 flags{};
+};
+
+} // namespace Shader::IR
diff --git a/src/shader_recompiler/frontend/ir/opcode.cpp b/src/shader_recompiler/frontend/ir/opcode.cpp
new file mode 100644
index 0000000000..65d0740296
--- /dev/null
+++ b/src/shader_recompiler/frontend/ir/opcode.cpp
@@ -0,0 +1,67 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <array>
+#include <string_view>
+
+#include "shader_recompiler/exception.h"
+#include "shader_recompiler/frontend/ir/opcode.h"
+
+namespace Shader::IR {
+namespace {
+struct OpcodeMeta {
+    std::string_view name;
+    Type type;
+    std::array<Type, 4> arg_types;
+};
+
+using enum Type;
+
+constexpr std::array META_TABLE{
+#define OPCODE(name_token, type_token, ...)                                                        \
+    OpcodeMeta{                                                                                    \
+        .name{#name_token},                                                                        \
+        .type{type_token},                                                                         \
+        .arg_types{__VA_ARGS__},                                                                   \
+    },
+#include "opcode.inc"
+#undef OPCODE
+};
+
+void ValidateOpcode(Opcode op) {
+    const size_t raw{static_cast<size_t>(op)};
+    if (raw >= META_TABLE.size()) {
+        throw InvalidArgument("Invalid opcode with raw value {}", raw);
+    }
+}
+} // Anonymous namespace
+
+Type TypeOf(Opcode op) {
+    ValidateOpcode(op);
+    return META_TABLE[static_cast<size_t>(op)].type;
+}
+
+size_t NumArgsOf(Opcode op) {
+    ValidateOpcode(op);
+    const auto& arg_types{META_TABLE[static_cast<size_t>(op)].arg_types};
+    const auto distance{std::distance(arg_types.begin(), std::ranges::find(arg_types, Type::Void))};
+    return static_cast<size_t>(distance);
+}
+
+Type ArgTypeOf(Opcode op, size_t arg_index) {
+    ValidateOpcode(op);
+    const auto& arg_types{META_TABLE[static_cast<size_t>(op)].arg_types};
+    if (arg_index >= arg_types.size() || arg_types[arg_index] == Type::Void) {
+        throw InvalidArgument("Out of bounds argument");
+    }
+    return arg_types[arg_index];
+}
+
+std::string_view NameOf(Opcode op) {
+    ValidateOpcode(op);
+    return META_TABLE[static_cast<size_t>(op)].name;
+}
+
+} // namespace Shader::IR
diff --git a/src/shader_recompiler/frontend/ir/opcode.h b/src/shader_recompiler/frontend/ir/opcode.h
new file mode 100644
index 0000000000..1f4440379d
--- /dev/null
+++ b/src/shader_recompiler/frontend/ir/opcode.h
@@ -0,0 +1,44 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string_view>
+
+#include <fmt/format.h>
+
+#include "shader_recompiler/frontend/ir/type.h"
+
+namespace Shader::IR {
+
+enum class Opcode {
+#define OPCODE(name, ...) name,
+#include "opcode.inc"
+#undef OPCODE
+};
+
+/// Get return type of an opcode
+[[nodiscard]] Type TypeOf(Opcode op);
+
+/// Get the number of arguments an opcode accepts
+[[nodiscard]] size_t NumArgsOf(Opcode op);
+
+/// Get the required type of an argument of an opcode
+[[nodiscard]] Type ArgTypeOf(Opcode op, size_t arg_index);
+
+/// Get the name of an opcode
+[[nodiscard]] std::string_view NameOf(Opcode op);
+
+} // namespace Shader::IR
+
+template <>
+struct fmt::formatter<Shader::IR::Opcode> {
+    constexpr auto parse(format_parse_context& ctx) {
+        return ctx.begin();
+    }
+    template <typename FormatContext>
+    auto format(const Shader::IR::Opcode& op, FormatContext& ctx) {
+        return format_to(ctx.out(), "{}", Shader::IR::NameOf(op));
+    }
+};
diff --git a/src/shader_recompiler/frontend/ir/opcode.inc b/src/shader_recompiler/frontend/ir/opcode.inc
new file mode 100644
index 0000000000..371064bf3d
--- /dev/null
+++ b/src/shader_recompiler/frontend/ir/opcode.inc
@@ -0,0 +1,142 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+//     opcode name,                                         return type,    arg1 type,      arg2 type,      arg3 type,      arg4 type,      ...
+OPCODE(Void,                                                Void,                                                                           )
+OPCODE(Identity,                                            Opaque,         Opaque,                                                         )
+
+// Control flow
+OPCODE(Branch,                                              Void,           Label,                                                          )
+OPCODE(BranchConditional,                                   Void,           U1,             Label,          Label,                          )
+OPCODE(Exit,                                                Void,                                                                           )
+OPCODE(Return,                                              Void,                                                                           )
+OPCODE(Unreachable,                                         Void,                                                                           )
+
+// Context getters/setters
+OPCODE(GetRegister,                                         U32,            Reg,                                                            )
+OPCODE(SetRegister,                                         Void,           Reg,            U32,                                            )
+OPCODE(GetPred,                                             U1,             Pred,                                                           )
+OPCODE(SetPred,                                             Void,           Pred,           U1,                                             )
+OPCODE(GetCbuf,                                             U32,            U32,            U32,                                            )
+OPCODE(GetAttribute,                                        U32,            Attribute,                                                      )
+OPCODE(SetAttribute,                                        U32,            Attribute,                                                      )
+OPCODE(GetAttributeIndexed,                                 U32,            U32,                                                            )
+OPCODE(SetAttributeIndexed,                                 U32,            U32,                                                            )
+OPCODE(GetZSCORaw,                                          U32,                                                                            )
+OPCODE(SetZSCORaw,                                          Void,           U32,                                                            )
+OPCODE(SetZSCO,                                             Void,           ZSCO,                                                           )
+OPCODE(GetZFlag,                                            U1,             Void,                                                           )
+OPCODE(GetSFlag,                                            U1,             Void,                                                           )
+OPCODE(GetCFlag,                                            U1,             Void,                                                           )
+OPCODE(GetOFlag,                                            U1,             Void,                                                           )
+OPCODE(SetZFlag,                                            Void,           U1,                                                             )
+OPCODE(SetSFlag,                                            Void,           U1,                                                             )
+OPCODE(SetCFlag,                                            Void,           U1,                                                             )
+OPCODE(SetOFlag,                                            Void,           U1,                                                             )
+
+// Memory operations
+OPCODE(WriteGlobalU8,                                       Void,           U64,            U32,                                            )
+OPCODE(WriteGlobalS8,                                       Void,           U64,            U32,                                            )
+OPCODE(WriteGlobalU16,                                      Void,           U64,            U32,                                            )
+OPCODE(WriteGlobalS16,                                      Void,           U64,            U32,                                            )
+OPCODE(WriteGlobal32,                                       Void,           U64,            U32,                                            )
+OPCODE(WriteGlobal64,                                       Void,           U64,            Opaque,                                         )
+OPCODE(WriteGlobal128,                                      Void,           U64,            Opaque,                                         )
+
+// Vector utility
+OPCODE(CompositeConstruct2,                                 Opaque,         Opaque,         Opaque,                                         )
+OPCODE(CompositeConstruct3,                                 Opaque,         Opaque,         Opaque,         Opaque,                         )
+OPCODE(CompositeConstruct4,                                 Opaque,         Opaque,         Opaque,         Opaque,         Opaque,         )
+OPCODE(CompositeExtract,                                    Opaque,         Opaque,         U32,                                            )
+
+// Bitwise conversions
+OPCODE(PackUint2x32,                                        U64,            Opaque,                                                         )
+OPCODE(UnpackUint2x32,                                      Opaque,         U64,                                                            )
+OPCODE(PackFloat2x16,                                       U32,            Opaque,                                                         )
+OPCODE(UnpackFloat2x16,                                     Opaque,         U32,                                                            )
+OPCODE(PackDouble2x32,                                      U64,            Opaque,                                                         )
+OPCODE(UnpackDouble2x32,                                    Opaque,         U64,                                                            )
+
+// Pseudo-operation, handled specially at final emit
+OPCODE(GetZeroFromOp,                                       U1,             Opaque,                                                         )
+OPCODE(GetSignFromOp,                                       U1,             Opaque,                                                         )
+OPCODE(GetCarryFromOp,                                      U1,             Opaque,                                                         )
+OPCODE(GetOverflowFromOp,                                   U1,             Opaque,                                                         )
+OPCODE(GetZSCOFromOp,                                       ZSCO,           Opaque,                                                         )
+
+// Floating-point operations
+OPCODE(FPAbs16,                                             U16,            U16                                                             )
+OPCODE(FPAbs32,                                             U32,            U32                                                             )
+OPCODE(FPAbs64,                                             U64,            U64                                                             )
+OPCODE(FPAdd16,                                             U16,            U16,            U16                                             )
+OPCODE(FPAdd32,                                             U32,            U32,            U32                                             )
+OPCODE(FPAdd64,                                             U64,            U64,            U64                                             )
+OPCODE(FPFma16,                                             U16,            U16,            U16                                             )
+OPCODE(FPFma32,                                             U32,            U32,            U32                                             )
+OPCODE(FPFma64,                                             U64,            U64,            U64                                             )
+OPCODE(FPMax32,                                             U32,            U32,            U32                                             )
+OPCODE(FPMax64,                                             U64,            U64,            U64                                             )
+OPCODE(FPMin32,                                             U32,            U32,            U32                                             )
+OPCODE(FPMin64,                                             U64,            U64,            U64                                             )
+OPCODE(FPMul16,                                             U16,            U16,            U16                                             )
+OPCODE(FPMul32,                                             U32,            U32,            U32                                             )
+OPCODE(FPMul64,                                             U64,            U64,            U64                                             )
+OPCODE(FPNeg16,                                             U16,            U16                                                             )
+OPCODE(FPNeg32,                                             U32,            U32                                                             )
+OPCODE(FPNeg64,                                             U64,            U64                                                             )
+OPCODE(FPRecip32,                                           U32,            U32                                                             )
+OPCODE(FPRecip64,                                           U64,            U64                                                             )
+OPCODE(FPRecipSqrt32,                                       U32,            U32                                                             )
+OPCODE(FPRecipSqrt64,                                       U64,            U64                                                             )
+OPCODE(FPSqrt,                                              U32,            U32                                                             )
+OPCODE(FPSin,                                               U32,            U32                                                             )
+OPCODE(FPSinNotReduced,                                     U32,            U32                                                             )
+OPCODE(FPExp2,                                              U32,            U32                                                             )
+OPCODE(FPExp2NotReduced,                                    U32,            U32                                                             )
+OPCODE(FPCos,                                               U32,            U32                                                             )
+OPCODE(FPCosNotReduced,                                     U32,            U32                                                             )
+OPCODE(FPLog2,                                              U32,            U32                                                             )
+OPCODE(FPSaturate16,                                        U16,            U16                                                             )
+OPCODE(FPSaturate32,                                        U32,            U32                                                             )
+OPCODE(FPSaturate64,                                        U64,            U64                                                             )
+OPCODE(FPRoundEven16,                                       U16,            U16                                                             )
+OPCODE(FPRoundEven32,                                       U32,            U32                                                             )
+OPCODE(FPRoundEven64,                                       U64,            U64                                                             )
+OPCODE(FPFloor16,                                           U16,            U16                                                             )
+OPCODE(FPFloor32,                                           U32,            U32                                                             )
+OPCODE(FPFloor64,                                           U64,            U64                                                             )
+OPCODE(FPCeil16,                                            U16,            U16                                                             )
+OPCODE(FPCeil32,                                            U32,            U32                                                             )
+OPCODE(FPCeil64,                                            U64,            U64                                                             )
+OPCODE(FPTrunc16,                                           U16,            U16                                                             )
+OPCODE(FPTrunc32,                                           U32,            U32                                                             )
+OPCODE(FPTrunc64,                                           U64,            U64                                                             )
+
+// Logical operations
+OPCODE(LogicalOr,                                           U1,             U1,             U1,                                             )
+OPCODE(LogicalAnd,                                          U1,             U1,             U1,                                             )
+OPCODE(LogicalNot,                                          U1,             U1,                                                             )
+
+// Conversion operations
+OPCODE(ConvertS16F16,                                       U32,            U16,                                                            )
+OPCODE(ConvertS16F32,                                       U32,            U32,                                                            )
+OPCODE(ConvertS16F64,                                       U32,            U64,                                                            )
+OPCODE(ConvertS32F16,                                       U32,            U16,                                                            )
+OPCODE(ConvertS32F32,                                       U32,            U32,                                                            )
+OPCODE(ConvertS32F64,                                       U32,            U64,                                                            )
+OPCODE(ConvertS64F16,                                       U64,            U16,                                                            )
+OPCODE(ConvertS64F32,                                       U64,            U32,                                                            )
+OPCODE(ConvertS64F64,                                       U64,            U64,                                                            )
+OPCODE(ConvertU16F16,                                       U32,            U16,                                                            )
+OPCODE(ConvertU16F32,                                       U32,            U32,                                                            )
+OPCODE(ConvertU16F64,                                       U32,            U64,                                                            )
+OPCODE(ConvertU32F16,                                       U32,            U16,                                                            )
+OPCODE(ConvertU32F32,                                       U32,            U32,                                                            )
+OPCODE(ConvertU32F64,                                       U32,            U64,                                                            )
+OPCODE(ConvertU64F16,                                       U64,            U16,                                                            )
+OPCODE(ConvertU64F32,                                       U64,            U32,                                                            )
+OPCODE(ConvertU64F64,                                       U64,            U64,                                                            )
+
+OPCODE(ConvertU64U32,                                       U64,            U32,                                                            )
+OPCODE(ConvertU32U64,                                       U32,            U64,                                                            )
diff --git a/src/shader_recompiler/frontend/ir/pred.h b/src/shader_recompiler/frontend/ir/pred.h
new file mode 100644
index 0000000000..37cc530068
--- /dev/null
+++ b/src/shader_recompiler/frontend/ir/pred.h
@@ -0,0 +1,28 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <fmt/format.h>
+
+namespace Shader::IR {
+
+enum class Pred { P0, P1, P2, P3, P4, P5, P6, PT };
+
+} // namespace Shader::IR
+
+template <>
+struct fmt::formatter<Shader::IR::Pred> {
+    constexpr auto parse(format_parse_context& ctx) {
+        return ctx.begin();
+    }
+    template <typename FormatContext>
+    auto format(const Shader::IR::Pred& pred, FormatContext& ctx) {
+        if (pred == Shader::IR::Pred::PT) {
+            return fmt::format_to(ctx.out(), "PT");
+        } else {
+            return fmt::format_to(ctx.out(), "P{}", static_cast<int>(pred));
+        }
+    }
+};
diff --git a/src/shader_recompiler/frontend/ir/reg.h b/src/shader_recompiler/frontend/ir/reg.h
new file mode 100644
index 0000000000..316fc4be8d
--- /dev/null
+++ b/src/shader_recompiler/frontend/ir/reg.h
@@ -0,0 +1,314 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <fmt/format.h>
+
+#include "common/common_types.h"
+#include "shader_recompiler/exception.h"
+
+namespace Shader::IR {
+
+enum class Reg : u64 {
+    R0,
+    R1,
+    R2,
+    R3,
+    R4,
+    R5,
+    R6,
+    R7,
+    R8,
+    R9,
+    R10,
+    R11,
+    R12,
+    R13,
+    R14,
+    R15,
+    R16,
+    R17,
+    R18,
+    R19,
+    R20,
+    R21,
+    R22,
+    R23,
+    R24,
+    R25,
+    R26,
+    R27,
+    R28,
+    R29,
+    R30,
+    R31,
+    R32,
+    R33,
+    R34,
+    R35,
+    R36,
+    R37,
+    R38,
+    R39,
+    R40,
+    R41,
+    R42,
+    R43,
+    R44,
+    R45,
+    R46,
+    R47,
+    R48,
+    R49,
+    R50,
+    R51,
+    R52,
+    R53,
+    R54,
+    R55,
+    R56,
+    R57,
+    R58,
+    R59,
+    R60,
+    R61,
+    R62,
+    R63,
+    R64,
+    R65,
+    R66,
+    R67,
+    R68,
+    R69,
+    R70,
+    R71,
+    R72,
+    R73,
+    R74,
+    R75,
+    R76,
+    R77,
+    R78,
+    R79,
+    R80,
+    R81,
+    R82,
+    R83,
+    R84,
+    R85,
+    R86,
+    R87,
+    R88,
+    R89,
+    R90,
+    R91,
+    R92,
+    R93,
+    R94,
+    R95,
+    R96,
+    R97,
+    R98,
+    R99,
+    R100,
+    R101,
+    R102,
+    R103,
+    R104,
+    R105,
+    R106,
+    R107,
+    R108,
+    R109,
+    R110,
+    R111,
+    R112,
+    R113,
+    R114,
+    R115,
+    R116,
+    R117,
+    R118,
+    R119,
+    R120,
+    R121,
+    R122,
+    R123,
+    R124,
+    R125,
+    R126,
+    R127,
+    R128,
+    R129,
+    R130,
+    R131,
+    R132,
+    R133,
+    R134,
+    R135,
+    R136,
+    R137,
+    R138,
+    R139,
+    R140,
+    R141,
+    R142,
+    R143,
+    R144,
+    R145,
+    R146,
+    R147,
+    R148,
+    R149,
+    R150,
+    R151,
+    R152,
+    R153,
+    R154,
+    R155,
+    R156,
+    R157,
+    R158,
+    R159,
+    R160,
+    R161,
+    R162,
+    R163,
+    R164,
+    R165,
+    R166,
+    R167,
+    R168,
+    R169,
+    R170,
+    R171,
+    R172,
+    R173,
+    R174,
+    R175,
+    R176,
+    R177,
+    R178,
+    R179,
+    R180,
+    R181,
+    R182,
+    R183,
+    R184,
+    R185,
+    R186,
+    R187,
+    R188,
+    R189,
+    R190,
+    R191,
+    R192,
+    R193,
+    R194,
+    R195,
+    R196,
+    R197,
+    R198,
+    R199,
+    R200,
+    R201,
+    R202,
+    R203,
+    R204,
+    R205,
+    R206,
+    R207,
+    R208,
+    R209,
+    R210,
+    R211,
+    R212,
+    R213,
+    R214,
+    R215,
+    R216,
+    R217,
+    R218,
+    R219,
+    R220,
+    R221,
+    R222,
+    R223,
+    R224,
+    R225,
+    R226,
+    R227,
+    R228,
+    R229,
+    R230,
+    R231,
+    R232,
+    R233,
+    R234,
+    R235,
+    R236,
+    R237,
+    R238,
+    R239,
+    R240,
+    R241,
+    R242,
+    R243,
+    R244,
+    R245,
+    R246,
+    R247,
+    R248,
+    R249,
+    R250,
+    R251,
+    R252,
+    R253,
+    R254,
+    RZ,
+};
+static_assert(static_cast<int>(Reg::RZ) == 255);
+
+[[nodiscard]] constexpr Reg operator+(Reg reg, int num) {
+    if (reg == Reg::RZ) {
+        // Adding or subtracting registers from RZ yields RZ
+        return Reg::RZ;
+    }
+    const int result{static_cast<int>(reg) + num};
+    if (result >= static_cast<int>(Reg::RZ)) {
+        throw LogicError("Overflow on register arithmetic");
+    }
+    if (result < 0) {
+        throw LogicError("Underflow on register arithmetic");
+    }
+    return static_cast<Reg>(result);
+}
+
+[[nodiscard]] constexpr Reg operator-(Reg reg, int num) {
+    return reg + (-num);
+}
+
+[[nodiscard]] constexpr bool IsAligned(Reg reg, size_t align) {
+    return (static_cast<size_t>(reg) / align) * align == static_cast<size_t>(reg);
+}
+
+} // namespace Shader::IR
+
+template <>
+struct fmt::formatter<Shader::IR::Reg> {
+    constexpr auto parse(format_parse_context& ctx) {
+        return ctx.begin();
+    }
+    template <typename FormatContext>
+    auto format(const Shader::IR::Reg& reg, FormatContext& ctx) {
+        if (reg == Shader::IR::Reg::RZ) {
+            return fmt::format_to(ctx.out(), "RZ");
+        } else if (static_cast<int>(reg) >= 0 && static_cast<int>(reg) < 255) {
+            return fmt::format_to(ctx.out(), "R{}", static_cast<int>(reg));
+        } else {
+            throw Shader::LogicError("Invalid register with raw value {}", static_cast<int>(reg));
+        }
+    }
+};
diff --git a/src/shader_recompiler/frontend/ir/type.cpp b/src/shader_recompiler/frontend/ir/type.cpp
new file mode 100644
index 0000000000..da1e2a0f6c
--- /dev/null
+++ b/src/shader_recompiler/frontend/ir/type.cpp
@@ -0,0 +1,36 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <string>
+
+#include "shader_recompiler/frontend/ir/type.h"
+
+namespace Shader::IR {
+
+std::string NameOf(Type type) {
+    static constexpr std::array names{
+        "Opaque", "Label", "Reg", "Pred", "Attribute", "U1", "U8", "U16", "U32", "U64", "ZSCO",
+    };
+    const size_t bits{static_cast<size_t>(type)};
+    if (bits == 0) {
+        return "Void";
+    }
+    std::string result;
+    for (size_t i = 0; i < names.size(); i++) {
+        if ((bits & (size_t{1} << i)) != 0) {
+            if (!result.empty()) {
+                result += '|';
+            }
+            result += names[i];
+        }
+    }
+    return result;
+}
+
+bool AreTypesCompatible(Type lhs, Type rhs) noexcept {
+    return lhs == rhs || lhs == Type::Opaque || rhs == Type::Opaque;
+}
+
+} // namespace Shader::IR
diff --git a/src/shader_recompiler/frontend/ir/type.h b/src/shader_recompiler/frontend/ir/type.h
new file mode 100644
index 0000000000..f753628e85
--- /dev/null
+++ b/src/shader_recompiler/frontend/ir/type.h
@@ -0,0 +1,47 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+
+#include <fmt/format.h>
+
+#include "common/common_funcs.h"
+#include "shader_recompiler/exception.h"
+
+namespace Shader::IR {
+
+enum class Type {
+    Void = 0,
+    Opaque = 1 << 0,
+    Label = 1 << 1,
+    Reg = 1 << 2,
+    Pred = 1 << 3,
+    Attribute = 1 << 4,
+    U1 = 1 << 5,
+    U8 = 1 << 6,
+    U16 = 1 << 7,
+    U32 = 1 << 8,
+    U64 = 1 << 9,
+    ZSCO = 1 << 10,
+};
+DECLARE_ENUM_FLAG_OPERATORS(Type)
+
+[[nodiscard]] std::string NameOf(Type type);
+
+[[nodiscard]] bool AreTypesCompatible(Type lhs, Type rhs) noexcept;
+
+} // namespace Shader::IR
+
+template <>
+struct fmt::formatter<Shader::IR::Type> {
+    constexpr auto parse(format_parse_context& ctx) {
+        return ctx.begin();
+    }
+    template <typename FormatContext>
+    auto format(const Shader::IR::Type& type, FormatContext& ctx) {
+        return fmt::format_to(ctx.out(), "{}", NameOf(type));
+    }
+};
diff --git a/src/shader_recompiler/frontend/ir/value.cpp b/src/shader_recompiler/frontend/ir/value.cpp
new file mode 100644
index 0000000000..7b5b35d6c5
--- /dev/null
+++ b/src/shader_recompiler/frontend/ir/value.cpp
@@ -0,0 +1,124 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "shader_recompiler/frontend/ir/microinstruction.h"
+#include "shader_recompiler/frontend/ir/opcode.h"
+#include "shader_recompiler/frontend/ir/value.h"
+
+namespace Shader::IR {
+
+Value::Value(IR::Inst* value) noexcept : type{Type::Opaque}, inst{value} {}
+
+Value::Value(IR::Block* value) noexcept : type{Type::Label}, label{value} {}
+
+Value::Value(IR::Reg value) noexcept : type{Type::Reg}, reg{value} {}
+
+Value::Value(IR::Pred value) noexcept : type{Type::Pred}, pred{value} {}
+
+Value::Value(IR::Attribute value) noexcept : type{Type::Attribute}, attribute{value} {}
+
+Value::Value(bool value) noexcept : type{Type::U1}, imm_u1{value} {}
+
+Value::Value(u8 value) noexcept : type{Type::U8}, imm_u8{value} {}
+
+Value::Value(u16 value) noexcept : type{Type::U16}, imm_u16{value} {}
+
+Value::Value(u32 value) noexcept : type{Type::U32}, imm_u32{value} {}
+
+Value::Value(u64 value) noexcept : type{Type::U64}, imm_u64{value} {}
+
+bool Value::IsIdentity() const noexcept {
+    return type == Type::Opaque && inst->Opcode() == Opcode::Identity;
+}
+
+bool Value::IsEmpty() const noexcept {
+    return type == Type::Void;
+}
+
+bool Value::IsImmediate() const noexcept {
+    if (IsIdentity()) {
+        return inst->Arg(0).IsImmediate();
+    }
+    return type != Type::Opaque;
+}
+
+bool Value::IsLabel() const noexcept {
+    return type == Type::Label;
+}
+
+IR::Type Value::Type() const noexcept {
+    if (IsIdentity()) {
+        return inst->Arg(0).Type();
+    }
+    if (type == Type::Opaque) {
+        return inst->Type();
+    }
+    return type;
+}
+
+IR::Inst* Value::Inst() const {
+    ValidateAccess(Type::Opaque);
+    return inst;
+}
+
+IR::Block* Value::Label() const {
+    ValidateAccess(Type::Label);
+    return label;
+}
+
+IR::Inst* Value::InstRecursive() const {
+    ValidateAccess(Type::Opaque);
+    if (IsIdentity()) {
+        return inst->Arg(0).InstRecursive();
+    }
+    return inst;
+}
+
+IR::Reg Value::Reg() const {
+    ValidateAccess(Type::Reg);
+    return reg;
+}
+
+IR::Pred Value::Pred() const {
+    ValidateAccess(Type::Pred);
+    return pred;
+}
+
+IR::Attribute Value::Attribute() const {
+    ValidateAccess(Type::Attribute);
+    return attribute;
+}
+
+bool Value::U1() const {
+    ValidateAccess(Type::U1);
+    return imm_u1;
+}
+
+u8 Value::U8() const {
+    ValidateAccess(Type::U8);
+    return imm_u8;
+}
+
+u16 Value::U16() const {
+    ValidateAccess(Type::U16);
+    return imm_u16;
+}
+
+u32 Value::U32() const {
+    ValidateAccess(Type::U32);
+    return imm_u32;
+}
+
+u64 Value::U64() const {
+    ValidateAccess(Type::U64);
+    return imm_u64;
+}
+
+void Value::ValidateAccess(IR::Type expected) const {
+    if (type != expected) {
+        throw LogicError("Reading {} out of {}", expected, type);
+    }
+}
+
+} // namespace Shader::IR
diff --git a/src/shader_recompiler/frontend/ir/value.h b/src/shader_recompiler/frontend/ir/value.h
new file mode 100644
index 0000000000..664dacf9d7
--- /dev/null
+++ b/src/shader_recompiler/frontend/ir/value.h
@@ -0,0 +1,98 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "shader_recompiler/exception.h"
+#include "shader_recompiler/frontend/ir/attribute.h"
+#include "shader_recompiler/frontend/ir/pred.h"
+#include "shader_recompiler/frontend/ir/reg.h"
+#include "shader_recompiler/frontend/ir/type.h"
+
+namespace Shader::IR {
+
+class Block;
+class Inst;
+
+class Value {
+public:
+    Value() noexcept : type{IR::Type::Void}, inst{nullptr} {}
+    explicit Value(IR::Inst* value) noexcept;
+    explicit Value(IR::Block* value) noexcept;
+    explicit Value(IR::Reg value) noexcept;
+    explicit Value(IR::Pred value) noexcept;
+    explicit Value(IR::Attribute value) noexcept;
+    explicit Value(bool value) noexcept;
+    explicit Value(u8 value) noexcept;
+    explicit Value(u16 value) noexcept;
+    explicit Value(u32 value) noexcept;
+    explicit Value(u64 value) noexcept;
+
+    [[nodiscard]] bool IsIdentity() const noexcept;
+    [[nodiscard]] bool IsEmpty() const noexcept;
+    [[nodiscard]] bool IsImmediate() const noexcept;
+    [[nodiscard]] bool IsLabel() const noexcept;
+    [[nodiscard]] IR::Type Type() const noexcept;
+
+    [[nodiscard]] IR::Inst* Inst() const;
+    [[nodiscard]] IR::Block* Label() const;
+    [[nodiscard]] IR::Inst* InstRecursive() const;
+    [[nodiscard]] IR::Reg Reg() const;
+    [[nodiscard]] IR::Pred Pred() const;
+    [[nodiscard]] IR::Attribute Attribute() const;
+    [[nodiscard]] bool U1() const;
+    [[nodiscard]] u8 U8() const;
+    [[nodiscard]] u16 U16() const;
+    [[nodiscard]] u32 U32() const;
+    [[nodiscard]] u64 U64() const;
+
+private:
+    void ValidateAccess(IR::Type expected) const;
+
+    IR::Type type;
+    union {
+        IR::Inst* inst;
+        IR::Block* label;
+        IR::Reg reg;
+        IR::Pred pred;
+        IR::Attribute attribute;
+        bool imm_u1;
+        u8 imm_u8;
+        u16 imm_u16;
+        u32 imm_u32;
+        u64 imm_u64;
+    };
+};
+
+template <IR::Type type_>
+class TypedValue : public Value {
+public:
+    TypedValue() = default;
+
+    template <IR::Type other_type>
+    requires((other_type & type_) != IR::Type::Void) explicit(false)
+        TypedValue(const TypedValue<other_type>& value)
+        : Value(value) {}
+
+    explicit TypedValue(const Value& value) : Value(value) {
+        if ((value.Type() & type_) == IR::Type::Void) {
+            throw InvalidArgument("Incompatible types {} and {}", type_, value.Type());
+        }
+    }
+
+    explicit TypedValue(IR::Inst* inst) : TypedValue(Value(inst)) {}
+};
+
+using U1 = TypedValue<Type::U1>;
+using U8 = TypedValue<Type::U8>;
+using U16 = TypedValue<Type::U16>;
+using U32 = TypedValue<Type::U32>;
+using U64 = TypedValue<Type::U64>;
+using U32U64 = TypedValue<Type::U32 | Type::U64>;
+using U16U32U64 = TypedValue<Type::U16 | Type::U32 | Type::U64>;
+using UAny = TypedValue<Type::U8 | Type::U16 | Type::U32 | Type::U64>;
+using ZSCO = TypedValue<Type::ZSCO>;
+
+} // namespace Shader::IR
diff --git a/src/shader_recompiler/frontend/maxwell/control_flow.cpp b/src/shader_recompiler/frontend/maxwell/control_flow.cpp
new file mode 100644
index 0000000000..fc4dba8269
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/control_flow.cpp
@@ -0,0 +1,531 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <array>
+#include <optional>
+#include <ranges>
+#include <string>
+#include <utility>
+
+#include <fmt/format.h>
+
+#include "shader_recompiler/exception.h"
+#include "shader_recompiler/frontend/maxwell/control_flow.h"
+#include "shader_recompiler/frontend/maxwell/decode.h"
+#include "shader_recompiler/frontend/maxwell/location.h"
+
+namespace Shader::Maxwell::Flow {
+
+static u32 BranchOffset(Location pc, Instruction inst) {
+    return pc.Offset() + inst.branch.Offset() + 8;
+}
+
+static std::array<Block, 2> Split(Block&& block, Location pc, BlockId new_id) {
+    if (pc <= block.begin || pc >= block.end) {
+        throw InvalidArgument("Invalid address to split={}", pc);
+    }
+    return {
+        Block{
+            .begin{block.begin},
+            .end{pc},
+            .end_class{EndClass::Branch},
+            .id{block.id},
+            .stack{block.stack},
+            .cond{true},
+            .branch_true{new_id},
+            .branch_false{UNREACHABLE_BLOCK_ID},
+        },
+        Block{
+            .begin{pc},
+            .end{block.end},
+            .end_class{block.end_class},
+            .id{new_id},
+            .stack{std::move(block.stack)},
+            .cond{block.cond},
+            .branch_true{block.branch_true},
+            .branch_false{block.branch_false},
+        },
+    };
+}
+
+static Token OpcodeToken(Opcode opcode) {
+    switch (opcode) {
+    case Opcode::PBK:
+    case Opcode::BRK:
+        return Token::PBK;
+    case Opcode::PCNT:
+    case Opcode::CONT:
+        return Token::PBK;
+    case Opcode::PEXIT:
+    case Opcode::EXIT:
+        return Token::PEXIT;
+    case Opcode::PLONGJMP:
+    case Opcode::LONGJMP:
+        return Token::PLONGJMP;
+    case Opcode::PRET:
+    case Opcode::RET:
+    case Opcode::CAL:
+        return Token::PRET;
+    case Opcode::SSY:
+    case Opcode::SYNC:
+        return Token::SSY;
+    default:
+        throw InvalidArgument("{}", opcode);
+    }
+}
+
+static bool IsAbsoluteJump(Opcode opcode) {
+    switch (opcode) {
+    case Opcode::JCAL:
+    case Opcode::JMP:
+    case Opcode::JMX:
+        return true;
+    default:
+        return false;
+    }
+}
+
+static bool HasFlowTest(Opcode opcode) {
+    switch (opcode) {
+    case Opcode::BRA:
+    case Opcode::BRX:
+    case Opcode::EXIT:
+    case Opcode::JMP:
+    case Opcode::JMX:
+    case Opcode::BRK:
+    case Opcode::CONT:
+    case Opcode::LONGJMP:
+    case Opcode::RET:
+    case Opcode::SYNC:
+        return true;
+    case Opcode::CAL:
+    case Opcode::JCAL:
+        return false;
+    default:
+        throw InvalidArgument("Invalid branch {}", opcode);
+    }
+}
+
+static std::string Name(const Block& block) {
+    if (block.begin.IsVirtual()) {
+        return fmt::format("\"Virtual {}\"", block.id);
+    } else {
+        return fmt::format("\"{}\"", block.begin);
+    }
+}
+
+void Stack::Push(Token token, Location target) {
+    entries.push_back({
+        .token{token},
+        .target{target},
+    });
+}
+
+std::pair<Location, Stack> Stack::Pop(Token token) const {
+    const std::optional<Location> pc{Peek(token)};
+    if (!pc) {
+        throw LogicError("Token could not be found");
+    }
+    return {*pc, Remove(token)};
+}
+
+std::optional<Location> Stack::Peek(Token token) const {
+    const auto reverse_entries{entries | std::views::reverse};
+    const auto it{std::ranges::find(reverse_entries, token, &StackEntry::token)};
+    if (it == reverse_entries.end()) {
+        return std::nullopt;
+    }
+    return it->target;
+}
+
+Stack Stack::Remove(Token token) const {
+    const auto reverse_entries{entries | std::views::reverse};
+    const auto it{std::ranges::find(reverse_entries, token, &StackEntry::token)};
+    const auto pos{std::distance(reverse_entries.begin(), it)};
+    Stack result;
+    result.entries.insert(result.entries.end(), entries.begin(), entries.end() - pos - 1);
+    return result;
+}
+
+bool Block::Contains(Location pc) const noexcept {
+    return pc >= begin && pc < end;
+}
+
+Function::Function(Location start_address)
+    : entrypoint{start_address}, labels{Label{
+                                     .address{start_address},
+                                     .block_id{0},
+                                     .stack{},
+                                 }} {}
+
+CFG::CFG(Environment& env_, Location start_address) : env{env_} {
+    functions.emplace_back(start_address);
+    for (FunctionId function_id = 0; function_id < functions.size(); ++function_id) {
+        while (!functions[function_id].labels.empty()) {
+            Function& function{functions[function_id]};
+            Label label{function.labels.back()};
+            function.labels.pop_back();
+            AnalyzeLabel(function_id, label);
+        }
+    }
+}
+
+void CFG::AnalyzeLabel(FunctionId function_id, Label& label) {
+    if (InspectVisitedBlocks(function_id, label)) {
+        // Label address has been visited
+        return;
+    }
+    // Try to find the next block
+    Function* function{&functions[function_id]};
+    Location pc{label.address};
+    const auto next{std::upper_bound(function->blocks.begin(), function->blocks.end(), pc,
+                                     [function](Location pc, u32 block_index) {
+                                         return pc < function->blocks_data[block_index].begin;
+                                     })};
+    const auto next_index{std::distance(function->blocks.begin(), next)};
+    const bool is_last{next == function->blocks.end()};
+    Location next_pc;
+    BlockId next_id{UNREACHABLE_BLOCK_ID};
+    if (!is_last) {
+        next_pc = function->blocks_data[*next].begin;
+        next_id = function->blocks_data[*next].id;
+    }
+    // Insert before the next block
+    Block block{
+        .begin{pc},
+        .end{pc},
+        .end_class{EndClass::Branch},
+        .id{label.block_id},
+        .stack{std::move(label.stack)},
+        .cond{true},
+        .branch_true{UNREACHABLE_BLOCK_ID},
+        .branch_false{UNREACHABLE_BLOCK_ID},
+    };
+    // Analyze instructions until it reaches an already visited block or there's a branch
+    bool is_branch{false};
+    while (is_last || pc < next_pc) {
+        is_branch = AnalyzeInst(block, function_id, pc) == AnalysisState::Branch;
+        if (is_branch) {
+            break;
+        }
+        ++pc;
+    }
+    if (!is_branch) {
+        // If the block finished without a branch,
+        // it means that the next instruction is already visited, jump to it
+        block.end = pc;
+        block.cond = true;
+        block.branch_true = next_id;
+        block.branch_false = UNREACHABLE_BLOCK_ID;
+    }
+    // Function's pointer might be invalid, resolve it again
+    function = &functions[function_id];
+    const u32 new_block_index = static_cast<u32>(function->blocks_data.size());
+    function->blocks.insert(function->blocks.begin() + next_index, new_block_index);
+    function->blocks_data.push_back(std::move(block));
+}
+
+bool CFG::InspectVisitedBlocks(FunctionId function_id, const Label& label) {
+    const Location pc{label.address};
+    Function& function{functions[function_id]};
+    const auto it{std::ranges::find_if(function.blocks, [&function, pc](u32 block_index) {
+        return function.blocks_data[block_index].Contains(pc);
+    })};
+    if (it == function.blocks.end()) {
+        // Address has not been visited
+        return false;
+    }
+    Block& block{function.blocks_data[*it]};
+    if (block.begin == pc) {
+        throw LogicError("Dangling branch");
+    }
+    const u32 first_index{*it};
+    const u32 second_index{static_cast<u32>(function.blocks_data.size())};
+    const std::array new_indices{first_index, second_index};
+    std::array split_blocks{Split(std::move(block), pc, label.block_id)};
+    function.blocks_data[*it] = std::move(split_blocks[0]);
+    function.blocks_data.push_back(std::move(split_blocks[1]));
+    function.blocks.insert(function.blocks.erase(it), new_indices.begin(), new_indices.end());
+    return true;
+}
+
+CFG::AnalysisState CFG::AnalyzeInst(Block& block, FunctionId function_id, Location pc) {
+    const Instruction inst{env.ReadInstruction(pc.Offset())};
+    const Opcode opcode{Decode(inst.raw)};
+    switch (opcode) {
+    case Opcode::BRA:
+    case Opcode::BRX:
+    case Opcode::JMP:
+    case Opcode::JMX:
+    case Opcode::RET:
+        if (!AnalyzeBranch(block, function_id, pc, inst, opcode)) {
+            return AnalysisState::Continue;
+        }
+        switch (opcode) {
+        case Opcode::BRA:
+        case Opcode::JMP:
+            AnalyzeBRA(block, function_id, pc, inst, IsAbsoluteJump(opcode));
+            break;
+        case Opcode::BRX:
+        case Opcode::JMX:
+            AnalyzeBRX(block, pc, inst, IsAbsoluteJump(opcode));
+            break;
+        case Opcode::RET:
+            block.end_class = EndClass::Return;
+            break;
+        default:
+            break;
+        }
+        block.end = pc;
+        return AnalysisState::Branch;
+    case Opcode::BRK:
+    case Opcode::CONT:
+    case Opcode::LONGJMP:
+    case Opcode::SYNC: {
+        if (!AnalyzeBranch(block, function_id, pc, inst, opcode)) {
+            return AnalysisState::Continue;
+        }
+        const auto [stack_pc, new_stack]{block.stack.Pop(OpcodeToken(opcode))};
+        block.branch_true = AddLabel(block, new_stack, stack_pc, function_id);
+        block.end = pc;
+        return AnalysisState::Branch;
+    }
+    case Opcode::PBK:
+    case Opcode::PCNT:
+    case Opcode::PEXIT:
+    case Opcode::PLONGJMP:
+    case Opcode::SSY:
+        block.stack.Push(OpcodeToken(opcode), BranchOffset(pc, inst));
+        return AnalysisState::Continue;
+    case Opcode::EXIT:
+        return AnalyzeEXIT(block, function_id, pc, inst);
+    case Opcode::PRET:
+        throw NotImplementedException("PRET flow analysis");
+    case Opcode::CAL:
+    case Opcode::JCAL: {
+        const bool is_absolute{IsAbsoluteJump(opcode)};
+        const Location cal_pc{is_absolute ? inst.branch.Absolute() : BranchOffset(pc, inst)};
+        // Technically CAL pushes into PRET, but that's implicit in the function call for us
+        // Insert the function into the list if it doesn't exist
+        if (std::ranges::find(functions, cal_pc, &Function::entrypoint) == functions.end()) {
+            functions.push_back(cal_pc);
+        }
+        // Handle CAL like a regular instruction
+        break;
+    }
+    default:
+        break;
+    }
+    const Predicate pred{inst.Pred()};
+    if (pred == Predicate{true} || pred == Predicate{false}) {
+        return AnalysisState::Continue;
+    }
+    const IR::Condition cond{static_cast<IR::Pred>(pred.index), pred.negated};
+    AnalyzeCondInst(block, function_id, pc, EndClass::Branch, cond);
+    return AnalysisState::Branch;
+}
+
+void CFG::AnalyzeCondInst(Block& block, FunctionId function_id, Location pc,
+                          EndClass insn_end_class, IR::Condition cond) {
+    if (block.begin != pc) {
+        // If the block doesn't start in the conditional instruction
+        // mark it as a label to visit it later
+        block.end = pc;
+        block.cond = true;
+        block.branch_true = AddLabel(block, block.stack, pc, function_id);
+        block.branch_false = UNREACHABLE_BLOCK_ID;
+        return;
+    }
+    // Impersonate the visited block with a virtual block
+    // Jump from this virtual to the real conditional instruction and the next instruction
+    Function& function{functions[function_id]};
+    const BlockId conditional_block_id{++function.current_block_id};
+    function.blocks.push_back(static_cast<u32>(function.blocks_data.size()));
+    Block& virtual_block{function.blocks_data.emplace_back(Block{
+        .begin{}, // Virtual block
+        .end{},
+        .end_class{EndClass::Branch},
+        .id{block.id}, // Impersonating
+        .stack{block.stack},
+        .cond{cond},
+        .branch_true{conditional_block_id},
+        .branch_false{UNREACHABLE_BLOCK_ID},
+    })};
+    // Set the end properties of the conditional instruction and give it a new identity
+    Block& conditional_block{block};
+    conditional_block.end = pc;
+    conditional_block.end_class = insn_end_class;
+    conditional_block.id = conditional_block_id;
+    // Add a label to the instruction after the conditional instruction
+    const BlockId endif_block_id{AddLabel(conditional_block, block.stack, pc + 1, function_id)};
+    // Branch to the next instruction from the virtual block
+    virtual_block.branch_false = endif_block_id;
+    // And branch to it from the conditional instruction if it is a branch
+    if (insn_end_class == EndClass::Branch) {
+        conditional_block.cond = true;
+        conditional_block.branch_true = endif_block_id;
+        conditional_block.branch_false = UNREACHABLE_BLOCK_ID;
+    }
+}
+
+bool CFG::AnalyzeBranch(Block& block, FunctionId function_id, Location pc, Instruction inst,
+                        Opcode opcode) {
+    if (inst.branch.is_cbuf) {
+        throw NotImplementedException("Branch with constant buffer offset");
+    }
+    const Predicate pred{inst.Pred()};
+    if (pred == Predicate{false}) {
+        return false;
+    }
+    const bool has_flow_test{HasFlowTest(opcode)};
+    const IR::FlowTest flow_test{has_flow_test ? inst.branch.flow_test.Value() : IR::FlowTest::T};
+    if (pred != Predicate{true} || flow_test != IR::FlowTest::T) {
+        block.cond = IR::Condition(flow_test, static_cast<IR::Pred>(pred.index), pred.negated);
+        block.branch_false = AddLabel(block, block.stack, pc + 1, function_id);
+    } else {
+        block.cond = true;
+    }
+    return true;
+}
+
+void CFG::AnalyzeBRA(Block& block, FunctionId function_id, Location pc, Instruction inst,
+                     bool is_absolute) {
+    const Location bra_pc{is_absolute ? inst.branch.Absolute() : BranchOffset(pc, inst)};
+    block.branch_true = AddLabel(block, block.stack, bra_pc, function_id);
+}
+
+void CFG::AnalyzeBRX(Block&, Location, Instruction, bool is_absolute) {
+    throw NotImplementedException("{}", is_absolute ? "JMX" : "BRX");
+}
+
+void CFG::AnalyzeCAL(Location pc, Instruction inst, bool is_absolute) {
+    const Location cal_pc{is_absolute ? inst.branch.Absolute() : BranchOffset(pc, inst)};
+    // Technically CAL pushes into PRET, but that's implicit in the function call for us
+    // Insert the function to the function list if it doesn't exist
+    const auto it{std::ranges::find(functions, cal_pc, &Function::entrypoint)};
+    if (it == functions.end()) {
+        functions.emplace_back(cal_pc);
+    }
+}
+
+CFG::AnalysisState CFG::AnalyzeEXIT(Block& block, FunctionId function_id, Location pc,
+                                    Instruction inst) {
+    const IR::FlowTest flow_test{inst.branch.flow_test};
+    const Predicate pred{inst.Pred()};
+    if (pred == Predicate{false} || flow_test == IR::FlowTest::F) {
+        // EXIT will never be taken
+        return AnalysisState::Continue;
+    }
+    if (pred != Predicate{true} || flow_test != IR::FlowTest::T) {
+        if (block.stack.Peek(Token::PEXIT).has_value()) {
+            throw NotImplementedException("Conditional EXIT with PEXIT token");
+        }
+        const IR::Condition cond{flow_test, static_cast<IR::Pred>(pred.index), pred.negated};
+        AnalyzeCondInst(block, function_id, pc, EndClass::Exit, cond);
+        return AnalysisState::Branch;
+    }
+    if (const std::optional<Location> exit_pc{block.stack.Peek(Token::PEXIT)}) {
+        const Stack popped_stack{block.stack.Remove(Token::PEXIT)};
+        block.cond = true;
+        block.branch_true = AddLabel(block, popped_stack, *exit_pc, function_id);
+        block.branch_false = UNREACHABLE_BLOCK_ID;
+        return AnalysisState::Branch;
+    }
+    block.end = pc;
+    block.end_class = EndClass::Exit;
+    return AnalysisState::Branch;
+}
+
+BlockId CFG::AddLabel(const Block& block, Stack stack, Location pc, FunctionId function_id) {
+    Function& function{functions[function_id]};
+    if (block.begin == pc) {
+        return block.id;
+    }
+    const auto target{std::ranges::find(function.blocks_data, pc, &Block::begin)};
+    if (target != function.blocks_data.end()) {
+        return target->id;
+    }
+    const BlockId block_id{++function.current_block_id};
+    function.labels.push_back(Label{
+        .address{pc},
+        .block_id{block_id},
+        .stack{std::move(stack)},
+    });
+    return block_id;
+}
+
+std::string CFG::Dot() const {
+    int node_uid{0};
+
+    std::string dot{"digraph shader {\n"};
+    for (const Function& function : functions) {
+        dot += fmt::format("\tsubgraph cluster_{} {{\n", function.entrypoint);
+        dot += fmt::format("\t\tnode [style=filled];\n");
+        for (const u32 block_index : function.blocks) {
+            const Block& block{function.blocks_data[block_index]};
+            const std::string name{Name(block)};
+            const auto add_branch = [&](BlockId branch_id, bool add_label) {
+                const auto it{std::ranges::find(function.blocks_data, branch_id, &Block::id)};
+                dot += fmt::format("\t\t{}->", name);
+                if (it == function.blocks_data.end()) {
+                    dot += fmt::format("\"Unknown label {}\"", branch_id);
+                } else {
+                    dot += Name(*it);
+                };
+                if (add_label && block.cond != true && block.cond != false) {
+                    dot += fmt::format(" [label=\"{}\"]", block.cond);
+                }
+                dot += '\n';
+            };
+            dot += fmt::format("\t\t{};\n", name);
+            switch (block.end_class) {
+            case EndClass::Branch:
+                if (block.cond != false) {
+                    add_branch(block.branch_true, true);
+                }
+                if (block.cond != true) {
+                    add_branch(block.branch_false, false);
+                }
+                break;
+            case EndClass::Exit:
+                dot += fmt::format("\t\t{}->N{};\n", name, node_uid);
+                dot += fmt::format("\t\tN{} [label=\"Exit\"][shape=square][style=stripped];\n",
+                                   node_uid);
+                ++node_uid;
+                break;
+            case EndClass::Return:
+                dot += fmt::format("\t\t{}->N{};\n", name, node_uid);
+                dot += fmt::format("\t\tN{} [label=\"Return\"][shape=square][style=stripped];\n",
+                                   node_uid);
+                ++node_uid;
+                break;
+            case EndClass::Unreachable:
+                dot += fmt::format("\t\t{}->N{};\n", name, node_uid);
+                dot += fmt::format(
+                    "\t\tN{} [label=\"Unreachable\"][shape=square][style=stripped];\n", node_uid);
+                ++node_uid;
+                break;
+            }
+        }
+        if (function.entrypoint == 8) {
+            dot += fmt::format("\t\tlabel = \"main\";\n");
+        } else {
+            dot += fmt::format("\t\tlabel = \"Function {}\";\n", function.entrypoint);
+        }
+        dot += "\t}\n";
+    }
+    if (!functions.empty()) {
+        if (functions.front().blocks.empty()) {
+            dot += "Start;\n";
+        } else {
+            dot += fmt::format("\tStart -> {};\n", Name(functions.front().blocks_data.front()));
+        }
+        dot += fmt::format("\tStart [shape=diamond];\n");
+    }
+    dot += "}\n";
+    return dot;
+}
+
+} // namespace Shader::Maxwell::Flow
diff --git a/src/shader_recompiler/frontend/maxwell/control_flow.h b/src/shader_recompiler/frontend/maxwell/control_flow.h
new file mode 100644
index 0000000000..b2ab0cdc35
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/control_flow.h
@@ -0,0 +1,137 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <compare>
+#include <optional>
+#include <span>
+#include <string>
+#include <vector>
+
+#include <boost/container/small_vector.hpp>
+
+#include "shader_recompiler/environment.h"
+#include "shader_recompiler/frontend/ir/condition.h"
+#include "shader_recompiler/frontend/maxwell/instruction.h"
+#include "shader_recompiler/frontend/maxwell/location.h"
+#include "shader_recompiler/frontend/maxwell/opcode.h"
+
+namespace Shader::Maxwell::Flow {
+
+using BlockId = u32;
+using FunctionId = size_t;
+
+constexpr BlockId UNREACHABLE_BLOCK_ID{static_cast<u32>(-1)};
+
+enum class EndClass {
+    Branch,
+    Exit,
+    Return,
+    Unreachable,
+};
+
+enum class Token {
+    SSY,
+    PBK,
+    PEXIT,
+    PRET,
+    PCNT,
+    PLONGJMP,
+};
+
+struct StackEntry {
+    auto operator<=>(const StackEntry&) const noexcept = default;
+
+    Token token;
+    Location target;
+};
+
+class Stack {
+public:
+    void Push(Token token, Location target);
+    [[nodiscard]] std::pair<Location, Stack> Pop(Token token) const;
+    [[nodiscard]] std::optional<Location> Peek(Token token) const;
+    [[nodiscard]] Stack Remove(Token token) const;
+
+private:
+    boost::container::small_vector<StackEntry, 3> entries;
+};
+
+struct Block {
+    [[nodiscard]] bool Contains(Location pc) const noexcept;
+
+    Location begin;
+    Location end;
+    EndClass end_class;
+    BlockId id;
+    Stack stack;
+    IR::Condition cond;
+    BlockId branch_true;
+    BlockId branch_false;
+};
+
+struct Label {
+    Location address;
+    BlockId block_id;
+    Stack stack;
+};
+
+struct Function {
+    Function(Location start_address);
+
+    Location entrypoint;
+    BlockId current_block_id{0};
+    boost::container::small_vector<Label, 16> labels;
+    boost::container::small_vector<u32, 0x130> blocks;
+    boost::container::small_vector<Block, 0x130> blocks_data;
+};
+
+class CFG {
+    enum class AnalysisState {
+        Branch,
+        Continue,
+    };
+
+public:
+    explicit CFG(Environment& env, Location start_address);
+
+    [[nodiscard]] std::string Dot() const;
+
+    [[nodiscard]] std::span<const Function> Functions() const noexcept {
+        return std::span(functions.data(), functions.size());
+    }
+
+private:
+    void AnalyzeLabel(FunctionId function_id, Label& label);
+
+    /// Inspect already visited blocks.
+    /// Return true when the block has already been visited
+    [[nodiscard]] bool InspectVisitedBlocks(FunctionId function_id, const Label& label);
+
+    [[nodiscard]] AnalysisState AnalyzeInst(Block& block, FunctionId function_id, Location pc);
+
+    void AnalyzeCondInst(Block& block, FunctionId function_id, Location pc, EndClass insn_end_class,
+                         IR::Condition cond);
+
+    /// Return true when the branch instruction is confirmed to be a branch
+    [[nodiscard]] bool AnalyzeBranch(Block& block, FunctionId function_id, Location pc,
+                                     Instruction inst, Opcode opcode);
+
+    void AnalyzeBRA(Block& block, FunctionId function_id, Location pc, Instruction inst,
+                    bool is_absolute);
+    void AnalyzeBRX(Block& block, Location pc, Instruction inst, bool is_absolute);
+    void AnalyzeCAL(Location pc, Instruction inst, bool is_absolute);
+    AnalysisState AnalyzeEXIT(Block& block, FunctionId function_id, Location pc, Instruction inst);
+
+    /// Return the branch target block id
+    [[nodiscard]] BlockId AddLabel(const Block& block, Stack stack, Location pc,
+                                   FunctionId function_id);
+
+    Environment& env;
+    boost::container::small_vector<Function, 1> functions;
+    FunctionId current_function_id{0};
+};
+
+} // namespace Shader::Maxwell::Flow
diff --git a/src/shader_recompiler/frontend/maxwell/decode.cpp b/src/shader_recompiler/frontend/maxwell/decode.cpp
new file mode 100644
index 0000000000..ab1cc6c8d3
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/decode.cpp
@@ -0,0 +1,149 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <array>
+#include <bit>
+#include <memory>
+#include <string_view>
+
+#include "common/common_types.h"
+#include "shader_recompiler/exception.h"
+#include "shader_recompiler/frontend/maxwell/decode.h"
+#include "shader_recompiler/frontend/maxwell/opcode.h"
+
+namespace Shader::Maxwell {
+namespace {
+struct MaskValue {
+    u64 mask;
+    u64 value;
+};
+
+constexpr MaskValue MaskValueFromEncoding(const char* encoding) {
+    u64 mask{};
+    u64 value{};
+    u64 bit{u64(1) << 63};
+    while (*encoding) {
+        switch (*encoding) {
+        case '0':
+            mask |= bit;
+            break;
+        case '1':
+            mask |= bit;
+            value |= bit;
+            break;
+        case '-':
+            break;
+        case ' ':
+            break;
+        default:
+            throw LogicError("Invalid encoding character '{}'", *encoding);
+        }
+        ++encoding;
+        if (*encoding != ' ') {
+            bit >>= 1;
+        }
+    }
+    return MaskValue{.mask{mask}, .value{value}};
+}
+
+struct InstEncoding {
+    MaskValue mask_value;
+    Opcode opcode;
+};
+constexpr std::array UNORDERED_ENCODINGS{
+#define INST(name, cute, encode)                                                                   \
+    InstEncoding{                                                                                  \
+        .mask_value{MaskValueFromEncoding(encode)},                                                \
+        .opcode{Opcode::name},                                                                     \
+    },
+#include "maxwell.inc"
+#undef INST
+};
+
+constexpr auto SortedEncodings() {
+    std::array encodings{UNORDERED_ENCODINGS};
+    std::ranges::sort(encodings, [](const InstEncoding& lhs, const InstEncoding& rhs) {
+        return std::popcount(lhs.mask_value.mask) > std::popcount(rhs.mask_value.mask);
+    });
+    return encodings;
+}
+constexpr auto ENCODINGS{SortedEncodings()};
+
+constexpr int WidestLeftBits() {
+    int bits{64};
+    for (const InstEncoding& encoding : ENCODINGS) {
+        bits = std::min(bits, std::countr_zero(encoding.mask_value.mask));
+    }
+    return 64 - bits;
+}
+constexpr int WIDEST_LEFT_BITS{WidestLeftBits()};
+constexpr int MASK_SHIFT{64 - WIDEST_LEFT_BITS};
+
+constexpr size_t ToFastLookupIndex(u64 value) {
+    return static_cast<size_t>(value >> MASK_SHIFT);
+}
+
+constexpr size_t FastLookupSize() {
+    size_t max_width{};
+    for (const InstEncoding& encoding : ENCODINGS) {
+        max_width = std::max(max_width, ToFastLookupIndex(encoding.mask_value.mask));
+    }
+    return max_width + 1;
+}
+constexpr size_t FAST_LOOKUP_SIZE{FastLookupSize()};
+
+struct InstInfo {
+    [[nodiscard]] u64 Mask() const noexcept {
+        return static_cast<u64>(high_mask) << MASK_SHIFT;
+    }
+
+    [[nodiscard]] u64 Value() const noexcept {
+        return static_cast<u64>(high_value) << MASK_SHIFT;
+    }
+
+    u16 high_mask;
+    u16 high_value;
+    Opcode opcode;
+};
+
+constexpr auto MakeFastLookupTableIndex(size_t index) {
+    std::array<InstInfo, 2> encodings{};
+    size_t element{};
+    for (const auto& encoding : ENCODINGS) {
+        const size_t mask{ToFastLookupIndex(encoding.mask_value.mask)};
+        const size_t value{ToFastLookupIndex(encoding.mask_value.value)};
+        if ((index & mask) == value) {
+            encodings.at(element) = InstInfo{
+                .high_mask{static_cast<u16>(encoding.mask_value.mask >> MASK_SHIFT)},
+                .high_value{static_cast<u16>(encoding.mask_value.value >> MASK_SHIFT)},
+                .opcode{encoding.opcode},
+            };
+            ++element;
+        }
+    }
+    return encodings;
+}
+
+/*constexpr*/ auto MakeFastLookupTable() {
+    auto encodings{std::make_unique<std::array<std::array<InstInfo, 2>, FAST_LOOKUP_SIZE>>()};
+    for (size_t index = 0; index < FAST_LOOKUP_SIZE; ++index) {
+        (*encodings)[index] = MakeFastLookupTableIndex(index);
+    }
+    return encodings;
+}
+const auto FAST_LOOKUP_TABLE{MakeFastLookupTable()};
+} // Anonymous namespace
+
+Opcode Decode(u64 insn) {
+    const auto& table{(*FAST_LOOKUP_TABLE)[ToFastLookupIndex(insn)]};
+    const auto it{std::ranges::find_if(
+        table, [insn](const InstInfo& info) { return (insn & info.Mask()) == info.Value(); })};
+    if (it == table.end()) {
+        throw NotImplementedException("Instruction 0x{:016x} is unknown / unimplemented", insn);
+    }
+    return it->opcode;
+}
+
+} // namespace Shader::Maxwell
diff --git a/src/shader_recompiler/frontend/maxwell/decode.h b/src/shader_recompiler/frontend/maxwell/decode.h
new file mode 100644
index 0000000000..2a3dd28e84
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/decode.h
@@ -0,0 +1,14 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "shader_recompiler/frontend/maxwell/opcode.h"
+
+namespace Shader::Maxwell {
+
+[[nodiscard]] Opcode Decode(u64 insn);
+
+} // namespace Shader::Maxwell
diff --git a/src/shader_recompiler/frontend/maxwell/instruction.h b/src/shader_recompiler/frontend/maxwell/instruction.h
new file mode 100644
index 0000000000..57fd531f2b
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/instruction.h
@@ -0,0 +1,62 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "shader_recompiler/frontend/ir/flow_test.h"
+
+namespace Shader::Maxwell {
+
+struct Predicate {
+    Predicate() = default;
+    Predicate(unsigned index_, bool negated_ = false) : index{index_}, negated{negated_} {}
+    Predicate(bool value) : index{7}, negated{!value} {}
+    Predicate(u64 raw) : index{static_cast<unsigned>(raw & 7)}, negated{(raw & 8) != 0} {}
+
+    unsigned index;
+    bool negated;
+};
+
+inline bool operator==(const Predicate& lhs, const Predicate& rhs) noexcept {
+    return lhs.index == rhs.index && lhs.negated == rhs.negated;
+}
+
+inline bool operator!=(const Predicate& lhs, const Predicate& rhs) noexcept {
+    return !(lhs == rhs);
+}
+
+union Instruction {
+    Instruction(u64 raw_) : raw{raw_} {}
+
+    u64 raw;
+
+    union {
+        BitField<5, 1, u64> is_cbuf;
+        BitField<0, 5, IR::FlowTest> flow_test;
+
+        [[nodiscard]] u32 Absolute() const noexcept {
+            return static_cast<u32>(absolute);
+        }
+
+        [[nodiscard]] s32 Offset() const noexcept {
+            return static_cast<s32>(offset);
+        }
+
+    private:
+        BitField<20, 24, s64> offset;
+        BitField<20, 32, u64> absolute;
+    } branch;
+
+    [[nodiscard]] Predicate Pred() const noexcept {
+        return Predicate{pred};
+    }
+
+private:
+    BitField<16, 4, u64> pred;
+};
+static_assert(std::is_trivially_copyable_v<Instruction>);
+
+} // namespace Shader::Maxwell
diff --git a/src/shader_recompiler/frontend/maxwell/location.h b/src/shader_recompiler/frontend/maxwell/location.h
new file mode 100644
index 0000000000..66b51a19e6
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/location.h
@@ -0,0 +1,106 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <compare>
+#include <iterator>
+
+#include <fmt/format.h>
+
+#include "common/common_types.h"
+#include "shader_recompiler/exception.h"
+
+namespace Shader::Maxwell {
+
+class Location {
+    static constexpr u32 VIRTUAL_OFFSET{std::numeric_limits<u32>::max()};
+
+public:
+    constexpr Location() = default;
+
+    constexpr Location(u32 initial_offset) : offset{initial_offset} {
+        if (initial_offset % 8 != 0) {
+            throw InvalidArgument("initial_offset={} is not a multiple of 8", initial_offset);
+        }
+        Align();
+    }
+
+    [[nodiscard]] constexpr u32 Offset() const noexcept {
+        return offset;
+    }
+
+    [[nodiscard]] constexpr bool IsVirtual() const {
+        return offset == VIRTUAL_OFFSET;
+    }
+
+    constexpr auto operator<=>(const Location&) const noexcept = default;
+
+    constexpr Location operator++() noexcept {
+        const Location copy{*this};
+        Step();
+        return copy;
+    }
+
+    constexpr Location operator++(int) noexcept {
+        Step();
+        return *this;
+    }
+
+    constexpr Location operator--() noexcept {
+        const Location copy{*this};
+        Back();
+        return copy;
+    }
+
+    constexpr Location operator--(int) noexcept {
+        Back();
+        return *this;
+    }
+
+    constexpr Location operator+(int number) const {
+        Location new_pc{*this};
+        while (number > 0) {
+            --number;
+            ++new_pc;
+        }
+        while (number < 0) {
+            ++number;
+            --new_pc;
+        }
+        return new_pc;
+    }
+
+    constexpr Location operator-(int number) const {
+        return operator+(-number);
+    }
+
+private:
+    constexpr void Align() {
+        offset += offset % 32 == 0 ? 8 : 0;
+    }
+
+    constexpr void Step() {
+        offset += 8 + (offset % 32 == 24 ? 8 : 0);
+    }
+
+    constexpr void Back() {
+        offset -= 8 + (offset % 32 == 8 ? 8 : 0);
+    }
+
+    u32 offset{VIRTUAL_OFFSET};
+};
+
+} // namespace Shader::Maxwell
+
+template <>
+struct fmt::formatter<Shader::Maxwell::Location> {
+    constexpr auto parse(format_parse_context& ctx) {
+        return ctx.begin();
+    }
+    template <typename FormatContext>
+    auto format(const Shader::Maxwell::Location& location, FormatContext& ctx) {
+        return fmt::format_to(ctx.out(), "{:04x}", location.Offset());
+    }
+};
diff --git a/src/shader_recompiler/frontend/maxwell/maxwell.inc b/src/shader_recompiler/frontend/maxwell/maxwell.inc
new file mode 100644
index 0000000000..1515285bf8
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/maxwell.inc
@@ -0,0 +1,285 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+INST(AL2P,         "AL2P",           "1110 1111 1010 0---")
+INST(ALD,          "ALD",            "1110 1111 1101 1---")
+INST(AST,          "AST",            "1110 1111 1111 0---")
+INST(ATOM_cas,     "ATOM (cas)",     "1110 1110 1111 ----")
+INST(ATOM,         "ATOM",           "1110 1101 ---- ----")
+INST(ATOMS_cas,    "ATOMS (cas)",    "1110 1110 ---- ----")
+INST(ATOMS,        "ATOMS",          "1110 1100 ---- ----")
+INST(B2R,          "B2R",            "1111 0000 1011 1---")
+INST(BAR,          "BAR",            "1111 0000 1010 1---")
+INST(BFE_reg,      "BFE (reg)",      "0101 1100 0000 0---")
+INST(BFE_cbuf,     "BFE (cbuf)",     "0100 1100 0000 0---")
+INST(BFE_imm,      "BFE (imm)",      "0011 100- 0000 0---")
+INST(BFI_reg,      "BFI (reg)",      "0101 1011 1111 0---")
+INST(BFI_rc,       "BFI (rc)",       "0101 0011 1111 0---")
+INST(BFI_cr,       "BFI (cr)",       "0100 1011 1111 0---")
+INST(BFI_imm,      "BFI (imm)",      "0011 011- 1111 0---")
+INST(BPT,          "BPT",            "1110 0011 1010 ----")
+INST(BRA,          "BRA",            "1110 0010 0100 ----")
+INST(BRK,          "BRK",            "1110 0011 0100 ----")
+INST(BRX,          "BRX",            "1110 0010 0101 ----")
+INST(CAL,          "CAL",            "1110 0010 0110 ----")
+INST(CCTL,         "CCTL",           "1110 1111 011- ----")
+INST(CCTLL,        "CCTLL",          "1110 1111 100- ----")
+INST(CONT,         "CONT",           "1110 0011 0101 ----")
+INST(CS2R,         "CS2R",           "0101 0000 1100 1---")
+INST(CSET,         "CSET",           "0101 0000 1001 1---")
+INST(CSETP,        "CSETP",          "0101 0000 1010 0---")
+INST(DADD_reg,     "DADD (reg)",     "0101 1100 0111 0---")
+INST(DADD_cbuf,    "DADD (cbuf)",    "0100 1100 0111 0---")
+INST(DADD_imm,     "DADD (imm)",     "0011 100- 0111 0---")
+INST(DEPBAR,       "DEPBAR",         "1111 0000 1111 0---")
+INST(DFMA_reg,     "DFMA (reg)",     "0101 1011 0111 ----")
+INST(DFMA_rc,      "DFMA (rc)",      "0101 0011 0111 ----")
+INST(DFMA_cr,      "DFMA (cr)",      "0010 1011 0111 ----")
+INST(DFMA_imm,     "DFMA (imm)",     "0011 011- 0111 ----")
+INST(DMNMX_reg,    "DMNMX (reg)",    "0100 1100 0101 0---")
+INST(DMNMX_cbuf,   "DMNMX (cbuf)",   "0101 1100 0101 0---")
+INST(DMNMX_imm,    "DMNMX (imm)",    "0011 100- 0101 0---")
+INST(DMUL_reg,     "DMUL (reg)",     "0101 1100 1000 0---")
+INST(DMUL_cbuf,    "DMUL (cbuf)",    "0100 1100 1000 0---")
+INST(DMUL_imm,     "DMUL (imm)",     "0011 100- 1000 0---")
+INST(DSET_reg,     "DSET (reg)",     "0101 1001 0--- ----")
+INST(DSET_cbuf,    "DSET (cbuf)",    "0100 1001 0--- ----")
+INST(DSET_imm,     "DSET (imm)",     "0011 001- 0--- ----")
+INST(DSETP_reg,    "DSETP (reg)",    "0101 1011 1000 ----")
+INST(DSETP_cbuf,   "DSETP (cbuf)",   "0100 1011 1000 ----")
+INST(DSETP_imm,    "DSETP (imm)",    "0011 011- 1000 ----")
+INST(EXIT,         "EXIT",           "1110 0011 0000 ----")
+INST(F2F_reg,      "F2F (reg)",      "0101 1100 1010 1---")
+INST(F2F_cbuf,     "F2F (cbuf)",     "0100 1100 1010 1---")
+INST(F2F_imm,      "F2F (imm)",      "0011 100- 1010 1---")
+INST(F2I_reg,      "F2I (reg)",      "0101 1100 1011 0---")
+INST(F2I_cbuf,     "F2I (cbuf)",     "0100 1100 1011 0---")
+INST(F2I_imm,      "F2I (imm)",      "0011 100- 1011 0---")
+INST(FADD_reg,     "FADD (reg)",     "0101 1100 0101 1---")
+INST(FADD_cbuf,    "FADD (cbuf)",    "0100 1100 0101 1---")
+INST(FADD_imm,     "FADD (imm)",     "0011 100- 0101 1---")
+INST(FADD32I,      "FADD32I",        "0000 10-- ---- ----")
+INST(FCHK_reg,     "FCHK (reg)",     "0101 1100 1000 1---")
+INST(FCHK_cbuf,    "FCHK (cbuf)",    "0100 1100 1000 1---")
+INST(FCHK_imm,     "FCHK (imm)",     "0011 100- 1000 1---")
+INST(FCMP_reg,     "FCMP (reg)",     "0101 1011 1010 ----")
+INST(FCMP_rc,      "FCMP (rc)",      "0101 0011 1010 ----")
+INST(FCMP_cr,      "FCMP (cr)",      "0100 1011 1010 ----")
+INST(FCMP_imm,     "FCMP (imm)",     "0011 011- 1010 ----")
+INST(FFMA_reg,     "FFMA (reg)",     "0101 1001 1--- ----")
+INST(FFMA_rc,      "FFMA (rc)",      "0101 0001 1--- ----")
+INST(FFMA_cr,      "FFMA (cr)",      "0100 1001 1--- ----")
+INST(FFMA_imm,     "FFMA (imm)",     "0011 001- 1--- ----")
+INST(FFMA32I,      "FFMA32I",        "0000 11-- ---- ----")
+INST(FLO_reg,      "FLO (reg)",      "0101 1100 0011 0---")
+INST(FLO_cbuf,     "FLO (cbuf)",     "0100 1100 0011 0---")
+INST(FLO_imm,      "FLO (imm)",      "0011 100- 0011 0---")
+INST(FMNMX_reg,    "FMNMX (reg)",    "0101 1100 0110 0---")
+INST(FMNMX_cbuf,   "FMNMX (cbuf)",   "0100 1100 0110 0---")
+INST(FMNMX_imm,    "FMNMX (imm)",    "0011 100- 0110 0---")
+INST(FMUL_reg,     "FMUL (reg)",     "0101 1100 0110 1---")
+INST(FMUL_cbuf,    "FMUL (cbuf)",    "0100 1100 0110 1---")
+INST(FMUL_imm,     "FMUL (imm)",     "0011 100- 0110 1---")
+INST(FMUL32I,      "FMUL32I",        "0001 1110 ---- ----")
+INST(FSET_reg,     "FSET (reg)",     "0101 1000 ---- ----")
+INST(FSET_cbuf,    "FSET (cbuf)",    "0100 1000 ---- ----")
+INST(FSET_imm,     "FSET (imm)",     "0011 000- ---- ----")
+INST(FSETP_reg,    "FSETP (reg)",    "0101 1011 1011 ----")
+INST(FSETP_cbuf,   "FSETP (cbuf)",   "0100 1011 1011 ----")
+INST(FSETP_imm,    "FSETP (imm)",    "0011 011- 1011 ----")
+INST(FSWZADD,      "FSWZADD",        "0101 0000 1111 1---")
+INST(GETCRSPTR,    "GETCRSPTR",      "1110 0010 1100 ----")
+INST(GETLMEMBASE,  "GETLMEMBASE",    "1110 0010 1101 ----")
+INST(HADD2_reg,    "HADD2 (reg)",    "0101 1101 0001 0---")
+INST(HADD2_cbuf,   "HADD2 (cbuf)",   "0111 101- 1--- ----")
+INST(HADD2_imm,    "HADD2 (imm)",    "0111 101- 0--- ----")
+INST(HADD2_32I,    "HADD2_32I",      "0010 110- ---- ----")
+INST(HFMA2_reg,    "HFMA2 (reg)",    "0101 1101 0000 0---")
+INST(HFMA2_rc,     "HFMA2 (rc)",     "0110 0--- 1--- ----")
+INST(HFMA2_cr,     "HFMA2 (cr)",     "0111 0--- 1--- ----")
+INST(HFMA2_imm,    "HFMA2 (imm)",    "0111 0--- 0--- ----")
+INST(HFMA2_32I,    "HFMA2_32I",      "0010 100- ---- ----")
+INST(HMUL2_reg,    "HMUL2 (reg)",    "0101 1101 0000 1---")
+INST(HMUL2_cbuf,   "HMUL2 (cbuf)",   "0111 100- 1--- ----")
+INST(HMUL2_imm,    "HMUL2 (imm)",    "0111 100- 0--- ----")
+INST(HMUL2_32I,    "HMUL2_32I",      "0010 101- ---- ----")
+INST(HSET2_reg,    "HSET2 (reg)",    "0101 1101 0001 1---")
+INST(HSET2_cbuf,   "HSET2 (cbuf)",   "0111 1100 1--- ----")
+INST(HSET2_imm,    "HSET2 (imm)",    "0111 1100 0--- ----")
+INST(HSETP2_reg,   "HSETP2 (reg)",   "0101 1101 0010 0---")
+INST(HSETP2_cbuf,  "HSETP2 (cbuf)",  "0111 111- 1--- ----")
+INST(HSETP2_imm,   "HSETP2 (imm)",   "0111 111- 0--- ----")
+INST(I2F_reg,      "I2F (reg)",      "0101 1100 1011 1---")
+INST(I2F_cbuf,     "I2F (cbuf)",     "0100 1100 1011 1---")
+INST(I2F_imm,      "I2F (imm)",      "0011 100- 1011 1---")
+INST(I2I_reg,      "I2I (reg)",      "0101 1100 1110 0---")
+INST(I2I_cbuf,     "I2I (cbuf)",     "0100 1100 1110 0---")
+INST(I2I_imm,      "I2I (imm)",      "0011 100- 1110 0---")
+INST(IADD_reg,     "IADD (reg)",     "0101 1100 0001 0---")
+INST(IADD_cbuf,    "IADD (cbuf)",    "0100 1100 0001 0---")
+INST(IADD_imm,     "IADD (imm)",     "0011 100- 0001 0---")
+INST(IADD3_reg,    "IADD3 (reg)",    "0101 1100 1100 ----")
+INST(IADD3_cbuf,   "IADD3 (cbuf)",   "0100 1100 1100 ----")
+INST(IADD3_imm,    "IADD3 (imm)",    "0011 100- 1100 ----")
+INST(IADD32I,      "IADD32I",        "0001 110- ---- ----")
+INST(ICMP_reg,     "ICMP (reg)",     "0101 1011 0100 ----")
+INST(ICMP_rc,      "ICMP (rc)",      "0101 0011 0100 ----")
+INST(ICMP_cr,      "ICMP (cr)",      "0100 1011 0100 ----")
+INST(ICMP_imm,     "ICMP (imm)",     "0011 011- 0100 ----")
+INST(IDE,          "IDE",            "1110 0011 1001 ----")
+INST(IDP_reg,      "IDP (reg)",      "0101 0011 1111 1---")
+INST(IDP_imm,      "IDP (imm)",      "0101 0011 1101 1---")
+INST(IMAD_reg,     "IMAD (reg)",     "0101 1010 0--- ----")
+INST(IMAD_rc,      "IMAD (rc)",      "0101 0010 0--- ----")
+INST(IMAD_cr,      "IMAD (cr)",      "0100 1010 0--- ----")
+INST(IMAD_imm,     "IMAD (imm)",     "0011 010- 0--- ----")
+INST(IMAD32I,      "IMAD32I",        "1000 00-- ---- ----")
+INST(IMADSP_reg,   "IMADSP (reg)",   "0101 1010 1--- ----")
+INST(IMADSP_rc,    "IMADSP (rc)",    "0101 0010 1--- ----")
+INST(IMADSP_cr,    "IMADSP (cr)",    "0100 1010 1--- ----")
+INST(IMADSP_imm,   "IMADSP (imm)",   "0011 010- 1--- ----")
+INST(IMNMX_reg,    "IMNMX (reg)",    "0101 1100 0010 0---")
+INST(IMNMX_cbuf,   "IMNMX (cbuf)",   "0100 1100 0010 0---")
+INST(IMNMX_imm,    "IMNMX (imm)",    "0011 100- 0010 0---")
+INST(IMUL_reg,     "IMUL (reg)",     "0101 1100 0011 1---")
+INST(IMUL_cbuf,    "IMUL (cbuf)",    "0100 1100 0011 1---")
+INST(IMUL_imm,     "IMUL (imm)",     "0011 100- 0011 1---")
+INST(IMUL32I,      "IMUL32I",        "0001 1111 ---- ----")
+INST(IPA,          "IPA",            "1110 0000 ---- ----")
+INST(ISBERD,       "ISBERD",         "1110 1111 1101 0---")
+INST(ISCADD_reg,   "ISCADD (reg)",   "0101 1100 0001 1---")
+INST(ISCADD_cbuf,  "ISCADD (cbuf)",  "0100 1100 0001 1---")
+INST(ISCADD_imm,   "ISCADD (imm)",   "0011 100- 0001 1---")
+INST(ISCADD32I,    "ISCADD32I",      "0001 01-- ---- ----")
+INST(ISET_reg,     "ISET (reg)",     "0101 1011 0101 ----")
+INST(ISET_cbuf,    "ISET (cbuf)",    "0100 1011 0101 ----")
+INST(ISET_imm,     "ISET (imm)",     "0011 011- 0101 ----")
+INST(ISETP_reg,    "ISETP (reg)",    "0101 1011 0110 ----")
+INST(ISETP_cbuf,   "ISETP (cbuf)",   "0100 1011 0110 ----")
+INST(ISETP_imm,    "ISETP (imm)",    "0011 011- 0110 ----")
+INST(JCAL,         "JCAL",           "1110 0010 0010 ----")
+INST(JMP,          "JMP",            "1110 0010 0001 ----")
+INST(JMX,          "JMX",            "1110 0010 0000 ----")
+INST(KIL,          "KIL",            "1110 0011 0011 ----")
+INST(LD,           "LD",             "100- ---- ---- ----")
+INST(LDC,          "LDC",            "1110 1111 1001 0---")
+INST(LDG,          "LDG",            "1110 1110 1101 0---")
+INST(LDL,          "LDL",            "1110 1111 0100 0---")
+INST(LDS,          "LDS",            "1110 1111 0100 1---")
+INST(LEA_hi_reg,   "LEA (hi reg)",   "0101 1011 1101 1---")
+INST(LEA_hi_cbuf,  "LEA (hi cbuf)",  "0001 10-- ---- ----")
+INST(LEA_lo_reg,   "LEA (lo reg)",   "0101 1011 1101 0---")
+INST(LEA_lo_cbuf,  "LEA (lo cbuf)",  "0100 1011 1101 ----")
+INST(LEA_lo_imm,   "LEA (lo imm)",   "0011 011- 1101 0---")
+INST(LEPC,         "LEPC",           "0101 0000 1101 0---")
+INST(LONGJMP,      "LONGJMP",        "1110 0011 0001 ----")
+INST(LOP_reg,      "LOP (reg)",      "0101 1100 0100 0---")
+INST(LOP_cbuf,     "LOP (cbuf)",     "0100 1100 0100 0---")
+INST(LOP_imm,      "LOP (imm)",      "0011 100- 0100 0---")
+INST(LOP3_reg,     "LOP3 (reg)",     "0101 1011 1110 0---")
+INST(LOP3_cbuf,    "LOP3 (cbuf)",    "0011 11-- ---- ----")
+INST(LOP3_imm,     "LOP3 (imm)",     "0000 001- ---- ----")
+INST(LOP32I,       "LOP32I",         "0000 01-- ---- ----")
+INST(MEMBAR,       "MEMBAR",         "1110 1111 1001 1---")
+INST(MOV_reg,      "MOV (reg)",      "0101 1100 1001 1---")
+INST(MOV_cbuf,     "MOV (cbuf)",     "0100 1100 1001 1---")
+INST(MOV_imm,      "MOV (imm)",      "0011 100- 1001 1---")
+INST(MOV32I,       "MOV32I",         "0000 0001 0000 ----")
+INST(MUFU,         "MUFU",           "0101 0000 1000 0---")
+INST(NOP,          "NOP",            "0101 0000 1011 0---")
+INST(OUT_reg,      "OUT (reg)",      "1111 1011 1110 0---")
+INST(OUT_cbuf,     "OUT (cbuf)",     "1110 1011 1110 0---")
+INST(OUT_imm,      "OUT (imm)",      "1111 011- 1110 0---")
+INST(P2R_reg,      "P2R (reg)",      "0101 1100 1110 1---")
+INST(P2R_cbuf,     "P2R (cbuf)",     "0100 1100 1110 1---")
+INST(P2R_imm,      "P2R (imm)",      "0011 1000 1110 1---")
+INST(PBK,          "PBK",            "1110 0010 1010 ----")
+INST(PCNT,         "PCNT",           "1110 0010 1011 ----")
+INST(PEXIT,        "PEXIT",          "1110 0010 0011 ----")
+INST(PIXLD,        "PIXLD",          "1110 1111 1110 1---")
+INST(PLONGJMP,     "PLONGJMP",       "1110 0010 1000 ----")
+INST(POPC_reg,     "POPC (reg)",     "0101 1100 0000 1---")
+INST(POPC_cbuf,    "POPC (cbuf)",    "0100 1100 0000 1---")
+INST(POPC_imm,     "POPC (imm)",     "0011 100- 0000 1---")
+INST(PRET,         "PRET",           "1110 0010 0111 ----")
+INST(PRMT_reg,     "PRMT (reg)",     "0101 1011 1100 ----")
+INST(PRMT_rc,      "PRMT (rc)",      "0101 0011 1100 ----")
+INST(PRMT_cr,      "PRMT (cr)",      "0100 1011 1100 ----")
+INST(PRMT_imm,     "PRMT (imm)",     "0011 011- 1100 ----")
+INST(PSET,         "PSET",           "0101 0000 1000 1---")
+INST(PSETP,        "PSETP",          "0101 0000 1001 0---")
+INST(R2B,          "R2B",            "1111 0000 1100 0---")
+INST(R2P_reg,      "R2P (reg)",      "0101 1100 1111 0---")
+INST(R2P_cbuf,     "R2P (cbuf)",     "0100 1100 1111 0---")
+INST(R2P_imm,      "R2P (imm)",      "0011 100- 1111 0---")
+INST(RAM,          "RAM",            "1110 0011 1000 ----")
+INST(RED,          "RED",            "1110 1011 1111 1---")
+INST(RET,          "RET",            "1110 0011 0010 ----")
+INST(RRO_reg,      "RRO (reg)",      "0101 1100 1001 0---")
+INST(RRO_cbuf,     "RRO (cbuf)",     "0100 1100 1001 0---")
+INST(RRO_imm,      "RRO (imm)",      "0011 100- 1001 0---")
+INST(RTT,          "RTT",            "1110 0011 0110 ----")
+INST(S2R,          "S2R",            "1111 0000 1100 1---")
+INST(SAM,          "SAM",            "1110 0011 0111 ----")
+INST(SEL_reg,      "SEL (reg)",      "0101 1100 1010 0---")
+INST(SEL_cbuf,     "SEL (cbuf)",     "0100 1100 1010 0---")
+INST(SEL_imm,      "SEL (imm)",      "0011 100- 1010 0---")
+INST(SETCRSPTR,    "SETCRSPTR",      "1110 0010 1110 ----")
+INST(SETLMEMBASE,  "SETLMEMBASE",    "1110 0010 1111 ----")
+INST(SHF_l_reg,    "SHF (l reg)",    "0101 1011 1111 1---")
+INST(SHF_l_imm,    "SHF (l imm)",    "0011 011- 1111 1---")
+INST(SHF_r_reg,    "SHF (r reg)",    "0101 1100 1111 1---")
+INST(SHF_r_imm,    "SHF (r imm)",    "0011 100- 1111 1---")
+INST(SHFL,         "SHFL",           "1110 1111 0001 0---")
+INST(SHL_reg,      "SHL (reg)",      "0101 1100 0100 1---")
+INST(SHL_cbuf,     "SHL (cbuf)",     "0100 1100 0100 1---")
+INST(SHL_imm,      "SHL (imm)",      "0011 100- 0100 1---")
+INST(SHR_reg,      "SHR (reg)",      "0101 1100 0010 1---")
+INST(SHR_cbuf,     "SHR (cbuf)",     "0100 1100 0010 1---")
+INST(SHR_imm,      "SHR (imm)",      "0011 100- 0010 1---")
+INST(SSY,          "SSY",            "1110 0010 1001 ----")
+INST(ST,           "ST",             "101- ---- ---- ----")
+INST(STG,          "STG",            "1110 1110 1101 1---")
+INST(STL,          "STL",            "1110 1111 0101 0---")
+INST(STP,          "STP",            "1110 1110 1010 0---")
+INST(STS,          "STS",            "1110 1111 0101 1---")
+INST(SUATOM_cas,   "SUATOM",         "1110 1010 ---- ----")
+INST(SULD,         "SULD",           "1110 1011 000- ----")
+INST(SURED,        "SURED",          "1110 1011 010- ----")
+INST(SUST,         "SUST",           "1110 1011 001- ----")
+INST(SYNC,         "SYNC",           "1111 0000 1111 1---")
+INST(TEX,          "TEX",            "1100 00-- --11 1---")
+INST(TEX_b,        "TEX (b)",        "1101 1110 1011 1---")
+INST(TEXS,         "TEXS",           "1101 -00- ---- ----")
+INST(TLD,          "TLD",            "1101 1100 --11 1---")
+INST(TLD_b,        "TLD (b)",        "1101 1101 --11 1---")
+INST(TLD4,         "TLD4",           "1100 10-- --11 1---")
+INST(TLD4_b,       "TLD4 (b)",       "1101 1110 1111 1---")
+INST(TLD4S,        "TLD4S",          "1101 1111 -0-- ----")
+INST(TLDS,         "TLDS",           "1101 -01- ---- ----")
+INST(TMML,         "TMML",           "1101 1111 0101 1---")
+INST(TMML_b,       "TMML (b)",       "1101 1111 0110 0---")
+INST(TXA,          "TXA",            "1101 1111 0100 0---")
+INST(TXD,          "TXD",            "1101 1110 0011 10--")
+INST(TXD_b,        "TXD (b)",        "1101 1110 0111 10--")
+INST(TXQ,          "TXQ",            "1101 1111 0100 1---")
+INST(TXQ_b,        "TXQ (b)",        "1101 1111 0101 0---")
+INST(VABSDIFF,     "VABSDIFF",       "0101 0100 ---- ----")
+INST(VABSDIFF4,    "VABSDIFF4",      "0101 0000 0--- ----")
+INST(VADD,         "VADD",           "0010 00-- ---- ----")
+INST(VMAD,         "VMAD",           "0101 1111 ---- ----")
+INST(VMNMX,        "VMNMX",          "0011 101- ---- ----")
+INST(VOTE,         "VOTE",           "0101 0000 1101 1---")
+INST(VOTE_vtg,     "VOTE (vtg)",     "0101 0000 1110 0---")
+INST(VSET,         "VSET",           "0100 000- ---- ----")
+INST(VSETP,        "VSETP",          "0101 0000 1111 0---")
+INST(VSHL,         "VSHL",           "0101 0111 ---- ----")
+INST(VSHR,         "VSHR",           "0101 0110 ---- ----")
+INST(XMAD_reg,     "XMAD (reg)",     "0101 1011 00-- ----")
+INST(XMAD_rc,      "XMAD (rc)",      "0101 0001 0--- ----")
+INST(XMAD_cr,      "XMAD (cr)",      "0100 111- ---- ----")
+INST(XMAD_imm,     "XMAD (imm)",     "0011 011- 00-- ----")
+
+// Removed due to its weird formatting making fast tables larger
+// INST(CCTLT,        "CCTLT",          "1110 1011 1111 0--0")
diff --git a/src/shader_recompiler/frontend/maxwell/opcode.cpp b/src/shader_recompiler/frontend/maxwell/opcode.cpp
new file mode 100644
index 0000000000..8a7bdb6115
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/opcode.cpp
@@ -0,0 +1,26 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+
+#include "shader_recompiler/exception.h"
+#include "shader_recompiler/frontend/maxwell/opcode.h"
+
+namespace Shader::Maxwell {
+namespace {
+constexpr std::array NAME_TABLE{
+#define INST(name, cute, encode) #cute,
+#include "maxwell.inc"
+#undef INST
+};
+} // Anonymous namespace
+
+const char* NameOf(Opcode opcode) {
+    if (static_cast<size_t>(opcode) >= NAME_TABLE.size()) {
+        throw InvalidArgument("Invalid opcode with raw value {}", static_cast<int>(opcode));
+    }
+    return NAME_TABLE[static_cast<size_t>(opcode)];
+}
+
+} // namespace Shader::Maxwell
diff --git a/src/shader_recompiler/frontend/maxwell/opcode.h b/src/shader_recompiler/frontend/maxwell/opcode.h
new file mode 100644
index 0000000000..cd574f29d0
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/opcode.h
@@ -0,0 +1,30 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <fmt/format.h>
+
+namespace Shader::Maxwell {
+
+enum class Opcode {
+#define INST(name, cute, encode) name,
+#include "maxwell.inc"
+#undef INST
+};
+
+const char* NameOf(Opcode opcode);
+
+} // namespace Shader::Maxwell
+
+template <>
+struct fmt::formatter<Shader::Maxwell::Opcode> {
+    constexpr auto parse(format_parse_context& ctx) {
+        return ctx.begin();
+    }
+    template <typename FormatContext>
+    auto format(const Shader::Maxwell::Opcode& opcode, FormatContext& ctx) {
+        return format_to(ctx.out(), "{}", NameOf(opcode));
+    }
+};
diff --git a/src/shader_recompiler/frontend/maxwell/program.cpp b/src/shader_recompiler/frontend/maxwell/program.cpp
new file mode 100644
index 0000000000..67a98ba57a
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/program.cpp
@@ -0,0 +1,69 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <memory>
+
+#include "shader_recompiler/frontend/maxwell/program.h"
+#include "shader_recompiler/frontend/maxwell/termination_code.h"
+#include "shader_recompiler/frontend/maxwell/translate/translate.h"
+
+namespace Shader::Maxwell {
+
+Program::Function::~Function() {
+    std::ranges::for_each(blocks, &std::destroy_at<IR::Block>);
+}
+
+Program::Program(Environment& env, const Flow::CFG& cfg) {
+    std::vector<IR::Block*> block_map;
+    functions.reserve(cfg.Functions().size());
+
+    for (const Flow::Function& cfg_function : cfg.Functions()) {
+        Function& function{functions.emplace_back()};
+
+        const size_t num_blocks{cfg_function.blocks.size()};
+        IR::Block* block_memory{block_alloc_pool.allocate(num_blocks)};
+        function.blocks.reserve(num_blocks);
+
+        block_map.resize(cfg_function.blocks_data.size());
+
+        // Visit the instructions of all blocks
+        for (const Flow::BlockId block_id : cfg_function.blocks) {
+            const Flow::Block& flow_block{cfg_function.blocks_data[block_id]};
+
+            IR::Block* const block{std::construct_at(block_memory, Translate(env, flow_block))};
+            ++block_memory;
+            function.blocks.push_back(block);
+            block_map[flow_block.id] = block;
+        }
+        // Now that all blocks are defined, emit the termination instructions
+        for (const Flow::BlockId block_id : cfg_function.blocks) {
+            const Flow::Block& flow_block{cfg_function.blocks_data[block_id]};
+            EmitTerminationCode(flow_block, block_map);
+        }
+    }
+}
+
+std::string DumpProgram(const Program& program) {
+    size_t index{0};
+    std::map<const IR::Inst*, size_t> inst_to_index;
+    std::map<const IR::Block*, size_t> block_to_index;
+
+    for (const Program::Function& function : program.functions) {
+        for (const IR::Block* const block : function.blocks) {
+            block_to_index.emplace(block, index);
+            ++index;
+        }
+    }
+    std::string ret;
+    for (const Program::Function& function : program.functions) {
+        ret += fmt::format("Function\n");
+        for (const IR::Block* const block : function.blocks) {
+            ret += IR::DumpBlock(*block, block_to_index, inst_to_index, index) + '\n';
+        }
+    }
+    return ret;
+}
+
+} // namespace Shader::Maxwell
diff --git a/src/shader_recompiler/frontend/maxwell/program.h b/src/shader_recompiler/frontend/maxwell/program.h
new file mode 100644
index 0000000000..7814b2c016
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/program.h
@@ -0,0 +1,39 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include <vector>
+
+#include <boost/pool/pool_alloc.hpp>
+
+#include "shader_recompiler/environment.h"
+#include "shader_recompiler/frontend/ir/basic_block.h"
+#include "shader_recompiler/frontend/maxwell/control_flow.h"
+
+namespace Shader::Maxwell {
+
+class Program {
+    friend std::string DumpProgram(const Program& program);
+
+public:
+    explicit Program(Environment& env, const Flow::CFG& cfg);
+
+private:
+    struct Function {
+        ~Function();
+
+        std::vector<IR::Block*> blocks;
+    };
+
+    boost::pool_allocator<IR::Block, boost::default_user_allocator_new_delete,
+                          boost::details::pool::null_mutex>
+        block_alloc_pool;
+    std::vector<Function> functions;
+};
+
+[[nodiscard]] std::string DumpProgram(const Program& program);
+
+} // namespace Shader::Maxwell
diff --git a/src/shader_recompiler/frontend/maxwell/termination_code.cpp b/src/shader_recompiler/frontend/maxwell/termination_code.cpp
new file mode 100644
index 0000000000..a4ea5c5e38
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/termination_code.cpp
@@ -0,0 +1,79 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <span>
+
+#include "shader_recompiler/exception.h"
+#include "shader_recompiler/frontend/ir/basic_block.h"
+#include "shader_recompiler/frontend/ir/ir_emitter.h"
+#include "shader_recompiler/frontend/maxwell/control_flow.h"
+#include "shader_recompiler/frontend/maxwell/termination_code.h"
+
+namespace Shader::Maxwell {
+
+static void EmitExit(IR::IREmitter& ir) {
+    ir.Exit();
+}
+
+static IR::U1 GetFlowTest(IR::FlowTest flow_test, IR::IREmitter& ir) {
+    switch (flow_test) {
+    case IR::FlowTest::T:
+        return ir.Imm1(true);
+    case IR::FlowTest::F:
+        return ir.Imm1(false);
+    case IR::FlowTest::NE:
+        // FIXME: Verify this
+        return ir.LogicalNot(ir.GetZFlag());
+    case IR::FlowTest::NaN:
+        // FIXME: Verify this
+        return ir.LogicalAnd(ir.GetSFlag(), ir.GetZFlag());
+    default:
+        throw NotImplementedException("Flow test {}", flow_test);
+    }
+}
+
+static IR::U1 GetCond(IR::Condition cond, IR::IREmitter& ir) {
+    const IR::FlowTest flow_test{cond.FlowTest()};
+    const auto [pred, pred_negated]{cond.Pred()};
+    if (pred == IR::Pred::PT && !pred_negated) {
+        return GetFlowTest(flow_test, ir);
+    }
+    if (flow_test == IR::FlowTest::T) {
+        return ir.GetPred(pred, pred_negated);
+    }
+    return ir.LogicalAnd(ir.GetPred(pred, pred_negated), GetFlowTest(flow_test, ir));
+}
+
+static void EmitBranch(const Flow::Block& flow_block, std::span<IR::Block* const> block_map,
+                       IR::IREmitter& ir) {
+    if (flow_block.cond == true) {
+        return ir.Branch(block_map[flow_block.branch_true]);
+    }
+    if (flow_block.cond == false) {
+        return ir.Branch(block_map[flow_block.branch_false]);
+    }
+    return ir.BranchConditional(GetCond(flow_block.cond, ir), block_map[flow_block.branch_true],
+                                block_map[flow_block.branch_false]);
+}
+
+void EmitTerminationCode(const Flow::Block& flow_block, std::span<IR::Block* const> block_map) {
+    IR::Block* const block{block_map[flow_block.id]};
+    IR::IREmitter ir(*block);
+    switch (flow_block.end_class) {
+    case Flow::EndClass::Branch:
+        EmitBranch(flow_block, block_map, ir);
+        break;
+    case Flow::EndClass::Exit:
+        EmitExit(ir);
+        break;
+    case Flow::EndClass::Return:
+        ir.Return();
+        break;
+    case Flow::EndClass::Unreachable:
+        ir.Unreachable();
+        break;
+    }
+}
+
+} // namespace Shader::Maxwell
diff --git a/src/shader_recompiler/frontend/maxwell/termination_code.h b/src/shader_recompiler/frontend/maxwell/termination_code.h
new file mode 100644
index 0000000000..b0d667942e
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/termination_code.h
@@ -0,0 +1,16 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <span>
+
+#include "shader_recompiler/frontend/ir/basic_block.h"
+#include "shader_recompiler/frontend/maxwell/control_flow.h"
+
+namespace Shader::Maxwell {
+
+void EmitTerminationCode(const Flow::Block& flow_block, std::span<IR::Block* const> block_map);
+
+} // namespace Shader::Maxwell
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/exit.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/exit.cpp
new file mode 100644
index 0000000000..e98bbd0d18
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/exit.cpp
@@ -0,0 +1,15 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/common_types.h"
+#include "shader_recompiler/exception.h"
+#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
+
+namespace Shader::Maxwell {
+
+void TranslatorVisitor::EXIT(u64) {
+    ir.Exit();
+}
+
+} // namespace Shader::Maxwell
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_conversion_integer.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_conversion_integer.cpp
new file mode 100644
index 0000000000..c4288d9a83
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_conversion_integer.cpp
@@ -0,0 +1,133 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/common_types.h"
+#include "shader_recompiler/exception.h"
+#include "shader_recompiler/frontend/maxwell/opcode.h"
+#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
+
+namespace Shader::Maxwell {
+namespace {
+enum class DestFormat : u64 {
+    Invalid,
+    I16,
+    I32,
+    I64,
+};
+enum class SrcFormat : u64 {
+    Invalid,
+    F16,
+    F32,
+    F64,
+};
+enum class Rounding : u64 {
+    Round,
+    Floor,
+    Ceil,
+    Trunc,
+};
+
+union F2I {
+    u64 raw;
+    BitField<0, 8, IR::Reg> dest_reg;
+    BitField<8, 2, DestFormat> dest_format;
+    BitField<10, 2, SrcFormat> src_format;
+    BitField<12, 1, u64> is_signed;
+    BitField<39, 1, Rounding> rounding;
+    BitField<49, 1, u64> half;
+    BitField<44, 1, u64> ftz;
+    BitField<45, 1, u64> abs;
+    BitField<47, 1, u64> cc;
+    BitField<49, 1, u64> neg;
+};
+
+size_t BitSize(DestFormat dest_format) {
+    switch (dest_format) {
+    case DestFormat::I16:
+        return 16;
+    case DestFormat::I32:
+        return 32;
+    case DestFormat::I64:
+        return 64;
+    default:
+        throw NotImplementedException("Invalid destination format {}", dest_format);
+    }
+}
+
+void TranslateF2I(TranslatorVisitor& v, u64 insn, const IR::U16U32U64& op_a) {
+    // F2I is used to convert from a floating point value to an integer
+    const F2I f2i{insn};
+
+    const IR::U16U32U64 float_value{v.ir.FPAbsNeg(op_a, f2i.abs != 0, f2i.neg != 0)};
+    const IR::U16U32U64 rounded_value{[&] {
+        switch (f2i.rounding) {
+        case Rounding::Round:
+            return v.ir.FPRoundEven(float_value);
+        case Rounding::Floor:
+            return v.ir.FPFloor(float_value);
+        case Rounding::Ceil:
+            return v.ir.FPCeil(float_value);
+        case Rounding::Trunc:
+            return v.ir.FPTrunc(float_value);
+        default:
+            throw NotImplementedException("Invalid F2I rounding {}", f2i.rounding.Value());
+        }
+    }()};
+
+    // TODO: Handle out of bounds conversions.
+    // For example converting F32 65537.0 to U16, the expected value is 0xffff,
+
+    const bool is_signed{f2i.is_signed != 0};
+    const size_t bitsize{BitSize(f2i.dest_format)};
+    const IR::U16U32U64 result{v.ir.ConvertFToI(bitsize, is_signed, rounded_value)};
+
+    v.X(f2i.dest_reg, result);
+
+    if (f2i.cc != 0) {
+        v.SetZFlag(v.ir.GetZeroFromOp(result));
+        if (is_signed) {
+            v.SetSFlag(v.ir.GetSignFromOp(result));
+        } else {
+            v.ResetSFlag();
+        }
+        v.ResetCFlag();
+
+        // TODO: Investigate if out of bound conversions sets the overflow flag
+        v.ResetOFlag();
+    }
+}
+} // Anonymous namespace
+
+void TranslatorVisitor::F2I_reg(u64 insn) {
+    union {
+        F2I base;
+        BitField<20, 8, IR::Reg> src_reg;
+    } const f2i{insn};
+
+    const IR::U16U32U64 op_a{[&]() -> IR::U16U32U64 {
+        switch (f2i.base.src_format) {
+        case SrcFormat::F16:
+            return ir.CompositeExtract(ir.UnpackFloat2x16(X(f2i.src_reg)), f2i.base.half);
+        case SrcFormat::F32:
+            return X(f2i.src_reg);
+        case SrcFormat::F64:
+            return ir.PackDouble2x32(ir.CompositeConstruct(X(f2i.src_reg), X(f2i.src_reg + 1)));
+        default:
+            throw NotImplementedException("Invalid F2I source format {}",
+                                          f2i.base.src_format.Value());
+        }
+    }()};
+
+    TranslateF2I(*this, insn, op_a);
+}
+
+void TranslatorVisitor::F2I_cbuf(u64) {
+    throw NotImplementedException("{}", Opcode::F2I_cbuf);
+}
+
+void TranslatorVisitor::F2I_imm(u64) {
+    throw NotImplementedException("{}", Opcode::F2I_imm);
+}
+
+} // namespace Shader::Maxwell
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_multi_function.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_multi_function.cpp
new file mode 100644
index 0000000000..e2ab0dab22
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/floating_point_multi_function.cpp
@@ -0,0 +1,71 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "shader_recompiler/exception.h"
+#include "shader_recompiler/frontend/maxwell/opcode.h"
+#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
+
+namespace Shader::Maxwell {
+namespace {
+enum class Operation {
+    Cos = 0,
+    Sin = 1,
+    Ex2 = 2,    // Base 2 exponent
+    Lg2 = 3,    // Base 2 logarithm
+    Rcp = 4,    // Reciprocal
+    Rsq = 5,    // Reciprocal square root
+    Rcp64H = 6, // 64-bit reciprocal
+    Rsq64H = 7, // 64-bit reciprocal square root
+    Sqrt = 8,
+};
+} // Anonymous namespace
+
+void TranslatorVisitor::MUFU(u64 insn) {
+    // MUFU is used to implement a bunch of special functions. See Operation.
+    union {
+        u64 raw;
+        BitField<0, 8, IR::Reg> dest_reg;
+        BitField<8, 8, IR::Reg> src_reg;
+        BitField<20, 4, Operation> operation;
+        BitField<46, 1, u64> abs;
+        BitField<48, 1, u64> neg;
+        BitField<50, 1, u64> sat;
+    } const mufu{insn};
+
+    const IR::U32 op_a{ir.FPAbsNeg(X(mufu.src_reg), mufu.abs != 0, mufu.neg != 0)};
+    IR::U32 value{[&]() -> IR::U32 {
+        switch (mufu.operation) {
+        case Operation::Cos:
+            return ir.FPCosNotReduced(op_a);
+        case Operation::Sin:
+            return ir.FPSinNotReduced(op_a);
+        case Operation::Ex2:
+            return ir.FPExp2NotReduced(op_a);
+        case Operation::Lg2:
+            return ir.FPLog2(op_a);
+        case Operation::Rcp:
+            return ir.FPRecip(op_a);
+        case Operation::Rsq:
+            return ir.FPRecipSqrt(op_a);
+        case Operation::Rcp64H:
+            throw NotImplementedException("MUFU.RCP64H");
+        case Operation::Rsq64H:
+            throw NotImplementedException("MUFU.RSQ64H");
+        case Operation::Sqrt:
+            return ir.FPSqrt(op_a);
+        default:
+            throw NotImplementedException("Invalid MUFU operation {}", mufu.operation.Value());
+        }
+    }()};
+
+    if (mufu.sat) {
+        value = ir.FPSaturate(value);
+    }
+
+    X(mufu.dest_reg, value);
+}
+
+} // namespace Shader::Maxwell
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/impl.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/impl.cpp
new file mode 100644
index 0000000000..7bc7ce9f29
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/impl.cpp
@@ -0,0 +1,79 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/bit_field.h"
+#include "shader_recompiler/frontend/ir/ir_emitter.h"
+#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
+
+namespace Shader::Maxwell {
+
+IR::U32 TranslatorVisitor::X(IR::Reg reg) {
+    return ir.GetReg(reg);
+}
+
+void TranslatorVisitor::X(IR::Reg dest_reg, const IR::U32& value) {
+    ir.SetReg(dest_reg, value);
+}
+
+IR::U32 TranslatorVisitor::GetCbuf(u64 insn) {
+    union {
+        u64 raw;
+        BitField<20, 14, s64> offset;
+        BitField<34, 5, u64> binding;
+    } const cbuf{insn};
+    if (cbuf.binding >= 18) {
+        throw NotImplementedException("Out of bounds constant buffer binding {}", cbuf.binding);
+    }
+    if (cbuf.offset >= 0x10'000 || cbuf.offset < 0) {
+        throw NotImplementedException("Out of bounds constant buffer offset {}", cbuf.offset);
+    }
+    const IR::U32 binding{ir.Imm32(static_cast<u32>(cbuf.binding))};
+    const IR::U32 byte_offset{ir.Imm32(static_cast<u32>(cbuf.offset) * 4)};
+    return ir.GetCbuf(binding, byte_offset);
+}
+
+IR::U32 TranslatorVisitor::GetImm(u64 insn) {
+    union {
+        u64 raw;
+        BitField<20, 19, u64> value;
+        BitField<56, 1, u64> is_negative;
+    } const imm{insn};
+    const s32 positive_value{static_cast<s32>(imm.value)};
+    const s32 value{imm.is_negative != 0 ? -positive_value : positive_value};
+    return ir.Imm32(value);
+}
+
+void TranslatorVisitor::SetZFlag(const IR::U1& value) {
+    ir.SetZFlag(value);
+}
+
+void TranslatorVisitor::SetSFlag(const IR::U1& value) {
+    ir.SetSFlag(value);
+}
+
+void TranslatorVisitor::SetCFlag(const IR::U1& value) {
+    ir.SetCFlag(value);
+}
+
+void TranslatorVisitor::SetOFlag(const IR::U1& value) {
+    ir.SetOFlag(value);
+}
+
+void TranslatorVisitor::ResetZero() {
+    SetZFlag(ir.Imm1(false));
+}
+
+void TranslatorVisitor::ResetSFlag() {
+    SetSFlag(ir.Imm1(false));
+}
+
+void TranslatorVisitor::ResetCFlag() {
+    SetCFlag(ir.Imm1(false));
+}
+
+void TranslatorVisitor::ResetOFlag() {
+    SetOFlag(ir.Imm1(false));
+}
+
+} // namespace Shader::Maxwell
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/impl.h b/src/shader_recompiler/frontend/maxwell/translate/impl/impl.h
new file mode 100644
index 0000000000..bc607b0025
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/impl.h
@@ -0,0 +1,316 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "shader_recompiler/environment.h"
+#include "shader_recompiler/frontend/ir/basic_block.h"
+#include "shader_recompiler/frontend/ir/ir_emitter.h"
+#include "shader_recompiler/frontend/maxwell/instruction.h"
+
+namespace Shader::Maxwell {
+
+class TranslatorVisitor {
+public:
+    explicit TranslatorVisitor(Environment& env_, IR::Block& block) : env{env_} ,ir(block) {}
+
+    Environment& env;
+    IR::IREmitter ir;
+
+    void AL2P(u64 insn);
+    void ALD(u64 insn);
+    void AST(u64 insn);
+    void ATOM_cas(u64 insn);
+    void ATOM(u64 insn);
+    void ATOMS_cas(u64 insn);
+    void ATOMS(u64 insn);
+    void B2R(u64 insn);
+    void BAR(u64 insn);
+    void BFE_reg(u64 insn);
+    void BFE_cbuf(u64 insn);
+    void BFE_imm(u64 insn);
+    void BFI_reg(u64 insn);
+    void BFI_rc(u64 insn);
+    void BFI_cr(u64 insn);
+    void BFI_imm(u64 insn);
+    void BPT(u64 insn);
+    void BRA(u64 insn);
+    void BRK(u64 insn);
+    void BRX(u64 insn);
+    void CAL(u64 insn);
+    void CCTL(u64 insn);
+    void CCTLL(u64 insn);
+    void CONT(u64 insn);
+    void CS2R(u64 insn);
+    void CSET(u64 insn);
+    void CSETP(u64 insn);
+    void DADD_reg(u64 insn);
+    void DADD_cbuf(u64 insn);
+    void DADD_imm(u64 insn);
+    void DEPBAR(u64 insn);
+    void DFMA_reg(u64 insn);
+    void DFMA_rc(u64 insn);
+    void DFMA_cr(u64 insn);
+    void DFMA_imm(u64 insn);
+    void DMNMX_reg(u64 insn);
+    void DMNMX_cbuf(u64 insn);
+    void DMNMX_imm(u64 insn);
+    void DMUL_reg(u64 insn);
+    void DMUL_cbuf(u64 insn);
+    void DMUL_imm(u64 insn);
+    void DSET_reg(u64 insn);
+    void DSET_cbuf(u64 insn);
+    void DSET_imm(u64 insn);
+    void DSETP_reg(u64 insn);
+    void DSETP_cbuf(u64 insn);
+    void DSETP_imm(u64 insn);
+    void EXIT(u64 insn);
+    void F2F_reg(u64 insn);
+    void F2F_cbuf(u64 insn);
+    void F2F_imm(u64 insn);
+    void F2I_reg(u64 insn);
+    void F2I_cbuf(u64 insn);
+    void F2I_imm(u64 insn);
+    void FADD_reg(u64 insn);
+    void FADD_cbuf(u64 insn);
+    void FADD_imm(u64 insn);
+    void FADD32I(u64 insn);
+    void FCHK_reg(u64 insn);
+    void FCHK_cbuf(u64 insn);
+    void FCHK_imm(u64 insn);
+    void FCMP_reg(u64 insn);
+    void FCMP_rc(u64 insn);
+    void FCMP_cr(u64 insn);
+    void FCMP_imm(u64 insn);
+    void FFMA_reg(u64 insn);
+    void FFMA_rc(u64 insn);
+    void FFMA_cr(u64 insn);
+    void FFMA_imm(u64 insn);
+    void FFMA32I(u64 insn);
+    void FLO_reg(u64 insn);
+    void FLO_cbuf(u64 insn);
+    void FLO_imm(u64 insn);
+    void FMNMX_reg(u64 insn);
+    void FMNMX_cbuf(u64 insn);
+    void FMNMX_imm(u64 insn);
+    void FMUL_reg(u64 insn);
+    void FMUL_cbuf(u64 insn);
+    void FMUL_imm(u64 insn);
+    void FMUL32I(u64 insn);
+    void FSET_reg(u64 insn);
+    void FSET_cbuf(u64 insn);
+    void FSET_imm(u64 insn);
+    void FSETP_reg(u64 insn);
+    void FSETP_cbuf(u64 insn);
+    void FSETP_imm(u64 insn);
+    void FSWZADD(u64 insn);
+    void GETCRSPTR(u64 insn);
+    void GETLMEMBASE(u64 insn);
+    void HADD2_reg(u64 insn);
+    void HADD2_cbuf(u64 insn);
+    void HADD2_imm(u64 insn);
+    void HADD2_32I(u64 insn);
+    void HFMA2_reg(u64 insn);
+    void HFMA2_rc(u64 insn);
+    void HFMA2_cr(u64 insn);
+    void HFMA2_imm(u64 insn);
+    void HFMA2_32I(u64 insn);
+    void HMUL2_reg(u64 insn);
+    void HMUL2_cbuf(u64 insn);
+    void HMUL2_imm(u64 insn);
+    void HMUL2_32I(u64 insn);
+    void HSET2_reg(u64 insn);
+    void HSET2_cbuf(u64 insn);
+    void HSET2_imm(u64 insn);
+    void HSETP2_reg(u64 insn);
+    void HSETP2_cbuf(u64 insn);
+    void HSETP2_imm(u64 insn);
+    void I2F_reg(u64 insn);
+    void I2F_cbuf(u64 insn);
+    void I2F_imm(u64 insn);
+    void I2I_reg(u64 insn);
+    void I2I_cbuf(u64 insn);
+    void I2I_imm(u64 insn);
+    void IADD_reg(u64 insn);
+    void IADD_cbuf(u64 insn);
+    void IADD_imm(u64 insn);
+    void IADD3_reg(u64 insn);
+    void IADD3_cbuf(u64 insn);
+    void IADD3_imm(u64 insn);
+    void IADD32I(u64 insn);
+    void ICMP_reg(u64 insn);
+    void ICMP_rc(u64 insn);
+    void ICMP_cr(u64 insn);
+    void ICMP_imm(u64 insn);
+    void IDE(u64 insn);
+    void IDP_reg(u64 insn);
+    void IDP_imm(u64 insn);
+    void IMAD_reg(u64 insn);
+    void IMAD_rc(u64 insn);
+    void IMAD_cr(u64 insn);
+    void IMAD_imm(u64 insn);
+    void IMAD32I(u64 insn);
+    void IMADSP_reg(u64 insn);
+    void IMADSP_rc(u64 insn);
+    void IMADSP_cr(u64 insn);
+    void IMADSP_imm(u64 insn);
+    void IMNMX_reg(u64 insn);
+    void IMNMX_cbuf(u64 insn);
+    void IMNMX_imm(u64 insn);
+    void IMUL_reg(u64 insn);
+    void IMUL_cbuf(u64 insn);
+    void IMUL_imm(u64 insn);
+    void IMUL32I(u64 insn);
+    void IPA(u64 insn);
+    void ISBERD(u64 insn);
+    void ISCADD_reg(u64 insn);
+    void ISCADD_cbuf(u64 insn);
+    void ISCADD_imm(u64 insn);
+    void ISCADD32I(u64 insn);
+    void ISET_reg(u64 insn);
+    void ISET_cbuf(u64 insn);
+    void ISET_imm(u64 insn);
+    void ISETP_reg(u64 insn);
+    void ISETP_cbuf(u64 insn);
+    void ISETP_imm(u64 insn);
+    void JCAL(u64 insn);
+    void JMP(u64 insn);
+    void JMX(u64 insn);
+    void KIL(u64 insn);
+    void LD(u64 insn);
+    void LDC(u64 insn);
+    void LDG(u64 insn);
+    void LDL(u64 insn);
+    void LDS(u64 insn);
+    void LEA_hi_reg(u64 insn);
+    void LEA_hi_cbuf(u64 insn);
+    void LEA_lo_reg(u64 insn);
+    void LEA_lo_cbuf(u64 insn);
+    void LEA_lo_imm(u64 insn);
+    void LEPC(u64 insn);
+    void LONGJMP(u64 insn);
+    void LOP_reg(u64 insn);
+    void LOP_cbuf(u64 insn);
+    void LOP_imm(u64 insn);
+    void LOP3_reg(u64 insn);
+    void LOP3_cbuf(u64 insn);
+    void LOP3_imm(u64 insn);
+    void LOP32I(u64 insn);
+    void MEMBAR(u64 insn);
+    void MOV_reg(u64 insn);
+    void MOV_cbuf(u64 insn);
+    void MOV_imm(u64 insn);
+    void MOV32I(u64 insn);
+    void MUFU(u64 insn);
+    void NOP(u64 insn);
+    void OUT_reg(u64 insn);
+    void OUT_cbuf(u64 insn);
+    void OUT_imm(u64 insn);
+    void P2R_reg(u64 insn);
+    void P2R_cbuf(u64 insn);
+    void P2R_imm(u64 insn);
+    void PBK(u64 insn);
+    void PCNT(u64 insn);
+    void PEXIT(u64 insn);
+    void PIXLD(u64 insn);
+    void PLONGJMP(u64 insn);
+    void POPC_reg(u64 insn);
+    void POPC_cbuf(u64 insn);
+    void POPC_imm(u64 insn);
+    void PRET(u64 insn);
+    void PRMT_reg(u64 insn);
+    void PRMT_rc(u64 insn);
+    void PRMT_cr(u64 insn);
+    void PRMT_imm(u64 insn);
+    void PSET(u64 insn);
+    void PSETP(u64 insn);
+    void R2B(u64 insn);
+    void R2P_reg(u64 insn);
+    void R2P_cbuf(u64 insn);
+    void R2P_imm(u64 insn);
+    void RAM(u64 insn);
+    void RED(u64 insn);
+    void RET(u64 insn);
+    void RRO_reg(u64 insn);
+    void RRO_cbuf(u64 insn);
+    void RRO_imm(u64 insn);
+    void RTT(u64 insn);
+    void S2R(u64 insn);
+    void SAM(u64 insn);
+    void SEL_reg(u64 insn);
+    void SEL_cbuf(u64 insn);
+    void SEL_imm(u64 insn);
+    void SETCRSPTR(u64 insn);
+    void SETLMEMBASE(u64 insn);
+    void SHF_l_reg(u64 insn);
+    void SHF_l_imm(u64 insn);
+    void SHF_r_reg(u64 insn);
+    void SHF_r_imm(u64 insn);
+    void SHFL(u64 insn);
+    void SHL_reg(u64 insn);
+    void SHL_cbuf(u64 insn);
+    void SHL_imm(u64 insn);
+    void SHR_reg(u64 insn);
+    void SHR_cbuf(u64 insn);
+    void SHR_imm(u64 insn);
+    void SSY(u64 insn);
+    void ST(u64 insn);
+    void STG(u64 insn);
+    void STL(u64 insn);
+    void STP(u64 insn);
+    void STS(u64 insn);
+    void SUATOM_cas(u64 insn);
+    void SULD(u64 insn);
+    void SURED(u64 insn);
+    void SUST(u64 insn);
+    void SYNC(u64 insn);
+    void TEX(u64 insn);
+    void TEX_b(u64 insn);
+    void TEXS(u64 insn);
+    void TLD(u64 insn);
+    void TLD_b(u64 insn);
+    void TLD4(u64 insn);
+    void TLD4_b(u64 insn);
+    void TLD4S(u64 insn);
+    void TLDS(u64 insn);
+    void TMML(u64 insn);
+    void TMML_b(u64 insn);
+    void TXA(u64 insn);
+    void TXD(u64 insn);
+    void TXD_b(u64 insn);
+    void TXQ(u64 insn);
+    void TXQ_b(u64 insn);
+    void VABSDIFF(u64 insn);
+    void VABSDIFF4(u64 insn);
+    void VADD(u64 insn);
+    void VMAD(u64 insn);
+    void VMNMX(u64 insn);
+    void VOTE(u64 insn);
+    void VOTE_vtg(u64 insn);
+    void VSET(u64 insn);
+    void VSETP(u64 insn);
+    void VSHL(u64 insn);
+    void VSHR(u64 insn);
+    void XMAD_reg(u64 insn);
+    void XMAD_rc(u64 insn);
+    void XMAD_cr(u64 insn);
+    void XMAD_imm(u64 insn);
+
+    [[nodiscard]] IR::U32 X(IR::Reg reg);
+    void X(IR::Reg dest_reg, const IR::U32& value);
+
+    [[nodiscard]] IR::U32 GetCbuf(u64 insn);
+
+    [[nodiscard]] IR::U32 GetImm(u64 insn);
+
+    void SetZFlag(const IR::U1& value);
+    void SetSFlag(const IR::U1& value);
+    void SetCFlag(const IR::U1& value);
+    void SetOFlag(const IR::U1& value);
+
+    void ResetZero();
+    void ResetSFlag();
+    void ResetCFlag();
+    void ResetOFlag();
+};
+
+} // namespace Shader::Maxwell
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_attribute.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_attribute.cpp
new file mode 100644
index 0000000000..23512db1a4
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_attribute.cpp
@@ -0,0 +1,92 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "shader_recompiler/exception.h"
+#include "shader_recompiler/frontend/maxwell/opcode.h"
+#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
+
+namespace Shader::Maxwell {
+namespace {
+enum class InterpolationMode : u64 {
+    Pass = 0,
+    Multiply = 1,
+    Constant = 2,
+    Sc = 3,
+};
+
+enum class SampleMode : u64 {
+    Default = 0,
+    Centroid = 1,
+    Offset = 2,
+};
+} // Anonymous namespace
+
+void TranslatorVisitor::IPA(u64 insn) {
+    // IPA is the instruction used to read varyings from a fragment shader.
+    // gl_FragCoord is mapped to the gl_Position attribute.
+    // It yields unknown results when used outside of the fragment shader stage.
+    union {
+        u64 raw;
+        BitField<0, 8, IR::Reg> dest_reg;
+        BitField<8, 8, IR::Reg> index_reg;
+        BitField<20, 8, IR::Reg> multiplier;
+        BitField<30, 8, IR::Attribute> attribute;
+        BitField<38, 1, u64> idx;
+        BitField<51, 1, u64> sat;
+        BitField<52, 2, SampleMode> sample_mode;
+        BitField<54, 2, InterpolationMode> interpolation_mode;
+    } const ipa{insn};
+
+    // Indexed IPAs are used for indexed varyings.
+    // For example:
+    //
+    // in vec4 colors[4];
+    // uniform int idx;
+    // void main() {
+    //     gl_FragColor = colors[idx];
+    // }
+    const bool is_indexed{ipa.idx != 0 && ipa.index_reg != IR::Reg::RZ};
+    if (is_indexed) {
+        throw NotImplementedException("IPA.IDX");
+    }
+
+    const IR::Attribute attribute{ipa.attribute};
+    IR::U32 value{ir.GetAttribute(attribute)};
+    if (IR::IsGeneric(attribute)) {
+        // const bool is_perspective{UnimplementedReadHeader(GenericAttributeIndex(attribute))};
+        const bool is_perspective{false};
+        if (is_perspective) {
+            const IR::U32 rcp_position_w{ir.FPRecip(ir.GetAttribute(IR::Attribute::PositionW))};
+            value = ir.FPMul(value, rcp_position_w);
+        }
+    }
+
+    switch (ipa.interpolation_mode) {
+    case InterpolationMode::Pass:
+        break;
+    case InterpolationMode::Multiply:
+        value = ir.FPMul(value, ir.GetReg(ipa.multiplier));
+        break;
+    case InterpolationMode::Constant:
+        throw NotImplementedException("IPA.CONSTANT");
+    case InterpolationMode::Sc:
+        throw NotImplementedException("IPA.SC");
+    }
+
+    // Saturated IPAs are generally generated out of clamped varyings.
+    // For example: clamp(some_varying, 0.0, 1.0)
+    const bool is_saturated{ipa.sat != 0};
+    if (is_saturated) {
+        if (attribute == IR::Attribute::FrontFace) {
+            throw NotImplementedException("IPA.SAT on FrontFace");
+        }
+        value = ir.FPSaturate(value);
+    }
+
+    ir.SetReg(ipa.dest_reg, value);
+}
+
+} // namespace Shader::Maxwell
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_memory.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_memory.cpp
new file mode 100644
index 0000000000..d8fd387cfb
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/load_store_memory.cpp
@@ -0,0 +1,90 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "shader_recompiler/exception.h"
+#include "shader_recompiler/frontend/maxwell/opcode.h"
+#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
+
+namespace Shader::Maxwell {
+namespace {
+enum class StoreSize : u64 {
+    U8,
+    S8,
+    U16,
+    S16,
+    B32,
+    B64,
+    B128,
+};
+
+// See Table 28 in https://docs.nvidia.com/cuda/parallel-thread-execution/index.html
+enum class StoreCache : u64 {
+    WB, // Cache write-back all coherent levels
+    CG, // Cache at global level
+    CS, // Cache streaming, likely to be accessed once
+    WT, // Cache write-through (to system memory)
+};
+} // Anonymous namespace
+
+void TranslatorVisitor::STG(u64 insn) {
+    // STG stores registers into global memory.
+    union {
+        u64 raw;
+        BitField<0, 8, IR::Reg> data_reg;
+        BitField<8, 8, IR::Reg> addr_reg;
+        BitField<45, 1, u64> e;
+        BitField<46, 2, StoreCache> cache;
+        BitField<48, 3, StoreSize> size;
+    } const stg{insn};
+
+    const IR::U64 address{[&]() -> IR::U64 {
+        if (stg.e == 0) {
+            // STG without .E uses a 32-bit pointer, zero-extend it
+            return ir.ConvertU(64, X(stg.addr_reg));
+        }
+        if (!IR::IsAligned(stg.addr_reg, 2)) {
+            throw NotImplementedException("Unaligned address register");
+        }
+        // Pack two registers to build the 32-bit address
+        return ir.PackUint2x32(ir.CompositeConstruct(X(stg.addr_reg), X(stg.addr_reg + 1)));
+    }()};
+
+    switch (stg.size) {
+    case StoreSize::U8:
+        ir.WriteGlobalU8(address, X(stg.data_reg));
+        break;
+    case StoreSize::S8:
+        ir.WriteGlobalS8(address, X(stg.data_reg));
+        break;
+    case StoreSize::U16:
+        ir.WriteGlobalU16(address, X(stg.data_reg));
+        break;
+    case StoreSize::S16:
+        ir.WriteGlobalS16(address, X(stg.data_reg));
+        break;
+    case StoreSize::B32:
+        ir.WriteGlobal32(address, X(stg.data_reg));
+        break;
+    case StoreSize::B64: {
+        if (!IR::IsAligned(stg.data_reg, 2)) {
+            throw NotImplementedException("Unaligned data registers");
+        }
+        const IR::Value vector{ir.CompositeConstruct(X(stg.data_reg), X(stg.data_reg + 1))};
+        ir.WriteGlobal64(address, vector);
+        break;
+    }
+    case StoreSize::B128:
+        if (!IR::IsAligned(stg.data_reg, 4)) {
+            throw NotImplementedException("Unaligned data registers");
+        }
+        const IR::Value vector{ir.CompositeConstruct(X(stg.data_reg), X(stg.data_reg + 1),
+                                                     X(stg.data_reg + 2), X(stg.data_reg + 3))};
+        ir.WriteGlobal128(address, vector);
+        break;
+    }
+}
+
+} // namespace Shader::Maxwell
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp
new file mode 100644
index 0000000000..c907c1ffb5
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/not_implemented.cpp
@@ -0,0 +1,1105 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/common_types.h"
+#include "shader_recompiler/exception.h"
+#include "shader_recompiler/frontend/maxwell/opcode.h"
+#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
+
+#include "shader_recompiler/ir_opt/passes.h"
+
+namespace Shader::Maxwell {
+
+[[maybe_unused]] static inline void DumpOptimized(IR::Block& block) {
+    auto raw{IR::DumpBlock(block)};
+
+    Optimization::GetSetElimination(block);
+    Optimization::DeadCodeEliminationPass(block);
+    Optimization::IdentityRemovalPass(block);
+    auto dumped{IR::DumpBlock(block)};
+
+    fmt::print(stderr, "{}", dumped);
+}
+
+[[noreturn]] static void ThrowNotImplemented(Opcode opcode) {
+    throw NotImplementedException("Instruction {} is not implemented", opcode);
+}
+
+void TranslatorVisitor::AL2P(u64) {
+    ThrowNotImplemented(Opcode::AL2P);
+}
+
+void TranslatorVisitor::ALD(u64) {
+    ThrowNotImplemented(Opcode::ALD);
+}
+
+void TranslatorVisitor::AST(u64) {
+    ThrowNotImplemented(Opcode::AST);
+}
+
+void TranslatorVisitor::ATOM_cas(u64) {
+    ThrowNotImplemented(Opcode::ATOM_cas);
+}
+
+void TranslatorVisitor::ATOM(u64) {
+    ThrowNotImplemented(Opcode::ATOM);
+}
+
+void TranslatorVisitor::ATOMS_cas(u64) {
+    ThrowNotImplemented(Opcode::ATOMS_cas);
+}
+
+void TranslatorVisitor::ATOMS(u64) {
+    ThrowNotImplemented(Opcode::ATOMS);
+}
+
+void TranslatorVisitor::B2R(u64) {
+    ThrowNotImplemented(Opcode::B2R);
+}
+
+void TranslatorVisitor::BAR(u64) {
+    ThrowNotImplemented(Opcode::BAR);
+}
+
+void TranslatorVisitor::BFE_reg(u64) {
+    ThrowNotImplemented(Opcode::BFE_reg);
+}
+
+void TranslatorVisitor::BFE_cbuf(u64) {
+    ThrowNotImplemented(Opcode::BFE_cbuf);
+}
+
+void TranslatorVisitor::BFE_imm(u64) {
+    ThrowNotImplemented(Opcode::BFE_imm);
+}
+
+void TranslatorVisitor::BFI_reg(u64) {
+    ThrowNotImplemented(Opcode::BFI_reg);
+}
+
+void TranslatorVisitor::BFI_rc(u64) {
+    ThrowNotImplemented(Opcode::BFI_rc);
+}
+
+void TranslatorVisitor::BFI_cr(u64) {
+    ThrowNotImplemented(Opcode::BFI_cr);
+}
+
+void TranslatorVisitor::BFI_imm(u64) {
+    ThrowNotImplemented(Opcode::BFI_imm);
+}
+
+void TranslatorVisitor::BPT(u64) {
+    ThrowNotImplemented(Opcode::BPT);
+}
+
+void TranslatorVisitor::BRA(u64) {
+    ThrowNotImplemented(Opcode::BRA);
+}
+
+void TranslatorVisitor::BRK(u64) {
+    ThrowNotImplemented(Opcode::BRK);
+}
+
+void TranslatorVisitor::BRX(u64) {
+    ThrowNotImplemented(Opcode::BRX);
+}
+
+void TranslatorVisitor::CAL(u64) {
+    ThrowNotImplemented(Opcode::CAL);
+}
+
+void TranslatorVisitor::CCTL(u64) {
+    ThrowNotImplemented(Opcode::CCTL);
+}
+
+void TranslatorVisitor::CCTLL(u64) {
+    ThrowNotImplemented(Opcode::CCTLL);
+}
+
+void TranslatorVisitor::CONT(u64) {
+    ThrowNotImplemented(Opcode::CONT);
+}
+
+void TranslatorVisitor::CS2R(u64) {
+    ThrowNotImplemented(Opcode::CS2R);
+}
+
+void TranslatorVisitor::CSET(u64) {
+    ThrowNotImplemented(Opcode::CSET);
+}
+
+void TranslatorVisitor::CSETP(u64) {
+    ThrowNotImplemented(Opcode::CSETP);
+}
+
+void TranslatorVisitor::DADD_reg(u64) {
+    ThrowNotImplemented(Opcode::DADD_reg);
+}
+
+void TranslatorVisitor::DADD_cbuf(u64) {
+    ThrowNotImplemented(Opcode::DADD_cbuf);
+}
+
+void TranslatorVisitor::DADD_imm(u64) {
+    ThrowNotImplemented(Opcode::DADD_imm);
+}
+
+void TranslatorVisitor::DEPBAR(u64) {
+    ThrowNotImplemented(Opcode::DEPBAR);
+}
+
+void TranslatorVisitor::DFMA_reg(u64) {
+    ThrowNotImplemented(Opcode::DFMA_reg);
+}
+
+void TranslatorVisitor::DFMA_rc(u64) {
+    ThrowNotImplemented(Opcode::DFMA_rc);
+}
+
+void TranslatorVisitor::DFMA_cr(u64) {
+    ThrowNotImplemented(Opcode::DFMA_cr);
+}
+
+void TranslatorVisitor::DFMA_imm(u64) {
+    ThrowNotImplemented(Opcode::DFMA_imm);
+}
+
+void TranslatorVisitor::DMNMX_reg(u64) {
+    ThrowNotImplemented(Opcode::DMNMX_reg);
+}
+
+void TranslatorVisitor::DMNMX_cbuf(u64) {
+    ThrowNotImplemented(Opcode::DMNMX_cbuf);
+}
+
+void TranslatorVisitor::DMNMX_imm(u64) {
+    ThrowNotImplemented(Opcode::DMNMX_imm);
+}
+
+void TranslatorVisitor::DMUL_reg(u64) {
+    ThrowNotImplemented(Opcode::DMUL_reg);
+}
+
+void TranslatorVisitor::DMUL_cbuf(u64) {
+    ThrowNotImplemented(Opcode::DMUL_cbuf);
+}
+
+void TranslatorVisitor::DMUL_imm(u64) {
+    ThrowNotImplemented(Opcode::DMUL_imm);
+}
+
+void TranslatorVisitor::DSET_reg(u64) {
+    ThrowNotImplemented(Opcode::DSET_reg);
+}
+
+void TranslatorVisitor::DSET_cbuf(u64) {
+    ThrowNotImplemented(Opcode::DSET_cbuf);
+}
+
+void TranslatorVisitor::DSET_imm(u64) {
+    ThrowNotImplemented(Opcode::DSET_imm);
+}
+
+void TranslatorVisitor::DSETP_reg(u64) {
+    ThrowNotImplemented(Opcode::DSETP_reg);
+}
+
+void TranslatorVisitor::DSETP_cbuf(u64) {
+    ThrowNotImplemented(Opcode::DSETP_cbuf);
+}
+
+void TranslatorVisitor::DSETP_imm(u64) {
+    ThrowNotImplemented(Opcode::DSETP_imm);
+}
+
+void TranslatorVisitor::EXIT(u64) {
+    throw LogicError("Visting EXIT instruction");
+}
+
+void TranslatorVisitor::F2F_reg(u64) {
+    ThrowNotImplemented(Opcode::F2F_reg);
+}
+
+void TranslatorVisitor::F2F_cbuf(u64) {
+    ThrowNotImplemented(Opcode::F2F_cbuf);
+}
+
+void TranslatorVisitor::F2F_imm(u64) {
+    ThrowNotImplemented(Opcode::F2F_imm);
+}
+
+void TranslatorVisitor::FADD_reg(u64) {
+    ThrowNotImplemented(Opcode::FADD_reg);
+}
+
+void TranslatorVisitor::FADD_cbuf(u64) {
+    ThrowNotImplemented(Opcode::FADD_cbuf);
+}
+
+void TranslatorVisitor::FADD_imm(u64) {
+    ThrowNotImplemented(Opcode::FADD_imm);
+}
+
+void TranslatorVisitor::FADD32I(u64) {
+    ThrowNotImplemented(Opcode::FADD32I);
+}
+
+void TranslatorVisitor::FCHK_reg(u64) {
+    ThrowNotImplemented(Opcode::FCHK_reg);
+}
+
+void TranslatorVisitor::FCHK_cbuf(u64) {
+    ThrowNotImplemented(Opcode::FCHK_cbuf);
+}
+
+void TranslatorVisitor::FCHK_imm(u64) {
+    ThrowNotImplemented(Opcode::FCHK_imm);
+}
+
+void TranslatorVisitor::FCMP_reg(u64) {
+    ThrowNotImplemented(Opcode::FCMP_reg);
+}
+
+void TranslatorVisitor::FCMP_rc(u64) {
+    ThrowNotImplemented(Opcode::FCMP_rc);
+}
+
+void TranslatorVisitor::FCMP_cr(u64) {
+    ThrowNotImplemented(Opcode::FCMP_cr);
+}
+
+void TranslatorVisitor::FCMP_imm(u64) {
+    ThrowNotImplemented(Opcode::FCMP_imm);
+}
+
+void TranslatorVisitor::FFMA_reg(u64) {
+    ThrowNotImplemented(Opcode::FFMA_reg);
+}
+
+void TranslatorVisitor::FFMA_rc(u64) {
+    ThrowNotImplemented(Opcode::FFMA_rc);
+}
+
+void TranslatorVisitor::FFMA_cr(u64) {
+    ThrowNotImplemented(Opcode::FFMA_cr);
+}
+
+void TranslatorVisitor::FFMA_imm(u64) {
+    ThrowNotImplemented(Opcode::FFMA_imm);
+}
+
+void TranslatorVisitor::FFMA32I(u64) {
+    ThrowNotImplemented(Opcode::FFMA32I);
+}
+
+void TranslatorVisitor::FLO_reg(u64) {
+    ThrowNotImplemented(Opcode::FLO_reg);
+}
+
+void TranslatorVisitor::FLO_cbuf(u64) {
+    ThrowNotImplemented(Opcode::FLO_cbuf);
+}
+
+void TranslatorVisitor::FLO_imm(u64) {
+    ThrowNotImplemented(Opcode::FLO_imm);
+}
+
+void TranslatorVisitor::FMNMX_reg(u64) {
+    ThrowNotImplemented(Opcode::FMNMX_reg);
+}
+
+void TranslatorVisitor::FMNMX_cbuf(u64) {
+    ThrowNotImplemented(Opcode::FMNMX_cbuf);
+}
+
+void TranslatorVisitor::FMNMX_imm(u64) {
+    ThrowNotImplemented(Opcode::FMNMX_imm);
+}
+
+void TranslatorVisitor::FMUL_reg(u64) {
+    ThrowNotImplemented(Opcode::FMUL_reg);
+}
+
+void TranslatorVisitor::FMUL_cbuf(u64) {
+    ThrowNotImplemented(Opcode::FMUL_cbuf);
+}
+
+void TranslatorVisitor::FMUL_imm(u64) {
+    ThrowNotImplemented(Opcode::FMUL_imm);
+}
+
+void TranslatorVisitor::FMUL32I(u64) {
+    ThrowNotImplemented(Opcode::FMUL32I);
+}
+
+void TranslatorVisitor::FSET_reg(u64) {
+    ThrowNotImplemented(Opcode::FSET_reg);
+}
+
+void TranslatorVisitor::FSET_cbuf(u64) {
+    ThrowNotImplemented(Opcode::FSET_cbuf);
+}
+
+void TranslatorVisitor::FSET_imm(u64) {
+    ThrowNotImplemented(Opcode::FSET_imm);
+}
+
+void TranslatorVisitor::FSETP_reg(u64) {
+    ThrowNotImplemented(Opcode::FSETP_reg);
+}
+
+void TranslatorVisitor::FSETP_cbuf(u64) {
+    ThrowNotImplemented(Opcode::FSETP_cbuf);
+}
+
+void TranslatorVisitor::FSETP_imm(u64) {
+    ThrowNotImplemented(Opcode::FSETP_imm);
+}
+
+void TranslatorVisitor::FSWZADD(u64) {
+    ThrowNotImplemented(Opcode::FSWZADD);
+}
+
+void TranslatorVisitor::GETCRSPTR(u64) {
+    ThrowNotImplemented(Opcode::GETCRSPTR);
+}
+
+void TranslatorVisitor::GETLMEMBASE(u64) {
+    ThrowNotImplemented(Opcode::GETLMEMBASE);
+}
+
+void TranslatorVisitor::HADD2_reg(u64) {
+    ThrowNotImplemented(Opcode::HADD2_reg);
+}
+
+void TranslatorVisitor::HADD2_cbuf(u64) {
+    ThrowNotImplemented(Opcode::HADD2_cbuf);
+}
+
+void TranslatorVisitor::HADD2_imm(u64) {
+    ThrowNotImplemented(Opcode::HADD2_imm);
+}
+
+void TranslatorVisitor::HADD2_32I(u64) {
+    ThrowNotImplemented(Opcode::HADD2_32I);
+}
+
+void TranslatorVisitor::HFMA2_reg(u64) {
+    ThrowNotImplemented(Opcode::HFMA2_reg);
+}
+
+void TranslatorVisitor::HFMA2_rc(u64) {
+    ThrowNotImplemented(Opcode::HFMA2_rc);
+}
+
+void TranslatorVisitor::HFMA2_cr(u64) {
+    ThrowNotImplemented(Opcode::HFMA2_cr);
+}
+
+void TranslatorVisitor::HFMA2_imm(u64) {
+    ThrowNotImplemented(Opcode::HFMA2_imm);
+}
+
+void TranslatorVisitor::HFMA2_32I(u64) {
+    ThrowNotImplemented(Opcode::HFMA2_32I);
+}
+
+void TranslatorVisitor::HMUL2_reg(u64) {
+    ThrowNotImplemented(Opcode::HMUL2_reg);
+}
+
+void TranslatorVisitor::HMUL2_cbuf(u64) {
+    ThrowNotImplemented(Opcode::HMUL2_cbuf);
+}
+
+void TranslatorVisitor::HMUL2_imm(u64) {
+    ThrowNotImplemented(Opcode::HMUL2_imm);
+}
+
+void TranslatorVisitor::HMUL2_32I(u64) {
+    ThrowNotImplemented(Opcode::HMUL2_32I);
+}
+
+void TranslatorVisitor::HSET2_reg(u64) {
+    ThrowNotImplemented(Opcode::HSET2_reg);
+}
+
+void TranslatorVisitor::HSET2_cbuf(u64) {
+    ThrowNotImplemented(Opcode::HSET2_cbuf);
+}
+
+void TranslatorVisitor::HSET2_imm(u64) {
+    ThrowNotImplemented(Opcode::HSET2_imm);
+}
+
+void TranslatorVisitor::HSETP2_reg(u64) {
+    ThrowNotImplemented(Opcode::HSETP2_reg);
+}
+
+void TranslatorVisitor::HSETP2_cbuf(u64) {
+    ThrowNotImplemented(Opcode::HSETP2_cbuf);
+}
+
+void TranslatorVisitor::HSETP2_imm(u64) {
+    ThrowNotImplemented(Opcode::HSETP2_imm);
+}
+
+void TranslatorVisitor::I2F_reg(u64) {
+    ThrowNotImplemented(Opcode::I2F_reg);
+}
+
+void TranslatorVisitor::I2F_cbuf(u64) {
+    ThrowNotImplemented(Opcode::I2F_cbuf);
+}
+
+void TranslatorVisitor::I2F_imm(u64) {
+    ThrowNotImplemented(Opcode::I2F_imm);
+}
+
+void TranslatorVisitor::I2I_reg(u64) {
+    ThrowNotImplemented(Opcode::I2I_reg);
+}
+
+void TranslatorVisitor::I2I_cbuf(u64) {
+    ThrowNotImplemented(Opcode::I2I_cbuf);
+}
+
+void TranslatorVisitor::I2I_imm(u64) {
+    ThrowNotImplemented(Opcode::I2I_imm);
+}
+
+void TranslatorVisitor::IADD_reg(u64) {
+    ThrowNotImplemented(Opcode::IADD_reg);
+}
+
+void TranslatorVisitor::IADD_cbuf(u64) {
+    ThrowNotImplemented(Opcode::IADD_cbuf);
+}
+
+void TranslatorVisitor::IADD_imm(u64) {
+    ThrowNotImplemented(Opcode::IADD_imm);
+}
+
+void TranslatorVisitor::IADD3_reg(u64) {
+    ThrowNotImplemented(Opcode::IADD3_reg);
+}
+
+void TranslatorVisitor::IADD3_cbuf(u64) {
+    ThrowNotImplemented(Opcode::IADD3_cbuf);
+}
+
+void TranslatorVisitor::IADD3_imm(u64) {
+    ThrowNotImplemented(Opcode::IADD3_imm);
+}
+
+void TranslatorVisitor::IADD32I(u64) {
+    ThrowNotImplemented(Opcode::IADD32I);
+}
+
+void TranslatorVisitor::ICMP_reg(u64) {
+    ThrowNotImplemented(Opcode::ICMP_reg);
+}
+
+void TranslatorVisitor::ICMP_rc(u64) {
+    ThrowNotImplemented(Opcode::ICMP_rc);
+}
+
+void TranslatorVisitor::ICMP_cr(u64) {
+    ThrowNotImplemented(Opcode::ICMP_cr);
+}
+
+void TranslatorVisitor::ICMP_imm(u64) {
+    ThrowNotImplemented(Opcode::ICMP_imm);
+}
+
+void TranslatorVisitor::IDE(u64) {
+    ThrowNotImplemented(Opcode::IDE);
+}
+
+void TranslatorVisitor::IDP_reg(u64) {
+    ThrowNotImplemented(Opcode::IDP_reg);
+}
+
+void TranslatorVisitor::IDP_imm(u64) {
+    ThrowNotImplemented(Opcode::IDP_imm);
+}
+
+void TranslatorVisitor::IMAD_reg(u64) {
+    ThrowNotImplemented(Opcode::IMAD_reg);
+}
+
+void TranslatorVisitor::IMAD_rc(u64) {
+    ThrowNotImplemented(Opcode::IMAD_rc);
+}
+
+void TranslatorVisitor::IMAD_cr(u64) {
+    ThrowNotImplemented(Opcode::IMAD_cr);
+}
+
+void TranslatorVisitor::IMAD_imm(u64) {
+    ThrowNotImplemented(Opcode::IMAD_imm);
+}
+
+void TranslatorVisitor::IMAD32I(u64) {
+    ThrowNotImplemented(Opcode::IMAD32I);
+}
+
+void TranslatorVisitor::IMADSP_reg(u64) {
+    ThrowNotImplemented(Opcode::IMADSP_reg);
+}
+
+void TranslatorVisitor::IMADSP_rc(u64) {
+    ThrowNotImplemented(Opcode::IMADSP_rc);
+}
+
+void TranslatorVisitor::IMADSP_cr(u64) {
+    ThrowNotImplemented(Opcode::IMADSP_cr);
+}
+
+void TranslatorVisitor::IMADSP_imm(u64) {
+    ThrowNotImplemented(Opcode::IMADSP_imm);
+}
+
+void TranslatorVisitor::IMNMX_reg(u64) {
+    ThrowNotImplemented(Opcode::IMNMX_reg);
+}
+
+void TranslatorVisitor::IMNMX_cbuf(u64) {
+    ThrowNotImplemented(Opcode::IMNMX_cbuf);
+}
+
+void TranslatorVisitor::IMNMX_imm(u64) {
+    ThrowNotImplemented(Opcode::IMNMX_imm);
+}
+
+void TranslatorVisitor::IMUL_reg(u64) {
+    ThrowNotImplemented(Opcode::IMUL_reg);
+}
+
+void TranslatorVisitor::IMUL_cbuf(u64) {
+    ThrowNotImplemented(Opcode::IMUL_cbuf);
+}
+
+void TranslatorVisitor::IMUL_imm(u64) {
+    ThrowNotImplemented(Opcode::IMUL_imm);
+}
+
+void TranslatorVisitor::IMUL32I(u64) {
+    ThrowNotImplemented(Opcode::IMUL32I);
+}
+
+void TranslatorVisitor::ISBERD(u64) {
+    ThrowNotImplemented(Opcode::ISBERD);
+}
+
+void TranslatorVisitor::ISCADD_reg(u64) {
+    ThrowNotImplemented(Opcode::ISCADD_reg);
+}
+
+void TranslatorVisitor::ISCADD_cbuf(u64) {
+    ThrowNotImplemented(Opcode::ISCADD_cbuf);
+}
+
+void TranslatorVisitor::ISCADD_imm(u64) {
+    ThrowNotImplemented(Opcode::ISCADD_imm);
+}
+
+void TranslatorVisitor::ISCADD32I(u64) {
+    ThrowNotImplemented(Opcode::ISCADD32I);
+}
+
+void TranslatorVisitor::ISET_reg(u64) {
+    ThrowNotImplemented(Opcode::ISET_reg);
+}
+
+void TranslatorVisitor::ISET_cbuf(u64) {
+    ThrowNotImplemented(Opcode::ISET_cbuf);
+}
+
+void TranslatorVisitor::ISET_imm(u64) {
+    ThrowNotImplemented(Opcode::ISET_imm);
+}
+
+void TranslatorVisitor::ISETP_reg(u64) {
+    ThrowNotImplemented(Opcode::ISETP_reg);
+}
+
+void TranslatorVisitor::ISETP_cbuf(u64) {
+    ThrowNotImplemented(Opcode::ISETP_cbuf);
+}
+
+void TranslatorVisitor::ISETP_imm(u64) {
+    ThrowNotImplemented(Opcode::ISETP_imm);
+}
+
+void TranslatorVisitor::JCAL(u64) {
+    ThrowNotImplemented(Opcode::JCAL);
+}
+
+void TranslatorVisitor::JMP(u64) {
+    ThrowNotImplemented(Opcode::JMP);
+}
+
+void TranslatorVisitor::JMX(u64) {
+    ThrowNotImplemented(Opcode::JMX);
+}
+
+void TranslatorVisitor::KIL(u64) {
+    ThrowNotImplemented(Opcode::KIL);
+}
+
+void TranslatorVisitor::LD(u64) {
+    ThrowNotImplemented(Opcode::LD);
+}
+
+void TranslatorVisitor::LDC(u64) {
+    ThrowNotImplemented(Opcode::LDC);
+}
+
+void TranslatorVisitor::LDG(u64) {
+    ThrowNotImplemented(Opcode::LDG);
+}
+
+void TranslatorVisitor::LDL(u64) {
+    ThrowNotImplemented(Opcode::LDL);
+}
+
+void TranslatorVisitor::LDS(u64) {
+    ThrowNotImplemented(Opcode::LDS);
+}
+
+void TranslatorVisitor::LEA_hi_reg(u64) {
+    ThrowNotImplemented(Opcode::LEA_hi_reg);
+}
+
+void TranslatorVisitor::LEA_hi_cbuf(u64) {
+    ThrowNotImplemented(Opcode::LEA_hi_cbuf);
+}
+
+void TranslatorVisitor::LEA_lo_reg(u64) {
+    ThrowNotImplemented(Opcode::LEA_lo_reg);
+}
+
+void TranslatorVisitor::LEA_lo_cbuf(u64) {
+    ThrowNotImplemented(Opcode::LEA_lo_cbuf);
+}
+
+void TranslatorVisitor::LEA_lo_imm(u64) {
+    ThrowNotImplemented(Opcode::LEA_lo_imm);
+}
+
+void TranslatorVisitor::LEPC(u64) {
+    ThrowNotImplemented(Opcode::LEPC);
+}
+
+void TranslatorVisitor::LONGJMP(u64) {
+    ThrowNotImplemented(Opcode::LONGJMP);
+}
+
+void TranslatorVisitor::LOP_reg(u64) {
+    ThrowNotImplemented(Opcode::LOP_reg);
+}
+
+void TranslatorVisitor::LOP_cbuf(u64) {
+    ThrowNotImplemented(Opcode::LOP_cbuf);
+}
+
+void TranslatorVisitor::LOP_imm(u64) {
+    ThrowNotImplemented(Opcode::LOP_imm);
+}
+
+void TranslatorVisitor::LOP3_reg(u64) {
+    ThrowNotImplemented(Opcode::LOP3_reg);
+}
+
+void TranslatorVisitor::LOP3_cbuf(u64) {
+    ThrowNotImplemented(Opcode::LOP3_cbuf);
+}
+
+void TranslatorVisitor::LOP3_imm(u64) {
+    ThrowNotImplemented(Opcode::LOP3_imm);
+}
+
+void TranslatorVisitor::LOP32I(u64) {
+    ThrowNotImplemented(Opcode::LOP32I);
+}
+
+void TranslatorVisitor::MEMBAR(u64) {
+    ThrowNotImplemented(Opcode::MEMBAR);
+}
+
+void TranslatorVisitor::MOV32I(u64) {
+    ThrowNotImplemented(Opcode::MOV32I);
+}
+
+void TranslatorVisitor::NOP(u64) {
+    ThrowNotImplemented(Opcode::NOP);
+}
+
+void TranslatorVisitor::OUT_reg(u64) {
+    ThrowNotImplemented(Opcode::OUT_reg);
+}
+
+void TranslatorVisitor::OUT_cbuf(u64) {
+    ThrowNotImplemented(Opcode::OUT_cbuf);
+}
+
+void TranslatorVisitor::OUT_imm(u64) {
+    ThrowNotImplemented(Opcode::OUT_imm);
+}
+
+void TranslatorVisitor::P2R_reg(u64) {
+    ThrowNotImplemented(Opcode::P2R_reg);
+}
+
+void TranslatorVisitor::P2R_cbuf(u64) {
+    ThrowNotImplemented(Opcode::P2R_cbuf);
+}
+
+void TranslatorVisitor::P2R_imm(u64) {
+    ThrowNotImplemented(Opcode::P2R_imm);
+}
+
+void TranslatorVisitor::PBK(u64) {
+    // PBK is a no-op
+}
+
+void TranslatorVisitor::PCNT(u64) {
+    ThrowNotImplemented(Opcode::PCNT);
+}
+
+void TranslatorVisitor::PEXIT(u64) {
+    ThrowNotImplemented(Opcode::PEXIT);
+}
+
+void TranslatorVisitor::PIXLD(u64) {
+    ThrowNotImplemented(Opcode::PIXLD);
+}
+
+void TranslatorVisitor::PLONGJMP(u64) {
+    ThrowNotImplemented(Opcode::PLONGJMP);
+}
+
+void TranslatorVisitor::POPC_reg(u64) {
+    ThrowNotImplemented(Opcode::POPC_reg);
+}
+
+void TranslatorVisitor::POPC_cbuf(u64) {
+    ThrowNotImplemented(Opcode::POPC_cbuf);
+}
+
+void TranslatorVisitor::POPC_imm(u64) {
+    ThrowNotImplemented(Opcode::POPC_imm);
+}
+
+void TranslatorVisitor::PRET(u64) {
+    ThrowNotImplemented(Opcode::PRET);
+}
+
+void TranslatorVisitor::PRMT_reg(u64) {
+    ThrowNotImplemented(Opcode::PRMT_reg);
+}
+
+void TranslatorVisitor::PRMT_rc(u64) {
+    ThrowNotImplemented(Opcode::PRMT_rc);
+}
+
+void TranslatorVisitor::PRMT_cr(u64) {
+    ThrowNotImplemented(Opcode::PRMT_cr);
+}
+
+void TranslatorVisitor::PRMT_imm(u64) {
+    ThrowNotImplemented(Opcode::PRMT_imm);
+}
+
+void TranslatorVisitor::PSET(u64) {
+    ThrowNotImplemented(Opcode::PSET);
+}
+
+void TranslatorVisitor::PSETP(u64) {
+    ThrowNotImplemented(Opcode::PSETP);
+}
+
+void TranslatorVisitor::R2B(u64) {
+    ThrowNotImplemented(Opcode::R2B);
+}
+
+void TranslatorVisitor::R2P_reg(u64) {
+    ThrowNotImplemented(Opcode::R2P_reg);
+}
+
+void TranslatorVisitor::R2P_cbuf(u64) {
+    ThrowNotImplemented(Opcode::R2P_cbuf);
+}
+
+void TranslatorVisitor::R2P_imm(u64) {
+    ThrowNotImplemented(Opcode::R2P_imm);
+}
+
+void TranslatorVisitor::RAM(u64) {
+    ThrowNotImplemented(Opcode::RAM);
+}
+
+void TranslatorVisitor::RED(u64) {
+    ThrowNotImplemented(Opcode::RED);
+}
+
+void TranslatorVisitor::RET(u64) {
+    ThrowNotImplemented(Opcode::RET);
+}
+
+void TranslatorVisitor::RRO_reg(u64) {
+    ThrowNotImplemented(Opcode::RRO_reg);
+}
+
+void TranslatorVisitor::RRO_cbuf(u64) {
+    ThrowNotImplemented(Opcode::RRO_cbuf);
+}
+
+void TranslatorVisitor::RRO_imm(u64) {
+    ThrowNotImplemented(Opcode::RRO_imm);
+}
+
+void TranslatorVisitor::RTT(u64) {
+    ThrowNotImplemented(Opcode::RTT);
+}
+
+void TranslatorVisitor::S2R(u64) {
+    ThrowNotImplemented(Opcode::S2R);
+}
+
+void TranslatorVisitor::SAM(u64) {
+    ThrowNotImplemented(Opcode::SAM);
+}
+
+void TranslatorVisitor::SEL_reg(u64) {
+    ThrowNotImplemented(Opcode::SEL_reg);
+}
+
+void TranslatorVisitor::SEL_cbuf(u64) {
+    ThrowNotImplemented(Opcode::SEL_cbuf);
+}
+
+void TranslatorVisitor::SEL_imm(u64) {
+    ThrowNotImplemented(Opcode::SEL_imm);
+}
+
+void TranslatorVisitor::SETCRSPTR(u64) {
+    ThrowNotImplemented(Opcode::SETCRSPTR);
+}
+
+void TranslatorVisitor::SETLMEMBASE(u64) {
+    ThrowNotImplemented(Opcode::SETLMEMBASE);
+}
+
+void TranslatorVisitor::SHF_l_reg(u64) {
+    ThrowNotImplemented(Opcode::SHF_l_reg);
+}
+
+void TranslatorVisitor::SHF_l_imm(u64) {
+    ThrowNotImplemented(Opcode::SHF_l_imm);
+}
+
+void TranslatorVisitor::SHF_r_reg(u64) {
+    ThrowNotImplemented(Opcode::SHF_r_reg);
+}
+
+void TranslatorVisitor::SHF_r_imm(u64) {
+    ThrowNotImplemented(Opcode::SHF_r_imm);
+}
+
+void TranslatorVisitor::SHFL(u64) {
+    ThrowNotImplemented(Opcode::SHFL);
+}
+
+void TranslatorVisitor::SHL_reg(u64) {
+    ThrowNotImplemented(Opcode::SHL_reg);
+}
+
+void TranslatorVisitor::SHL_cbuf(u64) {
+    ThrowNotImplemented(Opcode::SHL_cbuf);
+}
+
+void TranslatorVisitor::SHL_imm(u64) {
+    ThrowNotImplemented(Opcode::SHL_imm);
+}
+
+void TranslatorVisitor::SHR_reg(u64) {
+    ThrowNotImplemented(Opcode::SHR_reg);
+}
+
+void TranslatorVisitor::SHR_cbuf(u64) {
+    ThrowNotImplemented(Opcode::SHR_cbuf);
+}
+
+void TranslatorVisitor::SHR_imm(u64) {
+    ThrowNotImplemented(Opcode::SHR_imm);
+}
+
+void TranslatorVisitor::SSY(u64) {
+    ThrowNotImplemented(Opcode::SSY);
+}
+
+void TranslatorVisitor::ST(u64) {
+    ThrowNotImplemented(Opcode::ST);
+}
+
+void TranslatorVisitor::STL(u64) {
+    ThrowNotImplemented(Opcode::STL);
+}
+
+void TranslatorVisitor::STP(u64) {
+    ThrowNotImplemented(Opcode::STP);
+}
+
+void TranslatorVisitor::STS(u64) {
+    ThrowNotImplemented(Opcode::STS);
+}
+
+void TranslatorVisitor::SUATOM_cas(u64) {
+    ThrowNotImplemented(Opcode::SUATOM_cas);
+}
+
+void TranslatorVisitor::SULD(u64) {
+    ThrowNotImplemented(Opcode::SULD);
+}
+
+void TranslatorVisitor::SURED(u64) {
+    ThrowNotImplemented(Opcode::SURED);
+}
+
+void TranslatorVisitor::SUST(u64) {
+    ThrowNotImplemented(Opcode::SUST);
+}
+
+void TranslatorVisitor::SYNC(u64) {
+    ThrowNotImplemented(Opcode::SYNC);
+}
+
+void TranslatorVisitor::TEX(u64) {
+    ThrowNotImplemented(Opcode::TEX);
+}
+
+void TranslatorVisitor::TEX_b(u64) {
+    ThrowNotImplemented(Opcode::TEX_b);
+}
+
+void TranslatorVisitor::TEXS(u64) {
+    ThrowNotImplemented(Opcode::TEXS);
+}
+
+void TranslatorVisitor::TLD(u64) {
+    ThrowNotImplemented(Opcode::TLD);
+}
+
+void TranslatorVisitor::TLD_b(u64) {
+    ThrowNotImplemented(Opcode::TLD_b);
+}
+
+void TranslatorVisitor::TLD4(u64) {
+    ThrowNotImplemented(Opcode::TLD4);
+}
+
+void TranslatorVisitor::TLD4_b(u64) {
+    ThrowNotImplemented(Opcode::TLD4_b);
+}
+
+void TranslatorVisitor::TLD4S(u64) {
+    ThrowNotImplemented(Opcode::TLD4S);
+}
+
+void TranslatorVisitor::TLDS(u64) {
+    ThrowNotImplemented(Opcode::TLDS);
+}
+
+void TranslatorVisitor::TMML(u64) {
+    ThrowNotImplemented(Opcode::TMML);
+}
+
+void TranslatorVisitor::TMML_b(u64) {
+    ThrowNotImplemented(Opcode::TMML_b);
+}
+
+void TranslatorVisitor::TXA(u64) {
+    ThrowNotImplemented(Opcode::TXA);
+}
+
+void TranslatorVisitor::TXD(u64) {
+    ThrowNotImplemented(Opcode::TXD);
+}
+
+void TranslatorVisitor::TXD_b(u64) {
+    ThrowNotImplemented(Opcode::TXD_b);
+}
+
+void TranslatorVisitor::TXQ(u64) {
+    ThrowNotImplemented(Opcode::TXQ);
+}
+
+void TranslatorVisitor::TXQ_b(u64) {
+    ThrowNotImplemented(Opcode::TXQ_b);
+}
+
+void TranslatorVisitor::VABSDIFF(u64) {
+    ThrowNotImplemented(Opcode::VABSDIFF);
+}
+
+void TranslatorVisitor::VABSDIFF4(u64) {
+    ThrowNotImplemented(Opcode::VABSDIFF4);
+}
+
+void TranslatorVisitor::VADD(u64) {
+    ThrowNotImplemented(Opcode::VADD);
+}
+
+void TranslatorVisitor::VMAD(u64) {
+    ThrowNotImplemented(Opcode::VMAD);
+}
+
+void TranslatorVisitor::VMNMX(u64) {
+    ThrowNotImplemented(Opcode::VMNMX);
+}
+
+void TranslatorVisitor::VOTE(u64) {
+    ThrowNotImplemented(Opcode::VOTE);
+}
+
+void TranslatorVisitor::VOTE_vtg(u64) {
+    ThrowNotImplemented(Opcode::VOTE_vtg);
+}
+
+void TranslatorVisitor::VSET(u64) {
+    ThrowNotImplemented(Opcode::VSET);
+}
+
+void TranslatorVisitor::VSETP(u64) {
+    ThrowNotImplemented(Opcode::VSETP);
+}
+
+void TranslatorVisitor::VSHL(u64) {
+    ThrowNotImplemented(Opcode::VSHL);
+}
+
+void TranslatorVisitor::VSHR(u64) {
+    ThrowNotImplemented(Opcode::VSHR);
+}
+
+void TranslatorVisitor::XMAD_reg(u64) {
+    ThrowNotImplemented(Opcode::XMAD_reg);
+}
+
+void TranslatorVisitor::XMAD_rc(u64) {
+    ThrowNotImplemented(Opcode::XMAD_rc);
+}
+
+void TranslatorVisitor::XMAD_cr(u64) {
+    ThrowNotImplemented(Opcode::XMAD_cr);
+}
+
+void TranslatorVisitor::XMAD_imm(u64) {
+    ThrowNotImplemented(Opcode::XMAD_imm);
+}
+
+} // namespace Shader::Maxwell
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/register_move.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/register_move.cpp
new file mode 100644
index 0000000000..7fa35ba3a2
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/register_move.cpp
@@ -0,0 +1,45 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "shader_recompiler/exception.h"
+#include "shader_recompiler/frontend/maxwell/opcode.h"
+#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
+
+namespace Shader::Maxwell {
+namespace {
+union MOV {
+    u64 raw;
+    BitField<0, 8, IR::Reg> dest_reg;
+    BitField<20, 8, IR::Reg> src_reg;
+    BitField<39, 4, u64> mask;
+};
+
+void CheckMask(MOV mov) {
+    if (mov.mask != 0xf) {
+        throw NotImplementedException("Non-full move mask");
+    }
+}
+} // Anonymous namespace
+
+void TranslatorVisitor::MOV_reg(u64 insn) {
+    const MOV mov{insn};
+    CheckMask(mov);
+    X(mov.dest_reg, X(mov.src_reg));
+}
+
+void TranslatorVisitor::MOV_cbuf(u64 insn) {
+    const MOV mov{insn};
+    CheckMask(mov);
+    X(mov.dest_reg, GetCbuf(insn));
+}
+
+void TranslatorVisitor::MOV_imm(u64 insn) {
+    const MOV mov{insn};
+    CheckMask(mov);
+    X(mov.dest_reg, GetImm(insn));
+}
+
+} // namespace Shader::Maxwell
diff --git a/src/shader_recompiler/frontend/maxwell/translate/translate.cpp b/src/shader_recompiler/frontend/maxwell/translate/translate.cpp
new file mode 100644
index 0000000000..66a306745e
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/translate/translate.cpp
@@ -0,0 +1,50 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "shader_recompiler/environment.h"
+#include "shader_recompiler/frontend/ir/basic_block.h"
+#include "shader_recompiler/frontend/maxwell/decode.h"
+#include "shader_recompiler/frontend/maxwell/location.h"
+#include "shader_recompiler/frontend/maxwell/translate/impl/impl.h"
+#include "shader_recompiler/frontend/maxwell/translate/translate.h"
+
+namespace Shader::Maxwell {
+
+template <auto visitor_method>
+static void Invoke(TranslatorVisitor& visitor, Location pc, u64 insn) {
+    using MethodType = decltype(visitor_method);
+    if constexpr (std::is_invocable_r_v<void, MethodType, TranslatorVisitor&, Location, u64>) {
+        (visitor.*visitor_method)(pc, insn);
+    } else if constexpr (std::is_invocable_r_v<void, MethodType, TranslatorVisitor&, u64>) {
+        (visitor.*visitor_method)(insn);
+    } else {
+        (visitor.*visitor_method)();
+    }
+}
+
+IR::Block Translate(Environment& env, const Flow::Block& flow_block) {
+    IR::Block block{flow_block.begin.Offset(), flow_block.end.Offset()};
+    TranslatorVisitor visitor{env, block};
+
+    const Location pc_end{flow_block.end};
+    Location pc{flow_block.begin};
+    while (pc != pc_end) {
+        const u64 insn{env.ReadInstruction(pc.Offset())};
+        const Opcode opcode{Decode(insn)};
+        switch (opcode) {
+#define INST(name, cute, mask)                                                                     \
+    case Opcode::name:                                                                             \
+        Invoke<&TranslatorVisitor::name>(visitor, pc, insn);                                       \
+        break;
+#include "shader_recompiler/frontend/maxwell/maxwell.inc"
+#undef OPCODE
+        default:
+            throw LogicError("Invalid opcode {}", opcode);
+        }
+        ++pc;
+    }
+    return block;
+}
+
+} // namespace Shader::Maxwell
diff --git a/src/shader_recompiler/frontend/maxwell/translate/translate.h b/src/shader_recompiler/frontend/maxwell/translate/translate.h
new file mode 100644
index 0000000000..788742dea1
--- /dev/null
+++ b/src/shader_recompiler/frontend/maxwell/translate/translate.h
@@ -0,0 +1,16 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "shader_recompiler/environment.h"
+#include "shader_recompiler/frontend/ir/basic_block.h"
+#include "shader_recompiler/frontend/maxwell/location.h"
+#include "shader_recompiler/frontend/maxwell/control_flow.h"
+
+namespace Shader::Maxwell {
+
+[[nodiscard]] IR::Block Translate(Environment& env, const Flow::Block& flow_block);
+
+} // namespace Shader::Maxwell
-- 
cgit v1.2.3-70-g09d2