diff options
| -rw-r--r-- | source/slang/slang-diagnostic-defs.h | 19 | ||||
| -rw-r--r-- | source/slang/slang-emit.cpp | 3 | ||||
| -rw-r--r-- | source/slang/slang-ir-detect-uninitialized-resources.cpp | 237 | ||||
| -rw-r--r-- | source/slang/slang-ir-detect-uninitialized-resources.h | 9 | ||||
| -rw-r--r-- | tests/diagnostics/uninitialized-resource-field.slang | 26 |
5 files changed, 294 insertions, 0 deletions
diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h index 07433df5c..5544329a5 100644 --- a/source/slang/slang-diagnostic-defs.h +++ b/source/slang/slang-diagnostic-defs.h @@ -43,6 +43,7 @@ DIAGNOSTIC( "parameter type '$0'.") DIAGNOSTIC(-1, Note, noteShaderIsTargetingPipeine, "shader '$0' is targeting pipeline '$1'") DIAGNOSTIC(-1, Note, seeDefinitionOf, "see definition of '$0'") +DIAGNOSTIC(-1, Note, seeDefinitionOfStruct, "see definition of struct '$0'") DIAGNOSTIC(-1, Note, seeConstantBufferDefinition, "see constant buffer definition.") DIAGNOSTIC(-1, Note, seeInterfaceDefinitionOf, "see interface definition of '$0'") DIAGNOSTIC(-1, Note, seeUsingOf, "see using of '$0'") @@ -2507,6 +2508,24 @@ DIAGNOSTIC( "method marked `[mutable]` but never modifies `this`") DIAGNOSTIC( + 41024, + Error, + cannotDefaultInitializeResource, + "cannot default-initialize $0 with '{}'. Resource types must be explicitly initialized") + +DIAGNOSTIC( + 41024, + Error, + cannotDefaultInitializeStructWithUninitializedResource, + "cannot default-initialize struct '$0' with '{}' because it contains an uninitialized $1 field") + +DIAGNOSTIC( + 41024, + Error, + cannotDefaultInitializeStructContainingResources, + "cannot default-initialize struct '$0' with '{}' because it contains resource fields") + +DIAGNOSTIC( 41011, Error, typeDoesNotFitAnyValueSize, diff --git a/source/slang/slang-emit.cpp b/source/slang/slang-emit.cpp index 067b5a551..b548ef632 100644 --- a/source/slang/slang-emit.cpp +++ b/source/slang/slang-emit.cpp @@ -35,6 +35,7 @@ #include "slang-ir-dce.h" #include "slang-ir-defer-buffer-load.h" #include "slang-ir-defunctionalization.h" +#include "slang-ir-detect-uninitialized-resources.h" #include "slang-ir-diff-call.h" #include "slang-ir-dll-export.h" #include "slang-ir-dll-import.h" @@ -1102,6 +1103,8 @@ Result linkAndOptimizeIR( break; } + detectUninitializedResources(irModule, sink); + if (codeGenContext->removeAvailableInDownstreamIR) { removeAvailableInDownstreamModuleDecorations(target, irModule); diff --git a/source/slang/slang-ir-detect-uninitialized-resources.cpp b/source/slang/slang-ir-detect-uninitialized-resources.cpp new file mode 100644 index 000000000..dfadbe300 --- /dev/null +++ b/source/slang/slang-ir-detect-uninitialized-resources.cpp @@ -0,0 +1,237 @@ +// slang-ir-detect-uninitialized-resources.cpp +// +// This pass detects attempts to default-initialize resource types or structs containing +// resource types using empty initializers ({}) or undefined values. This pattern is not +// supported by downstream compilers like DXC. +// +// The pass detects empty makeStruct or undefined values being passed to constructors +// where the parameter type contains resources. + +#include "slang-ir-detect-uninitialized-resources.h" + +#include "slang-compiler.h" +#include "slang-ir-insts.h" +#include "slang-ir-util.h" +#include "slang-ir.h" + +namespace Slang +{ + +struct UninitializedResourceDetectionContext +{ + IRModule* module; + DiagnosticSink* sink; + + // Cache of types we've already checked + InstHashSet structsWithResources; + Dictionary<IRType*, bool> typeContainsResourceCache; + + UninitializedResourceDetectionContext(IRModule* module, DiagnosticSink* sink) + : module(module), sink(sink), structsWithResources(module) + { + } + + // Check if a type is or contains a resource type + bool containsResourceType(IRType* type) + { + // Check cache first + if (auto cached = typeContainsResourceCache.tryGetValue(type)) + return *cached; + + bool result = false; + + // Direct resource type + if (as<IRResourceType>(type)) + { + result = true; + } + // Check struct types for resource fields + else if (auto structType = as<IRStructType>(type)) + { + for (auto field : structType->getFields()) + { + if (containsResourceType(field->getFieldType())) + { + result = true; + break; + } + } + } + // Check array types + else if (auto arrayType = as<IRArrayType>(type)) + { + result = containsResourceType(arrayType->getElementType()); + } + // Check vector types + else if (auto vectorType = as<IRVectorType>(type)) + { + result = containsResourceType(vectorType->getElementType()); + } + // Check matrix types + else if (auto matrixType = as<IRMatrixType>(type)) + { + result = containsResourceType(matrixType->getElementType()); + } + + // Cache the result + typeContainsResourceCache[type] = result; + return result; + } + + // Get a human-readable name for a resource type + String getResourceTypeName(IRType* type) + { + if (as<IRTextureType>(type)) + return "texture"; + if (as<IRSamplerStateType>(type)) + return "sampler"; + if (as<IRHLSLStructuredBufferTypeBase>(type)) + return "buffer"; + + return "resource"; + } + + // Get struct name if available + String getStructName(IRStructType* structType) + { + if (auto name = structType->findDecoration<IRNameHintDecoration>()) + return String(name->getName()); + return "<unnamed>"; + } + + // Check if an instruction is a problematic uninitialized value + bool isUninitializedResourceValue(IRInst* inst) + { + // Check for empty makeStruct + if (auto makeStruct = as<IRMakeStruct>(inst)) + { + if (makeStruct->getOperandCount() == 0 && + containsResourceType(makeStruct->getDataType())) + { + return true; + } + } + + // Check for undefined values + if (as<IRUndefined>(inst)) + { + if (containsResourceType(inst->getDataType())) + { + return true; + } + } + + return false; + } + + // Check if an instruction is a constructor call + bool isConstructorCall(IRCall* call) + { + if (auto callee = call->getCallee()) + { + return callee->findDecoration<IRConstructorDecoration>() != nullptr; + } + return false; + } + + void processInst(IRInst* inst) + { + // We're only interested in constructor calls + if (auto call = as<IRCall>(inst)) + { + if (!isConstructorCall(call)) + return; + + // Check each argument + UInt argCount = call->getArgCount(); + for (UInt i = 0; i < argCount; i++) + { + auto arg = call->getArg(i); + if (isUninitializedResourceValue(arg)) + { + auto sourceLoc = call->sourceLoc; + auto uninitializedType = arg->getDataType(); + + // Get the type being constructed + IRType* constructedType = nullptr; + if (auto callee = call->getCallee()) + { + if (auto funcType = as<IRFuncType>(callee->getDataType())) + { + constructedType = funcType->getResultType(); + } + } + + // If we're constructing a struct and passing an uninitialized resource + if (constructedType && as<IRStructType>(constructedType)) + { + auto structType = as<IRStructType>(constructedType); + String structName = getStructName(structType); + + if (as<IRResourceType>(uninitializedType)) + { + String resourceName = getResourceTypeName(uninitializedType); + + // Main error + sink->diagnose( + sourceLoc, + Diagnostics::cannotDefaultInitializeStructWithUninitializedResource, + structName, + resourceName); + + // Note pointing to struct definition + if (structType->sourceLoc.isValid()) + { + sink->diagnose( + structType->sourceLoc, + Diagnostics::seeDefinitionOfStruct, + structName); + } + } + else + { + // Struct contains nested resources + sink->diagnose( + sourceLoc, + Diagnostics::cannotDefaultInitializeStructContainingResources, + structName); + } + } + else + { + // Direct resource initialization + String resourceName = getResourceTypeName(uninitializedType); + sink->diagnose( + sourceLoc, + Diagnostics::cannotDefaultInitializeResource, + resourceName); + } + } + } + } + } + + void processModule() + { + // Simple recursive traversal of the entire module + processInstRec(module->getModuleInst()); + } + + void processInstRec(IRInst* inst) + { + processInst(inst); + + for (auto child : inst->getChildren()) + { + processInstRec(child); + } + } +}; + +void detectUninitializedResources(IRModule* module, DiagnosticSink* sink) +{ + UninitializedResourceDetectionContext context(module, sink); + context.processModule(); +} + +} // namespace Slang diff --git a/source/slang/slang-ir-detect-uninitialized-resources.h b/source/slang/slang-ir-detect-uninitialized-resources.h new file mode 100644 index 000000000..8e4db0b61 --- /dev/null +++ b/source/slang/slang-ir-detect-uninitialized-resources.h @@ -0,0 +1,9 @@ +#pragma once + +namespace Slang +{ +struct IRModule; +class DiagnosticSink; + +void detectUninitializedResources(IRModule* module, DiagnosticSink* sink); +} // namespace Slang diff --git a/tests/diagnostics/uninitialized-resource-field.slang b/tests/diagnostics/uninitialized-resource-field.slang new file mode 100644 index 000000000..b2e87effb --- /dev/null +++ b/tests/diagnostics/uninitialized-resource-field.slang @@ -0,0 +1,26 @@ +//DIAGNOSTIC_TEST:SIMPLE(filecheck=CHECK): -stage compute -entry computeMain -target hlsl + +RWStructuredBuffer<int> outputBuffer; + +void useStruct(SomeStruct s) +{ + outputBuffer[0] = s.value; +} + +[shader("compute")] +[numthreads(1,1,1)] +void computeMain(uint3 threadId : SV_DispatchThreadID) +{ + // CHECK: ([[# @LINE+1]]): error {{.*}} cannot default-initialize struct 'SomeStruct' with '{}' because it contains an uninitialized texture field + SomeStruct s = {}; + + s.value = 10 + threadId.x; + useStruct(s); +} + +// CHECK: ([[# @LINE+1]]): note: see definition of struct 'SomeStruct' +struct SomeStruct +{ + int value; + Texture1D<float> tex; +} |
