#include "common/emulatorConfig.h"
#include "common/logging/log.h"
#include "common/stringUtils.h"
#include "common/threads.h"
#include "graphics/guest_gpu/hardwareContext.h"
#include "graphics/guest_gpu/pm4.h"
#include "graphics/host_gpu/objects/textureCommon.h"
#include "graphics/host_gpu/renderer/shaderResourceBarrier.h"
#include "graphics/host_gpu/renderer/shaderSubgroup.h"
#include "graphics/shader/recompiler/ExecMask.h"
#include "graphics/shader/recompiler/ResourceTracking.h"
#include "graphics/shader/recompiler/ScalarProvenance.h"
#include "graphics/shader/recompiler/ShaderCFG.h"
#include "graphics/shader/recompiler/ShaderDecoder.h"
#include "graphics/shader/recompiler/ShaderIR.h"
#include "graphics/shader/recompiler/ShaderInfoCollection.h"
#include "graphics/shader/recompiler/ShaderRecompiler.h"
#include "graphics/shader/recompiler/SpirvEmitter.h"
#include "graphics/shader/recompiler/SrtPatcher.h"
#include "graphics/shader/recompiler/SrtWalker.h"
#include "graphics/shader/recompiler/spirvEmitter/spirvEmitterInternal.h"
#include "graphics/shader/shader.h"
#include "libs/agc.h"
#include "spirv-tools/libspirv.hpp"

#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iterator>
#include <span>
#include <sstream>
#include <string>
#include <vector>

namespace Libs::Graphics {
namespace {

void Check(bool value, const char* text) {
	if (!value) {
		std::fprintf(stderr, "ShaderCfgTests: failed: %s\n", text);
		std::abort();
	}
}

void CheckSpirvBinaryValidates(const std::vector<uint32_t>& binary) {
	spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2);
	std::string          messages;

	tools.SetMessageConsumer([&messages](spv_message_level_t, const char*,
	                                     const spv_position_t& position, const char* message) {
		char buffer[1024] = {};
		std::snprintf(buffer, sizeof(buffer), "%zu:%zu: %s\n", position.line, position.column,
		              message);
		messages += buffer;
	});

	if (!tools.Validate(binary)) {
		std::fprintf(stderr, "SPIR-V binary validation failed:\n%s\n", messages.c_str());
		std::abort();
	}
}

std::string DisassembleSpirvBinary(const std::vector<uint32_t>& binary) {
	spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_2);
	std::string          messages;
	std::string          source;

	tools.SetMessageConsumer([&messages](spv_message_level_t, const char*,
	                                     const spv_position_t& position, const char* message) {
		char buffer[1024] = {};
		std::snprintf(buffer, sizeof(buffer), "%zu:%zu: %s\n", position.line, position.column,
		              message);
		messages += buffer;
	});

	if (!tools.Disassemble(binary.data(), binary.size(), &source,
	                       SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES |
	                           SPV_BINARY_TO_TEXT_OPTION_INDENT)) {
		std::fprintf(stderr, "SPIR-V binary disassembly failed:\n%s\n", messages.c_str());
		std::abort();
	}
	return std::string(source.c_str());
}

uint32_t CountSourceOccurrences(const std::string& source, const char* needle) {
	uint32_t count = 0;
	uint32_t from  = 0;
	for (;;) {
		const auto found = Common::FindIndex(source, std::string(needle), from);
		if (found == Common::FIND_INVALID_INDEX) {
			return count;
		}
		count++;
		from = found + static_cast<uint32_t>(std::strlen(needle));
	}
}

bool SpirvSourceHasInstructionUsing(const std::string& source, const char* opcode,
                                    const char* name) {
	std::istringstream stream(source);
	std::string        line;
	while (std::getline(stream, line)) {
		if (Common::ContainsStr(line, opcode) && Common::ContainsStr(line, name)) {
			return true;
		}
	}
	return false;
}

bool SpirvContainsOpcode(const std::vector<uint32_t>& binary, uint32_t opcode) {
	for (uint32_t word: binary) {
		if ((word & 0xffffu) == opcode) {
			return true;
		}
	}
	return false;
}

uint32_t SpirvInstructionOpcodeCount(const std::vector<uint32_t>& binary, uint32_t opcode) {
	uint32_t count = 0;
	for (size_t i = 5; i < binary.size();) {
		const uint32_t word       = binary[i];
		const uint32_t op         = word & 0xffffu;
		const uint32_t word_count = word >> 16u;
		if (word_count == 0 || i + word_count > binary.size()) {
			return count;
		}
		if (op == opcode) {
			count++;
		}
		i += word_count;
	}
	return count;
}

bool SpirvContainsVectorShuffle(const std::vector<uint32_t>&   binary,
                                const std::array<uint32_t, 4>& selectors) {
	for (size_t i = 5; i < binary.size();) {
		const uint32_t word       = binary[i];
		const uint32_t op         = word & 0xffffu;
		const uint32_t word_count = word >> 16u;
		if (word_count == 0 || i + word_count > binary.size()) {
			return false;
		}
		if (op == 79u && word_count == 9u &&
		    std::equal(selectors.begin(), selectors.end(), binary.begin() + i + 5u)) {
			return true;
		}
		i += word_count;
	}
	return false;
}

bool SpirvContainsCapability(const std::vector<uint32_t>& binary, uint32_t capability) {
	for (size_t i = 5; i < binary.size();) {
		const uint32_t word       = binary[i];
		const uint32_t opcode     = word & 0xffffu;
		const uint32_t word_count = word >> 16u;
		if (word_count == 0 || i + word_count > binary.size()) {
			return false;
		}
		if (opcode == 17u && word_count >= 2u && binary[i + 1] == capability) {
			return true;
		}
		i += word_count;
	}
	return false;
}

bool SpirvContainsExecutionMode(const std::vector<uint32_t>& binary, uint32_t mode) {
	for (size_t i = 5; i < binary.size();) {
		const uint32_t word       = binary[i];
		const uint32_t opcode     = word & 0xffffu;
		const uint32_t word_count = word >> 16u;
		if (word_count == 0 || i + word_count > binary.size()) {
			return false;
		}
		if (opcode == 16u && word_count >= 3u && binary[i + 2] == mode) {
			return true;
		}
		i += word_count;
	}
	return false;
}

bool SpirvContainsExtInst(const std::vector<uint32_t>& binary, uint32_t ext_inst) {
	for (size_t i = 5; i < binary.size();) {
		const uint32_t word       = binary[i];
		const uint32_t opcode     = word & 0xffffu;
		const uint32_t word_count = word >> 16u;
		if (word_count == 0 || i + word_count > binary.size()) {
			return false;
		}
		if (opcode == 12u && word_count >= 5u && binary[i + 4] == ext_inst) {
			return true;
		}
		i += word_count;
	}
	return false;
}

uint32_t SpirvExtInstCount(const std::vector<uint32_t>& binary, uint32_t ext_inst) {
	uint32_t count = 0;
	for (size_t i = 5; i < binary.size();) {
		const uint32_t word       = binary[i];
		const uint32_t opcode     = word & 0xffffu;
		const uint32_t word_count = word >> 16u;
		if (word_count == 0 || i + word_count > binary.size()) {
			return count;
		}
		if (opcode == 12u && word_count >= 5u && binary[i + 4] == ext_inst) {
			count++;
		}
		i += word_count;
	}
	return count;
}

bool SpirvContainsTypeImage(const std::vector<uint32_t>& binary, uint32_t dim, uint32_t arrayed,
                            uint32_t sampled) {
	for (size_t i = 5; i < binary.size();) {
		const uint32_t word       = binary[i];
		const uint32_t opcode     = word & 0xffffu;
		const uint32_t word_count = word >> 16u;
		if (word_count == 0 || i + word_count > binary.size()) {
			return false;
		}
		if (opcode == 25u && word_count >= 9u && binary[i + 3] == dim && binary[i + 5] == arrayed &&
		    binary[i + 7] == sampled) {
			return true;
		}
		i += word_count;
	}
	return false;
}

uint32_t SpirvDecorationValueCount(const std::vector<uint32_t>& binary, uint32_t decoration,
                                   uint32_t value) {
	uint32_t count = 0;
	for (size_t i = 5; i < binary.size();) {
		const uint32_t word       = binary[i];
		const uint32_t opcode     = word & 0xffffu;
		const uint32_t word_count = word >> 16u;
		if (word_count == 0 || i + word_count > binary.size()) {
			return count;
		}
		if (opcode == 71u && word_count >= 4u && binary[i + 2] == decoration &&
		    binary[i + 3] == value) {
			count++;
		}
		i += word_count;
	}
	return count;
}

bool SpirvHasDecorationValue(const std::vector<uint32_t>& binary, uint32_t decoration,
                             uint32_t value) {
	return SpirvDecorationValueCount(binary, decoration, value) != 0;
}

std::vector<uint32_t> SpirvDecorationValueTargets(const std::vector<uint32_t>& binary,
                                                  uint32_t decoration, uint32_t value) {
	std::vector<uint32_t> targets;
	for (size_t i = 5; i < binary.size();) {
		const uint32_t word       = binary[i];
		const uint32_t opcode     = word & 0xffffu;
		const uint32_t word_count = word >> 16u;
		if (word_count == 0 || i + word_count > binary.size()) {
			return targets;
		}
		if (opcode == 71u && word_count >= 4u && binary[i + 2] == decoration &&
		    binary[i + 3] == value) {
			targets.push_back(binary[i + 1]);
		}
		i += word_count;
	}
	return targets;
}

bool SpirvTargetHasDecoration(const std::vector<uint32_t>& binary, uint32_t target,
                              uint32_t decoration) {
	for (size_t i = 5; i < binary.size();) {
		const uint32_t word       = binary[i];
		const uint32_t opcode     = word & 0xffffu;
		const uint32_t word_count = word >> 16u;
		if (word_count == 0 || i + word_count > binary.size()) {
			return false;
		}
		if (opcode == 71u && word_count >= 3u && binary[i + 1] == target &&
		    binary[i + 2] == decoration) {
			return true;
		}
		i += word_count;
	}
	return false;
}

bool SpirvHasDecorationValueWithDecoration(const std::vector<uint32_t>& binary,
                                           uint32_t value_decoration, uint32_t value,
                                           uint32_t decoration) {
	const auto targets = SpirvDecorationValueTargets(binary, value_decoration, value);
	return std::any_of(targets.begin(), targets.end(), [&](uint32_t target) {
		return SpirvTargetHasDecoration(binary, target, decoration);
	});
}

bool ProgramHasInput(const ShaderRecompiler::IR::Program& program,
                     ShaderRecompiler::IR::StageInputKind kind) {
	return std::any_of(program.info.inputs.begin(), program.info.inputs.end(),
	                   [kind](const auto& input) { return input.kind == kind; });
}

uint32_t ProgramInputCount(const ShaderRecompiler::IR::Program& program,
                           ShaderRecompiler::IR::StageInputKind kind) {
	return static_cast<uint32_t>(
	    std::count_if(program.info.inputs.begin(), program.info.inputs.end(),
	                  [kind](const auto& input) { return input.kind == kind; }));
}

ShaderComputeInputInfo RegressionComputeInputInfo() {
	ShaderComputeInputInfo input_info;
	input_info.threads_num[0]     = 1;
	input_info.threads_num[1]     = 1;
	input_info.threads_num[2]     = 1;
	input_info.workgroup_register = 40;
	return input_info;
}

ShaderPixelInputInfo RegressionPixelInputInfo() {
	return {};
}

void SetIdentityInterpolatorSettings(ShaderPixelInputInfo* input_info) {
	Check(input_info != nullptr, "invalid pixel input info");
	for (uint32_t i = 0; i < std::size(input_info->interpolator_settings); i++) {
		input_info->interpolator_settings[i] = i;
	}
}

void EnsureConfigInitialized() {
	static bool config_initialized = false;
	if (!config_initialized) {
		Common::ThreadsSubsystem::Instance()->Init(nullptr);
		Config::ConfigSubsystem::Instance()->Init(nullptr);
		Config::ConfigOptions options;
		options.printf_direction = Config::OutputDirection::Silent;
		Config::Load(options);
		Log::LogSubsystem::Instance()->Init(nullptr);
		ShaderInit();
		config_initialized = true;
	}
}

void TestResourceDescriptorClassification() {
	uint32_t raw_texture[8] = {};
	raw_texture[3]          = 9u << 28u;
	raw_texture[5]          = 2u << 27u;
	Check(ShaderClassifyResourceDescriptor(raw_texture) == ResourceDescriptorType::Texture,
	      "raw texture descriptor must not be classified by ResourceDescriptor byte 23 fields");

	uint32_t tagged_buffer[8] = {};
	tagged_buffer[5]          = 1u << 27u;
	Check(ShaderClassifyResourceDescriptor(tagged_buffer) == ResourceDescriptorType::Buffer,
	      "tagged ResourceDescriptor buffer was not recognized");

	uint32_t tagged_sampler[8] = {};
	tagged_sampler[5]          = 2u << 27u;
	Check(ShaderClassifyResourceDescriptor(tagged_sampler) == ResourceDescriptorType::Sampler,
	      "tagged ResourceDescriptor sampler was not recognized");

	uint32_t tagged_unused[8] = {};
	tagged_unused[5]          = 3u << 27u;
	Check(ShaderClassifyResourceDescriptor(tagged_unused) == ResourceDescriptorType::Unused,
	      "tagged ResourceDescriptor unused slot was not recognized");

	uint32_t raw_buffer[8] = {};
	Check(ShaderClassifyResourceDescriptor(raw_buffer) == ResourceDescriptorType::Buffer,
	      "raw buffer descriptor fallback was not preserved");
}

constexpr uint32_t EncodeSMovB32(uint32_t dst, uint32_t src) {
	return 0x80000000u | (0x7du << 23u) | ((dst & 0x7fu) << 16u) | (0x03u << 8u) | (src & 0xffu);
}

constexpr uint32_t EncodeSop1(uint32_t opcode, uint32_t dst, uint32_t src) {
	return 0x80000000u | (0x7du << 23u) | ((dst & 0x7fu) << 16u) | ((opcode & 0xffu) << 8u) |
	       (src & 0xffu);
}

constexpr uint32_t EncodeSop2(uint32_t opcode, uint32_t dst, uint32_t src0, uint32_t src1) {
	return 0x80000000u | ((opcode & 0x7fu) << 23u) | ((dst & 0x7fu) << 16u) |
	       ((src1 & 0xffu) << 8u) | (src0 & 0xffu);
}

constexpr uint32_t EncodeSopk(uint32_t opcode, uint32_t dst, int16_t imm) {
	return 0x80000000u | (((opcode + 0x60u) & 0x7fu) << 23u) | ((dst & 0x7fu) << 16u) |
	       static_cast<uint16_t>(imm);
}

constexpr uint32_t EncodeSopc(uint32_t opcode, uint32_t src0, uint32_t src1) {
	return 0x80000000u | (0x7eu << 23u) | ((opcode & 0x7fu) << 16u) | ((src1 & 0xffu) << 8u) |
	       (src0 & 0xffu);
}

constexpr uint32_t EncodeSopp(uint32_t opcode, uint32_t simm = 0) {
	return 0x80000000u | (0x7fu << 23u) | ((opcode & 0x7fu) << 16u) | (simm & 0xffffu);
}

void TestNativeShaderResourceDependencies() {
	const auto stages = ShaderPipelineStages(
	    vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment |
	    vk::ShaderStageFlagBits::eCompute);
	Check(stages == (vk::PipelineStageFlagBits::eVertexShader |
	                 vk::PipelineStageFlagBits::eFragmentShader |
	                 vk::PipelineStageFlagBits::eComputeShader),
	      "shader-stage dependency mapping was not exact");
	const auto shader_barrier = MakeShaderWriteDependency();
	Check(shader_barrier.srcAccessMask == vk::AccessFlagBits::eShaderWrite &&
	          (shader_barrier.dstAccessMask & vk::AccessFlagBits::eShaderRead) &&
	          (shader_barrier.dstAccessMask & vk::AccessFlagBits::eVertexAttributeRead),
	      "shader-write dependency does not expose draw/dispatch buffer writes");

	ShaderRecompiler::IR::Program program;
	program.info.buffers.resize(3);
	program.info.buffers[0].written = true;
	program.info.buffers[1].read    = true;
	program.info.buffers[2].written = true;
	ShaderRecompiler::IR::ResourceSnapshot resources;
	resources.buffers.resize(3);
	auto set_buffer = [&](uint32_t index, uint64_t address, uint16_t stride, uint32_t records) {
		ShaderBufferResource descriptor;
		descriptor.UpdateAddress48(address);
		descriptor.fields[1] |= static_cast<uint32_t>(stride) << 16u;
		descriptor.fields[2] = records;
		std::memcpy(resources.buffers[index].dwords.data(), descriptor.fields,
		            sizeof(descriptor.fields));
		resources.buffers[index].dword_count = 4;
	};
	set_buffer(0, 0x1000, 16, 3);
	set_buffer(1, 0x1800, 4, 7);
	set_buffer(2, 0x2000, 0, 64);
	const auto writes = CollectShaderBufferWrites(program, resources);
	Check(writes == std::vector<ShaderBufferWriteRange>({{0x1000, 48}, {0x2000, 64}}),
	      "graphics/compute write collector lost or invented a storage-buffer range");

	VulkanImage image {VulkanImageType::StorageTexture};
	image.image              = reinterpret_cast<vk::Image::CType>(uintptr_t {1});
	image.layout             = vk::ImageLayout::eGeneral;
	image.layers             = 3;
	const auto image_barrier = MakeStorageImageDependency(image, true, true);
	Check(image_barrier.oldLayout == vk::ImageLayout::eGeneral &&
	          image_barrier.newLayout == vk::ImageLayout::eGeneral &&
	          (image_barrier.srcAccessMask & vk::AccessFlagBits::eMemoryWrite) &&
	          (image_barrier.dstAccessMask & vk::AccessFlagBits::eShaderRead) &&
	          (image_barrier.dstAccessMask & vk::AccessFlagBits::eShaderWrite) &&
	          image_barrier.subresourceRange.levelCount == VK_REMAINING_MIP_LEVELS &&
	          image_barrier.subresourceRange.layerCount == image.layers,
	      "GENERAL storage-image dependency lacks a complete memory barrier");

	VulkanBuffer buffer;
	buffer.buffer          = reinterpret_cast<vk::Buffer::CType>(uintptr_t {1});
	const auto gds_barrier = MakeGdsDependency(buffer);
	Check((gds_barrier.srcAccessMask & vk::AccessFlagBits::eHostWrite) &&
	          (gds_barrier.srcAccessMask & vk::AccessFlagBits::eTransferWrite) &&
	          (gds_barrier.srcAccessMask & vk::AccessFlagBits::eShaderWrite) &&
	          gds_barrier.dstAccessMask ==
	              (vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite) &&
	          gds_barrier.size == VK_WHOLE_SIZE,
	      "GDS dependency does not order host/transfer/shader writes");
}

void TestNativeSubgroupPolicy() {
	GraphicContext context;
	context.subgroup_size                 = 32;
	context.min_subgroup_size             = 32;
	context.max_subgroup_size             = 32;
	context.subgroup_size_control_enabled = true;
	context.required_subgroup_size_stages = vk::ShaderStageFlagBits::eAll;
	ShaderRecompiler::IR::Program safe;
	safe.wave_size      = 32;
	safe.lane_mask_mode = ShaderLaneMaskMode::NativeWave;
	Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
	                              vk::ShaderStageFlagBits::eVertex, safe).mode ==
	          ShaderSubgroupMode::Natural,
	      "native wave32 policy changed");
	safe.wave_size = 64;
	Check(SelectGraphicsLaneMaskMode(safe.wave_size) ==
	              ShaderLaneMaskMode::PerInvocation &&
	          ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
	                                  vk::ShaderStageFlagBits::eVertex, safe).mode ==
	              ShaderSubgroupMode::Unsupported,
	      "wave64 graphics mismatch accepted native-wave mask lowering");
	safe.lane_mask_mode = ShaderLaneMaskMode::PerInvocation;
	Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
	                              vk::ShaderStageFlagBits::eVertex, safe).mode ==
	          ShaderSubgroupMode::PerInvocationGraphics,
	      "wave64 graphics mismatch did not select per-invocation masks");

	ShaderRecompiler::IR::Program cross_lane = safe;
	cross_lane.blocks.emplace_back().instructions.emplace_back().op =
	    ShaderRecompiler::IR::Opcode::ReadLaneU32;
	Check(ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(cross_lane) &&
	          ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
	                                  vk::ShaderStageFlagBits::eVertex, cross_lane).mode ==
	              ShaderSubgroupMode::PerInvocationGraphics,
	      "graphics mismatch did not select per-invocation masks");
	auto cross_lane_compute           = cross_lane;
	cross_lane_compute.lane_mask_mode = ShaderLaneMaskMode::NativeWave;
	Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
	                              vk::ShaderStageFlagBits::eCompute, cross_lane_compute).mode ==
	          ShaderSubgroupMode::Unsupported,
	      "cross-lane compute mismatch bypassed the exact subgroup requirement");

	ShaderRecompiler::IR::Program zero_exec = safe;
	zero_exec.lane_mask_mode                = ShaderLaneMaskMode::NativeWave;
	zero_exec.provenance.values.resize(3);
	zero_exec.provenance.values[2].op  = ShaderRecompiler::IR::ScalarValueOp::Constant;
	zero_exec.provenance.values[2].imm = 0;
	auto& zero_bfm             = zero_exec.blocks.emplace_back().instructions.emplace_back();
	zero_bfm.op                = ShaderRecompiler::IR::Opcode::BitFieldMaskU64;
	zero_bfm.dst.kind          = ShaderRecompiler::IR::OperandKind::Register;
	zero_bfm.dst.reg           = {ShaderRecompiler::IR::RegisterFile::Exec, 0};
	zero_bfm.src_count         = 2;
	zero_bfm.scalar_sources[0] = 2;
	Check(!ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(zero_exec) &&
	          ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
	                                  vk::ShaderStageFlagBits::eCompute, zero_exec).mode ==
	              ShaderSubgroupMode::FlattenedMasks,
	      "compile-time uniform-zero EXEC write did not stay on the mask-free path");

	ShaderRecompiler::IR::Program selective_exec = safe;
	auto& move_exec    = selective_exec.blocks.emplace_back().instructions.emplace_back();
	move_exec.op       = ShaderRecompiler::IR::Opcode::MoveU32;
	move_exec.dst.kind = ShaderRecompiler::IR::OperandKind::Register;
	move_exec.dst.reg  = {ShaderRecompiler::IR::RegisterFile::Exec, 0};
	Check(ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(selective_exec),
	      "selective EXEC write was classified mask-free");

	ShaderRecompiler::IR::Program carry = safe;
	auto& add_carry                     = carry.blocks.emplace_back().instructions.emplace_back();
	add_carry.op                        = ShaderRecompiler::IR::Opcode::IAddCarryU32;
	add_carry.dst2.kind                 = ShaderRecompiler::IR::OperandKind::Register;
	add_carry.dst2.reg                  = {ShaderRecompiler::IR::RegisterFile::Vcc, 0};
	Check(ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(carry),
	      "lane-varying VCC carry producer was classified mask-free");

	ShaderRecompiler::IR::Program vcc_branch   = safe;
	auto&                         branch_block = vcc_branch.blocks.emplace_back();
	branch_block.terminator.kind      = ShaderRecompiler::CFG::TerminatorKind::ConditionalBranch;
	branch_block.terminator.condition = ShaderRecompiler::CFG::BranchCondition::VccNonZero;
	Check(ShaderRecompiler::Spirv::ProgramRequiresExactSubgroupSize(vcc_branch),
	      "whole-wave VCC branch was classified mask-free");

	ShaderRecompiler::IR::Program ds_partial = safe;
	ds_partial.lane_mask_mode                = ShaderLaneMaskMode::NativeWave;
	ds_partial.blocks.emplace_back().instructions.emplace_back().op =
	    ShaderRecompiler::IR::Opcode::DsAppend;
	Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
	                              vk::ShaderStageFlagBits::eCompute, ds_partial).mode ==
	          ShaderSubgroupMode::Unsupported,
	      "partial wave64 DS append bypassed the exact subgroup requirement");
	context.max_subgroup_size = 64;
	const auto controlled =
	    ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
	                            vk::ShaderStageFlagBits::eCompute, cross_lane_compute);
	Check(controlled.mode == ShaderSubgroupMode::Controlled && controlled.required_size == 64,
	      "supported controlled wave64 was not preferred over splitting");
	context.subgroup_size                 = 64;
	context.subgroup_size_control_enabled = false;
	cross_lane.wave_size                  = 32;
	cross_lane.lane_mask_mode             = ShaderLaneMaskMode::PerInvocation;
	Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
	                              vk::ShaderStageFlagBits::eFragment, cross_lane).mode ==
	          ShaderSubgroupMode::Unsupported,
	      "inverse graphics mismatch was accepted as one guest wave");
	cross_lane_compute.wave_size = 32;
	Check(ConfigureShaderSubgroup(ShaderSubgroupCapabilities {context},
	                              vk::ShaderStageFlagBits::eCompute, cross_lane_compute).mode ==
	          ShaderSubgroupMode::Unsupported,
	      "inverse cross-lane compute mismatch was accepted");
}

std::array<uint32_t, 64> ImageTestUserData(Prospero::ImageType type = Prospero::ImageType::kColor2D) {
	std::array<uint32_t, 64> data {};
	for (uint32_t start = 0; start + 3u < data.size(); start += 4u) {
		data[start]      = 0x1000u + start * 0x100u;
		data[start + 2u] = UINT32_MAX;
		data[start + 3u] = Prospero::GpuEnumValue(type) << 28u;
	}
	return data;
}

void SetImageTestType(std::array<uint32_t, 64>* data, uint32_t srsrc, Prospero::ImageType type) {
	const auto type_dword = srsrc * 4u + 3u;
	Check(data != nullptr && type_dword < data->size(), "invalid image test descriptor source");
	(*data)[type_dword] = Prospero::GpuEnumValue(type) << 28u;
}

bool ReadZeroTestMemory(void*, uint64_t, uint32_t* value) {
	if (value == nullptr) {
		return false;
	}
	*value = 0;
	return true;
}

constexpr uint32_t EncodeVop2(uint32_t opcode, uint32_t dst, uint32_t src0, uint32_t src1) {
	return ((opcode & 0x3fu) << 25u) | ((dst & 0xffu) << 17u) | ((src1 & 0xffu) << 9u) |
	       (src0 & 0x1ffu);
}

constexpr uint32_t EncodeVop1(uint32_t opcode, uint32_t dst, uint32_t src0) {
	return (0x3fu << 25u) | ((dst & 0xffu) << 17u) | ((opcode & 0xffu) << 9u) | (src0 & 0x1ffu);
}

constexpr uint32_t EncodeVop1Sdwa(uint32_t src0, uint32_t dst_sel = 6, uint32_t dst_u = 0,
                                  uint32_t src0_sel = 6, uint32_t src0_sext = 0,
                                  uint32_t src0_neg = 0, uint32_t src0_abs = 0, uint32_t s0 = 0) {
	return (src0 & 0xffu) | ((dst_sel & 0x7u) << 8u) | ((dst_u & 0x3u) << 11u) |
	       ((src0_sel & 0x7u) << 16u) | ((src0_sext & 0x1u) << 19u) | ((src0_neg & 0x1u) << 20u) |
	       ((src0_abs & 0x1u) << 21u) | ((s0 & 0x1u) << 23u);
}

constexpr uint32_t EncodeVop1Dpp(uint32_t src0, uint32_t dpp_ctrl = 0, uint32_t row_mask = 0xf,
                                 uint32_t bank_mask = 0xf) {
	return (src0 & 0xffu) | ((dpp_ctrl & 0x1ffu) << 8u) | ((bank_mask & 0xfu) << 24u) |
	       ((row_mask & 0xfu) << 28u);
}

constexpr uint32_t EncodeVop2Sdwa(uint32_t src0, uint32_t dst_sel = 6, uint32_t dst_u = 0,
                                  uint32_t src0_sel = 6, uint32_t src1_sel = 6,
                                  uint32_t src0_sext = 0, uint32_t src1_sext = 0,
                                  uint32_t src0_neg = 0, uint32_t src0_abs = 0,
                                  uint32_t src1_neg = 0, uint32_t src1_abs = 0, uint32_t s0 = 0,
                                  uint32_t s1 = 0) {
	return (src0 & 0xffu) | ((dst_sel & 0x7u) << 8u) | ((dst_u & 0x3u) << 11u) |
	       ((src0_sel & 0x7u) << 16u) | ((src0_sext & 0x1u) << 19u) | ((src0_neg & 0x1u) << 20u) |
	       ((src0_abs & 0x1u) << 21u) | ((s0 & 0x1u) << 23u) | ((src1_sel & 0x7u) << 24u) |
	       ((src1_sext & 0x1u) << 27u) | ((src1_neg & 0x1u) << 28u) | ((src1_abs & 0x1u) << 29u) |
	       ((s1 & 0x1u) << 31u);
}

constexpr uint32_t EncodeVop2Dpp(uint32_t src0, uint32_t dpp_ctrl = 0, uint32_t row_mask = 0xf,
                                 uint32_t bank_mask = 0xf, uint32_t src0_neg = 0,
                                 uint32_t src0_abs = 0, uint32_t src1_neg = 0,
                                 uint32_t src1_abs = 0) {
	return (src0 & 0xffu) | ((dpp_ctrl & 0x1ffu) << 8u) | ((src0_neg & 0x1u) << 20u) |
	       ((src0_abs & 0x1u) << 21u) | ((src1_neg & 0x1u) << 22u) | ((src1_abs & 0x1u) << 23u) |
	       ((bank_mask & 0xfu) << 24u) | ((row_mask & 0xfu) << 28u);
}

constexpr uint32_t EncodeVopc(uint32_t opcode, uint32_t src0, uint32_t src1) {
	return (0x3eu << 25u) | ((opcode & 0xffu) << 17u) | ((src1 & 0xffu) << 9u) | (src0 & 0x1ffu);
}

constexpr uint32_t EncodeVopcSdwa(uint32_t src0, uint32_t sdst = 0, uint32_t sd = 0,
                                  uint32_t src0_sel = 6, uint32_t src1_sel = 6,
                                  uint32_t src0_sext = 0, uint32_t src1_sext = 0,
                                  uint32_t src0_neg = 0, uint32_t src0_abs = 0,
                                  uint32_t src1_neg = 0, uint32_t src1_abs = 0, uint32_t s0 = 0,
                                  uint32_t s1 = 0) {
	return (src0 & 0xffu) | ((sdst & 0x7fu) << 8u) | ((sd & 0x1u) << 15u) |
	       ((src0_sel & 0x7u) << 16u) | ((src0_sext & 0x1u) << 19u) | ((src0_neg & 0x1u) << 20u) |
	       ((src0_abs & 0x1u) << 21u) | ((s0 & 0x1u) << 23u) | ((src1_sel & 0x7u) << 24u) |
	       ((src1_sext & 0x1u) << 27u) | ((src1_neg & 0x1u) << 28u) | ((src1_abs & 0x1u) << 29u) |
	       ((s1 & 0x1u) << 31u);
}

constexpr uint32_t EncodeVop3Word0(uint32_t opcode, uint32_t dst, uint32_t op_sel = 0,
                                   uint32_t abs = 0, bool clamp = false) {
	return (0x35u << 26u) | ((opcode & 0x3ffu) << 16u) | (dst & 0xffu) | ((abs & 0x7u) << 8u) |
	       ((op_sel & 0xfu) << 11u) | (clamp ? (1u << 15u) : 0u);
}

constexpr uint32_t EncodeVop3Word0Sdst(uint32_t opcode, uint32_t dst, uint32_t sdst) {
	return (0x35u << 26u) | ((opcode & 0x3ffu) << 16u) | ((sdst & 0x7fu) << 8u) | (dst & 0xffu);
}

constexpr uint32_t EncodeVop3Word1(uint32_t src0, uint32_t src1, uint32_t src2) {
	return (src0 & 0x1ffu) | ((src1 & 0x1ffu) << 9u) | ((src2 & 0x1ffu) << 18u);
}

constexpr uint32_t EncodeVop3pWord0(uint32_t opcode, uint32_t dst, uint32_t op_sel = 0,
                                    uint32_t op_sel_hi = 0, uint32_t neg_hi = 0,
                                    bool clamp = false) {
	return (0x33u << 26u) | ((opcode & 0x7fu) << 16u) | (dst & 0xffu) | ((neg_hi & 0x7u) << 8u) |
	       ((op_sel & 0x7u) << 11u) | (((op_sel_hi >> 2u) & 0x1u) << 14u) |
	       (clamp ? (1u << 15u) : 0u);
}

constexpr uint32_t EncodeVop3pWord1(uint32_t src0, uint32_t src1, uint32_t src2,
                                    uint32_t op_sel_hi = 0, uint32_t neg = 0) {
	return (src0 & 0x1ffu) | ((src1 & 0x1ffu) << 9u) | ((src2 & 0x1ffu) << 18u) |
	       ((op_sel_hi & 0x3u) << 27u) | ((neg & 0x7u) << 29u);
}

constexpr uint32_t EncodeSmem0(uint32_t opcode, uint32_t dst, uint32_t sbase) {
	return (0x3du << 26u) | ((opcode & 0xffu) << 18u) | ((dst & 0x7fu) << 6u) | (sbase & 0x3fu);
}

constexpr uint32_t EncodeMubuf0(uint32_t opcode, uint32_t offset = 0, bool idxen = true,
                                bool glc = false) {
	return (0x38u << 26u) | ((opcode & 0x7fu) << 18u) | (idxen ? (1u << 13u) : 0u) |
	       (glc ? (1u << 14u) : 0u) | (offset & 0xfffu);
}

constexpr uint32_t EncodeMubuf1(uint32_t vdata, uint32_t srsrc, uint32_t vaddr,
                                uint32_t soffset = 128) {
	return ((soffset & 0xffu) << 24u) | ((srsrc & 0x1fu) << 16u) | ((vdata & 0xffu) << 8u) |
	       (vaddr & 0xffu);
}

constexpr uint32_t EncodeMtbuf0(uint32_t opcode, uint32_t dfmt, uint32_t nfmt, uint32_t offset = 0,
                                bool idxen = true, bool offen = false) {
	return (0x3au << 26u) | (offset & 0xfffu) | (offen ? (1u << 12u) : 0u) |
	       (idxen ? (1u << 13u) : 0u) | ((opcode & 0x7u) << 16u) | ((dfmt & 0xfu) << 19u) |
	       ((nfmt & 0x7u) << 23u);
}

constexpr uint32_t EncodeMtbuf1(uint32_t opcode, uint32_t vdata, uint32_t srsrc, uint32_t vaddr,
                                uint32_t soffset = 128) {
	return ((opcode >> 3u) & 1u) << 21u | ((soffset & 0xffu) << 24u) | ((srsrc & 0x1fu) << 16u) |
	       ((vdata & 0xffu) << 8u) | (vaddr & 0xffu);
}

constexpr uint32_t EncodeFlat0(uint32_t opcode, uint32_t seg, uint32_t offset = 0) {
	return (0x37u << 26u) | ((opcode & 0x7fu) << 18u) | ((seg & 0x3u) << 14u) | (offset & 0xfffu);
}

constexpr uint32_t EncodeFlat1(uint32_t vdst, uint32_t saddr, uint32_t data, uint32_t addr) {
	return ((vdst & 0xffu) << 24u) | ((saddr & 0x7fu) << 16u) | ((data & 0xffu) << 8u) |
	       (addr & 0xffu);
}

constexpr uint32_t EncodeDs0(uint32_t opcode, uint32_t offset = 0) {
	return (0x36u << 26u) | ((opcode & 0xffu) << 18u) | (offset & 0xffffu);
}

constexpr uint32_t EncodeDs1Ex(uint32_t vdst, uint32_t data1, uint32_t data0, uint32_t addr) {
	return ((vdst & 0xffu) << 24u) | ((data1 & 0xffu) << 16u) | ((data0 & 0xffu) << 8u) |
	       (addr & 0xffu);
}

constexpr uint32_t EncodeDs1(uint32_t vdst, uint32_t data0, uint32_t addr) {
	return EncodeDs1Ex(vdst, 0, data0, addr);
}

constexpr uint32_t EncodeMimg0(uint32_t opcode, uint32_t dmask, bool glc = false,
                               uint32_t dim = 1) {
	return (0x3cu << 26u) | ((opcode & 0x7fu) << 18u) | ((dmask & 0xfu) << 8u) |
	       ((dim & 0x7u) << 3u) | (glc ? (1u << 13u) : 0u) | ((opcode >> 7u) & 1u);
}

constexpr uint32_t EncodeMimg1(uint32_t vdata, uint32_t srsrc, uint32_t ssamp, uint32_t vaddr,
                               bool a16 = false) {
	return ((ssamp & 0x1fu) << 21u) | ((srsrc & 0x1fu) << 16u) | ((vdata & 0xffu) << 8u) |
	       (vaddr & 0xffu) | (a16 ? (1u << 30u) : 0u);
}

constexpr uint32_t EncodeVintrp(uint32_t opcode, uint32_t vdst, uint32_t attr, uint32_t chan,
                                uint32_t vsrc) {
	return (0x32u << 26u) | ((opcode & 0x3u) << 16u) | ((vdst & 0xffu) << 18u) |
	       ((attr & 0x3fu) << 10u) | ((chan & 0x3u) << 8u) | (vsrc & 0xffu);
}

constexpr uint32_t EncodeExp0(uint32_t target, uint32_t en, bool done = true, bool compr = false,
                              bool vm = false) {
	return (0x3eu << 26u) | ((target & 0x3fu) << 4u) | (en & 0xfu) | (compr ? (1u << 10u) : 0u) |
	       (done ? (1u << 11u) : 0u) | (vm ? (1u << 12u) : 0u);
}

constexpr uint32_t EncodeExp1(uint32_t src0, uint32_t src1, uint32_t src2, uint32_t src3) {
	return (src0 & 0xffu) | ((src1 & 0xffu) << 8u) | ((src2 & 0xffu) << 16u) |
	       ((src3 & 0xffu) << 24u);
}

void TestNewShaderRecompilerSMovB32() {
	const uint32_t shader[] = {
	    EncodeSMovB32(0, 129),                      // s_mov_b32 s0, 1
	    EncodeSMovB32(1, 255),                      // s_mov_b32 s1, 0x12345678
	    0x12345678u,           EncodeSMovB32(2, 1), // s_mov_b32 s2, s1
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(!result.spirv.empty(), "new shader recompiler produced no SPIR-V");
	Check(result.spirv.front() == 0x07230203u, "new shader recompiler did not emit SPIR-V binary");
	Check(Common::ContainsStr(result.decoded_dump, "s_mov_b32 s0, 1"),
	      "new decoder did not decode inline S_MOV_B32 operand");
	Check(Common::ContainsStr(result.decoded_dump, "s_mov_b32 s1, 0x12345678"),
	      "new decoder did not decode literal S_MOV_B32 operand");
	Check(Common::ContainsStr(result.decoded_dump, "s_mov_b32 s2, s1"),
	      "new decoder did not decode register S_MOV_B32 operand");
	Check(Common::ContainsStr(result.ir_dump, "MoveU32 s0, 0x00000001"),
	      "new IR did not lower inline S_MOV_B32");
	Check(Common::ContainsStr(result.ir_dump, "MoveU32 s1, 0x12345678"),
	      "new IR did not lower literal S_MOV_B32");
	Check(Common::ContainsStr(result.ir_dump, "MoveU32 s2, s1"),
	      "new IR did not lower register S_MOV_B32");
	Check(std::find(result.spirv.begin(), result.spirv.end(), 0x12345678u) != result.spirv.end(),
	      "new SPIR-V emitter did not encode the literal as a binary word");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerSoppMarkers() {
	const uint32_t shader[] = {
	    EncodeSopp(0x00, 3),    // s_nop 3
	    EncodeSopp(0x0c, 0),    // s_waitcnt 0
	    EncodeSopp(0x10, 0x0f), // s_sendmsg 15
	    EncodeSopp(0x16, 0x2a), // s_ttracedata 42
	    EncodeSopp(0x20, 1),    // s_inst_prefetch 1
	    EncodeSopp(0x0a, 0),    // s_barrier
	    EncodeSopp(0x01, 0),    // s_endpgm
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "s_nop 0x00000003"),
	      "new decoder did not decode SOPP s_nop");
	Check(Common::ContainsStr(result.decoded_dump, "s_waitcnt 0x00000000"),
	      "new decoder did not decode SOPP s_waitcnt");
	Check(Common::ContainsStr(result.decoded_dump, "s_sendmsg 0x0000000f"),
	      "new decoder did not decode SOPP s_sendmsg");
	Check(Common::ContainsStr(result.decoded_dump, "s_ttracedata 0x0000002a"),
	      "new decoder did not decode SOPP s_ttracedata");
	Check(Common::ContainsStr(result.decoded_dump, "s_inst_prefetch 0x00000001"),
	      "new decoder did not decode SOPP s_inst_prefetch");
	Check(Common::ContainsStr(result.decoded_dump, "s_barrier"),
	      "new decoder did not decode SOPP s_barrier");
	Check(Common::ContainsStr(result.ir_dump, "ControlNop null, 0x00000003"),
	      "SOPP s_nop did not lower to an IR marker");
	Check(Common::ContainsStr(result.ir_dump, "Waitcnt null, 0x00000000"),
	      "SOPP s_waitcnt did not lower to an IR marker");
	Check(Common::ContainsStr(result.ir_dump, "Sendmsg null, 0x0000000f"),
	      "SOPP s_sendmsg did not lower to an IR marker");
	Check(Common::ContainsStr(result.ir_dump, "TtraceData null, 0x0000002a"),
	      "SOPP s_ttracedata did not lower to an IR marker");
	Check(Common::ContainsStr(result.ir_dump, "InstPrefetch null, 0x00000001"),
	      "SOPP s_inst_prefetch did not lower to an IR marker");
	Check(Common::ContainsStr(result.ir_dump, "Barrier null"),
	      "SOPP s_barrier did not lower to an IR marker");
	Check(SpirvContainsOpcode(result.spirv, 224),
	      "SPIR-V binary does not contain OpControlBarrier");
	Check(std::find(result.spirv.begin(), result.spirv.end(), 264u) != result.spirv.end(),
	      "SPIR-V barrier does not use workgroup acquire-release memory semantics");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerSopkWaitcntMarkers() {
	const uint32_t shader[] = {
	    EncodeSopk(0x17, 125, 0xffff), // s_waitcnt_vscnt null, 0xffff
	    EncodeSopk(0x18, 125, 0),      // s_waitcnt_vmcnt null, 0
	    EncodeSopk(0x19, 125, 0),      // s_waitcnt_expcnt null, 0
	    EncodeSopk(0x1a, 125, 0),      // s_waitcnt_lgkmcnt null, 0
	    EncodeSopp(0x01, 0),           // s_endpgm
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "s_waitcnt 0"),
	      "new decoder did not decode SOPK waitcnt marker");
	Check(Common::ContainsStr(result.decoded_dump, "s_waitcnt 65535"),
	      "SOPK waitcnt marker immediate was not kept unsigned");
	Check(Common::ContainsStr(result.ir_dump, "Waitcnt null, 0x00000000"),
	      "SOPK waitcnt did not lower to an IR marker");
	Check(Common::ContainsStr(result.ir_dump, "Waitcnt null, 0x0000ffff"),
	      "SOPK waitcnt marker immediate was not lowered as 16-bit unsigned");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerRdna2ScalarOpcodes() {
	const uint32_t shader[] = {
	    EncodeSMovB32(2, 135),         // s2 = 7
	    EncodeSMovB32(106, 144),       // vcc_lo = 16
	    EncodeSop1(0x1d, 106, 128),    // s_bitset1_b32 vcc_lo, 0
	    EncodeSopk(0x13, 106, 0x1019), // s_setreg_b32 vcc_lo, 0x1019
	    EncodeSop2(0x02, 106, 2, 239), // s_add_i32 vcc_lo, s2, pops_exiting_wave_id
	    EncodeSopp(0x0e, 0),           // s_sleep 0
	    EncodeSop2(0x02, 106, 239, 2), // s_add_i32 vcc_lo, pops_exiting_wave_id, s2
	    EncodeSopp(0x01, 0),           // s_endpgm
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "s_bitset1_b32 vcc_lo, 0"),
	      "new decoder did not decode RDNA2 S_BITSET1_B32");
	Check(Common::ContainsStr(result.decoded_dump, "s_setreg_b32 vcc_lo, 0x00001019"),
	      "new decoder did not decode S_SETREG_B32");
	Check(Common::ContainsStr(result.decoded_dump, "s_add_i32 vcc_lo, s2, pops_exiting_wave_id"),
	      "new decoder did not decode pops_exiting_wave_id as RHS scalar source");
	Check(Common::ContainsStr(result.decoded_dump, "s_add_i32 vcc_lo, pops_exiting_wave_id, s2"),
	      "new decoder did not decode pops_exiting_wave_id as LHS scalar source");
	Check(Common::ContainsStr(result.decoded_dump, "s_sleep 0x00000000"),
	      "new decoder did not decode S_SLEEP");
	Check(Common::ContainsStr(result.ir_dump, "BitSetU32 vcc_lo, vcc_lo, 0x00000000"),
	      "S_BITSET1_B32 did not lower to bit-set IR using the destination as input");
	Check(Common::ContainsStr(result.ir_dump, "ScalarSignedAddOverflowI32 vcc_lo, s2, 0x00000000"),
	      "pops_exiting_wave_id RHS did not lower to a deterministic zero value");
	Check(Common::ContainsStr(result.ir_dump, "ScalarSignedAddOverflowI32 vcc_lo, 0x00000000, s2"),
	      "pops_exiting_wave_id LHS did not lower to a deterministic zero value");
	Check(Common::ContainsStr(result.ir_dump, "ControlNop null, vcc_lo"),
	      "S_SETREG_B32 did not lower to an explicit control marker");
	Check(Common::ContainsStr(result.ir_dump, "ControlNop null, 0x00000000"),
	      "S_SLEEP did not lower to an explicit control marker");
	Check(SpirvContainsOpcode(result.spirv, 196),
	      "SPIR-V binary does not contain OpShiftLeftLogical for S_BITSET1_B32");
	Check(SpirvContainsOpcode(result.spirv, 197),
	      "SPIR-V binary does not contain OpBitwiseOr for S_BITSET1_B32");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerScalarVectorAlu() {
	const uint32_t shader[] = {
	    EncodeSMovB32(0, 129),           // s0 = 1
	    EncodeSMovB32(1, 130),           // s1 = 2
	    EncodeSop2(0x00, 2, 0, 1),       // s_add_u32 s2, s0, s1
	    EncodeSop2(0x01, 3, 2, 129),     // s_sub_u32 s3, s2, 1
	    EncodeSop2(0x0e, 4, 2, 3),       // s_and_b32 s4, s2, s3
	    EncodeSop2(0x10, 5, 2, 3),       // s_or_b32 s5, s2, s3
	    EncodeSop2(0x12, 6, 2, 3),       // s_xor_b32 s6, s2, s3
	    EncodeSop2(0x1e, 7, 2, 129),     // s_lshl_b32 s7, s2, 1
	    EncodeSop2(0x20, 8, 7, 129),     // s_lshr_b32 s8, s7, 1
	    EncodeSop2(0x2e, 9, 2, 1),       // s_lshl1_add_u32 s9, s2, s1
	    EncodeSop2(0x2f, 10, 9, 1),      // s_lshl2_add_u32 s10, s9, s1
	    EncodeSop2(0x30, 11, 10, 1),     // s_lshl3_add_u32 s11, s10, s1
	    EncodeSop2(0x31, 12, 11, 1),     // s_lshl4_add_u32 s12, s11, s1
	    EncodeSop2(0x35, 13, 12, 1),     // s_mul_hi_u32 s13, s12, s1
	    EncodeSopc(0x08, 8, 1),          // s_cmp_gt_u32 s8, s1
	    EncodeSop2(0x04, 14, 13, 129),   // s_addc_u32 s14, s13, 1
	    EncodeVop2(0x03, 1, 242, 0),     // v_add_f32 v1, 1.0, v0
	    EncodeVop2(0x08, 2, 1 + 256, 1), // v_mul_f32 v2, v1, v1
	    EncodeVop2(0x25, 3, 1 + 256, 2), // v_add_nc_u32 v3, v1, v2
	    EncodeVop2(0x1b, 4, 3 + 256, 2), // v_and_b32 v4, v3, v2
	    EncodeVop2(0x0b, 6, 3 + 256, 4), // v_mul_u32_u24 v6, v3, v4
	    EncodeVop2(0x09, 8, 4 + 256, 6), // v_mul_i32_i24 v8, v4, v6
	    EncodeVop2(0x22, 7, 4 + 256, 6), // v_bcnt_u32_b32 v7, v4, v6
	    EncodeVopc(0xc4, 3 + 256, 2),    // v_cmp_gt_u32 v3, v2
	    EncodeVop2(0x01, 5, 1 + 256, 2), // v_cndmask_b32 v5, v1, v2
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "s_add_u32 s2, s0, s1"),
	      "new decoder did not decode SOP2 add");
	Check(Common::ContainsStr(result.decoded_dump, "s_addc_u32 s14, s13, 1"),
	      "new decoder did not decode old-backed S_ADD_C_U32");
	Check(Common::ContainsStr(result.decoded_dump, "s_cmp_gt_u32 s8, s1"),
	      "new decoder did not decode SOPC compare");
	Check(Common::ContainsStr(result.decoded_dump, "s_lshl1_add_u32 s9, s2, s1"),
	      "new decoder did not decode old-backed S_LSHL1_ADD_U32");
	Check(Common::ContainsStr(result.decoded_dump, "s_lshl2_add_u32 s10, s9, s1"),
	      "new decoder did not decode old-backed S_LSHL2_ADD_U32");
	Check(Common::ContainsStr(result.decoded_dump, "s_lshl3_add_u32 s11, s10, s1"),
	      "new decoder did not decode old-backed S_LSHL3_ADD_U32");
	Check(Common::ContainsStr(result.decoded_dump, "s_lshl4_add_u32 s12, s11, s1"),
	      "new decoder did not decode old-backed S_LSHL4_ADD_U32");
	Check(Common::ContainsStr(result.decoded_dump, "s_mul_hi_u32 s13, s12, s1"),
	      "new decoder did not decode old-backed S_MUL_HI_U32");
	Check(Common::ContainsStr(result.decoded_dump, "v_add_f32 v1"),
	      "new decoder did not decode VOP2 float add");
	Check(Common::ContainsStr(result.decoded_dump, "v_cndmask_b32 v5"),
	      "new decoder did not decode VOP2 conditional mask select");
	Check(Common::ContainsStr(result.decoded_dump, "v_mul_u32_u24 v6"),
	      "new decoder did not decode VOP2 24-bit multiply");
	Check(Common::ContainsStr(result.decoded_dump, "v_mul_i32_i24 v8"),
	      "new decoder did not decode VOP2 signed 24-bit multiply");
	Check(Common::ContainsStr(result.decoded_dump, "v_bcnt_u32_b32 v7"),
	      "new decoder did not decode old-backed V_BCNT_U32_B32");
	Check(Common::ContainsStr(result.ir_dump, "ScalarAddCarryU32 s2, s0, s1, 0x00000000"),
	      "SOP2 add did not lower to scalar carry-writing IR");
	Check(Common::ContainsStr(result.ir_dump, "ScalarAddCarryU32 s14, s13, 0x00000001, scc"),
	      "S_ADD_C_U32 did not lower to scalar carry IR");
	Check(Common::ContainsStr(result.ir_dump, "ScalarShiftLeftAddCarryU32 s9, s2, 0x00000001, s1"),
	      "S_LSHL1_ADD_U32 did not lower through carry-writing shift-left-add IR");
	Check(Common::ContainsStr(result.ir_dump, "ScalarShiftLeftAddCarryU32 s10, s9, 0x00000002, s1"),
	      "S_LSHL2_ADD_U32 did not lower through carry-writing shift-left-add IR");
	Check(
	    Common::ContainsStr(result.ir_dump, "ScalarShiftLeftAddCarryU32 s11, s10, 0x00000003, s1"),
	    "S_LSHL3_ADD_U32 did not lower through carry-writing shift-left-add IR");
	Check(
	    Common::ContainsStr(result.ir_dump, "ScalarShiftLeftAddCarryU32 s12, s11, 0x00000004, s1"),
	    "S_LSHL4_ADD_U32 did not lower through carry-writing shift-left-add IR");
	Check(Common::ContainsStr(result.ir_dump, "UMulHighU32 s13, s12, s1"),
	      "S_MUL_HI_U32 did not lower to unsigned high-multiply IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareGtU32"), "SOPC compare did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "FAddF32 v1"), "VOP2 float add did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseAndU32 v4"),
	      "VOP2 bitwise op did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "SelectMaskU32 v5, vcc_lo, v2, v1"),
	      "VOP2 conditional mask did not lower through lane-mask select IR");
	Check(Common::ContainsStr(result.ir_dump, "UMulU24U32 v6"),
	      "VOP2 24-bit multiply did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "IMulI24U32 v8"),
	      "VOP2 signed 24-bit multiply did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "BitCountAddU32 v7, v4, v6"),
	      "V_BCNT_U32_B32 did not lower to bit-count-add IR");
	Check(SpirvContainsOpcode(result.spirv, 128), "SPIR-V binary does not contain OpIAdd");
	Check(SpirvContainsOpcode(result.spirv, 149), "SPIR-V binary does not contain OpIAddCarry");
	Check(SpirvContainsOpcode(result.spirv, 129), "SPIR-V binary does not contain OpFAdd");
	Check(SpirvContainsOpcode(result.spirv, 132), "SPIR-V binary does not contain OpIMul");
	Check(SpirvContainsOpcode(result.spirv, 151), "SPIR-V binary does not contain OpUMulExtended");
	Check(SpirvContainsOpcode(result.spirv, 205), "SPIR-V binary does not contain OpBitCount");
	Check(SpirvContainsOpcode(result.spirv, 169), "SPIR-V binary does not contain OpSelect");
	Check(SpirvContainsOpcode(result.spirv, 199), "SPIR-V binary does not contain OpBitwiseAnd");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerVop3LaneReadDestinationEncoding() {
	// Lane-read instructions have scalar results, but their VOP3A destination is encoded in
	// VDST [7:0], not the VOP3B SDST field [14:8].
	const uint32_t shader[] = {
	    EncodeVop3Word0(0x182, 25), EncodeVop3Word1(5 + 256, 0, 0),   // v_readfirstlane_b32 s25, v5
	    EncodeVop3Word0(0x360, 26), EncodeVop3Word1(5 + 256, 130, 0), // v_readlane_b32 s26, v5, 2
	    EncodeSopp(0x01, 0),
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "v_readfirstlane_b32 s25, v5"),
	      "VOP3 V_READFIRSTLANE_B32 destination was not decoded from VDST");
	Check(Common::ContainsStr(result.decoded_dump, "v_readlane_b32 s26, v5, 2"),
	      "VOP3 V_READLANE_B32 destination was not decoded from VDST");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerMoreAluFamilies() {
	const uint32_t shader[] = {
	    EncodeSopk(0x00, 9, 7), // s_movk_i32 s9, 7
	    EncodeSopk(0x0b, 9, 3), // s_cmp_gt_u32 s9, 3
	    EncodeVop1(0x00, 0, 0), // v_nop
	    EncodeVop1(0x01, 5, 9), // v_mov_b32 v5, s9
	    EncodeVop1(0x01, 103, 249),
	    EncodeVop1Sdwa(5, 6, 0, 4), // v_mov_b32 v103, v5 word0
	    EncodeVop1(0x06, 104, 249),
	    EncodeVop1Sdwa(103, 6, 0, 4), // v_cvt_f32_u32 v104, v103 word0
	    EncodeVop1(0x0a, 105, 249),
	    EncodeVop1Sdwa(6, 5, 2, 6), // v_cvt_f16_f32 v105.hi, v6
	    EncodeVop1(0x01, 106, 250),
	    EncodeVop1Dpp(5),                // v_mov_b32 v106, v5 dpp
	    EncodeVop1(0x02, 24, 5 + 256),   // v_readfirstlane_b32 s24, v5
	    EncodeVop1(0x06, 6, 5 + 256),    // v_cvt_f32_u32 v6, v5
	    EncodeVop1(0x07, 7, 6 + 256),    // v_cvt_u32_f32 v7, v6
	    EncodeVop1(0x05, 9, 5 + 256),    // v_cvt_f32_i32 v9, v5
	    EncodeVop1(0x08, 10, 6 + 256),   // v_cvt_i32_f32 v10, v6
	    EncodeVop1(0x0a, 99, 6 + 256),   // v_cvt_f16_f32 v99, v6
	    EncodeVop1(0x0b, 100, 99 + 256), // v_cvt_f32_f16 v100, v99
	    0x7e1016f9u,
	    0x00250602u,                      // v_cvt_f32_f16 v8, abs(v2.hi)
	    EncodeVop1(0x0d, 64, 6 + 256),    // v_cvt_flr_i32_f32 v64, v6
	    EncodeVop1(0x0e, 81, 5 + 256),    // v_cvt_off_f32_i4 v81, v5
	    EncodeVop1(0x11, 65, 5 + 256),    // v_cvt_f32_ubyte0 v65, v5
	    EncodeVop1(0x12, 66, 5 + 256),    // v_cvt_f32_ubyte1 v66, v5
	    EncodeVop1(0x13, 67, 5 + 256),    // v_cvt_f32_ubyte2 v67, v5
	    EncodeVop1(0x14, 68, 5 + 256),    // v_cvt_f32_ubyte3 v68, v5
	    EncodeVop1(0x2a, 11, 6 + 256),    // v_rcp_f32 v11, v6
	    EncodeVop1(0x20, 12, 6 + 256),    // v_fract_f32 v12, v6
	    EncodeVop1(0x21, 13, 6 + 256),    // v_trunc_f32 v13, v6
	    EncodeVop1(0x22, 14, 6 + 256),    // v_ceil_f32 v14, v6
	    EncodeVop1(0x23, 15, 6 + 256),    // v_rndne_f32 v15, v6
	    EncodeVop1(0x24, 16, 6 + 256),    // v_floor_f32 v16, v6
	    EncodeVop1(0x25, 17, 6 + 256),    // v_exp_f32 v17, v6
	    EncodeVop1(0x27, 18, 6 + 256),    // v_log_f32 v18, v6
	    EncodeVop1(0x2e, 19, 6 + 256),    // v_rsq_f32 v19, v6
	    EncodeVop1(0x33, 20, 6 + 256),    // v_sqrt_f32 v20, v6
	    EncodeVop1(0x35, 21, 6 + 256),    // v_sin_f32 v21, v6
	    EncodeVop1(0x36, 22, 6 + 256),    // v_cos_f32 v22, v6
	    EncodeVop1(0x37, 27, 5 + 256),    // v_not_b32 v27, v5
	    EncodeVop1(0x38, 28, 5 + 256),    // v_bfrev_b32 v28, v5
	    EncodeVop1(0x39, 31, 5 + 256),    // v_ffbh_u32 v31, v5
	    EncodeVop1(0x3a, 32, 5 + 256),    // v_ffbl_b32 v32, v5
	    0x7e6e870cu,                      // v_movrels_b32 v55, v12
	    EncodeVop2(0x1e, 83, 5 + 256, 6), // v_xnor_b32 v83, v5, v6
	    EncodeVop2(0x23, 84, 5 + 256, 6), // v_mbcnt_lo_u32_b32 v84, v5, v6
	    EncodeVop2(0x24, 85, 5 + 256, 6), // v_mbcnt_hi_u32_b32 v85, v5, v6
	    EncodeVop2(0x1f, 89, 6 + 256, 6), // v_mac_f32 v89, v6, v6
	    EncodeVop2(0x20, 90, 6 + 256, 5),
	    0x3f800000u, // v_madmk_f32 v90, v6, 1.0, v5
	    EncodeVop2(0x21, 91, 6 + 256, 5),
	    0x40000000u,                      // v_madak_f32 v91, v6, v5, 2.0
	    EncodeVop2(0x2b, 92, 6 + 256, 6), // v_mac_f32 v92, v6, v6
	    EncodeVop2(0x2c, 93, 6 + 256, 5),
	    0x3f000000u, // v_madmk_f32 v93, v6, 0.5, v5
	    EncodeVop2(0x2d, 94, 6 + 256, 5),
	    0x40400000u,                         // v_madak_f32 v94, v6, v5, 3.0
	    EncodeVop2(0x2f, 95, 6 + 256, 6),    // v_cvt_pkrtz_f16_f32 v95, v6, v6
	    EncodeVop2(0x02, 102, 95 + 256, 95), // v_dot2c_f32_f16 v102, v95, v95
	    EncodeVop2(0x28, 97, 5 + 256, 6),    // v_addc_u32 v97, vcc, v5, v6, vcc
	    EncodeVop2(0x25, 123, 249, 6),
	    EncodeVop2Sdwa(5, 6, 0, 4, 6),
	    EncodeVop2(0x1b, 124, 249, 6),
	    EncodeVop2Sdwa(5, 6, 0, 4, 5),
	    0x025e6af9u,
	    0x16060635u, // v_cndmask_b32 v47, v53, -v53
	    0x100490f9u,
	    0x86860600u, // v_mul_f32 v2, s0, s72 (SDWA full)
	    EncodeVop2(0x35, 9, 249, 1),
	    EncodeVop2Sdwa(0, 4, 2, 4, 4),
	    0x660000f9u,
	    EncodeVop2Sdwa(0, 4, 2, 4, 4),
	    EncodeVop2(0x03, 125, 250, 6),
	    EncodeVop2Dpp(5),
	    EncodeVop3Word0(0x103, 35),
	    EncodeVop3Word1(6 + 256, 6 + 256, 0),
	    EncodeVop3Word0(0x10b, 36),
	    EncodeVop3Word1(5 + 256, 5 + 256, 0),
	    EncodeVop3Word0(0x11e, 86),
	    EncodeVop3Word1(5 + 256, 6 + 256, 0),
	    EncodeVop3Word0(0x11f, 96),
	    EncodeVop3Word1(6 + 256, 6 + 256, 0),
	    EncodeVop3Word0Sdst(0x128, 98, 30),
	    EncodeVop3Word1(5 + 256, 6 + 256, 106),
	    EncodeVop3Word0(0x123, 87),
	    EncodeVop3Word1(5 + 256, 6 + 256, 0),
	    EncodeVop3Word0(0x124, 88),
	    EncodeVop3Word1(5 + 256, 6 + 256, 0),
	    EncodeVop3Word0(0x180, 0),
	    EncodeVop3Word1(0, 0, 0),
	    EncodeVop3Word0(0x181, 23),
	    EncodeVop3Word1(5 + 256, 0, 0),
	    EncodeVop3Word0(0x182, 25),
	    EncodeVop3Word1(5 + 256, 0, 0),
	    EncodeVop3Word0(0x185, 24),
	    EncodeVop3Word1(5 + 256, 0, 0),
	    EncodeVop3Word0(0x18a, 101),
	    EncodeVop3Word1(6 + 256, 0, 0),
	    EncodeVop3Word0(0x18d, 74),
	    EncodeVop3Word1(6 + 256, 0, 0),
	    EncodeVop3Word0(0x18e, 82),
	    EncodeVop3Word1(5 + 256, 0, 0),
	    EncodeVop3Word0(0x1a0, 25),
	    EncodeVop3Word1(6 + 256, 0, 0),
	    EncodeVop3Word0(0x1aa, 26),
	    EncodeVop3Word1(6 + 256, 0, 0),
	    EncodeVop3Word0(0x1b7, 29),
	    EncodeVop3Word1(5 + 256, 0, 0),
	    EncodeVop3Word0(0x1b8, 30),
	    EncodeVop3Word1(5 + 256, 0, 0),
	    EncodeVop3Word0(0x1b9, 33),
	    EncodeVop3Word1(5 + 256, 0, 0),
	    EncodeVop3Word0(0x1ba, 34),
	    EncodeVop3Word1(5 + 256, 0, 0),
	    EncodeVopc(0x04, 6 + 256, 6), // v_cmp_gt_f32 v6, v6
	    EncodeVopc(0xc4, 7 + 256, 5), // v_cmp_gt_u32 v7, v5
	    EncodeVopc(0x14, 6 + 256, 6), // v_cmpx_gt_f32 v6, v6
	    EncodeVopc(0xd4, 7 + 256, 5), // v_cmpx_gt_u32 v7, v5
	    EncodeVopc(0x00, 6 + 256, 6), // v_cmp_f_f32 v6, v6
	    EncodeVopc(0x0f, 6 + 256, 6), // v_cmp_tru_f32 v6, v6
	    EncodeVopc(0x07, 6 + 256, 6), // v_cmp_o_f32 v6, v6
	    EncodeVopc(0x08, 6 + 256, 6), // v_cmp_u_f32 v6, v6
	    EncodeVopc(0x09, 6 + 256, 6), // v_cmp_nge_f32 v6, v6
	    EncodeVopc(0x0a, 6 + 256, 6), // v_cmp_nlg_f32 v6, v6
	    EncodeVopc(0x0b, 6 + 256, 6), // v_cmp_ngt_f32 v6, v6
	    EncodeVopc(0x0c, 6 + 256, 6), // v_cmp_nle_f32 v6, v6
	    EncodeVopc(0x0d, 6 + 256, 6), // v_cmp_neq_f32 v6, v6
	    0x7c1a02f9u,
	    0x068680f0u, // v_cmp_neq_f32 s0, 0.5, v1 (SDWA)
	    EncodeVopc(0xd1, 249, 6),
	    EncodeVopcSdwa(5, 0, 0, 4),   // v_cmpx_lt_u32 exec, v5, v6 (SDWA)
	    EncodeVopc(0x0e, 6 + 256, 6), // v_cmp_nlt_f32 v6, v6
	    EncodeVopc(0x19, 6 + 256, 6), // v_cmpx_nge_f32 v6, v6
	    EncodeVopc(0x1a, 6 + 256, 6), // v_cmpx_nlg_f32 v6, v6
	    EncodeVopc(0x1b, 6 + 256, 6), // v_cmpx_ngt_f32 v6, v6
	    EncodeVopc(0x1c, 6 + 256, 6), // v_cmpx_nle_f32 v6, v6
	    EncodeVopc(0x1d, 6 + 256, 6), // v_cmpx_neq_f32 v6, v6
	    EncodeVopc(0x1e, 6 + 256, 6), // v_cmpx_nlt_f32 v6, v6
	    EncodeVopc(0x80, 5 + 256, 5), // v_cmp_f_i32 v5, v5
	    EncodeVopc(0x87, 5 + 256, 5), // v_cmp_t_i32 v5, v5
	    EncodeVopc(0xc0, 5 + 256, 5), // v_cmp_f_u32 v5, v5
	    EncodeVopc(0xc7, 5 + 256, 5), // v_cmp_t_u32 v5, v5
	    0xd4e5006au,
	    0x0000d47eu, // v_cmp_ne_u64 vcc, exec, vcc
	    EncodeVop3Word0(0x141, 8),
	    EncodeVop3Word1(6 + 256, 6 + 256, 6 + 256),
	    0xd5410004u,
	    0x20121301u, // v_mad_f32 v4, -v1, v9, s4
	    EncodeVop3Word0(0x144, 110),
	    EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
	    EncodeVop3Word0(0x145, 111),
	    EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
	    EncodeVop3Word0(0x146, 112),
	    EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
	    EncodeVop3Word0(0x147, 113),
	    EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
	    0xd5470005u,
	    0x841a0102u, // v_cubema_f32 v5, v2, v0, -v6
	    EncodeVop3Word0(0x14b, 37),
	    EncodeVop3Word1(6 + 256, 6 + 256, 6 + 256),
	    EncodeVop3Word0(0x151, 38),
	    EncodeVop3Word1(6 + 256, 6 + 256, 6 + 256),
	    EncodeVop3Word0(0x154, 39),
	    EncodeVop3Word1(6 + 256, 6 + 256, 6 + 256),
	    EncodeVop3Word0(0x157, 40),
	    EncodeVop3Word1(6 + 256, 6 + 256, 6 + 256),
	    EncodeVop3Word0(0x152, 41),
	    EncodeVop3Word1(5 + 256, 5 + 256, 5 + 256),
	    EncodeVop3Word0(0x153, 42),
	    EncodeVop3Word1(5 + 256, 5 + 256, 5 + 256),
	    EncodeVop3Word0(0x155, 43),
	    EncodeVop3Word1(5 + 256, 5 + 256, 5 + 256),
	    EncodeVop3Word0(0x156, 44),
	    EncodeVop3Word1(5 + 256, 5 + 256, 5 + 256),
	    EncodeVop3Word0(0x158, 45),
	    EncodeVop3Word1(5 + 256, 5 + 256, 5 + 256),
	    EncodeVop3Word0(0x159, 46),
	    EncodeVop3Word1(5 + 256, 5 + 256, 5 + 256),
	    EncodeVop3Word0(0x148, 47),
	    EncodeVop3Word1(5 + 256, 129, 132),
	    EncodeVop3Word0(0x149, 48),
	    EncodeVop3Word1(5 + 256, 129, 132),
	    EncodeVop3Word0(0x14a, 49),
	    EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
	    EncodeVop3Word0(0x14e, 50),
	    EncodeVop3Word1(5 + 256, 6 + 256, 129),
	    EncodeVop3Word0(0x36d, 51),
	    EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
	    EncodeVop3Word0(0x169, 52),
	    EncodeVop3Word1(5 + 256, 6 + 256, 0),
	    EncodeVop3Word0(0x16a, 53),
	    EncodeVop3Word1(5 + 256, 6 + 256, 0),
	    EncodeVop3Word0(0x16c, 114),
	    EncodeVop3Word1(5 + 256, 6 + 256, 0),
	    EncodeVop3Word0(0x371, 54),
	    EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
	    EncodeVop3Word0(0x372, 55),
	    EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
	    EncodeVop3Word0(0x178, 56),
	    EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
	    EncodeVop3Word0(0x346, 57),
	    EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
	    EncodeVop3Word0(0x347, 58),
	    EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
	    EncodeVop3Word0(0x345, 59),
	    EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
	    EncodeVop3Word0(0x36f, 60),
	    EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
	    EncodeVop3Word0(0x15d, 61),
	    EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
	    EncodeVop3Word0(0x142, 62),
	    EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
	    EncodeVop3Word0(0x143, 63),
	    EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256),
	    EncodeVop3Word0(0x16b, 69),
	    EncodeVop3Word1(5 + 256, 6 + 256, 0),
	    EncodeVop3Word0(0x30f, 70),
	    EncodeVop3Word1(5 + 256, 6 + 256, 0),
	    EncodeVop3Word0(0x310, 71),
	    EncodeVop3Word1(5 + 256, 6 + 256, 0),
	    EncodeVop3Word0(0x319, 72),
	    EncodeVop3Word1(5 + 256, 6 + 256, 0),
	    0xd70a1007u,
	    0x00020affu,
	    0x0000ffffu, // v_max_i16 v7, 0xffff, v5.hi
	    0xd70c5007u,
	    0x00020882u, // v_min_i16 v7, 2, v4.hi
	    EncodeVop3Word0(0x363, 73),
	    EncodeVop3Word1(129, 132, 0),
	    EncodeVop3Word0(0x360, 26),
	    EncodeVop3Word1(5 + 256, 130, 0),
	    EncodeVop3Word0(0x361, 107),
	    EncodeVop3Word1(5 + 256, 130, 0),
	    EncodeVop3Word0(0x377, 108),
	    EncodeVop3Word1(5 + 256, 128, 128),
	    EncodeVop3Word0(0x378, 109),
	    EncodeVop3Word1(5 + 256, 128, 128),
	    EncodeVop3Word0(0x12f, 75),
	    EncodeVop3Word1(6 + 256, 6 + 256, 0),
	    0xd52f0000u,
	    0x60020300u, // v_cvt_pkrtz_f16_f32 v0, -v0, -v1
	    EncodeVop3Word0(0x362, 76),
	    EncodeVop3Word1(6 + 256, 129, 0),
	    0xd7620107u,
	    0x00018509u, // v_ldexp_f32 v7, abs(v9), -2
	    0xd762800du,
	    0x00018906u, // v_ldexp_f32 clamp v13, v6, -4
	    EncodeVop3Word0(0x364, 77),
	    EncodeVop3Word1(5 + 256, 6 + 256, 0),
	    EncodeVop3Word0(0x368, 78),
	    EncodeVop3Word1(6 + 256, 6 + 256, 0),
	    EncodeVop3Word0(0x369, 79),
	    EncodeVop3Word1(6 + 256, 6 + 256, 0),
	    EncodeVop3Word0(0x36a, 80),
	    EncodeVop3Word1(5 + 256, 7 + 256, 0),
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "s_movk_i32 s9"),
	      "new decoder did not decode SOPK mov");
	Check(Common::ContainsStr(result.decoded_dump, "s_cmp_gt_u32"),
	      "new decoder did not decode SOPK compare");
	Check(Common::ContainsStr(result.decoded_dump, "v_nop"),
	      "new decoder did not decode old-backed VOP1 no-op");
	Check(Common::ContainsStr(result.decoded_dump, "v_mov_b32 v5"),
	      "new decoder did not decode VOP1 mov");
	Check(Common::ContainsStr(result.decoded_dump, "v_movrels_b32 v55, v12"),
	      "new decoder did not decode VOP1 V_MOVRELS_B32");
	Check(Common::ContainsStr(result.decoded_dump, "v_mov_b32 v103, v5.sdwa(sel=4"),
	      "new decoder did not decode VOP1 SDWA source selector");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_f16_f32 v105.sdwa(sel=5"),
	      "new decoder did not decode VOP1 SDWA destination selector");
	Check(Common::ContainsStr(result.decoded_dump, "v_mov_b32 v106, v5.dpp"),
	      "new decoder did not decode VOP1 DPP source metadata");
	Check(!Common::ContainsStr(result.decoded_dump, "VOP1 SDWA/DPP modifiers are not implemented"),
	      "new decoder still reports blanket VOP1 SDWA/DPP unsupported reason");
	Check(Common::ContainsStr(result.decoded_dump, "v_add_nc_u32 v123, v5.sdwa(sel=4"),
	      "new decoder did not decode VOP2 SDWA source selector");
	Check(Common::ContainsStr(result.decoded_dump, "v_and_b32 v124, v5.sdwa(sel=4"),
	      "new decoder did not decode VOP2 SDWA first source selector");
	Check(Common::ContainsStr(result.decoded_dump, "v6.sdwa(sel=5"),
	      "new decoder did not decode VOP2 SDWA second source selector");
	Check(Common::ContainsStr(result.decoded_dump, "v_cndmask_b32 v47, v53,") &&
	          Common::ContainsStr(result.decoded_dump, "v53.neg"),
	      "new decoder did not decode V_CNDMASK_B32 SDWA source modifier");
	Check(Common::ContainsStr(result.decoded_dump, "v_add_f32 v125, v5.dpp"),
	      "new decoder did not decode VOP2 DPP source metadata");
	Check(!Common::ContainsStr(result.decoded_dump, "VOP2 SDWA/DPP modifiers are not implemented"),
	      "new decoder still reports blanket VOP2 SDWA/DPP unsupported reason");
	Check(Common::ContainsStr(result.decoded_dump, "v_readfirstlane_b32 s24, v5"),
	      "new decoder did not decode old-backed V_READFIRSTLANE_B32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_f32_u32 v6"),
	      "new decoder did not decode VOP1 conversion");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_f32_i32 v9"),
	      "new decoder did not decode VOP1 signed int-to-float conversion");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_i32_f32 v10"),
	      "new decoder did not decode VOP1 float-to-signed-int conversion");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_f16_f32 v99"),
	      "new decoder did not decode old-backed V_CVT_F16_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_f32_f16 v100"),
	      "new decoder did not decode old-backed native V_CVT_F32_F16");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_f32_f16 v8, v2.sdwa(sel=5") &&
	          Common::ContainsStr(result.decoded_dump, "v2.sdwa(sel=5,sext=0).abs"),
	      "new decoder did not decode V_CVT_F32_F16 SDWA source selector modifier");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_flr_i32_f32 v64"),
	      "new decoder did not decode old-backed V_CVT_FLR_I32_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_off_f32_i4 v81"),
	      "new decoder did not decode old-backed V_CVT_OFF_F32_I4");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_f32_ubyte0 v65"),
	      "new decoder did not decode old-backed V_CVT_F32_UBYTE0");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_f32_ubyte1 v66"),
	      "new decoder did not decode old-backed V_CVT_F32_UBYTE1");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_f32_ubyte2 v67"),
	      "new decoder did not decode old-backed V_CVT_F32_UBYTE2");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_f32_ubyte3 v68"),
	      "new decoder did not decode old-backed V_CVT_F32_UBYTE3");
	Check(Common::ContainsStr(result.decoded_dump, "v_rcp_f32 v11"),
	      "new decoder did not decode VOP1 reciprocal");
	Check(Common::ContainsStr(result.decoded_dump, "v_fract_f32 v12"),
	      "new decoder did not decode VOP1 fract");
	Check(Common::ContainsStr(result.decoded_dump, "v_cos_f32 v22"),
	      "new decoder did not decode VOP1 cosine");
	Check(Common::ContainsStr(result.decoded_dump, "v_not_b32 v27"),
	      "new decoder did not decode VOP1 not");
	Check(Common::ContainsStr(result.decoded_dump, "v_bfrev_b32 v28"),
	      "new decoder did not decode VOP1 bit reverse");
	Check(Common::ContainsStr(result.decoded_dump, "v_ffbh_u32 v31"),
	      "new decoder did not decode VOP1 find-first-bit-high");
	Check(Common::ContainsStr(result.decoded_dump, "v_ffbl_b32 v32"),
	      "new decoder did not decode VOP1 find-first-bit-low");
	Check(Common::ContainsStr(result.decoded_dump, "v_xnor_b32 v83, v5, v6"),
	      "new decoder did not decode old-backed V_XNOR_B32");
	Check(Common::ContainsStr(result.decoded_dump, "v_mbcnt_lo_u32_b32 v84, v5, v6"),
	      "new decoder did not decode old-backed V_MBCNT_LO_U32_B32");
	Check(Common::ContainsStr(result.decoded_dump, "v_mbcnt_hi_u32_b32 v85, v5, v6"),
	      "new decoder did not decode old-backed V_MBCNT_HI_U32_B32");
	Check(Common::ContainsStr(result.decoded_dump, "v_mac_f32 v89, v6, v6"),
	      "new decoder did not decode old-backed V_MAC_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_madmk_f32 v90, v6, 0x3f800000, v5"),
	      "new decoder did not decode old-backed V_MADMK_F32 literal form");
	Check(Common::ContainsStr(result.decoded_dump, "v_madak_f32 v91, v6, v5, 0x40000000"),
	      "new decoder did not decode old-backed V_MADAK_F32 literal form");
	Check(Common::ContainsStr(result.decoded_dump, "v_mac_f32 v92, v6, v6"),
	      "new decoder did not decode old-backed alternate V_MAC_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_madmk_f32 v93, v6, 0x3f000000, v5"),
	      "new decoder did not decode old-backed alternate V_MADMK_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_madak_f32 v94, v6, v5, 0x40400000"),
	      "new decoder did not decode old-backed alternate V_MADAK_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_pkrtz_f16_f32 v95, v6, v6"),
	      "new decoder did not decode old-backed native V_CVT_PKRTZ_F16_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_dot2c_f32_f16 v102, v95, v95"),
	      "new decoder did not decode old-backed V_DOT2C_F32_F16");
	Check(Common::ContainsStr(result.decoded_dump, "v_addc_u32 v97, vcc_lo, v5, v6, vcc_lo"),
	      "new decoder did not decode old-backed V_ADD_CO_U32 carry form");
	Check(Common::ContainsStr(result.decoded_dump, "v_add_f32 v35"),
	      "new decoder did not decode VOP3-encoded VOP2 float add");
	Check(Common::ContainsStr(result.decoded_dump, "v_mul_u32_u24 v36"),
	      "new decoder did not decode VOP3-encoded VOP2 24-bit multiply");
	Check(Common::ContainsStr(result.decoded_dump, "v_xnor_b32 v86, v5, v6"),
	      "new decoder did not decode old-backed VOP3-encoded V_XNOR_B32");
	Check(Common::ContainsStr(result.decoded_dump, "v_mac_f32 v96, v6, v6"),
	      "new decoder did not decode old-backed VOP3-encoded V_MAC_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_addc_u32 v98, s30, v5, v6, vcc_lo"),
	      "new decoder did not decode old-backed VOP3 V_ADD_CO_U32 carry form");
	Check(Common::ContainsStr(result.decoded_dump, "v_mbcnt_lo_u32_b32 v87, v5, v6"),
	      "new decoder did not decode old-backed VOP3-encoded V_MBCNT_LO_U32_B32");
	Check(Common::ContainsStr(result.decoded_dump, "v_mbcnt_hi_u32_b32 v88, v5, v6"),
	      "new decoder did not decode old-backed VOP3-encoded V_MBCNT_HI_U32_B32");
	Check(Common::ContainsStr(result.decoded_dump, "v_mov_b32 v23"),
	      "new decoder did not decode VOP3-encoded VOP1 move");
	Check(Common::ContainsStr(result.decoded_dump, "v_readfirstlane_b32 s25, v5"),
	      "new decoder did not decode old-backed VOP3-encoded V_READFIRSTLANE_B32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_f32_i32 v24"),
	      "new decoder did not decode VOP3-encoded VOP1 signed conversion");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_f16_f32 v101"),
	      "new decoder did not decode old-backed VOP3-encoded V_CVT_F16_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_flr_i32_f32 v74"),
	      "new decoder did not decode old-backed VOP3-encoded V_CVT_FLR_I32_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_off_f32_i4 v82"),
	      "new decoder did not decode old-backed VOP3-encoded V_CVT_OFF_F32_I4");
	Check(Common::ContainsStr(result.decoded_dump, "v_fract_f32 v25"),
	      "new decoder did not decode VOP3-encoded VOP1 fract");
	Check(Common::ContainsStr(result.decoded_dump, "v_rcp_f32 v26"),
	      "new decoder did not decode VOP3-encoded VOP1 reciprocal");
	Check(Common::ContainsStr(result.decoded_dump, "v_not_b32 v29"),
	      "new decoder did not decode VOP3-encoded VOP1 not");
	Check(Common::ContainsStr(result.decoded_dump, "v_bfrev_b32 v30"),
	      "new decoder did not decode VOP3-encoded VOP1 bit reverse");
	Check(Common::ContainsStr(result.decoded_dump, "v_ffbh_u32 v33"),
	      "new decoder did not decode VOP3-encoded VOP1 find-first-bit-high");
	Check(Common::ContainsStr(result.decoded_dump, "v_ffbl_b32 v34"),
	      "new decoder did not decode VOP3-encoded VOP1 find-first-bit-low");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_gt_f32"),
	      "new decoder did not decode VOPC float compare");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmpx_gt_f32"),
	      "new decoder did not decode VOPC float compare-and-mask");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmpx_gt_u32"),
	      "new decoder did not decode VOPC uint compare-and-mask");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_f_f32"),
	      "new decoder did not decode old-backed V_CMP_F_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_tru_f32"),
	      "new decoder did not decode old-backed V_CMP_TRU_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_o_f32"),
	      "new decoder did not decode old-backed V_CMP_O_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_u_f32"),
	      "new decoder did not decode old-backed V_CMP_U_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_nge_f32"),
	      "new decoder did not decode old-backed V_CMP_NGE_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_nlg_f32"),
	      "new decoder did not decode old-backed V_CMP_NLG_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_ngt_f32"),
	      "new decoder did not decode old-backed V_CMP_NGT_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_nle_f32"),
	      "new decoder did not decode old-backed V_CMP_NLE_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_neq_f32"),
	      "new decoder did not decode old-backed V_CMP_NEQ_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_neq_f32 s0, 0.500000, v1"),
	      "new decoder did not decode old-backed V_CMP_NEQ_F32 SDWA scalar destination");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmpx_lt_u32 exec_lo, v5.sdwa(sel=4") &&
	          Common::ContainsStr(result.ir_dump, "CompareMaskLtU32 exec_lo"),
	      "new decoder did not route V_CMPX SDWA destination to exec");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_nlt_f32"),
	      "new decoder did not decode old-backed V_CMP_NLT_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmpx_nge_f32"),
	      "new decoder did not decode old-backed V_CMPX_NGE_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmpx_nlg_f32"),
	      "new decoder did not decode old-backed V_CMPX_NLG_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmpx_ngt_f32"),
	      "new decoder did not decode old-backed V_CMPX_NGT_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmpx_nle_f32"),
	      "new decoder did not decode old-backed V_CMPX_NLE_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmpx_neq_f32"),
	      "new decoder did not decode old-backed V_CMPX_NEQ_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmpx_nlt_f32"),
	      "new decoder did not decode old-backed V_CMPX_NLT_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_f_i32"),
	      "new decoder did not decode old-backed V_CMP_F_I32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_t_i32"),
	      "new decoder did not decode old-backed V_CMP_T_I32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_f_u32"),
	      "new decoder did not decode old-backed V_CMP_F_U32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_t_u32"),
	      "new decoder did not decode old-backed V_CMP_T_U32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_ne_u64 vcc_lo, exec_lo, vcc_lo"),
	      "new decoder did not decode VOP3-encoded V_CMP_NE_U64");
	Check(Common::ContainsStr(result.ir_dump, "CompareNeU64 vcc_lo, exec_lo, vcc_lo"),
	      "V_CMP_NE_U64 did not lower to 64-bit compare IR");
	Check(Common::ContainsStr(result.decoded_dump, "v_mad_f32 v8"),
	      "new decoder did not decode VOP3 mad");
	Check(Common::ContainsStr(result.decoded_dump, "v_mad_f32 v4, v1.neg, v9, s4"),
	      "new decoder did not decode VOP3 mad source modifiers");
	Check(Common::ContainsStr(result.decoded_dump, "v_cubeid_f32 v110, v5, v6, v7"),
	      "new decoder did not decode old-backed V_CUBEID_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cubesc_f32 v111, v5, v6, v7"),
	      "new decoder did not decode old-backed V_CUBESC_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cubetc_f32 v112, v5, v6, v7"),
	      "new decoder did not decode old-backed V_CUBETC_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cubema_f32 v113, v5, v6, v7"),
	      "new decoder did not decode old-backed V_CUBEMA_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cubema_f32 v5, v2, v0, v6.neg"),
	      "new decoder did not decode V_CUBEMA_F32 source modifier");
	Check(Common::ContainsStr(result.decoded_dump, "v_fma_f32 v37"),
	      "new decoder did not decode VOP3 fma");
	Check(Common::ContainsStr(result.decoded_dump, "v_min3_f32 v38"),
	      "new decoder did not decode VOP3 min3");
	Check(Common::ContainsStr(result.decoded_dump, "v_max3_f32 v39"),
	      "new decoder did not decode VOP3 max3");
	Check(Common::ContainsStr(result.decoded_dump, "v_med3_f32 v40"),
	      "new decoder did not decode VOP3 med3");
	Check(Common::ContainsStr(result.decoded_dump, "v_min3_i32 v41"),
	      "new decoder did not decode VOP3 signed min3");
	Check(Common::ContainsStr(result.decoded_dump, "v_min3_u32 v42"),
	      "new decoder did not decode VOP3 unsigned min3");
	Check(Common::ContainsStr(result.decoded_dump, "v_max3_i32 v43"),
	      "new decoder did not decode VOP3 signed max3");
	Check(Common::ContainsStr(result.decoded_dump, "v_max3_u32 v44"),
	      "new decoder did not decode VOP3 unsigned max3");
	Check(Common::ContainsStr(result.decoded_dump, "v_med3_i32 v45"),
	      "new decoder did not decode VOP3 signed med3");
	Check(Common::ContainsStr(result.decoded_dump, "v_med3_u32 v46"),
	      "new decoder did not decode VOP3 unsigned med3");
	Check(Common::ContainsStr(result.decoded_dump, "v_bfe_u32 v47"),
	      "new decoder did not decode VOP3 unsigned bitfield extract");
	Check(Common::ContainsStr(result.decoded_dump, "v_bfe_i32 v48"),
	      "new decoder did not decode VOP3 signed bitfield extract");
	Check(Common::ContainsStr(result.decoded_dump, "v_bfi_b32 v49"),
	      "new decoder did not decode VOP3 bitfield insert-select");
	Check(Common::ContainsStr(result.decoded_dump, "v_alignbit_b32 v50"),
	      "new decoder did not decode VOP3 alignbit");
	Check(Common::ContainsStr(result.decoded_dump, "v_add3_u32 v51"),
	      "new decoder did not decode VOP3 add3");
	Check(Common::ContainsStr(result.decoded_dump, "v_mul_lo_u32 v52, v5, v6"),
	      "new decoder did not decode old-backed V_MUL_LO_U32");
	Check(Common::ContainsStr(result.decoded_dump, "v_mul_hi_u32 v53, v5, v6"),
	      "new decoder did not decode old-backed V_MUL_HI_U32");
	Check(Common::ContainsStr(result.decoded_dump, "v_mul_hi_i32 v114, v5, v6"),
	      "new decoder did not decode RDNA2 V_MUL_HI_I32");
	Check(Common::ContainsStr(result.decoded_dump, "v_and_or_b32 v54, v5, v6, v7"),
	      "new decoder did not decode old-backed V_AND_OR_B32");
	Check(Common::ContainsStr(result.decoded_dump, "v_or3_b32 v55, v5, v6, v7"),
	      "new decoder did not decode old-backed V_OR3_B32");
	Check(Common::ContainsStr(result.decoded_dump, "v_xor3_b32 v56, v5, v6, v7"),
	      "new decoder did not decode old-backed V_XOR3_B32");
	Check(Common::ContainsStr(result.decoded_dump, "v_lshl_add_u32 v57, v5, v6, v7"),
	      "new decoder did not decode old-backed V_LSHL_ADD_U32");
	Check(Common::ContainsStr(result.decoded_dump, "v_add_lshl_u32 v58, v5, v6, v7"),
	      "new decoder did not decode old-backed V_ADD_LSHL_U32");
	Check(Common::ContainsStr(result.decoded_dump, "v_xad_u32 v59, v5, v6, v7"),
	      "new decoder did not decode old-backed V_XAD_U32");
	Check(Common::ContainsStr(result.decoded_dump, "v_lshl_or_b32 v60, v5, v6, v7"),
	      "new decoder did not decode old-backed V_LSHL_OR_B32");
	Check(Common::ContainsStr(result.decoded_dump, "v_sad_u32 v61, v5, v6, v7"),
	      "new decoder did not decode old-backed V_SAD_U32");
	Check(Common::ContainsStr(result.decoded_dump, "v_mad_i32_i24 v62, v5, v6, v7"),
	      "new decoder did not decode old-backed V_MAD_I32_I24");
	Check(Common::ContainsStr(result.decoded_dump, "v_mad_u32_u24 v63, v5, v6, v7"),
	      "new decoder did not decode old-backed V_MAD_U32_U24");
	Check(Common::ContainsStr(result.decoded_dump, "v_mul_lo_i32 v69, v5, v6"),
	      "new decoder did not decode old-backed V_MUL_LO_I32");
	Check(Common::ContainsStr(result.decoded_dump, "v_add_i32 v70, s0, v5, v6"),
	      "new decoder did not decode old-backed V_ADD_I32");
	Check(Common::ContainsStr(result.decoded_dump, "v_sub_i32 v71, s0, v5, v6"),
	      "new decoder did not decode RDNA2 V_SUB_CO_U32");
	Check(Common::ContainsStr(result.decoded_dump, "v_subrev_i32 v72, s0, v5, v6"),
	      "new decoder did not decode old-backed V_SUBREV_I32");
	Check(Common::ContainsStr(
	          result.decoded_dump,
	          "v_max_i16 v7.sdwa(sel=4,sext=0), 0x0000ffff, v5.opsel(lo=1,hi=0,neghi=0)"),
	      "new decoder did not decode RDNA2 V_MAX_I16 literal/op_sel form");
	Check(Common::ContainsStr(result.decoded_dump,
	                          "v_min_i16 v7.sdwa(sel=5,sext=0), 2, v4.opsel(lo=1,hi=0,neghi=0)"),
	      "new decoder did not decode RDNA2 V_MIN_I16 op_sel form");
	Check(Common::ContainsStr(result.decoded_dump, "v_bfm_b32 v73, 1, 4"),
	      "new decoder did not decode old-backed V_BFM_B32");
	Check(Common::ContainsStr(result.decoded_dump, "v_readlane_b32 s26, v5, 2"),
	      "new decoder did not decode old-backed V_READLANE_B32");
	Check(Common::ContainsStr(result.decoded_dump, "v_writelane_b32 v107, v5, 2"),
	      "new decoder did not decode old-backed V_WRITELANE_B32");
	Check(Common::ContainsStr(result.decoded_dump, "v_permlane16_b32 v108, v5, 0, 0"),
	      "new decoder did not decode old-backed V_PERMLANE16_B32");
	Check(Common::ContainsStr(result.decoded_dump, "v_permlanex16_b32 v109, v5, 0, 0"),
	      "new decoder did not decode old-backed V_PERMLANEX16_B32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_pkrtz_f16_f32 v75, v6, v6"),
	      "new decoder did not decode old-backed V_CVT_PKRTZ_F16_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_pkrtz_f16_f32 v0, v0.neg, v1.neg"),
	      "new decoder did not decode V_CVT_PKRTZ_F16_F32 source modifiers");
	Check(Common::ContainsStr(result.decoded_dump, "v_ldexp_f32 v76, v6, 1"),
	      "new decoder did not decode old-backed V_LDEXP_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_ldexp_f32 v7, v9.abs, -2"),
	      "new decoder did not decode V_LDEXP_F32 source modifier");
	Check(Common::ContainsStr(result.decoded_dump, "v_ldexp_f32 v13.clamp, v6, -4"),
	      "new decoder did not decode V_LDEXP_F32 clamp modifier");
	Check(Common::ContainsStr(result.decoded_dump, "v_bcnt_u32_b32 v77, v5, v6"),
	      "new decoder did not decode old-backed VOP3 V_BCNT_U32_B32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_pknorm_i16_f32 v78, v6, v6"),
	      "new decoder did not decode old-backed V_CVT_PKNORM_I16_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_pknorm_u16_f32 v79, v6, v6"),
	      "new decoder did not decode old-backed V_CVT_PKNORM_U16_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_pk_u16_u32 v80, v5, v7"),
	      "new decoder did not decode old-backed V_CVT_PK_U16_U32");
	Check(Common::ContainsStr(result.decoded_dump, "v_mul_f32 v2, s0, s72"),
	      "new decoder did not decode old-backed V_MUL_F32 SDWA full-width form");
	Check(Common::ContainsStr(result.decoded_dump, "v_mul_f16 v9.sdwa(sel=4") &&
	          Common::ContainsStr(result.decoded_dump, "v0.sdwa(sel=4") &&
	          Common::ContainsStr(result.decoded_dump, "v1.sdwa(sel=4"),
	      "new decoder did not decode V_MUL_F16 SDWA low-half form");
	Check(Common::ContainsStr(result.decoded_dump, "v_sub_f16 v0.sdwa(sel=4") &&
	          Common::ContainsStr(result.decoded_dump, "v0.sdwa(sel=4"),
	      "new decoder did not decode V_SUB_F16 SDWA low-half form");
	Check(Common::ContainsStr(result.ir_dump, "ConvertU32ToF32 v6"),
	      "VOP1 uint-to-float conversion did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "MoveU32 v103, v5.sdwa(sel=4"),
	      "VOP1 SDWA source selector did not lower to IR metadata");
	Check(Common::ContainsStr(result.ir_dump, "ConvertF32ToF16 v105.sdwa(sel=5"),
	      "VOP1 SDWA destination selector did not lower to IR metadata");
	Check(Common::ContainsStr(result.ir_dump, "MoveU32 v106.dpp") &&
	          Common::ContainsStr(result.ir_dump, "v5.dpp"),
	      "VOP1 DPP source/destination did not lower to IR metadata");
	Check(Common::ContainsStr(result.ir_dump, "MoveRelSourceU32 v55, v12, m0"),
	      "V_MOVRELS_B32 did not lower to indexed VGPR-source IR");
	Check(Common::ContainsStr(result.ir_dump, "IAddU32 v123, v5.sdwa(sel=4"),
	      "VOP2 SDWA did not lower first source metadata to IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseAndU32 v124, v5.sdwa(sel=4"),
	      "VOP2 SDWA bitwise op did not lower first source metadata to IR");
	Check(Common::ContainsStr(result.ir_dump, "v6.sdwa(sel=5"),
	      "VOP2 SDWA bitwise op did not lower second source metadata to IR");
	Check(Common::ContainsStr(result.ir_dump, "SelectMaskF32Bits v47, vcc_lo, v53.neg, v53") &&
	          Common::ContainsStr(result.ir_dump, "v53.neg"),
	      "V_CNDMASK_B32 SDWA source modifier did not lower to float-bit select IR");
	Check(Common::ContainsStr(result.ir_dump, "FAddF32 v125.dpp") &&
	          Common::ContainsStr(result.ir_dump, "v5.dpp"),
	      "VOP2 DPP source/destination did not lower to IR metadata");
	Check(Common::ContainsStr(result.ir_dump, "FMulF32 v2, s0, s72"),
	      "V_MUL_F32 SDWA full-width form did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "MulF16 v9.sdwa(sel=4") &&
	          Common::ContainsStr(result.ir_dump, "v0.sdwa(sel=4") &&
	          Common::ContainsStr(result.ir_dump, "v1.sdwa(sel=4"),
	      "V_MUL_F16 SDWA low-half form did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "SubF16 v0.sdwa(sel=4") &&
	          Common::ContainsStr(result.ir_dump, "v0.sdwa(sel=4"),
	      "V_SUB_F16 SDWA low-half form did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "ConvertF32ToU32 v7"),
	      "VOP1 float-to-uint conversion did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "ConvertI32ToF32 v9"),
	      "VOP1 signed int-to-float conversion did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "ConvertF32ToI32 v10"),
	      "VOP1 float-to-signed-int conversion did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "ConvertF32ToF16 v99, v6"),
	      "V_CVT_F16_F32 did not lower to shared half-pack IR");
	Check(Common::ContainsStr(result.ir_dump, "ConvertF16ToF32 v100, v99"),
	      "V_CVT_F32_F16 did not lower to shared half-unpack IR");
	Check(Common::ContainsStr(result.ir_dump, "ConvertF16ToF32 v8, v2.sdwa(sel=5") &&
	          Common::ContainsStr(result.ir_dump, "v2.sdwa(sel=5,sext=0).abs"),
	      "V_CVT_F32_F16 SDWA source selector modifier did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "ReadFirstLaneU32 s24, v5"),
	      "V_READFIRSTLANE_B32 did not lower to subgroup IR");
	Check(Common::ContainsStr(result.ir_dump, "ConvertFloorF32ToI32 v64, v6"),
	      "V_CVT_FLR_I32_F32 did not lower to shared IR");
	Check(Common::ContainsStr(result.ir_dump, "ConvertI4ToOffsetF32 v81, v5"),
	      "V_CVT_OFF_F32_I4 did not lower to shared offset-convert IR");
	Check(Common::ContainsStr(result.ir_dump, "ConvertByteU32ToF32 v65, v5, 0x00000000"),
	      "V_CVT_F32_UBYTE0 did not lower to shared byte-convert IR");
	Check(Common::ContainsStr(result.ir_dump, "ConvertByteU32ToF32 v66, v5, 0x00000001"),
	      "V_CVT_F32_UBYTE1 did not lower to shared byte-convert IR");
	Check(Common::ContainsStr(result.ir_dump, "ConvertByteU32ToF32 v67, v5, 0x00000002"),
	      "V_CVT_F32_UBYTE2 did not lower to shared byte-convert IR");
	Check(Common::ContainsStr(result.ir_dump, "ConvertByteU32ToF32 v68, v5, 0x00000003"),
	      "V_CVT_F32_UBYTE3 did not lower to shared byte-convert IR");
	Check(Common::ContainsStr(result.ir_dump, "RcpF32 v11"), "VOP1 reciprocal did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "FractF32 v12"), "VOP1 fract did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "TruncF32 v13"), "VOP1 trunc did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "CeilF32 v14"), "VOP1 ceil did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "RoundEvenF32 v15"),
	      "VOP1 round-even did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "FloorF32 v16"), "VOP1 floor did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "Exp2F32 v17"), "VOP1 exp2 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "Log2F32 v18"), "VOP1 log2 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "InverseSqrtF32 v19"),
	      "VOP1 inverse-sqrt did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "SqrtF32 v20"), "VOP1 sqrt did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "SinF32 v21"), "VOP1 sin did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "CosF32 v22"), "VOP1 cos did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseNotU32 v27"), "VOP1 not did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "BitReverseU32 v28"),
	      "VOP1 bit reverse did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "FindMsbFromHighU32 v31"),
	      "VOP1 find-first-bit-high did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "FindLsbU32 v32"),
	      "VOP1 find-first-bit-low did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseXnorU32 v83, v5, v6"),
	      "V_XNOR_B32 did not lower to shared xnor IR");
	Check(Common::ContainsStr(result.ir_dump, "MaskedBitCountLowU32 v84, v5, v6"),
	      "V_MBCNT_LO_U32_B32 did not lower to shared masked bit-count IR");
	Check(Common::ContainsStr(result.ir_dump, "MaskedBitCountHighU32 v85, v5, v6"),
	      "V_MBCNT_HI_U32_B32 did not lower to shared masked bit-count IR");
	Check(Common::ContainsStr(result.ir_dump, "FMadF32 v89, v6, v6, v89"),
	      "V_MAC_F32 did not lower with the destination register as addend");
	Check(Common::ContainsStr(result.ir_dump, "FMadF32 v90, v6, 0x3f800000, v5"),
	      "V_MADMK_F32 did not lower with the literal in source 1");
	Check(Common::ContainsStr(result.ir_dump, "FMadF32 v91, v6, v5, 0x40000000"),
	      "V_MADAK_F32 did not lower with the literal in source 2");
	Check(Common::ContainsStr(result.ir_dump, "FMadF32 v92, v6, v6, v92"),
	      "alternate V_MAC_F32 did not lower with the destination register as addend");
	Check(Common::ContainsStr(result.ir_dump, "FMadF32 v93, v6, 0x3f000000, v5"),
	      "alternate V_MADMK_F32 did not lower with the literal in source 1");
	Check(Common::ContainsStr(result.ir_dump, "FMadF32 v94, v6, v5, 0x40400000"),
	      "alternate V_MADAK_F32 did not lower with the literal in source 2");
	Check(Common::ContainsStr(result.ir_dump, "PackF32ToF16Rtz v95, v6, v6"),
	      "native V_CVT_PKRTZ_F16_F32 did not lower to shared pack IR");
	Check(Common::ContainsStr(result.ir_dump, "Dot2AccF32F16 v102, v95, v95, v102"),
	      "V_DOT2C_F32_F16 did not lower to explicit dot-accumulate IR");
	Check(Common::ContainsStr(result.ir_dump, "IAddCarryU32 v97, vcc_lo, v5, v6, vcc_lo"),
	      "V_ADD_CO_U32 did not lower to carry-add IR");
	Check(Common::ContainsStr(result.ir_dump, "FAddF32 v35"),
	      "VOP3-encoded VOP2 float add did not lower through shared IR");
	Check(Common::ContainsStr(result.ir_dump, "UMulU24U32 v36"),
	      "VOP3-encoded VOP2 24-bit multiply did not lower through shared IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseXnorU32 v86, v5, v6"),
	      "VOP3-encoded V_XNOR_B32 did not lower through shared IR");
	Check(Common::ContainsStr(result.ir_dump, "FMadF32 v96, v6, v6, v96"),
	      "VOP3-encoded V_MAC_F32 did not lower with the destination register as addend");
	Check(Common::ContainsStr(result.ir_dump, "IAddCarryU32 v98, s30, v5, v6, vcc_lo"),
	      "VOP3-encoded V_ADD_CO_U32 did not lower to carry-add IR");
	Check(Common::ContainsStr(result.ir_dump, "MaskedBitCountLowU32 v87, v5, v6"),
	      "VOP3-encoded V_MBCNT_LO_U32_B32 did not lower through shared IR");
	Check(Common::ContainsStr(result.ir_dump, "MaskedBitCountHighU32 v88, v5, v6"),
	      "VOP3-encoded V_MBCNT_HI_U32_B32 did not lower through shared IR");
	Check(Common::ContainsStr(result.ir_dump, "MoveU32 v23"),
	      "VOP3-encoded VOP1 move did not lower through shared IR");
	Check(Common::ContainsStr(result.ir_dump, "ReadFirstLaneU32 s25, v5"),
	      "VOP3-encoded V_READFIRSTLANE_B32 did not lower through shared IR");
	Check(Common::ContainsStr(result.ir_dump, "ReadLaneU32 s26, v5, 0x00000002"),
	      "V_READLANE_B32 did not lower to subgroup lane IR");
	Check(Common::ContainsStr(result.ir_dump, "WriteLaneU32 v107, v5, 0x00000002"),
	      "V_WRITELANE_B32 did not lower to subgroup lane IR");
	Check(Common::ContainsStr(result.ir_dump, "Permlane16B32 v108, v5, 0x00000000, 0x00000000"),
	      "V_PERMLANE16_B32 did not lower to subgroup perm-lane IR");
	Check(Common::ContainsStr(result.ir_dump, "Permlanex16B32 v109, v5, 0x00000000, 0x00000000"),
	      "V_PERMLANEX16_B32 did not lower to subgroup perm-lane IR");
	Check(Common::ContainsStr(result.ir_dump, "ConvertI32ToF32 v24"),
	      "VOP3-encoded VOP1 signed conversion did not lower through shared IR");
	Check(Common::ContainsStr(result.ir_dump, "ConvertFloorF32ToI32 v74, v6"),
	      "VOP3-encoded V_CVT_FLR_I32_F32 did not lower through shared IR");
	Check(Common::ContainsStr(result.ir_dump, "ConvertI4ToOffsetF32 v82, v5"),
	      "VOP3-encoded V_CVT_OFF_F32_I4 did not lower through shared IR");
	Check(Common::ContainsStr(result.ir_dump, "FractF32 v25"),
	      "VOP3-encoded VOP1 fract did not lower through shared IR");
	Check(Common::ContainsStr(result.ir_dump, "RcpF32 v26"),
	      "VOP3-encoded VOP1 reciprocal did not lower through shared IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseNotU32 v29"),
	      "VOP3-encoded VOP1 not did not lower through shared IR");
	Check(Common::ContainsStr(result.ir_dump, "BitReverseU32 v30"),
	      "VOP3-encoded VOP1 bit reverse did not lower through shared IR");
	Check(Common::ContainsStr(result.ir_dump, "FindMsbFromHighU32 v33"),
	      "VOP3-encoded VOP1 find-first-bit-high did not lower through shared IR");
	Check(Common::ContainsStr(result.ir_dump, "FindLsbU32 v34"),
	      "VOP3-encoded VOP1 find-first-bit-low did not lower through shared IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareGtF32"),
	      "VOPC float compare did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareMaskGtF32 exec_lo"),
	      "VOPC float compare-and-mask did not lower to exec mask IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareMaskGtU32 exec_lo"),
	      "VOPC uint compare-and-mask did not lower to exec mask IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareFalse vcc_lo, v6, v6"),
	      "VOPC false compare did not lower to shared IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareTrue vcc_lo, v6, v6"),
	      "VOPC true compare did not lower to shared IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareOrderedF32 vcc_lo, v6, v6"),
	      "VOPC ordered compare did not lower to shared IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareUnorderedF32 vcc_lo, v6, v6"),
	      "VOPC unordered compare did not lower to shared IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareUnordLtF32 vcc_lo, v6, v6"),
	      "VOPC unordered-less compare did not lower to shared IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareUnordEqF32 vcc_lo, v6, v6"),
	      "VOPC unordered-equal compare did not lower to shared IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareUnordLeF32 vcc_lo, v6, v6"),
	      "VOPC unordered-less-equal compare did not lower to shared IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareUnordGtF32 vcc_lo, v6, v6"),
	      "VOPC unordered-greater compare did not lower to shared IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareUnordNeF32 vcc_lo, v6, v6"),
	      "VOPC unordered-not-equal compare did not lower to shared IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareUnordNeF32 s0, 0x3f000000, v1"),
	      "VOPC SDWA unordered-not-equal compare did not lower to scalar-destination IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareUnordGeF32 vcc_lo, v6, v6"),
	      "VOPC unordered-greater-equal compare did not lower to shared IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareMaskUnordLtF32 exec_lo, v6, v6"),
	      "VOPC unordered-less compare-and-mask did not lower to shared IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareMaskUnordEqF32 exec_lo, v6, v6"),
	      "VOPC unordered-equal compare-and-mask did not lower to shared IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareMaskUnordLeF32 exec_lo, v6, v6"),
	      "VOPC unordered-less-equal compare-and-mask did not lower to shared IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareMaskUnordGtF32 exec_lo, v6, v6"),
	      "VOPC unordered-greater compare-and-mask did not lower to shared IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareMaskUnordNeF32 exec_lo, v6, v6"),
	      "VOPC unordered-not-equal compare-and-mask did not lower to shared IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareMaskUnordGeF32 exec_lo, v6, v6"),
	      "VOPC unordered-greater-equal compare-and-mask did not lower to shared IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareFalse vcc_lo, v5, v5"),
	      "VOPC integer false compare did not lower to shared IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareTrue vcc_lo, v5, v5"),
	      "VOPC integer true compare did not lower to shared IR");
	Check(Common::ContainsStr(result.ir_dump, "FMadF32 v8"), "VOP3 mad did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "FMadF32 v4, v1.neg, v9, s4"),
	      "VOP3 mad source modifiers did not lower to IR metadata");
	Check(Common::ContainsStr(result.ir_dump, "CubeIdF32 v110, v5, v6, v7"),
	      "V_CUBEID_F32 did not lower to cube IR");
	Check(Common::ContainsStr(result.ir_dump, "CubeScF32 v111, v5, v6, v7"),
	      "V_CUBESC_F32 did not lower to cube IR");
	Check(Common::ContainsStr(result.ir_dump, "CubeTcF32 v112, v5, v6, v7"),
	      "V_CUBETC_F32 did not lower to cube IR");
	Check(Common::ContainsStr(result.ir_dump, "CubeMaF32 v113, v5, v6, v7"),
	      "V_CUBEMA_F32 did not lower to cube IR");
	Check(Common::ContainsStr(result.ir_dump, "CubeMaF32 v5, v2, v0, v6.neg"),
	      "V_CUBEMA_F32 source modifier did not lower to cube IR");
	Check(Common::ContainsStr(result.ir_dump, "FMadF32 v37"), "VOP3 fma did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "FMin3F32 v38"), "VOP3 min3 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "FMax3F32 v39"), "VOP3 max3 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "FMed3F32 v40"), "VOP3 med3 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "IMin3I32 v41"),
	      "VOP3 signed min3 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "UMin3U32 v42"),
	      "VOP3 unsigned min3 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "IMax3I32 v43"),
	      "VOP3 signed max3 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "UMax3U32 v44"),
	      "VOP3 unsigned max3 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "IMed3I32 v45"),
	      "VOP3 signed med3 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "UMed3U32 v46"),
	      "VOP3 unsigned med3 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "BitFieldExtract3U32 v47"),
	      "VOP3 unsigned bitfield extract did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "BitFieldExtract3I32 v48"),
	      "VOP3 signed bitfield extract did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "BitFieldInsertSelectU32 v49"),
	      "VOP3 bitfield insert-select did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AlignBitU32 v50"),
	      "VOP3 alignbit did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "IAdd3U32 v51"), "VOP3 add3 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "IMulU32 v52, v5, v6"),
	      "V_MUL_LO_U32 did not lower to multiply IR");
	Check(Common::ContainsStr(result.ir_dump, "UMulHighU32 v53, v5, v6"),
	      "V_MUL_HI_U32 did not lower to high-multiply IR");
	Check(Common::ContainsStr(result.ir_dump, "SMulHighI32 v114, v5, v6"),
	      "V_MUL_HI_I32 did not lower to signed high-multiply IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseAndOrU32 v54, v5, v6, v7"),
	      "V_AND_OR_B32 did not lower to ternary bitwise IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseOr3U32 v55, v5, v6, v7"),
	      "V_OR3_B32 did not lower to ternary bitwise IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseXor3U32 v56, v5, v6, v7"),
	      "V_XOR3_B32 did not lower to ternary bitwise IR");
	Check(Common::ContainsStr(result.ir_dump, "ShiftLeftAddU32 v57, v5, v6, v7"),
	      "V_LSHL_ADD_U32 did not lower to shared shift-left-add IR");
	Check(Common::ContainsStr(result.ir_dump, "AddShiftLeftU32 v58, v5, v6, v7"),
	      "V_ADD_LSHL_U32 did not lower to add-shift-left IR");
	Check(Common::ContainsStr(result.ir_dump, "XorAddU32 v59, v5, v6, v7"),
	      "V_XAD_U32 did not lower to xor-add IR");
	Check(Common::ContainsStr(result.ir_dump, "ShiftLeftOrU32 v60, v5, v6, v7"),
	      "V_LSHL_OR_B32 did not lower to shift-left-or IR");
	Check(Common::ContainsStr(result.ir_dump, "SadU32 v61, v5, v6, v7"),
	      "V_SAD_U32 did not lower to sad IR");
	Check(Common::ContainsStr(result.ir_dump, "IMadI24U32 v62, v5, v6, v7"),
	      "V_MAD_I32_I24 did not lower to signed 24-bit mad IR");
	Check(Common::ContainsStr(result.ir_dump, "UMadU24U32 v63, v5, v6, v7"),
	      "V_MAD_U32_U24 did not lower to unsigned 24-bit mad IR");
	Check(Common::ContainsStr(result.ir_dump, "IMulU32 v69, v5, v6"),
	      "V_MUL_LO_I32 did not lower to multiply IR");
	Check(Common::ContainsStr(result.ir_dump, "IAddCarryU32 v70, s0, v5, v6, 0x00000000"),
	      "V_ADD_I32 did not lower to carry-out add IR");
	Check(Common::ContainsStr(result.ir_dump, "ISubBorrowU32 v71, s0, v5, v6"),
	      "V_SUB_I32 did not lower to borrow-out subtract IR");
	Check(Common::ContainsStr(result.ir_dump, "ISubBorrowU32 v72, s0, v6, v5"),
	      "V_SUBREV_I32 did not reverse source order in borrow-out IR");
	Check(Common::ContainsStr(
	          result.ir_dump,
	          "IMaxI16 v7.sdwa(sel=4,sext=0), 0x0000ffff, v5.opsel(lo=1,hi=0,neghi=0)"),
	      "V_MAX_I16 did not lower to signed halfword max IR");
	Check(Common::ContainsStr(
	          result.ir_dump,
	          "IMinI16 v7.sdwa(sel=5,sext=0), 0x00000002, v4.opsel(lo=1,hi=0,neghi=0)"),
	      "V_MIN_I16 did not lower to signed halfword min IR");
	Check(Common::ContainsStr(result.ir_dump, "BitFieldMaskU32 v73, 0x00000001, 0x00000004"),
	      "V_BFM_B32 did not lower to shared bitfield-mask IR");
	Check(Common::ContainsStr(result.ir_dump, "PackF32ToF16Rtz v75, v6, v6"),
	      "V_CVT_PKRTZ_F16_F32 did not lower to shared pack IR");
	Check(Common::ContainsStr(result.ir_dump, "PackF32ToF16Rtz v0, v0.neg, v1.neg"),
	      "V_CVT_PKRTZ_F16_F32 source modifiers did not lower to shared pack IR");
	Check(Common::ContainsStr(result.ir_dump, "LdexpF32 v76, v6, 0x00000001"),
	      "V_LDEXP_F32 did not lower to ldexp IR");
	Check(Common::ContainsStr(result.ir_dump, "LdexpF32 v7, v9.abs, 0xfffffffe"),
	      "V_LDEXP_F32 source modifier did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "LdexpF32 v13.clamp, v6, 0xfffffffc"),
	      "V_LDEXP_F32 clamp modifier did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "BitCountAddU32 v77, v5, v6"),
	      "VOP3 V_BCNT_U32_B32 did not lower to bit-count-add IR");
	Check(Common::ContainsStr(result.ir_dump, "PackSnorm2x16F32 v78, v6, v6"),
	      "V_CVT_PKNORM_I16_F32 did not lower to shared pack IR");
	Check(Common::ContainsStr(result.ir_dump, "PackUnorm2x16F32 v79, v6, v6"),
	      "V_CVT_PKNORM_U16_F32 did not lower to shared pack IR");
	Check(Common::ContainsStr(result.ir_dump, "PackU16U32 v80, v5, v7"),
	      "V_CVT_PK_U16_U32 did not lower to shared pack IR");
	Check(SpirvContainsOpcode(result.spirv, 112), "SPIR-V binary does not contain OpConvertUToF");
	Check(SpirvContainsOpcode(result.spirv, 109), "SPIR-V binary does not contain OpConvertFToU");
	Check(SpirvContainsOpcode(result.spirv, 111), "SPIR-V binary does not contain OpConvertSToF");
	Check(SpirvContainsOpcode(result.spirv, 110), "SPIR-V binary does not contain OpConvertFToS");
	Check(SpirvContainsOpcode(result.spirv, 136), "SPIR-V binary does not contain OpFDiv");
	Check(SpirvContainsOpcode(result.spirv, 127), "SPIR-V binary does not contain OpFNegate");
	Check(SpirvContainsOpcode(result.spirv, 12), "SPIR-V binary does not contain OpExtInst");
	Check(SpirvContainsExtInst(result.spirv, 58),
	      "SPIR-V binary does not contain GLSL.std.450 PackHalf2x16");
	Check(SpirvContainsExtInst(result.spirv, 62),
	      "SPIR-V binary does not contain GLSL.std.450 UnpackHalf2x16");
	Check(SpirvContainsExtInst(result.spirv, 50),
	      "SPIR-V binary does not contain GLSL.std.450 Fma");
	Check(SpirvContainsExtInst(result.spirv, 43),
	      "SPIR-V binary does not contain GLSL.std.450 FClamp");
	Check(SpirvContainsExtInst(result.spirv, 53),
	      "SPIR-V binary does not contain GLSL.std.450 Ldexp");
	Check(SpirvContainsOpcode(result.spirv, 128), "SPIR-V binary does not contain OpIAdd");
	Check(SpirvContainsOpcode(result.spirv, 149), "SPIR-V binary does not contain OpIAddCarry");
	Check(SpirvContainsOpcode(result.spirv, 130), "SPIR-V binary does not contain OpISub");
	Check(SpirvContainsOpcode(result.spirv, 132), "SPIR-V binary does not contain OpIMul");
	Check(SpirvContainsOpcode(result.spirv, 171), "SPIR-V binary does not contain OpINotEqual");
	Check(SpirvContainsOpcode(result.spirv, 200), "SPIR-V binary does not contain OpNot");
	Check(SpirvContainsOpcode(result.spirv, 204), "SPIR-V binary does not contain OpBitReverse");
	Check(SpirvContainsOpcode(result.spirv, 205), "SPIR-V binary does not contain OpBitCount");
	Check(SpirvContainsOpcode(result.spirv, 166), "SPIR-V binary does not contain OpLogicalOr");
	Check(SpirvContainsOpcode(result.spirv, 167), "SPIR-V binary does not contain OpLogicalAnd");
	Check(SpirvContainsOpcode(result.spirv, 186),
	      "SPIR-V binary does not contain OpFOrdGreaterThan");
	Check(SpirvContainsOpcode(result.spirv, 181), "SPIR-V binary does not contain OpFUnordEqual");
	Check(SpirvContainsOpcode(result.spirv, 183),
	      "SPIR-V binary does not contain OpFUnordNotEqual");
	Check(SpirvContainsOpcode(result.spirv, 185),
	      "SPIR-V binary does not contain OpFUnordLessThan");
	Check(SpirvContainsOpcode(result.spirv, 187),
	      "SPIR-V binary does not contain OpFUnordGreaterThan");
	Check(SpirvContainsOpcode(result.spirv, 189),
	      "SPIR-V binary does not contain OpFUnordLessThanEqual");
	Check(SpirvContainsOpcode(result.spirv, 191),
	      "SPIR-V binary does not contain OpFUnordGreaterThanEqual");
	Check(SpirvContainsOpcode(result.spirv, 190),
	      "SPIR-V binary does not contain OpFOrdGreaterThanEqual");
	Check(SpirvContainsOpcode(result.spirv, 184), "SPIR-V binary does not contain OpFOrdLessThan");
	Check(SpirvContainsOpcode(result.spirv, 173), "SPIR-V binary does not contain OpSGreaterThan");
	Check(SpirvContainsOpcode(result.spirv, 174),
	      "SPIR-V binary does not contain OpUGreaterThanEqual");
	Check(SpirvContainsOpcode(result.spirv, 177), "SPIR-V binary does not contain OpSLessThan");
	Check(SpirvContainsOpcode(result.spirv, 202),
	      "SPIR-V binary does not contain OpBitFieldSExtract");
	Check(SpirvContainsOpcode(result.spirv, 203),
	      "SPIR-V binary does not contain OpBitFieldUExtract");
	Check(SpirvContainsOpcode(result.spirv, 194),
	      "SPIR-V binary does not contain OpShiftRightLogical");
	Check(SpirvContainsOpcode(result.spirv, 61),
	      "SPIR-V binary does not contain OpLoad for cmpx old exec mask");
	Check(SpirvContainsOpcode(result.spirv, 133), "SPIR-V binary does not contain OpFMul");
	Check(SpirvContainsOpcode(result.spirv, 129), "SPIR-V binary does not contain OpFAdd");
	Check(SpirvContainsOpcode(result.spirv, 151), "SPIR-V binary does not contain OpUMulExtended");
	Check(SpirvContainsOpcode(result.spirv, 152), "SPIR-V binary does not contain OpSMulExtended");
	Check(SpirvContainsOpcode(result.spirv, 196),
	      "SPIR-V binary does not contain OpShiftLeftLogical");
	Check(SpirvContainsOpcode(result.spirv, 197), "SPIR-V binary does not contain OpBitwiseOr");
	Check(SpirvContainsOpcode(result.spirv, 198), "SPIR-V binary does not contain OpBitwiseXor");
	Check(SpirvContainsOpcode(result.spirv, 199), "SPIR-V binary does not contain OpBitwiseAnd");
	Check(SpirvContainsOpcode(result.spirv, 80),
	      "SPIR-V binary does not contain OpCompositeConstruct");
	Check(SpirvContainsOpcode(result.spirv, 81),
	      "SPIR-V binary does not contain OpCompositeExtract");
	Check(SpirvContainsOpcode(result.spirv, 339),
	      "SPIR-V binary does not contain OpGroupNonUniformBallot");
	Check(SpirvContainsOpcode(result.spirv, 343),
	      "SPIR-V binary does not contain OpGroupNonUniformBallotFindLSB");
	Check(SpirvContainsOpcode(result.spirv, 345),
	      "SPIR-V binary does not contain OpGroupNonUniformShuffle");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerExpandedAluBatch() {
	const uint32_t shader[] = {
	    EncodeSopk(0x00, 9, 7),          // s_movk_i32 s9, 7
	    EncodeSopk(0x0f, 9, 2),          // s_add_i32 s9, s9, 2
	    EncodeSopk(0x10, 9, 3),          // s_mulk_i32 s9, s9, 3
	    EncodeSop2(0x07, 10, 9, 128),    // s_min_u32 s10, s9, 0
	    EncodeSop2(0x09, 11, 10, 130),   // s_max_u32 s11, s10, 2
	    EncodeSop2(0x26, 12, 11, 130),   // s_mul_i32 s12, s11, 2
	    EncodeVop2(0x05, 1, 242, 0),     // v_subrev_f32 v1, 1.0, v0
	    EncodeVop2(0x0f, 2, 1 + 256, 0), // v_min_f32 v2, v1, v0
	    EncodeVop2(0x10, 3, 1 + 256, 2), // v_max_f32 v3, v1, v2
	    EncodeVop2(0x27, 4, 130, 3),     // v_subrev_nc_u32 v4, 2, v3
	    EncodeVop2(0x16, 5, 129, 4),     // v_lshrrev_b32 v5, 1, v4
	    EncodeVop2(0x1a, 6, 129, 5),     // v_lshlrev_b32 v6, 1, v5
	    EncodeVop2(0x13, 7, 6 + 256, 5), // v_min_u32 v7, v6, v5
	    EncodeVop2(0x14, 8, 7 + 256, 6), // v_max_u32 v8, v7, v6
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "s_min_u32"),
	      "new decoder did not decode S_MIN_U32");
	Check(Common::ContainsStr(result.decoded_dump, "s_mulk_i32"),
	      "new decoder did not decode S_MULK_I32");
	Check(Common::ContainsStr(result.decoded_dump, "v_subrev_f32"),
	      "new decoder did not decode V_SUBREV_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_lshlrev_b32"),
	      "new decoder did not decode V_LSHLREV_B32");
	Check(Common::ContainsStr(result.ir_dump, "IMulU32 s9, s9, 0x00000003"),
	      "SOPK multiply did not lower to self-multiply IR");
	Check(Common::ContainsStr(result.ir_dump, "UMinU32 s10"), "S_MIN_U32 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "UMaxU32 s11"), "S_MAX_U32 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "FSubF32 v1, v0, 0x3f800000"),
	      "V_SUBREV_F32 did not reverse source order in IR");
	Check(Common::ContainsStr(result.ir_dump, "FMinF32 v2"), "V_MIN_F32 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "FMaxF32 v3"), "V_MAX_F32 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "ISubU32 v4, v3, 0x00000002"),
	      "V_SUBREV_NC_U32 did not reverse source order in IR");
	Check(Common::ContainsStr(result.ir_dump, "ShiftLeftLogicalU32 v6, v5, 0x00000001"),
	      "V_LSHLREV_B32 did not reverse source order in IR");
	Check(Common::ContainsStr(result.ir_dump, "UMinU32 v7"), "V_MIN_U32 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "UMaxU32 v8"), "V_MAX_U32 did not lower to IR");
	Check(SpirvContainsOpcode(result.spirv, 132), "SPIR-V binary does not contain OpIMul");
	Check(SpirvContainsOpcode(result.spirv, 169), "SPIR-V binary does not contain OpSelect");
	Check(SpirvContainsOpcode(result.spirv, 172), "SPIR-V binary does not contain OpUGreaterThan");
	Check(SpirvContainsOpcode(result.spirv, 176), "SPIR-V binary does not contain OpULessThan");
	Check(SpirvContainsOpcode(result.spirv, 184), "SPIR-V binary does not contain OpFOrdLessThan");
	Check(SpirvContainsOpcode(result.spirv, 186),
	      "SPIR-V binary does not contain OpFOrdGreaterThan");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerVop3pPackedF16() {
	const uint32_t shader[] = {
	    EncodeVop3pWord0(0x0f, 114, 0, 7),
	    EncodeVop3pWord1(5 + 256, 6 + 256, 0, 7), // v_pk_add_f16 v114, v5, v6
	    EncodeVop3pWord0(0x10, 115, 0, 7),
	    EncodeVop3pWord1(5 + 256, 6 + 256, 0, 7), // v_pk_mul_f16 v115, v5, v6
	    EncodeVop3pWord0(0x11, 116, 0, 7),
	    EncodeVop3pWord1(5 + 256, 6 + 256, 0, 7), // v_pk_min_f16 v116, v5, v6
	    EncodeVop3pWord0(0x12, 117, 0, 7),
	    EncodeVop3pWord1(5 + 256, 6 + 256, 0, 7), // v_pk_max_f16 v117, v5, v6
	    EncodeVop3pWord0(0x0e, 118, 0, 7, 2),
	    EncodeVop3pWord1(5 + 256, 6 + 256, 7 + 256, 7, 1),
	    EncodeVop3pWord0(0x20, 119),
	    EncodeVop3pWord1(6 + 256, 6 + 256, 6 + 256), // v_fma_f32 via VOP3P
	    0xcc204044u,
	    0x1a022110u, // v_fma_mix_f32 v68, v16.lo, v16.lo, 0.lo
	    EncodeVop3pWord0(0x21, 120, 0, 7),
	    EncodeVop3pWord1(5 + 256, 6 + 256, 7 + 256, 7), // v_mad_mixlo_f16
	    EncodeVop3pWord0(0x22, 121, 0, 7, 1),
	    EncodeVop3pWord1(5 + 256, 6 + 256, 7 + 256, 7, 2), // v_mad_mixhi_f16
	    EncodeVop3Word0(0x34b, 122, 0x0f),
	    EncodeVop3Word1(5 + 256, 6 + 256, 7 + 256), // native v_fma_f16
	    EncodeVop3pWord0(0x22, 126, 0, 1, 0, true),
	    EncodeVop3pWord1(242, 243, 3 + 256, 1), // clamped v_mad_mixhi_f16
	    EncodeVop3Word0(0x34b, 127, 0x8, 0, true),
	    EncodeVop3Word1(240, 4 + 256, 241), // clamped native high-half v_fma_f16
	    0xd711001au,
	    0x0002170au, // v_pack_b32_f16 v26, v10, v11
	    0xd711182bu,
	    0x0002170au, // v_pack_b32_f16 v43, v10.hi, v11.hi
	    0x78765714u, // v_pk_fmac_f16 from boot shader 0x0000001c00f5c000
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "v_pk_add_f16 v114"),
	      "new decoder did not decode old-backed V_PK_ADD_F16");
	Check(Common::ContainsStr(result.decoded_dump, "v_pk_mul_f16 v115"),
	      "new decoder did not decode old-backed V_PK_MUL_F16");
	Check(Common::ContainsStr(result.decoded_dump, "v_pk_min_f16 v116"),
	      "new decoder did not decode old-backed V_PK_MIN_F16");
	Check(Common::ContainsStr(result.decoded_dump, "v_pk_max_f16 v117"),
	      "new decoder did not decode old-backed V_PK_MAX_F16");
	Check(Common::ContainsStr(result.decoded_dump, "v_pk_fma_f16 v118"),
	      "new decoder did not decode old-backed V_PK_FMA_F16");
	Check(Common::ContainsStr(result.decoded_dump, "v_fma_f32 v119"),
	      "new decoder did not decode old-backed VOP3P V_FMA_F32");
	Check(Common::ContainsStr(result.decoded_dump, "v_fma_f32 v68, v16.opsel(lo=0,hi=1,neghi=0)"),
	      "new decoder did not decode VOP3P V_FMA_MIX_F32 source selectors");
	Check(Common::ContainsStr(result.decoded_dump, "v_mad_mixlo_f16 v120"),
	      "new decoder did not decode old-backed V_MAD_MIXLO_F16");
	Check(Common::ContainsStr(result.decoded_dump, "v_mad_mixhi_f16 v121"),
	      "new decoder did not decode old-backed V_MAD_MIXHI_F16");
	Check(Common::ContainsStr(result.decoded_dump, "v_fma_f16 v122"),
	      "new decoder did not decode native VOP3 V_FMA_F16");
	Check(Common::ContainsStr(result.decoded_dump, "v_mad_mixhi_f16 v126.sdwa(sel=5"),
	      "new decoder did not decode clamped V_MAD_MIXHI_F16 high-half destination");
	Check(Common::ContainsStr(result.decoded_dump, "v_fma_f16 v127.sdwa(sel=5"),
	      "new decoder did not decode clamped native high-half V_FMA_F16");
	Check(Common::ContainsStr(result.decoded_dump, "v_pack_b32_f16 v26"),
	      "new decoder did not decode native V_PACK_B32_F16");
	Check(Common::ContainsStr(result.decoded_dump, "v_pack_b32_f16 v43") &&
	          Common::ContainsStr(result.decoded_dump, "v10.opsel(lo=1"),
	      "new decoder did not decode V_PACK_B32_F16 source lane selectors");
	Check(Common::ContainsStr(result.decoded_dump, "v_pk_fmac_f16 v59"),
	      "new decoder did not decode VOP2 V_PK_FMAC_F16");
	Check(Common::ContainsStr(result.decoded_dump, "v6.opsel(lo=0,hi=1,neghi=1)"),
	      "VOP3P high-lane source modifier was not dumped");
	Check(Common::ContainsStr(result.decoded_dump, "v120.sdwa(sel=4"),
	      "V_MAD_MIXLO_F16 did not expose low-half destination merge");
	Check(Common::ContainsStr(result.decoded_dump, "v121.sdwa(sel=5"),
	      "V_MAD_MIXHI_F16 did not expose high-half destination merge");
	Check(Common::ContainsStr(result.ir_dump, "PackedAddF16 v114"),
	      "V_PK_ADD_F16 did not lower to packed f16 IR");
	Check(Common::ContainsStr(result.ir_dump, "PackedMulF16 v115"),
	      "V_PK_MUL_F16 did not lower to packed f16 IR");
	Check(Common::ContainsStr(result.ir_dump, "PackedMinF16 v116"),
	      "V_PK_MIN_F16 did not lower to packed f16 IR");
	Check(Common::ContainsStr(result.ir_dump, "PackedMaxF16 v117"),
	      "V_PK_MAX_F16 did not lower to packed f16 IR");
	Check(Common::ContainsStr(result.ir_dump, "PackedFmaF16 v118"),
	      "V_PK_FMA_F16 did not lower to packed f16 IR");
	Check(Common::ContainsStr(result.ir_dump, "FMadF32 v119"),
	      "VOP3P V_FMA_F32 did not lower through shared fma IR");
	Check(Common::ContainsStr(result.ir_dump, "FMadF32 v68, v16.opsel(lo=0,hi=1,neghi=0)"),
	      "VOP3P V_FMA_MIX_F32 source selectors did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "MadMixF16 v120.sdwa(sel=4"),
	      "V_MAD_MIXLO_F16 did not lower to shared mad-mix IR");
	Check(Common::ContainsStr(result.ir_dump, "MadMixF16 v121.sdwa(sel=5"),
	      "V_MAD_MIXHI_F16 did not lower to shared mad-mix IR");
	Check(Common::ContainsStr(result.ir_dump, "FmaF16 v122"),
	      "native VOP3 V_FMA_F16 did not lower to native f16 fma IR");
	Check(Common::ContainsStr(result.ir_dump, "MadMixF16 v126.sdwa(sel=5"),
	      "clamped V_MAD_MIXHI_F16 did not lower to shared mad-mix IR");
	Check(Common::ContainsStr(result.ir_dump, "FmaF16 v127.sdwa(sel=5"),
	      "clamped native V_FMA_F16 did not lower to native f16 fma IR");
	Check(Common::ContainsStr(result.ir_dump, "PackB32F16 v26"),
	      "V_PACK_B32_F16 did not lower to packed-bit IR");
	Check(Common::ContainsStr(result.ir_dump, "PackB32F16 v43") &&
	          Common::ContainsStr(result.ir_dump, "v10.opsel(lo=1"),
	      "V_PACK_B32_F16 source selectors did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "PackedFmaF16 v59, v20, v43, v59"),
	      "V_PK_FMAC_F16 did not lower using destination as packed FMA accumulator");
	Check(SpirvContainsExtInst(result.spirv, 62),
	      "SPIR-V binary does not contain GLSL.std.450 UnpackHalf2x16 for VOP3P");
	Check(SpirvContainsExtInst(result.spirv, 58),
	      "SPIR-V binary does not contain GLSL.std.450 PackHalf2x16 for VOP3P");
	Check(SpirvContainsExtInst(result.spirv, 50),
	      "SPIR-V binary does not contain GLSL.std.450 Fma for VOP3P");
	Check(SpirvContainsExtInst(result.spirv, 43),
	      "SPIR-V binary does not contain GLSL.std.450 FClamp for clamped f16 ops");
	Check(!SpirvContainsExtInst(result.spirv, 37),
	      "SPIR-V binary still contains GLSL.std.450 FMin for VOP3P packed min");
	Check(!SpirvContainsExtInst(result.spirv, 40),
	      "SPIR-V binary still contains GLSL.std.450 FMax for VOP3P packed max");
	Check(SpirvContainsOpcode(result.spirv, 169),
	      "SPIR-V binary does not contain OpSelect for VOP3P packed min/max");
	Check(SpirvContainsOpcode(result.spirv, 184),
	      "SPIR-V binary does not contain OpFOrdLessThan for VOP3P packed min");
	Check(SpirvContainsOpcode(result.spirv, 190),
	      "SPIR-V binary does not contain OpFOrdGreaterThanEqual for VOP3P packed max");
	Check(SpirvContainsOpcode(result.spirv, 197),
	      "SPIR-V binary does not contain OpBitwiseOr for VOP3P packed min/max");
	Check(SpirvContainsOpcode(result.spirv, 199),
	      "SPIR-V binary does not contain OpBitwiseAnd for VOP3P packed min/max");
	Check(SpirvContainsOpcode(result.spirv, 129), "SPIR-V binary does not contain OpFAdd");
	Check(SpirvContainsOpcode(result.spirv, 133), "SPIR-V binary does not contain OpFMul");
	Check(SpirvContainsOpcode(result.spirv, 81),
	      "SPIR-V binary does not contain OpCompositeExtract");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerStagedShaderOps() {
	const uint32_t shader[] = {
	    EncodeSMovB32(0, 132),            // s0 = 4
	    EncodeSMovB32(1, 129),            // s1 = 1
	    EncodeSop2(0x05, 2, 0, 1),        // s_subb_u32 s2, s0, s1
	    EncodeSop1(0x1b, 3, 1),           // s_bitset0_b32 s3, s1
	    EncodeVop2(0x36, 70, 5 + 256, 6), // v_fmac_f16 v70, v5, v6
	    EncodeVop2(0x37, 71, 7 + 256, 8),
	    0x3c003c00u, // v_fmamk_f16
	    EncodeVop2(0x38, 72, 9 + 256, 10),
	    0x40004000u, // v_fmaak_f16
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "s_subb_u32 s2, s0, s1"),
	      "new decoder did not decode RDNA2 S_SUBB_U32");
	Check(Common::ContainsStr(result.decoded_dump, "s_bitset0_b32 s3, s1"),
	      "new decoder did not decode RDNA2 S_BITSET0_B32");
	Check(Common::ContainsStr(result.decoded_dump, "v_fmac_f16 v70.sdwa(sel=4"),
	      "new decoder did not decode RDNA2 V_FMAC_F16");
	Check(Common::ContainsStr(result.decoded_dump,
	                          "v_fmamk_f16 v71.sdwa(sel=4,sext=0), v7, 0x3c003c00, v8"),
	      "new decoder did not consume V_FMAMK_F16 literal as the multiply source");
	Check(Common::ContainsStr(result.decoded_dump,
	                          "v_fmaak_f16 v72.sdwa(sel=4,sext=0), v9, v10, 0x40004000"),
	      "new decoder did not consume V_FMAAK_F16 literal as the add source");
	Check(Common::ContainsStr(result.ir_dump, "ScalarSubBorrowCarryU32 s2, s0, s1, scc"),
	      "S_SUBB_U32 did not lower to scalar subtract-with-borrow IR");
	Check(Common::ContainsStr(result.ir_dump, "BitClearU32 s3, s3, s1"),
	      "S_BITSET0_B32 did not lower to bit-clear IR using the destination as input");
	Check(Common::ContainsStr(result.ir_dump,
	                          "FmaF16 v70.sdwa(sel=4,sext=0), v5, v6, v70.sdwa(sel=4,sext=0)"),
	      "V_FMAC_F16 did not lower using the destination as the FMA accumulator");
	Check(Common::ContainsStr(result.ir_dump, "FmaF16 v71.sdwa(sel=4,sext=0), v7, 0x3c003c00, v8"),
	      "V_FMAMK_F16 did not lower with the literal in source 1");
	Check(Common::ContainsStr(result.ir_dump, "FmaF16 v72.sdwa(sel=4,sext=0), v9, v10, 0x40004000"),
	      "V_FMAAK_F16 did not lower with the literal in source 2");
	Check(SpirvContainsOpcode(result.spirv, 130),
	      "SPIR-V binary does not contain OpISub for S_SUBB_U32");
	Check(SpirvContainsOpcode(result.spirv, 166),
	      "SPIR-V binary does not contain OpLogicalOr for S_SUBB_U32 borrow-out");
	Check(SpirvContainsOpcode(result.spirv, 172),
	      "SPIR-V binary does not contain OpUGreaterThan for S_SUBB_U32 borrow-out");
	Check(SpirvContainsOpcode(result.spirv, 196),
	      "SPIR-V binary does not contain OpShiftLeftLogical for S_BITSET0_B32");
	Check(SpirvContainsOpcode(result.spirv, 199),
	      "SPIR-V binary does not contain OpBitwiseAnd for S_BITSET0_B32");
	Check(SpirvContainsOpcode(result.spirv, 200),
	      "SPIR-V binary does not contain OpNot for S_BITSET0_B32");
	Check(SpirvContainsExtInst(result.spirv, 50),
	      "SPIR-V binary does not contain GLSL.std.450 Fma for VOP2 F16 FMA ops");
	Check(SpirvContainsExtInst(result.spirv, 58),
	      "SPIR-V binary does not contain GLSL.std.450 PackHalf2x16 for VOP2 F16 FMA ops");
	Check(SpirvContainsExtInst(result.spirv, 62),
	      "SPIR-V binary does not contain GLSL.std.450 UnpackHalf2x16 for VOP2 F16 FMA ops");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerBootF16UnaryOpcodes() {
	const uint32_t shader[] = {
	    EncodeVop1(0x55, 4, 249),
	    EncodeVop1Sdwa(3, 5, 2, 6),
	    EncodeVop1(0x5b, 5, 4 + 256),
	    0x7e12b8f9u,
	    0x0006150bu,
	    0x7e12b90cu,
	    EncodeVop1(0x5d, 6, 5 + 256),
	    0x7e0e02f9u,
	    0x008616c1u,
	    0x7e1002f9u,
	    0x008616c1u,
	    EncodeVop1(0x5e, 3, 3 + 256),
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "v_sqrt_f16 v4.sdwa(sel=5"),
	      "new decoder did not decode V_SQRT_F16 SDWA high-half destination");
	Check(Common::ContainsStr(result.decoded_dump, "v_rndne_f16 v3"),
	      "new decoder did not decode V_RNDNE_F16");
	Check(Common::ContainsStr(result.decoded_dump, "v_floor_f16 v5"),
	      "new decoder did not decode V_FLOOR_F16");
	Check(Common::ContainsStr(result.decoded_dump, "v_ceil_f16 v9.sdwa(sel=5"),
	      "new decoder did not decode boot V_CEIL_F16 SDWA high-half destination");
	Check(Common::ContainsStr(result.decoded_dump, "v_ceil_f16 v9"),
	      "new decoder did not decode boot V_CEIL_F16");
	Check(Common::ContainsStr(result.decoded_dump, "v_trunc_f16 v6"),
	      "new decoder did not decode V_TRUNC_F16");
	Check(Common::ContainsStr(result.decoded_dump, "v_mov_b32 v7, -1") &&
	          Common::ContainsStr(result.decoded_dump, "v_mov_b32 v8, -1"),
	      "new decoder did not accept full-width V_MOV_B32 SDWA with DST_U=PRESERVE");
	Check(!Common::ContainsStr(result.decoded_dump,
	                           "VOP1 SDWA destination selector is not supported"),
	      "full-width V_MOV_B32 SDWA with DST_U=PRESERVE was rejected");
	Check(!Common::ContainsStr(result.decoded_dump, "unsupported family=VOP2 opcode=0x00"),
	      "VOP1 SDWA extension word was decoded as a phantom VOP2 instruction");
	Check(Common::ContainsStr(result.ir_dump, "SqrtF16 v4.sdwa(sel=5"),
	      "V_SQRT_F16 did not lower to f16 sqrt IR");
	Check(Common::ContainsStr(result.ir_dump, "FloorF16 v5"),
	      "V_FLOOR_F16 did not lower to f16 floor IR");
	Check(Common::ContainsStr(result.ir_dump, "CeilF16 v9.sdwa(sel=5"),
	      "boot V_CEIL_F16 SDWA did not lower to f16 ceil IR");
	Check(Common::ContainsStr(result.ir_dump, "CeilF16 v9"),
	      "V_CEIL_F16 did not lower to f16 ceil IR");
	Check(Common::ContainsStr(result.ir_dump, "TruncF16 v6"),
	      "V_TRUNC_F16 did not lower to f16 trunc IR");
	Check(Common::ContainsStr(result.ir_dump, "MoveU32 v7, 0xffffffff") &&
	          Common::ContainsStr(result.ir_dump, "MoveU32 v8, 0xffffffff"),
	      "full-width V_MOV_B32 SDWA with DST_U=PRESERVE did not lower to move IR");
	Check(Common::ContainsStr(result.ir_dump, "RoundEvenF16 v3"),
	      "V_RNDNE_F16 did not lower to f16 round-even IR");
	Check(SpirvContainsExtInst(result.spirv, 31),
	      "SPIR-V binary does not contain GLSL.std.450 Sqrt for V_SQRT_F16");
	Check(SpirvContainsExtInst(result.spirv, 8),
	      "SPIR-V binary does not contain GLSL.std.450 Floor for V_FLOOR_F16");
	Check(SpirvContainsExtInst(result.spirv, 9),
	      "SPIR-V binary does not contain GLSL.std.450 Ceil for V_CEIL_F16");
	Check(SpirvContainsExtInst(result.spirv, 3),
	      "SPIR-V binary does not contain GLSL.std.450 Trunc for V_TRUNC_F16");
	Check(SpirvContainsExtInst(result.spirv, 2),
	      "SPIR-V binary does not contain GLSL.std.450 RoundEven for V_RNDNE_F16");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerBootB16PackedAndSdwaOpcodes() {
	const uint32_t shader[] = {
	    0xd7070001u,
	    0x00020081u, // v_lshrrev_b16 v1.lo, 1, v0.lo
	    0xd7071008u,
	    0x0002128fu, // v_lshrrev_b16 with selected high source lane
	    0xd7144005u,
	    0x00020481u, // v_lshlrev_b16 v5.hi, 1, v2.lo
	    0xd7034001u,
	    0x0002066au, // v_add_nc_u16 v1.hi, v106.lo, v3.lo
	    0xd70e4011u,
	    0x00020e80u, // v_sub_nc_i16 v17.hi, 0, v7.lo
	    0xcc03400eu,
	    0x10020e80u, // v_pk_sub_i16 from boot shader 0x0000001980eb0000
	    0xcc0a4104u,
	    0x300202ffu,
	    0x00007fffu, // v_pk_add_u16 with literal
	    0x360e04f9u,
	    0x04861482u, // v_and_b32 SDWA partial low-half destination
	    0x381212f9u,
	    0x04041508u, // v_or_b32 SDWA partial high-half destination
	    0xcc10c00cu,
	    0x18021d0du, // clamped v_pk_mul_f16
	    0x7e08a0f9u,
	    0x00061500u,                    // v_cvt_f16_u16 v4.hi, v0 from boot shader
	    EncodeVop1(0x50, 16, 10 + 256), // v_cvt_f16_u16 v16, v10
	    0x7e1ca307u,                    // v_cvt_f16_i16 v14, v7
	    0x7e1ca2f9u,
	    0x00051511u,                   // v_cvt_f16_i16 v14.hi, v17.hi
	    0x7e08a704u,                   // v_cvt_i16_f16 v4, v4 from boot shader
	    EncodeVop1(0x52, 15, 4 + 256), // v_cvt_u16_f16 v15, v4
	    0x4a0a08f9u,
	    0x0c860688u, // v_add_nc_u32 v5, 8, sign-extended v4.lo
	    0x4a0c0cf9u,
	    0x0d860688u, // v_add_nc_u32 v6, 8, sign-extended v6.hi
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "v_lshrrev_b16 v1.sdwa(sel=4"),
	      "new decoder did not decode low-half V_LSHRREV_B16");
	Check(Common::ContainsStr(result.decoded_dump, "v_lshlrev_b16 v5.sdwa(sel=5"),
	      "new decoder did not decode high-half V_LSHLREV_B16");
	Check(Common::ContainsStr(result.decoded_dump, "v_add_nc_u16 v1.sdwa(sel=5"),
	      "new decoder did not decode V_ADD_NC_U16 op_sel destination");
	Check(Common::ContainsStr(result.decoded_dump, "v_sub_nc_i16 v17.sdwa(sel=5"),
	      "new decoder did not decode V_SUB_NC_I16 op_sel destination");
	Check(Common::ContainsStr(result.decoded_dump, "v_pk_sub_i16 v14"),
	      "new decoder did not decode V_PK_SUB_I16");
	Check(Common::ContainsStr(result.decoded_dump, "v_pk_add_u16 v4"),
	      "new decoder did not decode V_PK_ADD_U16");
	Check(Common::ContainsStr(result.decoded_dump, "v_pk_add_u16 v4, 0x00007fff"),
	      "V_PK_ADD_U16 literal was not consumed as a source operand");
	Check(Common::ContainsStr(result.decoded_dump, "v_and_b32 v7.sdwa(sel=4"),
	      "new decoder did not decode partial-destination V_AND_B32 SDWA");
	Check(Common::ContainsStr(result.decoded_dump, "v_or_b32 v9.sdwa(sel=5"),
	      "new decoder did not decode partial-destination V_OR_B32 SDWA");
	Check(Common::ContainsStr(result.decoded_dump, "v_pk_mul_f16 v12"),
	      "new decoder did not decode clamped V_PK_MUL_F16");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_f16_u16 v4.sdwa(sel=5"),
	      "new decoder did not decode/consume SDWA V_CVT_F16_U16");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_f16_u16 v16"),
	      "new decoder did not decode plain V_CVT_F16_U16");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_f16_i16 v14"),
	      "new decoder did not decode plain V_CVT_F16_I16");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_f16_i16 v14.sdwa(sel=5"),
	      "new decoder did not decode V_CVT_F16_I16 SDWA high-half destination");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_i16_f16 v4"),
	      "new decoder did not decode plain V_CVT_I16_F16");
	Check(Common::ContainsStr(result.decoded_dump, "v_cvt_u16_f16 v15"),
	      "new decoder still rejects plain V_CVT_U16_F16");
	Check(Common::ContainsStr(result.decoded_dump, "v_add_nc_u32 v5") &&
	          Common::ContainsStr(result.decoded_dump, "v4.sdwa(sel=4,sext=1"),
	      "new decoder did not decode V_ADD_NC_U32 SDWA sign-extended low word");
	Check(Common::ContainsStr(result.decoded_dump, "v_add_nc_u32 v6") &&
	          Common::ContainsStr(result.decoded_dump, "v6.sdwa(sel=5,sext=1"),
	      "new decoder did not decode V_ADD_NC_U32 SDWA sign-extended high word");
	Check(!Common::ContainsStr(result.decoded_dump, "unsupported family=VOP2 opcode=0x00"),
	      "literal/SDWA extension words were decoded as phantom VOP2 instructions");
	Check(!Common::ContainsStr(result.decoded_dump,
	                           "VOP2 SDWA destination selector is not supported"),
	      "partial bitwise SDWA destination is still rejected");
	Check(Common::ContainsStr(result.ir_dump, "ShiftRightLogicalU16"),
	      "V_LSHRREV_B16 did not lower to 16-bit logical right shift IR");
	Check(Common::ContainsStr(result.ir_dump, "ShiftLeftLogicalU16"),
	      "V_LSHLREV_B16 did not lower to 16-bit logical left shift IR");
	Check(Common::ContainsStr(result.ir_dump, "IAddU16 v1.sdwa(sel=5"),
	      "V_ADD_NC_U16 did not lower to 16-bit add IR");
	Check(Common::ContainsStr(result.ir_dump, "ISubI16 v17.sdwa(sel=5"),
	      "V_SUB_NC_I16 did not lower to 16-bit subtract IR");
	Check(Common::ContainsStr(result.ir_dump, "PackedSubI16 v14"),
	      "V_PK_SUB_I16 did not lower to packed I16 subtract IR");
	Check(Common::ContainsStr(result.ir_dump, "PackedAddU16 v4"),
	      "V_PK_ADD_U16 did not lower to packed U16 add IR");
	Check(Common::ContainsStr(result.ir_dump, "ConvertI16ToF16 v14"),
	      "V_CVT_F16_I16 did not lower to signed I16-to-F16 conversion IR");
	Check(Common::ContainsStr(result.ir_dump, "ConvertU16ToF16 v4.sdwa(sel=5"),
	      "V_CVT_F16_U16 SDWA did not lower to unsigned U16-to-F16 conversion IR");
	Check(Common::ContainsStr(result.ir_dump, "ConvertU16ToF16 v16"),
	      "V_CVT_F16_U16 plain form did not lower to unsigned U16-to-F16 conversion IR");
	Check(Common::ContainsStr(result.ir_dump, "ConvertF16ToI16 v4"),
	      "V_CVT_I16_F16 did not lower to signed F16-to-I16 conversion IR");
	Check(Common::ContainsStr(result.ir_dump, "ConvertF16ToU16 v15"),
	      "V_CVT_U16_F16 did not lower to unsigned F16-to-U16 conversion IR");
	Check(Common::ContainsStr(result.ir_dump, "IAddU32 v5") &&
	          Common::ContainsStr(result.ir_dump, "v4.sdwa(sel=4,sext=1"),
	      "V_ADD_NC_U32 SDWA sign-extended low word did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "IAddU32 v6") &&
	          Common::ContainsStr(result.ir_dump, "v6.sdwa(sel=5,sext=1"),
	      "V_ADD_NC_U32 SDWA sign-extended high word did not lower to IR");
	Check(SpirvContainsOpcode(result.spirv, 128),
	      "SPIR-V binary does not contain OpIAdd for U16 operations");
	Check(SpirvContainsOpcode(result.spirv, 202),
	      "SPIR-V binary does not contain OpBitFieldSExtract for SDWA sign extension");
	Check(SpirvContainsOpcode(result.spirv, 130),
	      "SPIR-V binary does not contain OpISub for I16 operations");
	Check(SpirvContainsOpcode(result.spirv, 194),
	      "SPIR-V binary does not contain OpShiftRightLogical for B16 shift");
	Check(SpirvContainsOpcode(result.spirv, 196),
	      "SPIR-V binary does not contain OpShiftLeftLogical for B16 shift");
	Check(SpirvContainsOpcode(result.spirv, 197),
	      "SPIR-V binary does not contain OpBitwiseOr for packed U16 result");
	Check(SpirvContainsOpcode(result.spirv, 124),
	      "SPIR-V binary does not contain OpConvertSToF for V_CVT_F16_I16");
	Check(SpirvContainsOpcode(result.spirv, 109),
	      "SPIR-V binary does not contain OpConvertFToU for V_CVT_U16_F16");
	Check(SpirvContainsOpcode(result.spirv, 110),
	      "SPIR-V binary does not contain OpConvertFToS for V_CVT_I16_F16");
	Check(SpirvContainsOpcode(result.spirv, 112),
	      "SPIR-V binary does not contain OpConvertUToF for V_CVT_F16_U16");
	Check(SpirvContainsExtInst(result.spirv, 43),
	      "SPIR-V binary does not contain GLSL.std.450 FClamp for clamped V_PK_MUL_F16");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerScalarB64Alu() {
	const uint32_t shader[] = {
	    EncodeSMovB32(0, 129),          // s0 = 1
	    EncodeSMovB32(1, 130),          // s1 = 2
	    EncodeSop1(0x07, 41, 0),        // s_not_b32 s41, s0
	    EncodeSop1(0x04, 2, 0),         // s_mov_b64 s[2:3], s[0:1]
	    EncodeSop1(0x08, 4, 2),         // s_not_b64 s[4:5], s[2:3]
	    EncodeSop2(0x0f, 6, 2, 4),      // s_and_b64 s[6:7], s[2:3], s[4:5]
	    EncodeSop2(0x11, 8, 6, 2),      // s_or_b64 s[8:9], s[6:7], s[2:3]
	    EncodeSop2(0x13, 10, 8, 4),     // s_xor_b64 s[10:11], s[8:9], s[4:5]
	    EncodeSop2(0x14, 36, 0, 1),     // s_andn2_b32 s36, s0, s1
	    EncodeSop2(0x16, 37, 0, 1),     // s_orn2_b32 s37, s0, s1
	    EncodeSop2(0x18, 38, 0, 1),     // s_nand_b32 s38, s0, s1
	    EncodeSop2(0x1a, 39, 0, 1),     // s_nor_b32 s39, s0, s1
	    EncodeSop2(0x1c, 40, 0, 1),     // s_xnor_b32 s40, s0, s1
	    EncodeSopc(0x06, 0, 0),         // s_cmp_eq_u32 s0, s0
	    EncodeSop2(0x0b, 12, 10, 2),    // s_cselect_b64 s[12:13], s[10:11], s[2:3]
	    EncodeSop2(0x15, 14, 10, 4),    // s_andn2_b64 s[14:15], s[10:11], s[4:5]
	    EncodeSop2(0x17, 16, 14, 6),    // s_orn2_b64 s[16:17], s[14:15], s[6:7]
	    EncodeSop2(0x19, 18, 16, 8),    // s_nand_b64 s[18:19], s[16:17], s[8:9]
	    EncodeSop2(0x1b, 20, 18, 10),   // s_nor_b64 s[20:21], s[18:19], s[10:11]
	    EncodeSop2(0x1d, 22, 20, 12),   // s_xnor_b64 s[22:23], s[20:21], s[12:13]
	    EncodeSop2(0x1f, 24, 22, 1),    // s_lshl_b64 s[24:25], s[22:23], s1
	    EncodeSop2(0x21, 26, 24, 0),    // s_lshr_b64 s[26:27], s[24:25], s0
	    EncodeSop2(0x25, 28, 132, 130), // s_bfm_b64 s[28:29], 4, 2
	    EncodeSop1(0x10, 30, 26),       // s_bcnt1_i32_b64 s30, s[26:27]
	    EncodeSop1(0x16, 106, 26),      // s_flbit_i32_b64 vcc_lo, s[26:27]
	    EncodeSop1(0x3b, 32, 30),       // s_bitreplicate_b64_b32 s[32:33], s30
	    EncodeSop2(0x29, 34, 32, 255),  // s_bfe_u64 s[34:35], s[32:33], 0x00040002
	    0x00040002u,
	    EncodeSop1(0x0a, 126, 126), // s_wqm_b64 exec, exec
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "s_mov_b64 s2, s0"),
	      "new decoder did not decode old-backed S_MOV_B64");
	Check(Common::ContainsStr(result.decoded_dump, "s_not_b32 s41, s0"),
	      "new decoder did not decode RDNA2 S_NOT_B32");
	Check(Common::ContainsStr(result.decoded_dump, "s_not_b64 s4, s2"),
	      "new decoder did not decode old-backed S_NOT_B64");
	Check(Common::ContainsStr(result.decoded_dump, "s_and_b64 s6, s2, s4"),
	      "new decoder did not decode old-backed S_AND_B64");
	Check(Common::ContainsStr(result.decoded_dump, "s_or_b64 s8, s6, s2"),
	      "new decoder did not decode old-backed S_OR_B64");
	Check(Common::ContainsStr(result.decoded_dump, "s_xor_b64 s10, s8, s4"),
	      "new decoder did not decode old-backed S_XOR_B64");
	Check(Common::ContainsStr(result.decoded_dump, "s_andn2_b32 s36, s0, s1"),
	      "new decoder did not decode RDNA2 S_ANDN2_B32");
	Check(Common::ContainsStr(result.decoded_dump, "s_orn2_b32 s37, s0, s1"),
	      "new decoder did not decode RDNA2 S_ORN2_B32");
	Check(Common::ContainsStr(result.decoded_dump, "s_nand_b32 s38, s0, s1"),
	      "new decoder did not decode RDNA2 S_NAND_B32");
	Check(Common::ContainsStr(result.decoded_dump, "s_nor_b32 s39, s0, s1"),
	      "new decoder did not decode RDNA2 S_NOR_B32");
	Check(Common::ContainsStr(result.decoded_dump, "s_xnor_b32 s40, s0, s1"),
	      "new decoder did not decode RDNA2 S_XNOR_B32");
	Check(Common::ContainsStr(result.decoded_dump, "s_cselect_b64 s12, s10, s2"),
	      "new decoder did not decode old-backed S_CSELECT_B64");
	Check(Common::ContainsStr(result.decoded_dump, "s_andn2_b64 s14, s10, s4"),
	      "new decoder did not decode old-backed S_ANDN2_B64");
	Check(Common::ContainsStr(result.decoded_dump, "s_orn2_b64 s16, s14, s6"),
	      "new decoder did not decode old-backed S_ORN2_B64");
	Check(Common::ContainsStr(result.decoded_dump, "s_nand_b64 s18, s16, s8"),
	      "new decoder did not decode old-backed S_NAND_B64");
	Check(Common::ContainsStr(result.decoded_dump, "s_nor_b64 s20, s18, s10"),
	      "new decoder did not decode old-backed S_NOR_B64");
	Check(Common::ContainsStr(result.decoded_dump, "s_xnor_b64 s22, s20, s12"),
	      "new decoder did not decode old-backed S_XNOR_B64");
	Check(Common::ContainsStr(result.decoded_dump, "s_lshl_b64 s24, s22, s1"),
	      "new decoder did not decode old-backed S_LSHL_B64");
	Check(Common::ContainsStr(result.decoded_dump, "s_lshr_b64 s26, s24, s0"),
	      "new decoder did not decode old-backed S_LSHR_B64");
	Check(Common::ContainsStr(result.decoded_dump, "s_bfm_b64 s28, 4, 2"),
	      "new decoder did not decode old-backed S_BFM_B64");
	Check(Common::ContainsStr(result.decoded_dump, "s_bcnt1_i32_b64 s30, s26"),
	      "new decoder did not decode old-backed S_BCNT1_I32_B64");
	Check(Common::ContainsStr(result.decoded_dump, "s_flbit_i32_b64 vcc_lo, s26"),
	      "new decoder did not decode RDNA2 S_FLBIT_I32_B64");
	Check(Common::ContainsStr(result.decoded_dump, "s_bitreplicate_b64_b32 s32, s30"),
	      "new decoder did not decode old-backed S_BITREPLICATE_B64_B32");
	Check(Common::ContainsStr(result.decoded_dump, "s_bfe_u64 s34, s32, 0x00040002"),
	      "new decoder did not decode old-backed S_BFE_U64");
	Check(Common::ContainsStr(result.decoded_dump, "s_wqm_b64 exec_lo, exec_lo"),
	      "new decoder did not decode old-backed S_WQM_B64");
	Check(Common::ContainsStr(result.ir_dump, "MoveU64 s2, s0"),
	      "S_MOV_B64 did not lower to paired-dword IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseNotU32 s41, s0"),
	      "S_NOT_B32 did not lower to scalar bitwise-not IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseNotU64 s4, s2"),
	      "S_NOT_B64 did not lower to paired-dword IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseAndU64 s6, s2, s4"),
	      "S_AND_B64 did not lower to paired-dword IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseOrU64 s8, s6, s2"),
	      "S_OR_B64 did not lower to paired-dword IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseXorU64 s10, s8, s4"),
	      "S_XOR_B64 did not lower to paired-dword IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseAndNotU32 s36, s0, s1"),
	      "S_ANDN2_B32 did not lower to scalar bitwise-and-not IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseOrNotU32 s37, s0, s1"),
	      "S_ORN2_B32 did not lower to scalar bitwise-or-not IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseNandU32 s38, s0, s1"),
	      "S_NAND_B32 did not lower to scalar bitwise-nand IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseNorU32 s39, s0, s1"),
	      "S_NOR_B32 did not lower to scalar bitwise-nor IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseXnorU32 s40, s0, s1"),
	      "S_XNOR_B32 did not lower to scalar bitwise-xnor IR");
	Check(Common::ContainsStr(result.ir_dump, "SelectU64 s12"),
	      "S_CSELECT_B64 did not lower to paired-dword select IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseAndNotU64 s14, s10, s4"),
	      "S_ANDN2_B64 did not lower to paired-dword IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseOrNotU64 s16, s14, s6"),
	      "S_ORN2_B64 did not lower to paired-dword IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseNandU64 s18, s16, s8"),
	      "S_NAND_B64 did not lower to paired-dword IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseNorU64 s20, s18, s10"),
	      "S_NOR_B64 did not lower to paired-dword IR");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseXnorU64 s22, s20, s12"),
	      "S_XNOR_B64 did not lower to paired-dword IR");
	Check(Common::ContainsStr(result.ir_dump, "ShiftLeftLogicalU64 s24, s22, s1"),
	      "S_LSHL_B64 did not lower to paired-dword IR");
	Check(Common::ContainsStr(result.ir_dump, "ShiftRightLogicalU64 s26, s24, s0"),
	      "S_LSHR_B64 did not lower to paired-dword IR");
	Check(Common::ContainsStr(result.ir_dump, "BitFieldMaskU64 s28"),
	      "S_BFM_B64 did not lower to paired-dword IR");
	Check(Common::ContainsStr(result.ir_dump, "BitCountU64 s30, s26"),
	      "S_BCNT1_I32_B64 did not lower to paired-source bit count IR");
	Check(Common::ContainsStr(result.ir_dump, "FindMsbFromHighU64 vcc_lo, s26"),
	      "S_FLBIT_I32_B64 did not lower to paired-source leading-zero IR");
	Check(Common::ContainsStr(result.ir_dump, "BitReplicateB64B32 s32, s30"),
	      "S_BITREPLICATE_B64_B32 did not lower to paired-dword IR");
	Check(Common::ContainsStr(result.ir_dump, "BitFieldExtractU64 s34, s32, 0x00040002"),
	      "S_BFE_U64 did not lower to paired-dword IR");
	Check(Common::ContainsStr(result.ir_dump, "WqmB64 exec_lo, exec_lo"),
	      "S_WQM_B64 did not lower to whole-quad-mask IR");
	Check(SpirvContainsOpcode(result.spirv, 61),
	      "SPIR-V binary does not contain OpLoad for scalar B64 ops");
	Check(SpirvContainsOpcode(result.spirv, 62),
	      "SPIR-V binary does not contain OpStore for scalar B64 ops");
	Check(SpirvContainsOpcode(result.spirv, 197),
	      "SPIR-V binary does not contain OpBitwiseOr for scalar B64 ops");
	Check(SpirvContainsOpcode(result.spirv, 198),
	      "SPIR-V binary does not contain OpBitwiseXor for scalar B64 ops");
	Check(SpirvContainsOpcode(result.spirv, 199),
	      "SPIR-V binary does not contain OpBitwiseAnd for scalar B64 ops");
	Check(SpirvContainsOpcode(result.spirv, 200),
	      "SPIR-V binary does not contain OpNot for scalar B64 ops");
	Check(SpirvContainsOpcode(result.spirv, 169),
	      "SPIR-V binary does not contain OpSelect for scalar B64 select");
	Check(SpirvContainsOpcode(result.spirv, 171),
	      "SPIR-V binary does not contain OpINotEqual for scalar B64 whole-quad mask");
	Check(SpirvContainsOpcode(result.spirv, 196),
	      "SPIR-V binary does not contain OpShiftLeftLogical for scalar B64 shifts");
	Check(SpirvContainsOpcode(result.spirv, 194),
	      "SPIR-V binary does not contain OpShiftRightLogical for scalar B64 shifts");
	Check(SpirvContainsOpcode(result.spirv, 201),
	      "SPIR-V binary does not contain OpBitFieldInsert for scalar B64 mask");
	Check(SpirvContainsOpcode(result.spirv, 203),
	      "SPIR-V binary does not contain OpBitFieldUExtract for scalar B64 extract");
	Check(SpirvContainsOpcode(result.spirv, 205),
	      "SPIR-V binary does not contain OpBitCount for scalar B64 bit count");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerSignedCompareAlu() {
	const uint32_t shader[] = {
	    EncodeSMovB32(0, 193),   // s0 = -1
	    EncodeSMovB32(1, 129),   // s1 = 1
	    EncodeSopc(0x02, 0, 1),  // s_cmp_gt_i32 s0, s1
	    EncodeSopk(0x07, 0, -2), // s_cmp_lt_i32 s0, -2
	    EncodeVopc(0x84, 0, 1),  // v_cmp_gt_i32 s0, v1
	    EncodeVopc(0x81, 0, 1),  // v_cmp_lt_i32 s0, v1
	    0x7d130ef9u,
	    0x86068413u, // v_cmp_lt_i16 vcc_lo.sdwa, v19.word0, v7.word1
	    0x7d1d02f9u,
	    0x86068213u,            // v_cmp_ge_i16 vcc_lo.sdwa, v19.word0, v1.word1
	    EncodeVopc(0xa9, 0, 1), // v_cmp_lt_u16 s0, v1
	    EncodeVopc(0x94, 0, 1), // v_cmpx_gt_i32 s0, v1
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "s_cmp_gt_i32"),
	      "new decoder did not decode SOPC signed compare");
	Check(Common::ContainsStr(result.decoded_dump, "s_cmp_lt_i32"),
	      "new decoder did not decode SOPK signed compare");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_gt_i32"),
	      "new decoder did not decode VOPC signed greater-than compare");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_lt_i32"),
	      "new decoder did not decode VOPC signed less-than compare");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_lt_i16"),
	      "new decoder did not decode VOPC signed halfword less-than compare");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_ge_i16"),
	      "new decoder did not decode VOPC signed halfword greater-or-equal compare");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_lt_u16"),
	      "new decoder did not decode VOPC unsigned halfword less-than compare");
	Check(Common::ContainsStr(result.decoded_dump, "v_cmpx_gt_i32"),
	      "new decoder did not decode VOPC signed compare-and-mask");
	Check(Common::ContainsStr(result.ir_dump, "CompareGtI32"),
	      "signed greater-than compare did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareLtI32"),
	      "signed less-than compare did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareLtI16"),
	      "signed halfword less-than compare did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareGeI16"),
	      "signed halfword greater-or-equal compare did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareLtU16"),
	      "unsigned halfword less-than compare did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareMaskGtI32 exec_lo"),
	      "signed compare-and-mask did not lower to exec mask IR");
	Check(SpirvContainsOpcode(result.spirv, 173), "SPIR-V binary does not contain OpSGreaterThan");
	Check(SpirvContainsOpcode(result.spirv, 177), "SPIR-V binary does not contain OpSLessThan");
	Check(SpirvContainsOpcode(result.spirv, 202),
	      "SPIR-V binary does not contain OpBitFieldSExtract for I16 compare");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerSignedMinShiftAlu() {
	const uint32_t shader[] = {
	    EncodeSMovB32(0, 193),           // s0 = -1
	    EncodeSMovB32(1, 130),           // s1 = 2
	    EncodeSop2(0x06, 2, 0, 1),       // s_min_i32 s2, s0, s1
	    EncodeSop2(0x08, 3, 0, 1),       // s_max_i32 s3, s0, s1
	    EncodeSop2(0x22, 4, 0, 129),     // s_ashr_i32 s4, s0, 1
	    EncodeVop2(0x11, 1, 0, 0),       // v_min_i32 v1, s0, v0
	    EncodeVop2(0x12, 2, 1, 1),       // v_max_i32 v2, s1, v1
	    EncodeVop2(0x17, 3, 1 + 256, 1), // v_ashr_i32 v3, v1, v1
	    EncodeVop2(0x18, 4, 129, 3),     // v_ashrrev_i32 v4, 1, v3
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "s_min_i32"),
	      "new decoder did not decode S_MIN_I32");
	Check(Common::ContainsStr(result.decoded_dump, "s_ashr_i32"),
	      "new decoder did not decode S_ASHR_I32");
	Check(Common::ContainsStr(result.decoded_dump, "v_min_i32"),
	      "new decoder did not decode V_MIN_I32");
	Check(Common::ContainsStr(result.decoded_dump, "v_ashrrev_i32"),
	      "new decoder did not decode V_ASHRREV_I32");
	Check(Common::ContainsStr(result.ir_dump, "IMinI32 s2"), "S_MIN_I32 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "IMaxI32 s3"), "S_MAX_I32 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "ShiftRightArithmeticI32 s4"),
	      "S_ASHR_I32 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "IMinI32 v1"), "V_MIN_I32 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "IMaxI32 v2"), "V_MAX_I32 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "ShiftRightArithmeticI32 v4, v3, 0x00000001"),
	      "V_ASHRREV_I32 did not reverse source order in IR");
	Check(SpirvContainsOpcode(result.spirv, 173), "SPIR-V binary does not contain OpSGreaterThan");
	Check(SpirvContainsOpcode(result.spirv, 177), "SPIR-V binary does not contain OpSLessThan");
	Check(SpirvContainsOpcode(result.spirv, 195),
	      "SPIR-V binary does not contain OpShiftRightArithmetic");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerScalarBitfieldAlu() {
	const uint32_t shader[] = {
	    EncodeSMovB32(0, 193),         // s0 = -1
	    EncodeSMovB32(1, 129),         // s1 = 1
	    EncodeSopc(0x02, 1, 0),        // s_cmp_gt_i32 s1, s0
	    EncodeSop2(0x0a, 2, 1, 0),     // s_cselect_b32 s2, s1, s0
	    EncodeSop1(0x34, 3, 0),        // s_abs_i32 s3, s0
	    EncodeSop1(0x0b, 4, 1),        // s_brev_b32 s4, s1
	    EncodeSop2(0x24, 5, 132, 129), // s_bfm_b32 s5, 4, 1
	    EncodeSop2(0x27, 6, 4, 255),   // s_bfe_u32 s6, s4, literal
	    0x00080004u,
	    EncodeSop2(0x32, 7, 0, 1), // s_pack_ll_b32_b16 s7, s0, s1
	    EncodeSop2(0x33, 8, 0, 1), // s_pack_lh_b32_b16 s8, s0, s1
	    EncodeSop2(0x34, 9, 0, 1), // s_pack_hh_b32_b16 s9, s0, s1
	    EncodeSopc(0x0c, 0, 1),    // s_bitcmp0_b32 s0, s1
	    EncodeSopc(0x0d, 1, 0),    // s_bitcmp1_b32 s1, s0
	    EncodeSopc(0x13, 0, 2),    // s_cmp_lg_u64 s[0:1], s[2:3]
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "s_cselect_b32"),
	      "new decoder did not decode S_CSELECT_B32");
	Check(Common::ContainsStr(result.decoded_dump, "s_abs_i32"),
	      "new decoder did not decode S_ABS_I32");
	Check(Common::ContainsStr(result.decoded_dump, "s_brev_b32"),
	      "new decoder did not decode S_BREV_B32");
	Check(Common::ContainsStr(result.decoded_dump, "s_bfm_b32"),
	      "new decoder did not decode S_BFM_B32");
	Check(Common::ContainsStr(result.decoded_dump, "s_bfe_u32"),
	      "new decoder did not decode S_BFE_U32");
	Check(Common::ContainsStr(result.decoded_dump, "s_pack_hh_b32_b16"),
	      "new decoder did not decode S_PACK_HH_B32_B16");
	Check(Common::ContainsStr(result.decoded_dump, "s_bitcmp0_b32"),
	      "new decoder did not decode S_BITCMP0_B32");
	Check(Common::ContainsStr(result.decoded_dump, "s_bitcmp1_b32"),
	      "new decoder did not decode S_BITCMP1_B32");
	Check(Common::ContainsStr(result.decoded_dump, "s_cmp_lg_u64"),
	      "new decoder did not decode S_CMP_LG_U64");
	Check(Common::ContainsStr(result.ir_dump, "SelectU32 s2"), "S_CSELECT_B32 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AbsI32 s3"), "S_ABS_I32 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "BitReverseU32 s4"),
	      "S_BREV_B32 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "BitFieldMaskU32 s5"),
	      "S_BFM_B32 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "BitFieldExtractU32 s6"),
	      "S_BFE_U32 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "PackLowLowU16 s7"),
	      "S_PACK_LL_B32_B16 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "PackLowHighU16 s8"),
	      "S_PACK_LH_B32_B16 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "PackHighHighU16 s9"),
	      "S_PACK_HH_B32_B16 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "BitCompare0B32"),
	      "S_BITCMP0_B32 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "BitCompare1B32"),
	      "S_BITCMP1_B32 did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "CompareNeU64"), "S_CMP_LG_U64 did not lower to IR");
	Check(SpirvContainsOpcode(result.spirv, 126), "SPIR-V binary does not contain OpSNegate");
	Check(SpirvContainsOpcode(result.spirv, 169), "SPIR-V binary does not contain OpSelect");
	Check(SpirvContainsOpcode(result.spirv, 201),
	      "SPIR-V binary does not contain OpBitFieldInsert");
	Check(SpirvContainsOpcode(result.spirv, 203),
	      "SPIR-V binary does not contain OpBitFieldUExtract");
	Check(SpirvContainsOpcode(result.spirv, 171), "SPIR-V binary does not contain OpINotEqual");
	Check(SpirvContainsOpcode(result.spirv, 166), "SPIR-V binary does not contain OpLogicalOr");
	Check(SpirvContainsOpcode(result.spirv, 204), "SPIR-V binary does not contain OpBitReverse");
	CheckSpirvBinaryValidates(result.spirv);
}

void CheckNewDecoderUnsupported(const uint32_t* shader, uint32_t words, const char* family,
                                const char* opcode_name) {
	ShaderRecompiler::Decoder::Program program;
	std::string                        error;
	const std::span                    code {shader, words};
	Check(ShaderRecompiler::Decoder::DecodeProgram(code, program, &error), error.c_str());
	Check(program.instructions.size() >= 2, "decoder did not return instruction plus endpgm");
	const auto text = ShaderRecompiler::Decoder::ProgramToString(program);
	Check(Common::ContainsStr(text, family),
	      "decoder unsupported text did not include opcode family");
	Check(Common::ContainsStr(text, opcode_name),
	      "decoder unsupported text did not include opcode name");

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;
	ShaderRecompiler::CompileResult result;
	Check(!ShaderRecompiler::TryRecompile(code, options, result, &error),
	      "unsupported opcode unexpectedly lowered without implementation");
	Check(Common::ContainsStr(error, "no IR lowering") ||
	          Common::ContainsStr(error, "unsupported decoded"),
	      "unsupported lowering error was not explicit");
}

void TestNewShaderRecompilerMemoryFamilyLowering() {
	const uint32_t shader[] = {
	    EncodeSmem0(0x00, 0, 4),
	    0u, // s_load_dword s0
	    EncodeSmem0(0x08, 1, 4),
	    0u, // s_buffer_load_dword s1
	    EncodeMubuf0(0x0c, 4),
	    EncodeMubuf1(0, 0, 1), // buffer_load_dword v0
	    EncodeMubuf0(0x1c, 8),
	    EncodeMubuf1(0, 0, 1), // buffer_store_dword v0
	    EncodeDs0(0x0d),
	    EncodeDs1(0, 0, 1), // ds_write_b32 v0, v1
	    EncodeDs0(0x36),
	    EncodeDs1(2, 0, 1), // ds_read_b32 v2, v1
	    EncodeMimg0(0x0e, 0x1),
	    EncodeMimg1(5, 0, 0, 1), // image_get_resinfo v5
	    EncodeMimg0(0x00, 0xf),
	    EncodeMimg1(4, 0, 0, 1), // image_load v4
	    EncodeMimg0(0x20, 0xf),
	    EncodeMimg1(3, 0, 0, 1), // image_sample v3
	    0xbf810000u,
	};

	auto                             user_data = ImageTestUserData();
	ShaderRecompiler::CompileOptions options;
	options.stage       = ShaderType::Compute;
	options.dump_ir     = true;
	options.user_data   = user_data.data();
	options.read_memory = ReadZeroTestMemory;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	const auto compiled = ShaderRecompiler::TryRecompile(shader, options, result, &error);
	Check(compiled, error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "s_load_dword"),
	      "new decoder did not decode SMEM dword load");
	Check(Common::ContainsStr(result.decoded_dump, "s_buffer_load_dword"),
	      "new decoder did not decode SMEM scalar-buffer dword load");
	Check(Common::ContainsStr(result.decoded_dump, "s_buffer_load_dword s1, s8"),
	      "SMEM scalar-buffer SBASE was not decoded as an SGPR-pair index");
	Check(Common::ContainsStr(result.decoded_dump, "buffer_load_dword"),
	      "new decoder did not decode MUBUF dword load");
	Check(Common::ContainsStr(result.decoded_dump, "buffer_store_dword"),
	      "new decoder did not decode MUBUF dword store");
	Check(Common::ContainsStr(result.decoded_dump, "ds_read_b32"),
	      "new decoder did not decode DS dword read");
	Check(Common::ContainsStr(result.decoded_dump, "image_get_resinfo"),
	      "new decoder did not decode MIMG resinfo query");
	Check(Common::ContainsStr(result.decoded_dump, "image_load"),
	      "new decoder did not decode MIMG load");
	Check(Common::ContainsStr(result.decoded_dump, "image_sample"),
	      "new decoder did not decode MIMG sample");
	Check(!Common::ContainsStr(result.decoded_dump, "lowering is not implemented"),
	      "implemented memory decode still reports unsupported lowering");
	Check(Common::ContainsStr(result.ir_dump, "SLoadDword s0"), "SMEM load did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "SBufferLoadDword s1"),
	      "SMEM scalar-buffer load did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "BufferLoadDword v0"),
	      "MUBUF load did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "BufferStoreDword null, v0"),
	      "MUBUF store did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "DsWriteB32 null, v0"),
	      "DS write did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "DsReadB32 v2"), "DS read did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "ImageGetResinfo v5"),
	      "MIMG resinfo query did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "ImageLoad v4"), "MIMG load did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "ImageSample v3"), "MIMG sample did not lower to IR");
	Check(SpirvContainsOpcode(result.spirv, 65), "SPIR-V binary does not contain OpAccessChain");
	Check(SpirvContainsOpcode(result.spirv, 61), "SPIR-V binary does not contain OpLoad");
	Check(SpirvContainsOpcode(result.spirv, 62), "SPIR-V binary does not contain OpStore");
	Check(SpirvContainsOpcode(result.spirv, 95), "SPIR-V binary does not contain OpImageFetch");
	Check(SpirvContainsOpcode(result.spirv, 100), "SPIR-V binary does not contain OpImage");
	Check(SpirvContainsOpcode(result.spirv, 103),
	      "SPIR-V binary does not contain OpImageQuerySizeLod");
	Check(SpirvContainsOpcode(result.spirv, 88),
	      "SPIR-V binary does not contain OpImageSampleExplicitLod");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerImageQueryLowering() {
	const uint32_t shader[] = {
	    EncodeMimg0(0x60, 0x3),
	    EncodeMimg1(6, 0, 0, 1), // image_get_lod
	    0xbf810000u,
	};

	auto                             user_data = ImageTestUserData();
	ShaderRecompiler::CompileOptions options;
	options.stage     = ShaderType::Compute;
	options.dump_ir   = true;
	options.user_data = user_data.data();

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "image_get_lod"),
	      "new decoder did not decode MIMG image get-lod query");
	Check(Common::ContainsStr(result.decoded_dump, "dmask=0x3"),
	      "image_get_lod decode did not preserve dmask metadata");
	Check(Common::ContainsStr(result.ir_dump, "ImageGetLod v6"),
	      "image_get_lod did not lower to explicit query IR");
	Check(Common::ContainsStr(result.ir_dump, "data_dwords=2"),
	      "image_get_lod did not preserve two-component result metadata");
	Check(Common::ContainsStr(result.ir_dump, "image_addr=2"),
	      "image_get_lod did not preserve address component metadata");
	Check(SpirvContainsOpcode(result.spirv, 105), "SPIR-V binary does not contain OpImageQueryLod");
	Check(SpirvContainsOpcode(result.spirv, 80),
	      "SPIR-V binary does not contain coordinate composite for image_get_lod");
	Check(SpirvContainsOpcode(result.spirv, 81),
	      "SPIR-V binary does not contain dmask extraction for image_get_lod");
	Check(SpirvContainsOpcode(result.spirv, 124),
	      "SPIR-V binary does not contain result bitcast for image_get_lod");
	Check(std::find(result.spirv.begin(), result.spirv.end(), 5288u) != result.spirv.end(),
	      "SPIR-V binary does not request compute derivative group capability");
	Check(std::find(result.spirv.begin(), result.spirv.end(), 5289u) != result.spirv.end(),
	      "SPIR-V binary does not request compute derivative group execution mode");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerImageSampleVariants() {
	const uint32_t shader[] = {
	    EncodeMimg0(0x24, 0xf),
	    EncodeMimg1(8, 0, 0, 1), // image_sample_l
	    EncodeMimg0(0x25, 0xf),
	    EncodeMimg1(12, 0, 0, 1), // image_sample_b
	    EncodeMimg0(0x27, 0xf),
	    EncodeMimg1(16, 0, 0, 1), // image_sample_lz
	    EncodeMimg0(0x28, 0xf),
	    EncodeMimg1(20, 0, 0, 1), // image_sample_c
	    EncodeMimg0(0x30, 0xf),
	    EncodeMimg1(24, 0, 0, 1), // image_sample_o
	    EncodeMimg0(0x22, 0xf),
	    EncodeMimg1(28, 0, 0, 1), // image_sample_d
	    EncodeMimg0(0x2f, 0x1),
	    EncodeMimg1(32, 0, 0, 1), // image_sample_c_lz
	    EncodeMimg0(0x38, 0x1),
	    EncodeMimg1(36, 0, 0, 1), // image_sample_c_o
	    EncodeMimg0(0x2a, 0x1),
	    EncodeMimg1(40, 0, 0, 1), // image_sample_c_d
	    EncodeMimg0(0x20, 0x7),
	    EncodeMimg1(48, 0, 0, 1, true), // image_sample with A16 bit
	    0xbf810000u,
	};

	auto                             user_data = ImageTestUserData();
	ShaderRecompiler::CompileOptions options;
	options.stage     = ShaderType::Compute;
	options.dump_ir   = true;
	options.user_data = user_data.data();

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "image_sample_l"),
	      "new decoder did not decode IMAGE_SAMPLE_L through shared MIMG path");
	Check(Common::ContainsStr(result.decoded_dump, "image_sample_b"),
	      "new decoder did not decode IMAGE_SAMPLE_B through shared MIMG path");
	Check(Common::ContainsStr(result.decoded_dump, "image_sample_lz"),
	      "new decoder did not decode IMAGE_SAMPLE_LZ through shared MIMG path");
	Check(Common::ContainsStr(result.decoded_dump, "image_sample_c"),
	      "new decoder did not decode IMAGE_SAMPLE_C through shared MIMG path");
	Check(Common::ContainsStr(result.decoded_dump, "image_sample_o"),
	      "new decoder did not decode IMAGE_SAMPLE_O through shared MIMG path");
	Check(Common::ContainsStr(result.decoded_dump, "image_sample_d"),
	      "new decoder did not decode IMAGE_SAMPLE_D through shared MIMG path");
	Check(Common::ContainsStr(result.decoded_dump, "image_sample_c_lz"),
	      "new decoder did not decode IMAGE_SAMPLE_C_LZ through shared MIMG path");
	Check(Common::ContainsStr(result.decoded_dump, "image_sample_c_o"),
	      "new decoder did not decode IMAGE_SAMPLE_C_O through shared MIMG path");
	Check(Common::ContainsStr(result.decoded_dump, "image_sample_c_d"),
	      "new decoder did not decode IMAGE_SAMPLE_C_D through shared MIMG path");
	Check(Common::ContainsStr(result.decoded_dump, "sample_flags=a16"),
	      "IMAGE_SAMPLE with MIMG A16 bit did not expose A16 sample flag");
	Check(Common::ContainsStr(result.decoded_dump, "sample_flags=lod"),
	      "IMAGE_SAMPLE_L did not expose lod sample flag");
	Check(Common::ContainsStr(result.decoded_dump, "sample_flags=bias"),
	      "IMAGE_SAMPLE_B did not expose bias sample flag");
	Check(Common::ContainsStr(result.decoded_dump, "sample_flags=level_zero"),
	      "IMAGE_SAMPLE_LZ did not expose level-zero sample flag");
	Check(Common::ContainsStr(result.decoded_dump, "sample_flags=compare"),
	      "IMAGE_SAMPLE_C did not expose compare sample flag");
	Check(Common::ContainsStr(result.decoded_dump, "sample_flags=offset addr_components=3"),
	      "IMAGE_SAMPLE_O did not expose offset sample flag/address width");
	Check(Common::ContainsStr(result.decoded_dump, "sample_flags=derivative addr_components=6"),
	      "IMAGE_SAMPLE_D did not expose derivative sample flag/address width");
	Check(Common::ContainsStr(result.decoded_dump, "sample_flags=compare|level_zero"),
	      "IMAGE_SAMPLE_C_LZ did not expose compare+level-zero flags");
	Check(Common::ContainsStr(result.decoded_dump, "sample_flags=compare|offset addr_components=4"),
	      "IMAGE_SAMPLE_C_O did not expose compare+offset flags/address width");
	Check(Common::ContainsStr(result.decoded_dump,
	                          "sample_flags=derivative|compare addr_components=7"),
	      "IMAGE_SAMPLE_C_D did not expose compare+derivative address width");
	Check(Common::ContainsStr(result.ir_dump, "ImageSample v8"),
	      "IMAGE_SAMPLE_L did not lower to shared IR ImageSample");
	Check(Common::ContainsStr(result.ir_dump, "ImageSample v28"),
	      "IMAGE_SAMPLE_D did not lower to shared IR ImageSample");
	Check(Common::ContainsStr(result.ir_dump, "image_flags=0x4"),
	      "derivative sample flag did not survive into IR memory metadata");
	Check(Common::ContainsStr(result.ir_dump, "image_addr=6"),
	      "derivative sample address width did not survive into IR memory metadata");
	Check(Common::ContainsStr(result.ir_dump, "image_addr=7"),
	      "compare+derivative sample address width did not survive into IR memory metadata");
	Check(Common::ContainsStr(result.ir_dump, "image_flags=0x80"),
	      "MIMG A16 bit did not survive into IR memory metadata");
	Check(SpirvContainsOpcode(result.spirv, 88),
	      "SPIR-V binary does not contain shared OpImageSampleExplicitLod emission");
	Check(SpirvContainsOpcode(result.spirv, 90),
	      "SPIR-V binary does not contain shared OpImageSampleDrefExplicitLod emission");
	Check(SpirvContainsOpcode(result.spirv, 202),
	      "SPIR-V binary does not contain packed offset extraction");
	Check(SpirvContainsOpcode(result.spirv, 80),
	      "SPIR-V binary does not contain coordinate/gradient composite construction");
	Check(SpirvContainsExtInst(result.spirv, 62),
	      "SPIR-V binary does not unpack A16 image address halves");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerImageSampleA16SamplerCoords() {
	constexpr uint32_t MimgDim3D = 2;

	const uint32_t shader[] = {
	    EncodeMimg0(0x20, 0x7, false, MimgDim3D),
	    EncodeMimg1(8, 0, 0, 4, true), // image_sample, A16 bit, 3D xyz coords
	    0xbf810000u,
	};

	auto                             user_data = ImageTestUserData(Prospero::ImageType::kColor3D);
	ShaderRecompiler::CompileOptions options;
	options.stage     = ShaderType::Compute;
	options.dump_ir   = true;
	options.user_data = user_data.data();

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	const auto compiled = ShaderRecompiler::TryRecompile(shader, options, result, &error);
	Check(compiled, error.c_str());
	Check(
	    Common::ContainsStr(result.decoded_dump, "image_dim=3d sample_flags=a16 addr_components=3"),
	    "3D IMAGE_SAMPLE with MIMG A16 bit did not decode as three A16 sampler coords");
	Check(Common::ContainsStr(result.ir_dump, "ImageSample v8"),
	      "3D A16 IMAGE_SAMPLE did not lower to sample IR");
	Check(Common::ContainsStr(result.ir_dump, "image_flags=0x80"),
	      "3D A16 IMAGE_SAMPLE did not preserve A16 memory metadata");
	Check(Common::ContainsStr(result.ir_dump, "image_dim=3d"),
	      "3D A16 IMAGE_SAMPLE did not preserve image dimension metadata");
	Check(Common::ContainsStr(result.ir_dump, "image_addr=3"),
	      "3D A16 IMAGE_SAMPLE did not preserve three logical address components");
	Check(SpirvExtInstCount(result.spirv, 62) == 3,
	      "sampler A16 xyz coordinates should be converted from three packed f16 components");
	Check(SpirvContainsOpcode(result.spirv, 194),
	      "sampler A16 high-half coordinate extraction is missing");
	Check(SpirvContainsOpcode(result.spirv, 199),
	      "sampler A16 low-half coordinate masking is missing");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerImageSampleOpcodeAliases() {
	const uint32_t shader[] = {
	    0xf0800109u,
	    0x00c00602u, // observed image_sample_a v6, v2, s0, s24
	    EncodeMimg0(0xa5, 0xf),
	    EncodeMimg1(12, 0, 0, 4), // image_sample_b_a
	    EncodeMimg0(0xa8, 0x1),
	    EncodeMimg1(16, 0, 0, 4), // image_sample_c_a
	    EncodeMimg0(0xad, 0x1),
	    EncodeMimg1(20, 0, 0, 4), // image_sample_c_b_a
	    0xbf810000u,
	};

	auto                             user_data = ImageTestUserData();
	ShaderRecompiler::CompileOptions options;
	options.stage     = ShaderType::Compute;
	options.dump_ir   = true;
	options.user_data = user_data.data();

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "image_sample_a"),
	      "MIMG opcode 0xa0 should decode as image_sample_a alias");
	Check(Common::ContainsStr(result.decoded_dump, "image_sample_b_a"),
	      "MIMG opcode 0xa5 should decode as image_sample_b_a alias");
	Check(Common::ContainsStr(result.decoded_dump, "image_sample_c_a"),
	      "MIMG opcode 0xa8 should decode as image_sample_c_a alias");
	Check(Common::ContainsStr(result.decoded_dump, "image_sample_c_b_a"),
	      "MIMG opcode 0xad should decode as image_sample_c_b_a alias");
	Check(Common::ContainsStr(result.decoded_dump, "sample_flags=none"),
	      "opcode 0xa0 alias should use normal 32-bit sample coordinates");
	Check(Common::ContainsStr(result.decoded_dump, "sample_flags=bias|compare"),
	      "compare+bias opcode alias did not expose expected sample flags");
	Check(!Common::ContainsStr(result.decoded_dump, "a16"),
	      "A16 must come from MIMG bit 62, not from the opcode alias");
	Check(!SpirvContainsExtInst(result.spirv, 62),
	      "opcode aliases without bit 62 must not unpack sampled f16 address halves");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerImageSampleA16ExceptionComponents() {
	auto compile = [](uint32_t opcode, uint32_t dmask, uint32_t vdata) {
		const uint32_t shader[] = {
		    EncodeMimg0(opcode, dmask),
		    EncodeMimg1(vdata, 0, 0, 4, true),
		    0xbf810000u,
		};

		auto                             user_data = ImageTestUserData();
		ShaderRecompiler::CompileOptions options;
		options.stage     = ShaderType::Compute;
		options.dump_ir   = true;
		options.user_data = user_data.data();

		ShaderRecompiler::CompileResult result;
		std::string                     error;
		Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
		CheckSpirvBinaryValidates(result.spirv);
		return result;
	};

	{
		const auto result = compile(0x28, 0x1, 12); // image_sample_c with A16 bit
		Check(
		    Common::ContainsStr(result.decoded_dump, "sample_flags=compare|a16 addr_components=3"),
		    "A16 IMAGE_SAMPLE_C did not preserve compare+A16 metadata");
		Check(Common::ContainsStr(result.ir_dump, "image_flags=0x88"),
		      "A16 IMAGE_SAMPLE_C did not preserve compare+A16 IR flags");
		Check(SpirvInstructionOpcodeCount(result.spirv, 90) == 1,
		      "A16 IMAGE_SAMPLE_C should emit one dref sample");
		Check(SpirvExtInstCount(result.spirv, 62) == 2,
		      "PCF reference must remain 32-bit while only xy sampler coords use A16");
	}

	{
		const auto result = compile(0x30, 0xf, 16); // image_sample_o with A16 bit
		Check(Common::ContainsStr(result.decoded_dump, "sample_flags=offset|a16 addr_components=3"),
		      "A16 IMAGE_SAMPLE_O did not preserve offset+A16 metadata");
		Check(Common::ContainsStr(result.ir_dump, "image_flags=0x90"),
		      "A16 IMAGE_SAMPLE_O did not preserve offset+A16 IR flags");
		Check(SpirvContainsOpcode(result.spirv, 202),
		      "texel offset should still be decoded from its packed 6-bit fields");
		Check(SpirvExtInstCount(result.spirv, 62) == 2,
		      "texel offset must remain 32-bit while only xy sampler coords use A16");
	}
}

void TestNewShaderRecompilerImageSampleA16Rdna2AddressOrder() {
	namespace Decoder = Libs::Graphics::ShaderRecompiler::Decoder;
	namespace Emitter = Libs::Graphics::ShaderRecompiler::Spirv::Emitter;
	namespace IR      = Libs::Graphics::ShaderRecompiler::IR;

	auto make_inst = [](uint32_t flags) {
		IR::Instruction inst {};
		inst.src[0]                    = Emitter::MakeRegisterOperand(IR::RegisterFile::Vector, 20);
		inst.memory.image_sample_flags = flags | Decoder::ImageSampleFlagA16;
		inst.memory.image_address_components = 5;
		return inst;
	};
	auto check_vgpr = [](const IR::Operand& operand, uint32_t expected, const char* message) {
		Check(operand.kind == IR::OperandKind::Register &&
		          operand.reg.file == IR::RegisterFile::Vector && operand.reg.index == expected,
		      message);
	};

	{
		auto       inst = make_inst(Decoder::ImageSampleFlagBias | Decoder::ImageSampleFlagCompare);
		const auto layout = Emitter::MakeImageSampleLayout(inst, Emitter::ImageViewKind::Dim2D);
		Check(layout.bias == 0u, "RDNA2 sample_c_b A16 bias must precede PCF reference");
		Check(layout.dref == 1u, "RDNA2 sample_c_b A16 PCF reference must follow bias");
		Check(layout.coord == 2u, "RDNA2 sample_c_b A16 body must follow bias and PCF");
		Check(Emitter::ImageAddressHalfComponent(inst, layout.bias) == 0u,
		      "A16 bias should use the first packed half");
		Check(Emitter::ImageAddressHalfComponent(inst, layout.dref) == 2u,
		      "32-bit PCF reference must align to the next VGPR after A16 bias");
		Check(Emitter::ImageAddressHalfComponent(inst, layout.coord) == 4u,
		      "A16 coordinates must start after the aligned PCF reference");
		check_vgpr(Emitter::ImageAddressOperand(inst, inst.src[0], layout.bias), 20,
		           "A16 sample_c_b bias should read v20");
		check_vgpr(Emitter::ImageAddressOperand(inst, inst.src[0], layout.dref), 21,
		           "A16 sample_c_b PCF reference should read v21");
		check_vgpr(Emitter::ImageAddressOperand(inst, inst.src[0], layout.coord), 22,
		           "A16 sample_c_b x/y coordinates should start in v22");
	}

	{
		auto       inst = make_inst(Decoder::ImageSampleFlagOffset | Decoder::ImageSampleFlagBias |
		                            Decoder::ImageSampleFlagCompare);
		const auto layout = Emitter::MakeImageSampleLayout(inst, Emitter::ImageViewKind::Dim2D);
		Check(layout.offset == 0u && layout.bias == 1u && layout.dref == 2u && layout.coord == 3u,
		      "RDNA2 sample_c_b_o order must be offset, bias, PCF, body");
		Check(Emitter::ImageAddressHalfComponent(inst, layout.offset) == 0u,
		      "texel offset should stay a packed 32-bit VGPR");
		Check(Emitter::ImageAddressHalfComponent(inst, layout.bias) == 2u,
		      "A16 bias should follow the 32-bit texel offset");
		Check(Emitter::ImageAddressHalfComponent(inst, layout.dref) == 4u,
		      "PCF reference should align after A16 bias");
		Check(Emitter::ImageAddressHalfComponent(inst, layout.coord) == 6u,
		      "A16 coordinates should start after offset, bias, and PCF");
		check_vgpr(Emitter::ImageAddressOperand(inst, inst.src[0], layout.offset), 20,
		           "A16 sample_c_b_o offset should read v20");
		check_vgpr(Emitter::ImageAddressOperand(inst, inst.src[0], layout.bias), 21,
		           "A16 sample_c_b_o bias should read v21");
		check_vgpr(Emitter::ImageAddressOperand(inst, inst.src[0], layout.dref), 22,
		           "A16 sample_c_b_o PCF reference should read v22");
		check_vgpr(Emitter::ImageAddressOperand(inst, inst.src[0], layout.coord), 23,
		           "A16 sample_c_b_o x/y coordinates should start in v23");
	}
}

void TestNewShaderRecompilerImageLoadA16UintCoords() {
	const uint32_t shader[] = {
	    EncodeMimg0(0x00, 0x3),
	    EncodeMimg1(20, 0, 0, 4, true), // image_load, A16 bit
	    0xbf810000u,
	};

	auto                             user_data = ImageTestUserData();
	ShaderRecompiler::CompileOptions options;
	options.stage     = ShaderType::Compute;
	options.dump_ir   = true;
	options.user_data = user_data.data();

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "image_load"), "A16 IMAGE_LOAD did not decode");
	Check(Common::ContainsStr(result.ir_dump, "ImageLoad v20"),
	      "A16 IMAGE_LOAD did not lower to image-load IR");
	Check(Common::ContainsStr(result.ir_dump, "image_flags=0x80"),
	      "A16 IMAGE_LOAD did not preserve A16 memory metadata");
	Check(Common::ContainsStr(result.ir_dump, "image_addr=2"),
	      "A16 IMAGE_LOAD did not preserve logical address component count");
	Check(!SpirvContainsExtInst(result.spirv, 62),
	      "image ops without sampler use u16 A16 addresses and must not unpack f16");
	Check(SpirvContainsOpcode(result.spirv, 194),
	      "u16 A16 high-half coordinate extraction is missing");
	Check(SpirvContainsOpcode(result.spirv, 199), "u16 A16 low-half coordinate masking is missing");
	Check(SpirvContainsOpcode(result.spirv, 95), "A16 IMAGE_LOAD did not emit an image fetch");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerPixelImageSampleLodSelection() {
	constexpr uint32_t OpImageSampleImplicitLod     = 87;
	constexpr uint32_t OpImageSampleExplicitLod     = 88;
	constexpr uint32_t OpImageSampleDrefImplicitLod = 89;
	constexpr uint32_t OpImageSampleDrefExplicitLod = 90;

	auto compile = [](uint32_t opcode, uint32_t dmask) {
		const uint32_t shader[] = {
		    EncodeMimg0(opcode, dmask),
		    EncodeMimg1(0, 0, 0, 4),
		    0xbf810000u,
		};

		auto ps_info = RegressionPixelInputInfo();

		auto                             user_data = ImageTestUserData();
		ShaderRecompiler::CompileOptions options;
		options.stage            = ShaderType::Pixel;
		options.pixel_input_info = &ps_info;
		options.dump_ir          = true;
		options.user_data        = user_data.data();

		ShaderRecompiler::CompileResult result;
		std::string                     error;
		Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
		CheckSpirvBinaryValidates(result.spirv);
		return result;
	};

	{
		const auto result = compile(0x20, 0xf); // image_sample
		Check(Common::ContainsStr(result.decoded_dump, "image_sample"),
		      "plain pixel IMAGE_SAMPLE did not decode");
		Check(SpirvInstructionOpcodeCount(result.spirv, OpImageSampleImplicitLod) == 1,
		      "plain pixel IMAGE_SAMPLE must use OpImageSampleImplicitLod");
		Check(SpirvInstructionOpcodeCount(result.spirv, OpImageSampleExplicitLod) == 0,
		      "plain pixel IMAGE_SAMPLE unexpectedly used explicit lod");
	}
	{
		const auto result = compile(0x25, 0xf); // image_sample_b
		Check(Common::ContainsStr(result.decoded_dump, "sample_flags=bias"),
		      "pixel IMAGE_SAMPLE_B did not preserve bias metadata");
		Check(SpirvInstructionOpcodeCount(result.spirv, OpImageSampleImplicitLod) == 1,
		      "pixel IMAGE_SAMPLE_B must use implicit lod with a bias operand");
		Check(SpirvInstructionOpcodeCount(result.spirv, OpImageSampleExplicitLod) == 0,
		      "pixel IMAGE_SAMPLE_B unexpectedly used explicit lod");
	}
	{
		const auto result = compile(0x27, 0xf); // image_sample_lz
		Check(SpirvInstructionOpcodeCount(result.spirv, OpImageSampleExplicitLod) == 1,
		      "pixel IMAGE_SAMPLE_LZ must use explicit lod");
	}
	{
		const auto result = compile(0x28, 0x1); // image_sample_c
		Check(SpirvInstructionOpcodeCount(result.spirv, OpImageSampleDrefImplicitLod) == 1,
		      "pixel IMAGE_SAMPLE_C must use OpImageSampleDrefImplicitLod");
		Check(SpirvInstructionOpcodeCount(result.spirv, OpImageSampleDrefExplicitLod) == 0,
		      "pixel IMAGE_SAMPLE_C unexpectedly used explicit dref lod");
	}
	{
		const auto result = compile(0x2f, 0x1); // image_sample_c_lz
		Check(SpirvInstructionOpcodeCount(result.spirv, OpImageSampleDrefExplicitLod) == 1,
		      "pixel IMAGE_SAMPLE_C_LZ must use explicit dref lod");
	}
}

void TestNewShaderRecompilerImageViewDimensions() {
	constexpr uint32_t MimgDim3D      = 2;
	constexpr uint32_t MimgDim2DArray = 5;
	constexpr uint32_t SpirvDim2D     = 1;
	constexpr uint32_t SpirvDim3D     = 2;

	const uint32_t shader[] = {
	    EncodeMimg0(0x20, 0xf, false, MimgDim2DArray),
	    EncodeMimg1(0, 0, 1, 0), // image_sample 2D array
	    EncodeMimg0(0x24, 0xf, false, MimgDim2DArray),
	    EncodeMimg1(4, 1, 1, 4), // image_sample_l 2D array
	    EncodeMimg0(0x20, 0xf, false, MimgDim3D),
	    EncodeMimg1(8, 2, 1, 8), // image_sample 3D
	    EncodeMimg0(0x01, 0xf, false, MimgDim2DArray),
	    EncodeMimg1(12, 3, 0, 12), // image_load_mip 2D array
	    0xbf810000u,
	};

	auto ps_info   = RegressionPixelInputInfo();
	auto user_data = ImageTestUserData();
	SetImageTestType(&user_data, 0, Prospero::ImageType::kColor2DArray);
	SetImageTestType(&user_data, 1, Prospero::ImageType::kColor2DArray);
	SetImageTestType(&user_data, 2, Prospero::ImageType::kColor3D);
	SetImageTestType(&user_data, 3, Prospero::ImageType::kColor2DArray);

	ShaderRecompiler::CompileOptions options;
	options.stage            = ShaderType::Pixel;
	options.pixel_input_info = &ps_info;
	options.dump_ir          = true;
	options.user_data        = user_data.data();

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	const auto compiled = ShaderRecompiler::TryRecompile(shader, options, result, &error);
	Check(compiled, error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "image_dim=2d_array"),
	      "MIMG DIM did not decode 2D-array image view");
	Check(Common::ContainsStr(result.decoded_dump, "image_dim=3d"),
	      "MIMG DIM did not decode 3D image view");
	Check(Common::ContainsStr(result.decoded_dump, "image_sample_l"),
	      "2D-array LOD sample did not decode");
	Check(Common::ContainsStr(result.decoded_dump,
	                          "image_dim=2d_array sample_flags=lod addr_components=4"),
	      "2D-array LOD sample did not include slice plus LOD address components");
	Check(Common::ContainsStr(result.ir_dump, "image_dim=2d_array"),
	      "2D-array image view did not survive into IR metadata");
	Check(Common::ContainsStr(result.ir_dump, "image_dim=3d"),
	      "3D image view did not survive into IR metadata");
	Check(SpirvContainsTypeImage(result.spirv, SpirvDim2D, 1, 1),
	      "SPIR-V binary does not contain sampled 2D-array image type");
	Check(SpirvContainsTypeImage(result.spirv, SpirvDim3D, 0, 1),
	      "SPIR-V binary does not contain sampled 3D image type");
	Check(SpirvContainsOpcode(result.spirv, 95),
	      "SPIR-V binary does not contain array image fetch");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerImageGatherVariants() {
	const uint32_t shader[] = {
	    EncodeMimg0(0x47, 0x1),
	    EncodeMimg1(60, 0, 1, 4), // image_gather4_lz
	    EncodeMimg0(0x48, 0x2),
	    EncodeMimg1(64, 0, 1, 8), // image_gather4_c
	    EncodeMimg0(0x4f, 0x1),
	    EncodeMimg1(68, 0, 1, 12), // image_gather4_c_lz
	    EncodeMimg0(0x57, 0x4),
	    EncodeMimg1(72, 0, 1, 16), // image_gather4_lz_o
	    EncodeMimg0(0x58, 0x1),
	    EncodeMimg1(76, 0, 1, 20), // image_gather4_c_o
	    EncodeMimg0(0x5f, 0x1),
	    EncodeMimg1(80, 0, 1, 24), // image_gather4_c_lz_o
	    0xbf810000u,
	};

	auto                             user_data = ImageTestUserData();
	ShaderRecompiler::CompileOptions options;
	options.stage     = ShaderType::Compute;
	options.dump_ir   = true;
	options.user_data = user_data.data();

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "image_gather4_lz"),
	      "new decoder did not decode IMAGE_GATHER4_LZ");
	Check(Common::ContainsStr(result.decoded_dump, "image_gather4_lz_o"),
	      "new decoder did not decode IMAGE_GATHER4_LZ_O");
	Check(Common::ContainsStr(result.decoded_dump, "image_gather4_c"),
	      "new decoder did not decode IMAGE_GATHER4_C");
	Check(Common::ContainsStr(result.decoded_dump, "image_gather4_c_lz"),
	      "new decoder did not decode IMAGE_GATHER4_C_LZ");
	Check(Common::ContainsStr(result.decoded_dump, "image_gather4_c_o"),
	      "new decoder did not decode IMAGE_GATHER4_C_O");
	Check(Common::ContainsStr(result.decoded_dump, "image_gather4_c_lz_o"),
	      "new decoder did not decode IMAGE_GATHER4_C_LZ_O");
	Check(Common::ContainsStr(result.decoded_dump, "dmask=0x4"),
	      "IMAGE_GATHER4_LZ_O did not preserve gather component dmask");
	Check(Common::ContainsStr(result.decoded_dump,
	                          "sample_flags=offset|level_zero addr_components=3"),
	      "IMAGE_GATHER4_LZ_O did not expose shared offset sample metadata");
	Check(Common::ContainsStr(result.decoded_dump, "sample_flags=compare addr_components=3"),
	      "IMAGE_GATHER4_C did not expose compare sample metadata");
	Check(Common::ContainsStr(result.decoded_dump,
	                          "sample_flags=compare|level_zero addr_components=3"),
	      "IMAGE_GATHER4_C_LZ did not expose compare+level-zero sample metadata");
	Check(Common::ContainsStr(result.decoded_dump, "sample_flags=compare|offset addr_components=4"),
	      "IMAGE_GATHER4_C_O did not expose compare+offset sample metadata");
	Check(Common::ContainsStr(result.decoded_dump,
	                          "sample_flags=compare|offset|level_zero addr_components=4"),
	      "IMAGE_GATHER4_C_LZ_O did not expose compare+offset+level-zero sample metadata");
	Check(Common::ContainsStr(result.ir_dump, "ImageGather4 v60"),
	      "IMAGE_GATHER4_LZ did not lower to shared IR ImageGather4");
	Check(Common::ContainsStr(result.ir_dump, "ImageGather4 v64"),
	      "IMAGE_GATHER4_C did not lower to shared IR ImageGather4");
	Check(Common::ContainsStr(result.ir_dump, "ImageGather4 v68"),
	      "IMAGE_GATHER4_C_LZ did not lower to shared IR ImageGather4");
	Check(Common::ContainsStr(result.ir_dump, "ImageGather4 v72"),
	      "IMAGE_GATHER4_LZ_O did not lower to shared IR ImageGather4");
	Check(Common::ContainsStr(result.ir_dump, "ImageGather4 v76"),
	      "IMAGE_GATHER4_C_O did not lower to shared IR ImageGather4");
	Check(Common::ContainsStr(result.ir_dump, "ImageGather4 v80"),
	      "IMAGE_GATHER4_C_LZ_O did not lower to shared IR ImageGather4");
	Check(Common::ContainsStr(result.ir_dump, "data_dwords=4"),
	      "IMAGE_GATHER4 did not preserve four-component result metadata");
	Check(Common::ContainsStr(result.ir_dump, "image_flags=0x30"),
	      "IMAGE_GATHER4_LZ_O flags did not survive into IR memory metadata");
	Check(Common::ContainsStr(result.ir_dump, "image_flags=0x8"),
	      "IMAGE_GATHER4_C flags did not survive into IR memory metadata");
	Check(Common::ContainsStr(result.ir_dump, "image_flags=0x28"),
	      "IMAGE_GATHER4_C_LZ flags did not survive into IR memory metadata");
	Check(Common::ContainsStr(result.ir_dump, "image_flags=0x18"),
	      "IMAGE_GATHER4_C_O flags did not survive into IR memory metadata");
	Check(Common::ContainsStr(result.ir_dump, "image_flags=0x38"),
	      "IMAGE_GATHER4_C_LZ_O flags did not survive into IR memory metadata");
	Check(SpirvContainsCapability(result.spirv, 25),
	      "SPIR-V binary does not request ImageGatherExtended");
	Check(SpirvContainsOpcode(result.spirv, 96), "SPIR-V binary does not contain OpImageGather");
	Check(SpirvContainsOpcode(result.spirv, 97),
	      "SPIR-V binary does not contain OpImageDrefGather");
	Check(SpirvContainsOpcode(result.spirv, 202),
	      "SPIR-V binary does not contain packed gather offset extraction");
	Check(SpirvContainsOpcode(result.spirv, 81),
	      "SPIR-V binary does not contain gather result extraction");
	Check(SpirvContainsOpcode(result.spirv, 124),
	      "SPIR-V binary does not contain gather result bitcast");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerImageLoadVariants() {
	const uint32_t shader[] = {
	    EncodeMimg0(0x00, 0x3),
	    EncodeMimg1(8, 0, 0, 1), // image_load dmask xy
	    EncodeMimg0(0x01, 0xf),
	    EncodeMimg1(12, 1, 0, 4), // image_load_mip dmask xyzw
	    0xbf810000u,
	};

	auto                             user_data = ImageTestUserData();
	ShaderRecompiler::CompileOptions options;
	options.stage     = ShaderType::Compute;
	options.dump_ir   = true;
	options.user_data = user_data.data();

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "image_load"),
	      "new decoder did not decode MIMG image load");
	Check(Common::ContainsStr(result.decoded_dump, "image_load_mip"),
	      "new decoder did not decode MIMG image load mip");
	Check(Common::ContainsStr(result.decoded_dump, "dmask=0x3"),
	      "image_load decode did not preserve partial dmask");
	Check(Common::ContainsStr(result.decoded_dump, "dmask=0xf"),
	      "image_load_mip decode did not preserve full dmask");
	Check(Common::ContainsStr(result.ir_dump, "ImageLoad v8"),
	      "image_load did not lower through shared image-load IR");
	Check(Common::ContainsStr(result.ir_dump, "ImageLoad v12"),
	      "image_load_mip did not lower through shared image-load IR");
	Check(Common::ContainsStr(result.ir_dump, "data_dwords=2"),
	      "image_load dmask xy did not preserve two-component result metadata");
	Check(Common::ContainsStr(result.ir_dump, "data_dwords=4"),
	      "image_load_mip dmask xyzw did not preserve four-component result metadata");
	Check(Common::ContainsStr(result.ir_dump, "image_addr=3 image_mip=1"),
	      "image_load_mip did not preserve mip address component metadata");
	Check(SpirvContainsOpcode(result.spirv, 95), "SPIR-V binary does not contain OpImageFetch");
	Check(SpirvContainsOpcode(result.spirv, 81),
	      "SPIR-V binary does not contain OpCompositeExtract for image-load dmask");
	Check(SpirvContainsOpcode(result.spirv, 124),
	      "SPIR-V binary does not contain OpBitcast for image-load result bits");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerImageStoreLowering() {
	const uint32_t shader[] = {
	    EncodeMimg0(0x08, 0xf),
	    EncodeMimg1(20, 0, 0, 4), // image_store
	    EncodeMimg0(0x09, 0x3),
	    EncodeMimg1(24, 1, 0, 8), // image_store_mip
	    0xbf810000u,
	};

	auto                             user_data = ImageTestUserData();
	ShaderRecompiler::CompileOptions options;
	options.stage     = ShaderType::Compute;
	options.dump_ir   = true;
	options.user_data = user_data.data();

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "image_store"),
	      "new decoder did not decode MIMG image store");
	Check(Common::ContainsStr(result.decoded_dump, "image_store_mip"),
	      "new decoder did not decode MIMG image store mip");
	Check(Common::ContainsStr(result.decoded_dump, "dmask=0xf"),
	      "image store decode did not preserve full dmask");
	Check(Common::ContainsStr(result.decoded_dump, "dmask=0x3"),
	      "image store mip decode did not preserve partial dmask");
	Check(Common::ContainsStr(result.ir_dump, "ImageStore null, v20, v4"),
	      "image_store did not lower to shared image-store IR");
	Check(Common::ContainsStr(result.ir_dump, "ImageStore null, v24, v8"),
	      "image_store_mip did not lower to shared image-store IR");
	Check(Common::ContainsStr(result.ir_dump, "storage_image"),
	      "image store IR did not use storage-image resource metadata");
	Check(Common::ContainsStr(result.ir_dump, "data_dwords=4"),
	      "full-dmask image store did not preserve data component count");
	Check(Common::ContainsStr(result.ir_dump, "image_addr=3 image_mip=1"),
	      "mip image store did not preserve mip address component count");
	Check(SpirvContainsOpcode(result.spirv, 99), "SPIR-V binary does not contain OpImageWrite");
	Check(SpirvContainsOpcode(result.spirv, 80),
	      "SPIR-V binary does not contain image texel/coordinate composites");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerStorageImage3DDescriptorVariant() {
	constexpr uint32_t ImageTypeColor3D   = 10;
	constexpr uint32_t ImageFormatRgba16f = 71;
	constexpr uint32_t SpirvDim3D         = 2;

	constexpr uint32_t MimgDim3D = 2;

	const uint32_t shader[] = {
	    EncodeMimg0(0x08, 0xf, false, MimgDim3D),
	    EncodeMimg1(20, 5, 0, 4), // image_store 3D
	    0xbf810000u,
	};

	ShaderComputeInputInfo   input_info = RegressionComputeInputInfo();
	std::array<uint32_t, 64> user_data {};
	user_data[20] = 0x1000u;
	user_data[21] = ImageFormatRgba16f << 20u;
	user_data[22] = 255u | (255u << 14u);
	user_data[23] = ImageTypeColor3D << 28u;

	ShaderRecompiler::CompileOptions options;
	options.stage              = ShaderType::Compute;
	options.dump_ir            = true;
	options.compute_input_info = &input_info;
	options.user_data          = user_data.data();

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "image_dim=3d"),
	      "MIMG store did not decode the RDNA2 3D instruction dimension");
	const auto has_3d_store = std::any_of(
	    result.program.blocks.begin(), result.program.blocks.end(), [](const auto& block) {
		    return std::any_of(block.instructions.begin(), block.instructions.end(),
		                       [](const auto& inst) {
			                       return inst.op == ShaderRecompiler::IR::Opcode::ImageStore &&
			                              inst.memory.image_dimension ==
			                                  ShaderRecompiler::Decoder::ImageDimension::Dim3D &&
			                              inst.memory.image_address_components == 3;
		                       });
	    });
	Check(has_3d_store, "3D storage image store did not preserve three address components");
	Check(SpirvContainsTypeImage(result.spirv, SpirvDim3D, 0, 2),
	      "SPIR-V binary does not contain storage 3D image type");
	CheckSpirvBinaryValidates(result.spirv);

	const auto source = DisassembleSpirvBinary(result.spirv);
	Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "textures2D_L_3D"),
	      "storage image store did not access the 3D storage descriptor binding");
}

void TestNewShaderRecompilerStorageImage2DDescriptorOverridesMimg3D() {
	constexpr uint32_t ImageTypeColor2D   = 9;
	constexpr uint32_t ImageFormatRgba16f = 71;
	constexpr uint32_t MimgDim3D          = 2;
	constexpr uint32_t SpirvDim2D         = 1;

	const uint32_t shader[] = {
	    EncodeMimg0(0x08, 0xf, false, MimgDim3D),
	    EncodeMimg1(20, 5, 0, 4),
	    0xbf810000u,
	};

	ShaderComputeInputInfo   input_info = RegressionComputeInputInfo();
	std::array<uint32_t, 64> user_data {};
	user_data[20] = 0x1000u;
	user_data[21] = ImageFormatRgba16f << 20u;
	user_data[22] = 255u | (255u << 14u);
	user_data[23] = ImageTypeColor2D << 28u;

	ShaderRecompiler::CompileOptions options;
	options.stage              = ShaderType::Compute;
	options.dump_ir            = true;
	options.compute_input_info = &input_info;
	options.user_data          = user_data.data();

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "image_dim=3d"),
	      "test MIMG store should decode as a 3D instruction");
	Check(SpirvContainsTypeImage(result.spirv, SpirvDim2D, 0, 2),
	      "SPIR-V binary does not contain storage 2D image type");
	CheckSpirvBinaryValidates(result.spirv);

	const auto source = DisassembleSpirvBinary(result.spirv);
	Check(SpirvSourceHasInstructionUsing(source, "OpAccessChain", "textures2D_L"),
	      "2D descriptor storage image store did not access the base storage binding");
	Check(!SpirvSourceHasInstructionUsing(source, "OpAccessChain", "textures2D_L_A"),
	      "2D descriptor storage image store unexpectedly used the array storage binding");
	Check(!SpirvSourceHasInstructionUsing(source, "OpAccessChain", "textures2D_L_3D"),
	      "2D descriptor storage image store unexpectedly used the 3D storage binding");
}

void TestNewShaderRecompilerImageAtomicLowering() {
	const uint32_t shader[] = {
	    EncodeMimg0(0x11, 0x1, true),
	    EncodeMimg1(52, 0, 0, 1), // image_atomic_add
	    EncodeMimg0(0x15, 0x1, true),
	    EncodeMimg1(53, 0, 0, 1), // image_atomic_umin
	    EncodeMimg0(0x17, 0x1, true),
	    EncodeMimg1(57, 0, 0, 1), // image_atomic_umax
	    EncodeMimg0(0x18, 0x1, true),
	    EncodeMimg1(54, 0, 0, 1), // image_atomic_and
	    EncodeMimg0(0x19, 0x1, true),
	    EncodeMimg1(55, 0, 0, 1), // image_atomic_or
	    EncodeMimg0(0x1a, 0x1, true),
	    EncodeMimg1(56, 0, 0, 1), // image_atomic_xor
	    0xbf810000u,
	};

	auto                             user_data = ImageTestUserData();
	ShaderRecompiler::CompileOptions options;
	options.stage     = ShaderType::Compute;
	options.dump_ir   = true;
	options.user_data = user_data.data();

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "image_atomic_add"),
	      "new decoder did not decode MIMG image atomic add");
	Check(Common::ContainsStr(result.decoded_dump, "image_atomic_umin"),
	      "new decoder did not decode MIMG image atomic umin");
	Check(Common::ContainsStr(result.decoded_dump, "image_atomic_umax"),
	      "new decoder did not decode MIMG image atomic umax");
	Check(Common::ContainsStr(result.decoded_dump, "image_atomic_and"),
	      "new decoder did not decode MIMG image atomic and");
	Check(Common::ContainsStr(result.decoded_dump, "image_atomic_or"),
	      "new decoder did not decode MIMG image atomic or");
	Check(Common::ContainsStr(result.decoded_dump, "image_atomic_xor"),
	      "new decoder did not decode MIMG image atomic xor");
	Check(Common::ContainsStr(result.decoded_dump, "dmask=0x1"),
	      "image atomic decode did not preserve dmask metadata");
	Check(Common::ContainsStr(result.ir_dump, "AtomicAddU32 v52, v52, v1"),
	      "image_atomic_add did not lower through shared atomic IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicUMinU32 v53, v53, v1"),
	      "image_atomic_umin did not lower through shared atomic IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicUMaxU32 v57, v57, v1"),
	      "image_atomic_umax did not lower through shared atomic IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicAndU32 v54, v54, v1"),
	      "image_atomic_and did not lower through shared atomic IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicOrU32 v55, v55, v1"),
	      "image_atomic_or did not lower through shared atomic IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicXorU32 v56, v56, v1"),
	      "image_atomic_xor did not lower through shared atomic IR");
	Check(Common::ContainsStr(result.ir_dump, "storage_image_uint"),
	      "image atomic IR did not use uint storage-image metadata");
	Check(SpirvContainsOpcode(result.spirv, 60),
	      "SPIR-V binary does not contain OpImageTexelPointer");
	Check(SpirvContainsOpcode(result.spirv, 234), "SPIR-V binary does not contain OpAtomicIAdd");
	Check(SpirvContainsOpcode(result.spirv, 237), "SPIR-V binary does not contain OpAtomicUMin");
	Check(SpirvContainsOpcode(result.spirv, 239), "SPIR-V binary does not contain OpAtomicUMax");
	Check(SpirvContainsOpcode(result.spirv, 240), "SPIR-V binary does not contain OpAtomicAnd");
	Check(SpirvContainsOpcode(result.spirv, 241), "SPIR-V binary does not contain OpAtomicOr");
	Check(SpirvContainsOpcode(result.spirv, 242), "SPIR-V binary does not contain OpAtomicXor");
	Check(SpirvContainsOpcode(result.spirv, 225),
	      "image atomic SPIR-V binary does not contain OpMemoryBarrier");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerVintrpLowering() {
	const uint32_t shader[] = {
	    EncodeVintrp(0, 10, 1, 2, 4), // v_interp_p1_f32 v10, v4, attr1.z
	    EncodeVintrp(1, 11, 1, 2, 4), // v_interp_p2_f32 v11, v4, attr1.z
	    EncodeVintrp(2, 12, 0, 3, 2), // v_interp_mov_f32 v12, p0, attr0.w
	    0xbf810000u,
	};

	ShaderPixelInputInfo ps_info {};
	ps_info.input_num = 2;
	SetIdentityInterpolatorSettings(&ps_info);

	ShaderRecompiler::CompileOptions options;
	options.stage            = ShaderType::Pixel;
	options.pixel_input_info = &ps_info;
	options.dump_ir          = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "v_interp_p1_f32"),
	      "new decoder did not decode VINTRP P1");
	Check(Common::ContainsStr(result.decoded_dump, "v_interp_p2_f32"),
	      "new decoder did not decode VINTRP P2");
	Check(Common::ContainsStr(result.decoded_dump, "v_interp_mov_f32"),
	      "new decoder did not decode VINTRP MOV");
	Check(Common::ContainsStr(result.ir_dump, "ControlNop"),
	      "VINTRP P1 did not lower to an explicit no-op marker");
	Check(Common::ContainsStr(result.ir_dump, "LoadInputF32 v11"),
	      "VINTRP P2 did not lower to input-load IR");
	Check(Common::ContainsStr(result.ir_dump, "input_attr=1 input_chan=2"),
	      "VINTRP P2 did not preserve attr/channel metadata");
	Check(Common::ContainsStr(result.ir_dump, "LoadInputF32 v12"),
	      "VINTRP MOV did not lower to input-load IR");
	Check(Common::ContainsStr(result.ir_dump, "input_attr=0 input_chan=3"),
	      "VINTRP MOV did not preserve attr/channel metadata");
	Check(ProgramInputCount(result.program, ShaderRecompiler::IR::StageInputKind::Parameter) == 2,
	      "VINTRP pixel shader did not reflect parameter inputs");
	Check(SpirvContainsOpcode(result.spirv, 61), "SPIR-V binary does not contain OpLoad");
	Check(SpirvContainsOpcode(result.spirv, 62), "SPIR-V binary does not contain OpStore");
	Check(SpirvContainsOpcode(result.spirv, 81),
	      "SPIR-V binary does not contain input component extraction");
	Check(SpirvContainsOpcode(result.spirv, 124), "SPIR-V binary does not contain input bitcast");
	CheckSpirvBinaryValidates(result.spirv);

	const uint32_t remapped_shader[] = {
	    EncodeVintrp(1, 11, 2, 0, 4), // v_interp_p2_f32 v11, v4, attr2.x
	    0xbf810000u,
	};
	ShaderPixelInputInfo remapped_ps_info {};
	remapped_ps_info.input_num = 3;
	SetIdentityInterpolatorSettings(&remapped_ps_info);
	remapped_ps_info.interpolator_settings[2] = 3;

	options.pixel_input_info = &remapped_ps_info;

	ShaderRecompiler::CompileResult remapped_result;
	Check(ShaderRecompiler::TryRecompile(remapped_shader, options, remapped_result, &error),
	      error.c_str());
	Check(Common::ContainsStr(remapped_result.ir_dump, "input_attr=2 input_chan=0"),
	      "remapped VINTRP did not preserve raw pixel attribute metadata");
	Check(SpirvHasDecorationValue(remapped_result.spirv, 30u, 3u),
	      "remapped VINTRP did not use SPI_PS_INPUT_CNTL input offset as Location");
	Check(!SpirvHasDecorationValue(remapped_result.spirv, 30u, 2u),
	      "remapped VINTRP still emitted the raw attribute as Location");
	CheckSpirvBinaryValidates(remapped_result.spirv);

	const uint32_t duplicate_location_shader[] = {
	    EncodeVintrp(0, 6, 1, 0, 4),  // v_interp_p1_f32 v6, v4, attr1.x
	    EncodeVintrp(0, 10, 0, 0, 4), // v_interp_p1_f32 v10, v4, attr0.x
	    EncodeVintrp(1, 6, 1, 0, 4),  // v_interp_p2_f32 v6, v4, attr1.x
	    EncodeVintrp(1, 10, 0, 0, 4), // v_interp_p2_f32 v10, v4, attr0.x
	    0xbf810000u,
	};
	ShaderPixelInputInfo duplicate_ps_info {};
	duplicate_ps_info.input_num = 2;

	options.pixel_input_info = &duplicate_ps_info;

	ShaderRecompiler::CompileResult duplicate_result;
	Check(ShaderRecompiler::TryRecompile(duplicate_location_shader, options, duplicate_result,
	                                     &error),
	      error.c_str());
	Check(ProgramInputCount(duplicate_result.program,
	                        ShaderRecompiler::IR::StageInputKind::Parameter) == 2,
	      "duplicate-location VINTRP shader did not reflect both raw parameter attrs");
	Check(SpirvDecorationValueCount(duplicate_result.spirv, 30u, 0u) == 1,
	      "duplicate-location VINTRP emitted more than one Location 0 input");
	Check(SpirvDecorationValueCount(duplicate_result.spirv, 30u, 1u) == 1,
	      "duplicate-location VINTRP did not fall back attr1 to Location 1");
	CheckSpirvBinaryValidates(duplicate_result.spirv);

	const uint32_t flat_shader[] = {
	    EncodeVintrp(2, 12, 0, 0, 2), // v_interp_mov_f32 v12, p0, attr0.x
	    0xbf810000u,
	};
	ShaderPixelInputInfo flat_ps_info {};
	flat_ps_info.input_num = 1;
	SetIdentityInterpolatorSettings(&flat_ps_info);
	flat_ps_info.interpolator_settings[0] = 0x00000400u;

	options.pixel_input_info = &flat_ps_info;

	ShaderRecompiler::CompileResult flat_result;
	Check(ShaderRecompiler::TryRecompile(flat_shader, options, flat_result, &error),
	      error.c_str());
	Check(SpirvHasDecorationValueWithDecoration(flat_result.spirv, 30u, 0u, 14u),
	      "flat VINTRP input did not emit a Flat decoration");
	Check(!SpirvHasDecorationValueWithDecoration(flat_result.spirv, 30u, 0u, 13u),
	      "flat VINTRP input should not also emit NoPerspective");
	Check(SpirvHasDecorationValue(flat_result.spirv, 30u, 0u),
	      "flat VINTRP input did not preserve Location 0");
	CheckSpirvBinaryValidates(flat_result.spirv);

	ShaderPixelInputInfo no_perspective_ps_info {};
	no_perspective_ps_info.input_num         = 1;
	no_perspective_ps_info.ps_no_perspective = true;
	SetIdentityInterpolatorSettings(&no_perspective_ps_info);

	options.pixel_input_info = &no_perspective_ps_info;

	ShaderRecompiler::CompileResult no_perspective_result;
	Check(ShaderRecompiler::TryRecompile(flat_shader, options, no_perspective_result, &error),
	      error.c_str());
	Check(SpirvHasDecorationValueWithDecoration(no_perspective_result.spirv, 30u, 0u, 13u),
	      "no-perspective VINTRP input did not emit a NoPerspective decoration");
	Check(!SpirvHasDecorationValueWithDecoration(no_perspective_result.spirv, 30u, 0u, 14u),
	      "non-flat no-perspective VINTRP input should not emit Flat");
	CheckSpirvBinaryValidates(no_perspective_result.spirv);
}

void TestGraphicsCreateInterpolantMapping() {
	ShaderRegister regs[32] = {};

	Check(Gen5::GraphicsCreateInterpolantMapping(regs, nullptr, nullptr) == 0,
	      "null pixel shader interpolant mapping failed");
	for (uint32_t i = 0; i < 32u; i++) {
		Check(regs[i].offset == Pm4::SPI_PS_INPUT_CNTL_0 + i,
		      "identity interpolant register offset was unexpected");
		Check(regs[i].value == i, "identity interpolant register value was unexpected");
	}

	ShaderSemantic gs_semantics[3]   = {};
	gs_semantics[0].semantic         = 7;
	gs_semantics[0].hardware_mapping = 5;
	gs_semantics[1].semantic         = 9;
	gs_semantics[1].hardware_mapping = 12;
	gs_semantics[2].semantic         = 10;
	gs_semantics[2].hardware_mapping = 4;
	gs_semantics[2].is_f16           = 1;

	Shader gs {};
	gs.output_semantics     = gs_semantics;
	gs.num_output_semantics = static_cast<uint16_t>(std::size(gs_semantics));

	ShaderSemantic ps_semantics[4]   = {};
	ps_semantics[0].semantic         = 7;
	ps_semantics[1].semantic         = 8;
	ps_semantics[1].default_value    = 2;
	ps_semantics[2].semantic         = 9;
	ps_semantics[2].is_flat_shaded   = 1;
	ps_semantics[2].is_custom        = 1;
	ps_semantics[2].default_value    = 1;
	ps_semantics[3].semantic         = 10;
	ps_semantics[3].is_f16           = 1;
	ps_semantics[3].default_value    = 3;
	ps_semantics[3].default_value_hi = 2;

	Shader ps {};
	ps.input_semantics     = ps_semantics;
	ps.num_input_semantics = static_cast<uint32_t>(std::size(ps_semantics));

	Check(Gen5::GraphicsCreateInterpolantMapping(regs, &gs, &ps) == 0,
	      "shader interpolant mapping failed");
	Check(regs[0].value == 0x00000005u,
	      "matched interpolant mapping did not use GS hardware mapping");
	Check(regs[1].value == 0x00000220u, "missing interpolant mapping did not use PS default value");
	Check(regs[2].value == 0x0000052cu, "flat/custom interpolant mapping bits were unexpected");
	Check(regs[3].value == 0x01580304u, "f16 interpolant mapping bits were unexpected");
	Check(regs[4].offset == Pm4::SPI_PS_INPUT_CNTL_0 + 4u && regs[4].value == 4u,
	      "interpolant identity tail was not filled");
}

void TestNewShaderRecompilerWideMemoryLowering() {
	const uint32_t shader[] = {
	    EncodeSmem0(0x02, 4, 4),
	    0u, // s_load_dwordx4 s[4:7]
	    EncodeSmem0(0x0a, 8, 4),
	    0u, // s_buffer_load_dwordx4 s[8:11]
	    EncodeSmem0(0x09, 106, 4),
	    0u, // s_buffer_load_dwordx2 vcc_lo
	    EncodeMubuf0(0x0d, 16),
	    EncodeMubuf1(20, 0, 1), // buffer_load_dwordx2 v[20:21]
	    EncodeMubuf0(0x0f, 32),
	    EncodeMubuf1(24, 0, 1), // buffer_load_dwordx3 v[24:26]
	    EncodeMubuf0(0x0e, 48),
	    EncodeMubuf1(28, 0, 1), // buffer_load_dwordx4 v[28:31]
	    EncodeMubuf0(0x1d, 64),
	    EncodeMubuf1(32, 0, 1), // buffer_store_dwordx2 v[32:33]
	    EncodeMubuf0(0x1f, 80),
	    EncodeMubuf1(36, 0, 1), // buffer_store_dwordx3 v[36:38]
	    EncodeMubuf0(0x1e, 96),
	    EncodeMubuf1(40, 0, 1), // buffer_store_dwordx4 v[40:43]
	    EncodeMubuf0(0x08, 2),
	    EncodeMubuf1(44, 0, 1), // buffer_load_ubyte v44
	    EncodeMubuf0(0x0a, 4),
	    EncodeMubuf1(45, 0, 1), // buffer_load_ushort v45
	    EncodeFlat0(0x0c, 0, 4),
	    EncodeFlat1(50, 0x7d, 0, 1), // flat_load_dword
	    EncodeFlat0(0x0d, 1, 8),
	    EncodeFlat1(52, 0x7d, 0, 1), // scratch_load_dwordx2
	    EncodeFlat0(0x0e, 2, 12),
	    EncodeFlat1(56, 0x7d, 0, 1), // global_load_dwordx4
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage            = ShaderType::Compute;
	options.dump_ir          = true;
	options.flat_memory_base = 0;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "s_load_dwordx4"),
	      "new decoder did not decode SMEM x4 load");
	Check(Common::ContainsStr(result.decoded_dump, "s_buffer_load_dwordx4"),
	      "new decoder did not decode scalar-buffer x4 load");
	Check(Common::ContainsStr(result.decoded_dump, "s_buffer_load_dwordx2 vcc_lo"),
	      "new decoder did not decode scalar-buffer x2 load into VCC");
	Check(Common::ContainsStr(result.decoded_dump, "buffer_load_dwordx2"),
	      "new decoder did not decode buffer x2 load");
	Check(Common::ContainsStr(result.decoded_dump, "buffer_load_dwordx3"),
	      "new decoder did not decode buffer x3 load");
	Check(Common::ContainsStr(result.decoded_dump, "buffer_load_dwordx4"),
	      "new decoder did not decode buffer x4 load");
	Check(Common::ContainsStr(result.decoded_dump, "buffer_store_dwordx4"),
	      "new decoder did not decode buffer x4 store");
	Check(Common::ContainsStr(result.decoded_dump, "buffer_load_ubyte"),
	      "new decoder did not decode buffer ubyte load");
	Check(Common::ContainsStr(result.decoded_dump, "buffer_load_ushort"),
	      "new decoder did not decode buffer ushort load");
	Check(Common::ContainsStr(result.decoded_dump, "flat_load_dword"),
	      "new decoder did not decode flat dword load");
	Check(Common::ContainsStr(result.decoded_dump, "segment=1"),
	      "new decoder did not preserve scratch segment metadata");
	Check(Common::ContainsStr(result.decoded_dump, "segment=2"),
	      "new decoder did not preserve global segment metadata");
	Check(Common::ContainsStr(result.decoded_dump, "dwords=4 bits=32"),
	      "new decoder did not expose width metadata for wide memory ops");
	Check(Common::ContainsStr(result.ir_dump, "SLoadDword s4"),
	      "s_load_dwordx4 did not expand to first scalar dword IR load");
	Check(Common::ContainsStr(result.ir_dump, "SLoadDword s7"),
	      "s_load_dwordx4 did not expand to last scalar dword IR load");
	Check(Common::ContainsStr(result.ir_dump, "SBufferLoadDword s8"),
	      "s_buffer_load_dwordx4 did not expand to first scalar-buffer IR load");
	Check(Common::ContainsStr(result.ir_dump, "SBufferLoadDword s11"),
	      "s_buffer_load_dwordx4 did not expand to last scalar-buffer IR load");
	Check(Common::ContainsStr(result.ir_dump, "SBufferLoadDword vcc_lo"),
	      "s_buffer_load_dwordx2 did not expand to VCC low dword");
	Check(Common::ContainsStr(result.ir_dump, "SBufferLoadDword vcc_hi"),
	      "s_buffer_load_dwordx2 did not expand to VCC high dword");
	Check(Common::ContainsStr(result.ir_dump, "BufferLoadDword v20"),
	      "buffer_load_dwordx2 did not expand to dword IR loads");
	Check(Common::ContainsStr(result.ir_dump, "BufferLoadDword v31"),
	      "buffer_load_dwordx4 did not expand to the last dword IR load");
	Check(Common::ContainsStr(result.ir_dump, "BufferStoreDword null, v40"),
	      "buffer_store_dwordx4 did not expand to first dword IR store");
	Check(Common::ContainsStr(result.ir_dump, "BufferStoreDword null, v43"),
	      "buffer_store_dwordx4 did not expand to last dword IR store");
	Check(Common::ContainsStr(result.ir_dump, "BufferLoadUbyte v44"),
	      "buffer_load_ubyte did not lower to sub-dword IR load");
	Check(Common::ContainsStr(result.ir_dump, "BufferLoadUshort v45"),
	      "buffer_load_ushort did not lower to sub-dword IR load");
	Check(Common::ContainsStr(result.ir_dump, "FlatLoadDword v50"),
	      "flat_load_dword did not lower to flat dword IR load");
	Check(Common::ContainsStr(result.ir_dump, "scratch"),
	      "scratch segment load did not lower with scratch metadata");
	Check(Common::ContainsStr(result.ir_dump, "global"),
	      "global segment load did not lower with global metadata");
	Check(SpirvContainsOpcode(result.spirv, 65), "SPIR-V binary does not contain OpAccessChain");
	Check(SpirvContainsOpcode(result.spirv, 61), "SPIR-V binary does not contain OpLoad");
	Check(SpirvContainsOpcode(result.spirv, 62), "SPIR-V binary does not contain OpStore");
	Check(SpirvContainsOpcode(result.spirv, 194),
	      "SPIR-V binary does not contain OpShiftRightLogical");
	Check(SpirvContainsOpcode(result.spirv, 196),
	      "SPIR-V binary does not contain OpShiftLeftLogical");
	Check(SpirvContainsOpcode(result.spirv, 199), "SPIR-V binary does not contain OpBitwiseAnd");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerBufferSignedLoadLowering() {
	const uint32_t shader[] = {
	    EncodeMubuf0(0x09, 3), EncodeMubuf1(46, 0, 1), // buffer_load_sbyte
	    EncodeMubuf0(0x0b, 6), EncodeMubuf1(47, 0, 1), // buffer_load_sshort
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "buffer_load_sbyte"),
	      "new decoder did not decode buffer signed byte load");
	Check(Common::ContainsStr(result.decoded_dump, "buffer_load_sshort"),
	      "new decoder did not decode buffer signed short load");
	Check(Common::ContainsStr(result.decoded_dump, "bits=8"),
	      "signed byte buffer load did not preserve bit width metadata");
	Check(Common::ContainsStr(result.decoded_dump, "bits=16"),
	      "signed short buffer load did not preserve bit width metadata");
	Check(Common::ContainsStr(result.decoded_dump, "signed=1"),
	      "signed buffer loads did not preserve signed metadata");
	Check(Common::ContainsStr(result.ir_dump, "BufferLoadSbyte v46"),
	      "buffer_load_sbyte did not lower to signed byte IR load");
	Check(Common::ContainsStr(result.ir_dump, "BufferLoadSshort v47"),
	      "buffer_load_sshort did not lower to signed short IR load");
	Check(SpirvContainsOpcode(result.spirv, 61), "SPIR-V binary does not contain OpLoad");
	Check(SpirvContainsOpcode(result.spirv, 195),
	      "SPIR-V binary does not contain OpShiftRightArithmetic for sign extension");
	Check(SpirvContainsOpcode(result.spirv, 196),
	      "SPIR-V binary does not contain OpShiftLeftLogical for sign extension");
	Check(SpirvContainsOpcode(result.spirv, 199), "SPIR-V binary does not contain OpBitwiseAnd");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerBufferSubDwordStoreLowering() {
	const uint32_t shader[] = {
	    EncodeMubuf0(0x18, 2), EncodeMubuf1(48, 0, 1), // buffer_store_byte
	    EncodeMubuf0(0x1a, 4), EncodeMubuf1(49, 0, 1), // buffer_store_short
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "buffer_store_byte"),
	      "new decoder did not decode buffer byte store");
	Check(Common::ContainsStr(result.decoded_dump, "buffer_store_short"),
	      "new decoder did not decode buffer short store");
	Check(Common::ContainsStr(result.decoded_dump, "bits=8"),
	      "buffer byte store did not preserve bit width metadata");
	Check(Common::ContainsStr(result.decoded_dump, "bits=16"),
	      "buffer short store did not preserve bit width metadata");
	Check(Common::ContainsStr(result.ir_dump, "BufferStoreByte null, v48"),
	      "buffer_store_byte did not lower to byte store IR");
	Check(Common::ContainsStr(result.ir_dump, "BufferStoreShort null, v49"),
	      "buffer_store_short did not lower to short store IR");
	Check(SpirvContainsOpcode(result.spirv, 61),
	      "SPIR-V binary does not contain OpLoad for sub-dword store RMW");
	Check(SpirvContainsOpcode(result.spirv, 62),
	      "SPIR-V binary does not contain OpStore for sub-dword store RMW");
	Check(SpirvContainsOpcode(result.spirv, 197), "SPIR-V binary does not contain OpBitwiseOr");
	Check(SpirvContainsOpcode(result.spirv, 199), "SPIR-V binary does not contain OpBitwiseAnd");
	Check(SpirvContainsOpcode(result.spirv, 200), "SPIR-V binary does not contain OpNot");
	Check(SpirvContainsOpcode(result.spirv, 196),
	      "SPIR-V binary does not contain OpShiftLeftLogical");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerMubufFormatLowering() {
	const uint32_t shader[] = {
	    EncodeMubuf0(0x00, 4),
	    EncodeMubuf1(84, 0, 1), // buffer_load_format_x
	    EncodeMubuf0(0x01, 8),
	    EncodeMubuf1(88, 0, 1), // buffer_load_format_xy
	    EncodeMubuf0(0x02, 12),
	    EncodeMubuf1(92, 0, 1), // buffer_load_format_xyz
	    EncodeMubuf0(0x03, 16),
	    EncodeMubuf1(96, 0, 1), // buffer_load_format_xyzw
	    EncodeMubuf0(0x04, 20),
	    EncodeMubuf1(100, 0, 1), // buffer_store_format_x
	    EncodeMubuf0(0x05, 24),
	    EncodeMubuf1(104, 0, 1), // buffer_store_format_xy
	    EncodeMubuf0(0x06, 28),
	    EncodeMubuf1(108, 0, 1), // buffer_store_format_xyz
	    EncodeMubuf0(0x07, 32),
	    EncodeMubuf1(112, 0, 1), // buffer_store_format_xyzw
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "buffer_load_format_x"),
	      "new decoder did not decode MUBUF format-x load");
	Check(Common::ContainsStr(result.decoded_dump, "buffer_load_format_xyzw"),
	      "new decoder did not decode MUBUF format-xyzw load");
	Check(Common::ContainsStr(result.decoded_dump, "buffer_store_format_x"),
	      "new decoder did not decode MUBUF format-x store");
	Check(Common::ContainsStr(result.decoded_dump, "buffer_store_format_xyzw"),
	      "new decoder did not decode MUBUF format-xyzw store");
	Check(Common::ContainsStr(result.decoded_dump, "typed=0 formatted=1"),
	      "MUBUF format decode did not preserve formatted non-typed metadata");
	Check(Common::ContainsStr(result.ir_dump, "BufferLoadDword v84"),
	      "MUBUF format-x load did not lower through shared buffer load IR");
	Check(Common::ContainsStr(result.ir_dump, "BufferLoadDword v99"),
	      "MUBUF format-xyzw load did not expand to the last shared dword load");
	Check(Common::ContainsStr(result.ir_dump, "BufferStoreDword null, v100"),
	      "MUBUF format-x store did not lower through shared buffer store IR");
	Check(Common::ContainsStr(result.ir_dump, "BufferStoreDword null, v115"),
	      "MUBUF format-xyzw store did not expand to the last shared dword store");
	Check(Common::ContainsStr(result.ir_dump, "typed=0 formatted=1"),
	      "MUBUF format metadata did not survive into IR");
	Check(SpirvContainsOpcode(result.spirv, 65), "SPIR-V binary does not contain OpAccessChain");
	Check(SpirvContainsOpcode(result.spirv, 61), "SPIR-V binary does not contain OpLoad");
	Check(SpirvContainsOpcode(result.spirv, 62), "SPIR-V binary does not contain OpStore");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerFormattedStoreUsesRuntimeArrayLengthOnly() {
	const uint32_t shader[] = {
	    EncodeMubuf0(0x04),
	    EncodeMubuf1(0, 0, 1), // buffer_store_format_x
	    0xbf810000u,
	};

	std::array<uint32_t, 64> user_data {};
	user_data[1] = 4u << 16u;
	user_data[2] = 5u;
	user_data[3] = Prospero::GpuEnumValue(Prospero::BufferFormat::k32UInt) << 12u;

	ShaderRecompiler::CompileOptions options;
	options.stage     = ShaderType::Compute;
	options.dump_ir   = true;
	options.user_data = user_data.data();

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(result.resources.buffers.size() == 1 && result.resources.buffers[0].dwords[2] == 5u,
	      "formatted store test did not preserve descriptor NumRecords");
	Check(Common::ContainsStr(result.decoded_dump, "buffer_store_format_x"),
	      "formatted store regression did not decode buffer_store_format_x");
	Check(Common::ContainsStr(result.ir_dump, "typed=0 formatted=1"),
	      "formatted store regression did not preserve formatted metadata");
	CheckSpirvBinaryValidates(result.spirv);

	const auto source = DisassembleSpirvBinary(result.spirv);
	Check(Common::ContainsStr(source, "OpArrayLength"),
	      "formatted store SPIR-V lacks runtime storage-buffer bounds check");
	Check(!SpirvSourceHasInstructionUsing(source, "OpULessThan", "%uint_5"),
	      "formatted store SPIR-V baked descriptor NumRecords into a store guard");
}

void TestNewShaderRecompilerTypedBufferLowering() {
	const uint32_t shader[] = {
	    EncodeMtbuf0(0x00, 14, 7, 4),
	    EncodeMtbuf1(0x00, 60, 0, 1), // tbuffer_load_format_x
	    EncodeMtbuf0(0x01, 13, 7, 8),
	    EncodeMtbuf1(0x01, 61, 0, 1), // tbuffer_load_format_xy
	    EncodeMtbuf0(0x02, 13, 4, 12),
	    EncodeMtbuf1(0x02, 64, 0, 1), // tbuffer_load_format_xyz
	    EncodeMtbuf0(0x03, 14, 7, 16, true, true),
	    EncodeMtbuf1(0x03, 68, 0, 1), // tbuffer_load_format_xyzw
	    EncodeMtbuf0(0x04, 14, 7, 20),
	    EncodeMtbuf1(0x04, 72, 0, 1), // tbuffer_store_format_x
	    EncodeMtbuf0(0x05, 13, 7, 24),
	    EncodeMtbuf1(0x05, 76, 0, 1), // tbuffer_store_format_xy
	    EncodeMtbuf0(0x07, 14, 7, 28),
	    EncodeMtbuf1(0x07, 80, 0, 1), // tbuffer_store_format_xyzw
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "tbuffer_load_format_x"),
	      "new decoder did not decode typed buffer format-x load");
	Check(Common::ContainsStr(result.decoded_dump, "tbuffer_load_format_xy"),
	      "new decoder did not decode typed buffer format-xy load");
	Check(Common::ContainsStr(result.decoded_dump, "tbuffer_load_format_xyz"),
	      "new decoder did not decode typed buffer format-xyz load");
	Check(Common::ContainsStr(result.decoded_dump, "tbuffer_load_format_xyzw"),
	      "new decoder did not decode typed buffer format-xyzw load");
	Check(Common::ContainsStr(result.decoded_dump, "tbuffer_store_format_x"),
	      "new decoder did not decode typed buffer format-x store");
	Check(Common::ContainsStr(result.decoded_dump, "tbuffer_store_format_xy"),
	      "new decoder did not decode typed buffer format-xy store");
	Check(Common::ContainsStr(result.decoded_dump, "tbuffer_store_format_xyzw"),
	      "new decoder did not decode typed buffer format-xyzw store");
	Check(Common::ContainsStr(result.decoded_dump, "dfmt=14 nfmt=7"),
	      "MTBUF decode did not expose dfmt/nfmt metadata");
	Check(Common::ContainsStr(result.decoded_dump, "typed=1"),
	      "MTBUF decode did not preserve typed metadata");
	Check(Common::ContainsStr(result.decoded_dump, "offen=1"),
	      "MTBUF decode did not preserve offen metadata");
	Check(Common::ContainsStr(result.ir_dump, "BufferLoadDword v60"),
	      "typed buffer x load did not lower through shared buffer load IR");
	Check(Common::ContainsStr(result.ir_dump, "BufferLoadDword v71"),
	      "typed buffer xyzw load did not expand to last shared dword load");
	Check(Common::ContainsStr(result.ir_dump, "BufferStoreDword null, v72"),
	      "typed buffer x store did not lower through shared buffer store IR");
	Check(Common::ContainsStr(result.ir_dump, "BufferStoreDword null, v83"),
	      "typed buffer xyzw store did not expand to last shared dword store");
	Check(Common::ContainsStr(result.ir_dump, "typed=1"),
	      "typed buffer metadata did not survive into IR");
	Check(Common::ContainsStr(result.ir_dump, "dfmt=13 nfmt=7"),
	      "typed buffer format metadata did not survive into IR");
	Check(SpirvContainsOpcode(result.spirv, 65), "SPIR-V binary does not contain OpAccessChain");
	Check(SpirvContainsOpcode(result.spirv, 61), "SPIR-V binary does not contain OpLoad");
	Check(SpirvContainsOpcode(result.spirv, 62), "SPIR-V binary does not contain OpStore");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerFlatOldBackedLowering() {
	const uint32_t shader[] = {
	    EncodeFlat0(0x08, 0, 4),
	    EncodeFlat1(9, 0x7d, 0, 1), // flat_load_ubyte
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage            = ShaderType::Compute;
	options.dump_ir          = true;
	options.flat_memory_base = 0;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "flat_load_ubyte"),
	      "new decoder did not decode old-backed FLAT ubyte load");
	Check(Common::ContainsStr(result.ir_dump, "FlatLoadUbyte v9"),
	      "old-backed FLAT ubyte load did not lower to IR");
	Check(SpirvContainsOpcode(result.spirv, 65), "SPIR-V binary does not contain OpAccessChain");
	Check(SpirvContainsOpcode(result.spirv, 61), "SPIR-V binary does not contain OpLoad");
	Check(SpirvContainsOpcode(result.spirv, 194),
	      "SPIR-V binary does not contain OpShiftRightLogical");
	Check(SpirvContainsOpcode(result.spirv, 196),
	      "SPIR-V binary does not contain OpShiftLeftLogical");
	Check(SpirvContainsOpcode(result.spirv, 199), "SPIR-V binary does not contain OpBitwiseAnd");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerUnbasedFlatRequiresTranslator() {
	const uint32_t shader[] = {
	    EncodeFlat0(0x0c, 0, 0),
	    EncodeFlat1(0, 0x7d, 0, 1),
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage = ShaderType::Compute;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(!ShaderRecompiler::TryRecompile(shader, options, result, &error) &&
	          Common::ContainsStr(error, "requires runtime guest-address translation"),
	      "unbased FLAT compiled without an explicit translator");
}

void TestNewShaderRecompilerFlatSignedLoadLowering() {
	const uint32_t shader[] = {
	    EncodeFlat0(0x09, 0, 4),
	    EncodeFlat1(10, 0x7d, 0, 1), // flat_load_sbyte
	    EncodeFlat0(0x0b, 1, 8),
	    EncodeFlat1(11, 0x7d, 0, 1), // scratch_load_sshort
	    EncodeFlat0(0x09, 2, 12),
	    EncodeFlat1(12, 0x7d, 0, 1), // global_load_sbyte
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage            = ShaderType::Compute;
	options.dump_ir          = true;
	options.flat_memory_base = 0;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "flat_load_sbyte"),
	      "new decoder did not decode flat signed byte load");
	Check(Common::ContainsStr(result.decoded_dump, "flat_load_sshort"),
	      "new decoder did not decode flat signed short load");
	Check(Common::ContainsStr(result.decoded_dump, "bits=8"),
	      "signed byte flat load did not preserve bit width metadata");
	Check(Common::ContainsStr(result.decoded_dump, "bits=16"),
	      "signed short flat load did not preserve bit width metadata");
	Check(Common::ContainsStr(result.decoded_dump, "signed=1"),
	      "signed flat loads did not preserve signed metadata");
	Check(Common::ContainsStr(result.ir_dump, "FlatLoadSbyte v10"),
	      "flat_load_sbyte did not lower to signed byte IR load");
	Check(Common::ContainsStr(result.ir_dump, "FlatLoadSshort v11"),
	      "flat_load_sshort did not lower to signed short IR load");
	Check(Common::ContainsStr(result.ir_dump, "scratch"),
	      "signed scratch load did not preserve scratch metadata");
	Check(Common::ContainsStr(result.ir_dump, "global"),
	      "signed global load did not preserve global metadata");
	Check(SpirvContainsOpcode(result.spirv, 61), "SPIR-V binary does not contain OpLoad");
	Check(SpirvContainsOpcode(result.spirv, 195),
	      "SPIR-V binary does not contain OpShiftRightArithmetic for sign extension");
	Check(SpirvContainsOpcode(result.spirv, 196),
	      "SPIR-V binary does not contain OpShiftLeftLogical for sign extension");
	Check(SpirvContainsOpcode(result.spirv, 199), "SPIR-V binary does not contain OpBitwiseAnd");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerFlatStoreLowering() {
	const uint32_t shader[] = {
	    EncodeFlat0(0x18, 0, 2),
	    EncodeFlat1(0, 0x7d, 72, 1), // flat_store_byte
	    EncodeFlat0(0x1a, 1, 6),
	    EncodeFlat1(0, 0x7d, 73, 1), // scratch_store_short
	    EncodeFlat0(0x1c, 0, 4),
	    EncodeFlat1(0, 0x7d, 60, 1), // flat_store_dword
	    EncodeFlat0(0x1d, 1, 8),
	    EncodeFlat1(0, 0x7d, 61, 1), // scratch_store_dwordx2
	    EncodeFlat0(0x1f, 2, 12),
	    EncodeFlat1(0, 0x7d, 64, 1), // global_store_dwordx3
	    EncodeFlat0(0x1e, 2, 16),
	    EncodeFlat1(0, 0x7d, 68, 1), // global_store_dwordx4
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage            = ShaderType::Compute;
	options.dump_ir          = true;
	options.flat_memory_base = 0;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "flat_store_byte"),
	      "new decoder did not decode flat byte store");
	Check(Common::ContainsStr(result.decoded_dump, "flat_store_short"),
	      "new decoder did not decode flat short store");
	Check(Common::ContainsStr(result.decoded_dump, "flat_store_dword"),
	      "new decoder did not decode flat dword store");
	Check(Common::ContainsStr(result.decoded_dump, "flat_store_dwordx2"),
	      "new decoder did not decode flat dwordx2 store");
	Check(Common::ContainsStr(result.decoded_dump, "flat_store_dwordx3"),
	      "new decoder did not decode flat dwordx3 store");
	Check(Common::ContainsStr(result.decoded_dump, "flat_store_dwordx4"),
	      "new decoder did not decode flat dwordx4 store");
	Check(Common::ContainsStr(result.decoded_dump, "segment=1"),
	      "flat store decode did not preserve scratch segment metadata");
	Check(Common::ContainsStr(result.decoded_dump, "segment=2"),
	      "flat store decode did not preserve global segment metadata");
	Check(Common::ContainsStr(result.decoded_dump, "dwords=1 bits=8"),
	      "flat byte store decode did not expose byte-width metadata");
	Check(Common::ContainsStr(result.decoded_dump, "dwords=1 bits=16"),
	      "flat short store decode did not expose short-width metadata");
	Check(Common::ContainsStr(result.decoded_dump, "dwords=4 bits=32"),
	      "flat store decode did not expose wide width metadata");
	Check(Common::ContainsStr(result.ir_dump, "FlatStoreByte null, v72"),
	      "flat_store_byte did not lower through shared flat store IR");
	Check(Common::ContainsStr(result.ir_dump, "FlatStoreShort null, v73"),
	      "flat_store_short did not lower through shared flat store IR");
	Check(Common::ContainsStr(result.ir_dump, "FlatStoreDword null, v60"),
	      "flat_store_dword did not lower through shared flat store IR");
	Check(Common::ContainsStr(result.ir_dump, "FlatStoreDword null, v62"),
	      "scratch_store_dwordx2 did not expand to the last dword store");
	Check(Common::ContainsStr(result.ir_dump, "FlatStoreDword null, v66"),
	      "global_store_dwordx3 did not expand to the last dword store");
	Check(Common::ContainsStr(result.ir_dump, "FlatStoreDword null, v71"),
	      "global_store_dwordx4 did not expand to the last dword store");
	Check(Common::ContainsStr(result.ir_dump, "flat"),
	      "flat store IR did not preserve flat resource metadata");
	Check(Common::ContainsStr(result.ir_dump, "scratch"),
	      "flat store IR did not preserve scratch resource metadata");
	Check(Common::ContainsStr(result.ir_dump, "global"),
	      "flat store IR did not preserve global resource metadata");
	Check(SpirvContainsOpcode(result.spirv, 61),
	      "SPIR-V binary does not contain OpLoad for sub-dword flat store merge");
	Check(SpirvContainsOpcode(result.spirv, 65), "SPIR-V binary does not contain OpAccessChain");
	Check(SpirvContainsOpcode(result.spirv, 62), "SPIR-V binary does not contain OpStore");
	Check(SpirvContainsOpcode(result.spirv, 197),
	      "SPIR-V binary does not contain OpBitwiseOr for sub-dword flat store merge");
	Check(SpirvContainsOpcode(result.spirv, 200),
	      "SPIR-V binary does not contain OpNot for sub-dword flat store mask");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerAtomicLowering() {
	const uint32_t shader[] = {
	    EncodeMubuf0(0x30, 4, true, true),
	    EncodeMubuf1(0, 0, 1), // buffer_atomic_swap
	    EncodeMubuf0(0x32, 8, true, true),
	    EncodeMubuf1(1, 0, 1), // buffer_atomic_add
	    EncodeMubuf0(0x33, 12, true, true),
	    EncodeMubuf1(7, 0, 1), // buffer_atomic_sub
	    EncodeMubuf0(0x35, 16, true, true),
	    EncodeMubuf1(8, 0, 1), // buffer_atomic_smin
	    EncodeMubuf0(0x36, 20, true, true),
	    EncodeMubuf1(2, 0, 1), // buffer_atomic_umin
	    EncodeMubuf0(0x37, 24, true, true),
	    EncodeMubuf1(9, 0, 1), // buffer_atomic_smax
	    EncodeMubuf0(0x38, 28, true, true),
	    EncodeMubuf1(3, 0, 1), // buffer_atomic_umax
	    EncodeMubuf0(0x39, 32, true, true),
	    EncodeMubuf1(4, 0, 1), // buffer_atomic_and
	    EncodeMubuf0(0x3a, 36, true, true),
	    EncodeMubuf1(5, 0, 1), // buffer_atomic_or
	    EncodeMubuf0(0x3b, 40, true, true),
	    EncodeMubuf1(6, 0, 1), // buffer_atomic_xor
	    EncodeDs0(0x00),
	    EncodeDs1(0, 2, 1), // ds_add_u32
	    EncodeDs0(0x01),
	    EncodeDs1(0, 10, 1), // ds_sub_u32
	    EncodeDs0(0x05),
	    EncodeDs1(0, 11, 1), // ds_min_i32
	    EncodeDs0(0x06),
	    EncodeDs1(0, 12, 1), // ds_max_i32
	    EncodeDs0(0x09),
	    EncodeDs1(0, 13, 1), // ds_and_b32
	    EncodeDs0(0x0b),
	    EncodeDs1(0, 14, 1), // ds_xor_b32
	    EncodeDs0(0x20),
	    EncodeDs1(15, 2, 1), // ds_add_rtn_u32
	    EncodeDs0(0x21),
	    EncodeDs1(16, 10, 1), // ds_sub_rtn_u32
	    EncodeDs0(0x25),
	    EncodeDs1(17, 11, 1), // ds_min_rtn_i32
	    EncodeDs0(0x26),
	    EncodeDs1(18, 12, 1), // ds_max_rtn_i32
	    EncodeDs0(0x27),
	    EncodeDs1(19, 2, 1), // ds_min_rtn_u32
	    EncodeDs0(0x28),
	    EncodeDs1(20, 2, 1), // ds_max_rtn_u32
	    EncodeDs0(0x29),
	    EncodeDs1(21, 13, 1), // ds_and_rtn_b32
	    EncodeDs0(0x2a),
	    EncodeDs1(22, 2, 1), // ds_or_rtn_b32
	    EncodeDs0(0x2b),
	    EncodeDs1(23, 14, 1), // ds_xor_rtn_b32
	    EncodeDs0(0x2d),
	    EncodeDs1(24, 15, 1), // ds_wrxchg_rtn_b32
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "buffer_atomic_swap"),
	      "new decoder did not decode buffer atomic swap");
	Check(Common::ContainsStr(result.decoded_dump, "buffer_atomic_add"),
	      "new decoder did not decode buffer atomic add");
	Check(Common::ContainsStr(result.decoded_dump, "buffer_atomic_sub"),
	      "new decoder did not decode buffer atomic sub");
	Check(Common::ContainsStr(result.decoded_dump, "buffer_atomic_smin"),
	      "new decoder did not decode buffer atomic signed min");
	Check(Common::ContainsStr(result.decoded_dump, "buffer_atomic_smax"),
	      "new decoder did not decode buffer atomic signed max");
	Check(Common::ContainsStr(result.decoded_dump, "buffer_atomic_xor"),
	      "new decoder did not decode buffer atomic xor");
	Check(Common::ContainsStr(result.decoded_dump, "ds_add_u32"),
	      "new decoder did not decode DS atomic add");
	Check(Common::ContainsStr(result.decoded_dump, "ds_sub_u32"),
	      "new decoder did not decode DS atomic sub");
	Check(Common::ContainsStr(result.decoded_dump, "ds_min_i32"),
	      "new decoder did not decode DS signed min");
	Check(Common::ContainsStr(result.decoded_dump, "ds_xor_rtn_b32"),
	      "new decoder did not decode DS xor-return");
	Check(Common::ContainsStr(result.decoded_dump, "ds_wrxchg_rtn_b32"),
	      "new decoder did not decode DS write-exchange-return");
	Check(Common::ContainsStr(result.decoded_dump, "ds_add_rtn_u32"),
	      "new decoder did not decode DS atomic add-return");
	Check(Common::ContainsStr(result.ir_dump, "AtomicSwapU32 v0"),
	      "buffer atomic swap did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicAddU32 v1"),
	      "buffer atomic add did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicSubU32 v7"),
	      "buffer atomic sub did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicSMinI32 v8"),
	      "buffer atomic signed min did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicUMinU32 v2"),
	      "buffer atomic umin did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicSMaxI32 v9"),
	      "buffer atomic signed max did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicUMaxU32 v3"),
	      "buffer atomic umax did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicAndU32 v4"),
	      "buffer atomic and did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicOrU32 v5"),
	      "buffer atomic or did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicXorU32 v6"),
	      "buffer atomic xor did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicAddU32 null, v2"),
	      "DS no-return atomic add did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicSubU32 null, v10"),
	      "DS no-return atomic sub did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicSMinI32 null, v11"),
	      "DS no-return signed min did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicSMaxI32 null, v12"),
	      "DS no-return signed max did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicAndU32 null, v13"),
	      "DS no-return and did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicXorU32 null, v14"),
	      "DS no-return xor did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicAddU32 v15"),
	      "DS add-return did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicSubU32 v16"),
	      "DS sub-return did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicSMinI32 v17"),
	      "DS signed min-return did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicSMaxI32 v18"),
	      "DS signed max-return did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicUMinU32 v19"),
	      "DS unsigned min-return did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicUMaxU32 v20"),
	      "DS unsigned max-return did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicAndU32 v21"),
	      "DS and-return did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicOrU32 v22"),
	      "DS or-return did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicXorU32 v23"),
	      "DS xor-return did not lower to IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicSwapU32 v24"),
	      "DS write-exchange-return did not lower to shared atomic swap IR");
	Check(SpirvContainsOpcode(result.spirv, 229),
	      "SPIR-V binary does not contain OpAtomicExchange");
	Check(SpirvContainsOpcode(result.spirv, 234), "SPIR-V binary does not contain OpAtomicIAdd");
	Check(SpirvContainsOpcode(result.spirv, 235), "SPIR-V binary does not contain OpAtomicISub");
	Check(SpirvContainsOpcode(result.spirv, 236), "SPIR-V binary does not contain OpAtomicSMin");
	Check(SpirvContainsOpcode(result.spirv, 237), "SPIR-V binary does not contain OpAtomicUMin");
	Check(SpirvContainsOpcode(result.spirv, 238), "SPIR-V binary does not contain OpAtomicSMax");
	Check(SpirvContainsOpcode(result.spirv, 239), "SPIR-V binary does not contain OpAtomicUMax");
	Check(SpirvContainsOpcode(result.spirv, 240), "SPIR-V binary does not contain OpAtomicAnd");
	Check(SpirvContainsOpcode(result.spirv, 241), "SPIR-V binary does not contain OpAtomicOr");
	Check(SpirvContainsOpcode(result.spirv, 242), "SPIR-V binary does not contain OpAtomicXor");
	Check(SpirvContainsOpcode(result.spirv, 225),
	      "buffer atomic SPIR-V binary does not contain OpMemoryBarrier");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerDsReadWrite2Lowering() {
	const uint32_t shader[] = {
	    EncodeDs0(0x0e, (3u << 8u) | 1u),
	    EncodeDs1Ex(0, 61, 60, 1),
	    EncodeDs0(0x37, (4u << 8u) | 2u),
	    EncodeDs1Ex(70, 0, 0, 1),
	    EncodeDs0(0x38, (5u << 8u) | 1u),
	    EncodeDs1Ex(72, 0, 0, 1),
	    EncodeDs0(0x77, (6u << 8u) | 2u),
	    EncodeDs1Ex(80, 0, 0, 1),
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "ds_write2_b32"),
	      "new decoder did not decode old-backed DS write2");
	Check(Common::ContainsStr(result.decoded_dump, "ds_read2_b32"),
	      "new decoder did not decode old-backed DS read2");
	Check(Common::ContainsStr(result.decoded_dump, "ds_read2_b64"),
	      "new decoder did not decode old-backed DS read2 b64");
	Check(Common::ContainsStr(result.decoded_dump, "offset=4"),
	      "DS write2 decode did not scale offset0 to bytes");
	Check(Common::ContainsStr(result.decoded_dump, "offset2=12"),
	      "DS write2 decode did not scale offset1 to bytes");
	Check(Common::ContainsStr(result.decoded_dump, "offset=8"),
	      "DS read2 decode did not scale offset0 to bytes");
	Check(Common::ContainsStr(result.decoded_dump, "offset2=16"),
	      "DS read2 decode did not scale offset1 to bytes");
	Check(Common::ContainsStr(result.decoded_dump, "offset=256"),
	      "DS read2 st64 decode did not scale offset0 to bytes");
	Check(Common::ContainsStr(result.decoded_dump, "offset2=1280"),
	      "DS read2 st64 decode did not scale offset1 to bytes");
	Check(Common::ContainsStr(result.decoded_dump, "offset=16"),
	      "DS read2 b64 decode did not scale offset0 to bytes");
	Check(Common::ContainsStr(result.decoded_dump, "offset2=48"),
	      "DS read2 b64 decode did not scale offset1 to bytes");
	Check(Common::ContainsStr(result.decoded_dump, "dwords=2 bits=32"),
	      "DS read/write2 decode did not preserve two-dword metadata");
	Check(Common::ContainsStr(result.decoded_dump, "dwords=4 bits=32"),
	      "DS read2 b64 decode did not preserve four-dword metadata");
	Check(Common::ContainsStr(result.ir_dump, "DsWriteB32 null, v60"),
	      "DS write2 did not lower first dword through shared LDS store IR");
	Check(Common::ContainsStr(result.ir_dump, "DsWriteB32 null, v61"),
	      "DS write2 did not lower second dword through shared LDS store IR");
	Check(Common::ContainsStr(result.ir_dump, "DsReadB32 v70"),
	      "DS read2 did not lower first dword through shared LDS load IR");
	Check(Common::ContainsStr(result.ir_dump, "DsReadB32 v71"),
	      "DS read2 did not lower second dword through shared LDS load IR");
	Check(Common::ContainsStr(result.ir_dump, "DsReadB32 v72"),
	      "DS read2 st64 did not lower first dword through shared LDS load IR");
	Check(Common::ContainsStr(result.ir_dump, "DsReadB32 v73"),
	      "DS read2 st64 did not lower second dword through shared LDS load IR");
	Check(Common::ContainsStr(result.ir_dump, "DsReadB32 v80"),
	      "DS read2 b64 did not lower first dword through shared LDS load IR");
	Check(Common::ContainsStr(result.ir_dump, "DsReadB32 v83"),
	      "DS read2 b64 did not lower fourth dword through shared LDS load IR");
	Check(SpirvContainsOpcode(result.spirv, 61), "SPIR-V binary does not contain OpLoad");
	Check(SpirvContainsOpcode(result.spirv, 62), "SPIR-V binary does not contain OpStore");
	Check(SpirvContainsOpcode(result.spirv, 65), "SPIR-V binary does not contain OpAccessChain");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerDsSubDwordLowering() {
	const uint32_t shader[] = {
	    EncodeDs0(0x1e, 1), EncodeDs1(0, 40, 1), // ds_write_b8 v40, v1
	    EncodeDs0(0x1f, 2), EncodeDs1(0, 41, 1), // ds_write_b16 v41, v1
	    EncodeDs0(0x39, 3), EncodeDs1(42, 0, 1), // ds_read_i8 v42, v1
	    EncodeDs0(0x3a, 4), EncodeDs1(43, 0, 1), // ds_read_u8 v43, v1
	    EncodeDs0(0x3b, 6), EncodeDs1(44, 0, 1), // ds_read_i16 v44, v1
	    EncodeDs0(0x3c, 8), EncodeDs1(45, 0, 1), // ds_read_u16 v45, v1
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "ds_write_b8"),
	      "new decoder did not decode DS byte write");
	Check(Common::ContainsStr(result.decoded_dump, "ds_write_b16"),
	      "new decoder did not decode DS short write");
	Check(Common::ContainsStr(result.decoded_dump, "ds_read_i8"),
	      "new decoder did not decode DS signed byte read");
	Check(Common::ContainsStr(result.decoded_dump, "ds_read_u8"),
	      "new decoder did not decode DS unsigned byte read");
	Check(Common::ContainsStr(result.decoded_dump, "ds_read_i16"),
	      "new decoder did not decode DS signed short read");
	Check(Common::ContainsStr(result.decoded_dump, "ds_read_u16"),
	      "new decoder did not decode DS unsigned short read");
	Check(Common::ContainsStr(result.decoded_dump, "dwords=1 bits=8"),
	      "DS byte decode did not preserve byte-width metadata");
	Check(Common::ContainsStr(result.decoded_dump, "dwords=1 bits=16"),
	      "DS short decode did not preserve short-width metadata");
	Check(Common::ContainsStr(result.ir_dump, "DsWriteByte null, v40"),
	      "DS byte write did not lower to explicit IR");
	Check(Common::ContainsStr(result.ir_dump, "DsWriteShort null, v41"),
	      "DS short write did not lower to explicit IR");
	Check(Common::ContainsStr(result.ir_dump, "DsReadSbyte v42"),
	      "DS signed byte read did not lower to explicit IR");
	Check(Common::ContainsStr(result.ir_dump, "DsReadUbyte v43"),
	      "DS unsigned byte read did not lower to explicit IR");
	Check(Common::ContainsStr(result.ir_dump, "DsReadSshort v44"),
	      "DS signed short read did not lower to explicit IR");
	Check(Common::ContainsStr(result.ir_dump, "DsReadUshort v45"),
	      "DS unsigned short read did not lower to explicit IR");
	Check(SpirvContainsOpcode(result.spirv, 61), "SPIR-V binary does not contain OpLoad");
	Check(SpirvContainsOpcode(result.spirv, 62), "SPIR-V binary does not contain OpStore");
	Check(SpirvContainsOpcode(result.spirv, 65), "SPIR-V binary does not contain OpAccessChain");
	Check(SpirvContainsOpcode(result.spirv, 194),
	      "SPIR-V binary does not contain OpShiftRightLogical");
	Check(SpirvContainsOpcode(result.spirv, 195),
	      "SPIR-V binary does not contain OpShiftRightArithmetic");
	Check(SpirvContainsOpcode(result.spirv, 196),
	      "SPIR-V binary does not contain OpShiftLeftLogical");
	Check(SpirvContainsOpcode(result.spirv, 199), "SPIR-V binary does not contain OpBitwiseAnd");
	Check(SpirvContainsOpcode(result.spirv, 200), "SPIR-V binary does not contain OpNot");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerDsWideAndAtomicLowering() {
	const uint32_t shader[] = {
	    EncodeDs0(0x4d, 4),  EncodeDs1(0, 10, 1), // ds_write_b64 v[10:11], v1
	    EncodeDs0(0xde, 8),  EncodeDs1(0, 12, 1), // ds_write_b96 v[12:14], v1
	    EncodeDs0(0xdf, 12), EncodeDs1(0, 16, 1), // ds_write_b128 v[16:19], v1
	    EncodeDs0(0x76, 16), EncodeDs1(20, 0, 1), // ds_read_b64 v[20:21], v1
	    EncodeDs0(0xfe, 20), EncodeDs1(24, 0, 1), // ds_read_b96 v[24:26], v1
	    EncodeDs0(0xff, 24), EncodeDs1(28, 0, 1), // ds_read_b128 v[28:31], v1
	    EncodeDs0(0x07, 28), EncodeDs1(0, 32, 1), // ds_min_u32 v32, v1
	    EncodeDs0(0x08, 32), EncodeDs1(0, 33, 1), // ds_max_u32 v33, v1
	    EncodeDs0(0x0a, 36), EncodeDs1(0, 34, 1), // ds_or_b32 v34, v1
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "ds_write_b64"),
	      "new decoder did not decode DS b64 write");
	Check(Common::ContainsStr(result.decoded_dump, "ds_write_b96"),
	      "new decoder did not decode DS b96 write");
	Check(Common::ContainsStr(result.decoded_dump, "ds_write_b128"),
	      "new decoder did not decode DS b128 write");
	Check(Common::ContainsStr(result.decoded_dump, "ds_read_b64"),
	      "new decoder did not decode DS b64 read");
	Check(Common::ContainsStr(result.decoded_dump, "ds_read_b96"),
	      "new decoder did not decode DS b96 read");
	Check(Common::ContainsStr(result.decoded_dump, "ds_read_b128"),
	      "new decoder did not decode DS b128 read");
	Check(Common::ContainsStr(result.decoded_dump, "ds_min_u32"),
	      "new decoder did not decode DS min atomic");
	Check(Common::ContainsStr(result.decoded_dump, "ds_max_u32"),
	      "new decoder did not decode DS max atomic");
	Check(Common::ContainsStr(result.decoded_dump, "ds_or_b32"),
	      "new decoder did not decode DS or atomic");
	Check(Common::ContainsStr(result.decoded_dump, "dwords=4 bits=32"),
	      "new decoder did not expose DS wide width metadata");
	Check(Common::ContainsStr(result.ir_dump, "DsWriteB32 null, v10"),
	      "DS b64 write did not expand to first dword store");
	Check(Common::ContainsStr(result.ir_dump, "DsWriteB32 null, v19"),
	      "DS b128 write did not expand to last dword store");
	Check(Common::ContainsStr(result.ir_dump, "DsReadB32 v20"),
	      "DS b64 read did not expand to first dword load");
	Check(Common::ContainsStr(result.ir_dump, "DsReadB32 v31"),
	      "DS b128 read did not expand to last dword load");
	Check(Common::ContainsStr(result.ir_dump, "AtomicUMinU32 null, v32"),
	      "DS min atomic did not lower to shared atomic IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicUMaxU32 null, v33"),
	      "DS max atomic did not lower to shared atomic IR");
	Check(Common::ContainsStr(result.ir_dump, "AtomicOrU32 null, v34"),
	      "DS or atomic did not lower to shared atomic IR");
	Check(SpirvContainsOpcode(result.spirv, 61), "SPIR-V binary does not contain OpLoad");
	Check(SpirvContainsOpcode(result.spirv, 62), "SPIR-V binary does not contain OpStore");
	Check(SpirvContainsOpcode(result.spirv, 237), "SPIR-V binary does not contain OpAtomicUMin");
	Check(SpirvContainsOpcode(result.spirv, 239), "SPIR-V binary does not contain OpAtomicUMax");
	Check(SpirvContainsOpcode(result.spirv, 241), "SPIR-V binary does not contain OpAtomicOr");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerDsSwizzleLowering() {
	const uint32_t shader[] = {
	    EncodeDs0(0x35, 0x001f),
	    EncodeDs1(8, 0, 5), // ds_swizzle_b32 v8, v5
	    EncodeDs0(0x35, 0x801b),
	    EncodeDs1(9, 0, 6), // ds_swizzle_b32 v9, v6
	    0xd8d4c480u,
	    0x45000045u, // ds_swizzle_b32 rotate mode from boot shader
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "ds_swizzle_b32"),
	      "new decoder did not decode DS swizzle");
	Check(Common::ContainsStr(result.decoded_dump, "offset=31"),
	      "new decoder did not expose DS swizzle control");
	Check(Common::ContainsStr(result.decoded_dump, "offset=32795"),
	      "new decoder did not expose DS swizzle quad control");
	Check(Common::ContainsStr(result.decoded_dump, "offset=50304"),
	      "new decoder did not expose DS swizzle rotate control");
	Check(Common::ContainsStr(result.ir_dump, "DsSwizzleB32 v8, v5, 0x0000001f"),
	      "DS swizzle did not lower to explicit IR");
	Check(Common::ContainsStr(result.ir_dump, "DsSwizzleB32 v9, v6, 0x0000801b"),
	      "DS quad swizzle did not lower to explicit IR");
	Check(Common::ContainsStr(result.ir_dump, "DsSwizzleB32 v69, v69, 0x0000c480"),
	      "DS rotate swizzle did not lower to explicit IR");
	Check(SpirvContainsOpcode(result.spirv, 345),
	      "SPIR-V binary does not contain OpGroupNonUniformShuffle");
	Check(SpirvContainsOpcode(result.spirv, 128),
	      "SPIR-V binary does not contain OpIAdd for DS rotate swizzle");
	Check(SpirvContainsOpcode(result.spirv, 61), "SPIR-V binary does not contain OpLoad");
	Check(SpirvContainsOpcode(result.spirv, 62), "SPIR-V binary does not contain OpStore");
	Check(SpirvContainsOpcode(result.spirv, 196),
	      "SPIR-V binary does not contain OpShiftLeftLogical");
	Check(SpirvContainsOpcode(result.spirv, 198), "SPIR-V binary does not contain OpBitwiseOr");
	Check(SpirvContainsOpcode(result.spirv, 199), "SPIR-V binary does not contain OpBitwiseAnd");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerDsAddtidLowering() {
	const uint32_t shader[] = {
	    EncodeSMovB32(124, 132), // m0 = 4
	    EncodeDs0(0xb0, 8),
	    EncodeDs1(0, 7, 0), // ds_write_addtid_b32 v7
	    EncodeDs0(0xb1, 12),
	    EncodeDs1(8, 0, 0), // ds_read_addtid_b32 v8
	    0xbf810000u,
	};

	ShaderComputeInputInfo input_info = RegressionComputeInputInfo();
	input_info.thread_ids_num         = 1;

	ShaderRecompiler::CompileOptions options;
	options.stage              = ShaderType::Compute;
	options.dump_ir            = true;
	options.compute_input_info = &input_info;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "ds_write_addtid_b32"),
	      "new decoder did not decode DS write addtid");
	Check(Common::ContainsStr(result.decoded_dump, "ds_read_addtid_b32"),
	      "new decoder did not decode DS read addtid");
	Check(Common::ContainsStr(result.ir_dump, "DsWriteAddtidB32 null, v7, m0"),
	      "DS write addtid did not lower to explicit IR");
	Check(Common::ContainsStr(result.ir_dump, "DsReadAddtidB32 v8, m0"),
	      "DS read addtid did not lower to explicit IR");
	Check(
	    ProgramHasInput(result.program, ShaderRecompiler::IR::StageInputKind::LocalInvocationIndex),
	    "DS addtid did not request LocalInvocationIndex input");
	Check(SpirvContainsOpcode(result.spirv, 61), "SPIR-V binary does not contain OpLoad");
	Check(SpirvContainsOpcode(result.spirv, 62), "SPIR-V binary does not contain OpStore");
	Check(SpirvContainsOpcode(result.spirv, 65), "SPIR-V binary does not contain OpAccessChain");
	Check(SpirvContainsOpcode(result.spirv, 128), "SPIR-V binary does not contain OpIAdd");
	Check(SpirvContainsOpcode(result.spirv, 196),
	      "SPIR-V binary does not contain OpShiftLeftLogical");
	Check(SpirvContainsOpcode(result.spirv, 199), "SPIR-V binary does not contain OpBitwiseAnd");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerDsFloatMinMaxLowering() {
	const uint32_t shader[] = {
	    EncodeDs0(0x12, 4), EncodeDs1Ex(0, 9, 7, 1),  // ds_min_f32 v7, v9, v1
	    EncodeDs0(0x13, 8), EncodeDs1Ex(0, 10, 8, 1), // ds_max_f32 v8, v10, v1
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "ds_min_f32"),
	      "new decoder did not decode DS float min");
	Check(Common::ContainsStr(result.decoded_dump, "ds_max_f32"),
	      "new decoder did not decode DS float max");
	Check(Common::ContainsStr(result.ir_dump, "DsMinF32 null, v7, v1"),
	      "DS float min did not lower to explicit IR");
	Check(Common::ContainsStr(result.ir_dump, "DsMaxF32 null, v8, v1"),
	      "DS float max did not lower to explicit IR");
	Check(Common::ContainsStr(result.ir_dump, "v9"),
	      "DS float min did not retain DATA1 compare operand");
	Check(Common::ContainsStr(result.ir_dump, "v10"),
	      "DS float max did not retain DATA1 compare operand");
	Check(SpirvContainsOpcode(result.spirv, 12), "SPIR-V binary does not contain OpExtInst");
	Check(SpirvContainsOpcode(result.spirv, 61), "SPIR-V binary does not contain OpLoad");
	Check(SpirvContainsOpcode(result.spirv, 62), "SPIR-V binary does not contain OpStore");
	Check(SpirvContainsOpcode(result.spirv, 65), "SPIR-V binary does not contain OpAccessChain");
	Check(SpirvContainsOpcode(result.spirv, 124), "SPIR-V binary does not contain OpBitcast");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerCfgStraightLine() {
	const uint32_t shader[] = {
	    EncodeSMovB32(0, 129),
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.ir_dump, "CFG:"), "CFG dump was not emitted");
	Check(Common::ContainsStr(result.ir_dump, "block_0"), "straight-line CFG block missing");
	Check(Common::ContainsStr(result.ir_dump, "successors=["), "CFG successors were not dumped");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerCfgIfElse() {
	const uint32_t shader[] = {
	    EncodeSopc(0x06, 0, 0), // s_cmp_eq_u32 s0, s0
	    EncodeSopp(0x05, 2),    // s_cbranch_scc1 else
	    EncodeSMovB32(1, 129),  // then
	    EncodeSopp(0x02, 1),    // s_branch merge
	    EncodeSMovB32(1, 130),  // else
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.ir_dump, "condition=scc1"),
	      "if/else branch condition missing");
	Check(SpirvContainsOpcode(result.spirv, 247), "if/else SPIR-V lacks OpSelectionMerge");
	Check(SpirvContainsOpcode(result.spirv, 250), "if/else SPIR-V lacks OpBranchConditional");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerCfgTerminalExitMergePS() {
	const uint32_t shader[] = {
	    EncodeSopp(0x04, 2),   // s_cbranch_scc0 end
	    EncodeSMovB32(0, 129), // fallthrough work
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Pixel;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.ir_dump, "mode=structured"),
	      "terminal PS branch should stay on structured path");
	Check(!Common::ContainsStr(result.ir_dump, "conditional block 0 has no structured merge"),
	      "known terminal PS branch shape still reports missing merge");
	Check(SpirvContainsOpcode(result.spirv, 247),
	      "terminal PS branch SPIR-V lacks OpSelectionMerge");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerCfgPostEndTargetMergePS() {
	const uint32_t shader[] = {
	    EncodeSopp(0x04, 2),                          // s_cbranch_scc0 label_000c
	    EncodeSMovB32(0, 129),                        // fallthrough work
	    0xbf810000u,           EncodeSMovB32(1, 129), // branch target after first s_endpgm
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Pixel;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	const bool ok = ShaderRecompiler::TryRecompile(shader, options, result, &error);
	if (!ok) {
		std::fprintf(stderr, "PostEndTargetMergePS compile error: %s\n", error.c_str());
	}
	Check(ok, "post-end target PS shader failed to compile");
	Check(Common::ContainsStr(result.decoded_dump, "0x0000000c: s_mov_b32"),
	      "post-end branch target was not decoded");
	Check(Common::ContainsStr(result.ir_dump, "mode=structured"),
	      "post-end terminal branch should stay on structured path");
	Check(Common::ContainsStr(result.ir_dump, "pc=0x0000000c"),
	      "post-end branch target did not reach IR blocks");
	Check(!Common::ContainsStr(result.ir_dump, "conditional block 0 has no structured merge"),
	      "known post-end PS branch shape still reports missing merge");
	Check(SpirvContainsOpcode(result.spirv, 247),
	      "post-end terminal branch SPIR-V lacks OpSelectionMerge");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerCfgLoopBreakContinue() {
	const uint32_t shader[] = {
	    EncodeSMovB32(0, 128),       // s0 = 0
	    EncodeSopc(0x0a, 0, 129),    // s_cmp_lt_u32 s0, 1
	    EncodeSopp(0x04, 2),         // break when scc == 0
	    EncodeSop2(0x00, 0, 0, 129), // s_add_u32 s0, s0, 1
	    EncodeSopp(0x02, 0xfffcu),   // continue/backedge
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.ir_dump, "backedge"), "loop backedge was not detected");
	Check(Common::ContainsStr(result.ir_dump, "loop_header=1"), "loop header was not marked");
	Check(Common::ContainsStr(result.ir_dump, "continue="),
	      "loop continue block was not identified");
	Check(SpirvContainsOpcode(result.spirv, 246), "loop SPIR-V lacks OpLoopMerge");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerCfgLoopHeaderDynamicScalarBufferLoadStructured() {
	const uint32_t shader[] = {
	    EncodeSMovB32(0, 128), // preheader: s0 = 0
	    EncodeSmem0(0x08, 8, 4),
	    0u,                          // loop: s_buffer_load_dword s8, s[4:7]
	    EncodeSop2(0x00, 0, 0, 129), // s_add_u32 s0, s0, 1
	    EncodeSopc(0x0a, 0, 130),    // s_cmp_lt_u32 s0, 2
	    EncodeSopp(0x05, 0xfffbu),   // s_cbranch_scc1 loop
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(!ShaderRecompiler::TryRecompile(shader, options, result, &error) &&
	          Common::ContainsStr(error, "unsupported GPU selection"),
	      "self-modifying scalar-buffer descriptor should fail explicitly");
}

void TestNewShaderRecompilerCfgLoopHeaderBufferLoadDispatcher() {
	const uint32_t shader[] = {
	    EncodeSMovB32(0, 128), // preheader: s0 = 0
	    EncodeMubuf0(0x0c),
	    EncodeMubuf1(0, 0, 1),       // loop: buffer_load_dword v0
	    EncodeSop2(0x00, 0, 0, 129), // s_add_u32 s0, s0, 1
	    EncodeSopc(0x0a, 0, 130),    // s_cmp_lt_u32 s0, 2
	    EncodeSopp(0x05, 0xfffbu),   // s_cbranch_scc1 loop
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(!ShaderRecompiler::TryRecompile(shader, options, result, &error) &&
	          Common::ContainsStr(error, "unsupported GPU selection"),
	      "self-modifying vector-buffer descriptor should fail explicitly");
}

void TestNewShaderRecompilerCfgLoopHeaderDsAppendConsumeDispatcher() {
	const uint32_t shader[] = {
	    EncodeSMovB32(124, 129), // m0 = one counter
	    EncodeDs0(0x3e),         // loop: ds_append
	    EncodeDs1(0, 0, 0),
	    EncodeDs0(0x3d), // ds_consume
	    EncodeDs1(1, 0, 0),
	    EncodeSop2(0x00, 2, 2, 129), // s_add_u32 s2, s2, 1
	    EncodeSopc(0x0a, 2, 130),    // s_cmp_lt_u32 s2, 2
	    EncodeSopp(0x05, 0xfff9u),   // s_cbranch_scc1 loop
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.ir_dump, "mode=dispatcher"),
	      "DS append/consume loop header did not select dispatcher fallback");
	Check(SpirvContainsOpcode(result.spirv, 246), "DS dispatcher SPIR-V lacks OpLoopMerge");
	Check(SpirvContainsOpcode(result.spirv, 251), "DS dispatcher SPIR-V lacks OpSwitch");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerCfgSharedOuterAndLoopMerge() {
	const uint32_t shader[] = {
	    EncodeSopc(0x06, 0, 0),      // s_cmp_eq_u32 s0, s0
	    EncodeSopp(0x05, 5),         // outer early exit -> end
	    EncodeSMovB32(2, 128),       // s2 = 0
	    EncodeSopc(0x0a, 2, 129),    // loop: s_cmp_lt_u32 s2, 1
	    EncodeSopp(0x04, 2),         // loop exit -> same real end block
	    EncodeSop2(0x00, 2, 2, 129), // s_add_u32 s2, s2, 1
	    EncodeSopp(0x02, 0xfffcu),   // continue/backedge
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.ir_dump, "mode=structured"),
	      "shared outer/loop merge should stay on structured path");
	Check(!Common::ContainsStr(result.ir_dump, "duplicate structured merge block"),
	      "shared outer/loop merge was not split before structurization");
	Check(SpirvContainsOpcode(result.spirv, 246),
	      "shared outer/loop merge SPIR-V lacks OpLoopMerge");
	Check(SpirvContainsOpcode(result.spirv, 247),
	      "shared outer/loop merge SPIR-V lacks OpSelectionMerge");
	Check(!SpirvContainsOpcode(result.spirv, 251),
	      "shared outer/loop merge unexpectedly used dispatcher OpSwitch");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerCfgLoopSharedContinueSelectionMerges() {
	const uint32_t shader[] = {
	    EncodeSMovB32(0, 128),       // s0 = 0
	    EncodeSopc(0x0a, 0, 130),    // loop: s_cmp_lt_u32 s0, 2
	    EncodeSopp(0x04, 9),         // loop exit -> end
	    EncodeSopc(0x06, 1, 1),      // s_cmp_eq_u32 s1, s1
	    EncodeSopp(0x05, 5),         // first early continue -> continue block
	    EncodeSopc(0x06, 2, 2),      // s_cmp_eq_u32 s2, s2
	    EncodeSopp(0x05, 3),         // second early continue -> continue block
	    EncodeSopc(0x06, 3, 3),      // s_cmp_eq_u32 s3, s3
	    EncodeSopp(0x05, 1),         // third early continue -> continue block
	    EncodeSMovB32(4, 129),       // fallthrough work
	    EncodeSop2(0x00, 0, 0, 129), // continue: s_add_u32 s0, s0, 1
	    EncodeSopp(0x02, 0xfff5u),   // backedge -> loop header
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.ir_dump, "mode=structured"),
	      "shared loop continue selections should stay on structured path");
	Check(!Common::ContainsStr(result.ir_dump, "duplicate structured merge block"),
	      "shared loop continue selections were not split before structurization");
	Check(SpirvContainsOpcode(result.spirv, 246),
	      "shared loop continue selections SPIR-V lacks OpLoopMerge");
	Check(SpirvContainsOpcode(result.spirv, 247),
	      "shared loop continue selections SPIR-V lacks OpSelectionMerge");
	Check(!SpirvContainsOpcode(result.spirv, 251),
	      "shared loop continue selections unexpectedly used dispatcher OpSwitch");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerCfgDuplicateMergeStructuredSplit() {
	const uint32_t shader[] = {
	    EncodeSopc(0x06, 0, 0), // s_cmp_eq_u32 s0, s0
	    EncodeSopp(0x05, 2),    // first early exit -> end
	    EncodeSopp(0x05, 1),    // second early exit -> same end merge
	    EncodeSMovB32(0, 129),  // fallthrough work
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.ir_dump, "mode=structured"),
	      "duplicate merge CFG did not stay on structured path");
	Check(!Common::ContainsStr(result.ir_dump, "duplicate structured merge block"),
	      "duplicate merge CFG was not split before structurization");
	Check(SpirvContainsOpcode(result.spirv, 247), "duplicate merge SPIR-V lacks OpSelectionMerge");
	Check(!SpirvContainsOpcode(result.spirv, 251),
	      "duplicate merge CFG unexpectedly used dispatcher OpSwitch");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerCfgIrreducibleDispatcher() {
	const uint32_t shader[] = {
	    EncodeSopp(0x05, 2),       // entry -> B, fallthrough A
	    EncodeSopp(0x02, 0),       // A -> C
	    EncodeSopp(0x05, 0xfffeu), // C -> A, fallthrough B
	    EncodeSopp(0x02, 0xfffeu), // B -> C
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.ir_dump, "irreducible CFG"),
	      "irreducible CFG reason was not retained");
	Check(Common::ContainsStr(result.ir_dump, "mode=dispatcher"),
	      "irreducible CFG did not select dispatcher fallback");
	Check(SpirvContainsOpcode(result.spirv, 246), "dispatcher SPIR-V lacks OpLoopMerge");
	Check(SpirvContainsOpcode(result.spirv, 251), "dispatcher SPIR-V lacks OpSwitch");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerExecMaskHelpers() {
	using namespace ShaderRecompiler::Exec;

	State wave32;
	wave32.wave_size = 32;
	WriteExec(wave32, FullMask(64));
	Check(ReadExec(wave32).low == 0xffffffffu && ReadExec(wave32).high == 0u,
	      "wave32 EXEC high half was not masked off");
	AndExec(wave32, {0x0fu, 0xffffffffu});
	Check(ReadExec(wave32).low == 0x0fu && ReadExec(wave32).high == 0u,
	      "wave32 AndExec did not preserve wave size");
	InvertExec(wave32);
	Check(ReadExec(wave32).low == 0xfffffff0u && ReadExec(wave32).high == 0u,
	      "wave32 InvertExec did not preserve wave size");
	WriteExec(wave32, {0u, 0xffffffffu});
	Check(IsExecZero(wave32), "wave32 IsExecZero used inactive high lanes");

	State wave64;
	wave64.wave_size = 64;
	WriteExec(wave64, FullMask(64));
	Check(ReadExec(wave64).low == 0xffffffffu && ReadExec(wave64).high == 0xffffffffu,
	      "wave64 EXEC full mask was wrong");
	XorExec(wave64, {0xffffffffu, 0u});
	Check(ReadExec(wave64).low == 0u && ReadExec(wave64).high == 0xffffffffu,
	      "wave64 XorExec did not update both halves correctly");
	wave64.vcc = {0u, 1u};
	Check(!IsVccZero(wave64), "wave64 VCC high half was ignored");
}

void TestComputeShaderInputWaveSize() {
	const auto decode_wave_size = [](uint32_t rsrc1) {
		const bool wave32 = (((rsrc1 >> Pm4::COMPUTE_PGM_RSRC1_W32_EN_SHIFT) &
		                      Pm4::COMPUTE_PGM_RSRC1_W32_EN_MASK) != 0u);
		return wave32 ? 32u : 64u;
	};

	constexpr uint32_t observed_wave32_rsrc1_a = 0x402c0146u;
	constexpr uint32_t observed_wave32_rsrc1_b = 0x402c00c1u;
	Check(decode_wave_size(observed_wave32_rsrc1_a) == 32u,
	      "COMPUTE_PGM_RSRC1 W32_EN bit did not match observed PS5 value A");
	Check(decode_wave_size(observed_wave32_rsrc1_b) == 32u,
	      "COMPUTE_PGM_RSRC1 W32_EN bit did not match observed PS5 value B");

	constexpr uint32_t w32_en_bit = 1u << Pm4::COMPUTE_PGM_RSRC1_W32_EN_SHIFT;
	Check(decode_wave_size(observed_wave32_rsrc1_a & ~w32_en_bit) == 64u,
	      "cleared COMPUTE_PGM_RSRC1 W32_EN bit did not decode as wave64");
}

void TestNewShaderRecompilerWave32MasksExecHighStores() {
	const uint32_t shader[] = {
	    EncodeSop1(0x04, 126, 193), // s_mov_b64 exec, -1
	    EncodeSopp(0x01),
	};

	ShaderRecompiler::CompileOptions options;
	options.stage     = ShaderType::Compute;
	options.dump_ir   = true;
	options.wave_size = 32;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "s_mov_b64 exec_lo, -1"),
	      "wave32 EXEC mask regression did not decode s_mov_b64 exec, -1");
	CheckSpirvBinaryValidates(result.spirv);

	const auto source = DisassembleSpirvBinary(result.spirv);
	Check(CountSourceOccurrences(source, "OpStore %exec_hi %uint_0") >= 2u,
	      "wave32 EXEC high half was not clamped to zero in SPIR-V stores");
	Check(!Common::ContainsStr(source, "OpStore %exec_hi %uint_4294967295"),
	      "wave32 EXEC high half was stored as a full 64-lane mask");
}

void TestNewShaderRecompilerWave32VccHighScalarStores() {
	const uint32_t shader[] = {
	    EncodeSmem0(0x09, 106, 4),
	    0u, // s_buffer_load_dwordx2 vcc_lo
	    EncodeSopp(0x01),
	};

	ShaderRecompiler::CompileOptions options;
	options.stage     = ShaderType::Compute;
	options.dump_ir   = true;
	options.wave_size = 32;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.ir_dump, "SBufferLoadDword vcc_hi"),
	      "wave32 VCC scalar regression did not lower the high VCC dword load");
	CheckSpirvBinaryValidates(result.spirv);

	const auto source = DisassembleSpirvBinary(result.spirv);
	Check(CountSourceOccurrences(source, "OpStore %vcc_hi") >= 2u,
	      "wave32 VCC high scalar load did not store to vcc_hi");
	Check(CountSourceOccurrences(source, "OpStore %vcc_hi %uint_0") == 1u,
	      "wave32 VCC high scalar load was clamped to zero instead of preserving data");
}

void TestNewShaderRecompilerCompareMaskIsFullWaveBallot() {
	const uint32_t shader[] = {
	    EncodeVopc(0xc1, 0 + 256, 1),  // v_cmp_lt_u32 vcc, v0, v1
	    EncodeSop2(0x0f, 2, 126, 106), // s_and_b64 s[2:3], exec, vcc
	    EncodeSop1(0x04, 126, 2),      // s_mov_b64 exec, s[2:3]
	    EncodeSopp(0x01),
	};

	ShaderRecompiler::CompileOptions options;
	options.stage     = ShaderType::Compute;
	options.dump_ir   = true;
	options.wave_size = 32;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "v_cmp_lt_u32"),
	      "compare-mask ballot regression did not decode V_CMP_LT_U32");
	Check(Common::ContainsStr(result.ir_dump, "CompareLtU32 vcc_lo"),
	      "compare-mask ballot regression did not lower compare to VCC");
	Check(Common::ContainsStr(result.ir_dump, "BitwiseAndU64 s2, exec_lo, vcc_lo"),
	      "compare-mask ballot regression did not consume VCC through scalar mask ALU");
	CheckSpirvBinaryValidates(result.spirv);

	const auto source = DisassembleSpirvBinary(result.spirv);
	Check(Common::ContainsStr(source, "OpGroupNonUniformBallot"),
	      "compare-mask result was not materialized with a subgroup ballot");
	Check(Common::ContainsStr(source, "OpStore %vcc_lo"),
	      "compare-mask ballot regression did not store VCC low mask");
	Check(Common::ContainsStr(source, "OpStore %vcc_hi %uint_0"),
	      "wave32 compare-mask VCC high half was not cleared");
}

void TestNewShaderRecompilerBufferLoadsGuardedByExec() {
	const uint32_t shader[] = {
	    EncodeMubuf0(0x0c),
	    EncodeMubuf1(0, 0, 1), // buffer_load_dword v0
	    EncodeSopp(0x01),
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "buffer_load_dword"),
	      "buffer load guard regression did not decode MUBUF load");
	CheckSpirvBinaryValidates(result.spirv);

	const auto source       = DisassembleSpirvBinary(result.spirv);
	const auto exec_branch  = Common::FindIndex(source, std::string("OpBranchConditional"), 0);
	const auto array_length = Common::FindIndex(source, std::string("OpArrayLength"), 0);
	const auto bounds_branch =
	    Common::FindIndex(source, std::string("OpBranchConditional"), array_length);
	const auto element_access =
	    Common::FindIndex(source, std::string("OpAccessChain %_ptr_StorageBuffer_uint"), 0);
	Check(exec_branch != Common::FIND_INVALID_INDEX, "buffer load SPIR-V lacks EXEC guard branch");
	Check(array_length != Common::FIND_INVALID_INDEX,
	      "buffer load SPIR-V lacks storage buffer array-length bounds check");
	Check(bounds_branch != Common::FIND_INVALID_INDEX,
	      "buffer load SPIR-V lacks storage buffer bounds branch");
	Check(element_access != Common::FIND_INVALID_INDEX,
	      "buffer load SPIR-V lacks storage element access");
	Check(exec_branch < array_length, "buffer load bounds check was emitted outside EXEC guard");
	Check(bounds_branch < element_access,
	      "buffer load storage element pointer was formed before bounds guard");
}

void TestNewShaderRecompilerBufferAtomicsGuardedByBounds() {
	const uint32_t shader[] = {
	    EncodeMubuf0(0x32, 0, true, true),
	    EncodeMubuf1(0, 0, 1), // buffer_atomic_add
	    EncodeSopp(0x01),
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "buffer_atomic_add"),
	      "buffer atomic bounds regression did not decode MUBUF atomic");
	CheckSpirvBinaryValidates(result.spirv);

	const auto source       = DisassembleSpirvBinary(result.spirv);
	const auto array_length = Common::FindIndex(source, std::string("OpArrayLength"), 0);
	const auto bounds_branch =
	    Common::FindIndex(source, std::string("OpBranchConditional"), array_length);
	const auto atomic         = Common::FindIndex(source, std::string("OpAtomicIAdd"), 0);
	const auto memory_barrier = Common::FindIndex(source, std::string("OpMemoryBarrier"), atomic);
	Check(array_length != Common::FIND_INVALID_INDEX,
	      "buffer atomic SPIR-V lacks storage buffer array-length bounds check");
	Check(bounds_branch != Common::FIND_INVALID_INDEX,
	      "buffer atomic SPIR-V lacks storage buffer bounds branch");
	Check(atomic != Common::FIND_INVALID_INDEX, "buffer atomic SPIR-V lacks atomic operation");
	Check(memory_barrier != Common::FIND_INVALID_INDEX,
	      "buffer atomic SPIR-V lacks memory barrier after atomic operation");
	Check(bounds_branch < atomic, "buffer atomic was emitted before bounds guard");
	Check(atomic < memory_barrier, "buffer atomic memory barrier was emitted before atomic");
}

void TestNewShaderRecompilerBranchConditionForms() {
	struct Case {
		uint32_t    opcode;
		const char* condition;
	};
	const Case cases[] = {
	    {0x04, "condition=scc0"},
	    {0x08, "condition=execz"},
	    {0x07, "condition=vccnz"},
	};

	for (const auto& c: cases) {
		const uint32_t shader[] = {
		    EncodeSopp(c.opcode, 1),
		    EncodeSMovB32(0, 129),
		    0xbf810000u,
		};

		ShaderRecompiler::CompileOptions options;
		options.stage   = ShaderType::Compute;
		options.dump_ir = true;

		ShaderRecompiler::CompileResult result;
		std::string                     error;
		Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
		Check(Common::ContainsStr(result.ir_dump, c.condition),
		      "branch condition was not preserved");
		Check(SpirvContainsOpcode(result.spirv, 250),
		      "branch condition SPIR-V lacks OpBranchConditional");
		CheckSpirvBinaryValidates(result.spirv);
		if (c.opcode == 0x04) {
			const auto source = DisassembleSpirvBinary(result.spirv);
			Check(!Common::ContainsStr(source, "OpGroupNonUniformBallot"),
			      "structured SCC branch retained a host-subgroup reduction");
		}
	}
}

void TestNewShaderRecompilerSetpcBranch() {
	const uint32_t shader[] = {
	    EncodeSop1(0x1f, 4, 0),      // s_getpc_b64 s[4:5]
	    EncodeSop2(0x00, 4, 4, 140), // s_add_u32 s4, s4, 12
	    EncodeSop1(0x20, 0, 4),      // s_setpc_b64 s[4:5]
	    EncodeSMovB32(0, 129),       0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "s_setpc_b64"), "S_SETPC_B64 was not decoded");
	Check(Common::ContainsStr(result.ir_dump, "successors=["),
	      "S_SETPC_B64 did not participate in CFG");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerSetpcJumpTable() {
	const uint32_t shader[] = {
	    EncodeSop2(0x07, 0, 0, 129), // s_min_u32 s0, s0, 1
	    EncodeSop2(0x1e, 0, 0, 131), // s_lshl_b32 s0, s0, 3
	    EncodeSop1(0x1f, 8, 0),      // s_getpc_b64 s[8:9]
	    EncodeSop2(0x00, 8, 8, 255), // s_add_u32 s8, s8, literal
	    0x00000038u,
	    EncodeSop2(0x04, 9, 9, 128), // s_addc_u32 s9, s9, 0
	    EncodeSmem0(0x01, 4, 4),
	    0u,                          // s_load_dwordx2 s[4:5], s[8:9], s0
	    EncodeSopp(0x0c, 0),         // s_waitcnt 0
	    EncodeSop1(0x1f, 10, 0),     // s_getpc_b64 s[10:11]
	    EncodeSop2(0x00, 10, 10, 4), // s_add_u32 s10, s10, s4
	    EncodeSop2(0x04, 11, 11, 5), // s_addc_u32 s11, s11, s5
	    EncodeSop1(0x20, 0, 10),     // s_setpc_b64 s[10:11]
	    EncodeSMovB32(1, 129),       // case 0
	    EncodeSopp(0x02, 1),
	    EncodeSMovB32(2, 129), // case 1
	    EncodeSopp(0x01, 0),
	    0x0000000cu,
	    0x00000000u, // table: target case 0 relative to pc 0x28
	    0x00000014u,
	    0x00000000u, // table: target case 1 relative to pc 0x28
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Compute;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.ir_dump, "mode=dispatcher"),
	      "S_SETPC_B64 jump table did not select dispatcher fallback");
	Check(Common::ContainsStr(result.ir_dump, "indirect_targets=2"),
	      "S_SETPC_B64 jump table did not retain both targets");
	Check(Common::ContainsStr(result.ir_dump, "selector_values=2"),
	      "S_SETPC_B64 jump table did not retain selector mapping");
	Check(!Common::ContainsStr(result.ir_dump, "SLoadDword"),
	      "S_SETPC_B64 jump table load was reflected as a raw scalar buffer load");
	Check(SpirvContainsOpcode(result.spirv, 251), "dispatcher SPIR-V lacks OpSwitch");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerExpVertexOutputs() {
	const uint32_t shader[] = {
	    EncodeExp0(0x0c, 0xf), EncodeExp1(0, 1, 2, 3), // POS0
	    EncodeExp0(0x20, 0xf), EncodeExp1(4, 5, 6, 7), // PARAM0
	    EncodeExp0(0x14, 0x1), EncodeExp1(8, 0, 0, 0), // PRIM
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Vertex;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.decoded_dump, "target=0x0c"), "POS export was not decoded");
	Check(Common::ContainsStr(result.decoded_dump, "target=0x20"), "PARAM export was not decoded");
	Check(Common::ContainsStr(result.decoded_dump, "target=0x14"), "PRIM export was not decoded");
	Check(Common::ContainsStr(result.ir_dump, "position"), "POS export did not reach IR");
	Check(Common::ContainsStr(result.ir_dump, "parameter"), "PARAM export did not reach IR");
	Check(Common::ContainsStr(result.ir_dump, "primitive"), "PRIM export did not reach IR");
	Check(SpirvContainsOpcode(result.spirv, 62), "vertex export SPIR-V lacks OpStore");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerVertexSystemVgprs() {
	using StageInputKind = ShaderRecompiler::IR::StageInputKind;

	const uint32_t shader[] = {
	    EncodeVop1(0x01, 0, 5 + 256), // v_mov_b32 v0, v5
	    EncodeVop1(0x01, 1, 8 + 256), // v_mov_b32 v1, v8
	    EncodeExp0(0x0c, 0xf),
	    EncodeExp1(0, 1, 2, 3), // POS0
	    EncodeExp0(0x20, 0x3),
	    EncodeExp1(0, 1, 0, 0), // PARAM0
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Vertex;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(ProgramHasInput(result.program, StageInputKind::VertexIndex),
	      "vertex shader missing VertexIndex input");
	Check(ProgramHasInput(result.program, StageInputKind::InstanceIndex),
	      "vertex shader missing InstanceIndex input");
	Check(Common::ContainsStr(result.ir_dump, "MoveU32 v0, v5"),
	      "vertex shader did not keep v5 as a guest VGPR source");
	Check(Common::ContainsStr(result.ir_dump, "MoveU32 v1, v8"),
	      "vertex shader did not keep v8 as a guest VGPR source");
	CheckSpirvBinaryValidates(result.spirv);

	const auto source = DisassembleSpirvBinary(result.spirv);
	Check(CountSourceOccurrences(source, "OpLoad %int %gl_VertexIndex") == 1u,
	      "vertex SPIR-V does not load gl_VertexIndex");
	Check(CountSourceOccurrences(source, "OpLoad %int %gl_InstanceIndex") == 1u,
	      "vertex SPIR-V does not load gl_InstanceIndex");
	Check(CountSourceOccurrences(source, "OpStore %v5") >= 2u,
	      "vertex SPIR-V does not seed guest v5 from gl_VertexIndex");
	Check(CountSourceOccurrences(source, "OpStore %v8") >= 2u,
	      "vertex SPIR-V does not seed guest v8 from gl_InstanceIndex");
}

void TestNewShaderRecompilerVertexExportUsesLaneExecMask() {
	const uint32_t shader[] = {
	    EncodeSop1(0x04, 126, 129), // s_mov_b64 exec, 1
	    EncodeExp0(0x0c, 0xf),
	    EncodeExp1(0, 1, 2, 3), // POS0
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Vertex;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	CheckSpirvBinaryValidates(result.spirv);

	const auto source = DisassembleSpirvBinary(result.spirv);
	Check(Common::ContainsStr(source, "gl_SubgroupInvocationID"),
	      "vertex export EXEC guard did not test the current lane");
	Check(Common::ContainsStr(source, "OpBranchConditional"),
	      "vertex export EXEC guard did not branch on the lane-active mask");

	options.lane_mask_mode = ShaderLaneMaskMode::PerInvocation;
	ShaderRecompiler::CompileResult local_result;
	Check(ShaderRecompiler::TryRecompile(shader, options, local_result, &error), error.c_str());
	Check(local_result.program.lane_mask_mode == ShaderLaneMaskMode::PerInvocation,
	      "per-invocation mask mode was not preserved in the immutable program");
	CheckSpirvBinaryValidates(local_result.spirv);
	const auto local_source = DisassembleSpirvBinary(local_result.spirv);
	Check(!Common::ContainsStr(local_source, "OpLoad %uint %gl_SubgroupInvocationID"),
	      "per-invocation EXEC still depends on the native subgroup lane");
	Check(Common::ContainsStr(local_source, "OpBranchConditional"),
	      "per-invocation vertex export lost its EXEC guard");
}

void TestNewShaderRecompilerPerInvocationMasks() {
	const uint32_t local_shader[] = {
	    EncodeVopc(0xc1, 0 + 256, 1),    // v_cmp_lt_u32 vcc, v0, v1
	    EncodeSop2(0x0f, 2, 126, 106),   // s_and_b64 s[2:3], exec, vcc
	    EncodeSop1(0x24, 4, 126),        // s_and_saveexec_b64 s[4:5], exec
	    EncodeSop2(0x25, 126, 132, 128), // s_bfm_b64 exec, 4, 0
	    EncodeSop1(0x08, 126, 126),      // s_not_b64 exec, exec
	    EncodeSop1(0x08, 126, 126),      // s_not_b64 exec, exec
	    EncodeExp0(0x0c, 0xf),
	    EncodeExp1(0, 1, 2, 3), // POS0
	    EncodeSopp(0x01),
	};

	ShaderRecompiler::CompileOptions options;
	options.stage          = ShaderType::Vertex;
	options.lane_mask_mode = ShaderLaneMaskMode::PerInvocation;
	options.dump_ir        = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(local_shader, options, result, &error), error.c_str());
	CheckSpirvBinaryValidates(result.spirv);
	const auto source = DisassembleSpirvBinary(result.spirv);
	Check(!Common::ContainsStr(source, "OpGroupNonUniformBallot"),
	      "per-invocation VCC producer still materialized a shared subgroup mask");
	Check(!Common::ContainsStr(source, "OpLoad %uint %gl_SubgroupInvocationID"),
	      "per-invocation BFM EXEC prefix still selected native subgroup lanes");
	Check(Common::ContainsStr(source, "OpLogicalAnd") &&
	          Common::ContainsStr(source, "OpLogicalNot"),
	      "per-invocation wide mask ALU did not use Boolean operations");
	Check(Common::ContainsStr(source, "OpStore %vcc_lo") &&
	          Common::ContainsStr(source, "OpStore %vcc_hi %uint_0"),
	      "per-invocation comparison did not store a local Boolean VCC pair");
	Check(Common::ContainsStr(result.ir_dump, "SaveexecB64 s4, exec_lo"),
	      "per-invocation SAVEEXEC regression did not reach IR");

	const uint32_t wqm_shader[] = {
	    EncodeSop1(0x0a, 2, 126), // s_wqm_b64 s[2:3], exec
	    EncodeExp0(0x0c, 0xf),
	    EncodeExp1(0, 1, 2, 3), // POS0
	    EncodeSopp(0x01),
	};
	Check(ShaderRecompiler::TryRecompile(wqm_shader, options, result, &error), error.c_str());
	CheckSpirvBinaryValidates(result.spirv);
	const auto wqm_source = DisassembleSpirvBinary(result.spirv);
	Check(Common::ContainsStr(wqm_source, "OpCapability GroupNonUniformBallot") &&
	          Common::ContainsStr(wqm_source, "OpGroupNonUniformBallot"),
	      "per-invocation scalar WQM omitted its subgroup ballot capability");

	const uint32_t cross_lane_shader[] = {
	    EncodeSop2(0x25, 126, 132, 128), // s_bfm_b64 exec, 4, 0
	    EncodeVop1(0x01, 0, 250),
	    EncodeVop1Dpp(1), // v_mov_b32 v0, v1 dpp
	    EncodeExp0(0x0c, 0xf),
	    EncodeExp1(0, 1, 2, 3), // POS0
	    EncodeSopp(0x01),
	};
	Check(ShaderRecompiler::TryRecompile(cross_lane_shader, options, result, &error),
	      error.c_str());
	CheckSpirvBinaryValidates(result.spirv);
	Check(Common::ContainsStr(DisassembleSpirvBinary(result.spirv), "OpGroupNonUniformBallot"),
	      "per-invocation cross-lane EXEC was not reconstructed as a subgroup ballot");
}

void TestNewShaderRecompilerExpPixelOutputs() {
	const uint32_t shader[] = {
	    EncodeExp0(0x00, 0xf),
	    EncodeExp1(0, 1, 2, 3), // MRT0
	    EncodeExp0(0x08, 0x1),
	    EncodeExp1(4, 0, 0, 0), // MRTZ depth
	    EncodeExp0(0x09, 0x0),
	    EncodeExp1(0, 0, 0, 0), // NULL
	    EncodeExp0(0x00, 0xf, true, true),
	    EncodeExp1(5, 6, 0, 0), // compressed MRT0
	    0xbf810000u,
	};

	ShaderRecompiler::CompileOptions options;
	options.stage   = ShaderType::Pixel;
	options.dump_ir = true;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(Common::ContainsStr(result.ir_dump, "mrt"), "MRT export did not reach IR");
	Check(Common::ContainsStr(result.ir_dump, "mrtz"), "MRTZ export did not reach IR");
	Check(Common::ContainsStr(result.ir_dump, "null"), "NULL export did not reach IR");
	Check(Common::ContainsStr(result.ir_dump, "compr=1"), "compressed MRT export did not reach IR");
	Check(SpirvContainsOpcode(result.spirv, 62), "pixel export SPIR-V lacks OpStore");
	Check(SpirvContainsExtInst(result.spirv, 62),
	      "compressed pixel export SPIR-V lacks GLSL.std.450 UnpackHalf2x16");
	Check(SpirvContainsOpcode(result.spirv, 81),
	      "compressed pixel export SPIR-V lacks OpCompositeExtract");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestRenderTargetReverseFloat16ExportMapping() {
	const auto format =
	    TextureGetRenderTargetFormat(Prospero::GpuEnumValue(Prospero::ChannelLayout::k16_16_16_16),
	                                 Prospero::GpuEnumValue(Prospero::ChannelType::kFloat),
	                                 Prospero::GpuEnumValue(Prospero::ChannelOrder::kReversed));
	Check(format.format == vk::Format::eR16G16B16A16Sfloat && format.bytes_per_element == 8u,
	      "reverse RGBA16F render target did not retain its native host format "
	      "and size");
	Check(format.export_mapping == Prospero::ColorMappingAbgr &&
	          format.export_mapping.ApplyMask(0x1u) == 0x8u &&
	          format.export_mapping.ApplyMask(0x2u) == 0x4u &&
	          format.export_mapping.ApplyMask(0x4u) == 0x2u &&
	          format.export_mapping.ApplyMask(0x8u) == 0x1u &&
	          format.export_mapping.ApplyMask(0xfu) == 0xfu,
	      "reverse RGBA16F render-target export or write-mask mapping is "
	      "incorrect");
	const auto legacy_alt = TextureGetRenderTargetFormat(
	    Prospero::GpuEnumValue(Prospero::ChannelLayout::k8_8_8_8),
	    Prospero::GpuEnumValue(Prospero::ChannelType::kUNorm),
	    Prospero::GpuEnumValue(Prospero::ChannelOrder::kAlt));
	Check(legacy_alt.format == vk::Format::eB8G8R8A8Unorm && legacy_alt.export_mapping.IsIdentity(),
	      "legacy BGRA render target acquired a duplicate shader export mapping");

	const uint32_t shader[] = {
	    EncodeExp0(0x00, 0xf),
	    EncodeExp1(0, 1, 2, 3), // MRT0
	    0xbf810000u,
	};
	ShaderPixelInputInfo identity_info;
	ShaderPixelInputInfo reversed_info;
	reversed_info.target_export_mapping[0] = format.export_mapping;

	ShaderRecompiler::CompileOptions options;
	options.stage            = ShaderType::Pixel;
	options.pixel_input_info = &identity_info;
	ShaderRecompiler::CompileResult identity_result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, identity_result, &error), error.c_str());
	Check(SpirvInstructionOpcodeCount(identity_result.spirv, 79u) == 0u,
	      "identity MRT export unexpectedly added a component shuffle");

	options.pixel_input_info = &reversed_info;
	ShaderRecompiler::CompileResult reversed_result;
	Check(ShaderRecompiler::TryRecompile(shader, options, reversed_result, &error), error.c_str());
	Check(SpirvContainsVectorShuffle(reversed_result.spirv, {3u, 2u, 1u, 0u}),
	      "reverse MRT export did not emit a WZYX component shuffle");
	CheckSpirvBinaryValidates(reversed_result.spirv);

	HW::PixelShaderInfo regs {};
	Check(ShaderGetIdPS(regs, identity_info, false) !=
	          ShaderGetIdPS(regs, reversed_info, false),
	      "pixel shader cache identity omitted the render-target export mapping");

	regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(shader);
	regs.ps_regs.chksum    = 0xf16ab6f000000001ull;
	ShaderMappedData mapped {};
	mapped.code_size_bytes = sizeof(shader);
	ShaderMapUserData(regs.ps_regs.data_addr, mapped);
	HW::ShaderRegisters sh {};
	ShaderVertexInputInfo vs_info {};
	vs_info.stage.program = std::make_shared<ShaderRecompiler::IR::Program>();
	std::array<Prospero::ColorComponentMapping, 8> mappings {};
	mappings[0] = format.export_mapping;
	ShaderPixelInputInfo      compiled_info {};
	std::span<const uint32_t> compiled_spirv;
	Check(ShaderCompileInfoPS(regs, sh, ShaderLaneMaskMode::NativeWave, vs_info, mappings,
	                          compiled_info, compiled_spirv) &&
	          compiled_info.target_export_mapping[0].IsIdentity(),
	      "inactive reverse MRT mapping was not normalized out of the shader cache key");
	sh.target_output_mode[0] = 4;
	Check(ShaderCompileInfoPS(regs, sh, ShaderLaneMaskMode::NativeWave, vs_info, mappings,
	                          compiled_info, compiled_spirv) &&
	          compiled_info.target_export_mapping[0] == format.export_mapping,
	      "active reverse MRT mapping was lost before shader specialization");
}

void TestNewShaderRecompilerEarlyZDisabledWhenPixelKillEnabled() {
	constexpr uint32_t ExecutionModeEarlyFragmentTests = 9;

	const uint32_t shader[] = {
	    EncodeExp0(0x00, 0xf, true, false, true),
	    EncodeExp1(0, 1, 2, 3),
	    0xbf810000u,
	};

	ShaderPixelInputInfo ps_info;
	ps_info.ps_early_z           = true;
	ps_info.ps_pixel_kill_enable = true;

	ShaderRecompiler::CompileOptions options;
	options.stage            = ShaderType::Pixel;
	options.pixel_input_info = &ps_info;

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, options, result, &error), error.c_str());
	Check(SpirvContainsOpcode(result.spirv, 252),
	      "pixel valid-mask export should still lower to OpKill");
	Check(!SpirvContainsExecutionMode(result.spirv, ExecutionModeEarlyFragmentTests),
	      "pixel shaders that may kill fragments must not request EarlyFragmentTests");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerNativeBindingPlan() {
	using BindingKind = ShaderRecompiler::IR::DescriptorBindingKind;

	const uint32_t shader[] = {
	    EncodeSmem0(0x08, 0, 4),
	    0u, // s_buffer_load_dword s0
	    EncodeMubuf0(0x1c, 8),
	    EncodeMubuf1(0, 1, 1), // buffer_store_dword
	    EncodeMimg0(0x20, 0xf),
	    EncodeMimg1(8, 2, 3, 1), // image_sample
	    EncodeMimg0(0x08, 0xf),
	    EncodeMimg1(12, 5, 0, 1), // image_store
	    EncodeMimg0(0x11, 0x1),
	    EncodeMimg1(16, 6, 0, 1), // image_atomic_add
	    0xbf810000u,
	};

	auto                             user_data = ImageTestUserData();
	ShaderRecompiler::CompileOptions options;
	options.stage     = ShaderType::Compute;
	options.dump_ir   = true;
	options.user_data = user_data.data();

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	const auto compiled = ShaderRecompiler::TryRecompile(shader, options, result, &error);
	Check(compiled, error.c_str());
	const auto* buffers =
	    ShaderRecompiler::IR::FindBinding(result.program.bindings, BindingKind::Buffers);
	const auto* sampled =
	    ShaderRecompiler::IR::FindBinding(result.program.bindings, BindingKind::Sampled2D);
	const auto* storage =
	    ShaderRecompiler::IR::FindBinding(result.program.bindings, BindingKind::Storage2D);
	const auto* uint_storage =
	    ShaderRecompiler::IR::FindBinding(result.program.bindings, BindingKind::StorageUint2D);
	const auto* samplers =
	    ShaderRecompiler::IR::FindBinding(result.program.bindings, BindingKind::Samplers);
	Check(buffers != nullptr && buffers->resources.size() == 2,
	      "native binding plan did not allocate scalar/vector buffers");
	Check(sampled != nullptr && sampled->resources.size() == 1,
	      "native binding plan did not allocate the sampled image");
	Check(storage != nullptr && storage->resources.size() == 1,
	      "native binding plan did not allocate the float storage image");
	Check(uint_storage != nullptr && uint_storage->resources.size() == 1,
	      "native binding plan did not allocate the uint storage image");
	Check(samplers != nullptr && samplers->resources.size() == 1,
	      "native binding plan did not allocate the sampler");
	Check(SpirvContainsOpcode(result.spirv, 86),
	      "SPIR-V binary does not combine separate image/sampler descriptors");
	Check(SpirvHasDecorationValue(result.spirv, 33u, buffers->binding),
	      "SPIR-V lacks storage-buffer Binding decoration");
	Check(SpirvHasDecorationValue(result.spirv, 33u, sampled->binding),
	      "SPIR-V lacks sampled-image Binding decoration");
	Check(SpirvHasDecorationValue(result.spirv, 33u, samplers->binding),
	      "SPIR-V lacks sampler Binding decoration");
	Check(SpirvHasDecorationValue(result.spirv, 34u, result.program.bindings.descriptor_set),
	      "SPIR-V lacks DescriptorSet decoration");
	CheckSpirvBinaryValidates(result.spirv);

	auto malformed = result.program;
	auto malformed_buffers =
	    std::find_if(malformed.bindings.descriptors.begin(), malformed.bindings.descriptors.end(),
	                 [](const auto& binding) { return binding.kind == BindingKind::Buffers; });
	Check(malformed_buffers != malformed.bindings.descriptors.end(),
	      "native validation fixture lacks a buffer group");
	malformed_buffers->resources.clear();
	std::vector<uint32_t> rejected_spirv  = {0xdeadbeefu};
	const auto            rejected_before = rejected_spirv;
	std::string           rejected_error;
	Check(!ShaderRecompiler::Spirv::EmitProgram(malformed, result.resources, nullptr, nullptr,
	                                            nullptr, rejected_spirv, &rejected_error) &&
	          rejected_spirv == rejected_before &&
	          rejected_error.find("topology") != std::string::npos,
	      "malformed native binding plan was not rejected transactionally");
	const auto FindBufferInstruction = [](auto* program) {
		for (auto& block: program->blocks) {
			const auto found = std::find_if(
			    block.instructions.begin(), block.instructions.end(), [](const auto& inst) {
				    return inst.memory.kind == ShaderRecompiler::IR::ResourceKind::Buffer;
			    });
			if (found != block.instructions.end()) {
				return &*found;
			}
		}
		return static_cast<ShaderRecompiler::IR::Instruction*>(nullptr);
	};

	auto  malformed_inst = result.program;
	auto* buffer_inst    = FindBufferInstruction(&malformed_inst);
	Check(buffer_inst != nullptr, "native validation fixture lacks a buffer instruction");
	buffer_inst->op          = ShaderRecompiler::IR::Opcode::FlatLoadDword;
	buffer_inst->memory.kind = ShaderRecompiler::IR::ResourceKind::Flat;
	buffer_inst->src_count   = 1;
	rejected_spirv           = rejected_before;
	rejected_error.clear();
	Check(!ShaderRecompiler::Spirv::EmitProgram(malformed_inst, result.resources, nullptr, nullptr,
	                                            nullptr, rejected_spirv, &rejected_error) &&
	          rejected_spirv == rejected_before &&
	          rejected_error.find("contract") != std::string::npos,
	      "malformed native instruction was not rejected before fatal emitter paths");

	auto missing_buffer_operand = result.program;
	buffer_inst                 = FindBufferInstruction(&missing_buffer_operand);
	Check(buffer_inst != nullptr, "native validation fixture lacks a buffer-arity candidate");
	buffer_inst->src_count--;
	rejected_spirv = rejected_before;
	rejected_error.clear();
	Check(!ShaderRecompiler::Spirv::EmitProgram(missing_buffer_operand, result.resources, nullptr,
	                                            nullptr, nullptr, rejected_spirv,
	                                            &rejected_error) &&
	          rejected_spirv == rejected_before &&
	          rejected_error.find("contract") != std::string::npos,
	      "buffer instruction with a missing address operand was accepted");

	auto extra_buffer_operand = result.program;
	buffer_inst               = FindBufferInstruction(&extra_buffer_operand);
	Check(buffer_inst != nullptr, "native validation fixture lacks an extra-operand candidate");
	buffer_inst->src_count++;
	rejected_spirv = rejected_before;
	rejected_error.clear();
	Check(!ShaderRecompiler::Spirv::EmitProgram(extra_buffer_operand, result.resources, nullptr,
	                                            nullptr, nullptr, rejected_spirv,
	                                            &rejected_error) &&
	          rejected_spirv == rejected_before &&
	          rejected_error.find("contract") != std::string::npos,
	      "buffer instruction with an extra address operand was accepted");

	auto  malformed_atomic = result.program;
	auto* atomic_inst      = FindBufferInstruction(&malformed_atomic);
	Check(atomic_inst != nullptr, "native validation fixture lacks an atomic candidate");
	atomic_inst->op          = ShaderRecompiler::IR::Opcode::AtomicAddU32;
	atomic_inst->memory.kind = ShaderRecompiler::IR::ResourceKind::ScalarBuffer;
	atomic_inst->src_count   = 2;
	rejected_spirv           = rejected_before;
	rejected_error.clear();
	Check(
	    !ShaderRecompiler::Spirv::EmitProgram(malformed_atomic, result.resources, nullptr, nullptr,
	                                          nullptr, rejected_spirv, &rejected_error) &&
	        rejected_spirv == rejected_before && rejected_error.find("atomic") != std::string::npos,
	    "malformed atomic instruction reached a fatal descriptor path");

	auto overwide_atomic = result.program;
	atomic_inst          = FindBufferInstruction(&overwide_atomic);
	Check(atomic_inst != nullptr, "native validation fixture lacks an overwide atomic candidate");
	atomic_inst->op        = ShaderRecompiler::IR::Opcode::AtomicAddU32;
	atomic_inst->src_count = 5;
	rejected_spirv         = rejected_before;
	rejected_error.clear();
	Check(!ShaderRecompiler::Spirv::EmitProgram(overwide_atomic, result.resources, nullptr, nullptr,
	                                            nullptr, rejected_spirv, &rejected_error) &&
	          rejected_spirv == rejected_before &&
	          rejected_error.find("fixed IR storage") != std::string::npos,
	      "overwide atomic operands reached fixed emitter storage");

	auto  overwide_move = result.program;
	auto& move_inst     = overwide_move.blocks.front().instructions.front();
	move_inst.op        = ShaderRecompiler::IR::Opcode::MoveU32;
	move_inst.memory    = {};
	move_inst.src_count = 5;
	rejected_spirv      = rejected_before;
	rejected_error.clear();
	Check(!ShaderRecompiler::Spirv::EmitProgram(overwide_move, result.resources, nullptr, nullptr,
	                                            nullptr, rejected_spirv, &rejected_error) &&
	          rejected_spirv == rejected_before &&
	          rejected_error.find("fixed IR storage") != std::string::npos,
	      "overwide non-memory operands reached fixed emitter storage");

	auto  malformed_image = result.program;
	auto* image_inst      = static_cast<ShaderRecompiler::IR::Instruction*>(nullptr);
	for (auto& block: malformed_image.blocks) {
		const auto found = std::find_if(
		    block.instructions.begin(), block.instructions.end(),
		    [](const auto& inst) { return inst.op == ShaderRecompiler::IR::Opcode::ImageSample; });
		if (found != block.instructions.end()) {
			image_inst = &*found;
			break;
		}
	}
	Check(image_inst != nullptr, "native validation fixture lacks a sampled image");
	image_inst->src_count = 0;
	rejected_spirv        = rejected_before;
	rejected_error.clear();
	Check(!ShaderRecompiler::Spirv::EmitProgram(malformed_image, result.resources, nullptr, nullptr,
	                                            nullptr, rejected_spirv, &rejected_error) &&
	          rejected_spirv == rejected_before &&
	          rejected_error.find("sampled image") != std::string::npos,
	      "malformed image operands reached fixed emitter storage");

	auto     stale_resources = result.resources;
	uint32_t float_storage   = UINT32_MAX;
	for (uint32_t i = 0; i < result.program.info.images.size(); i++) {
		if (result.program.info.images[i].kind ==
		    ShaderRecompiler::IR::ResourceKind::StorageImage) {
			float_storage = i;
			break;
		}
	}
	Check(float_storage != UINT32_MAX, "native validation fixture lacks float storage image");
	stale_resources.images[float_storage].dwords[1] = 20u << 20u;
	rejected_spirv                                  = rejected_before;
	rejected_error.clear();
	Check(!ShaderRecompiler::Spirv::EmitProgram(result.program, stale_resources, nullptr, nullptr,
	                                            nullptr, rejected_spirv, &rejected_error) &&
	          rejected_spirv == rejected_before &&
	          rejected_error.find("specialized format") != std::string::npos,
	      "stale runtime image format re-selected an absent descriptor group");

	auto stale_dimension = result.resources;
	stale_dimension.images[float_storage].dwords[3] &= 0x0fffffffu;
	rejected_spirv = rejected_before;
	rejected_error.clear();
	Check(!ShaderRecompiler::Spirv::EmitProgram(result.program, stale_dimension, nullptr, nullptr,
	                                            nullptr, rejected_spirv, &rejected_error) &&
	          rejected_spirv == rejected_before &&
	          rejected_error.find("specialized dimension") != std::string::npos,
	      "unsupported runtime image type bypassed specialization validation");
}

bool LowerProvenanceOnly(const uint32_t* code, uint32_t words, ShaderRecompiler::IR::Program& ir,
                         std::string* error) {
	ShaderRecompiler::Decoder::Program decoded;
	if (!ShaderRecompiler::Decoder::DecodeProgram(std::span {code, words}, decoded, error)) {
		return false;
	}
	ShaderRecompiler::CFG::Graph cfg;
	if (!ShaderRecompiler::CFG::BuildGraph(decoded, cfg, error) ||
	    !ShaderRecompiler::IR::LowerProgram(decoded, cfg, ShaderType::Compute, 64, ir, error)) {
		return false;
	}
	return ShaderRecompiler::IR::BuildScalarProvenance(ir, error) &&
	       ShaderRecompiler::IR::BuildSrtPlan(ir, error);
}

bool ReadSrtHostDword(void*, uint64_t address, uint32_t* value) {
	if (address == 0 || value == nullptr) {
		return false;
	}
	std::memcpy(value, reinterpret_cast<const void*>(address), sizeof(*value));
	return true;
}

void TestScalarProvenanceRealWideMoveLowering() {
	const uint32_t shader[] = {
	    EncodeSop1(0x04, 0, 20),                        // s_mov_b64 s[0:1], s[20:21]
	    EncodeSop1(0x04, 2, 22),                        // s_mov_b64 s[2:3], s[22:23]
	    EncodeMubuf0(0x0c),      EncodeMubuf1(0, 0, 1), // buffer_load_dword via copied s[0:3]
	    EncodeSopp(0x01),
	};

	std::string                   error;
	ShaderRecompiler::IR::Program ir;
	const auto                    lowered =
	    LowerProvenanceOnly(shader, static_cast<uint32_t>(std::size(shader)), ir, &error);
	Check(lowered, error.c_str());

	const ShaderRecompiler::IR::Instruction* use = nullptr;
	for (const auto& block: ir.blocks) {
		for (const auto& inst: block.instructions) {
			if (inst.op == ShaderRecompiler::IR::Opcode::BufferLoadDword) {
				use = &inst;
			}
		}
	}
	Check(use != nullptr, "real wide-move shader did not lower a buffer use");
	const auto* source = ShaderRecompiler::IR::GetDescriptorSource(ir, use->memory.resource_source);
	Check(source != nullptr &&
	          ShaderRecompiler::IR::DescriptorSourceResolved(ir, use->memory.resource_source),
	      "real wide-move descriptor source was unresolved");
	for (uint32_t i = 0; i < 4; i++) {
		const auto& value = ir.provenance.values[source->dwords[i]];
		Check(value.op == ShaderRecompiler::IR::ScalarValueOp::UserData && value.imm == 20 + i,
		      "real s_mov_b64 lowering did not copy both descriptor pairs");
	}
}

void TestScalarProvenanceRealCarryAndScalarLoads() {
	const uint32_t carry_shader[] = {
	    EncodeSop1(0x1f, 0, 0),      // s_getpc_b64 s[0:1]
	    EncodeSop2(0x00, 0, 0, 129), // s_add_u32 s0, s0, 1
	    EncodeSop2(0x04, 1, 1, 128), // s_addc_u32 s1, s1, 0
	    EncodeMubuf0(0x0c),
	    EncodeMubuf1(0, 0, 1), // buffer_load_dword via computed s[0:3]
	    EncodeSopp(0x01),
	};
	std::string                   error;
	ShaderRecompiler::IR::Program carry_ir;
	const auto                    carry_lowered = LowerProvenanceOnly(
	    carry_shader, static_cast<uint32_t>(std::size(carry_shader)), carry_ir, &error);
	Check(carry_lowered, error.c_str());
	const ShaderRecompiler::IR::DescriptorValue* carry_source = nullptr;
	for (const auto& block: carry_ir.blocks) {
		for (const auto& inst: block.instructions) {
			if (inst.op == ShaderRecompiler::IR::Opcode::BufferLoadDword) {
				carry_source = ShaderRecompiler::IR::GetDescriptorSource(
				    carry_ir, inst.memory.resource_source);
			}
		}
	}
	Check(carry_source != nullptr, "real add/addc descriptor source was not attached");
	const auto& high = carry_ir.provenance.values[carry_source->dwords[1]];
	Check(high.op == ShaderRecompiler::IR::ScalarValueOp::AddCarry &&
	          carry_ir.provenance.values[high.args[2]].op ==
	              ShaderRecompiler::IR::ScalarValueOp::Carry,
	      "real s_add_u32/s_addc_u32 lowering lost SCC carry provenance");

	const uint32_t load_shader[] = {
	    EncodeSmem0(0x02, 0, 4),
	    0u, // s_load_dwordx4 s[0:3]
	    EncodeMubuf0(0x0c),
	    EncodeMubuf1(0, 0, 1),
	    EncodeSmem0(0x0a, 4, 4),
	    0u, // s_buffer_load_dwordx4 s[4:7]
	    EncodeMubuf0(0x0c),
	    EncodeMubuf1(0, 1, 1),
	    EncodeSopp(0x01),
	};
	ShaderRecompiler::IR::Program load_ir;
	const auto                    load_lowered = LowerProvenanceOnly(
	    load_shader, static_cast<uint32_t>(std::size(load_shader)), load_ir, &error);
	Check(load_lowered, error.c_str());
	uint32_t uses = 0;
	for (const auto& block: load_ir.blocks) {
		for (const auto& inst: block.instructions) {
			if (inst.op != ShaderRecompiler::IR::Opcode::BufferLoadDword) {
				continue;
			}
			const auto* source =
			    ShaderRecompiler::IR::GetDescriptorSource(load_ir, inst.memory.resource_source);
			Check(source != nullptr, "real scalar-load descriptor source was not attached");
			const auto expected = uses++ == 0
			                          ? ShaderRecompiler::IR::ScalarValueOp::ReadConst
			                          : ShaderRecompiler::IR::ScalarValueOp::ReadConstBuffer;
			for (uint32_t i = 0; i < 4; i++) {
				Check(load_ir.provenance.values[source->dwords[i]].op == expected,
				      "real scalar load used the wrong provenance operation");
			}
		}
	}
	Check(uses == 2, "real scalar-load provenance test did not find both buffer uses");

	const uint32_t inline_sampler_shader[] = {
	    EncodeSop2(0x25, 12, 128 + 12, 128 + 44), // s_bfm_b64 s[12:13], 12, 44
	    EncodeSop1(0x04, 14, 255),
	    0x09500000u, // s_mov_b64 s[14:15], 0x09500000
	    EncodeMimg0(0xa0, 0xf),
	    EncodeMimg1(2, 1, 3, 6), // image_sample_a v2, v6, s[4:11], s[12:15]
	    EncodeSopp(0x01),
	};
	ShaderRecompiler::IR::Program inline_sampler_ir;
	error.clear();
	Check(LowerProvenanceOnly(inline_sampler_shader,
	                          static_cast<uint32_t>(std::size(inline_sampler_shader)),
	                          inline_sampler_ir, &error),
	      error.c_str());
	uint32_t sampler_source = 0;
	for (const auto& block: inline_sampler_ir.blocks) {
		for (const auto& inst: block.instructions) {
			if (inst.op == ShaderRecompiler::IR::Opcode::ImageSample) {
				sampler_source = inst.memory.sampler_source;
			}
		}
	}
	ShaderRecompiler::IR::DescriptorValue sampler;
	ShaderRecompiler::IR::SrtRuntime      runtime;
	Check(sampler_source >= 2 &&
	          ShaderRecompiler::IR::DescriptorSourceResolved(inline_sampler_ir, sampler_source) &&
	          ShaderRecompiler::IR::EvaluateDescriptorSource(inline_sampler_ir, sampler_source,
	                                                         0x10, runtime, sampler, &error) &&
	          sampler.dwords[0] == 0 && sampler.dwords[1] == 0x00fff000u &&
	          sampler.dwords[2] == 0x09500000u && sampler.dwords[3] == 0,
	      "real inline sampler construction was unresolved or evaluated incorrectly");

	auto                             user_data = ImageTestUserData();
	ShaderRecompiler::CompileOptions options;
	options.stage     = ShaderType::Pixel;
	options.user_data = user_data.data();
	ShaderRecompiler::CompileResult result;
	error.clear();
	Check(ShaderRecompiler::TryRecompile(inline_sampler_shader, options, result, &error),
	      error.c_str());
}

void TestSrtWalkerRealSmemLowering() {
	const uint32_t shader[] = {
	    EncodeSMovB32(124, 130), // m0 = 2
	    EncodeSmem0(0x02, 0, 4),
	    (124u << 25u) | 2u, // s_load_dwordx4 s[0:3], s[8:9], m0 offset + immediate 2
	    EncodeMubuf0(0x0c),      EncodeMubuf1(0, 0, 1), EncodeSopp(0x01),
	};
	std::string                   error;
	ShaderRecompiler::IR::Program ir;
	const auto                    lowered =
	    LowerProvenanceOnly(shader, static_cast<uint32_t>(std::size(shader)), ir, &error);
	Check(lowered, error.c_str());
	Check(ir.srt.reads.size() == 4 && ir.srt.dynamic_reads.empty(),
	      "real SMEM lowering did not build four compact SRT reads");

	const std::array<uint32_t, 4> table     = {0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u};
	std::array<uint32_t, 16>      user_data = {};
	const auto                    address   = reinterpret_cast<uint64_t>(table.data());
	user_data[8]                            = static_cast<uint32_t>(address);
	user_data[9]                            = static_cast<uint32_t>(address >> 32u);
	std::vector<uint32_t>                  flat;
	const ShaderRecompiler::IR::SrtRuntime runtime {user_data, 0, ReadSrtHostDword, nullptr};
	const auto walked = ShaderRecompiler::IR::WalkSrt(ir, runtime, flat, &error);
	Check(walked, error.c_str());
	Check(flat.size() == table.size() && std::equal(flat.begin(), flat.end(), table.begin()),
	      "real SMEM SRT walk did not apply component-level alignment");
	Check(ShaderRecompiler::IR::PatchSrtReads(ir, &error), error.c_str());
	uint32_t patched = 0;
	for (const auto& block: ir.blocks) {
		for (const auto& inst: block.instructions) {
			if (inst.op == ShaderRecompiler::IR::Opcode::LoadSrtDword) {
				Check(inst.src[0].imm == patched++, "real SMEM patch used the wrong flat offset");
			}
		}
	}
	Check(patched == 4, "real SMEM patch did not rewrite every component");
}

void TestSrtWalkerRealSBufferLowering() {
	const uint32_t shader[] = {
	    EncodeSMovB32(124, 130), // m0 = 2
	    EncodeSmem0(0x0a, 0, 4),
	    (124u << 25u) | 2u, // s_buffer_load_dwordx4 s[0:3], s[8:11], m0 + 2
	    EncodeMubuf0(0x0c),      EncodeMubuf1(0, 0, 1), EncodeSopp(0x01),
	};
	std::string                   error;
	ShaderRecompiler::IR::Program ir;
	const auto                    lowered =
	    LowerProvenanceOnly(shader, static_cast<uint32_t>(std::size(shader)), ir, &error);
	Check(lowered, error.c_str());
	Check(ir.srt.reads.size() == 4 && ir.srt.dynamic_reads.empty(),
	      "real S_BUFFER_LOAD lowering did not build four compact reads");

	const std::array<uint32_t, 5> table     = {0x11111111u, 0x22222222u, 0x33333333u, 0x44444444u,
	                                           0x55555555u};
	std::array<uint32_t, 16>      user_data = {};
	const auto                    address   = reinterpret_cast<uint64_t>(table.data());
	user_data[8]                            = static_cast<uint32_t>(address);
	user_data[9]                            = static_cast<uint32_t>(address >> 32u);
	user_data[10]                           = sizeof(table);
	std::vector<uint32_t>                  flat;
	const ShaderRecompiler::IR::SrtRuntime runtime {user_data, 0, ReadSrtHostDword, nullptr};
	const auto walked = ShaderRecompiler::IR::WalkSrt(ir, runtime, flat, &error);
	Check(walked, error.c_str());
	Check(flat.size() == 4 && std::equal(flat.begin(), flat.end(), table.begin() + 1),
	      "real S_BUFFER_LOAD walk used the wrong final alignment");

	user_data[10]                  = 4 * sizeof(uint32_t);
	const auto flat_before_failure = flat;
	const auto bounds_walked       = ShaderRecompiler::IR::WalkSrt(ir, runtime, flat, &error);
	Check(!bounds_walked && error.find("exceeds size=16") != std::string::npos,
	      "real S_BUFFER_LOAD walk ignored descriptor bounds");
	Check(flat == flat_before_failure,
	      "failed real S_BUFFER_LOAD walk changed the prior flat snapshot");
	Check(ShaderRecompiler::IR::PatchSrtReads(ir, &error), error.c_str());
	uint32_t patched = 0;
	for (const auto& block: ir.blocks) {
		for (const auto& inst: block.instructions) {
			if (inst.op == ShaderRecompiler::IR::Opcode::LoadSrtDword) {
				Check(inst.src[0].imm == patched++,
				      "real S_BUFFER_LOAD patch used the wrong flat offset");
			}
		}
	}
	Check(patched == 4, "real S_BUFFER_LOAD patch did not rewrite every component");

	const uint32_t negative_shader[] = {
	    EncodeSmem0(0x0a, 0, 4),
	    (125u << 25u) | 0x1ffffeu, // illegal negative S_BUFFER immediate
	    EncodeMubuf0(0x0c),        EncodeMubuf1(0, 0, 1), EncodeSopp(0x01),
	};
	ShaderRecompiler::IR::Program negative_ir;
	const auto                    negative_lowered = LowerProvenanceOnly(
	    negative_shader, static_cast<uint32_t>(std::size(negative_shader)), negative_ir, &error);
	Check(negative_lowered, error.c_str());
	user_data[10] = sizeof(table);
	Check(!ShaderRecompiler::IR::WalkSrt(negative_ir, runtime, flat, &error) &&
	          error.find("negative immediate") != std::string::npos,
	      "real S_BUFFER_LOAD walk accepted a negative immediate");
}

void TestScalarMemoryLoadsSnapshotOverlappingOperands() {
	const auto CheckOverlap = [](uint32_t opcode) {
		const uint32_t shader[] = {
		    EncodeSmem0(opcode, 0, 0),
		    125u << 25u, // load s[0:3] through overlapping s[0:*], null SOFFSET
		    EncodeMubuf0(0x0c),
		    EncodeMubuf1(0, 0, 1),
		    EncodeSopp(0x01),
		};
		std::string                   error;
		ShaderRecompiler::IR::Program ir;
		Check(LowerProvenanceOnly(shader, static_cast<uint32_t>(std::size(shader)), ir, &error),
		      error.c_str());
		Check(ir.srt.reads.size() == 4 && ir.srt.dynamic_reads.empty(),
		      opcode == 0x02
		          ? "overlapping S_LOAD operands were evaluated after a component write"
		          : "overlapping S_BUFFER_LOAD operands were evaluated after a component write");
		Check(ShaderRecompiler::IR::PatchSrtReads(ir, &error), error.c_str());
		uint32_t patched = 0;
		for (const auto& block: ir.blocks) {
			for (const auto& inst: block.instructions) {
				if (inst.op == ShaderRecompiler::IR::Opcode::LoadSrtDword) {
					Check(inst.src[0].imm == patched++,
					      "overlapping scalar-memory patch used the wrong flat offset");
				}
			}
		}
		Check(patched == 4, "overlapping scalar-memory load was not fully patched");
	};
	CheckOverlap(0x02); // s_load_dwordx4
	CheckOverlap(0x0a); // s_buffer_load_dwordx4
}

void TestScalarMemoryLoadCrossesIntoVcc() {
	const uint32_t shader[] = {
	    EncodeSmem0(0x02, 104, 4),
	    125u << 25u, // s_load_dwordx4 s[104:105], vcc_lo, vcc_hi
	    EncodeMubuf0(0x0c),
	    EncodeMubuf1(0, 26, 1),
	    EncodeSopp(0x01),
	};
	std::string                   error;
	ShaderRecompiler::IR::Program ir;
	Check(LowerProvenanceOnly(shader, static_cast<uint32_t>(std::size(shader)), ir, &error),
	      error.c_str());
	Check(ir.srt.reads.size() == 4 && ir.srt.dynamic_reads.empty(),
	      "wide SMEM destination crossing into VCC lost scalar provenance");
	Check(ShaderRecompiler::IR::PatchSrtReads(ir, &error), error.c_str());
	uint32_t patched = 0;
	for (const auto& block: ir.blocks) {
		for (const auto& inst: block.instructions) {
			if (inst.op == ShaderRecompiler::IR::Opcode::LoadSrtDword) {
				Check(inst.src[0].imm == patched++,
				      "wide SMEM destination crossing into VCC used the wrong flat offset");
			}
		}
	}
	Check(patched == 4, "wide SMEM destination crossing into VCC was not fully patched");
}

void TestResourceTrackingRealDensePatching() {
	const uint32_t shader[] = {
	    EncodeMubuf0(0x0c),     EncodeMubuf1(0, 0, 1),    // buffer load s[0:3]
	    EncodeMubuf0(0x0c),     EncodeMubuf1(1, 0, 1),    // same buffer
	    EncodeMubuf0(0x1c),     EncodeMubuf1(2, 1, 1),    // buffer store s[4:7]
	    EncodeMimg0(0x20, 0xf), EncodeMimg1(8, 0, 2, 1),  // sampled image s[0:7], sampler s[8:11]
	    EncodeMimg0(0x08, 0xf), EncodeMimg1(12, 0, 0, 1), // storage view of the same image source
	    EncodeSopp(0x01),
	};
	std::string                   error;
	ShaderRecompiler::IR::Program ir;
	const auto                    lowered =
	    LowerProvenanceOnly(shader, static_cast<uint32_t>(std::size(shader)), ir, &error);
	Check(lowered, error.c_str());
	Check(ShaderRecompiler::IR::PatchSrtReads(ir, &error), error.c_str());
	const auto tracked = ShaderRecompiler::IR::TrackResources(ir, &error);
	Check(tracked, error.c_str());
	Check(ir.info.buffers.size() == 2 && ir.info.images.size() == 2 && ir.info.samplers.size() == 1,
	      "real resource tracking produced the wrong dense list sizes");

	uint32_t buffer_use = 0;
	uint32_t image_use  = 0;
	for (const auto& block: ir.blocks) {
		for (const auto& inst: block.instructions) {
			switch (inst.memory.kind) {
				case ShaderRecompiler::IR::ResourceKind::Buffer: {
					const uint32_t expected = buffer_use++ < 2 ? 0 : 1;
					Check(inst.memory.resource == expected,
					      "real buffer operand was not patched densely");
					break;
				}
				case ShaderRecompiler::IR::ResourceKind::Image:
				case ShaderRecompiler::IR::ResourceKind::StorageImage:
					Check(inst.memory.resource == image_use++,
					      "real image operand was not patched by view class");
					if (inst.op == ShaderRecompiler::IR::Opcode::ImageSample) {
						Check(inst.memory.sampler == 0,
						      "real sampler operand was not patched densely");
					}
					break;
				default: break;
			}
		}
	}
	Check(buffer_use == 3 && image_use == 2 && ir.info.buffers[0].read &&
	          ir.info.buffers[1].written,
	      "real tracked resource access facts were incomplete");
}

void TestLowerProgramResetsAnalysisState() {
	const uint32_t first_shader[] = {EncodeMubuf0(0x0c), EncodeMubuf1(0, 0, 1), EncodeSopp(0x01)};
	std::string    error;
	ShaderRecompiler::IR::Program ir;
	Check(LowerProvenanceOnly(first_shader, static_cast<uint32_t>(std::size(first_shader)), ir,
	                          &error),
	      error.c_str());
	Check(ShaderRecompiler::IR::PatchSrtReads(ir, &error), error.c_str());
	Check(ShaderRecompiler::IR::TrackResources(ir, &error), error.c_str());
	ShaderComputeInputInfo compute;
	Check(ShaderRecompiler::IR::CollectShaderInfo(ir, {.compute = &compute}, &error),
	      error.c_str());
	Check(ir.resource_tracking_complete && ir.shader_info_complete && !ir.info.buffers.empty(),
	      "analysis-reset fixture did not reach completed state");
	ir.shader_hash = 0xdeadbeef;

	const uint32_t second_shader[] = {EncodeSopp(0x01)};
	Check(LowerProvenanceOnly(second_shader, static_cast<uint32_t>(std::size(second_shader)), ir,
	                          &error),
	      error.c_str());
	Check(!ir.resource_tracking_complete && !ir.shader_info_complete && ir.srt_plan_complete &&
	          ir.shader_hash == 0 && ir.info.buffers.empty() && ir.info.images.empty() &&
	          ir.info.samplers.empty() && ir.info.sampled_pairs.empty() && ir.info.inputs.empty() &&
	          ir.info.outputs.empty(),
	      "LowerProgram reused stale provenance/resource/interface state");
}

void TestNewShaderRecompilerStageInputInfo() {
	using StageInputKind = ShaderRecompiler::IR::StageInputKind;

	const uint32_t shader[] = {0xbf810000u};

	ShaderComputeInputInfo cs_info {};
	cs_info.group_id[0]                = true;
	cs_info.thread_ids_num             = 3;
	cs_info.dispatch_thread_dimensions = true;

	ShaderRecompiler::CompileOptions cs_options;
	cs_options.stage              = ShaderType::Compute;
	cs_options.compute_input_info = &cs_info;

	ShaderRecompiler::CompileResult cs_result;
	std::string                     error;
	Check(ShaderRecompiler::TryRecompile(shader, cs_options, cs_result, &error), error.c_str());
	Check(ProgramHasInput(cs_result.program, StageInputKind::WorkgroupId),
	      "compute WorkgroupId input missing from reflection");
	Check(ProgramHasInput(cs_result.program, StageInputKind::LocalInvocationId),
	      "compute LocalInvocationId input missing from reflection");
	Check(ProgramHasInput(cs_result.program, StageInputKind::LocalInvocationIndex),
	      "compute LocalInvocationIndex input missing from reflection");
	Check(ProgramHasInput(cs_result.program, StageInputKind::GlobalInvocationId),
	      "compute GlobalInvocationId input missing from reflection");
	Check(SpirvHasDecorationValue(cs_result.spirv, 11u, 26u),
	      "SPIR-V lacks WorkgroupId BuiltIn decoration");
	Check(SpirvHasDecorationValue(cs_result.spirv, 11u, 27u),
	      "SPIR-V lacks LocalInvocationId BuiltIn decoration");
	Check(SpirvHasDecorationValue(cs_result.spirv, 11u, 28u),
	      "SPIR-V lacks GlobalInvocationId BuiltIn decoration");
	Check(SpirvHasDecorationValue(cs_result.spirv, 11u, 29u),
	      "SPIR-V lacks LocalInvocationIndex BuiltIn decoration");
	CheckSpirvBinaryValidates(cs_result.spirv);

	ShaderRecompiler::CompileOptions vs_options;
	vs_options.stage = ShaderType::Vertex;

	ShaderRecompiler::CompileResult vs_result;
	Check(ShaderRecompiler::TryRecompile(shader, vs_options, vs_result, &error), error.c_str());
	Check(ProgramHasInput(vs_result.program, StageInputKind::VertexIndex),
	      "vertex VertexIndex input missing from reflection");
	Check(ProgramHasInput(vs_result.program, StageInputKind::InstanceIndex),
	      "vertex InstanceIndex input missing from reflection");
	Check(SpirvHasDecorationValue(vs_result.spirv, 11u, 42u),
	      "SPIR-V lacks VertexIndex BuiltIn decoration");
	Check(SpirvHasDecorationValue(vs_result.spirv, 11u, 43u),
	      "SPIR-V lacks InstanceIndex BuiltIn decoration");
	CheckSpirvBinaryValidates(vs_result.spirv);

	ShaderPixelInputInfo ps_info {};
	ps_info.input_num = 2;
	ps_info.ps_pos_x  = true;
	ps_info.ps_pos_y  = true;
	ps_info.ps_pos_z  = true;
	SetIdentityInterpolatorSettings(&ps_info);

	ShaderRecompiler::CompileOptions ps_options;
	ps_options.stage            = ShaderType::Pixel;
	ps_options.pixel_input_info = &ps_info;

	ShaderRecompiler::CompileResult ps_result;
	Check(ShaderRecompiler::TryRecompile(shader, ps_options, ps_result, &error), error.c_str());
	Check(ProgramHasInput(ps_result.program, StageInputKind::FragCoord),
	      "pixel FragCoord input missing from reflection");
	Check(ProgramInputCount(ps_result.program, StageInputKind::Parameter) == 2,
	      "pixel interpolant inputs missing from reflection");
	Check(SpirvHasDecorationValue(ps_result.spirv, 11u, 15u),
	      "SPIR-V lacks FragCoord BuiltIn decoration");
	Check(SpirvHasDecorationValue(ps_result.spirv, 30u, 0u),
	      "SPIR-V lacks interpolant Location 0 decoration");
	Check(SpirvHasDecorationValue(ps_result.spirv, 30u, 1u),
	      "SPIR-V lacks interpolant Location 1 decoration");
	CheckSpirvBinaryValidates(ps_result.spirv);

	ShaderPixelInputInfo ps_pos_y_info {};
	ps_pos_y_info.input_num            = 1;
	ps_pos_y_info.ps_system_input_base = 2;
	ps_pos_y_info.ps_pos_y             = true;
	SetIdentityInterpolatorSettings(&ps_pos_y_info);
	ps_options.pixel_input_info = &ps_pos_y_info;

	ShaderRecompiler::CompileResult ps_pos_y_result;
	Check(ShaderRecompiler::TryRecompile(shader, ps_options, ps_pos_y_result, &error),
	      error.c_str());
	Check(ProgramHasInput(ps_pos_y_result.program, StageInputKind::FragCoord),
	      "pixel POS_Y-only FragCoord input missing from reflection");
	Check(SpirvHasDecorationValue(ps_pos_y_result.spirv, 11u, 15u),
	      "SPIR-V lacks POS_Y-only FragCoord BuiltIn decoration");
	CheckSpirvBinaryValidates(ps_pos_y_result.spirv);
}

void TestNewShaderRecompilerPixelPipelineEntry() {
	const uint32_t shader[] = {0xbf810000u};

	EnsureConfigInitialized();

	HW::PixelShaderInfo regs {};
	regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(shader);
	regs.ps_regs.chksum    = 0x1234567800000001ull;
	ShaderMappedData mapped {};
	mapped.code_size_bytes = sizeof(shader);
	ShaderMapUserData(regs.ps_regs.data_addr, mapped);

	HW::ShaderRegisters   sh {};
	ShaderPixelInputInfo  input_info {};
	std::vector<uint32_t> spirv;
	Check(ShaderCompileSpirvPS(regs, sh, ShaderLaneMaskMode::NativeWave, input_info, spirv),
	      "new pixel shader recompiler wrapper did not produce SPIR-V");
	Check(!spirv.empty(), "new pixel shader recompiler wrapper returned empty SPIR-V");
	Check(spirv.front() == 0x07230203u,
	      "new pixel shader recompiler wrapper did not emit SPIR-V binary");
	CheckSpirvBinaryValidates(spirv);

	const uint32_t vcc_load_shader[] = {
	    EncodeSop1(0x24, 0, 106), // s_and_saveexec_b64 s[0:1], vcc
	    EncodeSMovB32(106, 27),   EncodeSMovB32(107, 28), EncodeSmem0(0x02, 44, 53),
	    (0x7du << 25u) | 160u,    EncodeMubuf0(0x0c),     EncodeMubuf1(0, 11, 1),
	    EncodeSopp(0x01),
	};
	std::array<uint32_t, 44> table {};
	std::array<uint32_t, 1>  buffer {};
	const auto               table_address  = reinterpret_cast<uint64_t>(table.data());
	const auto               buffer_address = reinterpret_cast<uint64_t>(buffer.data());
	table[40]                               = static_cast<uint32_t>(buffer_address);
	table[41]                               = static_cast<uint32_t>(buffer_address >> 32u);
	table[42]                               = 1;

	HW::PixelShaderInfo vcc_regs {};
	vcc_regs.ps_regs.data_addr       = reinterpret_cast<uint64_t>(vcc_load_shader);
	vcc_regs.ps_regs.chksum          = 0x6306606500000001ull;
	vcc_regs.ps_regs.rsrc2.user_sgpr = 29;
	vcc_regs.ps_user_sgpr.value[27]  = static_cast<uint32_t>(table_address);
	vcc_regs.ps_user_sgpr.value[28]  = static_cast<uint32_t>(table_address >> 32u);
	ShaderMappedData vcc_mapped {};
	vcc_mapped.code_size_bytes = sizeof(vcc_load_shader);
	ShaderMapUserData(vcc_regs.ps_regs.data_addr, vcc_mapped);

	ShaderPixelInputInfo  vcc_input {};
	std::vector<uint32_t> vcc_spirv;
	Check(ShaderCompileSpirvPS(vcc_regs, sh, ShaderLaneMaskMode::NativeWave, vcc_input, vcc_spirv),
	      "VCC raw-load pointer was lost");
	CheckSpirvBinaryValidates(vcc_spirv);
}

void TestPixelProgramCacheDescriptorSetIdentity() {
	const uint32_t      shader_01[]   = {0xbf810000u};
	const uint32_t      shader_10[]   = {0xbf810000u};
	const uint32_t      shader_mask[] = {0xbf810000u};
	HW::ShaderRegisters sh {};

	auto check_transition = [&](const uint32_t* shader, uint64_t checksum,
	                            bool first_has_vs_descriptors, bool second_has_vs_descriptors) {
		HW::PixelShaderInfo regs {};
		regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(shader);
		regs.ps_regs.chksum    = checksum;
		ShaderMappedData mapped {};
		mapped.code_size_bytes = sizeof(uint32_t);
		ShaderMapUserData(regs.ps_regs.data_addr, mapped);

		auto compile = [&](bool has_vs_descriptors) {
			auto vs_program = std::make_shared<ShaderRecompiler::IR::Program>();
			if (has_vs_descriptors) {
				vs_program->bindings.descriptors.emplace_back();
			}
			ShaderVertexInputInfo vs_info {};
			vs_info.stage.program = std::move(vs_program);
			const std::array<Prospero::ColorComponentMapping, 8> identity_mappings {};
			ShaderPixelInputInfo      ps_info {};
			std::span<const uint32_t> spirv;
			Check(ShaderCompileInfoPS(regs, sh, ShaderLaneMaskMode::NativeWave, vs_info,
			                          identity_mappings, ps_info, spirv),
			      "pixel program-cache transition failed to compile");
			const auto expected_set = has_vs_descriptors ? 1u : 0u;
			Check(ps_info.descriptor_set == expected_set && ps_info.stage.program != nullptr &&
			          ps_info.stage.program->bindings.descriptor_set == expected_set,
			      "pixel program cache reused SPIR-V with the paired VS descriptor set");
		};

		compile(first_has_vs_descriptors);
		compile(second_has_vs_descriptors);
	};

	check_transition(shader_01, 0x91a27e6300000001ull, false, true);
	check_transition(shader_10, 0x91a27e6300000002ull, true, false);

	HW::PixelShaderInfo mask_regs {};
	mask_regs.ps_regs.data_addr = reinterpret_cast<uint64_t>(shader_mask);
	mask_regs.ps_regs.chksum    = 0x91a27e6300000003ull;
	ShaderMappedData mapped {};
	mapped.code_size_bytes = sizeof(shader_mask);
	ShaderMapUserData(mask_regs.ps_regs.data_addr, mapped);
	auto compile_mask_mode = [&](ShaderLaneMaskMode mode) {
		ShaderVertexInputInfo vs_info {};
		vs_info.stage.program = std::make_shared<ShaderRecompiler::IR::Program>();
		const std::array<Prospero::ColorComponentMapping, 8> identity_mappings {};
		ShaderPixelInputInfo      ps_info {};
		std::span<const uint32_t> spirv;
		Check(ShaderCompileInfoPS(mask_regs, sh, mode, vs_info, identity_mappings, ps_info, spirv),
		      "pixel lane-mask cache transition failed to compile");
		Check(ps_info.stage.program != nullptr && ps_info.stage.program->lane_mask_mode == mode,
		      "pixel program cache reused a different lane-mask lowering");
	};
	compile_mask_mode(ShaderLaneMaskMode::NativeWave);
	compile_mask_mode(ShaderLaneMaskMode::PerInvocation);
}

void TestNewShaderRecompilerUnsupportedMemoryDecode() {
	const uint32_t mubuf_unknown[] = {EncodeMubuf0(0x7b), EncodeMubuf1(0, 0, 1), 0xbf810000u};
	CheckNewDecoderUnsupported(mubuf_unknown, static_cast<uint32_t>(std::size(mubuf_unknown)),
	                           "MUBUF", "opcode=0x7b");

	const uint32_t flat_unknown[] = {EncodeFlat0(0x7e, 0, 4), EncodeFlat1(9, 0x7d, 0, 1),
	                                 0xbf810000u};
	CheckNewDecoderUnsupported(flat_unknown, static_cast<uint32_t>(std::size(flat_unknown)), "FLAT",
	                           "opcode=0x7e");

	const uint32_t mtbuf_unknown[] = {EncodeMtbuf0(0x08, 14, 7, 4), EncodeMtbuf1(0x08, 9, 0, 1),
	                                  0xbf810000u};
	CheckNewDecoderUnsupported(mtbuf_unknown, static_cast<uint32_t>(std::size(mtbuf_unknown)),
	                           "MTBUF", "opcode=0x08");
}

void TestNewShaderRecompilerFlatUserPointerProvenance() {
	const uint32_t shader[] = {
	    EncodeVop2(0x25, 0, 0, 2), // v_add_nc_u32 v0, s0, v2
	    EncodeVop1(0x01, 1, 1),    // v_mov_b32 v1, s1
	    EncodeFlat0(0x0c, 0, 0),
	    EncodeFlat1(3, 0x7d, 0, 0), // flat_load_dword v3, v[0:1]
	    0xbf810000u,
	};
	const uint32_t user_data[] = {0x34567000u, 0x00000012u};

	ShaderRecompiler::CompileOptions options;
	options.stage           = ShaderType::Compute;
	options.user_data       = user_data;
	options.user_data_count = static_cast<uint32_t>(std::size(user_data));

	ShaderRecompiler::CompileResult result;
	std::string                     error;
	const bool compiled = ShaderRecompiler::TryRecompile(shader, options, result, &error);
	Check(compiled, error.c_str());
	Check(result.program.info.addresses.size() == 1 &&
	          result.program.info.addresses[0].source !=
	              ShaderRecompiler::IR::ScalarProvenance::Unknown,
	      "FLAT user pointer provenance was not attached");
	Check(result.resources.addresses.size() == 1 &&
	          result.resources.addresses[0].guest_base == 0x0000001234567000ull &&
	          result.resources.addresses[0].binding_base == 0x0000001234560000ull &&
	          result.program.info.addresses[0].specialized_base == 0x0000001234560000ull,
	      "FLAT user pointer did not materialize its guest base");
	CheckSpirvBinaryValidates(result.spirv);
}

void TestNewShaderRecompilerFlatAddressProvenanceBoundaries() {
	const uint32_t segmented_shader[] = {
	    EncodeVop2(0x25, 0, 0, 2),
	    EncodeVop1(0x01, 1, 1),
	    EncodeFlat0(0x0c, 2, 0),
	    EncodeFlat1(3, 0x7d, 0, 0), // global_load_dword v3, v[0:1]
	    EncodeFlat0(0x0c, 1, 0),
	    EncodeFlat1(4, 0x7d, 0, 0), // scratch_load_dword v4, v[0:1]
	    0xbf810000u,
	};
	const uint32_t user_data[] = {0x34567000u, 0u, 0x1000u};

	ShaderRecompiler::CompileOptions options;
	options.stage            = ShaderType::Compute;
	options.user_data        = user_data;
	options.user_data_count  = static_cast<uint32_t>(std::size(user_data));
	options.flat_memory_base = 0;
	ShaderRecompiler::CompileResult result;
	std::string                     error;
	const bool                      compiled =
	    ShaderRecompiler::TryRecompile(segmented_shader, options, result, &error);
	Check(compiled, error.c_str());
	Check(result.program.info.addresses.size() == 2,
	      "segmented address resources were not tracked independently");
	for (const auto& address: result.program.info.addresses) {
		Check(address.source == ShaderRecompiler::IR::ScalarProvenance::Unknown,
		      "GLOBAL/SCRATCH null-SADDR incorrectly inherited FLAT VGPR provenance");
	}
}

} // namespace
} // namespace Libs::Graphics

int main() {
	using namespace Libs::Graphics;

	EnsureConfigInitialized();

	TestResourceDescriptorClassification();
	TestNativeShaderResourceDependencies();
	TestNativeSubgroupPolicy();
	TestNewShaderRecompilerSMovB32();
	TestNewShaderRecompilerSoppMarkers();
	TestNewShaderRecompilerSopkWaitcntMarkers();
	TestNewShaderRecompilerRdna2ScalarOpcodes();
	TestNewShaderRecompilerScalarVectorAlu();
	TestNewShaderRecompilerVop3LaneReadDestinationEncoding();
	TestNewShaderRecompilerMoreAluFamilies();
	TestNewShaderRecompilerExpandedAluBatch();
	TestNewShaderRecompilerVop3pPackedF16();
	TestNewShaderRecompilerStagedShaderOps();
	TestNewShaderRecompilerBootF16UnaryOpcodes();
	TestNewShaderRecompilerBootB16PackedAndSdwaOpcodes();
	TestNewShaderRecompilerScalarB64Alu();
	TestNewShaderRecompilerSignedCompareAlu();
	TestNewShaderRecompilerSignedMinShiftAlu();
	TestNewShaderRecompilerScalarBitfieldAlu();
	TestNewShaderRecompilerMemoryFamilyLowering();
	TestNewShaderRecompilerImageQueryLowering();
	TestNewShaderRecompilerImageSampleVariants();
	TestNewShaderRecompilerImageSampleA16SamplerCoords();
	TestNewShaderRecompilerImageSampleOpcodeAliases();
	TestNewShaderRecompilerImageSampleA16ExceptionComponents();
	TestNewShaderRecompilerImageSampleA16Rdna2AddressOrder();
	TestNewShaderRecompilerPixelImageSampleLodSelection();
	TestNewShaderRecompilerImageViewDimensions();
	TestNewShaderRecompilerImageGatherVariants();
	TestNewShaderRecompilerImageLoadA16UintCoords();
	TestNewShaderRecompilerImageLoadVariants();
	TestNewShaderRecompilerImageStoreLowering();
	TestNewShaderRecompilerStorageImage3DDescriptorVariant();
	TestNewShaderRecompilerStorageImage2DDescriptorOverridesMimg3D();
	TestNewShaderRecompilerImageAtomicLowering();
	TestNewShaderRecompilerVintrpLowering();
	TestNewShaderRecompilerWideMemoryLowering();
	TestNewShaderRecompilerBufferSignedLoadLowering();
	TestNewShaderRecompilerBufferSubDwordStoreLowering();
	TestNewShaderRecompilerMubufFormatLowering();
	TestNewShaderRecompilerFormattedStoreUsesRuntimeArrayLengthOnly();
	TestNewShaderRecompilerTypedBufferLowering();
	TestNewShaderRecompilerFlatOldBackedLowering();
	TestNewShaderRecompilerUnbasedFlatRequiresTranslator();
	TestNewShaderRecompilerFlatUserPointerProvenance();
	TestNewShaderRecompilerFlatAddressProvenanceBoundaries();
	TestNewShaderRecompilerFlatSignedLoadLowering();
	TestNewShaderRecompilerFlatStoreLowering();
	TestNewShaderRecompilerAtomicLowering();
	TestNewShaderRecompilerDsReadWrite2Lowering();
	TestNewShaderRecompilerDsSubDwordLowering();
	TestNewShaderRecompilerDsWideAndAtomicLowering();
	TestNewShaderRecompilerDsSwizzleLowering();
	TestNewShaderRecompilerDsAddtidLowering();
	TestNewShaderRecompilerDsFloatMinMaxLowering();
	TestNewShaderRecompilerCfgStraightLine();
	TestNewShaderRecompilerCfgIfElse();
	TestNewShaderRecompilerCfgTerminalExitMergePS();
	TestNewShaderRecompilerCfgPostEndTargetMergePS();
	TestNewShaderRecompilerCfgLoopBreakContinue();
	TestNewShaderRecompilerCfgLoopHeaderDynamicScalarBufferLoadStructured();
	TestNewShaderRecompilerCfgLoopHeaderBufferLoadDispatcher();
	TestNewShaderRecompilerCfgLoopHeaderDsAppendConsumeDispatcher();
	TestNewShaderRecompilerCfgSharedOuterAndLoopMerge();
	TestNewShaderRecompilerCfgLoopSharedContinueSelectionMerges();
	TestNewShaderRecompilerCfgDuplicateMergeStructuredSplit();
	TestNewShaderRecompilerCfgIrreducibleDispatcher();
	TestNewShaderRecompilerExecMaskHelpers();
	TestComputeShaderInputWaveSize();
	TestNewShaderRecompilerWave32MasksExecHighStores();
	TestNewShaderRecompilerWave32VccHighScalarStores();
	TestNewShaderRecompilerCompareMaskIsFullWaveBallot();
	TestNewShaderRecompilerBufferLoadsGuardedByExec();
	TestNewShaderRecompilerBufferAtomicsGuardedByBounds();
	TestNewShaderRecompilerBranchConditionForms();
	TestNewShaderRecompilerSetpcBranch();
	TestNewShaderRecompilerSetpcJumpTable();
	TestNewShaderRecompilerExpVertexOutputs();
	TestNewShaderRecompilerVertexSystemVgprs();
	TestNewShaderRecompilerVertexExportUsesLaneExecMask();
	TestNewShaderRecompilerPerInvocationMasks();
	TestNewShaderRecompilerExpPixelOutputs();
	TestRenderTargetReverseFloat16ExportMapping();
	TestNewShaderRecompilerEarlyZDisabledWhenPixelKillEnabled();
	TestScalarProvenanceRealWideMoveLowering();
	TestScalarProvenanceRealCarryAndScalarLoads();
	TestSrtWalkerRealSmemLowering();
	TestSrtWalkerRealSBufferLowering();
	TestScalarMemoryLoadsSnapshotOverlappingOperands();
	TestScalarMemoryLoadCrossesIntoVcc();
	TestResourceTrackingRealDensePatching();
	TestLowerProgramResetsAnalysisState();
	TestNewShaderRecompilerNativeBindingPlan();
	TestNewShaderRecompilerStageInputInfo();
	TestGraphicsCreateInterpolantMapping();
	TestNewShaderRecompilerPixelPipelineEntry();
	TestPixelProgramCacheDescriptorSetIdentity();
	TestNewShaderRecompilerUnsupportedMemoryDecode();

	return 0;
}
