From 07f21ee31b5f427edb72d5578f713b3da3f3b96f Mon Sep 17 00:00:00 2001 From: ArielG-NV <159081215+ArielG-NV@users.noreply.github.com> Date: Fri, 8 Aug 2025 13:19:25 -0700 Subject: Error if super-type capabilities are a super-set of sub-type (#7452) Fixes: #7410 Changes: 1. super-type capabilities must be a super-set of sub-type capabilities (and support the same shader stages/targets) * InheritanceDecl visits super-type to inherit it's capabilities; validate InheritanceDecl capabilities against sub-type * visit all container decl's with a default case * clean up functionDeclBase visitor * Simplify `diagnoseUndeclaredCapability` by moving logic into capability checking (more correct*) 3. added changed behavior to documentation 4. fixed some incorrect capabilities 5. **we do not** diagnose capability errors on interface requirement-to-implementation if both lack explicit capability requirements. This change is to work around a slangpy regression (test case for the failing situation is in `tests\language-feature\capability\capability-interface-extension-1.slang`), Note: maybe for slang-2026 we don't do this? 6. requirement & implementation must support the same shader stage/target. This was changed because otherwise we can have cases where `X` inherits from `Y`, but `Y` is only expected to be used in `glsl` whilst `X` is expected to be used in `hlsl | glsl` 7. removed `tests/language-feature/capability/capabilitySimplification3.slang` because it tests nothing special (redundant) Note: not using rebase due to separate branches depending on this PR --------- Co-authored-by: slangbot <186143334+slangbot@users.noreply.github.com> --- docs/command-line-slangc-reference.md | 1 + docs/user-guide/05-capabilities.md | 193 +++++++++++- .../user-guide/a3-02-reference-capability-atoms.md | 3 + docs/user-guide/toc.html | 7 +- source/slang/glsl.meta.slang | 24 +- source/slang/hlsl.meta.slang | 85 +++--- source/slang/slang-capabilities.capdef | 12 +- source/slang/slang-capability.cpp | 131 ++++++-- source/slang/slang-capability.h | 32 +- source/slang/slang-check-decl.cpp | 335 ++++++++++++++------- source/slang/slang-check-stmt.cpp | 4 +- source/slang/slang-diagnostic-defs.h | 27 +- source/slang/slang-lower-to-ir.cpp | 2 +- source/slang/slang-parser.cpp | 4 +- source/slang/slang.natvis | 3 +- tests/diagnostics/discard-in-rt.slang | 4 +- .../wave-rotate/wave-rotate-clustered.slang | 4 +- tests/hlsl-intrinsic/wave-rotate/wave-rotate.slang | 8 +- .../capability/capability-inheritance-1.slang | 77 +++++ .../capability-interface-requirement-1.slang | 56 ++++ .../language-feature/capability/capability2.slang | 27 +- .../capability/capabilitySimplification1.slang | 30 +- .../capability/capabilitySimplification3.slang | 17 -- .../capability/explicit-shader-stage-2.slang | 2 +- 24 files changed, 815 insertions(+), 273 deletions(-) create mode 100644 tests/language-feature/capability/capability-inheritance-1.slang create mode 100644 tests/language-feature/capability/capability-interface-requirement-1.slang delete mode 100644 tests/language-feature/capability/capabilitySimplification3.slang diff --git a/docs/command-line-slangc-reference.md b/docs/command-line-slangc-reference.md index 95a13a263..cf858a54c 100644 --- a/docs/command-line-slangc-reference.md +++ b/docs/command-line-slangc-reference.md @@ -1360,6 +1360,7 @@ A capability describes an optional feature that a target may or may not support. * `GL_NV_shader_subgroup_partitioned` : enables the GL_NV_shader_subgroup_partitioned extension * `GL_NV_shader_texture_footprint` : enables the GL_NV_shader_texture_footprint extension * `GL_NV_cluster_acceleration_structure` : enables the GL_NV_cluster_acceleration_structure extension +* `GL_NV_cooperative_vector` : enables the GL_NV_cooperative_vector extension * `nvapi` * `raytracing` * `ser` diff --git a/docs/user-guide/05-capabilities.md b/docs/user-guide/05-capabilities.md index b398ba3a5..1c8503899 100644 --- a/docs/user-guide/05-capabilities.md +++ b/docs/user-guide/05-capabilities.md @@ -61,10 +61,9 @@ their corresponding group. If two capability requirements contain different atoms that are conflicting with each other, these two requirements are considered __incompatible__. For example, requirement `spvShaderClockKHR + fragment` and requirement `spvShaderClockKHR + vertex` are incompatible, because `fragment` conflicts with `vertex`. -## Requirements in Parent Scope +## Capabilities Between Parent and Members -The capability requirement of a decl is always merged with the requirements declared in its parents. If the decl declares requirements for additional compilation targets, they are added -to the requirement set as a separate disjunction. +The capability requirement of a member is always merged with the requirements declared in its parent(s). If the member declares requirements for additional compilation targets, they are added to the requirement set as a separate disjunction. For example, given: ```csharp [require(glsl)] @@ -79,7 +78,7 @@ struct MyType `MyType.method` will have requirement `glsl | hlsl + hlsl_nvapi | spirv`. The `[require]` attribute can also be used on module declarations, so that the requirement will -apply to all decls within the module. For example: +apply to all members within the module. For example: ```csharp [require(glsl)] [require(hlsl)] @@ -92,7 +91,124 @@ public void myFunc() } ``` -## Inference of Capability Requirements +## Capabilities Between Subtype and Supertype + +For inheritance/implementing-interfaces the story is a bit different. +We require that the subtype (`Foo1`) have a subset of capabilities to the supertype (`IFoo1`). + +For example: +```csharp +[require(sm_4_0)] +interface IFoo1 +{ +} +[require(sm_6_0)] +struct Foo1 : IFoo1 +{ +} +``` +We error here since `Foo1` is not a subset to `IFoo1`. `Foo1` has `sm_6_0`, which includes capabilities `sm_4_0` does not have. + +```csharp +[require(sm_6_0)] +interface IFoo2 +{ +} +[require(sm_4_0)] +interface IFoo1 +{ +} +[require(sm_4_0)] +struct Foo1 : IFoo1, IFoo2 +{ +} +``` +We do not error here since `IFoo2` and `IFoo1` are supersets to `Foo1`. + +Additionally, any supertype to subtype relationship must share the same shader stage and shader target support. + +```csharp +// Error, Foo1 is missing `spirv` +[require(hlsl)] +[require(spirv)] +interface IFoo1 +{ +} +[require(hlsl)] +struct Foo1 : IFoo1 +{ +} + +// Error, IFoo1 is missing `hlsl` +[require(hlsl)] +interface IFoo1 +{ +} +[require(hlsl)] +[require(spirv)] +struct Foo1 : IFoo1 +{ +} +``` + +## Capabilities Between Requirement and Implementation + +We require that all requirement capabilities are supersets of their implementation (only required if capabilities are explicitly annotated). + +```csharp +public interface IAtomicAddable_Pass +{ + public static void atomicAdd(RWByteAddressBuffer buf, uint addr, This value); +} +public extension int64_t : IAtomicAddable_Pass +{ + public static void atomicAdd(RWByteAddressBuffer buf, uint addr, int64_t value) { buf.InterlockedAddI64(addr, value); } +} + +public interface IAtomicAddable_Error +{ + [require(glsl, sm_4_0)] + public static void atomicAdd(RWByteAddressBuffer buf, uint addr, This value); +} +public extension uint : IAtomicAddable_Error +{ + // Error: implementation has superset of capabilites, sm_6_0 vs. sm_4_0 + // Note: sm_6_0 is inferred from `InterlockedAddI64` + public static void atomicAdd(RWByteAddressBuffer buf, uint addr, int64_t value) { buf.InterlockedAddI64(addr, value); } +} +``` + +Requirment and implementation must also share the same shader stage and shader target support. + +```csharp +public interface IAtomicAddable_Error +{ + [require(glsl)] + [require(hlsl)] + public static void atomicAdd(RWByteAddressBuffer buf, uint addr, This value); +} +public extension uint : IAtomicAddable_Error +{ + [require(glsl)] // Error, missing `hlsl` + public static void atomicAdd(RWByteAddressBuffer buf, uint addr, int64_t value) { buf.InterlockedAddI64(addr, value); } +} + +public interface IAtomicAddable_Error +{ + [require(glsl)] + public static void atomicAdd(RWByteAddressBuffer buf, uint addr, This value); +} +public extension uint : IAtomicAddable_Error +{ + [require(glsl)] + [require(hlsl)] // Error, has additional capability `hlsl` + public static void atomicAdd(RWByteAddressBuffer buf, uint addr, int64_t value) { buf.InterlockedAddI64(addr, value); } +} +``` + +## Capabilities of Functions + +### Inference of Capability Requirements By default, Slang will infer the capability requirements of a function given its definition, as long as the function has `internal` or `private` visibility. For example, given: ```csharp @@ -110,7 +226,7 @@ Slang will automatically deduce that `myFunc` has capability ``` Since `discard` statement requires capability `fragment`. -## Inference on target_switch +### Inference on target_switch A `__target_switch` statement will introduce disjunctions in its inferred capability requirement. For example: ```csharp @@ -126,10 +242,71 @@ void myFunc() The capability requirement of `myFunc` is `(spirv | hlsl)`, meaning that the function can be called from a context where either `spirv` or `hlsl` capability is available. -## Capability Aliases +### Capability Incompatabilities + +The function declaration must be a superset of the capabilities the function body uses **for any shader stage/target the function declaration implicitly/explicitly requires**. + +```csharp +[require(sm_5_0)] +public void requires_sm_5_0() +{ + +} +[require(sm_4_0)] +public void logic_sm_5_0_error() // Error, missing `sm_5_0` support +{ + requires_sm_5_0(); +} + +public void logic_sm_5_0__pass() // Pass, no requirements +{ + requires_sm_5_0(); +} + +[require(hlsl, vertex)] +public void logic_vertex() +{ + +} +[require(hlsl, fragment)] +public void logic_fragment() +{ + +} +[require(hlsl, vertex, fragment)] +public void logic_stage_pass_1() // Pass, `vertex` and `fragment` supported +{ + __stage_switch + { + case vertex: + logic_vertex(); + case fragment: + logic_fragment(); + } +} + +[require(hlsl, vertex, fragment, mesh, hull, domain)] +public void logic_many_stages() +{ + +} +[require(hlsl, vertex, fragment)] +public void logic_stage_pass_2() // Pass, function only requires that the body implements the stages `vertex` & `fragment`, the rest are irelevant +{ + logic_many_stages(); +} + +[require(hlsl, any_hit)] +public void logic_stage_fail_1() // Error, function requires `any_hit`, body does not support `any_hit` +{ + logic_many_stages(); +} +``` + +## Capability Aliases To make it easy to specify capabilities on different platforms, Slang also defines many aliases that can be used in `[require]` attributes. -For example, Slang declares: +For example, Slang declares in `slang-capabilities.capdef`: ``` alias sm_6_6 = _sm_6_6 | glsl_spirv_1_5 + sm_6_5 diff --git a/docs/user-guide/a3-02-reference-capability-atoms.md b/docs/user-guide/a3-02-reference-capability-atoms.md index d46861d48..ea460da34 100644 --- a/docs/user-guide/a3-02-reference-capability-atoms.md +++ b/docs/user-guide/a3-02-reference-capability-atoms.md @@ -810,6 +810,9 @@ Extensions `spvVulkanMemoryModelKHR` > Represents the SPIR-V capability for vulkan memory model. +`GL_NV_cooperative_vector` +> Represents the GL_NV_cooperative_vector extension. + Compound Capabilities ---------------------- *Capabilities to specify capabilities created by other capabilities (`raytracing`, `meshshading`...)* diff --git a/docs/user-guide/toc.html b/docs/user-guide/toc.html index f559fec31..b32ad71da 100644 --- a/docs/user-guide/toc.html +++ b/docs/user-guide/toc.html @@ -68,9 +68,10 @@ diff --git a/source/slang/glsl.meta.slang b/source/slang/glsl.meta.slang index 782c09bb9..eeaf2a58c 100644 --- a/source/slang/glsl.meta.slang +++ b/source/slang/glsl.meta.slang @@ -4778,9 +4778,9 @@ public property uint3 gl_LaunchSizeEXT // casting conflict due to the spirv implementation of PrimitiveIndex(). internal in uint __gl_PrimitiveID : SV_PrimitiveID; -public property int gl_PrimitiveID +public property int gl_PrimitiveID { - [require(cuda_glsl_hlsl_spirv)] + [require(cuda_glsl_hlsl_spirv, raytracing_allstages)] get { __stage_switch @@ -4796,9 +4796,9 @@ public property int gl_PrimitiveID } } -public property int gl_InstanceID +public property int gl_InstanceID { - [require(cuda_glsl_hlsl_spirv)] + [require(cuda_glsl_hlsl_spirv, raytracing_allstages)] get { __stage_switch @@ -8473,7 +8473,7 @@ public vector subgroupPartitionedInclusiveMulNV(vector value, uvec4 __generic [ForceInline] -[require(cuda_glsl_spirv, subgroup_partitioned)] +[require(glsl_spirv, subgroup_partitioned)] public T subgroupPartitionedInclusiveMinNV(T value, uvec4 ballot) { return WaveMultiPrefixInclusiveMin(value, ballot); @@ -8481,7 +8481,7 @@ public T subgroupPartitionedInclusiveMinNV(T value, uvec4 ballot) __generic [ForceInline] -[require(cuda_glsl_spirv, subgroup_partitioned)] +[require(glsl_spirv, subgroup_partitioned)] public vector subgroupPartitionedInclusiveMinNV(vector value, uvec4 ballot) { return WaveMultiPrefixInclusiveMin(value, ballot); @@ -8489,7 +8489,7 @@ public vector subgroupPartitionedInclusiveMinNV(vector value, uvec4 __generic [ForceInline] -[require(cuda_glsl_spirv, subgroup_partitioned)] +[require(glsl_spirv, subgroup_partitioned)] public T subgroupPartitionedInclusiveMaxNV(T value, uvec4 ballot) { return WaveMultiPrefixInclusiveMax(value, ballot); @@ -8497,7 +8497,7 @@ public T subgroupPartitionedInclusiveMaxNV(T value, uvec4 ballot) __generic [ForceInline] -[require(cuda_glsl_spirv, subgroup_partitioned)] +[require(glsl_spirv, subgroup_partitioned)] public vector subgroupPartitionedInclusiveMaxNV(vector value, uvec4 ballot) { return WaveMultiPrefixInclusiveMax(value, ballot); @@ -8585,7 +8585,7 @@ public vector subgroupPartitionedExclusiveMulNV(vector value, uvec4 __generic [ForceInline] -[require(cuda_glsl_spirv, subgroup_partitioned)] +[require(glsl_spirv, subgroup_partitioned)] public T subgroupPartitionedExclusiveMinNV(T value, uvec4 ballot) { return WaveMultiPrefixExclusiveMin(value, ballot); @@ -8593,7 +8593,7 @@ public T subgroupPartitionedExclusiveMinNV(T value, uvec4 ballot) __generic [ForceInline] -[require(cuda_glsl_spirv, subgroup_partitioned)] +[require(glsl_spirv, subgroup_partitioned)] public vector subgroupPartitionedExclusiveMinNV(vector value, uvec4 ballot) { return WaveMultiPrefixExclusiveMin(value, ballot); @@ -8601,7 +8601,7 @@ public vector subgroupPartitionedExclusiveMinNV(vector value, uvec4 __generic [ForceInline] -[require(cuda_glsl_spirv, subgroup_partitioned)] +[require(glsl_spirv, subgroup_partitioned)] public T subgroupPartitionedExclusiveMaxNV(T value, uvec4 ballot) { return WaveMultiPrefixExclusiveMax(value, ballot); @@ -8609,7 +8609,7 @@ public T subgroupPartitionedExclusiveMaxNV(T value, uvec4 ballot) __generic [ForceInline] -[require(cuda_glsl_spirv, subgroup_partitioned)] +[require(glsl_spirv, subgroup_partitioned)] public vector subgroupPartitionedExclusiveMaxNV(vector value, uvec4 ballot) { return WaveMultiPrefixExclusiveMax(value, ballot); diff --git a/source/slang/hlsl.meta.slang b/source/slang/hlsl.meta.slang index 2d9543716..07f59ac46 100644 --- a/source/slang/hlsl.meta.slang +++ b/source/slang/hlsl.meta.slang @@ -4947,6 +4947,7 @@ T __getElement(U collection, I index); /// @category stage_io Stage IO types __generic +[require(glsl_hlsl_spirv, geometry)] [require(glsl_hlsl_spirv, hull)] __magic_type(HLSLInputPatchType) __intrinsic_type($(kIROp_HLSLInputPatchType)) @@ -5017,6 +5018,7 @@ This type is supported natively when targeting HLSL. */ __magic_type(HLSL$(item.name)Type) __intrinsic_type($(item.op)) +[require(byteaddressbuffer_rw)] struct $(item.name) { // Note(tfoley): supports all operations from `ByteAddressBuffer` @@ -5025,7 +5027,7 @@ struct $(item.name) /// Get the number of bytes in the buffer. ///@param[out] dim The number of bytes in the buffer. [ForceInline] - [require(cpp_cuda_glsl_hlsl_spirv_wgsl, structuredbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_spirv_wgsl)] void GetDimensions(out uint dim) { __target_switch @@ -5054,7 +5056,7 @@ struct $(item.name) /// When targeting non-HLSL, the status is always 0. [__NoSideEffect] [ForceInline] - [require(cpp_cuda_glsl_hlsl_metal_spirv_wgsl, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_metal_spirv_wgsl)] uint Load(int location) { __target_switch @@ -5067,7 +5069,7 @@ struct $(item.name) [__NoSideEffect] [ForceInline] - [require(hlsl, byteaddressbuffer_rw)] + [require(hlsl)] uint Load(int location, out uint status) { __target_switch @@ -5093,7 +5095,7 @@ struct $(item.name) /// When targeting non-HLSL, the status is always 0. [__NoSideEffect] [ForceInline] - [require(cpp_cuda_glsl_hlsl_metal_spirv, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_metal_spirv)] uint2 Load2(uint location) { __target_switch @@ -5106,7 +5108,7 @@ struct $(item.name) [__NoSideEffect] [ForceInline] - [require(cpp_cuda_glsl_hlsl_metal_spirv, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_metal_spirv)] uint2 Load2Aligned(uint location, uint alignment) { __target_switch @@ -5123,7 +5125,7 @@ struct $(item.name) ///@return `uint2` Two 32-bit unsigned integers loaded from the buffer. [__NoSideEffect] [ForceInline] - [require(cpp_cuda_glsl_hlsl_metal_spirv, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_metal_spirv)] uint2 Load2Aligned(uint location) { __target_switch @@ -5136,7 +5138,7 @@ struct $(item.name) [__NoSideEffect] [ForceInline] - [require(hlsl, byteaddressbuffer_rw)] + [require(hlsl)] uint2 Load2(uint location, out uint status) { __target_switch @@ -5161,7 +5163,7 @@ struct $(item.name) /// When targeting non-HLSL, the status is always 0. [__NoSideEffect] [ForceInline] - [require(cpp_cuda_glsl_hlsl_metal_spirv, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_metal_spirv)] uint3 Load3(uint location) { __target_switch @@ -5174,7 +5176,7 @@ struct $(item.name) [__NoSideEffect] [ForceInline] - [require(cpp_cuda_glsl_hlsl_metal_spirv, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_metal_spirv)] uint3 Load3Aligned(uint location, uint alignment) { __target_switch @@ -5191,7 +5193,7 @@ struct $(item.name) ///@return `uint3` Three 32-bit unsigned integer value loaded from the buffer. [__NoSideEffect] [ForceInline] - [require(cpp_cuda_glsl_hlsl_metal_spirv, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_metal_spirv)] uint3 Load3Aligned(uint location) { __target_switch @@ -5204,7 +5206,7 @@ struct $(item.name) [__NoSideEffect] [ForceInline] - [require(hlsl, byteaddressbuffer_rw)] + [require(hlsl)] uint3 Load3(uint location, out uint status) { __target_switch @@ -5228,7 +5230,7 @@ struct $(item.name) /// If any values were taken from an unmapped tile, `CheckAccessFullyMapped` returns FALSE. [__NoSideEffect] [ForceInline] - [require(cpp_cuda_glsl_hlsl_metal_spirv, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_metal_spirv)] uint4 Load4(uint location) { __target_switch @@ -5241,7 +5243,7 @@ struct $(item.name) [__NoSideEffect] [ForceInline] - [require(cpp_cuda_glsl_hlsl_metal_spirv, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_metal_spirv)] uint4 Load4Aligned(uint location, uint alignment) { __target_switch @@ -5258,7 +5260,7 @@ struct $(item.name) ///@return `uint4` Four 32-bit unsigned integer value loaded from the buffer. [__NoSideEffect] [ForceInline] - [require(cpp_cuda_glsl_hlsl_metal_spirv, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_metal_spirv)] uint4 Load4Aligned(uint location) { __target_switch @@ -5271,7 +5273,7 @@ struct $(item.name) [__NoSideEffect] [ForceInline] - [require(hlsl, byteaddressbuffer_rw)] + [require(hlsl)] uint4 Load4(uint location, out uint status) { __target_switch @@ -5282,7 +5284,7 @@ struct $(item.name) [__NoSideEffect] [ForceInline] - [require(cpp_cuda_glsl_hlsl_metal_spirv, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_metal_spirv)] T Load(uint location) { return __byteAddressBufferLoad(this, location, 0); @@ -5290,7 +5292,7 @@ struct $(item.name) [__NoSideEffect] [ForceInline] - [require(cpp_cuda_glsl_hlsl_metal_spirv, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_metal_spirv)] T LoadAligned(uint location, uint alignment) { return __byteAddressBufferLoad(this, location, alignment); @@ -5303,7 +5305,7 @@ struct $(item.name) ///Currently, this function only supports when `T` is scalar, vector, or matrix type. [__NoSideEffect] [ForceInline] - [require(cpp_cuda_glsl_hlsl_metal_spirv, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_metal_spirv)] T LoadAligned(uint location) { return __byteAddressBufferLoad(this, location, __naturalStrideOf()); @@ -5420,6 +5422,7 @@ ${{{{ /// maps to `atomicAdd`. [__requiresNVAPI] [ForceInline] + [require(sm_5_0)] void InterlockedAddF16(uint byteAddress, half value, out half originalValue) { __target_switch @@ -5459,6 +5462,7 @@ ${{{{ /// maps to `atomicAdd`. [__requiresNVAPI] [ForceInline] + [require(sm_5_0)] void InterlockedAddF16Emulated(uint byteAddress, half value, out half originalValue) { __target_switch @@ -5735,7 +5739,7 @@ ${{{{ /// @param value The operand of the atomic operation. /// @param original_value The original value at `dest` before the $(op.internalName) operation. [ForceInline] - [require(cuda_glsl_hlsl_metal_spirv, atomic_glsl_hlsl_cuda_metal, byteaddressbuffer_rw)] + [require(cuda_glsl_hlsl_metal_spirv, atomic_glsl_hlsl_cuda_metal)] void Interlocked$(op.name)( UINT dest, UINT value, @@ -5751,7 +5755,7 @@ ${{{{ } [ForceInline] - [require(cuda_glsl_hlsl_metal_spirv, atomic_glsl_hlsl_cuda_metal, byteaddressbuffer_rw)] + [require(cuda_glsl_hlsl_metal_spirv, atomic_glsl_hlsl_cuda_metal)] void Interlocked$(op.name)( UINT dest, UINT value) @@ -5778,7 +5782,7 @@ ${{{{ /// translates to `InterlockedCompareExchange`. /// For CUDA, this function maps to `atomicCAS`. [ForceInline] - [require(cuda_glsl_hlsl_metal_spirv, atomic_glsl_hlsl_cuda_metal, byteaddressbuffer_rw)] + [require(cuda_glsl_hlsl_metal_spirv, atomic_glsl_hlsl_cuda_metal)] void InterlockedCompareExchange( UINT dest, UINT compare_value, @@ -5803,7 +5807,7 @@ ${{{{ /// translates to `InterlockedCompareStore`. /// For CUDA, this function maps to `atomicCAS`. [ForceInline] - [require(cuda_glsl_hlsl_metal_spirv, atomic_glsl_hlsl_cuda_metal, byteaddressbuffer_rw)] + [require(cuda_glsl_hlsl_metal_spirv, atomic_glsl_hlsl_cuda_metal)] void InterlockedCompareStore( UINT dest, UINT compare_value, @@ -5824,7 +5828,7 @@ ${{{{ ///@param address The input address in bytes, which must be a multiple of 4. ///@param alignment Specifies the alignment of the location, which must be a multiple of 4. [ForceInline] - [require(cpp_cuda_glsl_hlsl_metal_spirv, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_metal_spirv)] void Store(uint address, uint value) { __target_switch @@ -5841,7 +5845,7 @@ ${{{{ ///@param value Two input values. ///@param alignment Specifies the alignment of the location, which must be a multiple of 4. [ForceInline] - [require(cpp_cuda_glsl_hlsl_metal_spirv, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_metal_spirv)] void Store2(uint address, uint2 value) { __target_switch @@ -5854,7 +5858,7 @@ ${{{{ [ForceInline] - [require(cpp_cuda_glsl_hlsl_metal_spirv, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_metal_spirv)] void Store2(uint address, uint2 value, uint alignment) { __target_switch @@ -5870,7 +5874,7 @@ ${{{{ ///@param address The input address in bytes, which must be a multiple of 8. ///@param value Two input values. [ForceInline] - [require(cpp_cuda_glsl_hlsl_metal_spirv, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_metal_spirv)] void Store2Aligned(uint address, uint2 value) { __target_switch @@ -5886,7 +5890,7 @@ ${{{{ ///@param value Three input values. ///@param alignment Specifies the alignment of the location, which must be a multiple of 4. [ForceInline] - [require(cpp_cuda_glsl_hlsl_metal_spirv, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_metal_spirv)] void Store3(uint address, uint3 value) { __target_switch @@ -5898,7 +5902,7 @@ ${{{{ } [ForceInline] - [require(cpp_cuda_glsl_hlsl_metal_spirv, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_metal_spirv)] void Store3(uint address, uint3 value, uint alignment) { __target_switch @@ -5914,7 +5918,7 @@ ${{{{ ///@param address The input address in bytes, which must be a multiple of 12. ///@param value Three input values. [ForceInline] - [require(cpp_cuda_glsl_hlsl_spirv, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_spirv)] void Store3Aligned(uint address, uint3 value) { __target_switch @@ -5930,7 +5934,7 @@ ${{{{ ///@param value Four input values. ///@param alignment Specifies the alignment of the location, which must be a multiple of 4. [ForceInline] - [require(cpp_cuda_glsl_hlsl_spirv, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_spirv)] void Store4(uint address, uint4 value) { __target_switch @@ -5943,7 +5947,7 @@ ${{{{ [ForceInline] - [require(cpp_cuda_glsl_hlsl_metal_spirv, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_metal_spirv)] void Store4(uint address, uint4 value, uint alignment) { __target_switch @@ -5959,7 +5963,7 @@ ${{{{ ///@param address The input address in bytes, which must be a multiple of 16. ///@param value Four input values. [ForceInline] - [require(cpp_cuda_glsl_hlsl_metal_spirv, byteaddressbuffer_rw)] + [require(cpp_cuda_glsl_hlsl_metal_spirv)] void Store4Aligned(uint address, uint4 value) { __target_switch @@ -7392,7 +7396,6 @@ float16_t asfloat16(uint16_t value) } [__readNone] -[require(cuda_glsl_hlsl_spirv, shader5_sm_5_0)] vector asfloat16(vector value) { __target_switch @@ -7414,7 +7417,6 @@ matrix asfloat16(matrix v [__unsafeForceInlineEarly] [__readNone] -[require(cuda_hlsl_metal_spirv, shader5_sm_5_0)] int16_t asint16(float16_t value) { __target_switch @@ -7430,8 +7432,7 @@ int16_t asint16(float16_t value) } [__unsafeForceInlineEarly] -[__readNone] -[require(cuda_hlsl_metal_spirv, shader5_sm_5_0)] +[__readNone] vector asint16(vector value) { __target_switch @@ -7444,7 +7445,6 @@ vector asint16(vector value) [__unsafeForceInlineEarly] [__readNone] -[require(cuda_hlsl_spirv, shader5_sm_5_0)] matrix asint16(matrix value) { __target_switch @@ -7455,8 +7455,7 @@ matrix asint16(matrix valu } [__readNone] -[__unsafeForceInlineEarly] -[require(cuda_hlsl_metal_spirv, shader5_sm_5_0)] +[__unsafeForceInlineEarly] float16_t asfloat16(int16_t value) { __target_switch @@ -7473,7 +7472,6 @@ float16_t asfloat16(int16_t value) [__unsafeForceInlineEarly] [__readNone] -[require(cuda_hlsl_metal_spirv, shader5_sm_5_0)] vector asfloat16(vector value) { __target_switch @@ -7489,7 +7487,6 @@ vector asfloat16(vector value) [__unsafeForceInlineEarly] [__readNone] -[require(cuda_hlsl_spirv, shader5_sm_5_0)] matrix asfloat16(matrix value) { __target_switch @@ -10270,7 +10267,7 @@ vector fwidth_coarse(vector x) __generic [__readNone] -[require(glsl_hlsl_spirv, fragmentprocessing)] +[require(glsl_hlsl_spirv, fragmentprocessing_derivativecontrol)] matrix fwidth_coarse(matrix x) { __target_switch @@ -10332,7 +10329,7 @@ vector fwidth_fine(vector x) __generic [__readNone] -[require(glsl_hlsl_spirv, fragmentprocessing)] +[require(glsl_hlsl_spirv, fragmentprocessing_derivativecontrol)] matrix fwidth_fine(matrix x) { __target_switch @@ -26175,8 +26172,10 @@ CoopVec atan(CoopVec yO // [ForceInline] [require(cooperative_vector)] +[require(cooperative_vector)] [require(hlsl_coopvec_poc)] [require(optix_coopvec)] +[require(GL_ARB_gpu_shader5)] CoopVec fma(CoopVec a, CoopVec b, CoopVec c) { // TODO: Investigate, why does this fail if it's not inlined diff --git a/source/slang/slang-capabilities.capdef b/source/slang/slang-capabilities.capdef index ec821ef21..95f1335da 100644 --- a/source/slang/slang-capabilities.capdef +++ b/source/slang/slang-capabilities.capdef @@ -152,7 +152,7 @@ def glsl_spirv_1_6 : glsl_spirv_1_5; // We have multiple capabilities for the various SPIR-V versions, // arranged so that they inherit from one another to represent which versions -// provide a super-set of the features of earlier ones (e.g., SPIR-V 1.4 is +// provide a superset of the features of earlier ones (e.g., SPIR-V 1.4 is // expressed as inheriting from SPIR-V 1.3). def _spirv_1_0 : spirv; @@ -887,6 +887,7 @@ def _GL_NV_shader_invocation_reorder : _GLSL_460; def _GL_NV_shader_subgroup_partitioned : _GLSL_140; def _GL_NV_shader_texture_footprint : _GLSL_450; def _GL_NV_cluster_acceleration_structure : _GLSL_460; +def _GL_NV_cooperative_vector : _GLSL_450; // GLSL extension and SPV extension associations. @@ -1134,6 +1135,10 @@ alias GL_NV_shader_texture_footprint = _GL_NV_shader_texture_footprint | spvImag /// [EXT] alias GL_NV_cluster_acceleration_structure = _GL_NV_cluster_acceleration_structure | spvRayTracingClusterAccelerationStructureNV; +/// Represents the GL_NV_cooperative_vector extension. +/// [EXT] +alias GL_NV_cooperative_vector = _GL_NV_cooperative_vector | spvCooperativeVectorNV + spvCooperativeVectorTrainingNV; + // Define feature names not reliant on shader stages /// NVAPI capability for HLSL @@ -1190,11 +1195,10 @@ alias bufferreference_int64 = bufferreference + GL_EXT_shader_explicit_arithmeti /// Note that cpp and cuda are supported via a fallback non-cooperative implementation /// No HLSL shader model bound yet /// [Compound] -alias cooperative_vector = _sm_6_9 | cpp | _cuda_sm_9_0 | spvCooperativeVectorNV; +alias cooperative_vector = _sm_6_9 | cpp | _cuda_sm_9_0 | spvCooperativeVectorNV | _GL_NV_cooperative_vector; /// Capabilities needed to train cooperative vectors /// [Compound] -alias cooperative_vector_training = spvCooperativeVectorTrainingNV; - +alias cooperative_vector_training = spvCooperativeVectorTrainingNV | _GL_NV_cooperative_vector; /// Capabilities needed to use cooperative matrices /// [Compound] alias cooperative_matrix = spvCooperativeMatrixKHR; diff --git a/source/slang/slang-capability.cpp b/source/slang/slang-capability.cpp index a2fef9f8a..a965ecd93 100644 --- a/source/slang/slang-capability.cpp +++ b/source/slang/slang-capability.cpp @@ -269,6 +269,22 @@ CapabilityAtomSet CapabilityAtomSet::newSetWithoutImpliedAtoms() const //// CapabiltySet +CapabilityAtomSet getTargetAtomsInSet(const CapabilitySet& set) +{ + CapabilityAtomSet out; + for (auto i : set.getCapabilityTargetSets()) + out.add((UInt)i.first); + return out; +} + +CapabilityAtomSet getStageAtomsInSet(const CapabilityTargetSet& set) +{ + CapabilityAtomSet out; + for (auto i : set.getShaderStageSets()) + out.add((UInt)i.first); + return out; +} + CapabilityAtom getTargetAtomInSet(const CapabilityAtomSet& atomSet) { auto targetSet = getAtomSetOfTargets(); @@ -959,56 +975,117 @@ CapabilitySet::AtomSets::Iterator CapabilitySet::getAtomSets() const return CapabilitySet::AtomSets::Iterator(&this->getCapabilityTargetSets()).begin(); } -bool CapabilitySet::checkCapabilityRequirement( +void CapabilitySet::checkCapabilityRequirement( + CheckCapabilityRequirementOptions options, CapabilitySet const& available, CapabilitySet const& required, - CapabilityAtomSet& outFailedAvailableSet) + CapabilityAtomSet& outFailedAvailableSet, + CheckCapabilityRequirementResult& result) { - // Requirements x are met by available disjoint capabilities (a | b) iff + // 'required' capabilities x are met by 'available' disjoint capabilities (a | b) iff // both 'a' satisfies x and 'b' satisfies x. // If we have a caller function F() decorated with: // [require(hlsl, _sm_6_3)] [require(spirv, _spv_ray_tracing)] void F() { g(); } // We'd better make sure that `g()` can be compiled with both (hlsl+_sm_6_3) and // (spirv+_spv_ray_tracing) capability sets. In this method, F()'s capability declaration is // represented by `available`, and g()'s capability is represented by `required`. We will check - // that for every capability conjunction X of F(), there is one capability conjunction Y in g() + // that for every capability conjunction X of F(), there is a capability conjunction Y in g() // such that X implies Y. // - // if empty there is no body, all capabilities are supported. - if (required.isEmpty()) - return true; + // If empty, all capabilities are supported. + // Either, we require no capabilities (return true) + // or we have no capability requirements (return true) + if (required.isEmpty() || available.isEmpty()) + { + result = CheckCapabilityRequirementResult::AvailableIsASuperSetToRequired; + return; + } + // invalid isn't a fail because the capabilities already threw an error. if (required.isInvalid()) { outFailedAvailableSet.add((UInt)CapabilityAtom::Invalid); - return false; + result = CheckCapabilityRequirementResult::AvailableIsASuperSetToRequired; + return; } - // If F's capability is empty, we can satisfy any non-empty requirements. - // - if (available.isEmpty() && !required.isEmpty()) - return false; + auto availableTargetSets = available.getCapabilityTargetSets(); + auto requiredTargetSets = required.getCapabilityTargetSets(); + if (options == CheckCapabilityRequirementOptions::MustHaveEqualAbstractAtoms) + { + // If we have a mismatch in capability-target count we clearly have a + // mismatch and will fail + auto availableTargetSetsCount = availableTargetSets.getCount(); + auto requiredTargetSetsCount = requiredTargetSets.getCount(); + if (availableTargetSetsCount != requiredTargetSetsCount) + { + auto availableTargets = getTargetAtomsInSet(available); + auto requiredTargets = getTargetAtomsInSet(required); + if (requiredTargetSetsCount > availableTargetSetsCount) + { + result = CheckCapabilityRequirementResult::AvailableIsNotASuperSetToRequired; + requiredTargets.subtractWith((UIntSet)availableTargets); + outFailedAvailableSet.add((UIntSet)requiredTargets); + } + else + { + result = CheckCapabilityRequirementResult::RequiredIsMissingAbstractAtoms; + availableTargets.subtractWith((UIntSet)requiredTargets); + outFailedAvailableSet.add((UIntSet)availableTargets); + } + return; + } + } - // if all sets in `available` are not a super-set to at least 1 `required` set, then we have an - // err - for (auto& availableTarget : available.m_targetSets) + // if all sets in `available` are not a superset to `required` then we have an + // error. + for (auto& availableTarget : availableTargetSets) { - auto reqTarget = required.m_targetSets.tryGetValue(availableTarget.first); + auto reqTarget = requiredTargetSets.tryGetValue(availableTarget.first); if (!reqTarget) { outFailedAvailableSet.add((UInt)availableTarget.first); - return false; + result = CheckCapabilityRequirementResult::RequiredIsMissingAbstractAtoms; + return; } - for (auto& availableStage : availableTarget.second.shaderStageSets) + if (options == CheckCapabilityRequirementOptions::MustHaveEqualAbstractAtoms) { - auto reqStage = reqTarget->shaderStageSets.tryGetValue(availableStage.first); + // If we have a mismatch in capability-stage count we clearly have a + // mismatch and will fail + auto availableStageSetsCount = availableTarget.second.getShaderStageSets().getCount(); + auto requiredStageSetsCount = reqTarget->getShaderStageSets().getCount(); + if (availableStageSetsCount != requiredStageSetsCount) + { + auto availableStages = getStageAtomsInSet(availableTarget.second); + auto requiredStages = getStageAtomsInSet(*reqTarget); + + if (requiredStageSetsCount > availableStageSetsCount) + { + result = CheckCapabilityRequirementResult::AvailableIsNotASuperSetToRequired; + requiredStages.subtractWith((UIntSet)availableStages); + outFailedAvailableSet.add((UIntSet)requiredStages); + } + else + { + result = CheckCapabilityRequirementResult::RequiredIsMissingAbstractAtoms; + availableStages.subtractWith((UIntSet)requiredStages); + outFailedAvailableSet.add((UIntSet)availableStages); + } + return; + } + } + + for (auto& availableStage : availableTarget.second.getShaderStageSets()) + { + auto reqStage = reqTarget->getShaderStageSets().tryGetValue(availableStage.first); if (!reqStage) { outFailedAvailableSet.add((UInt)availableStage.first); - return false; + result = CheckCapabilityRequirementResult::RequiredIsMissingAbstractAtoms; + return; } const CapabilityAtomSet* lastBadStage = nullptr; @@ -1020,7 +1097,7 @@ bool CapabilitySet::checkCapabilityRequirement( { const auto& reqStageSet = reqStage->atomSet.value(); if (availableStageSet.contains(reqStageSet)) - break; + continue; else lastBadStage = &reqStageSet; } @@ -1031,13 +1108,19 @@ bool CapabilitySet::checkCapabilityRequirement( outFailedAvailableSet, *lastBadStage, availableStageSet); - return false; + + // Not a failiure if nothing is missing + if (outFailedAvailableSet.isEmpty()) + continue; + result = CheckCapabilityRequirementResult::AvailableIsNotASuperSetToRequired; + return; } } } } - return true; + result = CheckCapabilityRequirementResult::AvailableIsASuperSetToRequired; + return; } /// Converts spirv version atom to the glsl_spirv equivlent. If not possible, Invalid is returned @@ -1097,7 +1180,7 @@ UnownedStringSlice capabilityNameToStringWithoutPrefix(CapabilityName capability return name; } -void printDiagnosticArg(StringBuilder& sb, const CapabilityAtomSet atomSet) +void printDiagnosticArg(StringBuilder& sb, const CapabilityAtomSet& atomSet) { bool isFirst = true; for (auto atom : atomSet.newSetWithoutImpliedAtoms()) diff --git a/source/slang/slang-capability.h b/source/slang/slang-capability.h index 4bf0704a0..43f933620 100644 --- a/source/slang/slang-capability.h +++ b/source/slang/slang-capability.h @@ -36,7 +36,7 @@ namespace Slang // The situation is slightly more complicated for a function. A function // might require a specific set of atomic feature, and that is the simple // case. In this simple case, we know that a target can run a function -// if the features of the target are a super-set of those required by +// if the features of the target are a superset of those required by // the function. // // In the more general case, we might have a function that can be used @@ -98,6 +98,27 @@ struct CapabilityTargetSet /// 2. `this` has completly disjoint shader stages from other. bool tryJoin(const CapabilityTargetSets& other); void unionWith(const CapabilityTargetSet& other); + + const CapabilityStageSets& getShaderStageSets() const { return shaderStageSets; } +}; + +enum class CheckCapabilityRequirementOptions +{ + // `available` can have a subset of the abstract atoms `required` has + AvailableCanHaveSubsetOfAbstractAtoms, + // `available` and `required` both must have equal abstract stage & target atoms + MustHaveEqualAbstractAtoms, +}; + +enum class CheckCapabilityRequirementResult +{ + // `available` is a superset to `required` + AvailableIsASuperSetToRequired, + // `available` is not a superset to `required` + AvailableIsNotASuperSetToRequired, + // `available` has abstract atoms that `required` is missing. + // Only possible with CheckCapabilityRequirementOptions::MustHaveEqualAbstractAtoms + RequiredIsMissingAbstractAtoms, }; struct CapabilitySet @@ -186,12 +207,14 @@ public: CapabilitySet const& targetCaps, bool& isEqual) const; - /// Find any capability sets which are in 'available' but not in 'required'. Return false if + /// Identify capability sets which are in 'available' but not in 'required'. Return false if /// this situation occurs. - static bool checkCapabilityRequirement( + static void checkCapabilityRequirement( + CheckCapabilityRequirementOptions options, CapabilitySet const& available, CapabilitySet const& required, - CapabilityAtomSet& outFailedAvailableSet); + CapabilityAtomSet& outFailedAvailableSet, + CheckCapabilityRequirementResult& result); // For each element in `elementsToPermutateWith`, create and add a different conjunction // permutation by adding to `setToPermutate`. @@ -370,6 +393,7 @@ bool isSpirvExtensionAtom(CapabilityAtom name); void printDiagnosticArg(StringBuilder& sb, CapabilityAtom atom); void printDiagnosticArg(StringBuilder& sb, CapabilityName name); +void printDiagnosticArg(StringBuilder& sb, const CapabilityAtomSet& atomSet); const CapabilityAtomSet& getAtomSetOfTargets(); const CapabilityAtomSet& getAtomSetOfStages(); diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp index 72b5c19db..0a9853012 100644 --- a/source/slang/slang-check-decl.cpp +++ b/source/slang/slang-check-decl.cpp @@ -995,12 +995,10 @@ struct SemanticsDeclCapabilityVisitor : public SemanticsDeclVisitorBase, CapabilitySet getDeclaredCapabilitySet(Decl* decl); - void visitDecl(Decl*) {} void visitDeclGroup(DeclGroup*) {} void checkVarDeclCommon(VarDeclBase* varDecl); - void visitAggTypeDeclBase(AggTypeDeclBase* decl); - void visitNamespaceDeclBase(NamespaceDeclBase* decl); + void visitContainerDecl(ContainerDecl* decl); void visitVarDecl(VarDecl* varDecl) { checkVarDeclCommon(varDecl); } @@ -1013,7 +1011,8 @@ struct SemanticsDeclCapabilityVisitor : public SemanticsDeclVisitorBase, void diagnoseUndeclaredCapability( Decl* decl, const DiagnosticInfo& diagnosticInfo, - const CapabilityAtomSet& failedAtomsInsideAvailableSet); + const CapabilityAtomSet& failedAtomsInsideAvailableSet, + bool printProvenance); }; @@ -14382,26 +14381,29 @@ struct CapabilityDeclReferenceVisitor auto targetCaseCount = stmt->targetCases.getCount(); for (Index targetCaseIndex = 0; targetCaseIndex < targetCaseCount; targetCaseIndex++) { - // We may recieve a `default:` case for a `__target_switch`. If this is the case, - // we must resolve the target capability for a non empty set of - // `calling_functions_targets`: - // ``` default_target = calling_functions_targets-{other_case_targets} ``` + // The logic here is to collect a list of `case` statment capabilities + // so that down-the-line we can specialize according to the compile-capabilities. // - // * `calling_functions_capability` = `requirement attribute` of the calling - // function; if missing - // we can assume it is `any_target` + // The additional goal we have is to merge all case-capabilities into 1 set + // so that we can propegate them to the parent-function so that a user may break-down + // a functon into `stage`/`target` specific code. // - // * `{other_case_targets}` = set of all capabilities all `case` statments target - // inside the `__target_switch` - - // If we do not handle `default:`, the codegen will fail when trying to find a - // specific codegen target not handled explicitly by a `case` statment. We must also - // ensure the `default` case is last so we have priority to hit `case` statments and - // can preprocess `case` statments before the `default` case. + // A few important details + // 1. Case statments (other than `default:`) may have overlapping capabilities. This is + // to allow "more specialized" `case` statments to support specializing code on + // higher-feature-levels to support writing 1 function to handle cases such as sm_5_0 + // and sm_6_0 support all in 1 function. + // + // 2. All `case:` statments are explicit with their own-capabilities, `default:` + // statments are not. `default:` statments have the value `CapabilityName::Invalid`. If + // we find a `default` statment we assign it all shader target/stage capabilities that + // the other `case` statments did not specify which is legal for the current calling + // function (based on the parent function `require` decl). CapabilitySet targetCap; if (CapabilityName(stmt->targetCases[targetCaseIndex]->capability) == CapabilityName::Invalid) { + // swap the `default` case to the end so that we process it last if (targetCaseCount - 1 != targetCaseIndex) { for (Index i = targetCaseIndex; i < targetCaseCount - 1; i++) @@ -14594,30 +14596,22 @@ CapabilitySet SemanticsDeclCapabilityVisitor::getDeclaredCapabilitySet(Decl* dec return declaredCaps; } -void SemanticsDeclCapabilityVisitor::visitAggTypeDeclBase(AggTypeDeclBase* decl) -{ - decl->inferredCapabilityRequirements = getDeclaredCapabilitySet(decl); -} - -void SemanticsDeclCapabilityVisitor::visitNamespaceDeclBase(NamespaceDeclBase* decl) +void SemanticsDeclCapabilityVisitor::visitContainerDecl(ContainerDecl* decl) { + // Any potential child must get it's capabilities from `getDeclaredCapabilitySet`. decl->inferredCapabilityRequirements = getDeclaredCapabilitySet(decl); } -template -static inline void _dispatchCapabilitiesVisitorOfFunctionDecl( - SemanticsVisitor* visitor, - FunctionDeclBase* funcDecl, - const ProcessFunc& processFunc, - const ParentDiagnosticFunc& parentDiagnosticFunc) +void SemanticsDeclCapabilityVisitor::visitFunctionDeclBase(FunctionDeclBase* funcDecl) { - visitor->setParentFuncOfVisitor(funcDecl); + setParentFuncOfVisitor(funcDecl); + // visit the members of our funcDecl for (auto member : funcDecl->getDirectMemberDecls()) { - visitor->ensureDecl(member, DeclCheckState::CapabilityChecked); + ensureDecl(member, DeclCheckState::CapabilityChecked); _propagateRequirement( - visitor, + this, funcDecl->inferredCapabilityRequirements, funcDecl, member, @@ -14625,22 +14619,35 @@ static inline void _dispatchCapabilitiesVisitorOfFunctionDecl( member->loc); } + // visit the body of our funcDecl, propagate capabilities. visitReferencedDecls( - *visitor, + *this, funcDecl->body, funcDecl->loc, funcDecl->findModifier(), - processFunc, - parentDiagnosticFunc); + [this, funcDecl](SyntaxNode* node, const CapabilitySet& nodeCaps, SourceLoc refLoc) + { + _propagateRequirement( + this, + funcDecl->inferredCapabilityRequirements, + funcDecl, + node, + nodeCaps, + refLoc); + }, + [this, funcDecl](DiagnosticCategory category) + { _propagateSeeDefinitionOf(this, funcDecl, category); }); + // non-static function join's capabilities with parent + // to become a superset of the parent. if (!isEffectivelyStatic(funcDecl)) { auto parentAggTypeDecl = getParentAggTypeDecl(funcDecl); if (parentAggTypeDecl) { - visitor->ensureDecl(parentAggTypeDecl, DeclCheckState::CapabilityChecked); + ensureDecl(parentAggTypeDecl, DeclCheckState::CapabilityChecked); _propagateRequirement( - visitor, + this, funcDecl->inferredCapabilityRequirements, funcDecl, parentAggTypeDecl, @@ -14648,34 +14655,13 @@ static inline void _dispatchCapabilitiesVisitorOfFunctionDecl( funcDecl->loc); } } -} - -void SemanticsDeclCapabilityVisitor::visitFunctionDeclBase(FunctionDeclBase* funcDecl) -{ - // If the function is an entrypoint and specifies a target stage, add the capabilities to - // our function capabilities. - _dispatchCapabilitiesVisitorOfFunctionDecl( - this, - funcDecl, - [this, funcDecl](SyntaxNode* node, const CapabilitySet& nodeCaps, SourceLoc refLoc) - { - _propagateRequirement( - this, - funcDecl->inferredCapabilityRequirements, - funcDecl, - node, - nodeCaps, - refLoc); - }, - [this, funcDecl](DiagnosticCategory category) - { _propagateSeeDefinitionOf(this, funcDecl, category); }); + // Get require of decl + add parents auto declaredCaps = getDeclaredCapabilitySet(funcDecl); - auto vis = getDeclVisibility(funcDecl); - // If 0 capabilities were annotated on a function, capabilities are inferred from the - // function body + // If 0 capabilities were annotated on this function, + // capabilities are inferred from the children. if (declaredCaps.isEmpty()) { declaredCaps = funcDecl->inferredCapabilityRequirements; @@ -14687,30 +14673,37 @@ void SemanticsDeclCapabilityVisitor::visitFunctionDeclBase(FunctionDeclBase* fun addModifier(funcDecl, declaredCapModifier); if (vis == DeclVisibility::Public) { - // For public decls, we need to enforce that the function - // only uses capabilities that it declares. - // At a minimum we will propagate shader requirements to our - // function from calling children in all cases so the parent - // can enforce shader targets correctly and propagate to `main` + // We need to enforce that the function-body + // only uses capabilities that the function-decl declares. + // + // A small exception to this rule is that the body must + // implement all shader stages/targets of the functionDecl + // requirements. The body can support *more* stages/targets, + // these will just be not accessible (which is fine since a user + // may only need hlsl support for a function, using an STD-LIB function + // implemented for all targets/stages). CapabilityAtomSet failedAvailableCapabilityConjunction; - if (!CapabilitySet::checkCapabilityRequirement( - declaredCaps, - funcDecl->inferredCapabilityRequirements, - failedAvailableCapabilityConjunction)) - { - diagnoseUndeclaredCapability( - funcDecl, - Diagnostics::useOfUndeclaredCapability, - failedAvailableCapabilityConjunction); - funcDecl->inferredCapabilityRequirements = declaredCaps; - } - else - funcDecl->inferredCapabilityRequirements.nonDestructiveJoin(declaredCaps); + CheckCapabilityRequirementResult checkCapabilityResult; + CapabilitySet::checkCapabilityRequirement( + CheckCapabilityRequirementOptions::AvailableCanHaveSubsetOfAbstractAtoms, + declaredCaps, + funcDecl->inferredCapabilityRequirements, + failedAvailableCapabilityConjunction, + checkCapabilityResult); + diagnoseUndeclaredCapability( + funcDecl, + Diagnostics::useOfUndeclaredCapability, + failedAvailableCapabilityConjunction, + true); + + // declared capabilities must be a superset. + funcDecl->inferredCapabilityRequirements = declaredCaps; } else { // For internal decls, their inferred capability should be joined - // with the declared capabilities. + // with the declared capabilities since we are assuming the stdlib + // is not wrong. funcDecl->inferredCapabilityRequirements.join(declaredCaps); } } @@ -14718,8 +14711,29 @@ void SemanticsDeclCapabilityVisitor::visitFunctionDeclBase(FunctionDeclBase* fun void SemanticsDeclCapabilityVisitor::visitInheritanceDecl(InheritanceDecl* inheritanceDecl) { - // Check that the implementation of an interface requirement is not using more capabilities - // than what's declared on the interface method. + auto inheritanceParentDecl = inheritanceDecl->parentDecl; + ensureDecl(inheritanceParentDecl, DeclCheckState::CapabilityChecked); + + // Propegate capabilities of inheritance `base` to + // `InheritanceDecl` + visitReferencedDecls( + *this, + inheritanceDecl->base, + inheritanceDecl->loc, + nullptr, + [this, inheritanceDecl](SyntaxNode* node, const CapabilitySet& nodeCaps, SourceLoc refLoc) + { + _propagateRequirement( + this, + inheritanceDecl->inferredCapabilityRequirements, + inheritanceDecl, + node, + nodeCaps, + refLoc); + }, + [this, inheritanceDecl](DiagnosticCategory category) + { _propagateSeeDefinitionOf(this, inheritanceDecl, category); }); + if (inheritanceDecl->witnessTable) { for (auto& kv : inheritanceDecl->witnessTable->m_requirementDictionary) @@ -14727,29 +14741,116 @@ void SemanticsDeclCapabilityVisitor::visitInheritanceDecl(InheritanceDecl* inher if (kv.value.getFlavor() != RequirementWitness::Flavor::declRef) continue; auto requirementDecl = kv.key; - auto implDecl = kv.value.getDeclRef(); - if (!implDecl) + auto implDeclRef = kv.value.getDeclRef(); + if (!implDeclRef) continue; - if (getModuleDecl(implDecl.getDecl())->languageVersion == SLANG_LANGUAGE_VERSION_LEGACY) + if (getModuleDecl(implDeclRef.getDecl())->languageVersion == + SLANG_LANGUAGE_VERSION_LEGACY) break; ensureDecl(requirementDecl, DeclCheckState::CapabilityChecked); - ensureDecl(implDecl.declRefBase, DeclCheckState::CapabilityChecked); + ensureDecl(implDeclRef.declRefBase, DeclCheckState::CapabilityChecked); + + // Only if capabilities are opted-into, should we error. + auto implDecl = implDeclRef.getDecl(); + if (!requirementDecl->hasModifier() && + !implDecl->hasModifier()) + continue; CapabilityAtomSet failedAvailableCapabilityConjunction; - if (!CapabilitySet::checkCapabilityRequirement( - requirementDecl->inferredCapabilityRequirements, - implDecl.getDecl()->inferredCapabilityRequirements, - failedAvailableCapabilityConjunction)) + CheckCapabilityRequirementResult checkCapabilityResult; + CapabilitySet::checkCapabilityRequirement( + CheckCapabilityRequirementOptions::MustHaveEqualAbstractAtoms, + requirementDecl->inferredCapabilityRequirements, + implDecl->inferredCapabilityRequirements, + failedAvailableCapabilityConjunction, + checkCapabilityResult); + + if (checkCapabilityResult == + CheckCapabilityRequirementResult::AvailableIsNotASuperSetToRequired) { diagnoseUndeclaredCapability( - implDecl.getDecl(), + implDecl, Diagnostics::useOfUndeclaredCapabilityOfInterfaceRequirement, + failedAvailableCapabilityConjunction, + false); + maybeDiagnose( + getSink(), + getOptionSet(), + DiagnosticCategory::Capability, + requirementDecl, + Diagnostics::seeDeclarationOf, + requirementDecl); + } + else if ( + checkCapabilityResult == + CheckCapabilityRequirementResult::RequiredIsMissingAbstractAtoms) + { + maybeDiagnose( + getSink(), + getOptionSet(), + DiagnosticCategory::Capability, + implDecl, + Diagnostics::requirmentHasSubsetOfAbstractAtomsToImplementation, + implDecl, failedAvailableCapabilityConjunction); + maybeDiagnose( + getSink(), + getOptionSet(), + DiagnosticCategory::Capability, + requirementDecl, + Diagnostics::seeDeclarationOf, + requirementDecl); } } } + + // validate that super-type is a super set of capabilities + CapabilityAtomSet failedAvailableCapabilityConjunction; + CheckCapabilityRequirementResult checkCapabilityResult; + CapabilitySet::checkCapabilityRequirement( + CheckCapabilityRequirementOptions::MustHaveEqualAbstractAtoms, + inheritanceDecl->inferredCapabilityRequirements, + inheritanceParentDecl->inferredCapabilityRequirements, + failedAvailableCapabilityConjunction, + checkCapabilityResult); + + if (checkCapabilityResult == + CheckCapabilityRequirementResult::AvailableIsNotASuperSetToRequired) + { + diagnoseUndeclaredCapability( + inheritanceParentDecl, + Diagnostics::useOfUndeclaredCapabilityOfInheritanceDecl, + failedAvailableCapabilityConjunction, + false); + maybeDiagnose( + getSink(), + getOptionSet(), + DiagnosticCategory::Capability, + inheritanceDecl->base, + Diagnostics::seeDeclarationOf, + inheritanceDecl->base); + } + else if ( + checkCapabilityResult == CheckCapabilityRequirementResult::RequiredIsMissingAbstractAtoms) + { + maybeDiagnose( + getSink(), + getOptionSet(), + DiagnosticCategory::Capability, + inheritanceParentDecl, + Diagnostics::subTypeHasSubsetOfAbstractAtomsToSuperType, + inheritanceParentDecl, + failedAvailableCapabilityConjunction); + maybeDiagnose( + getSink(), + getOptionSet(), + DiagnosticCategory::Capability, + inheritanceDecl->base, + Diagnostics::seeDeclarationOf, + inheritanceDecl->base); + } } DeclVisibility getDeclVisibility(Decl* decl) @@ -15044,13 +15145,11 @@ void diagnoseCapabilityProvenance( void SemanticsDeclCapabilityVisitor::diagnoseUndeclaredCapability( Decl* decl, const DiagnosticInfo& diagnosticInfo, - const CapabilityAtomSet& failedAtomsInsideAvailableSet) + const CapabilityAtomSet& failedAtomsInsideAvailableSet, + bool printProvenance) { if (decl->inferredCapabilityRequirements.isEmpty()) return; - if (failedAtomsInsideAvailableSet.isEmpty() || - failedAtomsInsideAvailableSet.contains((UInt)CapabilityAtom::Invalid)) - return; // There are two causes for why type checking failed on failedAvailableSet. // The first scenario is that failedAvailableSet defines a set of capabilities on a @@ -15075,7 +15174,7 @@ void SemanticsDeclCapabilityVisitor::diagnoseUndeclaredCapability( getSink(), this->getOptionSet(), DiagnosticCategory::Capability, - decl->loc, + decl, Diagnostics::declHasDependenciesNotCompatibleOnTarget, decl, outFailedAtom); @@ -15090,16 +15189,19 @@ void SemanticsDeclCapabilityVisitor::diagnoseUndeclaredCapability( getAtomSetOfTargets(), failedAtomSet); - HashSet printedDecls; - for (auto atom : targetsNotUsedSet) + if (printProvenance) { - CapabilityAtom formattedAtom = asAtom(atom); - diagnoseCapabilityProvenance( - this->getOptionSet(), - getSink(), - decl, - formattedAtom, - printedDecls); + HashSet printedDecls; + for (auto atom : targetsNotUsedSet) + { + CapabilityAtom formattedAtom = asAtom(atom); + diagnoseCapabilityProvenance( + this->getOptionSet(), + getSink(), + decl, + formattedAtom, + printedDecls); + } } return; } @@ -15130,7 +15232,7 @@ void SemanticsDeclCapabilityVisitor::diagnoseUndeclaredCapability( getSink(), this->getOptionSet(), DiagnosticCategory::Capability, - decl->loc, + decl, Diagnostics::declHasDependenciesNotCompatibleOnStage, decl, formattedAtom); @@ -15141,18 +15243,21 @@ void SemanticsDeclCapabilityVisitor::diagnoseUndeclaredCapability( getSink(), this->getOptionSet(), DiagnosticCategory::Capability, - decl->loc, + decl, diagnosticInfo, decl, formattedAtom); } - // Print provenances. - diagnoseCapabilityProvenance( - this->getOptionSet(), - getSink(), - decl, - formattedAtom, - printedDecls); + if (printProvenance) + { + // Print provenances. + diagnoseCapabilityProvenance( + this->getOptionSet(), + getSink(), + decl, + formattedAtom, + printedDecls); + } } } diff --git a/source/slang/slang-check-stmt.cpp b/source/slang/slang-check-stmt.cpp index 8c9e24d48..4c45db1d7 100644 --- a/source/slang/slang-check-stmt.cpp +++ b/source/slang/slang-check-stmt.cpp @@ -442,14 +442,14 @@ void SemanticsStmtVisitor::visitTargetSwitchStmt(TargetSwitchStmt* stmt) bool isStage = isStageAtom((CapabilityName)caseStmt->capability, canonicalStage); if (as(stmt)) { - if (!isStage && caseStmt->capability != 0) + if (!isStage && caseStmt->capability != (int32_t)CapabilityName::Invalid) { getSink()->diagnose( caseStmt->capabilityToken.loc, Diagnostics::unknownStageName, caseStmt->capabilityToken); } - caseStmt->capability = (int)canonicalStage; + caseStmt->capability = (int32_t)canonicalStage; } else { diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h index 8ec910f15..8d20483a4 100644 --- a/source/slang/slang-diagnostic-defs.h +++ b/source/slang/slang-diagnostic-defs.h @@ -1157,24 +1157,30 @@ DIAGNOSTIC( missingCapabilityRequirementOnPublicDecl, "public symbol '$0' is missing capability requirement declaration, the symbol is assumed to " "require inferred capabilities '$1'.") -DIAGNOSTIC(36104, Error, useOfUndeclaredCapability, "'$0' uses undeclared capability '$1'.") +DIAGNOSTIC(36104, Error, useOfUndeclaredCapability, "'$0' uses undeclared capability '$1'") DIAGNOSTIC( 36104, Error, useOfUndeclaredCapabilityOfInterfaceRequirement, - "'$0' uses capability '$1' that is missing from the interface requirement.") + "'$0' uses capability '$1' that is incompatable with the interface requirement") +DIAGNOSTIC( + 36104, + Error, + useOfUndeclaredCapabilityOfInheritanceDecl, + "'$0' uses capability '$1' that is incompatable with the supertype") DIAGNOSTIC(36105, Error, unknownCapability, "unknown capability name '$0'.") DIAGNOSTIC(36106, Error, expectCapability, "expect a capability name.") DIAGNOSTIC( 36107, Error, entryPointUsesUnavailableCapability, - "entrypoint '$0' uses features that are not available in '$2' stage for '$1' target.") + "entrypoint '$0' uses features that are not available in '$2' stage for '$1' compilation " + "target.") DIAGNOSTIC( 36108, Error, declHasDependenciesNotCompatibleOnTarget, - "'$0' has dependencies that are not compatible on the required target '$1'.") + "'$0' has dependencies that are not compatible on the required compilation target '$1'.") DIAGNOSTIC(36109, Error, invalidTargetSwitchCase, "'$0' cannot be used as a target_switch case.") DIAGNOSTIC( 36110, @@ -1211,7 +1217,18 @@ DIAGNOSTIC( 36117, Error, declHasDependenciesNotCompatibleOnStage, - "'$0' uses features that are not available in '$1' stage.") + "'$0' requires support for stage '$1', but stage is unsupported.") +DIAGNOSTIC( + 36118, + Error, + subTypeHasSubsetOfAbstractAtomsToSuperType, + "subtype '$0' must have the same target/stage support as the supertype; '$0' is missing '$1'") +DIAGNOSTIC( + 36118, + Error, + requirmentHasSubsetOfAbstractAtomsToImplementation, + "requirement '$0' must have the same target/stage support as the implementation; '$0' is " + "missing '$1'") // Attributes DIAGNOSTIC(31000, Warning, unknownAttributeName, "unknown attribute '$0'") diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp index 526e2f952..52d24ff7d 100644 --- a/source/slang/slang-lower-to-ir.cpp +++ b/source/slang/slang-lower-to-ir.cpp @@ -7186,7 +7186,7 @@ struct StmtLoweringVisitor : StmtVisitor if (!builder->getBlock()->getTerminator()) builder->emitBranch(breakLabel); } - if (targetCase->capability == 0) + if (targetCase->capability == (int32_t)CapabilityName::Invalid) { info.defaultLabel = caseBlock; } diff --git a/source/slang/slang-parser.cpp b/source/slang/slang-parser.cpp index 7e9740b53..1302975df 100644 --- a/source/slang/slang-parser.cpp +++ b/source/slang/slang-parser.cpp @@ -5754,7 +5754,7 @@ static Stmt* parseTargetSwitchStmtImpl(Parser* parser, TargetSwitchStmt* stmt) Diagnostics::unknownTargetName, caseName.getContent()); } - targetCase->capability = int32_t(cap); + targetCase->capability = (int32_t)cap; targetCase->capabilityToken = caseName; targetCase->loc = caseName.loc; targetCase->body = bodyStmt; @@ -8743,7 +8743,7 @@ Expr* Parser::ParseLeafExpression() /// Parse an argument to an application of a generic static Expr* _parseGenericArg(Parser* parser) { - // The grammar for generic arguments needs to be a super-set of the + // The grammar for generic arguments needs to be a superset of the // grammar for types and for expressions, because we do not know // which to expect at each argument position during parsing. // diff --git a/source/slang/slang.natvis b/source/slang/slang.natvis index 38ec85b62..3dd3ef1af 100644 --- a/source/slang/slang.natvis +++ b/source/slang/slang.natvis @@ -286,7 +286,8 @@ (Slang::ContinueStmt*)&astNodeType (Slang::ReturnStmt*)&astNodeType (Slang::ExpressionStmt*)&astNodeType - (Slang::Stmt*)this,! + (Slang::TargetSwitchStmt*)&astNodeType + (Slang::Stmt*)this,! diff --git a/tests/diagnostics/discard-in-rt.slang b/tests/diagnostics/discard-in-rt.slang index 93c3d1038..4af4430a4 100644 --- a/tests/diagnostics/discard-in-rt.slang +++ b/tests/diagnostics/discard-in-rt.slang @@ -1,6 +1,6 @@ //TEST:SIMPLE(filecheck=CHECK): -target spirv -// CHECK: 'closestHit' uses features that are not available in 'closesthit' stage. +// CHECK: error 36117:{{.*}}'closestHit'{{.*}}'closesthit' // CHECK: see using of 'discard' struct PrimaryRayPayload{} @@ -13,7 +13,7 @@ void closestHit( discard; } -// CHECK: 'closestHit1' uses features that are not available in 'closesthit' stage +// CHECK: error 36107:{{.*}}'closestHit1'{{.*}}'closesthit'{{.* }}'spirv' // CHECK: see using of 'discard' [shader("closesthit")] void closestHit1( diff --git a/tests/hlsl-intrinsic/wave-rotate/wave-rotate-clustered.slang b/tests/hlsl-intrinsic/wave-rotate/wave-rotate-clustered.slang index a8ca2d66f..81601e9be 100644 --- a/tests/hlsl-intrinsic/wave-rotate/wave-rotate-clustered.slang +++ b/tests/hlsl-intrinsic/wave-rotate/wave-rotate-clustered.slang @@ -1,8 +1,8 @@ //TEST_CATEGORY(wave, compute) //TEST:COMPARE_COMPUTE_EX(filecheck-buffer=CHECK):-vk -compute -shaderobj -emit-spirv-directly -//TEST:COMPARE_COMPUTE_EX(filecheck-buffer=CHECK):-vk -compute -shaderobj -emit-spirv-via-glsl -Xslang... -capability GL_KHR_shader_subgroup_rotate -X. +//TEST:COMPARE_COMPUTE_EX(filecheck-buffer=CHECK):-vk -compute -shaderobj -emit-spirv-via-glsl -profile sm_6_0 -Xslang... -capability GL_KHR_shader_subgroup_rotate -X. //TEST:COMPARE_COMPUTE_EX(filecheck-buffer=CHECK):-vk -compute -shaderobj -emit-spirv-directly -xslang -DUSE_GLSL_SYNTAX -allow-glsl -//TEST:COMPARE_COMPUTE_EX(filecheck-buffer=CHECK):-vk -compute -shaderobj -emit-spirv-via-glsl -allow-glsl -Xslang... -DUSE_GLSL_SYNTAX -capability GL_KHR_shader_subgroup_rotate -X. +//TEST:COMPARE_COMPUTE_EX(filecheck-buffer=CHECK):-vk -compute -shaderobj -emit-spirv-via-glsl -profile sm_6_0 -allow-glsl -Xslang... -DUSE_GLSL_SYNTAX -capability GL_KHR_shader_subgroup_rotate -X. #if defined(USE_GLSL_SYNTAX) #define __clusteredRotate subgroupClusteredRotate diff --git a/tests/hlsl-intrinsic/wave-rotate/wave-rotate.slang b/tests/hlsl-intrinsic/wave-rotate/wave-rotate.slang index 5dc319254..353afbb35 100644 --- a/tests/hlsl-intrinsic/wave-rotate/wave-rotate.slang +++ b/tests/hlsl-intrinsic/wave-rotate/wave-rotate.slang @@ -1,11 +1,11 @@ -//TEST_CATEGORY(wave, compute) -//TEST:COMPARE_COMPUTE_EX(filecheck-buffer=CHECK):-vk -compute -shaderobj -emit-spirv-directly -//TEST:COMPARE_COMPUTE_EX(filecheck-buffer=CHECK):-vk -compute -shaderobj -emit-spirv-via-glsl -Xslang... -capability GL_KHR_shader_subgroup_rotate -X. +// TEST_CATEGORY(wave, compute) +// TEST:COMPARE_COMPUTE_EX(filecheck-buffer=CHECK):-vk -compute -shaderobj -emit-spirv-directly +// TEST:COMPARE_COMPUTE_EX(filecheck-buffer=CHECK):-slang -compute -vk -shaderobj -emit-spirv-via-glsl -profile sm_6_0 -Xslang... -capability GL_KHR_shader_subgroup_rotate -X. //TEST:COMPARE_COMPUTE_EX(filecheck-buffer=CHECK):-metal -compute -shaderobj -xslang -DMETAL //TEST:COMPARE_COMPUTE_EX(filecheck-buffer=CHECK):-vk -compute -shaderobj -emit-spirv-directly -xslang -DUSE_GLSL_SYNTAX -allow-glsl -//TEST:COMPARE_COMPUTE_EX(filecheck-buffer=CHECK):-vk -compute -shaderobj -emit-spirv-via-glsl -allow-glsl -Xslang... -DUSE_GLSL_SYNTAX -capability GL_KHR_shader_subgroup_rotate -X. +//TEST:COMPARE_COMPUTE_EX(filecheck-buffer=CHECK):-slang -compute -vk -shaderobj -emit-spirv-via-glsl -allow-glsl -profile sm_6_0 -Xslang... -DUSE_GLSL_SYNTAX -capability GL_KHR_shader_subgroup_rotate -X. //TEST:COMPARE_COMPUTE_EX(filecheck-buffer=CHECK):-metal -compute -shaderobj -xslang -DMETAL -xslang -DUSE_GLSL_SYNTAX -allow-glsl diff --git a/tests/language-feature/capability/capability-inheritance-1.slang b/tests/language-feature/capability/capability-inheritance-1.slang new file mode 100644 index 000000000..b8104b350 --- /dev/null +++ b/tests/language-feature/capability/capability-inheritance-1.slang @@ -0,0 +1,77 @@ +//TEST:SIMPLE(filecheck=CHECK): -target hlsl -entry computeMain -stage compute + +// Test if InheritanceDecl fail to conform to parent capabilities + +// mismatch of target abstract atoms +[require(hlsl)] +interface IFoo0 +{ +} +[require(hlsl)] +[require(spirv)] +// CHECK-DAG: ([[# @LINE+1]]): error 36108{{.*}}spirv +struct Foo0 : IFoo0 +{ +} + +// mismatch of target abstract atoms +[require(hlsl)] +[require(spirv)] +interface IFoo1 +{ +} +[require(hlsl)] +// CHECK-DAG: ([[# @LINE+1]]): error 36118{{.*}}spirv +struct Foo1 : IFoo1 +{ +} + +// subtype is superset +[require(sm_4_0)] +interface IFoo2 +{ +} +[require(sm_5_0)] +// CHECK-DAG: ([[# @LINE+1]]): error 36104{{.*}}sm_5_0 +struct Foo2 : IFoo2 +{ +} + +// subtype is subset, no error +[require(sm_5_0)] +interface IFoo3 +{ +} +[require(sm_4_0)] +struct Foo3 : IFoo3 +{ +} + +// mismatch of stage abstract atoms +[require(hlsl, compute)] +interface IFoo4 +{ +} +[require(hlsl, fragment)] +// CHECK-DAG: ([[# @LINE+1]]): error 36118{{.*}}compute +struct Foo4 : IFoo4 +{ +} + +// mismatch of stage abstract atoms +[require(fragment)] +[require(vertex)] +interface IFoo5 +{ +} +[require(fragment)] +// CHECK-DAG: ([[# @LINE+1]]): error 36118{{.*}}vertex +struct Foo5 : IFoo5 +{ +} + +[require(hlsl)] +[numthreads(1,1,1)] +void computeMain() +{ +} diff --git a/tests/language-feature/capability/capability-interface-requirement-1.slang b/tests/language-feature/capability/capability-interface-requirement-1.slang new file mode 100644 index 000000000..687ea51c9 --- /dev/null +++ b/tests/language-feature/capability/capability-interface-requirement-1.slang @@ -0,0 +1,56 @@ +//TEST:SIMPLE(filecheck=CHECK): -target hlsl -entry computeMain -stage compute + +// Test for requirment/implementation conformance given mismatch + +public interface IAtomicAddable_Error1 +{ + [require(sm_5_0, glsl)] + [require(sm_5_0, hlsl)] + public void atomicAdd(RWByteAddressBuffer buf, uint addr, int64_t value); +} +public struct AtomicAddable_Error1 : IAtomicAddable_Error1 +{ + [require(sm_5_0, glsl)] + // CHECK: ([[# @LINE+1]]): error 36118: {{.*}}hlsl + public void atomicAdd(RWByteAddressBuffer buf, uint addr, int64_t value) { } +} + +// ([[# @LINE-9]]): note: see declaration of 'atomicAdd' + +public interface IAtomicAddable_Error2 +{ + [require(sm_5_0, glsl)] + public void atomicAdd(RWByteAddressBuffer buf, uint addr, int64_t value); +} +public struct AtomicAddable_Error2 : IAtomicAddable_Error2 +{ + [require(sm_5_0, glsl)] + [require(sm_5_0, hlsl)] + // CHECK: ([[# @LINE+1]]): error 36108: {{.*}}hlsl + public void atomicAdd(RWByteAddressBuffer buf, uint addr, int64_t value) { } +} + +// ([[# @LINE-9]]): note: see declaration of 'atomicAdd' + +// Test that we do not error (should be implicitly inferring capabilities) +// CHECK-NOT: error + +public interface IAtomicAddable3 +{ + public void atomicAdd(RWByteAddressBuffer buf, uint addr, int value); +} + +public struct AtomicAddable_Error4 : IAtomicAddable3 +{ + public void atomicAdd(RWByteAddressBuffer buf, uint addr, int value) { buf.InterlockedAdd(addr, value); } +} + +public struct AtomicAddable_Error5 : IAtomicAddable3 +{ + public void atomicAdd(RWByteAddressBuffer buf, uint addr, int value) { buf.InterlockedAddI64(addr, value); } +} + +[numthreads(1,1,1)] +void computeMain() +{ +} diff --git a/tests/language-feature/capability/capability2.slang b/tests/language-feature/capability/capability2.slang index 6125c21d3..354b87dd5 100644 --- a/tests/language-feature/capability/capability2.slang +++ b/tests/language-feature/capability/capability2.slang @@ -12,6 +12,11 @@ interface IFoo void method2(); } +interface IFoo2 +{ + void method3(); +} + [require(spvGroupNonUniformArithmetic)] void useNonUniformArithmetic() {} @@ -39,20 +44,32 @@ struct Impl1 : IFoo } } -struct Impl2 : IFoo +struct Impl2 : IFoo, IFoo2 { - // CHECK: error 36104: {{.*}}spvGroupNonUniformArithmetic + // error here because explicit requirement is on `method1` + // CHECK: error 36104:{{.*}}spvGroupNonUniformArithmetic void method1() { - useRayQueryKHR(); // OK. - useNonUniformArithmetic(); // error. + useRayQueryKHR(); + useNonUniformArithmetic(); } + // error here because capabilities are explicitly tagged on + // the requirement parent `IFoo` // CHECK: error 36104: {{.*}}spvGroupNonUniformArithmetic void method2() { useAtomicFloat16(); - useNonUniformArithmetic(); // error. + useNonUniformArithmetic(); + } + + // do not error here because capabilities are not explicitly tagged + // on the requirement parent `IFoo2` or requirment method + // CHECK-NOT: error + void method3() + { + useAtomicFloat16(); + useNonUniformArithmetic(); } } diff --git a/tests/language-feature/capability/capabilitySimplification1.slang b/tests/language-feature/capability/capabilitySimplification1.slang index 440ac1ced..0e6303412 100644 --- a/tests/language-feature/capability/capabilitySimplification1.slang +++ b/tests/language-feature/capability/capabilitySimplification1.slang @@ -1,24 +1,13 @@ //TEST:SIMPLE(filecheck=CHECK): -target glsl -entry computeMain -stage compute -profile sm_5_0 //TEST:SIMPLE(filecheck=CHECK_IGNORE_CAPS): -target glsl -emit-spirv-directly -entry computeMain -stage compute -profile sm_5_0 -ignore-capabilities - // CHECK_IGNORE_CAPS-NOT: error 36107 -// CHECK: error 36107 -// CHECK-SAME: entrypoint 'computeMain' uses features that are not available in 'compute' stage for 'glsl' target. -// CHECK: capabilitySimplification1.slang(21): note: see using of 'WaveMultiPrefixCountBits' -// CHECK-NOT: see using of 'WaveMultiPrefixCountBits' -// CHECK: {{.*}}.meta.slang({{.*}}): note: see definition of 'WaveMultiPrefixCountBits' -// CHECK: {{.*}}.meta.slang({{.*}}): note: see declaration of 'require' - -void nestedSafeCall() -{ - AllMemoryBarrier(); -} - -void nestedBadCall() +[numthreads(1, 1, 1)] +// CHECK: ([[# @LINE+1]]): error 36107: {{.*}}computeMain{{.*}}compute{{.*}}glsl +void computeMain() { - WaveMultiPrefixCountBits(true, 0); + nestedCall(); } void nestedCall() @@ -27,8 +16,13 @@ void nestedCall() nestedBadCall(); } -[numthreads(1,1,1)] -void computeMain() +void nestedSafeCall() { - nestedCall(); + AllMemoryBarrier(); } + +void nestedBadCall() +{ + // CHECK: ([[# @LINE+1]]): note: see using of 'WaveMultiPrefixCountBits' + WaveMultiPrefixCountBits(true, 0); +} \ No newline at end of file diff --git a/tests/language-feature/capability/capabilitySimplification3.slang b/tests/language-feature/capability/capabilitySimplification3.slang deleted file mode 100644 index 150f926a9..000000000 --- a/tests/language-feature/capability/capabilitySimplification3.slang +++ /dev/null @@ -1,17 +0,0 @@ -//TEST:SIMPLE(filecheck=CHECK): -target glsl -entry computeMain -stage compute -profile sm_5_0 -//TEST:SIMPLE(filecheck=CHECK_IGNORE_CAPS): -target glsl -emit-spirv-directly -entry computeMain -stage compute -profile sm_5_0 -ignore-capabilities - - -// CHECK_IGNORE_CAPS-NOT: error 36107 - -// CHECK: error 36107: entrypoint 'computeMain' uses features that are not available in 'compute' stage for 'glsl' target. -// CHECK: capabilitySimplification3.slang(16): note: see using of 'WaveMultiPrefixCountBits' -// CHECK-NOT: see using of 'WaveMultiPrefixCountBits' -// CHECK: {{.*}}.meta.slang({{.*}}): note: see definition of 'WaveMultiPrefixCountBits' -// CHECK: {{.*}}.meta.slang({{.*}}): note: see declaration of 'require' - -[numthreads(1,1,1)] -void computeMain() -{ - WaveMultiPrefixCountBits(true, 0); -} diff --git a/tests/language-feature/capability/explicit-shader-stage-2.slang b/tests/language-feature/capability/explicit-shader-stage-2.slang index a01cff7c2..8ef3e158c 100644 --- a/tests/language-feature/capability/explicit-shader-stage-2.slang +++ b/tests/language-feature/capability/explicit-shader-stage-2.slang @@ -1,7 +1,7 @@ //TEST:SIMPLE(filecheck=CHECK): -target hlsl -entry main -allow-glsl -profile sm_5_0 //TEST:SIMPLE(filecheck=CHECK_IGNORE_CAPS): -target hlsl -entry main -allow-glsl -profile sm_5_0 -ignore-capabilities -//CHECK: error 36107: entrypoint 'main' uses features that are not available in 'fragment' stage for 'hlsl' target. +// CHECK: error 36107: entrypoint 'main' uses features that are not available in 'fragment' stage for 'hlsl' //CHECK_IGNORE_CAPS-NOT: error 36100 [shader("fragment")] float4 main() -- cgit v1.2.3