summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEllie Hermaszewska <ellieh@nvidia.com>2025-09-04 04:05:26 +0800
committerGitHub <noreply@github.com>2025-09-03 20:05:26 +0000
commita766d27447aa0fcf69334c0467d9b1124892e180 (patch)
tree67ca5615e4a8c94d7454ee43375eeffc8c8a7d4c
parentbf607e2f3fa183e9a2b18c7a98438a05247d6ed3 (diff)
Diagnose on structured buffers containing resources (#8222)
closes https://github.com/shader-slang/slang/issues/3313
-rw-r--r--source/slang/slang-ast-base.h14
-rw-r--r--source/slang/slang-check-decl.cpp105
-rw-r--r--source/slang/slang-check-impl.h15
-rw-r--r--source/slang/slang-diagnostic-defs.h12
-rw-r--r--source/slang/slang-emit.cpp3
-rw-r--r--source/slang/slang-ir-validate.cpp145
-rw-r--r--source/slang/slang-ir-validate.h5
-rw-r--r--source/slang/slang-ir.cpp9
-rw-r--r--source/slang/slang-ir.h1
-rw-r--r--tests/diagnostics/recursive-type.slang8
-rw-r--r--tests/diagnostics/structuredbuffer-resource-static.slang53
-rw-r--r--tests/diagnostics/structuredbuffer-resource-struct-array.slang27
-rw-r--r--tests/diagnostics/structuredbuffer-resource-struct-recursive-mutual.slang34
-rw-r--r--tests/diagnostics/structuredbuffer-resource-struct-recursive.slang21
-rw-r--r--tests/diagnostics/structuredbuffer-resource-struct.slang46
-rw-r--r--tests/diagnostics/structuredbuffer-resource.slang28
16 files changed, 510 insertions, 16 deletions
diff --git a/source/slang/slang-ast-base.h b/source/slang/slang-ast-base.h
index dbbfe6b92..828bbdb5c 100644
--- a/source/slang/slang-ast-base.h
+++ b/source/slang/slang-ast-base.h
@@ -598,6 +598,20 @@ protected:
ASTBuilder* m_astBuilderForReflection;
};
+struct TypePair
+{
+ Type* type0;
+ Type* type1;
+ HashCode getHashCode() const
+ {
+ return combineHash(Slang::getHashCode(type0), Slang::getHashCode(type1));
+ }
+ bool operator==(const TypePair& other) const
+ {
+ return type0 == other.type0 && type1 == other.type1;
+ }
+};
+
template<typename T>
SLANG_FORCE_INLINE T* as(Type* obj)
{
diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp
index e59cf6ad5..4711eaddd 100644
--- a/source/slang/slang-check-decl.cpp
+++ b/source/slang/slang-check-decl.cpp
@@ -2698,6 +2698,8 @@ static Expr* constructDefaultInitExprForType(SemanticsVisitor* visitor, VarDeclB
}
}
+void validateStructuredBufferElementType(SemanticsVisitor* visitor, VarDeclBase* varDecl);
+
void SemanticsDeclBodyVisitor::checkVarDeclCommon(VarDeclBase* varDecl)
{
DiagnoseIsAllowedInitExpr(varDecl, getSink());
@@ -2892,6 +2894,9 @@ void SemanticsDeclBodyVisitor::checkVarDeclCommon(VarDeclBase* varDecl)
}
}
}
+
+ validateStructuredBufferElementType(this, varDecl);
+
bool isGlobalOrLocalVar = !isGlobalShaderParameter(varDecl) && !as<ParamDecl>(varDecl) &&
(!parentDecl || isEffectivelyStatic(varDecl));
if (isGlobalOrLocalVar)
@@ -15018,6 +15023,106 @@ bool isOpaqueHandleType(Type* type)
return false;
}
+bool containsRecursiveTypeImpl(SemanticsVisitor* visitor, Type* type, HashSet<Decl*>& currentPath)
+{
+ // Skip modified types (const, etc.)
+ while (auto modifiedType = as<ModifiedType>(type))
+ type = modifiedType->getBase();
+
+ // Check if this is a StructuredBuffer type and look inside it
+ if (auto structuredBufferType = as<HLSLStructuredBufferTypeBase>(type))
+ {
+ return containsRecursiveTypeImpl(
+ visitor,
+ structuredBufferType->getElementType(),
+ currentPath);
+ }
+
+ // Check if this is an array type and look inside it
+ if (auto arrayType = as<ArrayExpressionType>(type))
+ {
+ return containsRecursiveTypeImpl(visitor, arrayType->getElementType(), currentPath);
+ }
+
+ if (auto declRefType = as<DeclRefType>(type))
+ {
+ auto typeDecl = declRefType->getDeclRef().getDecl();
+
+ // Check global cache first - if we've already fully analyzed this type, use that result
+ auto shared = visitor->getShared();
+ if (auto cachedResult = shared->m_typeContainsRecursionCache.tryGetValue(typeDecl))
+ {
+ return *cachedResult;
+ }
+
+ // If we're currently exploring this type, we found a cycle!
+ if (currentPath.contains(typeDecl))
+ {
+ return true;
+ }
+
+ // Add to current exploration path
+ currentPath.add(typeDecl);
+
+ bool hasRecursion = false;
+
+ // Check members if it's an aggregate type
+ if (auto aggTypeDecl = declRefType->getDeclRef().as<AggTypeDecl>())
+ {
+ for (auto member : aggTypeDecl.getDecl()->getMembersOfType<VarDeclBase>())
+ {
+ if (isEffectivelyStatic(member))
+ continue;
+
+ if (containsRecursiveTypeImpl(visitor, member->getType(), currentPath))
+ {
+ hasRecursion = true;
+ break;
+ }
+ }
+ }
+
+ // Remove from current exploration path
+ currentPath.remove(typeDecl);
+
+ // Cache the result globally
+ shared->m_typeContainsRecursionCache[typeDecl] = hasRecursion;
+
+ return hasRecursion;
+ }
+
+ return false;
+}
+
+bool containsRecursiveType(SemanticsVisitor* visitor, Type* type)
+{
+ HashSet<Decl*> currentPath;
+ return containsRecursiveTypeImpl(visitor, type, currentPath);
+}
+
+void validateStructuredBufferElementType(SemanticsVisitor* visitor, VarDeclBase* varDecl)
+{
+ auto type = unwrapArrayType(varDecl->getType());
+
+ // Check if this is a StructuredBuffer type
+ auto structuredBufferType = as<HLSLStructuredBufferTypeBase>(type);
+
+ if (!structuredBufferType)
+ return;
+
+ // Get the element type
+ auto elementType = structuredBufferType->getElementType();
+
+ // Check if the element type contains recursive references
+ if (containsRecursiveType(visitor, elementType))
+ {
+ visitor->getSink()->diagnose(
+ varDecl->loc,
+ Diagnostics::recursiveTypesFoundInStructuredBuffer,
+ elementType);
+ }
+}
+
void diagnoseMissingCapabilityProvenance(
CompilerOptionSet& optionSet,
DiagnosticSink* sink,
diff --git a/source/slang/slang-check-impl.h b/source/slang/slang-check-impl.h
index e6c66ddd3..d82bf4427 100644
--- a/source/slang/slang-check-impl.h
+++ b/source/slang/slang-check-impl.h
@@ -699,6 +699,8 @@ struct SharedSemanticsContext : public RefObject
GLSLBindingOffsetTracker m_glslBindingOffsetTracker;
+ Dictionary<Decl*, bool> m_typeContainsRecursionCache;
+
public:
SharedSemanticsContext(
Linkage* linkage,
@@ -919,19 +921,6 @@ private:
FacetList baseFacets,
FacetList::Builder& ioMergedFacets);
- struct TypePair
- {
- Type* type0;
- Type* type1;
- HashCode getHashCode() const
- {
- return combineHash(Slang::getHashCode(type0), Slang::getHashCode(type1));
- }
- bool operator==(const TypePair& other) const
- {
- return type0 == other.type0 && type1 == other.type1;
- }
- };
Dictionary<Type*, InheritanceInfo> m_mapTypeToInheritanceInfo;
Dictionary<DeclRef<Decl>, InheritanceInfo> m_mapDeclRefToInheritanceInfo;
Dictionary<TypePair, SubtypeWitness*> m_mapTypePairToSubtypeWitness;
diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h
index a30b5f362..2febd317e 100644
--- a/source/slang/slang-diagnostic-defs.h
+++ b/source/slang/slang-diagnostic-defs.h
@@ -2254,6 +2254,18 @@ DIAGNOSTIC(
vectorWithInvalidElementCountEncountered,
"vector has invalid element count '$0', valid values are between '$1' and '$2' inclusive")
+DIAGNOSTIC(
+ 38204,
+ Error,
+ cannotUseResourceTypeInStructuredBuffer,
+ "StructuredBuffer element type '$0' cannot contain resource or opaque handle types")
+
+DIAGNOSTIC(
+ 38205,
+ Error,
+ recursiveTypesFoundInStructuredBuffer,
+ "structured buffer element type '$0' contains recursive type references")
+
// 39xxx - Type layout and parameter binding.
DIAGNOSTIC(
diff --git a/source/slang/slang-emit.cpp b/source/slang/slang-emit.cpp
index 7d8f1438d..f5b818c5d 100644
--- a/source/slang/slang-emit.cpp
+++ b/source/slang/slang-emit.cpp
@@ -1304,6 +1304,9 @@ Result linkAndOptimizeIR(
#endif
validateIRModuleIfEnabled(codeGenContext, irModule);
+ if (!validateStructuredBufferResourceTypes(irModule, sink, targetRequest))
+ return SLANG_FAIL;
+
// Many of our target languages and/or downstream compilers
// don't support `struct` types that have resource-type fields.
// In order to work around this limitation, we will rewrite the
diff --git a/source/slang/slang-ir-validate.cpp b/source/slang/slang-ir-validate.cpp
index 156fe249f..55f3ad227 100644
--- a/source/slang/slang-ir-validate.cpp
+++ b/source/slang/slang-ir-validate.cpp
@@ -1,6 +1,7 @@
// slang-ir-validate.cpp
#include "slang-ir-validate.h"
+#include "slang-compiler.h"
#include "slang-ir-dominators.h"
#include "slang-ir-insts.h"
#include "slang-ir-util.h"
@@ -25,6 +26,31 @@ struct IRValidateContext
HashSet<IRInst*> seenInsts;
};
+// Context class for structured buffer validation
+class StructuredBufferValidationContext
+{
+public:
+ StructuredBufferValidationContext(DiagnosticSink* sink, TargetRequest* targetRequest)
+ : m_sink(sink), m_targetRequest(targetRequest), m_hasErrors(false)
+ {
+ }
+
+ bool validate(IRModule* module);
+
+private:
+ DiagnosticSink* m_sink;
+ TargetRequest* m_targetRequest;
+ bool m_hasErrors;
+
+ // Cache of types we've already checked for containing opaque handles
+ HashSet<IRType*> m_checkedTypes;
+ HashSet<IRType*> m_typesWithOpaqueHandles;
+
+ bool containsOpaqueHandleTypeCached(IRType* type);
+ bool containsOpaqueHandleTypeInternal(IRType* type, HashSet<IRType*>& visitedInCurrentCheck);
+ void validateStructuredBufferVariable(IRInst* inst);
+};
+
void validateIRInst(IRValidateContext* context, IRInst* inst);
void validate(IRValidateContext* context, bool condition, IRInst* inst, char const* message)
@@ -624,4 +650,123 @@ void validateVectorsAndMatrices(
}
}
+//
+// Structure buffer resource types
+//
+
+bool StructuredBufferValidationContext::containsOpaqueHandleTypeCached(IRType* type)
+{
+ // Check cache first
+ if (m_checkedTypes.contains(type))
+ {
+ return m_typesWithOpaqueHandles.contains(type);
+ }
+
+ // Not in cache, need to check
+ HashSet<IRType*> visitedInCurrentCheck;
+ bool result = containsOpaqueHandleTypeInternal(type, visitedInCurrentCheck);
+
+ // Cache the result
+ m_checkedTypes.add(type);
+ if (result)
+ {
+ m_typesWithOpaqueHandles.add(type);
+ }
+
+ return result;
+}
+
+bool StructuredBufferValidationContext::containsOpaqueHandleTypeInternal(
+ IRType* type,
+ HashSet<IRType*>& visitedInCurrentCheck)
+{
+ // Prevent infinite recursion in current check
+ if (!visitedInCurrentCheck.add(type))
+ return false;
+
+ // Check if the type itself is an opaque handle
+ if (isResourceType(type))
+ return true;
+
+ // Check struct types
+ if (auto structType = as<IRStructType>(type))
+ {
+ for (auto field : structType->getFields())
+ {
+ if (containsOpaqueHandleTypeInternal(field->getFieldType(), visitedInCurrentCheck))
+ return true;
+ }
+ }
+ else if (auto arrayType = as<IRArrayTypeBase>(type))
+ {
+ return containsOpaqueHandleTypeInternal(arrayType->getElementType(), visitedInCurrentCheck);
+ }
+ else if (auto ptrType = as<IRPtrTypeBase>(type))
+ {
+ return containsOpaqueHandleTypeInternal(ptrType->getValueType(), visitedInCurrentCheck);
+ }
+
+ return false;
+}
+
+void StructuredBufferValidationContext::validateStructuredBufferVariable(IRInst* inst)
+{
+ IRType* type = inst->getDataType();
+
+ // Unwrap arrays if present
+ type = unwrapArrayAndPointers(type);
+
+ // Check if this is a structured buffer type
+ auto structuredBufferType = as<IRHLSLStructuredBufferTypeBase>(type);
+ if (!structuredBufferType)
+ return;
+
+ // Get the element type
+ auto elementType = structuredBufferType->getElementType();
+
+ // Check if the element type contains any resource/opaque handle types
+ if (containsOpaqueHandleTypeCached(elementType))
+ {
+ m_sink->diagnose(
+ inst->sourceLoc,
+ Diagnostics::cannotUseResourceTypeInStructuredBuffer,
+ elementType);
+ m_hasErrors = true;
+ }
+}
+
+bool StructuredBufferValidationContext::validate(IRModule* module)
+{
+ // Skip validation if bindless is enabled for this target
+ if (m_targetRequest && areResourceTypesBindlessOnTarget(m_targetRequest))
+ return true;
+
+ // Iterate through all global instructions
+ for (auto globalInst : module->getGlobalInsts())
+ {
+ if (auto globalVar = as<IRGlobalParam>(globalInst))
+ {
+ validateStructuredBufferVariable(globalVar);
+ }
+ else if (auto func = as<IRFunc>(globalInst))
+ {
+ for (auto param : func->getParams())
+ {
+ validateStructuredBufferVariable(param);
+ }
+ }
+ }
+
+ return !m_hasErrors;
+}
+
+bool validateStructuredBufferResourceTypes(
+ IRModule* module,
+ DiagnosticSink* sink,
+ TargetRequest* targetRequest)
+{
+ StructuredBufferValidationContext context(sink, targetRequest);
+ return context.validate(module);
+}
+
} // namespace Slang
diff --git a/source/slang/slang-ir-validate.h b/source/slang/slang-ir-validate.h
index 7fc882f37..950e1b765 100644
--- a/source/slang/slang-ir-validate.h
+++ b/source/slang/slang-ir-validate.h
@@ -85,4 +85,9 @@ void validateVectorsAndMatrices(
DiagnosticSink* sink,
TargetRequest* targetRequest);
+bool validateStructuredBufferResourceTypes(
+ IRModule* module,
+ DiagnosticSink* sink,
+ TargetRequest* targetRequest);
+
} // namespace Slang
diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp
index ab59112f3..c63ca2bec 100644
--- a/source/slang/slang-ir.cpp
+++ b/source/slang/slang-ir.cpp
@@ -8781,6 +8781,15 @@ IRType* unwrapArray(IRType* type)
return t;
}
+IRType* unwrapArrayAndPointers(IRType* type)
+{
+ if (const auto a = as<IRArrayTypeBase>(type))
+ return unwrapArrayAndPointers(a->getElementType());
+ if (const auto p = as<IRPtrTypeBase>(type))
+ return unwrapArrayAndPointers(p->getValueType());
+ return type;
+}
+
//
// IRTargetIntrinsicDecoration
//
diff --git a/source/slang/slang-ir.h b/source/slang/slang-ir.h
index d8fe51ddf..69a000b81 100644
--- a/source/slang/slang-ir.h
+++ b/source/slang/slang-ir.h
@@ -961,6 +961,7 @@ struct IRType : IRInst
};
IRType* unwrapArray(IRType* type);
+IRType* unwrapArrayAndPointers(IRType* type);
FIDDLE()
struct IRBasicType : IRType
diff --git a/tests/diagnostics/recursive-type.slang b/tests/diagnostics/recursive-type.slang
index 90f49aa86..ccf7da1f8 100644
--- a/tests/diagnostics/recursive-type.slang
+++ b/tests/diagnostics/recursive-type.slang
@@ -6,9 +6,11 @@ struct Outer {
Outer next; // non-pointer
int y;
};
-RWStructuredBuffer<Outer> Buf;
+RWStructuredBuffer<int> Buf;
[numthreads(1,1,1)]
void csmain() {
- Buf[0].y = 0;
-} \ No newline at end of file
+ Outer outer;
+ outer.y = 0;
+ Buf[0] = outer.y;
+}
diff --git a/tests/diagnostics/structuredbuffer-resource-static.slang b/tests/diagnostics/structuredbuffer-resource-static.slang
new file mode 100644
index 000000000..12cbe492e
--- /dev/null
+++ b/tests/diagnostics/structuredbuffer-resource-static.slang
@@ -0,0 +1,53 @@
+//DIAGNOSTIC_TEST:SIMPLE(filecheck=CHECK): -spirv
+
+struct WithStaticTexture
+{
+ float4 color;
+ static Texture2D tex;
+ float scale;
+}
+
+struct WithStaticSampler
+{
+ static SamplerState sampler;
+ float2 uv;
+}
+
+struct NestedWithStatic
+{
+ WithStaticTexture data;
+ float value;
+}
+
+// These should NOT produce error 38204 because resources are static
+//CHECK-NOT: error 38204
+StructuredBuffer<WithStaticTexture> bufferWithStaticTexture;
+
+//CHECK-NOT: error 38204
+StructuredBuffer<WithStaticSampler> bufferWithStaticSampler;
+
+//CHECK-NOT: error 38204
+StructuredBuffer<NestedWithStatic> bufferNestedStatic;
+
+RWStructuredBuffer<float4> output;
+
+Texture2D tex;
+SamplerState sampler;
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ uint i = dispatchThreadID.x;
+
+ WithStaticTexture.tex = tex;
+ WithStaticSampler.sampler = sampler;
+
+ // Use all non-static members and static resources
+ float4 result = bufferWithStaticTexture[i].color * bufferWithStaticTexture[i].scale
+ + WithStaticTexture.tex.Sample(WithStaticSampler.sampler, bufferWithStaticSampler[i].uv)
+ + bufferNestedStatic[i].data.color * bufferNestedStatic[i].data.scale
+ + float4(bufferNestedStatic[i].value, 0, 0, 0);
+
+ output[i] = result;
+}
+
diff --git a/tests/diagnostics/structuredbuffer-resource-struct-array.slang b/tests/diagnostics/structuredbuffer-resource-struct-array.slang
new file mode 100644
index 000000000..891a63b07
--- /dev/null
+++ b/tests/diagnostics/structuredbuffer-resource-struct-array.slang
@@ -0,0 +1,27 @@
+//DIAGNOSTIC_TEST:SIMPLE(filecheck=CHECK): -target spirv
+
+struct HasResource
+{
+ Texture2D tex;
+}
+
+//CHECK-DAG: ([[# @LINE+1]]): error 38204
+StructuredBuffer<HasResource> bufferArray[4];
+
+//CHECK-DAG: ([[# @LINE+1]]): error 38204
+RWStructuredBuffer<Texture2D> rwBufferArray[2];
+
+SamplerState sampler;
+RWStructuredBuffer<float> output;
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ uint i = dispatchThreadID.x;
+
+ // Force usage of all array elements and struct members
+ float result = bufferArray[i % 4][i].tex.Sample(sampler, float2(0, 0)).x;
+ rwBufferArray[i % 2][i] = bufferArray[0][i].tex;
+ output[i] = result;
+}
+
diff --git a/tests/diagnostics/structuredbuffer-resource-struct-recursive-mutual.slang b/tests/diagnostics/structuredbuffer-resource-struct-recursive-mutual.slang
new file mode 100644
index 000000000..ac9449965
--- /dev/null
+++ b/tests/diagnostics/structuredbuffer-resource-struct-recursive-mutual.slang
@@ -0,0 +1,34 @@
+//DIAGNOSTIC_TEST:SIMPLE(filecheck=CHECK): -target spirv
+
+struct MutualB
+{
+ //CHECK-DAG: ([[# @LINE+1]]): error 38205
+ StructuredBuffer<MutualA> aBuffer;
+}
+
+struct MutualA
+{
+ //CHECK-DAG: ([[# @LINE+1]]): error 38205
+ StructuredBuffer<MutualB> bBuffer;
+}
+
+StructuredBuffer<MutualA> mutualRoot;
+RWStructuredBuffer<float> output;
+
+// External function to force consumption of recursive types
+float consumeMutual(MutualA a, MutualB b);
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ uint i = dispatchThreadID.x;
+
+ float result = 0;
+ // Force usage of mutual recursion
+ MutualA a = mutualRoot[i];
+ MutualB b = a.bBuffer[0];
+ result += consumeMutual(a, b);
+
+ output[i] = result;
+}
+
diff --git a/tests/diagnostics/structuredbuffer-resource-struct-recursive.slang b/tests/diagnostics/structuredbuffer-resource-struct-recursive.slang
new file mode 100644
index 000000000..ae8f916dd
--- /dev/null
+++ b/tests/diagnostics/structuredbuffer-resource-struct-recursive.slang
@@ -0,0 +1,21 @@
+//DIAGNOSTIC_TEST:SIMPLE(filecheck=CHECK): -target spirv
+
+struct RecursiveFoo
+{
+ float value;
+ //CHECK-DAG: ([[# @LINE+1]]): error 38205
+ StructuredBuffer<RecursiveFoo> children;
+}
+
+StructuredBuffer<RecursiveFoo> recursiveRoot;
+RWStructuredBuffer<float> output;
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ uint i = dispatchThreadID.x;
+ RecursiveFoo foo = recursiveRoot[i];
+ float result = foo.value + foo.children[0].value + foo.children[0].children[0].value;
+ output[i] = result;
+}
+
diff --git a/tests/diagnostics/structuredbuffer-resource-struct.slang b/tests/diagnostics/structuredbuffer-resource-struct.slang
new file mode 100644
index 000000000..8654b0ad6
--- /dev/null
+++ b/tests/diagnostics/structuredbuffer-resource-struct.slang
@@ -0,0 +1,46 @@
+//DIAGNOSTIC_TEST:SIMPLE(filecheck=CHECK): -target spirv
+
+struct WithTexture
+{
+ float4 color;
+ Texture2D tex;
+ float scale;
+}
+
+struct WithSampler
+{
+ SamplerState sampler;
+ float2 uv;
+}
+
+struct Nested
+{
+ WithTexture data;
+ float value;
+}
+
+//CHECK-DAG: ([[# @LINE+1]]): error 38204
+StructuredBuffer<WithTexture> bufferWithTexture;
+
+//CHECK-DAG: ([[# @LINE+1]]): error 38204
+StructuredBuffer<WithSampler> bufferWithSampler;
+
+//CHECK-DAG: ([[# @LINE+1]]): error 38204
+StructuredBuffer<Nested> bufferNested;
+
+RWStructuredBuffer<float4> output;
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ uint i = dispatchThreadID.x;
+
+ // Use all struct members from all buffers
+ float4 result = bufferWithTexture[i].color * bufferWithTexture[i].scale
+ + bufferWithTexture[i].tex.Sample(bufferWithSampler[i].sampler, bufferWithSampler[i].uv)
+ + bufferNested[i].data.tex.Sample(bufferWithSampler[0].sampler, float2(0, 0)) * bufferNested[i].data.scale
+ + float4(bufferNested[i].value, 0, 0, 0);
+
+ output[i] = result;
+}
+
diff --git a/tests/diagnostics/structuredbuffer-resource.slang b/tests/diagnostics/structuredbuffer-resource.slang
new file mode 100644
index 000000000..3f30f0102
--- /dev/null
+++ b/tests/diagnostics/structuredbuffer-resource.slang
@@ -0,0 +1,28 @@
+//DIAGNOSTIC_TEST:SIMPLE(filecheck=CHECK): -target spirv
+
+// Direct resource types as StructuredBuffer elements
+
+//CHECK-DAG: ([[# @LINE+1]]): error 38204
+StructuredBuffer<Texture2D<float>> textureBuffer;
+
+//CHECK-DAG: ([[# @LINE+1]]): error 38204
+StructuredBuffer<SamplerState> samplerBuffer;
+
+//CHECK-DAG: ([[# @LINE+1]]): error 38204
+StructuredBuffer<StructuredBuffer<float>> nestedBuffer;
+
+//CHECK-DAG: ([[# @LINE+1]]): error 38204
+RWStructuredBuffer<Texture2D<float>> rwTextureBuffer;
+
+RWStructuredBuffer<float> output;
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ uint i = dispatchThreadID.x;
+ float result = textureBuffer[i].Sample(samplerBuffer[i], float2(0, 0)).x
+ + nestedBuffer[i][0];
+
+ rwTextureBuffer[i] = textureBuffer[i];
+ output[i] = result;
+}