diff options
Diffstat (limited to 'source/slang/slang-ir-specialize.cpp')
| -rw-r--r-- | source/slang/slang-ir-specialize.cpp | 1864 |
1 files changed, 1864 insertions, 0 deletions
diff --git a/source/slang/slang-ir-specialize.cpp b/source/slang/slang-ir-specialize.cpp new file mode 100644 index 000000000..fe6b82184 --- /dev/null +++ b/source/slang/slang-ir-specialize.cpp @@ -0,0 +1,1864 @@ +// slang-ir-specialize.cpp +#include "slang-ir-specialize.h" + +#include "slang-ir.h" +#include "slang-ir-clone.h" +#include "slang-ir-insts.h" + +namespace Slang +{ + +// This file implements the primary specialization pass, that takes +// generic/polymorphic Slang code and specializes/monomorphises it. +// +// At present this primarily means generating specialized copies +// of generic functions/types based on the concrete types used +// at specialization sites, and also specializing instances +// of witness-table lookup to directly refer to the concrete +// values for witnesses when witness tables are known. +// +// This pass also performs some amount of simplification and +// specialization for code using existential (interface) types +// for local variables and function parameters/results. +// +// Eventually, this pass will also need to perform specialization +// of functions to argument values for parameters that must +// be compile-time constants, +// +// All of these passes are inter-related in that applying +// simplifications/specializations of one category can open +// up opportunities for transformations in the other categories. + +struct SpecializationContext; + +IRInst* specializeGenericImpl( + IRGeneric* genericVal, + IRSpecialize* specializeInst, + IRModule* module, + SpecializationContext* context); + +struct SpecializationContext +{ + // For convenience, we will keep a pointer to the module + // we are specializing. + IRModule* module; + + // We know that we can only perform generic specialization when all + // of the arguments to a generic are also fully specialized. + // The "is fully specialized" condition is something we + // need to solve for over the program, because the fully- + // specialized-ness of an instruction depends on the + // fully-specialized-ness of its operands. + // + // We will build an explicit hash set to encode those + // instructions that are fully specialized. + // + HashSet<IRInst*> fullySpecializedInsts; + + // An instruction is then fully specialized if and only + // if it is in our set. + // + bool isInstFullySpecialized( + IRInst* inst) + { + // A small wrinkle is that a null instruction pointer + // sometimes appears a a type, and so should be treated + // as fully specialized too. + // + // TODO: It would be nice to remove this wrinkle. + // + if(!inst) return true; + + return fullySpecializedInsts.Contains(inst); + } + + // When an instruction isn't fully specialized, but its operands *are* + // then it is a candidate for specialization itself, so we will have + // a query to check for the "all operands fully specialized" case. + // + bool areAllOperandsFullySpecialized( + IRInst* inst) + { + if(!isInstFullySpecialized(inst->getFullType())) + return false; + + UInt operandCount = inst->getOperandCount(); + for(UInt ii = 0; ii < operandCount; ++ii) + { + IRInst* operand = inst->getOperand(ii); + if(!isInstFullySpecialized(operand)) + return false; + } + + return true; + } + + // We will use a single work list of instructions that need + // to be considered for specialization or simplification, + // whether generic, existential, etc. + // + List<IRInst*> workList; + HashSet<IRInst*> workListSet; + + HashSet<IRInst*> cleanInsts; + + void addToWorkList( + IRInst* inst) + { + // We will ignore any code that is nested under a generic, + // because it doesn't make sense to perform specialization + // on such code. + // + for( auto ii = inst->getParent(); ii; ii = ii->getParent() ) + { + if(as<IRGeneric>(ii)) + return; + } + + if(workListSet.Contains(inst)) + return; + + workList.add(inst); + workListSet.Add(inst); + cleanInsts.Remove(inst); + + addUsersToWorkList(inst); + } + + // When a transformation makes a change to an instruction, + // we may need to re-consider transformations for instructions + // that use its value. In those cases we will call `addUsersToWorkList` + // on the instruction that is being modified or replaced. + // + void addUsersToWorkList( + IRInst* inst) + { + for( auto use = inst->firstUse; use; use = use->nextUse ) + { + auto user = use->getUser(); + addToWorkList(user); + } + } + + // One of the main transformations we will apply is to + // consider an instruction as being fully specialized. + // + void markInstAsFullySpecialized( + IRInst* inst) + { + if(fullySpecializedInsts.Contains(inst)) + return; + fullySpecializedInsts.Add(inst); + + // If we know that an instruction is fully specialized, + // then we should start to consider its uses and children + // as candidates for being fully specialized too... + // + addUsersToWorkList(inst); + } + + + // Of course, somewhere along the way we expect + // to run into uses of `specialize(...)` instructions + // to bind a generic to arguments that we want to + // specialize into concrete code. + // + // We also know that if we encouter `specialize(g, a, b, c)` + // and then later `specialize(g, a, b, c)` again, we + // only want to generate the specialized code for `g<a,b,c>` + // *once*, and re-use it for both versions. + // + // We will cache existing specializations of generic function/types + // using the simple key type defined as part of the IR cloning infrastructure. + // + typedef IRSimpleSpecializationKey Key; + Dictionary<Key, IRInst*> genericSpecializations; + + // We will also use some shared IR building state across + // all of our specialization/cloning steps. + // + SharedIRBuilder sharedBuilderStorage; + + // Now let's look at the task of finding or generation a + // specialization of some generic `g`, given a specialization + // instruction like `specialize(g, a, b, c)`. + // + // The `specializeGeneric` function will return a value + // suitable for use as a replacement for the `specialize(...)` + // instruction. + // + IRInst* specializeGeneric( + IRGeneric* genericVal, + IRSpecialize* specializeInst) + { + // First, we want to see if an existing specialization + // has already been made. To do that we will construct a key + // for lookup in the generic specialization context. + // + // Our key will consist of the identity of the generic + // being specialized, and each of the argument values + // being pased to it. In our hypothetical example of + // `specialize(g, a, b, c)` the key will then be + // the array `[g, a, b, c]`. + // + Key key; + key.vals.add(specializeInst->getBase()); + UInt argCount = specializeInst->getArgCount(); + for( UInt ii = 0; ii < argCount; ++ii ) + { + key.vals.add(specializeInst->getArg(ii)); + } + + { + // We use our generated key to look for an + // existing specialization that has been registered. + // If one is found, our work is done. + // + IRInst* specializedVal = nullptr; + if(genericSpecializations.TryGetValue(key, specializedVal)) + return specializedVal; + } + + // If no existing specialization is found, we need + // to create the specialization instead. + // This mostly amounts to evaluating the generic as + // if it were a function being called. + // + // We will use a free function to do the actual work + // of evaluating the generic, so that the logic + // can be re-used in other cases that need to + // do one-off specialization. + // + IRInst* specializedVal = specializeGenericImpl(genericVal, specializeInst, module, this); + + + // The value that was returned from evaluating + // the generic is the specialized value, and we + // need to remember it in our dictionary of + // specializations so that we don't instantiate + // this generic again for the same arguments. + // + genericSpecializations.Add(key, specializedVal); + + return specializedVal; + } + + // The logic for generating a specialization of an IR generic + // relies on the ability to "evaluate" the code in the body of + // the generic, but that obviously doesn't work if we don't + // actually have the full definition for the body. + // + // This can arise in particular for builtin operations/types. + // + // Before calling `specializeGeneric()` we need to make sure + // that the generic is actually amenable to specialization, + // by looking at whether it is a definition or a declaration. + // + bool canSpecializeGeneric( + IRGeneric* generic) + { + // It is possible to have multiple "layers" of generics + // (e.g., when a generic function is nested in a generic + // type). Therefore we need to drill down through all + // of the layers present to see if at the leaf we have + // something that looks like a definition. + // + IRGeneric* g = generic; + for(;;) + { + // Given the generic `g`, we will find the value + // it appears to return in its body. + // + auto val = findGenericReturnVal(g); + if(!val) + return false; + + // If `g` returns an inner generic, then we need + // to drill down further. + // + if (auto nestedGeneric = as<IRGeneric>(val)) + { + g = nestedGeneric; + continue; + } + + // Once we've found the leaf value that will be produced + // after all specialization is complete, we can check + // whether it looks like a definition or not. + // + return isDefinition(val); + } + } + + // Now that we know when we can specialize a generic, and how + // to do it, we can write a subroutine that takes a + // `specialize(g, a, b, c, ...)` instruction and performs + // specialization if it is possible. + // + void maybeSpecializeGeneric( + IRSpecialize* specInst) + { + // We will only attempt to specialize when all of the + // operands to the `speicalize(...)` instruction are + // themselves fully specialized. + // + if(!areAllOperandsFullySpecialized(specInst)) + return; + + // The invariant that the arguments are fully specialized + // should mean that `a, b, c, ...` are in a form that + // we can work with, but it does *not* guarantee + // that the `g` operand is something we can work with. + // + // We can only perform specialization in the case where + // the base `g` is a known `generic` instruction. + // + auto baseVal = specInst->getBase(); + auto genericVal = as<IRGeneric>(baseVal); + if(!genericVal) + return; + + // We can also only specialize a generic if it + // represents a definition rather than a declaration. + // + if(!canSpecializeGeneric(genericVal)) + return; + + // Once we know that specialization is possible, + // the actual work is fairly simple. + // + // First, we find or generate a specialized + // version of the result of the generic (a specialized + // type, function, or whatever). + // + auto specializedVal = specializeGeneric(genericVal, specInst); + + // Any uses of this `specialize(...)` instruction will + // become uses of `specializeVal`, so we want to re-consider + // them for subsequent transformations. + // + addUsersToWorkList(specInst); + + // Then we simply replace any uses of the `specialize(...)` + // instruction with the specialized value and delete + // the `specialize(...)` instruction from existence. + // + specInst->replaceUsesWith(specializedVal); + specInst->removeAndDeallocate(); + } + + // Generic specialization depends on identifying when + // instructions are fully specialized. + // + void maybeMarkAsFullySpecialized( + IRInst* inst) + { + switch(inst->op) + { + default: + // The default case is that an instruction can + // be considered as fully specialized as soon + // as all of its operands are. + // + // TODO: We realistically need a more refined + // check here that uses a white-list of instructions + // that can represent values suitable for use + // as generic arguments. + // + if(areAllOperandsFullySpecialized(inst)) + { + markInstAsFullySpecialized(inst); + } + break; + + // Certain instructions cannot ever be considered + // fully specialized because they should never + // be substituted into a generic as its arguments. + case kIROp_Specialize: + case kIROp_lookup_interface_method: + case kIROp_ExtractExistentialType: + case kIROp_BindExistentialsType: + break; + } + } + + // The core of this pass is to look at one instruction + // at a time, and try to perform whatever specialization + // is appropriate based on its opcode. + // + void maybeSpecializeInst( + IRInst* inst) + { + switch(inst->op) + { + default: + // By default we assume that specialization is + // not possible for a given opcode. + // + break; + + case kIROp_Specialize: + // The logic for specializing a `specialize(...)` + // instruction has already been elaborated above. + // + maybeSpecializeGeneric(cast<IRSpecialize>(inst)); + break; + + case kIROp_lookup_interface_method: + // The remaining case we need to consider here for generics + // is when we have a `lookup_witness_method` instruction + // that is being applied to a concrete witness table, + // because we can specialize it to just be a direct + // reference to the actual witness value from the table. + // + maybeSpecializeWitnessLookup(cast<IRLookupWitnessMethod>(inst)); + break; + + case kIROp_Call: + // When writing functions with existential-type parameters, + // we need additional support to specialize a callee + // function based on the concrete type encapsulated in + // an argument of existential type. + // + maybeSpecializeExistentialsForCall(cast<IRCall>(inst)); + break; + + // The specialization of functions with existential-type + // parameters can create further opportunities for specialization, + // but in order to realize these we often need to propagate + // through local simplification on values of existential type. + // + case kIROp_ExtractExistentialType: + maybeSpecializeExtractExistentialType(inst); + break; + case kIROp_ExtractExistentialValue: + maybeSpecializeExtractExistentialValue(inst); + break; + case kIROp_ExtractExistentialWitnessTable: + maybeSpecializeExtractExistentialWitnessTable(inst); + break; + + case kIROp_Load: + maybeSpecializeLoad(as<IRLoad>(inst)); + break; + + case kIROp_FieldExtract: + maybeSpecializeFieldExtract(as<IRFieldExtract>(inst)); + break; + case kIROp_FieldAddress: + maybeSpecializeFieldAddress(as<IRFieldAddress>(inst)); + break; + + case kIROp_BindExistentialsType: + maybeSpecializeBindExistentialsType(as<IRBindExistentialsType>(inst)); + break; + } + } + + // Specializing lookup on witness tables is a general + // transformation that helps with both generic and + // existential-based code. + // + void maybeSpecializeWitnessLookup( + IRLookupWitnessMethod* lookupInst) + { + // Note: While we currently have named the instruction + // `lookup_witness_method`, the `method` part is a misnomer + // and the same instruction can look up *any* interface + // requirement based on the witness table that provides + // a conformance, and the "key" that indicates the interface + // requirement. + + // We can only specialize in the case where the lookup + // is being done on a concrete witness table, and not + // the result of a `specialize` instruction or other + // operation that will yield such a table. + // + auto witnessTable = as<IRWitnessTable>(lookupInst->getWitnessTable()); + if(!witnessTable) + return; + + // Because we have a concrete witness table, we can + // use it to look up the IR value that satisfies + // the given interface requirement. + // + auto requirementKey = lookupInst->getRequirementKey(); + auto satisfyingVal = findWitnessVal(witnessTable, requirementKey); + + // We expect to always find a satisfying value, but + // we will go ahead and code defensively so that + // we leave "correct" but unspecialized code if + // we cannot find a concrete value to use. + // + if(!satisfyingVal) + return; + + // At this point, we know that `satisfyingVal` is what + // would result from executing this `lookup_witness_method` + // instruction dynamically, so we can go ahead and + // replace the original instruction with that value. + // + // We also make sure to add any uses of the lookup + // instruction to our work list, because subsequent + // simplifications might be possible now. + // + addUsersToWorkList(lookupInst); + lookupInst->replaceUsesWith(satisfyingVal); + lookupInst->removeAndDeallocate(); + } + + // The above subroutine needed a way to look up + // the satisfying value for a given requirement + // key in a concrete witness table, so let's + // define that now. + // + IRInst* findWitnessVal( + IRWitnessTable* witnessTable, + IRInst* requirementKey) + { + // A witness table is basically just a container + // for key-value pairs, and so the best we can + // do for now is a naive linear search. + // + for( auto entry : witnessTable->getEntries() ) + { + if (requirementKey == entry->getRequirementKey()) + { + return entry->getSatisfyingVal(); + } + } + return nullptr; + } + + // All of the machinery for generic specialization + // has been defined above, so we will now walk + // through the flow of the overall specialization pass. + // + void processModule() + { + // We start by initializing our shared IR building state, + // since we will re-use that state for any code we + // generate along the way. + // + SharedIRBuilder* sharedBuilder = &sharedBuilderStorage; + sharedBuilder->module = module; + sharedBuilder->session = module->session; + + // The unspecialized IR we receive as input will have + // `IRBindGlobalGenericParam` instructions that associate + // each global-scope generic parameter (a type, witness + // table, or what-have-you) with the value that it should + // be bound to for the purposes of this code-generation + // pass. + // + // Before doing any other specialization work, we will + // iterate over these instructions (which may only + // appear at the global scope) and use them to drive + // replacement of the given generic type parameter with + // the desired concrete value. + // + // TODO: When we start to support global shader parameters + // that include existential/interface types, we will need + // to support a similar specialization step for them. + // + specializeGlobalGenericParameters(); + + // Now that we've eliminated all cases of global generic parameters, + // we should now have the properties that: + // + // 1. Execution starts in non-generic code, with no unbound + // generic parameters in scope. + // + // 2. Any case where non-generic code makes use of a generic + // type/function, there will be a `specialize` instruction + // that specifies both the generic and the (concrete) type + // arguments that should be provided to it. + // + // The basic approach now is to look for opportunities to apply + // our specialization rules (e.g., a `specialize` instruction + // where all the type arguments are concrete types) and then + // processing any additional opportunities created along the way. + // + // We start out simple by putting the root instruction for the + // module onto our work list. + // + addToWorkList(module->getModuleInst()); + + while(workList.getCount() != 0) + { + + // We will then iterate until our work list goes dry. + // + while(workList.getCount() != 0) + { + IRInst* inst = workList.getLast(); + + workList.removeLast(); + workListSet.Remove(inst); + cleanInsts.Add(inst); + + // For each instruction we process, we want to perform + // a few steps. + // + // First we will do any checking required to tag an + // instruction as being fully specialized. + // + maybeMarkAsFullySpecialized(inst); + + // Next we will look for all the general-purpose + // specialization opportunities (generic specialization, + // existential specialization, simplifications, etc.) + // + maybeSpecializeInst(inst); + + // Finally, we need to make our logic recurse through + // the whole IR module, so we want to add the children + // of any parent instructions to our work list so that + // we process them too. + // + // Note that we are adding the children of an instruction + // in reverse order. This is because the way we are + // using the work list treats it like a stack (LIFO) and + // we know that fully-specialized-ness will tend to flow + // top-down through the program, so that we want to process + // the children of an instruction in their original order. + // + for(auto child = inst->getLastChild(); child; child = child->getPrevInst()) + { + // Also note that `addToWorkList` has been written + // to avoid adding any instruction that is a descendent + // of an IR generic, because we don't actually want + // to perform specialization inside of generics. + // + addToWorkList(child); + } + } + + addDirtyInstsToWorkListRec(module->getModuleInst()); + + } + + // Once the work list has gone dry, we should have the invariant + // that there are no `specialize` instructions inside of non-generic + // functions that in turn reference a generic type/function, *except* + // in the case where that generic is for a builtin type/function, in + // which case we wouldn't want to specialize it anyway. + } + + void addDirtyInstsToWorkListRec(IRInst* inst) + { + if( !cleanInsts.Contains(inst) ) + { + addToWorkList(inst); + } + + for(auto child = inst->getLastChild(); child; child = child->getPrevInst()) + { + addDirtyInstsToWorkListRec(child); + } + } + + // Given a `call` instruction in the IR, we need to detect the case + // where the callee has some interface-type parameter(s) and at the + // call site it is statically clear what concrete type(s) the arguments + // will have. + // + void maybeSpecializeExistentialsForCall(IRCall* inst) + { + // We can only specialize a call when the callee function is known. + // + auto calleeFunc = as<IRFunc>(inst->getCallee()); + if(!calleeFunc) + return; + + // We can only specialize if we have access to a body for the callee. + // + if(!calleeFunc->isDefinition()) + return; + + // We shouldn't bother specializing unless the callee has at least + // one parameter that has an existential/interface type. + // + bool shouldSpecialize = false; + UInt argCounter = 0; + for( auto param : calleeFunc->getParams() ) + { + auto arg = inst->getArg(argCounter++); + if( !isExistentialType(param->getDataType()) ) + continue; + + shouldSpecialize = true; + + // We *cannot* specialize unless the argument value corresponding + // to such a parameter is one we can specialize. + // + if( !canSpecializeExistentialArg(arg)) + return; + + } + // If we never found a parameter worth specializing, we should bail out. + // + if(!shouldSpecialize) + return; + + // At this point, we believe we *should* and *can* specialize. + // + // We need a specialized variant of the callee (with the concrete + // types substituted in for existential-type parameters), and then + // we can replace the call site to call the new function instead. + // + // Any two call sites where the argument types are the same can + // re-use the same callee, so we will cache and re-use the + // specialized functions that we generate (similar to how generic + // specialization works). Therefore we will construct a key + // for use when caching the specialized functions. + // + IRSimpleSpecializationKey key; + + // The specialized callee will always depend on the unspecialized + // function from which it is generated, so we add that to our key. + // + key.vals.add(calleeFunc); + + // Also, for any parameter that has an existential type, the + // specialized function will depend on the concrete type of the + // argument. + // + argCounter = 0; + for( auto param : calleeFunc->getParams() ) + { + auto arg = inst->getArg(argCounter++); + if( !isExistentialType(param->getDataType()) ) + continue; + + if( auto makeExistential = as<IRMakeExistential>(arg) ) + { + // Note that we use the *type* stored in the + // existential-type argument, but not anything to + // do with the particular value (otherwise we'd only + // be able to re-use the specialized callee for + // call sites that pass in the exact same argument). + // + auto val = makeExistential->getWrappedValue(); + auto valType = val->getFullType(); + key.vals.add(valType); + + // We are also including the witness table in the key. + // This isn't required with our current language model, + // since a given type can only conform to a given interface + // in one way (so there can be only one witness table). + // That means that the `valType` and the existential + // type of `param` above should uniquely determine + // the witness table we see. + // + // There are forward-looking cases where supporting + // "overlapping conformances" could be required, and + // there is low incremental cost to future-proofing + // this code, so we go ahead and add the witness + // table even if it is redundant. + // + auto witnessTable = makeExistential->getWitnessTable(); + key.vals.add(witnessTable); + } + else if( auto wrapExistential = as<IRWrapExistential>(arg) ) + { + auto val = wrapExistential->getWrappedValue(); + auto valType = val->getFullType(); + key.vals.add(valType); + + UInt slotOperandCount = wrapExistential->getSlotOperandCount(); + for( UInt ii = 0; ii < slotOperandCount; ++ii ) + { + auto slotOperand = wrapExistential->getSlotOperand(ii); + key.vals.add(slotOperand); + } + } + else + { + SLANG_UNEXPECTED("missing case for existential argument"); + } + } + + // Once we've constructed our key, we can try to look for an + // existing specialization of the callee that we can use. + // + IRFunc* specializedCallee = nullptr; + if( !existentialSpecializedFuncs.TryGetValue(key, specializedCallee) ) + { + // If we didn't find a specialized callee already made, then we + // will go ahead and create one, and then register it in our cache. + // + specializedCallee = createExistentialSpecializedFunc(inst, calleeFunc); + existentialSpecializedFuncs.Add(key, specializedCallee); + } + + // At this point we have found or generated a specialized version + // of the callee, and we need to emit a call to it. + // + // We will start by constructing the argument list for the new call. + // + argCounter = 0; + List<IRInst*> newArgs; + for( auto param : calleeFunc->getParams() ) + { + auto arg = inst->getArg(argCounter++); + + // How we handle each argument depends on whether the corresponding + // parameter has an existential type or not. + // + if( !isExistentialType(param->getDataType()) ) + { + // If the parameter doesn't have an existential type, then we + // don't want to change up the argument we pass at all. + // + newArgs.add(arg); + } + else + { + // Any place where the original function had a parameter of + // existential type, we will now be passing in the concrete + // argument value instead of an existential wrapper. + // + if( auto makeExistential = as<IRMakeExistential>(arg) ) + { + auto val = makeExistential->getWrappedValue(); + newArgs.add(val); + } + else if( auto wrapExistential = as<IRWrapExistential>(arg) ) + { + auto val = wrapExistential->getWrappedValue(); + newArgs.add(val); + } + else + { + SLANG_UNEXPECTED("missing case for existential argument"); + } + } + } + + // Now that we've built up our argument list, it is simple enough + // to construct a new `call` instruction. + // + IRBuilder builderStorage; + auto builder = &builderStorage; + builder->sharedBuilder = &sharedBuilderStorage; + + builder->setInsertBefore(inst); + auto newCall = builder->emitCallInst( + inst->getFullType(), specializedCallee, newArgs); + + // We will completely replace the old `call` instruction with the + // new one, and will go so far as to transfer any decorations + // that were attached to the old call over to the new one. + // + inst->transferDecorationsTo(newCall); + inst->replaceUsesWith(newCall); + inst->removeAndDeallocate(); + + // Just in case, we will add any instructions that used the + // result of this call to our work list for re-consideration. + // At this moment this shouldn't open up new opportunities + // for specialization, but we can always play it safe. + // + addUsersToWorkList(newCall); + } + + // The above `maybeSpecializeExistentialsForCall` routine needed + // a few utilities, which we will now define. + + // First, we want to be able to test whether a type (used by + // a parameter) is an existential type so that we should specialize it. + // + bool isExistentialType(IRType* type) + { + // An IR-level interface type is always an existential. + // + if(as<IRInterfaceType>(type)) + return true; + + // Eventually we will also want to handle arrays over + // existential types, but that will require careful + // handling in many places. + + return false; + } + + // Similarly, we want to be able to test whether an instruction + // used as an argument for an existential-type parameter is + // suitable for use in specialization. + // + bool canSpecializeExistentialArg(IRInst* inst) + { + // A `makeExistential(v, w)` instruction can be used + // for specialization, since we have the concrete value `v` + // (which implicitly determines the concrete type), and + // the witness table `w. + // + if(as<IRMakeExistential>(inst)) + return true; + + // A `wrapExistential(v, T0,w0, T1, w1, ...)` instruction + // is just a generalization of `makeExistential`, so it + // can apply in the same cases. + // + if(as<IRWrapExistential>(inst)) + return true; + + // If we start to specialize functions that take arrays + // of existentials as input, we will need a strategy to + // determine arguments suitable for use in specializing + // them (these would need to be arrays that nominally + // have an existential element type, but somehow have + // annotations to indicate that the concrete type + // underlying the elements in homogeneous). + + return false; + } + + // In order to cache and re-use functions that have had existential-type + // parameters specialized, we need storage for the cache. + // + Dictionary<IRSimpleSpecializationKey, IRFunc*> existentialSpecializedFuncs; + + // The logic for creating a specialized callee function by plugging + // in concrete types for existentials is similar to other cases of + // specialization in the compiler. + // + IRFunc* createExistentialSpecializedFunc( + IRCall* oldCall, + IRFunc* oldFunc) + { + // We will make use of the infrastructure for cloning + // IR code, that is defined in `ir-clone.{h,cpp}`. + // + // In order to do the cloning work we need an + // "environment" that will map old values to + // their replacements. + // + IRCloneEnv cloneEnv; + + // We also need some IR building state, for any + // new instructions we will emit. + // + IRBuilder builderStorage; + auto builder = &builderStorage; + builder->sharedBuilder = &sharedBuilderStorage; + + // We will start out by determining what the parameters + // of the specialized function should be, based on + // the parameters of the original, and the concrete + // type of selected arguments at the call site. + // + // Along the way we will build up explicit lists of + // the parameters, as well as any new instructions + // that need to be added to the body of the function + // we generate (as a kind of "prologue"). We build + // the lists here because we don't yet have a basic + // block, or even a function, to insert them into. + // + List<IRParam*> newParams; + List<IRInst*> newBodyInsts; + UInt argCounter = 0; + for( auto oldParam : oldFunc->getParams() ) + { + auto arg = oldCall->getArg(argCounter++); + + // Given an old parameter, and the argument value at + // the (old) call site, we need to determine what + // value should stand in for that parameter in + // the specialized callee. + // + IRInst* replacementVal = nullptr; + + // The trickier case is when we have an existential-type + // parameter, because we need to extract out the concrete + // type that is coming from the call site. + // + if( auto oldMakeExistential = as<IRMakeExistential>(arg) ) + { + // In this case, the `arg` is `makeExistential(val, witnessTable)` + // and we know that the specialized call site will just be + // passing in `val`. + // + auto val = oldMakeExistential->getWrappedValue(); + auto witnessTable = oldMakeExistential->getWitnessTable(); + + // Our specialized function needs to take a parameter with the + // same type as `val`, to match the call site(s) that will be + // created. + // + auto valType = val->getFullType(); + auto newParam = builder->createParam(valType); + newParams.add(newParam); + + // Within the body of the function we cannot just use `val` + // directly, because the existing code expects an existential + // value, including its witness table. + // + // Therefore we will create a `makeExistential(newParam, witnessTable)` + // in the body of the new function and use *that* as the replacement + // value for the original parameter (since it will have the + // correct existential type, and stores the right witness table). + // + auto newMakeExistential = builder->emitMakeExistential(oldParam->getFullType(), newParam, witnessTable); + newBodyInsts.add(newMakeExistential); + replacementVal = newMakeExistential; + } + else if( auto oldWrapExistential = as<IRWrapExistential>(arg) ) + { + auto val = oldWrapExistential->getWrappedValue(); + auto valType = val->getFullType(); + + auto newParam = builder->createParam(valType); + newParams.add(newParam); + + // Within the body of the function we cannot just use `val` + // directly, because the existing code expects an existential + // value, including its witness table. + // + // Therefore we will create a `makeExistential(newParam, witnessTable)` + // in the body of the new function and use *that* as the replacement + // value for the original parameter (since it will have the + // correct existential type, and stores the right witness table). + // + auto newWrapExistential = builder->emitWrapExistential( + oldParam->getFullType(), + newParam, + oldWrapExistential->getSlotOperandCount(), + oldWrapExistential->getSlotOperands()); + newBodyInsts.add(newWrapExistential); + replacementVal = newWrapExistential; + } + else + { + // For parameters that don't have an existential type, + // there is nothing interesting to do. The new function + // will also have a parameter of the exact same type, + // and we'll use that instead of the original parameter. + // + auto newParam = builder->createParam(oldParam->getFullType()); + newParams.add(newParam); + replacementVal = newParam; + } + + // Whatever replacement value was constructed, we need to + // register it as the replacement for the original parameter. + // + cloneEnv.mapOldValToNew.Add(oldParam, replacementVal); + } + + // Next we will create the skeleton of the new + // specialized function, including its type. + // + // In order to construct the type of the new function, we + // need to extract the types of all its parameters. + // + List<IRType*> newParamTypes; + for( auto newParam : newParams ) + { + newParamTypes.add(newParam->getFullType()); + } + IRType* newFuncType = builder->getFuncType( + newParamTypes.getCount(), + newParamTypes.getBuffer(), + oldFunc->getResultType()); + IRFunc* newFunc = builder->createFunc(); + newFunc->setFullType(newFuncType); + + // By construction, our new function type will be + // "fully specialized" by the rules used for doing + // generic specialization elsewhere in this pass. + // + fullySpecializedInsts.Add(newFuncType); + + // The above steps have accomplished the "first phase" + // of cloning the function (since `IRFunc`s have no + // operands). + // + // We can now use the shared IR cloning infrastructure + // to perform the second phase of cloning, which will recursively + // clone any nested decorations, blocks, and instructions. + // + cloneInstDecorationsAndChildren( + &cloneEnv, + builder->sharedBuilder, + oldFunc, + newFunc); + + // Now that the main body of existing isntructions have + // been cloned into the new function, we can go ahead + // and insert all the parameters and body instructions + // we built up into the function at the right place. + // + // We expect the function to always have at least one + // block (this was an invariant established before + // we decided to specialize). + // + auto newEntryBlock = newFunc->getFirstBlock(); + SLANG_ASSERT(newEntryBlock); + + // We expect every valid block to have at least one + // "ordinary" instruction (it will at least have + // a terminator like a `return`). + // + auto newFirstOrdinary = newEntryBlock->getFirstOrdinaryInst(); + SLANG_ASSERT(newFirstOrdinary); + + // All of our parameters will get inserted before + // the first ordinary instruction (since the function parameters + // should come at the start of the first block). + // + for( auto newParam : newParams ) + { + newParam->insertBefore(newFirstOrdinary); + } + + // All of our new body instructions will *also* be inserted + // before the first ordinary instruction (but will come + // *after* the parameters by the order of these two loops). + // + for( auto newBodyInst : newBodyInsts ) + { + newBodyInst->insertBefore(newFirstOrdinary); + } + + // After all this work we have a valid `newFunc` that has been + // specialized to match the types at the call site. + // + // There might be further opportunities for simplification and + // specialization in the function body now that we've plugged + // in some more concrete type information, so we will + // add the whole function to our work list for subsequent + // consideration. + // + addToWorkList(newFunc); + + return newFunc; + } + + // When we've specialized a function with an interface-type parameter + // we will still end up with a `makeExistential` operation in its + // body, which could impede subequent specializations. + // + // For example, if we have the following after specialization: + // + // e = makeExistential(v, w1); + // w2 = extractExistentialWitnessTable(e); + // f = lookup_witness_method(w2, k); + // call(f, ...); + // + // We cannot then specialize the lookup for `f` in this code as written, + // but it seems obvious that we could replace `w2` with `w1` and maybe + // get further along. + // + // In order to set up further specialization opportunities we need + // to implement a few simplification rules around operations that + // extract from an existential, when their operand is a `makeExistential`. + // + // Let's start with the routine for the case above of extracting + // a witness table. + // + void maybeSpecializeExtractExistentialWitnessTable(IRInst* inst) + { + // We know `inst` is `extractExistentialWitnessTable(existentialArg)`. + // + auto existentialArg = inst->getOperand(0); + + if( auto makeExistential = as<IRMakeExistential>(existentialArg) ) + { + // In this case we know `inst` is: + // + // extractExistentialWitnessTable(makeExistential(..., witnessTable)) + // + // and we can just simplify that to `witnessTable`. + // + auto witnessTable = makeExistential->getWitnessTable(); + + // Anything that used this instruction is now a candidate for + // further simplification or specialization (e.g., one of + // the users of this instruction could be a `lookup_witness_method` + // that we can now specialize). + // + addUsersToWorkList(inst); + + inst->replaceUsesWith(witnessTable); + inst->removeAndDeallocate(); + } + } + + // The cases for simplifying `extractExistentialValue` is more or less the same + // as for witness tables. + // + void maybeSpecializeExtractExistentialValue(IRInst* inst) + { + // We know `inst` is `extractExistentialValue(existentialArg)`. + // + auto existentialArg = inst->getOperand(0); + if( auto makeExistential = as<IRMakeExistential>(existentialArg) ) + { + // Now we know `inst` is: + // + // extractExistentialValue(makeExistential(val, ...)) + // + // and we can just simplify that to `val`. + // + auto val = makeExistential->getWrappedValue(); + + addUsersToWorkList(inst); + + inst->replaceUsesWith(val); + inst->removeAndDeallocate(); + } + } + + // The cases for simplifying `extractExistentialType` is more or less the same + // as for witness tables. + // + void maybeSpecializeExtractExistentialType(IRInst* inst) + { + // We know `inst` is `extractExistentialValue(existentialArg)`. + // + auto existentialArg = inst->getOperand(0); + if( auto makeExistential = as<IRMakeExistential>(existentialArg) ) + { + // Now we know `inst` is: + // + // extractExistentialType(makeExistential(val, ...)) + // + // and we can just simplify that to type type of `val`. + // + auto val = makeExistential->getWrappedValue(); + auto valType = val->getFullType(); + + addUsersToWorkList(inst); + + inst->replaceUsesWith(valType); + inst->removeAndDeallocate(); + } + } + + void maybeSpecializeLoad(IRLoad* inst) + { + auto ptrArg = inst->ptr.get(); + + if( auto wrapInst = as<IRWrapExistential>(ptrArg) ) + { + // We have an instruction of the form `load(wrapExistential(val, ...))` + // + auto val = wrapInst->getWrappedValue(); + + // We know what type we are expected to + // produce (which should be the pointed-to + // type for whatever the type of the + // `wrapExistential` is). + // + auto resultType = inst->getFullType(); + + IRBuilder builder; + builder.sharedBuilder = &sharedBuilderStorage; + builder.setInsertBefore(inst); + + // We'd *like* to replace this instruction with + // `wrapExistential(load(val))` instead, since that + // will enable subsequent specializations. + // + // To do that, we need to be able to determine + // the type that `load(val)` should return. + // + auto elementType = tryGetPointedToType(&builder, val->getDataType()); + if(!elementType) + return; + + + List<IRInst*> slotOperands; + UInt slotOperandCount = wrapInst->getSlotOperandCount(); + for( UInt ii = 0; ii < slotOperandCount; ++ii ) + { + slotOperands.add(wrapInst->getSlotOperand(ii)); + } + + auto newLoadInst = builder.emitLoad(elementType, val); + auto newWrapExistentialInst = builder.emitWrapExistential( + resultType, + newLoadInst, + slotOperandCount, + slotOperands.getBuffer()); + + addUsersToWorkList(inst); + + inst->replaceUsesWith(newWrapExistentialInst); + inst->removeAndDeallocate(); + } + } + + UInt calcExistentialBoxSlotCount(IRType* type) + { + top: + if( as<IRExistentialBoxType>(type) ) + { + return 2; + } + else if( auto ptrType = as<IRPtrTypeBase>(type) ) + { + type = ptrType->getValueType(); + goto top; + } + else if( auto ptrLikeType = as<IRPointerLikeType>(type) ) + { + type = ptrLikeType->getElementType(); + goto top; + } + else if( auto structType = as<IRStructType>(type) ) + { + UInt count = 0; + for( auto field : structType->getFields() ) + { + count += calcExistentialBoxSlotCount(field->getFieldType()); + } + return count; + } + else + { + return 0; + } + } + + void maybeSpecializeFieldExtract(IRFieldExtract* inst) + { + auto baseArg = inst->getBase(); + auto fieldKey = inst->getField(); + + if( auto wrapInst = as<IRWrapExistential>(baseArg) ) + { + // We have `getField(wrapExistential(val, ...), fieldKey)` + // + auto val = wrapInst->getWrappedValue(); + + // We know what type we are expected to produce. + // + auto resultType = inst->getFullType(); + + IRBuilder builder; + builder.sharedBuilder = &sharedBuilderStorage; + builder.setInsertBefore(inst); + + // We'd *like* to replace this instruction with + // `wrapExistential(getField(val, fieldKey), ...)` instead, since that + // will enable subsequent specializations. + // + // To do that, we need to figure out: + // + // 1. What type that inner `getField` would return (what + // is the type of the `fieldKey` field in `val`?) + // + // 2. Which of the existential slot operands in `...` there + // actually apply to the given field. + // + + // To determine these things, we need the type of + // `val` to be a structure type so that we can look + // up the field corresponding to `fieldKey`. + // + auto valType = val->getDataType(); + auto valStructType = as<IRStructType>(valType); + if(!valStructType) + return; + + UInt slotOperandOffset = 0; + + IRStructField* foundField = nullptr; + for( auto valField : valStructType->getFields() ) + { + if( valField->getKey() == fieldKey ) + { + foundField = valField; + break; + } + + slotOperandOffset += calcExistentialBoxSlotCount(valField->getFieldType()); + } + + if(!foundField) + return; + + auto foundFieldType = foundField->getFieldType(); + + List<IRInst*> slotOperands; + UInt slotOperandCount = calcExistentialBoxSlotCount(foundFieldType); + + for( UInt ii = 0; ii < slotOperandCount; ++ii ) + { + slotOperands.add(wrapInst->getSlotOperand(slotOperandOffset + ii)); + } + + auto newGetField = builder.emitFieldExtract( + foundFieldType, + val, + fieldKey); + + auto newWrapExistentialInst = builder.emitWrapExistential( + resultType, + newGetField, + slotOperandCount, + slotOperands.getBuffer()); + + addUsersToWorkList(inst); + inst->replaceUsesWith(newWrapExistentialInst); + inst->removeAndDeallocate(); + } + } + + + void maybeSpecializeFieldAddress(IRFieldAddress* inst) + { + auto baseArg = inst->getBase(); + auto fieldKey = inst->getField(); + + if( auto wrapInst = as<IRWrapExistential>(baseArg) ) + { + // We have `getFieldAddr(wrapExistential(val, ...), fieldKey)` + // + auto val = wrapInst->getWrappedValue(); + + // We know what type we are expected to produce. + // + auto resultType = inst->getFullType(); + + IRBuilder builder; + builder.sharedBuilder = &sharedBuilderStorage; + builder.setInsertBefore(inst); + + // We'd *like* to replace this instruction with + // `wrapExistential(getFieldAddr(val, fieldKey), ...)` instead, since that + // will enable subsequent specializations. + // + // To do that, we need to figure out: + // + // 1. What type that inner `getFieldAddr` would return (what + // is the type of the `fieldKey` field in `val`?) + // + // 2. Which of the existential slot operands in `...` there + // actually apply to the given field. + // + + // To determine these things, we need the type of + // `val` to be a (pointer to a) structure type so that we can look + // up the field corresponding to `fieldKey`. + // + auto valType = tryGetPointedToType(&builder, val->getDataType()); + if(!valType) + return; + + auto valStructType = as<IRStructType>(valType); + if(!valStructType) + return; + + UInt slotOperandOffset = 0; + + IRStructField* foundField = nullptr; + for( auto valField : valStructType->getFields() ) + { + if( valField->getKey() == fieldKey ) + { + foundField = valField; + break; + } + + slotOperandOffset += calcExistentialBoxSlotCount(valField->getFieldType()); + } + + if(!foundField) + return; + + auto foundFieldType = foundField->getFieldType(); + + List<IRInst*> slotOperands; + UInt slotOperandCount = calcExistentialBoxSlotCount(foundFieldType); + + for( UInt ii = 0; ii < slotOperandCount; ++ii ) + { + slotOperands.add(wrapInst->getSlotOperand(slotOperandOffset + ii)); + } + + auto newGetFieldAddr = builder.emitFieldAddress( + builder.getPtrType(foundFieldType), + val, + fieldKey); + + auto newWrapExistentialInst = builder.emitWrapExistential( + resultType, + newGetFieldAddr, + slotOperandCount, + slotOperands.getBuffer()); + + addUsersToWorkList(inst); + inst->replaceUsesWith(newWrapExistentialInst); + inst->removeAndDeallocate(); + } + } + + UInt calcExistentialTypeParamSlotCount(IRType* type) + { + top: + if( as<IRInterfaceType>(type) ) + { + return 2; + } + else if( auto ptrType = as<IRPtrTypeBase>(type) ) + { + type = ptrType->getValueType(); + goto top; + } + else if( auto ptrLikeType = as<IRPointerLikeType>(type) ) + { + type = ptrLikeType->getElementType(); + goto top; + } + else if( auto structType = as<IRStructType>(type) ) + { + UInt count = 0; + for( auto field : structType->getFields() ) + { + count += calcExistentialTypeParamSlotCount(field->getFieldType()); + } + return count; + } + else + { + return 0; + } + } + + Dictionary<IRSimpleSpecializationKey, IRStructType*> existentialSpecializedStructs; + + void maybeSpecializeBindExistentialsType(IRBindExistentialsType* type) + { + auto baseType = type->getBaseType(); + UInt slotOperandCount = type->getExistentialArgCount(); + + IRBuilder builder; + builder.sharedBuilder = &sharedBuilderStorage; + builder.setInsertBefore(type); + + if( auto baseInterfaceType = as<IRInterfaceType>(baseType) ) + { + // A `BindExistentials<ISomeInterface, ConcreteType, ...>` can + // just be simplified to `ExistentialBox<ConcreteType>`. + // + // Note: We do *not* simplify straight to `ConcreteType`, because + // that would mess up the layout for aggregate types that + // contain interfaces. The logical indirection introduced + // by `ExistentialBox<...>` will be handled by a later type + // legalization pass that moved the type "pointed to" by + // the box out of line from other fields. + + // We always expect two slot operands, one for the concrete type + // and one for the witness table. + // + SLANG_ASSERT(slotOperandCount == 2); + if(slotOperandCount <= 1) return; + + auto concreteType = (IRType*) type->getExistentialArg(0); + auto newVal = builder.getPtrType(kIROp_ExistentialBoxType, concreteType); + + addUsersToWorkList(type); + type->replaceUsesWith(newVal); + type->removeAndDeallocate(); + return; + } + else if( auto basePtrLikeType = as<IRPointerLikeType>(baseType) ) + { + // A `BindExistentials<P<T>, ...>` can be simplified to + // `P<BindExistentials<T, ...>>` when `P` is a pointer-like + // type constructor. + // + auto baseElementType = basePtrLikeType->getElementType(); + IRInst* wrappedElementType = builder.getBindExistentialsType( + baseElementType, + slotOperandCount, + type->getExistentialArgs()); + addToWorkList(wrappedElementType); + + auto newPtrLikeType = builder.getType( + basePtrLikeType->op, + 1, + &wrappedElementType); + addToWorkList(newPtrLikeType); + + addUsersToWorkList(type); + type->replaceUsesWith(newPtrLikeType); + type->removeAndDeallocate(); + return; + } + else if( auto baseStructType = as<IRStructType>(baseType) ) + { + // In order to bind a `struct` type we will generate + // a new specialized `struct` type on demand and then + // cache and re-use it. + // + // We don't want to start specializing here unless + // all the operand types (and witness tables) we + // will be specializing to are themselves fully + // specialized, so that we can be sure that we + // have a unique type. + // + if( !areAllOperandsFullySpecialized(type) ) + return; + + // Now we we check to see if we've already created + // a specialized struct type or not. + // + IRSimpleSpecializationKey key; + key.vals.add(baseStructType); + for( UInt ii = 0; ii < slotOperandCount; ++ii ) + { + key.vals.add(type->getExistentialArg(ii)); + } + + IRStructType* newStructType = nullptr; + if( !existentialSpecializedStructs.TryGetValue(key, newStructType) ) + { + builder.setInsertBefore(baseStructType); + newStructType = builder.createStructType(); + + auto fieldSlotArgs = type->getExistentialArgs(); + + for( auto oldField : baseStructType->getFields() ) + { + // TODO: we need to figure out which of the specialization arguments + // apply to this field... + + auto oldFieldType = oldField->getFieldType(); + auto fieldSlotArgCount = calcExistentialTypeParamSlotCount(oldFieldType); + + auto newFieldType = builder.getBindExistentialsType( + oldFieldType, + fieldSlotArgCount, + fieldSlotArgs); + + addToWorkList(newFieldType); + + fieldSlotArgs += fieldSlotArgCount; + + builder.createStructField(newStructType, oldField->getKey(), newFieldType); + } + + existentialSpecializedStructs.Add(key, newStructType); + addToWorkList(newStructType); + } + + addUsersToWorkList(type); + type->replaceUsesWith(newStructType); + type->removeAndDeallocate(); + return; + + } + } + + // The handling of specialization for global generic type + // parameters involves searching for all `bind_global_generic_param` + // instructions in the input module. + // + void specializeGlobalGenericParameters() + { + auto moduleInst = module->getModuleInst(); + for(auto inst : moduleInst->getChildren()) + { + // We only want to consider the `bind_global_generic_param` + // instructions, and ignore everything else. + // + auto bindInst = as<IRBindGlobalGenericParam>(inst); + if(!bindInst) + continue; + + // HACK: Our current front-end emit logic can end up emitting multiple + // `bind_global_generic_param` instructions for the same parameter. This is + // a buggy behavior, but a real fix would require refactoring the way + // global generic arguments are specified today. + // + // For now we will do a sanity check to detect parameters that + // have already been specialized. + // + if( !as<IRGlobalGenericParam>(bindInst->getOperand(0)) ) + { + // The "parameter" operand is no longer a parameter, so it + // seems things must have been specialized already. + // + continue; + } + + // The actual logic for applying the substitution is + // almost trivial: we will replace any uses of the + // global generic parameter with its desired value. + // + auto param = bindInst->getParam(); + auto val = bindInst->getVal(); + param->replaceUsesWith(val); + } + { + // Now that we've replaced any uses of global generic + // parameters, we will do a second pass to remove + // the parameters and any `bind_global_generic_param` + // instructions, since both should be dead/unused. + // + IRInst* next = nullptr; + for(auto inst = moduleInst->getFirstChild(); inst; inst = next) + { + next = inst->getNextInst(); + + switch(inst->op) + { + default: + break; + + case kIROp_GlobalGenericParam: + case kIROp_BindGlobalGenericParam: + // A `bind_global_generic_param` instruction should + // have no uses in the first place, and all the global + // generic parameters should have had their uses replaced. + // + SLANG_ASSERT(!inst->firstUse); + inst->removeAndDeallocate(); + break; + } + } + } + } +}; + +void specializeModule( + IRModule* module) +{ + SpecializationContext context; + context.module = module; + context.processModule(); +} + + +IRInst* specializeGenericImpl( + IRGeneric* genericVal, + IRSpecialize* specializeInst, + IRModule* module, + SpecializationContext* context) +{ + // Effectively, specializing a generic amounts to "calling" the generic + // on its concrete argument values and computing the + // result it returns. + // + // For now, all of our generics consist of a single + // basic block, so we can "call" them just by + // cloning the instructions in their single block + // into the global scope, using an environment for + // cloning that maps the generic parameters to + // the concrete arguments that were provided + // by the `specialize(...)` instruction. + // + IRCloneEnv env; + + // We will walk through the parameters of the generic and + // register the corresponding argument of the `specialize` + // instruction to be used as the "cloned" value for each + // parameter. + // + // Suppose we are looking at `specialize(g, a, b, c)` and `g` has + // three generic parameters: `T`, `U`, and `V`. Then we will + // be initializing our environment to map `T -> a`, `U -> b`, + // and `V -> c`. + // + UInt argCounter = 0; + for( auto param : genericVal->getParams() ) + { + UInt argIndex = argCounter++; + SLANG_ASSERT(argIndex < specializeInst->getArgCount()); + + IRInst* arg = specializeInst->getArg(argIndex); + + env.mapOldValToNew.Add(param, arg); + } + + // We will set up an IR builder for insertion + // into the global scope, at the same location + // as the original generic. + // + SharedIRBuilder sharedBuilderStorage; + sharedBuilderStorage.module = module; + sharedBuilderStorage.session = module->getSession(); + + IRBuilder builderStorage; + IRBuilder* builder = &builderStorage; + builder->sharedBuilder = &sharedBuilderStorage; + builder->setInsertBefore(genericVal); + + // Now we will run through the body of the generic and + // clone each of its instructions into the global scope, + // until we reach a `return` instruction. + // + for( auto bb : genericVal->getBlocks() ) + { + // We expect a generic to only ever contain a single block. + // + SLANG_ASSERT(bb == genericVal->getFirstBlock()); + + // We will iterate over the non-parameter ("ordinary") + // instructions only, because parameters were dealt + // with explictly at an earlier point. + // + for( auto ii : bb->getOrdinaryInsts() ) + { + // The last block of the generic is expected to end with + // a `return` instruction for the specialized value that + // comes out of the abstraction. + // + // We thus use that cloned value as the result of the + // specialization step. + // + if( auto returnValInst = as<IRReturnVal>(ii) ) + { + auto specializedVal = findCloneForOperand(&env, returnValInst->getVal()); + return specializedVal; + } + + // For any instruction other than a `return`, we will + // simply clone it completely into the global scope. + // + IRInst* clonedInst = cloneInst(&env, builder, ii); + + // Any new instructions we create during cloning were + // not present when we initially built our work list, + // so we need to make sure to consider them now. + // + // This is important for the cases where one generic + // invokes another, because there will be `specialize` + // operations nested inside the first generic that refer + // to the second. + // + if( context ) + { + context->addToWorkList(clonedInst); + } + } + } + + // If we reach this point, something went wrong, because we + // never encountered a `return` inside the body of the generic. + // + SLANG_UNEXPECTED("no return from generic"); + UNREACHABLE_RETURN(nullptr); +} + +IRInst* specializeGeneric( + IRSpecialize* specializeInst) +{ + auto baseGeneric = as<IRGeneric>(specializeInst->getBase()); + SLANG_ASSERT(baseGeneric); + if(!baseGeneric) return specializeInst; + + auto module = specializeInst->getModule(); + SLANG_ASSERT(module); + if(!module) return specializeInst; + + return specializeGenericImpl(baseGeneric, specializeInst, module, nullptr); +} + + +} // namespace Slang |
