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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
|
// slang-ir-generics-lowering-context.cpp
#include "slang-ir-generics-lowering-context.h"
#include "slang-ir-layout.h"
#include "slang-ir-util.h"
namespace Slang
{
bool isPolymorphicType(IRInst* typeInst)
{
if (as<IRParam>(typeInst) && as<IRTypeType>(typeInst->getDataType()))
return true;
switch (typeInst->getOp())
{
case kIROp_ThisType:
case kIROp_AssociatedType:
case kIROp_InterfaceType:
case kIROp_LookupWitnessMethod:
return true;
case kIROp_Specialize:
{
for (UInt i = 0; i < typeInst->getOperandCount(); i++)
{
if (isPolymorphicType(typeInst->getOperand(i)))
return true;
}
return false;
}
default:
break;
}
if (auto ptrType = as<IRPtrTypeBase>(typeInst))
{
return isPolymorphicType(ptrType->getValueType());
}
return false;
}
bool isTypeValue(IRInst* typeInst)
{
if (typeInst)
{
switch (typeInst->getOp())
{
case kIROp_TypeType:
case kIROp_TypeKind:
return true;
default:
return false;
}
}
return false;
}
IRInst* SharedGenericsLoweringContext::maybeEmitRTTIObject(IRInst* typeInst)
{
IRInst* result = nullptr;
if (mapTypeToRTTIObject.tryGetValue(typeInst, result))
return result;
IRBuilder builderStorage(module);
auto builder = &builderStorage;
builder->setInsertAfter(typeInst);
result = builder->emitMakeRTTIObject(typeInst);
// For now the only type info we encapsualte is type size.
IRSizeAndAlignment sizeAndAlignment;
getNaturalSizeAndAlignment(targetProgram->getOptionSet(), (IRType*)typeInst, &sizeAndAlignment);
builder->addRTTITypeSizeDecoration(result, sizeAndAlignment.size);
// Give a name to the rtti object.
if (auto exportDecoration = typeInst->findDecoration<IRExportDecoration>())
{
String rttiObjName = exportDecoration->getMangledName();
builder->addExportDecoration(result, rttiObjName.getUnownedSlice());
}
// Make sure the RTTI object for an exported struct type is marked as export if the type is.
if (typeInst->findDecoration<IRHLSLExportDecoration>())
{
builder->addHLSLExportDecoration(result);
builder->addKeepAliveDecoration(result);
}
mapTypeToRTTIObject[typeInst] = result;
return result;
}
IRInst* SharedGenericsLoweringContext::findInterfaceRequirementVal(
IRInterfaceType* interfaceType,
IRInst* requirementKey)
{
if (auto dict = mapInterfaceRequirementKeyValue.tryGetValue(interfaceType))
return dict->getValue(requirementKey);
_builldInterfaceRequirementMap(interfaceType);
return findInterfaceRequirementVal(interfaceType, requirementKey);
}
void SharedGenericsLoweringContext::_builldInterfaceRequirementMap(IRInterfaceType* interfaceType)
{
mapInterfaceRequirementKeyValue.add(interfaceType, Dictionary<IRInst*, IRInst*>());
auto dict = mapInterfaceRequirementKeyValue.tryGetValue(interfaceType);
for (UInt i = 0; i < interfaceType->getOperandCount(); i++)
{
auto entry = cast<IRInterfaceRequirementEntry>(interfaceType->getOperand(i));
(*dict)[entry->getRequirementKey()] = entry->getRequirementVal();
}
}
IRType* SharedGenericsLoweringContext::lowerAssociatedType(IRBuilder* builder, IRInst* type)
{
if (type->getOp() != kIROp_AssociatedType)
return (IRType*)type;
IRIntegerValue anyValueSize = kInvalidAnyValueSize;
for (UInt i = 0; i < type->getOperandCount(); i++)
{
anyValueSize =
Math::Min(anyValueSize, getInterfaceAnyValueSize(type->getOperand(i), type->sourceLoc));
}
if (anyValueSize == kInvalidAnyValueSize)
{
// We could conceivably make it an error to have an associated type
// without an `[anyValueSize(...)]` attribute, but then we risk
// producing error messages even when doing 100% static specialization.
//
// It is simpler to use a reasonable default size and treat any
// type without an explicit attribute as using that size.
//
anyValueSize = kDefaultAnyValueSize;
}
return builder->getAnyValueType(anyValueSize);
}
IRType* SharedGenericsLoweringContext::lowerType(
IRBuilder* builder,
IRInst* paramType,
const Dictionary<IRInst*, IRInst*>& typeMapping,
IRType* concreteType)
{
if (!paramType)
return nullptr;
IRInst* resultType;
if (typeMapping.tryGetValue(paramType, resultType))
return (IRType*)resultType;
if (isTypeValue(paramType))
{
return builder->getRTTIHandleType();
}
switch (paramType->getOp())
{
case kIROp_WitnessTableType:
case kIROp_WitnessTableIDType:
case kIROp_ExtractExistentialType:
// Do not translate these types.
return (IRType*)paramType;
case kIROp_Param:
{
if (auto anyValueSizeDecor = paramType->findDecoration<IRTypeConstraintDecoration>())
{
if (isBuiltin(anyValueSizeDecor->getConstraintType()))
return (IRType*)paramType;
auto anyValueSize = getInterfaceAnyValueSize(
anyValueSizeDecor->getConstraintType(),
paramType->sourceLoc);
return builder->getAnyValueType(anyValueSize);
}
// We could conceivably make it an error to have a generic parameter
// without an `[anyValueSize(...)]` attribute, but then we risk
// producing error messages even when doing 100% static specialization.
//
// It is simpler to use a reasonable default size and treat any
// type without an explicit attribute as using that size.
//
return builder->getAnyValueType(kDefaultAnyValueSize);
}
case kIROp_ThisType:
{
auto interfaceType = cast<IRThisType>(paramType)->getConstraintType();
if (isBuiltin(interfaceType))
return (IRType*)paramType;
if (isComInterfaceType((IRType*)interfaceType))
return (IRType*)interfaceType;
auto anyValueSize = getInterfaceAnyValueSize(
cast<IRThisType>(paramType)->getConstraintType(),
paramType->sourceLoc);
return builder->getAnyValueType(anyValueSize);
}
case kIROp_AssociatedType:
{
return lowerAssociatedType(builder, paramType);
}
case kIROp_InterfaceType:
{
if (isBuiltin(paramType))
return (IRType*)paramType;
if (isComInterfaceType((IRType*)paramType))
return (IRType*)paramType;
// In the dynamic-dispatch case, a value of interface type
// is going to be packed into the "any value" part of a tuple.
// The size of the "any value" part depends on the interface
// type (e.g., it might have an `[anyValueSize(8)]` attribute
// indicating that 8 bytes needs to be reserved).
//
auto anyValueSize = getInterfaceAnyValueSize(paramType, paramType->sourceLoc);
// If there is a non-null `concreteType` parameter, then this
// interface type is one that has been statically bound (via
// specialization parameters) to hold a value of that concrete
// type.
//
IRType* pendingType = nullptr;
if (concreteType)
{
// Because static specialization is being used (at least in part),
// we do *not* have a guarantee that the `concreteType` is one
// that can fit into the `anyValueSize` of the interface.
//
// We will use the IR layout logic to see if we can compute
// a size for the type, which can lead to a few different outcomes:
//
// * If a size is computed successfully, and it is smaller than or
// equal to `anyValueSize`, then the concrete value will fit into
// the reserved area, and the layout will match the dynamic case.
//
// * If a size is computed successfully, and it is larger than
// `anyValueSize`, then the concrete value cannot fit into the
// reserved area, and it needs to be stored out-of-line.
//
// * If size cannot be computed, then that implies that the type
// includes non-ordinary data (e.g., a `Texture2D` on a D3D11
// target), and cannot possible fit into the reserved area
// (which consists of only uniform bytes). In this case, the
// value must be stored out-of-line.
//
IRSizeAndAlignment sizeAndAlignment;
Result result = getNaturalSizeAndAlignment(
targetProgram->getOptionSet(),
concreteType,
&sizeAndAlignment);
if (SLANG_FAILED(result) || (sizeAndAlignment.size > anyValueSize))
{
// If the value must be stored out-of-line, we construct
// a "pseudo pointer" to the concrete type, and the
// constructed tuple will contain such a pseudo pointer.
//
// Semantically, the pseudo pointer behaves a bit like
// a pointer to the concrete type, in that it can be
// (pseudo-)dereferenced to produce a value of the chosen
// type.
//
// In terms of layout, the pseudo pointer occupies no
// space in the parent tuple/type, and will be automatically
// moved out-of-line by a later type legalization pass.
//
pendingType = builder->getPseudoPtrType(concreteType);
}
}
auto anyValueType = builder->getAnyValueType(anyValueSize);
auto witnessTableType = builder->getWitnessTableIDType((IRType*)paramType);
auto rttiType = builder->getRTTIHandleType();
IRType* tupleType = nullptr;
if (!pendingType)
{
// In the oridnary (dynamic) case, an existential type decomposes
// into a tuple of:
//
// (RTTI, witness table, any-value).
//
tupleType = builder->getTupleType(rttiType, witnessTableType, anyValueType);
}
else
{
// In the case where static specialization mandateds out-of-line storage,
// an existential type decomposes into a tuple of:
//
// (RTTI, witness table, pseudo pointer, any-value)
//
tupleType =
builder->getTupleType(rttiType, witnessTableType, pendingType, anyValueType);
//
// Note that in each of the cases, the third element of the tuple
// is a representation of the value being stored in the existential.
//
// Also note that each of these representations has the same
// size and alignment when only "ordinary" data is considered
// (the pseudo-pointer will eventually be legalized away, leaving
// behind a tuple with equivalent layout).
}
return tupleType;
}
case kIROp_LookupWitnessMethod:
{
auto lookupInterface = static_cast<IRLookupWitnessMethod*>(paramType);
auto witnessTableType =
as<IRWitnessTableType>(lookupInterface->getWitnessTable()->getDataType());
if (!witnessTableType)
return (IRType*)paramType;
auto interfaceType = as<IRInterfaceType>(witnessTableType->getConformanceType());
if (!interfaceType || isBuiltin(interfaceType))
return (IRType*)paramType;
// Make sure we are looking up inside the original interface type (prior to lowering).
// Only in the original interface type will an associated type entry have an
// IRAssociatedType value. We need to extract AnyValueSize from this IRAssociatedType.
// In lowered interface type, that entry is lowered into an Ptr(RTTIType) and this info
// is lost.
mapLoweredInterfaceToOriginal.tryGetValue(interfaceType, interfaceType);
auto reqVal =
findInterfaceRequirementVal(interfaceType, lookupInterface->getRequirementKey());
SLANG_ASSERT(reqVal && reqVal->getOp() == kIROp_AssociatedType);
return lowerType(builder, reqVal, typeMapping, nullptr);
}
case kIROp_BoundInterfaceType:
{
// A bound interface type represents an existential together with
// static knowledge that the value stored in the extistential has
// a particular concrete type.
//
// We handle this case by lowering the underlying interface type,
// but pass along the concrete type so that it can impact the
// layout of the interface type.
//
auto boundInterfaceType = static_cast<IRBoundInterfaceType*>(paramType);
return lowerType(
builder,
boundInterfaceType->getInterfaceType(),
typeMapping,
boundInterfaceType->getConcreteType());
}
default:
{
bool translated = false;
List<IRInst*> loweredOperands;
for (UInt i = 0; i < paramType->getOperandCount(); i++)
{
loweredOperands.add(
lowerType(builder, paramType->getOperand(i), typeMapping, nullptr));
if (loweredOperands.getLast() != paramType->getOperand(i))
translated = true;
}
if (translated)
return builder->getType(
paramType->getOp(),
loweredOperands.getCount(),
loweredOperands.getBuffer());
return (IRType*)paramType;
}
}
}
List<IRWitnessTable*> getWitnessTablesFromInterfaceType(IRModule* module, IRInst* interfaceType)
{
List<IRWitnessTable*> witnessTables;
for (auto globalInst : module->getGlobalInsts())
{
if (globalInst->getOp() == kIROp_WitnessTable &&
cast<IRWitnessTableType>(globalInst->getDataType())->getConformanceType() ==
interfaceType)
{
witnessTables.add(cast<IRWitnessTable>(globalInst));
}
}
return witnessTables;
}
List<IRWitnessTable*> SharedGenericsLoweringContext::getWitnessTablesFromInterfaceType(
IRInst* interfaceType)
{
return Slang::getWitnessTablesFromInterfaceType(module, interfaceType);
}
IRIntegerValue SharedGenericsLoweringContext::getInterfaceAnyValueSize(
IRInst* type,
SourceLoc usageLoc)
{
SLANG_UNUSED(usageLoc);
if (auto decor = type->findDecoration<IRAnyValueSizeDecoration>())
{
return decor->getSize();
}
// We could conceivably make it an error to have an interface
// without an `[anyValueSize(...)]` attribute, but then we risk
// producing error messages even when doing 100% static specialization.
//
// It is simpler to use a reasonable default size and treat any
// type without an explicit attribute as using that size.
//
return kDefaultAnyValueSize;
}
bool SharedGenericsLoweringContext::doesTypeFitInAnyValue(
IRType* concreteType,
IRInterfaceType* interfaceType,
IRIntegerValue* outTypeSize,
IRIntegerValue* outLimit,
bool* outIsTypeOpaque)
{
auto anyValueSize = getInterfaceAnyValueSize(interfaceType, interfaceType->sourceLoc);
if (outLimit)
*outLimit = anyValueSize;
if (!areResourceTypesBindlessOnTarget(targetProgram->getTargetReq()))
{
IRType* opaqueType = nullptr;
if (isOpaqueType(concreteType, &opaqueType))
{
if (outIsTypeOpaque)
*outIsTypeOpaque = true;
return false;
}
}
IRSizeAndAlignment sizeAndAlignment;
Result result =
getNaturalSizeAndAlignment(targetProgram->getOptionSet(), concreteType, &sizeAndAlignment);
if (outTypeSize)
*outTypeSize = sizeAndAlignment.size;
if (SLANG_FAILED(result) || (sizeAndAlignment.size > anyValueSize))
{
// The value does not fit, either because it is too large,
// or because it includes types that cannot be stored
// in uniform/ordinary memory for this target.
//
return false;
}
return true;
}
} // namespace Slang
|