From 62da4508b0066adb8c0f04766ddea90cc4b847d9 Mon Sep 17 00:00:00 2001 From: Jack Elliott Date: Tue, 1 Sep 2026 11:33:03 +1200 Subject: [PATCH] [HLSL] Require FP8 conversion support in the LinAlg convert gate The LinAlg convert tests gate on a capability query and classify every outcome as CapabilityGated, so a device that reports no FP8 support skips rather than fails. That is wrong for FP8: proposal 0035 "Emulating FP" states that "the DirectX API specification requires that all implementations support both FP8 formats for matrices, bias, and input vectors", and permits emulation where the hardware lacks native support. The D3D12 runtime feature document agrees - the "Optional" entries in the Tier 1 vector-matrix table are the Native column only, L427 makes the emulation itself required, and the version history records "FP8 is required for tier 1". An implementation that cannot convert to FP8 is non-conformant, so the suite must report it. There is no capability query for the convert operation; the operation type enum covers matrix construction, the three multiply scopes, outer product and atomic accumulate store. The gate therefore proxies through THREAD_VECTOR_MATRIX_MULTIPLY, asking whether any supported configuration advertises FP8 as its VectorInputType. That proxy is sound precisely because 0035 makes FP8-as-input-vector mandatory. queryConvertSupport previously collapsed three distinct outcomes into a single unsupported answer: no linear algebra tier at all, an unsupported source component type, and an unsupported destination. Only the last is a conformance failure. The tier and source results are now reported separately so the caller can downgrade to CapabilityGated for the first two, mirroring matVecMulApplicable. Without that downgrade this change would fail devices that have no linear algebra, or that lack native 16-bit shader operations and so cannot perform an F16 source conversion at all - both strictly worse than the gap being closed. The requirement is passed at the call site, matching how the rest of the file declares mandatory cases. Only the two FP8 destination gates become Mandatory; the I16, I32 and F16 destinations keep their existing CapabilityGated classification, so their behaviour is unchanged. Validation is a per-test differential on preview WARP: 78/76/0/2 with zero per-test movement against the parent commit. That is the expected result and it is not evidence the change works - WARP enumerates VectorInputType=20 and 21, so it never reaches the classification. The evidence is a discriminating control that forces the FP8 destination query to report unsupported: this commit fails both FP8 conversion tests, while the parent skips them. Two further controls confirm the downgrades, with an absent tier and absent native 16-bit support each still skipping. No IHV driver in the loop reports FP8 vector input as unsupported, so the mandatory path is exercised only by forced control on WARP and never by a real driver refusing. This builds on the FP8 datatype mapping fix; without it the gate asserts in toLinAlgDataType before the classification is reached. Assisted-by: GitHub Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 83725f5d-8e98-4c1d-91ee-ad47629e007b --- .../clang/unittests/HLSLExec/LinAlgTests.cpp | 73 +++++++++++++------ 1 file changed, 50 insertions(+), 23 deletions(-) diff --git a/tools/clang/unittests/HLSLExec/LinAlgTests.cpp b/tools/clang/unittests/HLSLExec/LinAlgTests.cpp index 7d46666575..1b7f4abc03 100644 --- a/tools/clang/unittests/HLSLExec/LinAlgTests.cpp +++ b/tools/clang/unittests/HLSLExec/LinAlgTests.cpp @@ -9024,7 +9024,10 @@ static HRESULT queryConvertDestinationGranular( static HRESULT queryConvertSupport(ID3D12Device *Device, ComponentType SourceCompType, ComponentType DestinationCompType, + bool &TierSupported, bool &SourceSupported, bool &Supported) { + TierSupported = false; + SourceSupported = false; Supported = false; if (!Device) return E_INVALIDARG; @@ -9044,10 +9047,13 @@ static HRESULT queryConvertSupport(ID3D12Device *Device, linalg_test::TierSupport Tier; HRESULT HR = linalg_test::queryTierSupport(Device, Tier); - if (FAILED(HR) || !Tier.supported()) + if (FAILED(HR)) return HR; - bool SourceSupported = false; + TierSupported = Tier.supported(); + if (!TierSupported) + return S_OK; + HR = queryConvertSourceSupport(Device, SourceCompType, SourceSupported); if (FAILED(HR) || !SourceSupported) return HR; @@ -9068,21 +9074,32 @@ static HRESULT queryConvertSupport(ID3D12Device *Device, return HR; } -static bool convertTypesApplicable(ID3D12Device *Device, - ComponentType SourceCompType, - ComponentType DestinationCompType, - LPCWSTR CaseName) { +static bool +convertTypesApplicable(ID3D12Device *Device, ComponentType SourceCompType, + ComponentType DestinationCompType, + linalg_test::CapabilityRequirement Requirement, + LPCWSTR CaseName) { + bool TierSupported = false; + bool SourceSupported = false; bool Supported = false; - const HRESULT QueryResult = queryConvertSupport( - Device, SourceCompType, DestinationCompType, Supported); - if (!applyApplicability( - linalg_test::classifyApplicability( - QueryResult, Supported, - linalg_test::CapabilityRequirement::CapabilityGated), - CaseName)) - return false; + const HRESULT QueryResult = + queryConvertSupport(Device, SourceCompType, DestinationCompType, + TierSupported, SourceSupported, Supported); - return true; + // A device without linear algebra is outside the Tier 1 requirements, and a + // mandatory destination is still unreachable when the source type itself is + // unsupported, so both skip rather than failing. + const bool QueryAnswered = SUCCEEDED(QueryResult); + const bool NoLinearAlgebra = QueryAnswered && !TierSupported; + const bool UnsupportedSource = QueryAnswered && !SourceSupported; + + linalg_test::CapabilityRequirement Effective = Requirement; + if (NoLinearAlgebra || UnsupportedSource) + Effective = linalg_test::CapabilityRequirement::CapabilityGated; + + return applyApplicability( + linalg_test::classifyApplicability(QueryResult, Supported, Effective), + CaseName); } template @@ -9420,8 +9437,10 @@ void DxilConf_SM610_LinAlg::CopyConvert_Wave_4x8_F32_ToF16_Transpose() { } void DxilConf_SM610_LinAlg::Convert_I16_ToI32_Exact() { - if (!convertTypesApplicable(D3DDevice, ComponentType::I16, ComponentType::I32, - L"Convert_I16_ToI32_Exact")) + if (!convertTypesApplicable( + D3DDevice, ComponentType::I16, ComponentType::I32, + linalg_test::CapabilityRequirement::CapabilityGated, + L"Convert_I16_ToI32_Exact")) return; runExactConvert( @@ -9447,8 +9466,10 @@ static void runFP8ConvertCase(ID3D12Device *Device, } void DxilConf_SM610_LinAlg::Convert_F32_ToI16_RTNE_Saturate() { - if (!convertTypesApplicable(D3DDevice, ComponentType::F32, ComponentType::I16, - L"Convert_F32_ToI16_RTNE_Saturate")) + if (!convertTypesApplicable( + D3DDevice, ComponentType::F32, ComponentType::I16, + linalg_test::CapabilityRequirement::CapabilityGated, + L"Convert_F32_ToI16_RTNE_Saturate")) return; runExactConvert(D3DDevice, DxcSupport, ConvertF32ToI16CoverageShader, @@ -9471,10 +9492,13 @@ void DxilConf_SM610_LinAlg::Convert_F16_ToE4M3FN_AndBack() { if (!convertTypesApplicable(D3DDevice, ComponentType::F16, ComponentType::F8_E4M3FN, + linalg_test::CapabilityRequirement::Mandatory, L"Convert_F16_ToE4M3FN_AndBack")) return; - if (!convertTypesApplicable(D3DDevice, ComponentType::U32, ComponentType::F16, - L"Convert_F16_ToE4M3FN_AndBack")) + if (!convertTypesApplicable( + D3DDevice, ComponentType::U32, ComponentType::F16, + linalg_test::CapabilityRequirement::CapabilityGated, + L"Convert_F16_ToE4M3FN_AndBack")) return; runFP8ConvertCase(D3DDevice, DxcSupport, ComponentType::F8_E4M3FN, *Data, VerboseLogging); @@ -9492,10 +9516,13 @@ void DxilConf_SM610_LinAlg::Convert_F16_ToE5M2_AndBack() { if (!convertTypesApplicable(D3DDevice, ComponentType::F16, ComponentType::F8_E5M2, + linalg_test::CapabilityRequirement::Mandatory, L"Convert_F16_ToE5M2_AndBack")) return; - if (!convertTypesApplicable(D3DDevice, ComponentType::U32, ComponentType::F16, - L"Convert_F16_ToE5M2_AndBack")) + if (!convertTypesApplicable( + D3DDevice, ComponentType::U32, ComponentType::F16, + linalg_test::CapabilityRequirement::CapabilityGated, + L"Convert_F16_ToE5M2_AndBack")) return; runFP8ConvertCase(D3DDevice, DxcSupport, ComponentType::F8_E5M2, *Data, VerboseLogging);