1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
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
|