Skip to content

Commit 62d16a2

Browse files
authored
Specialize generic/existential calls within generic functions. (shader-slang#2294)
* Expose internals of dce and use it to implement call graph walk. * Specialize calls in generic functions. * Fix clang error. Co-authored-by: Yong He <yhe@nvidia.com>
1 parent 8da47c4 commit 62d16a2

10 files changed

+271
-115
lines changed

source/slang/slang-ir-any-value-marshalling.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,7 @@ namespace Slang
636636
// will become duplicates with new types we introduced (e.g. PtrType(AnyValueStruct)), and therefore
637637
// invalidates our `globalValueNumberingMap` hash map. We need to rebuild it.
638638
sharedContext->sharedBuilderStorage.deduplicateAndRebuildGlobalNumberingMap();
639+
sharedContext->mapInterfaceRequirementKeyValue.Clear();
639640
}
640641
};
641642

source/slang/slang-ir-dce.cpp

+129-115
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
namespace Slang
88
{
9-
109
struct DeadCodeEliminationContext
1110
{
1211
// This type implements a simple global DCE pass over
@@ -158,20 +157,8 @@ struct DeadCodeEliminationContext
158157
// "weak" references -- they can never hold things alive, and
159158
// whenever we delete the referenced value, these operands needs
160159
// to be replaced with `undef`.
161-
switch (inst->getOp())
162-
{
163-
case kIROp_BoundInterfaceType:
164-
if (inst->getOperand(ii)->getOp() == kIROp_WitnessTable)
165-
continue;
166-
break;
167-
case kIROp_SpecializationDictionaryItem:
168-
// Ignore all operands of SpecializationDictionaryItem.
169-
// This inst is used as a cache and shouldn't hold anything alive.
170-
continue;
171-
default:
172-
break;
173-
}
174-
markInstAsLive(inst->getOperand(ii));
160+
if (!isWeakReferenceOperand(inst, ii))
161+
markInstAsLive(inst->getOperand(ii));
175162
}
176163

177164
// Finally, we need to consider the children
@@ -267,119 +254,146 @@ struct DeadCodeEliminationContext
267254
//
268255
bool shouldInstBeLiveIfParentIsLive(IRInst* inst)
269256
{
270-
// The main source of confusion/complexity here is that
271-
// we are using the same routine to decide:
272-
//
273-
// * Should some ordinary instruction in a basic block be kept around?
274-
// * Should a basic block in some function be kept around?
275-
// * Should a function/type/variable in a module be kept around?
276-
//
277-
// Still, there are a few basic patterns we can observe.
278-
// First, if `inst` is an instruction that might have some effects
279-
// when it is executed, then we should keep it around.
280-
//
281-
if(inst->mightHaveSideEffects())
282-
return true;
283-
//
284-
// The `mightHaveSideEffects` query is conservative, and will
285-
// return `true` as its default mode, so once we are past that
286-
// query we know that `inst` is either something "structural"
287-
// (that makes up the program) rather than executable, or it
288-
// is executable but was on an allow-list of things that are
289-
// safe to eliminate.
257+
return Slang::shouldInstBeLiveIfParentIsLive(inst, options);
258+
}
259+
};
290260

291-
// Most top-level objects (functions, types, etc.) obviously
292-
// do *not* have side effects. That creates the risk that
293-
// we'll just go ahead and eliminate every single function/type
294-
// in a module. There needs to be a way to identify the
295-
// functions we want to keep around, and for right now
296-
// that is handled with the `[keepAlive]` decoration.
297-
//
298-
if(inst->findDecorationImpl(kIROp_KeepAliveDecoration))
299-
return true;
300-
//
301-
// We also consider anything with an `[export(...)]` as live,
302-
// when the appropriate option has been set.
303-
//
304-
// Note: our current approach to linking for back-end compilation
305-
// leaves many linakge decorations in place that we seemingly
306-
// don't need/want, so this option currently can't be enabled
307-
// unconditionally.
308-
//
309-
if( options.keepExportsAlive )
310-
{
311-
if( inst->findDecoration<IRExportDecoration>() )
312-
{
313-
return true;
314-
}
315-
}
261+
bool shouldInstBeLiveIfParentIsLive(IRInst* inst, IRDeadCodeEliminationOptions options)
262+
{
263+
// The main source of confusion/complexity here is that
264+
// we are using the same routine to decide:
265+
//
266+
// * Should some ordinary instruction in a basic block be kept around?
267+
// * Should a basic block in some function be kept around?
268+
// * Should a function/type/variable in a module be kept around?
269+
//
270+
// Still, there are a few basic patterns we can observe.
271+
// First, if `inst` is an instruction that might have some effects
272+
// when it is executed, then we should keep it around.
273+
//
274+
if (inst->mightHaveSideEffects())
275+
return true;
276+
//
277+
// The `mightHaveSideEffects` query is conservative, and will
278+
// return `true` as its default mode, so once we are past that
279+
// query we know that `inst` is either something "structural"
280+
// (that makes up the program) rather than executable, or it
281+
// is executable but was on an allow-list of things that are
282+
// safe to eliminate.
316283

317-
if (options.keepLayoutsAlive && inst->findDecoration<IRLayoutDecoration>())
284+
// Most top-level objects (functions, types, etc.) obviously
285+
// do *not* have side effects. That creates the risk that
286+
// we'll just go ahead and eliminate every single function/type
287+
// in a module. There needs to be a way to identify the
288+
// functions we want to keep around, and for right now
289+
// that is handled with the `[keepAlive]` decoration.
290+
//
291+
if (inst->findDecorationImpl(kIROp_KeepAliveDecoration))
292+
return true;
293+
//
294+
// We also consider anything with an `[export(...)]` as live,
295+
// when the appropriate option has been set.
296+
//
297+
// Note: our current approach to linking for back-end compilation
298+
// leaves many linakge decorations in place that we seemingly
299+
// don't need/want, so this option currently can't be enabled
300+
// unconditionally.
301+
//
302+
if (options.keepExportsAlive)
303+
{
304+
if (inst->findDecoration<IRExportDecoration>())
318305
{
319306
return true;
320307
}
308+
}
321309

322-
// A basic block is an interesting case. Knowing that a function
323-
// is live means that its entry block is live, but the liveness
324-
// of any other blocks is determined by whether they are referenced
325-
// by other instructions (e.g., a branch from one block to
326-
// another).
310+
if (options.keepLayoutsAlive && inst->findDecoration<IRLayoutDecoration>())
311+
{
312+
return true;
313+
}
314+
315+
// A basic block is an interesting case. Knowing that a function
316+
// is live means that its entry block is live, but the liveness
317+
// of any other blocks is determined by whether they are referenced
318+
// by other instructions (e.g., a branch from one block to
319+
// another).
320+
//
321+
if (auto block = as<IRBlock>(inst))
322+
{
323+
// To determine whether this is the first block in its
324+
// parent function (or what-have-you) we can simply
325+
// check if there is a previous block before it.
327326
//
328-
if( auto block = as<IRBlock>(inst) )
329-
{
330-
// To determine whether this is the first block in its
331-
// parent function (or what-have-you) we can simply
332-
// check if there is a previous block before it.
333-
//
334-
auto prevBlock = block->getPrevBlock();
335-
return prevBlock == nullptr;
336-
}
327+
auto prevBlock = block->getPrevBlock();
328+
return prevBlock == nullptr;
329+
}
337330

338-
// There are a few special cases of "structural" instructions
339-
// that we don't want to eliminate, so we'll check for those next.
331+
// There are a few special cases of "structural" instructions
332+
// that we don't want to eliminate, so we'll check for those next.
333+
//
334+
switch (inst->getOp())
335+
{
336+
// Function parameters obviously shouldn't get eliminated,
337+
// even if nothing references them, and block parameters
338+
// (phi nodes) will be considered live when their block is,
339+
// just so that we don't have to deal with any complications
340+
// around re-writing the relevant inter-block argument passing.
340341
//
341-
switch( inst->getOp() )
342-
{
343-
// Function parameters obviously shouldn't get eliminated,
344-
// even if nothing references them, and block parameters
345-
// (phi nodes) will be considered live when their block is,
346-
// just so that we don't have to deal with any complications
347-
// around re-writing the relevant inter-block argument passing.
348-
//
349-
// TODO: A smarter DCE pass could deal with this case more
350-
// carefully, or we could improve the interprocedural SCCP
351-
// pass to deal with block parameters instead.
352-
//
353-
case kIROp_Param:
354-
return true;
342+
// TODO: A smarter DCE pass could deal with this case more
343+
// carefully, or we could improve the interprocedural SCCP
344+
// pass to deal with block parameters instead.
345+
//
346+
case kIROp_Param:
347+
return true;
355348

356-
// IR struct types and witness tables are currently kludged
357-
// so that they have child instructions that represent their
358-
// entries (effectively `(key,value)` pairs), and those child
359-
// instructions are never directly referenced (e.g., an access
360-
// to a struct field references the *key* but not the `(key,value)`
361-
// pair that is the `IRField` instruction.
362-
//
363-
// TODO: at some point the IR should use a different representation
364-
// for struct types and witness tables that does away with
365-
// this problem.
366-
//
367-
case kIROp_StructField:
368-
case kIROp_WitnessTableEntry:
369-
return true;
349+
// IR struct types and witness tables are currently kludged
350+
// so that they have child instructions that represent their
351+
// entries (effectively `(key,value)` pairs), and those child
352+
// instructions are never directly referenced (e.g., an access
353+
// to a struct field references the *key* but not the `(key,value)`
354+
// pair that is the `IRField` instruction.
355+
//
356+
// TODO: at some point the IR should use a different representation
357+
// for struct types and witness tables that does away with
358+
// this problem.
359+
//
360+
case kIROp_StructField:
361+
case kIROp_WitnessTableEntry:
362+
return true;
370363

371-
default:
372-
break;
373-
}
364+
default:
365+
break;
366+
}
374367

375-
// If none of the explicit cases above matched, then we will consider
376-
// the instruction to not be live just because its parent is. Further
377-
// analysis could still lead to a change in the status of `inst`, if
378-
// an instruction that uses it as an operand is marked live.
379-
//
380-
return false;
368+
// If none of the explicit cases above matched, then we will consider
369+
// the instruction to not be live just because its parent is. Further
370+
// analysis could still lead to a change in the status of `inst`, if
371+
// an instruction that uses it as an operand is marked live.
372+
//
373+
return false;
374+
}
375+
376+
bool isWeakReferenceOperand(IRInst* inst, UInt operandIndex)
377+
{
378+
// There are some type of operands that needs to be treated as
379+
// "weak" references -- they can never hold things alive, and
380+
// whenever we delete the referenced value, these operands needs
381+
// to be replaced with `undef`.
382+
switch (inst->getOp())
383+
{
384+
case kIROp_BoundInterfaceType:
385+
if (inst->getOperand(operandIndex)->getOp() == kIROp_WitnessTable)
386+
return true;
387+
break;
388+
case kIROp_SpecializationDictionaryItem:
389+
// Ignore all operands of SpecializationDictionaryItem.
390+
// This inst is used as a cache and shouldn't hold anything alive.
391+
return true;
392+
default:
393+
break;
381394
}
382-
};
395+
return false;
396+
}
383397

384398
// The top-level function for invoking the DCE pass
385399
// is straighforward. We set up the context object

source/slang/slang-ir-dce.h

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// slang-ir-dce.h
22
#pragma once
33

4+
#include "slang-ir-insts.h"
5+
46
namespace Slang
57
{
68
struct IRModule;
@@ -21,4 +23,8 @@ namespace Slang
2123
bool eliminateDeadCode(
2224
IRModule* module,
2325
IRDeadCodeEliminationOptions const& options = IRDeadCodeEliminationOptions());
26+
27+
bool shouldInstBeLiveIfParentIsLive(IRInst* inst, IRDeadCodeEliminationOptions options);
28+
29+
bool isWeakReferenceOperand(IRInst* inst, UInt operandIndex);
2430
}

source/slang/slang-ir-generics-lowering-context.h

+44
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include "slang-ir.h"
55
#include "slang-ir-insts.h"
6+
#include "slang-ir-dce.h"
67

78
#include "slang-ir-lower-generics.h"
89

@@ -49,6 +50,8 @@ namespace Slang
4950
void addToWorkList(
5051
IRInst* inst)
5152
{
53+
if (!inst) return;
54+
5255
for (auto ii = inst->getParent(); ii; ii = ii->getParent())
5356
{
5457
if (as<IRGeneric>(ii))
@@ -128,4 +131,45 @@ namespace Slang
128131
}
129132
}
130133
}
134+
135+
template<typename TFunc>
136+
void workOnCallGraph(SharedGenericsLoweringContext* sharedContext, const TFunc& func)
137+
{
138+
SharedIRBuilder* sharedBuilder = &sharedContext->sharedBuilderStorage;
139+
sharedBuilder->init(sharedContext->module);
140+
141+
sharedContext->addToWorkList(sharedContext->module->getModuleInst());
142+
IRDeadCodeEliminationOptions dceOptions;
143+
dceOptions.keepExportsAlive = true;
144+
dceOptions.keepLayoutsAlive = true;
145+
146+
while (sharedContext->workList.getCount() != 0)
147+
{
148+
IRInst* inst = sharedContext->workList.getLast();
149+
150+
sharedContext->workList.removeLast();
151+
152+
sharedContext->addToWorkList(inst->parent);
153+
sharedContext->addToWorkList(inst->getFullType());
154+
155+
UInt operandCount = inst->getOperandCount();
156+
for (UInt ii = 0; ii < operandCount; ++ii)
157+
{
158+
if (!isWeakReferenceOperand(inst, ii))
159+
sharedContext->addToWorkList(inst->getOperand(ii));
160+
}
161+
162+
if (auto call = as<IRCall>(inst))
163+
{
164+
if (func(call))
165+
return;
166+
}
167+
168+
for (auto child = inst->getLastChild(); child; child = child->getPrevInst())
169+
{
170+
if (shouldInstBeLiveIfParentIsLive(child, dceOptions))
171+
sharedContext->addToWorkList(child);
172+
}
173+
}
174+
}
131175
}

source/slang/slang-ir-lower-generic-function.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ namespace Slang
315315
}
316316
// Update hash keys of globalNumberingMap, since the types are modified.
317317
sharedContext->sharedBuilderStorage.deduplicateAndRebuildGlobalNumberingMap();
318+
sharedContext->mapInterfaceRequirementKeyValue.Clear();
318319
}
319320

320321
void processModule()

source/slang/slang-ir-lower-generic-type.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ namespace Slang
7979
}
8080
}
8181
sharedContext->sharedBuilderStorage.deduplicateAndRebuildGlobalNumberingMap();
82+
sharedContext->mapInterfaceRequirementKeyValue.Clear();
8283
}
8384
};
8485

source/slang/slang-ir-lower-generics.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ namespace Slang
109109
return;
110110

111111
sharedContext->sharedBuilderStorage.deduplicateAndRebuildGlobalNumberingMap();
112+
sharedContext->mapInterfaceRequirementKeyValue.Clear();
112113

113114
specializeRTTIObjectReferences(sharedContext);
114115

0 commit comments

Comments
 (0)