From b1eeb9eb7ff4506975e51ef6bde04b1f8ac95da4 Mon Sep 17 00:00:00 2001 From: Chris Bieneman Date: Tue, 18 Aug 2026 14:34:09 -0500 Subject: [PATCH 1/4] [dxv] Disallow work graps in DXIL 1.10 In DXIL 1.10, we're disallowing all the dxil operations associated with work graphs. This is captured in the Dxil 1.10 specification in [PR #918] (microsoft/hlsl-specs#918). Assisted-by: Copilot --- docs/ReleaseNotes.md | 3 +- .../work-graph-opcodes-sm-6-10.ll | 90 +++++++++++++++++++ utils/hct/hctdb.py | 5 ++ utils/hct/hctdb_instrhelp.py | 40 +++++++-- 4 files changed, 129 insertions(+), 9 deletions(-) create mode 100644 tools/clang/test/LitDXILValidation/work-graph-opcodes-sm-6-10.ll diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 8ff6ad323c..dba894cbf7 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -68,7 +68,8 @@ first shipped in the 1.10.2605 preview. - Restricted the component types allowed in LinAlg matrices [#8608](https://github.com/microsoft/DirectXShaderCompiler/pull/8608). - Added `BFloat16` to the ComponentType enum in DxilConstants and the linalg - header [#8722](https://github.com/microsoft/DirectXShaderCompiler/issues/8722) + header [#8722](https://github.com/microsoft/DirectXShaderCompiler/issues/8722). +- Removed work graph support from Shader Model 6.10, and DXIL 1.10 [#8798](https://github.com/microsoft/DirectXShaderCompiler/pull/8798) ### Version 1.9.2607 diff --git a/tools/clang/test/LitDXILValidation/work-graph-opcodes-sm-6-10.ll b/tools/clang/test/LitDXILValidation/work-graph-opcodes-sm-6-10.ll new file mode 100644 index 0000000000..5cf931e7dd --- /dev/null +++ b/tools/clang/test/LitDXILValidation/work-graph-opcodes-sm-6-10.ll @@ -0,0 +1,90 @@ +; REQUIRES: dxil-1-10 +; RUN: not %dxv %s 2>&1 | FileCheck %s + +; Work graph opcodes are not valid in shader model 6.10. + +; CHECK-DAG: error: Opcode AllocateNodeOutputRecords not valid in shader model lib_6_10(node). +; CHECK-DAG: error: Opcode GetNodeRecordPtr not valid in shader model lib_6_10(node). +; CHECK-DAG: error: Opcode IncrementOutputCount not valid in shader model lib_6_10(node). +; CHECK-DAG: error: Opcode OutputComplete not valid in shader model lib_6_10(node). +; CHECK-DAG: error: Opcode GetInputRecordCount not valid in shader model lib_6_10(node). +; CHECK-DAG: error: Opcode FinishedCrossGroupSharing not valid in shader model lib_6_10(node). +; CHECK-DAG: error: Opcode BarrierByNodeRecordHandle not valid in shader model lib_6_10(node). +; CHECK-DAG: error: Opcode CreateNodeOutputHandle not valid in shader model lib_6_10(node). +; CHECK-DAG: error: Opcode IndexNodeHandle not valid in shader model lib_6_10(node). +; CHECK-DAG: error: Opcode AnnotateNodeHandle not valid in shader model lib_6_10(node). +; CHECK-DAG: error: Opcode CreateNodeInputRecordHandle not valid in shader model lib_6_10(node). +; CHECK-DAG: error: Opcode AnnotateNodeRecordHandle not valid in shader model lib_6_10(node). +; CHECK-DAG: error: Opcode NodeOutputIsValid not valid in shader model lib_6_10(node). +; CHECK-DAG: error: Opcode GetRemainingRecursionLevels not valid in shader model lib_6_10(node). +; CHECK: Validation failed. + +target datalayout = "e-m:e-p:32:32-i1:32-i8:32-i16:32-i32:32-i64:64-f16:32-f32:32-f64:64-n8:16:32:64" +target triple = "dxil-ms-dx" + +%dx.types.NodeHandle = type { i8* } +%dx.types.NodeInfo = type { i32, i32 } +%dx.types.NodeRecordHandle = type { i8* } +%dx.types.NodeRecordInfo = type { i32, i32 } +%struct.Record = type { i32 } + +define void @Node() { + %output = call %dx.types.NodeHandle @dx.op.createNodeOutputHandle(i32 247, i32 0) + %indexed = call %dx.types.NodeHandle @dx.op.indexNodeHandle(i32 248, %dx.types.NodeHandle %output, i32 0) + %annotatedOutput = call %dx.types.NodeHandle @dx.op.annotateNodeHandle(i32 249, %dx.types.NodeHandle %indexed, %dx.types.NodeInfo { i32 6, i32 4 }) + %outputRecord = call %dx.types.NodeRecordHandle @dx.op.allocateNodeOutputRecords(i32 238, %dx.types.NodeHandle %annotatedOutput, i32 1, i1 true) + %annotatedOutputRecord = call %dx.types.NodeRecordHandle @dx.op.annotateNodeRecordHandle(i32 251, %dx.types.NodeRecordHandle %outputRecord, %dx.types.NodeRecordInfo { i32 38, i32 4 }) + %recordPtr = call %struct.Record addrspace(6)* @dx.op.getNodeRecordPtr.struct.Record(i32 239, %dx.types.NodeRecordHandle %annotatedOutputRecord, i32 0) + call void @dx.op.incrementOutputCount(i32 240, %dx.types.NodeHandle %annotatedOutput, i32 1, i1 true) + call void @dx.op.outputComplete(i32 241, %dx.types.NodeRecordHandle %annotatedOutputRecord) + %inputRecord = call %dx.types.NodeRecordHandle @dx.op.createNodeInputRecordHandle(i32 250, i32 0) + %inputCount = call i32 @dx.op.getInputRecordCount(i32 242, %dx.types.NodeRecordHandle %inputRecord) + %finished = call i1 @dx.op.finishedCrossGroupSharing(i32 243, %dx.types.NodeRecordHandle %inputRecord) + call void @dx.op.barrierByNodeRecordHandle(i32 246, %dx.types.NodeRecordHandle %inputRecord, i32 0) + %valid = call i1 @dx.op.nodeOutputIsValid(i32 252, %dx.types.NodeHandle %annotatedOutput) + %levels = call i32 @dx.op.getRemainingRecursionLevels(i32 253) + ret void +} + +declare %dx.types.NodeRecordHandle @dx.op.allocateNodeOutputRecords(i32, %dx.types.NodeHandle, i32, i1) +declare %struct.Record addrspace(6)* @dx.op.getNodeRecordPtr.struct.Record(i32, %dx.types.NodeRecordHandle, i32) +declare void @dx.op.incrementOutputCount(i32, %dx.types.NodeHandle, i32, i1) +declare void @dx.op.outputComplete(i32, %dx.types.NodeRecordHandle) +declare i32 @dx.op.getInputRecordCount(i32, %dx.types.NodeRecordHandle) +declare i1 @dx.op.finishedCrossGroupSharing(i32, %dx.types.NodeRecordHandle) +declare void @dx.op.barrierByNodeRecordHandle(i32, %dx.types.NodeRecordHandle, i32) +declare %dx.types.NodeHandle @dx.op.createNodeOutputHandle(i32, i32) +declare %dx.types.NodeHandle @dx.op.indexNodeHandle(i32, %dx.types.NodeHandle, i32) +declare %dx.types.NodeHandle @dx.op.annotateNodeHandle(i32, %dx.types.NodeHandle, %dx.types.NodeInfo) +declare %dx.types.NodeRecordHandle @dx.op.createNodeInputRecordHandle(i32, i32) +declare %dx.types.NodeRecordHandle @dx.op.annotateNodeRecordHandle(i32, %dx.types.NodeRecordHandle, %dx.types.NodeRecordInfo) +declare i1 @dx.op.nodeOutputIsValid(i32, %dx.types.NodeHandle) +declare i32 @dx.op.getRemainingRecursionLevels(i32) + +!llvm.ident = !{!0} +!dx.version = !{!1} +!dx.valver = !{!1} +!dx.shaderModel = !{!2} +!dx.typeAnnotations = !{!3} +!dx.entryPoints = !{!7, !9} + +!0 = !{!"custom IR"} +!1 = !{i32 1, i32 10} +!2 = !{!"lib", i32 6, i32 10} +!3 = !{i32 1, void ()* @Node, !4} +!4 = !{!5} +!5 = !{i32 0, !6, !6} +!6 = !{} +!7 = !{null, !"", null, null, !8} +!8 = !{i32 0, i64 524288} +!9 = !{void ()* @Node, !"Node", null, null, !10} +!10 = !{i32 8, i32 15, i32 13, i32 3, i32 15, !11, i32 16, i32 -1, i32 20, !12, i32 21, !15, i32 4, !18, i32 5, !19} +!11 = !{!"Node", i32 0} +!12 = !{!13} +!13 = !{i32 1, i32 37, i32 2, !14} +!14 = !{i32 0, i32 4, i32 2, i32 4} +!15 = !{!16} +!16 = !{i32 1, i32 6, i32 2, !14, i32 3, i32 1, i32 0, !17} +!17 = !{!"output", i32 0} +!18 = !{i32 1, i32 1, i32 1} +!19 = !{i32 0} diff --git a/utils/hct/hctdb.py b/utils/hct/hctdb.py index d8210907f6..5eebd7c428 100644 --- a/utils/hct/hctdb.py +++ b/utils/hct/hctdb.py @@ -123,6 +123,7 @@ def __init__(self, name, **kwargs): self.is_barrier = False # whether this is a barrier operation self.shader_stages = () # shader stages to which this applies, empty for all. self.shader_model = 6, 0 # minimum shader model required + self.shader_model_max = () # maximum shader model allowed, empty for no maximum self.inst_helper_prefix = None self.fully_qualified_name_prefix = "hlsl::OP::OpCode" self.shader_model_translated = () # minimum shader model required with translation by linker @@ -1037,6 +1038,10 @@ def populate_categories_and_models(self): self.name_idx[i].category = "Work Graph intrinsics" self.name_idx[i].shader_model = 6, 8 self.name_idx[i].shader_stages = ("node",) + for i in ( + "AllocateNodeOutputRecords,GetNodeRecordPtr,IncrementOutputCount,OutputComplete,GetInputRecordCount,FinishedCrossGroupSharing,BarrierByNodeRecordHandle,CreateNodeOutputHandle,IndexNodeHandle,AnnotateNodeHandle,CreateNodeInputRecordHandle,AnnotateNodeRecordHandle,NodeOutputIsValid,GetRemainingRecursionLevels" + ).split(","): + self.name_idx[i].shader_model_max = 6, 9 # All barrier ops: for i in "Barrier".split(","): self.name_idx[i].category = "Synchronization" diff --git a/utils/hct/hctdb_instrhelp.py b/utils/hct/hctdb_instrhelp.py index 3980ff9b66..a087a2992e 100644 --- a/utils/hct/hctdb_instrhelp.py +++ b/utils/hct/hctdb_instrhelp.py @@ -1609,14 +1609,21 @@ def flush_instrs(grouped_instrs, last_model, last_model_translated, last_stage): def get_valopcode_sm_text(): db = get_db_dxil() instrs = sorted( - db.get_dxil_ops(), key=lambda v: (v.shader_model, v.shader_stages, v.dxil_opid) + db.get_dxil_ops(), + key=lambda v: ( + v.shader_model, + v.shader_model_max, + v.shader_stages, + v.dxil_opid, + ), ) last_model = None + last_model_max = None last_stage = None grouped_instrs = [] code = "" - def flush_instrs(grouped_instrs, last_model, last_stage): + def flush_instrs(grouped_instrs, last_model, last_model_max, last_stage): if len(grouped_instrs) == 0: return "" result = format_comment( @@ -1631,20 +1638,30 @@ def flush_instrs(grouped_instrs, last_model, last_stage): ) result += " return " - model_cond = stage_cond = None + model_cond = model_max_cond = stage_cond = None if last_model != (6, 0): model_cond = "major > %d || (major == %d && minor >= %d)" % ( last_model[0], last_model[0], last_model[1], ) + if last_model_max: + model_max_cond = "major < %d || (major == %d && minor <= %d)" % ( + last_model_max[0], + last_model_max[0], + last_model_max[1], + ) if last_stage: stage_cond = " || ".join( [check_pSM_for_shader_stage[c] for c in last_stage] ) - if model_cond or stage_cond: + if model_cond or model_max_cond or stage_cond: result += "\n && ".join( - ["(%s)" % expr for expr in (model_cond, stage_cond) if expr] + [ + "(%s)" % expr + for expr in (model_cond, model_max_cond, stage_cond) + if expr + ] ) return result + ";\n" else: @@ -1652,13 +1669,20 @@ def flush_instrs(grouped_instrs, last_model, last_stage): return "" for i in instrs: - if (i.shader_model, i.shader_stages) != (last_model, last_stage): - code += flush_instrs(grouped_instrs, last_model, last_stage) + if (i.shader_model, i.shader_model_max, i.shader_stages) != ( + last_model, + last_model_max, + last_stage, + ): + code += flush_instrs( + grouped_instrs, last_model, last_model_max, last_stage + ) grouped_instrs = [] last_model = i.shader_model + last_model_max = i.shader_model_max last_stage = i.shader_stages grouped_instrs.append(i) - code += flush_instrs(grouped_instrs, last_model, last_stage) + code += flush_instrs(grouped_instrs, last_model, last_model_max, last_stage) code += "return true;\n" return code From 07f15badf0c3254e09d17d806e62a19ead50573c Mon Sep 17 00:00:00 2001 From: Chris B Date: Mon, 31 Aug 2026 14:46:08 -0500 Subject: [PATCH 2/4] Apply suggestion from @llvm-beanz --- docs/ReleaseNotes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index dba894cbf7..210ca789a4 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -69,7 +69,7 @@ first shipped in the 1.10.2605 preview. [#8608](https://github.com/microsoft/DirectXShaderCompiler/pull/8608). - Added `BFloat16` to the ComponentType enum in DxilConstants and the linalg header [#8722](https://github.com/microsoft/DirectXShaderCompiler/issues/8722). -- Removed work graph support from Shader Model 6.10, and DXIL 1.10 [#8798](https://github.com/microsoft/DirectXShaderCompiler/pull/8798) +- Removed work graph support from Shader Model 6.10, and DXIL 1.10 [microsoft/hlsl-specs#915](https://github.com/microsoft/hlsl-specs/issues/915) ### Version 1.9.2607 From 1a9a7af5937c31b7679394b4a33049f531587cfa Mon Sep 17 00:00:00 2001 From: Chris B Date: Mon, 31 Aug 2026 14:46:57 -0500 Subject: [PATCH 3/4] Apply suggestion from @llvm-beanz --- docs/ReleaseNotes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 210ca789a4..41ef27edaa 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -69,7 +69,7 @@ first shipped in the 1.10.2605 preview. [#8608](https://github.com/microsoft/DirectXShaderCompiler/pull/8608). - Added `BFloat16` to the ComponentType enum in DxilConstants and the linalg header [#8722](https://github.com/microsoft/DirectXShaderCompiler/issues/8722). -- Removed work graph support from Shader Model 6.10, and DXIL 1.10 [microsoft/hlsl-specs#915](https://github.com/microsoft/hlsl-specs/issues/915) +- Removed work graph support from Shader Model 6.10, and DXIL 1.10 [microsoft/hlsl-specs#915](https://github.com/microsoft/hlsl-specs/issues/915). ### Version 1.9.2607 From 7726f7e8739056717bb669623f504d2dd27dddb7 Mon Sep 17 00:00:00 2001 From: Chris Bieneman Date: Tue, 1 Sep 2026 09:52:42 -0500 Subject: [PATCH 4/4] Make node entry annotation invalid in SM 6.10 --- docs/DXIL.rst | 1 + lib/DXIL/DxilOperations.cpp | 2 +- lib/DxilValidation/DxilValidation.cpp | 6 +++ .../LitDXILValidation/node-shader-sm-6-10.ll | 37 +++++++++++++++++++ utils/hct/hctdb.py | 7 +++- 5 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 tools/clang/test/LitDXILValidation/node-shader-sm-6-10.ll diff --git a/docs/DXIL.rst b/docs/DXIL.rst index 74acdd51fa..35f00c5b72 100644 --- a/docs/DXIL.rst +++ b/docs/DXIL.rst @@ -3418,6 +3418,7 @@ SM.RESOURCERANGEOVERLAP Resource ranges must not o SM.ROVONLYINPS RasterizerOrdered objects are only allowed in 5.0+ pixel shaders. SM.SAMPLECOUNTONLYON2DMS Only Texture2DMS/2DMSArray could has sample count. SM.SEMANTIC Semantic must be defined in target shader model +SM.SHADERSTAGE Shader stage must be supported by the target shader model SM.STREAMINDEXRANGE Stream index (%0) must between 0 and %1. SM.TESSFACTORFORDOMAIN Required TessFactor for domain not found declared anywhere in Patch Constant data. SM.TESSFACTORSIZEMATCHDOMAIN TessFactor rows, columns (%0, %1) invalid for domain %2. Expected %3 rows and 1 column. diff --git a/lib/DXIL/DxilOperations.cpp b/lib/DXIL/DxilOperations.cpp index 8dd92ce0fd..4af688f5db 100644 --- a/lib/DXIL/DxilOperations.cpp +++ b/lib/DXIL/DxilOperations.cpp @@ -4001,7 +4001,7 @@ void OP::GetMinShaderModelAndMask(OpCode C, bool bWithTranslation, if ((2147483649 <= op && op <= 2147483650)) { major = 6; minor = 10; - mask = SFLAG(Compute) | SFLAG(Mesh) | SFLAG(Amplification) | SFLAG(Node); + mask = SFLAG(Compute) | SFLAG(Mesh) | SFLAG(Amplification); return; } // Instructions: ClusterID=2147483651, TriangleObjectPosition=2147483655 diff --git a/lib/DxilValidation/DxilValidation.cpp b/lib/DxilValidation/DxilValidation.cpp index ca6cefb673..d8eabce9ca 100644 --- a/lib/DxilValidation/DxilValidation.cpp +++ b/lib/DxilValidation/DxilValidation.cpp @@ -6663,6 +6663,12 @@ static void ValidateEntryProps(ValidationContext &ValCtx, ValidateWaveSize(ValCtx, EntryProps, F); + const ShaderModel *SM = ValCtx.DxilMod.GetShaderModel(); + if (Props.IsNode() && SM->IsSM610Plus()) + ValCtx.EmitFnFormatError( + F, ValidationRule::SmShaderStage, + {ShaderModel::GetKindName(ShaderType), SM->GetName()}); + if (ShaderType == DXIL::ShaderKind::Compute || Props.IsNode()) { unsigned X = Props.numThreads[0]; unsigned Y = Props.numThreads[1]; diff --git a/tools/clang/test/LitDXILValidation/node-shader-sm-6-10.ll b/tools/clang/test/LitDXILValidation/node-shader-sm-6-10.ll new file mode 100644 index 0000000000..9486ecb327 --- /dev/null +++ b/tools/clang/test/LitDXILValidation/node-shader-sm-6-10.ll @@ -0,0 +1,37 @@ +; REQUIRES: dxil-1-10 +; RUN: not %dxv %s 2>&1 | FileCheck %s + +; Node shaders are not valid in shader model 6.10, even when the entry point +; contains no DXIL calls. + +; CHECK: Function: Node: error: Shader stage 'node' not valid in shader model lib_6_10. +; CHECK: Validation failed. + +target datalayout = "e-m:e-p:32:32-i1:32-i8:32-i16:32-i32:32-i64:64-f16:32-f32:32-f64:64-n8:16:32:64" +target triple = "dxil-ms-dx" + +define void @Node() { + ret void +} + +!llvm.ident = !{!0} +!dx.version = !{!1} +!dx.valver = !{!1} +!dx.shaderModel = !{!2} +!dx.typeAnnotations = !{!3} +!dx.entryPoints = !{!7, !9} + +!0 = !{!"custom IR"} +!1 = !{i32 1, i32 10} +!2 = !{!"lib", i32 6, i32 10} +!3 = !{i32 1, void ()* @Node, !4} +!4 = !{!5} +!5 = !{i32 0, !6, !6} +!6 = !{} +!7 = !{null, !"", null, null, !8} +!8 = !{i32 0, i64 524288} +!9 = !{void ()* @Node, !"Node", null, null, !10} +!10 = !{i32 8, i32 15, i32 13, i32 1, i32 15, !11, i32 16, i32 -1, i32 4, !12, i32 5, !13} +!11 = !{!"Node", i32 0} +!12 = !{i32 1, i32 1, i32 1} +!13 = !{i32 0} diff --git a/utils/hct/hctdb.py b/utils/hct/hctdb.py index 5eebd7c428..c507230907 100644 --- a/utils/hct/hctdb.py +++ b/utils/hct/hctdb.py @@ -1115,7 +1115,7 @@ def populate_categories_and_models_ExperimentalOps(self): for i in insts("GetGroupWaveIndex,GetGroupWaveCount"): i.category = "Group Wave Ops" i.shader_model = experimental_sm - i.shader_stages = ("compute", "mesh", "amplification", "node") + i.shader_stages = ("compute", "mesh", "amplification") i.is_wave = True # Clustered Geometry @@ -8795,6 +8795,11 @@ def build_valrules(self): "Opcode must be defined in target shader model", "Opcode %0 not valid in shader model %1.", ) + self.add_valrule_msg( + "Sm.ShaderStage", + "Shader stage must be supported by the target shader model", + "Shader stage '%0' not valid in shader model %1.", + ) self.add_valrule( "Sm.Operand", "Operand must be defined in target shader model." )