diff options
| author | Yong He <yonghe@outlook.com> | 2024-09-10 08:13:21 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-09-10 08:13:21 -0700 |
| commit | 936c22a9a938744eb43c310dd82c9c6944f79e87 (patch) | |
| tree | ece9880056c94415379cd89303a4542894fb7f32 /source | |
| parent | f51b74ddee7ec7104d021006575c601245814bb1 (diff) | |
Specialize existential return types when possible. (#5044)
* Fix inccorect dropping of declref during Unification of DeclaredSubtypeWitness.
* Add extension test.
* Specialize existential return types when possible.
* Fix.
* Fix.
* Fix falcor issue.
Diffstat (limited to 'source')
| -rw-r--r-- | source/slang/slang-emit.cpp | 4 | ||||
| -rw-r--r-- | source/slang/slang-ir-inst-defs.h | 5 | ||||
| -rw-r--r-- | source/slang/slang-ir-insts.h | 16 | ||||
| -rw-r--r-- | source/slang/slang-ir-specialize.cpp | 121 |
4 files changed, 137 insertions, 9 deletions
diff --git a/source/slang/slang-emit.cpp b/source/slang/slang-emit.cpp index 2ccf075f3..cdd2ca5b6 100644 --- a/source/slang/slang-emit.cpp +++ b/source/slang/slang-emit.cpp @@ -841,6 +841,10 @@ Result linkAndOptimizeIR( { simplifyIR(targetProgram, irModule, fastIRSimplificationOptions, sink); } + else if (requiredLoweringPassSet.generics) + { + eliminateDeadCode(irModule, fastIRSimplificationOptions.deadCodeElimOptions); + } if (!ArtifactDescUtil::isCpuLikeTarget(artifactDesc) && targetProgram->getOptionSet().shouldRunNonEssentialValidation()) diff --git a/source/slang/slang-ir-inst-defs.h b/source/slang/slang-ir-inst-defs.h index afc09f480..b526df3a9 100644 --- a/source/slang/slang-ir-inst-defs.h +++ b/source/slang/slang-ir-inst-defs.h @@ -772,6 +772,11 @@ INST_RANGE(BindingQuery, GetRegisterIndex, GetRegisterSpace) needs to be special cased for lookup. */ INST(TransitoryDecoration, transitory, 0, 0) + // The result witness table that the functon's return type is a subtype of an interface. + // This is used to keep track of the original witness table in a function that used to + // return an existential value but now returns a concrete type after specialization. + INST(ResultWitnessDecoration, ResultWitness, 1, 0) + INST(VulkanRayPayloadDecoration, vulkanRayPayload, 0, 0) INST(VulkanRayPayloadInDecoration, vulkanRayPayloadIn, 0, 0) INST(VulkanHitAttributesDecoration, vulkanHitAttributes, 0, 0) diff --git a/source/slang/slang-ir-insts.h b/source/slang/slang-ir-insts.h index a0ed8ff0e..69f129986 100644 --- a/source/slang/slang-ir-insts.h +++ b/source/slang/slang-ir-insts.h @@ -799,6 +799,17 @@ struct IRSequentialIDDecoration : IRDecoration IRIntegerValue getSequentialID() { return getSequentialIDOperand()->getValue(); } }; +struct IRResultWitnessDecoration : IRDecoration +{ + enum + { + kOp = kIROp_ResultWitnessDecoration + }; + IR_LEAF_ISA(ResultWitnessDecoration) + + IRInst* getWitness() { return getOperand(0); } +}; + struct IRDynamicDispatchWitnessDecoration : IRDecoration { IR_LEAF_ISA(DynamicDispatchWitnessDecoration) @@ -4540,6 +4551,11 @@ public: void addHighLevelDeclDecoration(IRInst* value, Decl* decl); + IRDecoration* addResultWitnessDecoration(IRInst* value, IRInst* witness) + { + return addDecoration(value, kIROp_ResultWitnessDecoration, witness); + } + IRDecoration* addTargetSystemValueDecoration(IRInst* value, UnownedStringSlice sysValName, UInt index = 0) { IRInst* operands[] = { getStringValue(sysValName), getIntValue(getIntType(), index)}; diff --git a/source/slang/slang-ir-specialize.cpp b/source/slang/slang-ir-specialize.cpp index a56dae025..519c4b260 100644 --- a/source/slang/slang-ir-specialize.cpp +++ b/source/slang/slang-ir-specialize.cpp @@ -1204,6 +1204,38 @@ struct SpecializationContext return false; } + // Is the function's actual return type statically known? + // If so can we specialize the function even if it has no existential parameters. + // + bool isExistentialReturnTypeSpecializable(IRFunc* callee) + { + if (!as<IRInterfaceType>(callee->getResultType())) + return false; + + IRInst* witness = nullptr; + + for (auto block : callee->getBlocks()) + { + if (auto returnInst = as<IRReturn>(block->getTerminator())) + { + if (auto makeExistential = as<IRMakeExistential>(returnInst->getVal())) + { + if (witness == nullptr) + witness = makeExistential->getWitnessTable(); + else if (witness != makeExistential->getWitnessTable()) + return false; + if (isChildInstOf(witness, callee)) + return false; + } + else + { + return false; + } + } + } + return true; + } + // 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 @@ -1243,9 +1275,10 @@ struct SpecializationContext return false; // We shouldn't bother specializing unless the callee has at least - // one parameter that has an existential/interface type. + // one parameter/return type that has an existential/interface type. // - bool shouldSpecialize = false; + bool returnTypeNeedSpecialization = isExistentialReturnTypeSpecializable(calleeFunc); + bool argumentNeedSpecialization = false; UInt argCounter = 0; for (auto param : calleeFunc->getParams()) { @@ -1253,18 +1286,18 @@ struct SpecializationContext 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 false; + argumentNeedSpecialization = true; } - // If we never found a parameter worth specializing, we should bail out. + + // If we never found a parameter or return type worth specializing, we should bail out. // - if (!shouldSpecialize) + if (!returnTypeNeedSpecialization && !argumentNeedSpecialization) return false; // At this point, we believe we *should* and *can* specialize. @@ -1341,7 +1374,7 @@ struct SpecializationContext } else { - SLANG_UNEXPECTED("missing case for existential argument"); + SLANG_UNEXPECTED("unhandled existential argument"); } } @@ -1409,8 +1442,20 @@ struct SpecializationContext auto builder = &builderStorage; builder->setInsertBefore(inst); - auto newCall = builder->emitCallInst( - inst->getFullType(), specializedCallee, (UInt)newArgs.getCount(), newArgs.getArrayView().getBuffer()); + auto callResultType = specializedCallee->getResultType(); + IRInst* newCall = builder->emitCallInst( + callResultType, specializedCallee, (UInt)newArgs.getCount(), newArgs.getArrayView().getBuffer()); + + if (as<IRInterfaceType>(inst->getDataType())) + { + // If the result of the original call is specialized to a concrete type, + // we need to wrap it back into an existential type. + // + if (auto resultWitnessDecor = specializedCallee->findDecoration<IRResultWitnessDecoration>()) + { + newCall = builder->emitMakeExistential(inst->getDataType(), newCall, resultWitnessDecor->getWitness()); + } + } // We will completely replace the old `call` instruction with the // new one, and will go so far as to transfer any decorations @@ -1765,6 +1810,62 @@ struct SpecializationContext simplifyFunc(targetProgram, newFunc, IRSimplificationOptions::getFast(targetProgram)); + if (as<IRInterfaceType>(newFunc->getResultType())) + { + // If th result type is an interface type, and all return values are of the same + // concrete type, we can simplify the function to return the concrete type. + // We also need to mark the simplfiied function with a result witness decoration + // so we can rewrite all the callsites into IRMakeExistential using the witness. + // This is effectively pushing the MakeExistential to the call sites, so optimizations + // can happen across the function call boundaries. + IRInst* witnessTable = nullptr; + IRInst* concreteType = nullptr; + for (auto block : newFunc->getBlocks()) + { + if (auto returnInst = as<IRReturn>(block->getTerminator())) + { + if (auto makeExistential = as<IRMakeExistential>(returnInst->getVal())) + { + if (!concreteType) + { + concreteType = makeExistential->getWrappedValue()->getDataType(); + witnessTable = makeExistential->getWitnessTable(); + } + else if (concreteType != makeExistential->getWrappedValue()->getDataType()) + { + concreteType = nullptr; + break; + } + if (isChildInstOf(witnessTable, newFunc)) + { + concreteType = nullptr; + break; + } + } + else + { + concreteType = nullptr; + break; + } + } + } + if (concreteType) + { + for (auto block : newFunc->getBlocks()) + { + if (auto returnInst = as<IRReturn>(block->getTerminator())) + { + if (auto makeExistential = as<IRMakeExistential>(returnInst->getVal())) + { + returnInst->setOperand(0, makeExistential->getWrappedValue()); + } + } + } + builder->addResultWitnessDecoration(newFunc, witnessTable); + fixUpFuncType(newFunc, (IRType*)concreteType); + } + } + return newFunc; } @@ -2758,12 +2859,14 @@ void finalizeSpecialization(IRModule* module) break; case kIROp_StructKey: + case kIROp_Func: for (auto decor = inst->getFirstDecoration(); decor; ) { auto nextDecor = decor->getNextDecoration(); switch (decor->getOp()) { case kIROp_DispatchFuncDecoration: + case kIROp_ResultWitnessDecoration: decor->removeAndDeallocate(); break; default: |
