diff options
Diffstat (limited to 'source/slang/slang-ir-specialize.cpp')
| -rw-r--r-- | source/slang/slang-ir-specialize.cpp | 231 |
1 files changed, 127 insertions, 104 deletions
diff --git a/source/slang/slang-ir-specialize.cpp b/source/slang/slang-ir-specialize.cpp index b9a8f68ab..3ef79df28 100644 --- a/source/slang/slang-ir-specialize.cpp +++ b/source/slang/slang-ir-specialize.cpp @@ -4,6 +4,7 @@ #include "slang-ir.h" #include "slang-ir-clone.h" #include "slang-ir-insts.h" +#include "slang-ir-ssa-simplification.h" namespace Slang { @@ -258,6 +259,10 @@ struct SpecializationContext // IRInst* specializedVal = specializeGenericImpl(genericVal, specializeInst, module, this); + // The body of the specialized generic may expose more specialization opportunities, so + // we add the children to workList. + for (auto child : specializedVal->getDecorationsAndChildren()) + addToWorkList(child); // The value that was returned from evaluating // the generic is the specialized value, and we @@ -341,7 +346,7 @@ struct SpecializationContext // `specialize(g, a, b, c, ...)` instruction and performs // specialization if it is possible. // - void maybeSpecializeGeneric( + bool maybeSpecializeGeneric( IRSpecialize* specInst) { // We will only attempt to specialize when all of the @@ -349,7 +354,7 @@ struct SpecializationContext // themselves fully specialized. // if(!areAllOperandsFullySpecialized(specInst)) - return; + return false; // The invariant that the arguments are fully specialized // should mean that `a, b, c, ...` are in a form that @@ -362,13 +367,13 @@ struct SpecializationContext auto baseVal = specInst->getBase(); auto genericVal = as<IRGeneric>(baseVal); if(!genericVal) - return; + return false; // We can also only specialize a generic if it // represents a definition rather than a declaration. // if(!canSpecializeGeneric(genericVal)) - return; + return false; // Once we know that specialization is possible, // the actual work is fairly simple. @@ -391,6 +396,8 @@ struct SpecializationContext // specInst->replaceUsesWith(specializedVal); specInst->removeAndDeallocate(); + + return true; } // Generic specialization depends on identifying when @@ -493,7 +500,7 @@ struct SpecializationContext // at a time, and try to perform whatever specialization // is appropriate based on its opcode. // - void maybeSpecializeInst( + bool maybeSpecializeInst( IRInst* inst) { switch(inst->getOp()) @@ -502,14 +509,13 @@ struct SpecializationContext // By default we assume that specialization is // not possible for a given opcode. // - break; + return false; case kIROp_Specialize: // The logic for specializing a `specialize(...)` // instruction has already been elaborated above. // - maybeSpecializeGeneric(cast<IRSpecialize>(inst)); - break; + return maybeSpecializeGeneric(cast<IRSpecialize>(inst)); case kIROp_lookup_interface_method: // The remaining case we need to consider here for generics @@ -518,8 +524,7 @@ struct SpecializationContext // because we can specialize it to just be a direct // reference to the actual witness value from the table. // - maybeSpecializeWitnessLookup(cast<IRLookupWitnessMethod>(inst)); - break; + return maybeSpecializeWitnessLookup(cast<IRLookupWitnessMethod>(inst)); case kIROp_Call: // When writing functions with existential-type parameters, @@ -527,8 +532,7 @@ struct SpecializationContext // function based on the concrete type encapsulated in // an argument of existential type. // - maybeSpecializeExistentialsForCall(cast<IRCall>(inst)); - break; + return maybeSpecializeExistentialsForCall(cast<IRCall>(inst)); // The specialization of functions with existential-type // parameters can create further opportunities for specialization, @@ -536,36 +540,27 @@ struct SpecializationContext // through local simplification on values of existential type. // case kIROp_ExtractExistentialType: - maybeSpecializeExtractExistentialType(inst); - break; + return maybeSpecializeExtractExistentialType(inst); case kIROp_ExtractExistentialValue: - maybeSpecializeExtractExistentialValue(inst); - break; + return maybeSpecializeExtractExistentialValue(inst); case kIROp_ExtractExistentialWitnessTable: - maybeSpecializeExtractExistentialWitnessTable(inst); - break; + return maybeSpecializeExtractExistentialWitnessTable(inst); case kIROp_Load: - maybeSpecializeLoad(as<IRLoad>(inst)); - break; + return maybeSpecializeLoad(as<IRLoad>(inst)); case kIROp_FieldExtract: - maybeSpecializeFieldExtract(as<IRFieldExtract>(inst)); - break; + return maybeSpecializeFieldExtract(as<IRFieldExtract>(inst)); case kIROp_FieldAddress: - maybeSpecializeFieldAddress(as<IRFieldAddress>(inst)); - break; + return maybeSpecializeFieldAddress(as<IRFieldAddress>(inst)); case kIROp_getElement: - maybeSpecializeGetElement(as<IRGetElement>(inst)); - break; + return maybeSpecializeGetElement(as<IRGetElement>(inst)); case kIROp_getElementPtr: - maybeSpecializeGetElementAddress(as<IRGetElementPtr>(inst)); - break; + return maybeSpecializeGetElementAddress(as<IRGetElementPtr>(inst)); case kIROp_BindExistentialsType: - maybeSpecializeBindExistentialsType(as<IRBindExistentialsType>(inst)); - break; + return maybeSpecializeBindExistentialsType(as<IRBindExistentialsType>(inst)); } } @@ -573,7 +568,7 @@ struct SpecializationContext // transformation that helps with both generic and // existential-based code. // - void maybeSpecializeWitnessLookup( + bool maybeSpecializeWitnessLookup( IRLookupWitnessMethod* lookupInst) { // Note: While we currently have named the instruction @@ -590,7 +585,7 @@ struct SpecializationContext // auto witnessTable = as<IRWitnessTable>(lookupInst->getWitnessTable()); if(!witnessTable) - return; + return false; // Because we have a concrete witness table, we can // use it to look up the IR value that satisfies @@ -605,7 +600,7 @@ struct SpecializationContext // we cannot find a concrete value to use. // if(!satisfyingVal) - return; + return false; // At this point, we know that `satisfyingVal` is what // would result from executing this `lookup_witness_method` @@ -619,6 +614,8 @@ struct SpecializationContext addUsersToWorkList(lookupInst); lookupInst->replaceUsesWith(satisfyingVal); lookupInst->removeAndDeallocate(); + + return true; } // The above subroutine needed a way to look up @@ -727,6 +724,8 @@ struct SpecializationContext SharedIRBuilder* sharedBuilder = &sharedBuilderStorage; sharedBuilder->init(module); + bool changed = true; + // Read specialization dictionary from module if it is defined. // This prevents us from generating duplicated specializations // when this pass is invoked iteratively. @@ -771,60 +770,65 @@ struct SpecializationContext // We start out simple by putting the root instruction for the // module onto our work list. // - addToWorkList(module->getModuleInst()); - - while(workList.Count() != 0) + while (changed) { + changed = false; + addToWorkList(module->getModuleInst()); - // We will then iterate until our work list goes dry. - // - while(workList.Count() != 0) - { - IRInst* inst = workList.getLast(); + while (workList.Count() != 0) + { + // We will then iterate until our work list goes dry. + // + while (workList.Count() != 0) + { + IRInst* inst = workList.getLast(); - workList.removeLast(); + workList.removeLast(); - cleanInsts.Add(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); + // 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); + // Next we will look for all the general-purpose + // specialization opportunities (generic specialization, + // existential specialization, simplifications, etc.) + // + changed |= 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); - } - } + // 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()); + addDirtyInstsToWorkListRec(module->getModuleInst()); + } + if (changed) + simplifyIR(module); } // Once the work list has gone dry, we should have the invariant @@ -968,19 +972,19 @@ struct SpecializationContext // call site it is statically clear what concrete type(s) the arguments // will have. // - void maybeSpecializeExistentialsForCall(IRCall* inst) + bool maybeSpecializeExistentialsForCall(IRCall* inst) { // Handle a special case of `StructuredBuffer.operator[]/Load/Consume` // calls first. These calls on builtin generic types should be handled // the same way as a `load` inst. if (maybeSpecializeBufferLoadCall(inst)) - return; + return false; // We can only specialize a call when the callee function is known. // auto calleeFunc = as<IRFunc>(inst->getCallee()); if(!calleeFunc) - return; + return false; // Update result type since the callee may have been changed. if (inst->getDataType() != calleeFunc->getResultType()) @@ -991,7 +995,7 @@ struct SpecializationContext // We can only specialize if we have access to a body for the callee. // if(!calleeFunc->isDefinition()) - return; + return false; // We shouldn't bother specializing unless the callee has at least // one parameter that has an existential/interface type. @@ -1010,13 +1014,13 @@ struct SpecializationContext // to such a parameter is one we can specialize. // if( !canSpecializeExistentialArg(arg)) - return; + return false; } // If we never found a parameter worth specializing, we should bail out. // if(!shouldSpecialize) - return; + return false; // At this point, we believe we *should* and *can* specialize. // @@ -1177,6 +1181,8 @@ struct SpecializationContext // for specialization, but we can always play it safe. // addUsersToWorkList(newCall); + + return true; } // The above `maybeSpecializeExistentialsForCall` routine needed @@ -1530,7 +1536,7 @@ struct SpecializationContext // Let's start with the routine for the case above of extracting // a witness table. // - void maybeSpecializeExtractExistentialWitnessTable(IRInst* inst) + bool maybeSpecializeExtractExistentialWitnessTable(IRInst* inst) { // We know `inst` is `extractExistentialWitnessTable(existentialArg)`. // @@ -1555,13 +1561,15 @@ struct SpecializationContext inst->replaceUsesWith(witnessTable); inst->removeAndDeallocate(); + return true; } + return false; } // The cases for simplifying `extractExistentialValue` is more or less the same // as for witness tables. // - void maybeSpecializeExtractExistentialValue(IRInst* inst) + bool maybeSpecializeExtractExistentialValue(IRInst* inst) { // We know `inst` is `extractExistentialValue(existentialArg)`. // @@ -1580,13 +1588,15 @@ struct SpecializationContext inst->replaceUsesWith(val); inst->removeAndDeallocate(); + return true; } + return false; } // The cases for simplifying `extractExistentialType` is more or less the same // as for witness tables. // - void maybeSpecializeExtractExistentialType(IRInst* inst) + bool maybeSpecializeExtractExistentialType(IRInst* inst) { // We know `inst` is `extractExistentialValue(existentialArg)`. // @@ -1606,10 +1616,12 @@ struct SpecializationContext inst->replaceUsesWith(valType); inst->removeAndDeallocate(); + return true; } + return false; } - void maybeSpecializeLoad(IRLoad* inst) + bool maybeSpecializeLoad(IRLoad* inst) { auto ptrArg = inst->ptr.get(); @@ -1638,7 +1650,7 @@ struct SpecializationContext // auto elementType = tryGetPointedToType(&builder, val->getDataType()); if(!elementType) - return; + return false; List<IRInst*> slotOperands; @@ -1659,7 +1671,9 @@ struct SpecializationContext inst->replaceUsesWith(newWrapExistentialInst); inst->removeAndDeallocate(); + return true; } + return false; } UInt calcExistentialBoxSlotCount(IRType* type) @@ -1708,7 +1722,7 @@ struct SpecializationContext } } - void maybeSpecializeFieldExtract(IRFieldExtract* inst) + bool maybeSpecializeFieldExtract(IRFieldExtract* inst) { auto baseArg = inst->getBase(); auto fieldKey = inst->getField(); @@ -1746,7 +1760,7 @@ struct SpecializationContext auto valType = val->getDataType(); auto valStructType = as<IRStructType>(valType); if(!valStructType) - return; + return false; UInt slotOperandOffset = 0; @@ -1763,7 +1777,7 @@ struct SpecializationContext } if(!foundField) - return; + return false; auto foundFieldType = foundField->getFieldType(); @@ -1789,11 +1803,13 @@ struct SpecializationContext addUsersToWorkList(inst); inst->replaceUsesWith(newWrapExistentialInst); inst->removeAndDeallocate(); + return true; } + return false; } - void maybeSpecializeFieldAddress(IRFieldAddress* inst) + bool maybeSpecializeFieldAddress(IRFieldAddress* inst) { auto baseArg = inst->getBase(); auto fieldKey = inst->getField(); @@ -1830,11 +1846,11 @@ struct SpecializationContext // auto valType = tryGetPointedToType(&builder, val->getDataType()); if(!valType) - return; + return false; auto valStructType = as<IRStructType>(valType); if(!valStructType) - return; + return false; UInt slotOperandOffset = 0; @@ -1851,7 +1867,7 @@ struct SpecializationContext } if(!foundField) - return; + return false; auto foundFieldType = foundField->getFieldType(); @@ -1877,10 +1893,12 @@ struct SpecializationContext addUsersToWorkList(inst); inst->replaceUsesWith(newWrapExistentialInst); inst->removeAndDeallocate(); + return true; } + return false; } - void maybeSpecializeGetElement(IRGetElement* inst) + bool maybeSpecializeGetElement(IRGetElement* inst) { auto baseArg = inst->getBase(); if (auto wrapInst = as<IRWrapExistential>(baseArg)) @@ -1914,10 +1932,12 @@ struct SpecializationContext addUsersToWorkList(inst); inst->replaceUsesWith(newWrapExistentialInst); inst->removeAndDeallocate(); + return true; } + return false; } - void maybeSpecializeGetElementAddress(IRGetElementPtr* inst) + bool maybeSpecializeGetElementAddress(IRGetElementPtr* inst) { auto baseArg = inst->getBase(); if (auto wrapInst = as<IRWrapExistential>(baseArg)) @@ -1954,7 +1974,9 @@ struct SpecializationContext addUsersToWorkList(inst); inst->replaceUsesWith(newWrapExistentialInst); inst->removeAndDeallocate(); + return true; } + return false; } UInt calcExistentialTypeParamSlotCount(IRType* type) @@ -1996,7 +2018,7 @@ struct SpecializationContext Dictionary<IRSimpleSpecializationKey, IRStructType*> existentialSpecializedStructs; - void maybeSpecializeBindExistentialsType(IRBindExistentialsType* type) + bool maybeSpecializeBindExistentialsType(IRBindExistentialsType* type) { auto baseType = type->getBaseType(); UInt slotOperandCount = type->getExistentialArgCount(); @@ -2010,7 +2032,7 @@ struct SpecializationContext // and one for the witness table. // SLANG_ASSERT(slotOperandCount == 2); - if(slotOperandCount < 2) return; + if(slotOperandCount < 2) return false; auto concreteType = (IRType*) type->getExistentialArg(0); auto witnessTable = type->getExistentialArg(1); @@ -2019,7 +2041,7 @@ struct SpecializationContext addUsersToWorkList(type); type->replaceUsesWith(newVal); type->removeAndDeallocate(); - return; + return true; } else if( as<IRPointerLikeType>(baseType) || as<IRHLSLStructuredBufferTypeBase>(baseType) || @@ -2052,7 +2074,7 @@ struct SpecializationContext type->replaceUsesWith(newPtrLikeType); type->removeAndDeallocate(); - return; + return true; } else if( auto baseStructType = as<IRStructType>(baseType) ) { @@ -2067,7 +2089,7 @@ struct SpecializationContext // have a unique type. // if( !areAllOperandsFullySpecialized(type) ) - return; + return false; // Now we we check to see if we've already created // a specialized struct type or not. @@ -2115,9 +2137,10 @@ struct SpecializationContext type->replaceUsesWith(newStructType); type->removeAndDeallocate(); - return; + return true; } + return false; } // The handling of specialization for global generic type |
