#include "Recompiler.hpp" #include #include #include #include #include #include #include #include #include #include namespace { using namespace ShaderRecompiler; void require(bool condition, const std::string& message) { if (!condition) throw std::runtime_error(message); } constexpr std::array CvtF16F32Store{0x7e021500u, 0xe0700000u, 0x80000100u, 0xbf810000u}; std::vector recompile(std::span capabilities) { const std::array userData{0x10000000u, 0x00100000u, 0x40u, 0x00027facu}; const std::array extensions{"SPV_KHR_storage_buffer_storage_class"}; RecompileRequest request{}; request.shader = {ShaderStage::Compute, 0x20000u, CvtF16F32Store, 0, {}}; request.context.waveSize = 64; request.context.userDataBaseRegister = 0; request.context.userData = userData; request.context.compute = ShaderComputeStageInfo{{64u, 1u, 1u}, 0u, {false, false, false}, false, 1u}; request.target.vulkanVersion = 0x00403000u; request.target.spirvVersion = 0x00010600u; request.target.subgroupSize = 64; request.target.bdaAbiVersion = 1; request.target.supportedCapabilities = capabilities; request.target.supportedExtensions = extensions; request.target.maxWorkgroupSize = {1024u, 1024u, 64u}; request.target.maxWorkgroupInvocations = 1024; request.target.maxWorkgroupSharedMemoryBytes = 49152; request.layout = {0, 0, 0, 128}; request.useCache = false; auto result = Recompile(request); require(!result.spirv.empty(), "v_cvt_f16_f32 did not recompile"); return std::move(result.spirv); } struct Usage { std::size_t floatConverts = 0; bool float16Capability = false; bool roundingModeRte16 = false; bool denormPreserve16 = false; }; Usage scan(std::span words) { Usage usage; for (std::size_t cursor = 5; cursor < words.size();) { const auto count = words[cursor] >> 16u; const auto op = static_cast(words[cursor] & 0xffffu); require(count != 0 && cursor + count <= words.size(), "truncated SPIR-V instruction"); if (op == spv::OpFConvert) ++usage.floatConverts; if (op == spv::OpCapability && words[cursor + 1] == spv::CapabilityFloat16) usage.float16Capability = true; if (op == spv::OpExecutionMode && count == 4 && words[cursor + 3] == 16u) { if (words[cursor + 2] == spv::ExecutionModeRoundingModeRTE) usage.roundingModeRte16 = true; if (words[cursor + 2] == spv::ExecutionModeDenormPreserve) usage.denormPreserve16 = true; } cursor += count; } return usage; } void verifyNative() { const std::array capabilities{spv::CapabilityShader, spv::CapabilityGroupNonUniform, spv::CapabilityFloat16, spv::CapabilityRoundingModeRTE, spv::CapabilityDenormPreserve}; const auto usage = scan(recompile(capabilities)); require(usage.floatConverts != 0 && usage.float16Capability, "a device that rounds 16-bit floats to nearest even did not get OpFConvert to f16"); require(usage.roundingModeRte16 && usage.denormPreserve16, "native f16 conversion without the 16-bit RoundingModeRTE and DenormPreserve execution modes"); } void verifyEmulated() { const std::array withoutRte{spv::CapabilityShader, spv::CapabilityGroupNonUniform, spv::CapabilityFloat16}; const auto usage = scan(recompile(withoutRte)); require(usage.floatConverts == 0 && !usage.float16Capability && !usage.roundingModeRte16, "a device without RoundingModeRTE for 16-bit floats got native f16 conversion"); } } int main() { try { verifyNative(); verifyEmulated(); } catch (const std::exception& error) { std::fprintf(stderr, "Float16Conversion: %s\n", error.what()); return 1; } std::puts("Float16Conversion: native and emulated f16 conversion selected by device capabilities"); return 0; }