From 66e975acbfa1e30d9010cee1a1bde9c29daa3604 Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Sun, 22 Mar 2026 08:12:29 +0100 Subject: [PATCH 1/8] Signal when SPIR-V legalization needs targeted cleanup --- external/SPIRV-Headers | 2 +- external/SPIRV-Tools | 2 +- .../lib/Frontend/Rewrite/RewriteObjC.cpp | 2 +- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 22 ++++++++++++--- tools/clang/lib/SPIRV/SpirvEmitter.h | 2 ++ .../cast.flat-conversion.matrix.hlsl | 27 ++++++++++--------- .../legal-examples/15-loop-var-unroll-ok.hlsl | 16 +++++------ tools/clang/test/CodeGenSPIRV/max_id.hlsl | 4 +-- ...uctured-buffer.array.counter.indirect.hlsl | 6 +++-- ...ctured-buffer.array.counter.indirect2.hlsl | 6 +++-- ....global-struct-of-resources.optimized.hlsl | 24 ++++++++--------- .../CodeGenSPIRV/vk.spec-constant.reuse.hlsl | 2 +- 12 files changed, 68 insertions(+), 47 deletions(-) diff --git a/external/SPIRV-Headers b/external/SPIRV-Headers index f31ca173ef..465055f6c9 160000 --- a/external/SPIRV-Headers +++ b/external/SPIRV-Headers @@ -1 +1 @@ -Subproject commit f31ca173eff866369e54d35e53375fadbabd58f4 +Subproject commit 465055f6c9128772e20082e893d974146acf7a02 diff --git a/external/SPIRV-Tools b/external/SPIRV-Tools index 64f5770f59..7134be5024 160000 --- a/external/SPIRV-Tools +++ b/external/SPIRV-Tools @@ -1 +1 @@ -Subproject commit 64f5770f59db933d46b9cad6edc42b4186409ef4 +Subproject commit 7134be5024ffe0b48de1f8206f0fe69e34497bbe diff --git a/tools/clang/lib/Frontend/Rewrite/RewriteObjC.cpp b/tools/clang/lib/Frontend/Rewrite/RewriteObjC.cpp index 204820b304..e55d2acd99 100644 --- a/tools/clang/lib/Frontend/Rewrite/RewriteObjC.cpp +++ b/tools/clang/lib/Frontend/Rewrite/RewriteObjC.cpp @@ -483,7 +483,7 @@ namespace { result = Context->getObjCIdType(); FunctionProtoType::ExtProtoInfo fpi; fpi.Variadic = variadic; - return Context->getFunctionType(result, args, fpi); + return Context->getFunctionType(result, args, fpi, {}); } // Helper function: create a CStyleCastExpr with trivial type source info. diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index 930532191a..4a616a00e8 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -593,6 +593,8 @@ SpirvEmitter::SpirvEmitter(CompilerInstance &ci) constEvaluator(astContext, spvBuilder), entryFunction(nullptr), curFunction(nullptr), curThis(nullptr), seenPushConstantAt(), isSpecConstantMode(false), needsLegalization(false), + needsLegalizationLoopUnroll(false), + needsLegalizationSsaRewrite(false), beforeHlslLegalization(false), mainSourceFile(nullptr) { // Get ShaderModel from command line hlsl profile option. @@ -954,6 +956,9 @@ void SpirvEmitter::HandleTranslationUnit(ASTContext &context) { declIdMapper.requiresFlatteningCompositeResources() || !dsetbindingsToCombineImageSampler.empty() || spirvOptions.signaturePacking; + needsLegalizationSsaRewrite = + needsLegalizationSsaRewrite || + !dsetbindingsToCombineImageSampler.empty(); // Run legalization passes if (spirvOptions.codeGenHighLevel) { @@ -5823,8 +5828,11 @@ SpirvInstruction *SpirvEmitter::createImageSample( SpirvInstruction *minLod, SpirvInstruction *residencyCodeId, SourceLocation loc, SourceRange range) { - if (varOffset) + if (varOffset) { needsLegalization = true; + needsLegalizationLoopUnroll = true; + needsLegalizationSsaRewrite = true; + } // SampleDref* instructions in SPIR-V always return a scalar. // They also have the correct type in HLSL. @@ -8045,7 +8053,7 @@ SpirvInstruction *SpirvEmitter::createVectorSplat(const Expr *scalarExpr, // Should find a more meaningful one. if (auto *constVal = dyn_cast(scalarVal)) { llvm::SmallVector elements(size_t(size), constVal); - const bool isSpecConst = constVal->getopcode() == spv::Op::OpSpecConstant; + const bool isSpecConst = constVal->isSpecConstant(); auto *value = spvBuilder.getConstantComposite(vecType, elements, isSpecConst); if (!value) @@ -16665,7 +16673,15 @@ bool SpirvEmitter::spirvToolsLegalize(std::vector *mod, optimizer.RegisterPass( spvtools::CreateInterfaceVariableScalarReplacementPass()); } - optimizer.RegisterLegalizationPasses(spirvOptions.preserveInterface); + auto legalizationSsaRewriteMode = spvtools::SSARewriteMode::None; + if (needsLegalizationLoopUnroll) { + legalizationSsaRewriteMode = spvtools::SSARewriteMode::All; + } else if (needsLegalizationSsaRewrite) { + legalizationSsaRewriteMode = spvtools::SSARewriteMode::OpaqueOnly; + } + optimizer.RegisterLegalizationPasses( + spirvOptions.preserveInterface, needsLegalizationLoopUnroll, + legalizationSsaRewriteMode); // Add flattening of resources if needed. if (spirvOptions.flattenResourceArrays) { optimizer.RegisterPass( diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.h b/tools/clang/lib/SPIRV/SpirvEmitter.h index 9b890d3af4..e6f5f5e57a 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.h +++ b/tools/clang/lib/SPIRV/SpirvEmitter.h @@ -1578,6 +1578,8 @@ class SpirvEmitter : public ASTConsumer { /// /// Note: legalization specific code bool needsLegalization; + bool needsLegalizationLoopUnroll; + bool needsLegalizationSsaRewrite; /// Whether the translated SPIR-V binary passes --before-hlsl-legalization /// option to spirv-val because of illegal function parameter scope. diff --git a/tools/clang/test/CodeGenSPIRV/cast.flat-conversion.matrix.hlsl b/tools/clang/test/CodeGenSPIRV/cast.flat-conversion.matrix.hlsl index f0b9d547e9..0ba8084f38 100644 --- a/tools/clang/test/CodeGenSPIRV/cast.flat-conversion.matrix.hlsl +++ b/tools/clang/test/CodeGenSPIRV/cast.flat-conversion.matrix.hlsl @@ -16,20 +16,18 @@ RWStructuredBuffer t_output; // COL: OpMemberDecorate %S 0 RowMajor // ROW: OpMemberDecorate %S 0 ColMajor -// The DXIL generated for the two cases seem to produce the same results, -// and this matches that behaviour. -// CHECK: [[array_const:%[0-9]+]] = OpConstantComposite %_arr_float_uint_6 %float_0 %float_1 %float_2 %float_3 %float_4 %float_5 -// CHECK: [[t:%[0-9]+]] = OpConstantComposite %T [[array_const]] - // The DXIL that is generates different order for the values depending on // whether the matrix is column or row major. However, for SPIR-V, the value // stored in both cases is the same because the decoration, which is checked // above, is what determines the layout in memory for the value. -// CHECK: [[row0:%[0-9]+]] = OpConstantComposite %v3float %float_0 %float_1 %float_2 -// CHECK: [[row1:%[0-9]+]] = OpConstantComposite %v3float %float_3 %float_4 %float_5 -// CHECK: [[mat:%[0-9]+]] = OpConstantComposite %mat2v3float %33 %34 -// CHECK: [[s:%[0-9]+]] = OpConstantComposite %S %35 +// CHECK: [[mat:%[0-9]+]] = OpLoad %mat2v3float +// CHECK: [[e00:%[0-9]+]] = OpCompositeExtract %float [[mat]] 0 0 +// CHECK: [[e01:%[0-9]+]] = OpCompositeExtract %float [[mat]] 0 1 +// CHECK: [[e02:%[0-9]+]] = OpCompositeExtract %float [[mat]] 0 2 +// CHECK: [[e10:%[0-9]+]] = OpCompositeExtract %float [[mat]] 1 0 +// CHECK: [[e11:%[0-9]+]] = OpCompositeExtract %float [[mat]] 1 1 +// CHECK: [[e12:%[0-9]+]] = OpCompositeExtract %float [[mat]] 1 2 void main() { S s; @@ -40,13 +38,16 @@ void main() { s.a[i][j] = i*3+j; } } -// CHECK: [[ac:%[0-9]+]] = OpAccessChain %_ptr_Uniform_T %t_output %int_0 %uint_0 -// CHECK: OpStore [[ac]] [[t]] +// CHECK: [[tptr:%[0-9]+]] = OpAccessChain %_ptr_Uniform_T %t_output %int_0 %uint_0 +// CHECK: [[tarr:%[0-9]+]] = OpCompositeConstruct %_arr_float_uint_6 [[e00]] [[e01]] [[e02]] [[e10]] [[e11]] [[e12]] +// CHECK: [[tval:%[0-9]+]] = OpCompositeConstruct %T [[tarr]] +// CHECK: OpStore [[tptr]] [[tval]] T t = (T)(s); t_output[0] = t; -// CHECK: [[ac:%[0-9]+]] = OpAccessChain %_ptr_Uniform_S %s_output %int_0 %uint_0 -// CHECK: OpStore [[ac]] [[s]] +// CHECK: [[sptr:%[0-9]+]] = OpAccessChain %_ptr_Uniform_S %s_output %int_0 %uint_0 +// CHECK: [[sval:%[0-9]+]] = OpCompositeConstruct %S [[mat]] +// CHECK: OpStore [[sptr]] [[sval]] s = (S)t; s_output[0] = s; } diff --git a/tools/clang/test/CodeGenSPIRV/legal-examples/15-loop-var-unroll-ok.hlsl b/tools/clang/test/CodeGenSPIRV/legal-examples/15-loop-var-unroll-ok.hlsl index be80b6c5a8..334eeb676a 100644 --- a/tools/clang/test/CodeGenSPIRV/legal-examples/15-loop-var-unroll-ok.hlsl +++ b/tools/clang/test/CodeGenSPIRV/legal-examples/15-loop-var-unroll-ok.hlsl @@ -1,14 +1,12 @@ // RUN: %dxc -T cs_6_0 -E main -O3 -Vd %s -spirv | FileCheck %s -// CHECK: [[ptr:%[0-9]+]] = OpAccessChain %_ptr_Uniform_S %gSBuffer2 -// CHECK-NEXT: [[val:%[0-9]+]] = OpLoad %S [[ptr]] -// CHECK-NEXT: [[ptr:%[0-9]+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer -// CHECK-NEXT: OpStore [[ptr]] [[val]] - -// CHECK: [[ptr:%[0-9]+]] = OpAccessChain %_ptr_Uniform_S %gSBuffer2 -// CHECK-NEXT: [[val:%[0-9]+]] = OpLoad %S [[ptr]] -// CHECK-NEXT: [[ptr:%[0-9]+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer -// CHECK-NEXT: OpStore [[ptr]] [[val]] +// CHECK: OpLoopMerge {{%[0-9]+}} {{%[0-9]+}} Unroll +// CHECK: [[which:%[0-9]+]] = OpSelect %_ptr_Uniform_type_StructuredBuffer_S {{%[0-9]+}} %gSBuffer1 %gSBuffer2 +// CHECK: [[idx:%[0-9]+]] = OpBitcast %uint {{%[0-9]+}} +// CHECK: [[src:%[0-9]+]] = OpAccessChain %_ptr_Uniform_S [[which]] %int_0 [[idx]] +// CHECK: [[val:%[0-9]+]] = OpLoad %S [[src]] +// CHECK: [[dst:%[0-9]+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer %int_0 [[idx]] +// CHECK: OpStore [[dst]] [[val]] struct S { float4 f; diff --git a/tools/clang/test/CodeGenSPIRV/max_id.hlsl b/tools/clang/test/CodeGenSPIRV/max_id.hlsl index d57267b399..9e1c32c024 100644 --- a/tools/clang/test/CodeGenSPIRV/max_id.hlsl +++ b/tools/clang/test/CodeGenSPIRV/max_id.hlsl @@ -8,7 +8,7 @@ // CHECK-30: fatal error: failed to optimize SPIR-V: ID overflow. Try running compact-ids. // With a larger limit, the test case can compile successfully. -// CHECK-400: Bound: 204 +// CHECK-400: Bound: RWStructuredBuffer data; @@ -20,4 +20,4 @@ void main(uint3 id : SV_DispatchThreadID) for( int i = 0; i < 64; i++ ) { data[i] = i; } -} \ No newline at end of file +} diff --git a/tools/clang/test/CodeGenSPIRV/type.rwstructured-buffer.array.counter.indirect.hlsl b/tools/clang/test/CodeGenSPIRV/type.rwstructured-buffer.array.counter.indirect.hlsl index 76d8165107..3d3a633abb 100644 --- a/tools/clang/test/CodeGenSPIRV/type.rwstructured-buffer.array.counter.indirect.hlsl +++ b/tools/clang/test/CodeGenSPIRV/type.rwstructured-buffer.array.counter.indirect.hlsl @@ -20,11 +20,13 @@ void func(RWStructuredBuffer local) { float4 main(PSInput input) : SV_TARGET { -// CHECK: [[ac2:%[0-9]+]] = OpAccessChain %_ptr_Uniform_int %counter_var_g_rwbuffer {{%[0-9]+}} %uint_0 +// CHECK: [[counter_struct:%[0-9]+]] = OpAccessChain %_ptr_Uniform_type_ACSBuffer_counter %counter_var_g_rwbuffer {{%[0-9]+}} +// CHECK: [[ac2:%[0-9]+]] = OpAccessChain %_ptr_Uniform_int [[counter_struct]] %uint_0 // CHECK: OpAtomicIAdd %int [[ac2]] %uint_1 %uint_0 %int_1 func(g_rwbuffer[input.idx]); -// CHECK: [[ac2_0:%[0-9]+]] = OpAccessChain %_ptr_Uniform_uint %g_rwbuffer {{%[0-9]+}} %int_0 %uint_0 +// CHECK: [[rwbuffer_struct:%[0-9]+]] = OpAccessChain %_ptr_Uniform_type_RWStructuredBuffer_uint %g_rwbuffer {{%[0-9]+}} +// CHECK: [[ac2_0:%[0-9]+]] = OpAccessChain %_ptr_Uniform_uint [[rwbuffer_struct]] %int_0 %uint_0 // CHECK: OpLoad %uint [[ac2_0]] return g_rwbuffer[input.idx][0]; } diff --git a/tools/clang/test/CodeGenSPIRV/type.rwstructured-buffer.array.counter.indirect2.hlsl b/tools/clang/test/CodeGenSPIRV/type.rwstructured-buffer.array.counter.indirect2.hlsl index 0ea52864a2..9ad0fac8f6 100644 --- a/tools/clang/test/CodeGenSPIRV/type.rwstructured-buffer.array.counter.indirect2.hlsl +++ b/tools/clang/test/CodeGenSPIRV/type.rwstructured-buffer.array.counter.indirect2.hlsl @@ -18,11 +18,13 @@ float4 main(PSInput input) : SV_TARGET { RWStructuredBuffer l_rwbuffer[5] = g_rwbuffer; -// CHECK: [[ac2:%[0-9]+]] = OpAccessChain %_ptr_Uniform_int %counter_var_g_rwbuffer %int_0 %uint_0 +// CHECK: [[counter_struct:%[0-9]+]] = OpAccessChain %_ptr_Uniform_type_ACSBuffer_counter %counter_var_g_rwbuffer %int_0 +// CHECK: [[ac2:%[0-9]+]] = OpAccessChain %_ptr_Uniform_int [[counter_struct]] %uint_0 // CHECK: OpAtomicIAdd %int [[ac2]] %uint_1 %uint_0 %int_1 l_rwbuffer[0].IncrementCounter(); -// CHECK: [[ac2_0:%[0-9]+]] = OpAccessChain %_ptr_Uniform_uint %g_rwbuffer {{%[0-9]+}} %int_0 %uint_0 +// CHECK: [[rwbuffer_struct:%[0-9]+]] = OpAccessChain %_ptr_Uniform_type_RWStructuredBuffer_uint %g_rwbuffer {{%[0-9]+}} +// CHECK: [[ac2_0:%[0-9]+]] = OpAccessChain %_ptr_Uniform_uint [[rwbuffer_struct]] %int_0 %uint_0 // CHECK: OpLoad %uint [[ac2_0]] return l_rwbuffer[input.idx][0]; } diff --git a/tools/clang/test/CodeGenSPIRV/vk.binding.global-struct-of-resources.optimized.hlsl b/tools/clang/test/CodeGenSPIRV/vk.binding.global-struct-of-resources.optimized.hlsl index 1656e7f50f..5f668532d1 100644 --- a/tools/clang/test/CodeGenSPIRV/vk.binding.global-struct-of-resources.optimized.hlsl +++ b/tools/clang/test/CodeGenSPIRV/vk.binding.global-struct-of-resources.optimized.hlsl @@ -3,20 +3,20 @@ // Check the names // // CHECK: OpName %secondGlobal_t "secondGlobal.t" -// CHECK: OpName %[[fg0:firstGlobal_[0-9]+__t]] "firstGlobal{{.*}}.t" -// CHECK: OpName %[[fg1:firstGlobal_[0-9]+__t]] "firstGlobal{{.*}}.t" -// CHECK: OpName %[[fg2:firstGlobal_[0-9]+__t]] "firstGlobal{{.*}}.t" -// CHECK: OpName %[[fg3:firstGlobal_[0-9]+__t]] "firstGlobal{{.*}}.t" +// CHECK: OpName %[[fg0:[0-9A-Za-z_]+]] "firstGlobal{{.*}}.t" +// CHECK: OpName %[[fg1:[0-9A-Za-z_]+]] "firstGlobal{{.*}}.t" +// CHECK: OpName %[[fg2:[0-9A-Za-z_]+]] "firstGlobal{{.*}}.t" +// CHECK: OpName %[[fg3:[0-9A-Za-z_]+]] "firstGlobal{{.*}}.t" // CHECK: OpName %secondGlobal_tt_0__s "secondGlobal.tt[0].s" // CHECK: OpName %secondGlobal_tt_1__s "secondGlobal.tt[1].s" -// CHECK: OpName %[[fgtt0_0:firstGlobal_[0-9]+__tt_0__s]] "firstGlobal{{.*}}.tt[0].s" -// CHECK: OpName %[[fgtt0_1:firstGlobal_[0-9]+__tt_1__s]] "firstGlobal{{.*}}.tt[1].s" -// CHECK: OpName %[[fgtt1_0:firstGlobal_[0-9]+__tt_0__s]] "firstGlobal{{.*}}.tt[0].s" -// CHECK: OpName %[[fgtt1_1:firstGlobal_[0-9]+__tt_1__s]] "firstGlobal{{.*}}.tt[1].s" -// CHECK: OpName %[[fgtt2_0:firstGlobal_[0-9]+__tt_0__s]] "firstGlobal{{.*}}.tt[0].s" -// CHECK: OpName %[[fgtt2_1:firstGlobal_[0-9]+__tt_1__s]] "firstGlobal{{.*}}.tt[1].s" -// CHECK: OpName %[[fgtt3_0:firstGlobal_[0-9]+__tt_0__s]] "firstGlobal{{.*}}.tt[0].s" -// CHECK: OpName %[[fgtt3_1:firstGlobal_[0-9]+__tt_1__s]] "firstGlobal{{.*}}.tt[1].s" +// CHECK: OpName %[[fgtt0_0:[0-9A-Za-z_]+]] "firstGlobal{{.*}}.tt[0].s" +// CHECK: OpName %[[fgtt0_1:[0-9A-Za-z_]+]] "firstGlobal{{.*}}.tt[1].s" +// CHECK: OpName %[[fgtt1_0:[0-9A-Za-z_]+]] "firstGlobal{{.*}}.tt[0].s" +// CHECK: OpName %[[fgtt1_1:[0-9A-Za-z_]+]] "firstGlobal{{.*}}.tt[1].s" +// CHECK: OpName %[[fgtt2_0:[0-9A-Za-z_]+]] "firstGlobal{{.*}}.tt[0].s" +// CHECK: OpName %[[fgtt2_1:[0-9A-Za-z_]+]] "firstGlobal{{.*}}.tt[1].s" +// CHECK: OpName %[[fgtt3_0:[0-9A-Za-z_]+]] "firstGlobal{{.*}}.tt[0].s" +// CHECK: OpName %[[fgtt3_1:[0-9A-Za-z_]+]] "firstGlobal{{.*}}.tt[1].s" // Check flattening of bindings // Explanation: Only the resources that are used will have a binding assignment diff --git a/tools/clang/test/CodeGenSPIRV/vk.spec-constant.reuse.hlsl b/tools/clang/test/CodeGenSPIRV/vk.spec-constant.reuse.hlsl index 95fb0502d7..3a1aeab246 100644 --- a/tools/clang/test/CodeGenSPIRV/vk.spec-constant.reuse.hlsl +++ b/tools/clang/test/CodeGenSPIRV/vk.spec-constant.reuse.hlsl @@ -5,6 +5,6 @@ [shader("pixel")] float4 main(float4 position : SV_Position) : SV_Target0 { -// CHECK: OpConstantComposite %v4bool %y %y %y %y +// CHECK: OpSpecConstantComposite %v4bool %y %y %y %y return y ? position : 1.0; } From 9ac57982725c4264348b20ad3fb271acc0993754 Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Mon, 23 Mar 2026 10:41:25 +0100 Subject: [PATCH 2/8] Update SPIR-V submodule pointers --- external/SPIRV-Headers | 2 +- external/SPIRV-Tools | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/external/SPIRV-Headers b/external/SPIRV-Headers index 465055f6c9..c141151dd5 160000 --- a/external/SPIRV-Headers +++ b/external/SPIRV-Headers @@ -1 +1 @@ -Subproject commit 465055f6c9128772e20082e893d974146acf7a02 +Subproject commit c141151dd53cbd5b1ced0665ad95ae3e91e8f916 diff --git a/external/SPIRV-Tools b/external/SPIRV-Tools index 7134be5024..2a730e127a 160000 --- a/external/SPIRV-Tools +++ b/external/SPIRV-Tools @@ -1 +1 @@ -Subproject commit 7134be5024ffe0b48de1f8206f0fe69e34497bbe +Subproject commit 2a730e127a32ac8b0713f5e1490d7b9be9d1cc9a From 5f9c364370e290885a5669c32ad4b4fae91a2fec Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Sat, 28 Mar 2026 23:06:40 +0100 Subject: [PATCH 3/8] Add O1experimental SPIR-V fast compile mode --- docs/SPIR-V.rst | 22 ++++++--- external/SPIRV-Tools | 2 +- include/dxc/Support/HLSLOptions.td | 3 ++ include/dxc/Support/SPIRVOptions.h | 1 + lib/DxcSupport/HLSLOptions.cpp | 17 ++++++- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 49 +++++++++++++++---- tools/clang/lib/SPIRV/SpirvEmitter.h | 2 + .../legal-examples/15-loop-var-unroll-ok.hlsl | 23 +++++---- 8 files changed, 91 insertions(+), 28 deletions(-) diff --git a/docs/SPIR-V.rst b/docs/SPIR-V.rst index 423262e15d..a3d7d571d3 100644 --- a/docs/SPIR-V.rst +++ b/docs/SPIR-V.rst @@ -518,12 +518,22 @@ generate valid SPIR-V for Vulkan. Optimization ~~~~~~~~~~~~ -Optimization is also delegated to SPIRV-Tools. Right now there are no difference -between optimization levels greater than zero; they will all invoke the same -optimization recipe. That is, the recipe behind ``spirv-opt -O``. If you want to -run a custom optimization recipe, you can do so using the command line option -``-Oconfig=`` and specifying a comma-separated list of your desired passes. -The passes are invoked in the specified order. +Optimization is also delegated to SPIRV-Tools. There are two built-in +optimization recipes for SPIR-V code generation: + +* ``-O1experimental``: a development-oriented recipe that favors lower + optimizer wall time. This profile is opt-in, leaves the default ``-O0``, + ``-O1``, ``-O2``, ``-O3`` and ``-Oconfig`` behavior unchanged, and may emit + ``VariablePointers`` for modules that need the fast path. On Vulkan targets, + use at least ``-fspv-target-env=vulkan1.1`` and ensure the target device + supports the required variable-pointer features. Vulkan 1.4 guarantees that + support. +* ``-O1``, ``-O2``, and ``-O3``: the performance-oriented recipe behind + ``spirv-opt -O``. + +If you want to run a custom optimization recipe, you can do so using the +command line option ``-Oconfig=`` and specifying a comma-separated list of +your desired passes. The passes are invoked in the specified order. For example, you can specify ``-Oconfig=--loop-unroll,--scalar-replacement=300,--eliminate-dead-code-aggressive`` to firstly invoke loop unrolling, then invoke scalar replacement of aggregates, diff --git a/external/SPIRV-Tools b/external/SPIRV-Tools index 2a730e127a..0ecbcc95a1 160000 --- a/external/SPIRV-Tools +++ b/external/SPIRV-Tools @@ -1 +1 @@ -Subproject commit 2a730e127a32ac8b0713f5e1490d7b9be9d1cc9a +Subproject commit 0ecbcc95a108f1a3313ea184260b10d21e158a47 diff --git a/include/dxc/Support/HLSLOptions.td b/include/dxc/Support/HLSLOptions.td index 819847ae4e..92514c5787 100644 --- a/include/dxc/Support/HLSLOptions.td +++ b/include/dxc/Support/HLSLOptions.td @@ -96,6 +96,9 @@ def O0 : Flag<["-", "/"], "O0">, Group, Flags<[CoreOption]>, HelpText<"Optimization Level 0">; def O1 : Flag<["-", "/"], "O1">, Group, Flags<[CoreOption]>, HelpText<"Optimization Level 1">; +def O1experimental : Flag<["-"], "O1experimental">, Group, + Flags<[CoreOption, DriverOption]>, + HelpText<"Enable the experimental SPIR-V fast-compile profile; may emit VariablePointers and requires target support for them when used">; def O2 : Flag<["-", "/"], "O2">, Group, Flags<[CoreOption]>, HelpText<"Optimization Level 2">; def O3 : Flag<["-", "/"], "O3">, Group, Flags<[CoreOption]>, diff --git a/include/dxc/Support/SPIRVOptions.h b/include/dxc/Support/SPIRVOptions.h index f39602e9d8..b9a8a05c0c 100644 --- a/include/dxc/Support/SPIRVOptions.h +++ b/include/dxc/Support/SPIRVOptions.h @@ -59,6 +59,7 @@ struct SpirvCodeGenOptions { bool noWarnIgnoredFeatures = false; bool preserveBindings = false; bool preserveInterface = false; + bool o1ExperimentalFastCompile = false; bool useDxLayout = false; bool useGlLayout = false; bool useLegacyBufferMatrixOrder = false; diff --git a/lib/DxcSupport/HLSLOptions.cpp b/lib/DxcSupport/HLSLOptions.cpp index a34b00b84d..0c43b9ffb1 100644 --- a/lib/DxcSupport/HLSLOptions.cpp +++ b/lib/DxcSupport/HLSLOptions.cpp @@ -792,11 +792,15 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude, } opts.DisableOptimizations = false; - if (Arg *A = Args.getLastArg(OPT_O0, OPT_O1, OPT_O2, OPT_O3, OPT_Od)) { + if (Arg *A = + Args.getLastArg(OPT_O0, OPT_O1, OPT_O1experimental, OPT_O2, OPT_O3, + OPT_Od)) { if (A->getOption().matches(OPT_O0)) opts.OptLevel = 0; if (A->getOption().matches(OPT_O1)) opts.OptLevel = 1; + if (A->getOption().matches(OPT_O1experimental)) + opts.OptLevel = 1; if (A->getOption().matches(OPT_O2)) opts.OptLevel = 2; if (A->getOption().matches(OPT_O3)) @@ -1096,6 +1100,8 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude, // SPIRV Change Starts #ifdef ENABLE_SPIRV_CODEGEN opts.GenSPIRV = Args.hasFlag(OPT_spirv, OPT_INVALID, false); + opts.SpirvOptions.o1ExperimentalFastCompile = + Args.hasFlag(OPT_O1experimental, OPT_INVALID, false); opts.SpirvOptions.invertY = Args.hasFlag(OPT_fvk_invert_y, OPT_INVALID, false); opts.SpirvOptions.invertW = @@ -1261,7 +1267,8 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude, errors << "-Oconfig should not be specified more than once"; return 1; } - if (Args.getLastArg(OPT_O0, OPT_O1, OPT_O2, OPT_O3)) { + if (Args.getLastArg(OPT_O0, OPT_O1, OPT_O1experimental, OPT_O2, + OPT_O3)) { errors << "-Oconfig should not be used together with -O"; return 1; } @@ -1273,6 +1280,11 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude, opts.SpirvOptions.entrypointName = Args.getLastArgValue(OPT_fspv_entrypoint_name_EQ); + if (opts.SpirvOptions.o1ExperimentalFastCompile && !opts.GenSPIRV) { + errors << "-O1experimental requires -spirv"; + return 1; + } + // Check for use of options not implemented in the SPIR-V backend. if (Args.hasFlag(OPT_spirv, OPT_INVALID, false) && hasUnsupportedSpirvOption(Args, errors)) @@ -1295,6 +1307,7 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude, Args.hasFlag(OPT_fspv_reflect, OPT_INVALID, false) || Args.hasFlag(OPT_fspv_fix_func_call_arguments, OPT_INVALID, false) || Args.hasFlag(OPT_fspv_print_all, OPT_INVALID, false) || + Args.hasFlag(OPT_O1experimental, OPT_INVALID, false) || Args.hasFlag(OPT_Wno_vk_ignored_features, OPT_INVALID, false) || Args.hasFlag(OPT_Wno_vk_emulated_features, OPT_INVALID, false) || Args.hasFlag(OPT_fvk_auto_shift_bindings, OPT_INVALID, false) || diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index 4a616a00e8..31f4e298e5 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -595,6 +595,7 @@ SpirvEmitter::SpirvEmitter(CompilerInstance &ci) isSpecConstantMode(false), needsLegalization(false), needsLegalizationLoopUnroll(false), needsLegalizationSsaRewrite(false), + sawExplicitUnrollHint(false), beforeHlslLegalization(false), mainSourceFile(nullptr) { // Get ShaderModel from command line hlsl profile option. @@ -920,6 +921,20 @@ void SpirvEmitter::HandleTranslationUnit(ASTContext &context) { } } + if (useSpirvFastCompileProfile() && + (needsLegalizationLoopUnroll || sawExplicitUnrollHint)) { + if (featureManager.isTargetEnvVulkan() && + !featureManager.isTargetEnvVulkan1p1OrAbove()) { + emitFatalError( + "-O1experimental requires -fspv-target-env=vulkan1.1 or above " + "when the generated module needs VariablePointers", + {}); + return; + } + + spvBuilder.requireCapability(spv::Capability::VariablePointers); + } + // Output the constructed module. std::vector m = spvBuilder.takeModule(); if (context.getDiagnostics().hasErrorOccurred()) @@ -2289,6 +2304,7 @@ spv::LoopControlMask SpirvEmitter::translateLoopAttribute(const Stmt *stmt, case attr::HLSLFastOpt: return spv::LoopControlMask::DontUnroll; case attr::HLSLUnroll: + sawExplicitUnrollHint = true; return spv::LoopControlMask::Unroll; case attr::HLSLAllowUAVCondition: if (!spirvOptions.noWarnIgnoredFeatures) { @@ -16627,8 +16643,12 @@ bool SpirvEmitter::spirvToolsOptimize(std::vector *mod, options.set_max_id_bound(spirvOptions.maxId); if (spirvOptions.optConfig.empty()) { - // Add performance passes. - optimizer.RegisterPerformancePasses(spirvOptions.preserveInterface); + if (useSpirvFastCompileProfile()) { + optimizer.RegisterPerformancePassesFastCompile( + spirvOptions.preserveInterface); + } else { + optimizer.RegisterPerformancePasses(spirvOptions.preserveInterface); + } // Add propagation of volatile semantics passes. optimizer.RegisterPass(spvtools::CreateSpreadVolatileSemanticsPass()); @@ -16648,6 +16668,11 @@ bool SpirvEmitter::spirvToolsOptimize(std::vector *mod, return optimizer.Run(mod->data(), mod->size(), mod, options); } +bool SpirvEmitter::useSpirvFastCompileProfile() const { + return spirvOptions.o1ExperimentalFastCompile && + spirvOptions.optConfig.empty(); +} + bool SpirvEmitter::spirvToolsLegalize(std::vector *mod, std::string *messages, const std::vector @@ -16673,15 +16698,19 @@ bool SpirvEmitter::spirvToolsLegalize(std::vector *mod, optimizer.RegisterPass( spvtools::CreateInterfaceVariableScalarReplacementPass()); } - auto legalizationSsaRewriteMode = spvtools::SSARewriteMode::None; - if (needsLegalizationLoopUnroll) { - legalizationSsaRewriteMode = spvtools::SSARewriteMode::All; - } else if (needsLegalizationSsaRewrite) { - legalizationSsaRewriteMode = spvtools::SSARewriteMode::OpaqueOnly; + if (useSpirvFastCompileProfile()) { + auto legalizationSsaRewriteMode = spvtools::SSARewriteMode::None; + if (needsLegalizationLoopUnroll) { + legalizationSsaRewriteMode = spvtools::SSARewriteMode::All; + } else if (needsLegalizationSsaRewrite) { + legalizationSsaRewriteMode = spvtools::SSARewriteMode::OpaqueOnly; + } + optimizer.RegisterLegalizationPasses( + spirvOptions.preserveInterface, needsLegalizationLoopUnroll, + legalizationSsaRewriteMode); + } else { + optimizer.RegisterLegalizationPasses(spirvOptions.preserveInterface); } - optimizer.RegisterLegalizationPasses( - spirvOptions.preserveInterface, needsLegalizationLoopUnroll, - legalizationSsaRewriteMode); // Add flattening of resources if needed. if (spirvOptions.flattenResourceArrays) { optimizer.RegisterPass( diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.h b/tools/clang/lib/SPIRV/SpirvEmitter.h index e6f5f5e57a..62f917972c 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.h +++ b/tools/clang/lib/SPIRV/SpirvEmitter.h @@ -1339,6 +1339,7 @@ class SpirvEmitter : public ASTConsumer { /// gets the info/warning/error messages via |messages|. /// Returns true on success and false otherwise. bool spirvToolsOptimize(std::vector *mod, std::string *messages); + bool useSpirvFastCompileProfile() const; // \brief Runs the pass represented by the given pass token on the module. // Returns true if the pass was successfully run. Any messages from the @@ -1580,6 +1581,7 @@ class SpirvEmitter : public ASTConsumer { bool needsLegalization; bool needsLegalizationLoopUnroll; bool needsLegalizationSsaRewrite; + bool sawExplicitUnrollHint; /// Whether the translated SPIR-V binary passes --before-hlsl-legalization /// option to spirv-val because of illegal function parameter scope. diff --git a/tools/clang/test/CodeGenSPIRV/legal-examples/15-loop-var-unroll-ok.hlsl b/tools/clang/test/CodeGenSPIRV/legal-examples/15-loop-var-unroll-ok.hlsl index 334eeb676a..a063ec4f0c 100644 --- a/tools/clang/test/CodeGenSPIRV/legal-examples/15-loop-var-unroll-ok.hlsl +++ b/tools/clang/test/CodeGenSPIRV/legal-examples/15-loop-var-unroll-ok.hlsl @@ -1,12 +1,17 @@ -// RUN: %dxc -T cs_6_0 -E main -O3 -Vd %s -spirv | FileCheck %s - -// CHECK: OpLoopMerge {{%[0-9]+}} {{%[0-9]+}} Unroll -// CHECK: [[which:%[0-9]+]] = OpSelect %_ptr_Uniform_type_StructuredBuffer_S {{%[0-9]+}} %gSBuffer1 %gSBuffer2 -// CHECK: [[idx:%[0-9]+]] = OpBitcast %uint {{%[0-9]+}} -// CHECK: [[src:%[0-9]+]] = OpAccessChain %_ptr_Uniform_S [[which]] %int_0 [[idx]] -// CHECK: [[val:%[0-9]+]] = OpLoad %S [[src]] -// CHECK: [[dst:%[0-9]+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer %int_0 [[idx]] -// CHECK: OpStore [[dst]] [[val]] +// RUN: %dxc -T cs_6_0 -E main -O3 -Vd -fspv-target-env=vulkan1.3 %s -spirv | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-O3 +// RUN: %dxc -T cs_6_0 -E main -O1experimental -Vd -fspv-target-env=vulkan1.3 %s -spirv | FileCheck %s --check-prefix=CHECK --check-prefix=CHECK-O1EXP + +// CHECK-O1EXP: OpCapability VariablePointers + +// CHECK: [[ptr:%[0-9]+]] = OpAccessChain %_ptr_Uniform_S %gSBuffer2 +// CHECK-NEXT: [[val:%[0-9]+]] = OpLoad %S [[ptr]] +// CHECK-NEXT: [[ptr:%[0-9]+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer +// CHECK-NEXT: OpStore [[ptr]] [[val]] + +// CHECK: [[ptr:%[0-9]+]] = OpAccessChain %_ptr_Uniform_S %gSBuffer2 +// CHECK-NEXT: [[val:%[0-9]+]] = OpLoad %S [[ptr]] +// CHECK-NEXT: [[ptr:%[0-9]+]] = OpAccessChain %_ptr_Uniform_S %gRWSBuffer +// CHECK-NEXT: OpStore [[ptr]] [[val]] struct S { float4 f; From bd2ec9c23dcc221ddc73788d6dfb6175203d79fd Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Sun, 29 Mar 2026 00:41:38 +0100 Subject: [PATCH 4/8] Update SPIRV-Tools --- external/SPIRV-Tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/SPIRV-Tools b/external/SPIRV-Tools index 0ecbcc95a1..4fce38cf11 160000 --- a/external/SPIRV-Tools +++ b/external/SPIRV-Tools @@ -1 +1 @@ -Subproject commit 0ecbcc95a108f1a3313ea184260b10d21e158a47 +Subproject commit 4fce38cf1105346a47f5226bcb77defcdd84df5e From 4dd86f92d4e4b915c3d6f158ac8a6aac1d4beeac Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Sun, 29 Mar 2026 08:09:03 +0200 Subject: [PATCH 5/8] Route O1experimental through fast compile APIs --- external/SPIRV-Tools | 2 +- tools/clang/lib/SPIRV/SpirvEmitter.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/external/SPIRV-Tools b/external/SPIRV-Tools index 4fce38cf11..5bc9ddf5be 160000 --- a/external/SPIRV-Tools +++ b/external/SPIRV-Tools @@ -1 +1 @@ -Subproject commit 4fce38cf1105346a47f5226bcb77defcdd84df5e +Subproject commit 5bc9ddf5be12ff872c260240bfed7923fde43791 diff --git a/tools/clang/lib/SPIRV/SpirvEmitter.cpp b/tools/clang/lib/SPIRV/SpirvEmitter.cpp index 31f4e298e5..82b53abb79 100644 --- a/tools/clang/lib/SPIRV/SpirvEmitter.cpp +++ b/tools/clang/lib/SPIRV/SpirvEmitter.cpp @@ -16705,7 +16705,7 @@ bool SpirvEmitter::spirvToolsLegalize(std::vector *mod, } else if (needsLegalizationSsaRewrite) { legalizationSsaRewriteMode = spvtools::SSARewriteMode::OpaqueOnly; } - optimizer.RegisterLegalizationPasses( + optimizer.RegisterLegalizationPassesFastCompile( spirvOptions.preserveInterface, needsLegalizationLoopUnroll, legalizationSsaRewriteMode); } else { From 899afef2a8665fa9c861b6b7be8562bf7efb850e Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Sun, 29 Mar 2026 18:43:20 +0200 Subject: [PATCH 6/8] Update SPIRV-Tools for O1experimental cleanup --- external/SPIRV-Tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/SPIRV-Tools b/external/SPIRV-Tools index 5bc9ddf5be..9d209eaaee 160000 --- a/external/SPIRV-Tools +++ b/external/SPIRV-Tools @@ -1 +1 @@ -Subproject commit 5bc9ddf5be12ff872c260240bfed7923fde43791 +Subproject commit 9d209eaaeef8c468686aef3ff6fde23f34453b2c From ea6bd944852d2ebac21f9ffc021be2c71e7a56fe Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Tue, 31 Mar 2026 11:57:03 +0200 Subject: [PATCH 7/8] Update SPIRV-Tools for O1experimental VariablePointers cleanup Pull in the companion SPIRV-Tools change that adds a dedicated TrimVariablePointersCapabilitiesPass at the end of the O1experimental fast performance recipe. The follow-up fixes a real sampler regression where the final optimized module still carried stale VariablePointers / VariablePointersStorageBuffer capability declarations even though the final IR no longer contained the pointer forms that required them. The failing EX37 shader remained validator-legal and had only scalar OpSelect %float, but removing only those stale capability lines fixed the downstream runtime corruption. Keep the default trim path untouched by keeping the cleanup isolated to the experimental fast path in SPIRV-Tools. --- external/SPIRV-Tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/SPIRV-Tools b/external/SPIRV-Tools index 9d209eaaee..f5339a9d2d 160000 --- a/external/SPIRV-Tools +++ b/external/SPIRV-Tools @@ -1 +1 @@ -Subproject commit 9d209eaaeef8c468686aef3ff6fde23f34453b2c +Subproject commit f5339a9d2dc0fee9980ccf148b7f69ca3d19b73d From 36527954ae5ee4df11e02deeb4e914267f402c86 Mon Sep 17 00:00:00 2001 From: Arkadiusz Lachowicz Date: Wed, 1 Apr 2026 09:54:40 +0200 Subject: [PATCH 8/8] Update SPIRV-Tools for ordinary-store VariablePointers trim VariablePointers tracks pointer values that remain first-class in the final module. A plain OpStore stores through a pointer. It does not by itself prove that the module still needs VariablePointers. The SPIR-V OpStore definition distinguishes Pointer as the location being stored through and Object as the value being stored. The VariablePointers rules separately constrain the case where a pointer is the Object operand of OpStore or the result of OpLoad. Ordinary stores of non-pointer objects through StorageBuffer pointers do not require keeping VariablePointers alive. This updates the DXC-side SPIRV-Tools pointer to the SPIRV-Tools change that stops treating every OpStore as a VariablePointers requirement. Spec references: https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpStore https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#VariablePointers --- external/SPIRV-Tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/SPIRV-Tools b/external/SPIRV-Tools index f5339a9d2d..cbffcb76a8 160000 --- a/external/SPIRV-Tools +++ b/external/SPIRV-Tools @@ -1 +1 @@ -Subproject commit f5339a9d2dc0fee9980ccf148b7f69ca3d19b73d +Subproject commit cbffcb76a8170ff2ec9358e6b15edb6c4878b7a8