// slang-ir-ssa-register-allocate.cpp #include "slang-ir-ssa-register-allocate.h" #include "slang-ir-dominators.h" #include "slang-ir-insts.h" #include "slang-ir-reachability.h" #include "slang-ir-util.h" #include "slang-ir.h" namespace Slang { struct RegisterAllocateContext { OrderedDictionary>> mapTypeToRegisterList; bool allocateForCompositeTypeOnly; RegisterAllocateContext(bool compositeTypeOnly) : allocateForCompositeTypeOnly(compositeTypeOnly) { } List>& getRegisterListForType(IRType* type) { if (auto list = mapTypeToRegisterList.tryGetValue(type)) { return *list; } mapTypeToRegisterList[type] = List>(); return mapTypeToRegisterList[type].getValue(); } void assignInstToNewRegister(List>& regList, IRInst* inst) { auto reg = new RegisterInfo(); reg->type = inst->getFullType(); reg->insts.add(inst); regList.add(reg); } bool areInstsPreferredToBeCoalescedImpl(IRInst* inst0, IRInst* inst1) { switch (inst1->getOp()) { case kIROp_UpdateElement: if (inst0 == inst1->getOperand(0)) return true; break; default: break; } // If insts have the same name, prefer to coalesce them. auto name1 = inst0->findDecoration(); auto name2 = inst1->findDecoration(); if (name1 && name2 && name1->getName() == name2->getName()) return true; return false; } bool areInstsPreferredToBeCoalesced(IRInst* inst0, IRInst* inst1) { return areInstsPreferredToBeCoalescedImpl(inst0, inst1) || areInstsPreferredToBeCoalescedImpl(inst1, inst0); } bool isRegisterPreferred( RegisterInfo* existingRegister, RegisterInfo* newRegister, IRInst* inst) { int preferredCountExistingReg = 0; int preferredCountNewReg = 0; for (auto existingInst : existingRegister->insts) { if (areInstsPreferredToBeCoalesced(existingInst, inst)) preferredCountExistingReg++; } for (auto existingInst : newRegister->insts) { if (areInstsPreferredToBeCoalesced(existingInst, inst)) preferredCountNewReg++; } return preferredCountNewReg > preferredCountExistingReg; } bool canCoalesce(IRInst* inst1, IRInst* inst2) { // If two insts are Phis from the same block, don't coalesce. // This logic should not be needed in most cases because params from the same block should // always interfere anyways. However if a param is never used for for // some reason not DCE'd out, we don't want it to share the same register as another // param to avoid problems during phi elimination. if (inst1->getParent() == inst2->getParent() && inst1->getOp() == kIROp_Param && inst2->getOp() == kIROp_Param) return false; // If two insts are coming from two separate user defined names, don't coalesce them into // the same register. auto name1 = inst1->findDecoration(); auto name2 = inst2->findDecoration(); if (name1 && !name2 || !name1 && name2) return true; if (!name1 || !name2) return true; if (name1->getName() != name2->getName()) return false; return true; } bool isUseOfParamAfterPhiAssignment( IRDominatorTree* dom, IRUse* useToTest, IRInst* phiParam, IRInst* phiArg) { IRParam* param = as(phiParam); if (!param) return false; IRUse* branchUse = nullptr; for (auto use = phiArg->firstUse; use; use = use->nextUse) { if (use->getUser()->getOp() == kIROp_UnconditionalBranch) { if (!branchUse) branchUse = use; else { // If arg is being used in more than one branch, don't handle it. return false; } } } if (!branchUse) return false; auto branch = as(branchUse->getUser()); auto branchTarget = branch->getTargetBlock(); if (param->getParent() != branchTarget) return false; auto paramIndex = getParamIndexInBlock(param); if (paramIndex >= (int)branch->getArgCount() || paramIndex == -1) return false; if (branch->getArg(paramIndex) != phiArg) return false; // If we reach here, then phiArg is indeed used as arg for phiParam. // We will allow any use of phiParam when phiArg isn't live. if (dom->dominates(phiArg, useToTest->getUser())) return false; return true; } RegisterAllocationResult allocateRegisters( IRGlobalValueWithCode* func, RefPtr& inOutDom) { ReachabilityContext reachabilityContext(func); mapTypeToRegisterList.clear(); auto dom = computeDominatorTree(func); inOutDom = dom; // Note that if inst A does not dominate inst B, then A can't be alive at B. // Therefore we only need to test interference against insts that dominates the // current inst. // // We can visit the dominance tree in pre-order and assign insts to registers. // This order allows us to easily track what is dominating the current inst. // We track the insts dominating the current location in a stack. InstWorkList dominatingInsts(func->getModule()); InstHashSet dominatingInstSet(func->getModule()); struct WorkStackItem { IRBlock* block; Index dominatingInstCount; WorkStackItem() = default; WorkStackItem(IRBlock* inBlock, Index inDominatingInstCount) { block = inBlock; dominatingInstCount = inDominatingInstCount; } }; List workStack; workStack.add(WorkStackItem(func->getFirstBlock(), 0)); while (workStack.getCount()) { auto item = workStack.getLast(); workStack.removeLast(); // Pop dominatingInst stack to correct location. for (Index i = item.dominatingInstCount; i < dominatingInsts.getCount(); i++) dominatingInstSet.remove(dominatingInsts[i]); dominatingInsts.setCount(item.dominatingInstCount); for (auto inst : item.block->getChildren()) { if (!instNeedsProcessing(func, inst)) continue; // This is an inst we need to allocate register for. // Find register list for this type. auto& registers = getRegisterListForType(inst->getFullType()); RegisterInfo* allocatedReg = nullptr; for (auto reg : registers) { // Can we assign inst to this reg? // We answer this by checking if any insts already assigned // to this register is alive. If none are alive we can assign // the register. bool hasInterference = false; for (auto existingInst : reg->insts) { // If `existingInst` does not dominate `inst`, it // can't be alive here and during the entire life-time of the `inst`. // This means that `inst` and `existingInst` won't interfere. if (!dominatingInstSet.contains(existingInst)) continue; // In the general case, we need to check all its use // sites U to see if there is a path from `inst` to U. // The idea is that is `existingInst` is never used // anywhere after `inst`, then its lifetime ended before // `inst` is defined, so it is still fine to place them // in the same register. for (auto use = existingInst->firstUse; use; use = use->nextUse) { if (use->getUser() == inst) continue; if (!canCoalesce(existingInst, inst) || reachabilityContext.isInstReachable(inst, use->getUser())) { // We found a use of `existingInst` (U) where // there is a path from `inst` to U. // Generally this means that existingInst and inst interfere. // However, an exception is that existingInst is a PhiParam, // and inst is an arg to that param, and use happens after // the phi assignment. if (isUseOfParamAfterPhiAssignment(dom, use, existingInst, inst)) continue; hasInterference = true; goto endRegInstCheck; } } } endRegInstCheck:; if (!hasInterference) { if (!allocatedReg || isRegisterPreferred(allocatedReg, reg, inst)) { allocatedReg = reg; } } } if (!allocatedReg) { assignInstToNewRegister(registers, inst); } else { allocatedReg->insts.add(inst); } dominatingInsts.add(inst); dominatingInstSet.add(inst); } // Recursively visit idom children. for (auto idomChild : dom->getImmediatelyDominatedBlocks(item.block)) { workStack.add(WorkStackItem(idomChild, dominatingInsts.getCount())); } } RegisterAllocationResult result; result.mapTypeToRegisterList = _Move(mapTypeToRegisterList); for (auto& regList : result.mapTypeToRegisterList) { for (auto reg : regList.value) { for (auto inst : reg->insts) { result.mapInstToRegister[inst] = reg; } } } return result; } bool instNeedsProcessing(IRGlobalValueWithCode* func, IRInst* inst) { switch (inst->getOp()) { case kIROp_Param: if (inst->getParent() == func->getFirstBlock()) return false; if (allocateForCompositeTypeOnly && !isCompositeType(inst->getFullType())) return false; return true; case kIROp_UpdateElement: return true; default: return false; } } bool needProcessing(IRGlobalValueWithCode* func) { for (auto block : func->getBlocks()) { for (auto inst : block->getChildren()) { if (instNeedsProcessing(func, inst)) return true; } } return false; } }; RegisterAllocationResult allocateRegistersForFunc( IRGlobalValueWithCode* func, RefPtr& inOutDom, bool allocateForCompositeTypeOnly) { RegisterAllocateContext context(allocateForCompositeTypeOnly); if (context.needProcessing(func)) return context.allocateRegisters(func, inOutDom); return RegisterAllocationResult(); } } // namespace Slang