//===- subzero/src/IceTargetLowering.cpp - Basic lowering implementation --===// // // The Subzero Code Generator // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// /// /// \file /// \brief Implements the skeleton of the TargetLowering class. /// /// Specifically this invokes the appropriate lowering method for a given /// instruction kind and driving global register allocation. It also implements /// the non-deleted instruction iteration in LoweringContext. /// //===----------------------------------------------------------------------===// #include "IceTargetLowering.h" #include "IceBitVector.h" #include "IceCfg.h" // setError() #include "IceCfgNode.h" #include "IceGlobalContext.h" #include "IceGlobalInits.h" #include "IceInstVarIter.h" #include "IceLiveness.h" #include "IceOperand.h" #include "IceRegAlloc.h" #include #include #define TARGET_LOWERING_CLASS_FOR(t) Target_##t // We prevent target-specific implementation details from leaking outside their // implementations by forbidding #include of target-specific header files // anywhere outside their own files. To create target-specific objects // (TargetLowering, TargetDataLowering, and TargetHeaderLowering) we use the // following named constructors. For reference, each target Foo needs to // implement the following named constructors and initializer: // // namespace Foo { // unique_ptr createTargetLowering(Ice::Cfg *); // unique_ptr // createTargetDataLowering(Ice::GlobalContext*); // unique_ptr // createTargetHeaderLowering(Ice::GlobalContext *); // void staticInit(::Ice::GlobalContext *); // } #define SUBZERO_TARGET(X) \ namespace X { \ std::unique_ptr<::Ice::TargetLowering> \ createTargetLowering(::Ice::Cfg *Func); \ std::unique_ptr<::Ice::TargetDataLowering> \ createTargetDataLowering(::Ice::GlobalContext *Ctx); \ std::unique_ptr<::Ice::TargetHeaderLowering> \ createTargetHeaderLowering(::Ice::GlobalContext *Ctx); \ void staticInit(::Ice::GlobalContext *Ctx); \ bool shouldBePooled(const ::Ice::Constant *C); \ ::Ice::Type getPointerType(); \ } // end of namespace X #include "SZTargets.def" #undef SUBZERO_TARGET namespace Ice { void LoweringContext::init(CfgNode *N) { Node = N; End = getNode()->getInsts().end(); rewind(); advanceForward(Next); } void LoweringContext::rewind() { Begin = getNode()->getInsts().begin(); Cur = Begin; skipDeleted(Cur); Next = Cur; availabilityReset(); } void LoweringContext::insert(Inst *Instr) { getNode()->getInsts().insert(Next, Instr); LastInserted = Instr; } void LoweringContext::skipDeleted(InstList::iterator &I) const { while (I != End && I->isDeleted()) ++I; } void LoweringContext::advanceForward(InstList::iterator &I) const { if (I != End) { ++I; skipDeleted(I); } } Inst *LoweringContext::getLastInserted() const { assert(LastInserted); return LastInserted; } void LoweringContext::availabilityReset() { LastDest = nullptr; LastSrc = nullptr; } void LoweringContext::availabilityUpdate() { availabilityReset(); Inst *Instr = LastInserted; if (Instr == nullptr) return; if (!Instr->isVarAssign()) return; // Since isVarAssign() is true, the source operand must be a Variable. LastDest = Instr->getDest(); LastSrc = llvm::cast(Instr->getSrc(0)); } Variable *LoweringContext::availabilityGet(Operand *Src) const { assert(Src); if (Src == LastDest) return LastSrc; return nullptr; } namespace { void printRegisterSet(Ostream &Str, const SmallBitVector &Bitset, std::function getRegName, const std::string &LineIndentString) { constexpr size_t RegistersPerLine = 16; size_t Count = 0; for (RegNumT RegNum : RegNumBVIter(Bitset)) { if (Count == 0) { Str << LineIndentString; } else { Str << ","; } if (Count > 0 && Count % RegistersPerLine == 0) Str << "\n" << LineIndentString; ++Count; Str << getRegName(RegNum); } if (Count) Str << "\n"; } // Splits ":" into "" plus "". If there is no // component, the result is "" plus "". void splitToClassAndName(const std::string &RegName, std::string *SplitRegClass, std::string *SplitRegName) { constexpr const char Separator[] = ":"; constexpr size_t SeparatorWidth = llvm::array_lengthof(Separator) - 1; size_t Pos = RegName.find(Separator); if (Pos == std::string::npos) { *SplitRegClass = ""; *SplitRegName = RegName; } else { *SplitRegClass = RegName.substr(0, Pos); *SplitRegName = RegName.substr(Pos + SeparatorWidth); } } LLVM_ATTRIBUTE_NORETURN void badTargetFatalError(TargetArch Target) { llvm::report_fatal_error("Unsupported target: " + std::string(targetArchString(Target))); } } // end of anonymous namespace void TargetLowering::filterTypeToRegisterSet( GlobalContext *Ctx, int32_t NumRegs, SmallBitVector TypeToRegisterSet[], size_t TypeToRegisterSetSize, std::function getRegName, std::function getRegClassName) { std::vector UseSet(TypeToRegisterSetSize, SmallBitVector(NumRegs)); std::vector ExcludeSet(TypeToRegisterSetSize, SmallBitVector(NumRegs)); std::unordered_map RegNameToIndex; for (int32_t RegIndex = 0; RegIndex < NumRegs; ++RegIndex) { const auto RegNum = RegNumT::fromInt(RegIndex); RegNameToIndex[getRegName(RegNum)] = RegNum; } std::vector BadRegNames; // The processRegList function iterates across the RegNames vector. Each // entry in the vector is a string of the form "" or ":". // The register class and register number are computed, and the corresponding // bit is set in RegSet[][]. If ":" is missing, then the bit is set // for all classes. auto processRegList = [&](const std::vector &RegNames, std::vector &RegSet) { for (const std::string &RegClassAndName : RegNames) { std::string RClass; std::string RName; splitToClassAndName(RegClassAndName, &RClass, &RName); if (!RegNameToIndex.count(RName)) { BadRegNames.push_back(RName); continue; } const int32_t RegIndex = RegNameToIndex.at(RName); for (SizeT TypeIndex = 0; TypeIndex < TypeToRegisterSetSize; ++TypeIndex) { if (RClass.empty() || RClass == getRegClassName(static_cast(TypeIndex))) { RegSet[TypeIndex][RegIndex] = TypeToRegisterSet[TypeIndex][RegIndex]; } } } }; processRegList(getFlags().getUseRestrictedRegisters(), UseSet); processRegList(getFlags().getExcludedRegisters(), ExcludeSet); if (!BadRegNames.empty()) { std::string Buffer; llvm::raw_string_ostream StrBuf(Buffer); StrBuf << "Unrecognized use/exclude registers:"; for (const auto &RegName : BadRegNames) StrBuf << " " << RegName; llvm::report_fatal_error(StrBuf.str()); } // Apply filters. for (size_t TypeIndex = 0; TypeIndex < TypeToRegisterSetSize; ++TypeIndex) { SmallBitVector *TypeBitSet = &TypeToRegisterSet[TypeIndex]; SmallBitVector *UseBitSet = &UseSet[TypeIndex]; SmallBitVector *ExcludeBitSet = &ExcludeSet[TypeIndex]; if (UseBitSet->any()) *TypeBitSet = *UseBitSet; (*TypeBitSet).reset(*ExcludeBitSet); } // Display filtered register sets, if requested. if (BuildDefs::dump() && NumRegs && (getFlags().getVerbose() & IceV_AvailableRegs)) { Ostream &Str = Ctx->getStrDump(); const std::string Indent = " "; const std::string IndentTwice = Indent + Indent; Str << "Registers available for register allocation:\n"; for (size_t TypeIndex = 0; TypeIndex < TypeToRegisterSetSize; ++TypeIndex) { Str << Indent << getRegClassName(static_cast(TypeIndex)) << ":\n"; printRegisterSet(Str, TypeToRegisterSet[TypeIndex], getRegName, IndentTwice); } Str << "\n"; } } std::unique_ptr TargetLowering::createLowering(TargetArch Target, Cfg *Func) { switch (Target) { default: badTargetFatalError(Target); #define SUBZERO_TARGET(X) \ case TARGET_LOWERING_CLASS_FOR(X): \ return ::X::createTargetLowering(Func); #include "SZTargets.def" #undef SUBZERO_TARGET } } void TargetLowering::staticInit(GlobalContext *Ctx) { const TargetArch Target = getFlags().getTargetArch(); // Call the specified target's static initializer. switch (Target) { default: badTargetFatalError(Target); #define SUBZERO_TARGET(X) \ case TARGET_LOWERING_CLASS_FOR(X): { \ static bool InitGuard##X = false; \ if (InitGuard##X) { \ return; \ } \ InitGuard##X = true; \ ::X::staticInit(Ctx); \ } break; #include "SZTargets.def" #undef SUBZERO_TARGET } } bool TargetLowering::shouldBePooled(const Constant *C) { const TargetArch Target = getFlags().getTargetArch(); switch (Target) { default: return false; #define SUBZERO_TARGET(X) \ case TARGET_LOWERING_CLASS_FOR(X): \ return ::X::shouldBePooled(C); #include "SZTargets.def" #undef SUBZERO_TARGET } } ::Ice::Type TargetLowering::getPointerType() { const TargetArch Target = getFlags().getTargetArch(); switch (Target) { default: return ::Ice::IceType_void; #define SUBZERO_TARGET(X) \ case TARGET_LOWERING_CLASS_FOR(X): \ return ::X::getPointerType(); #include "SZTargets.def" #undef SUBZERO_TARGET } } TargetLowering::SandboxType TargetLowering::determineSandboxTypeFromFlags(const ClFlags &Flags) { assert(!Flags.getUseSandboxing() || !Flags.getUseNonsfi()); if (Flags.getUseNonsfi()) { return TargetLowering::ST_Nonsfi; } if (Flags.getUseSandboxing()) { return TargetLowering::ST_NaCl; } return TargetLowering::ST_None; } TargetLowering::TargetLowering(Cfg *Func) : Func(Func), Ctx(Func->getContext()), SandboxingType(determineSandboxTypeFromFlags(getFlags())) {} TargetLowering::AutoBundle::AutoBundle(TargetLowering *Target, InstBundleLock::Option Option) : Target(Target), NeedSandboxing(getFlags().getUseSandboxing()) { assert(!Target->AutoBundling); Target->AutoBundling = true; if (NeedSandboxing) { Target->_bundle_lock(Option); } } TargetLowering::AutoBundle::~AutoBundle() { assert(Target->AutoBundling); Target->AutoBundling = false; if (NeedSandboxing) { Target->_bundle_unlock(); } } void TargetLowering::genTargetHelperCalls() { TimerMarker T(TimerStack::TT_genHelpers, Func); Utils::BoolFlagSaver _(GeneratingTargetHelpers, true); for (CfgNode *Node : Func->getNodes()) { Context.init(Node); while (!Context.atEnd()) { PostIncrLoweringContext _(Context); genTargetHelperCallFor(iteratorToInst(Context.getCur())); } } } void TargetLowering::doAddressOpt() { doAddressOptOther(); if (llvm::isa(*Context.getCur())) doAddressOptLoad(); else if (llvm::isa(*Context.getCur())) doAddressOptStore(); else if (auto *Intrinsic = llvm::dyn_cast(&*Context.getCur())) { if (Intrinsic->getIntrinsicInfo().ID == Intrinsics::LoadSubVector) doAddressOptLoadSubVector(); else if (Intrinsic->getIntrinsicInfo().ID == Intrinsics::StoreSubVector) doAddressOptStoreSubVector(); } Context.advanceCur(); Context.advanceNext(); } void TargetLowering::doNopInsertion(RandomNumberGenerator &RNG) { Inst *I = iteratorToInst(Context.getCur()); bool ShouldSkip = llvm::isa(I) || llvm::isa(I) || llvm::isa(I) || I->isRedundantAssign() || I->isDeleted(); if (!ShouldSkip) { int Probability = getFlags().getNopProbabilityAsPercentage(); for (int I = 0; I < getFlags().getMaxNopsPerInstruction(); ++I) { randomlyInsertNop(Probability / 100.0, RNG); } } } // Lowers a single instruction according to the information in Context, by // checking the Context.Cur instruction kind and calling the appropriate // lowering method. The lowering method should insert target instructions at // the Cur.Next insertion point, and should not delete the Context.Cur // instruction or advance Context.Cur. // // The lowering method may look ahead in the instruction stream as desired, and // lower additional instructions in conjunction with the current one, for // example fusing a compare and branch. If it does, it should advance // Context.Cur to point to the next non-deleted instruction to process, and it // should delete any additional instructions it consumes. void TargetLowering::lower() { assert(!Context.atEnd()); Inst *Instr = iteratorToInst(Context.getCur()); Instr->deleteIfDead(); if (!Instr->isDeleted() && !llvm::isa(Instr) && !llvm::isa(Instr)) { // Mark the current instruction as deleted before lowering, otherwise the // Dest variable will likely get marked as non-SSA. See // Variable::setDefinition(). However, just pass-through FakeDef and // FakeUse instructions that might have been inserted prior to lowering. Instr->setDeleted(); switch (Instr->getKind()) { case Inst::Alloca: lowerAlloca(llvm::cast(Instr)); break; case Inst::Arithmetic: lowerArithmetic(llvm::cast(Instr)); break; case Inst::Assign: lowerAssign(llvm::cast(Instr)); break; case Inst::Br: lowerBr(llvm::cast(Instr)); break; case Inst::Breakpoint: lowerBreakpoint(llvm::cast(Instr)); break; case Inst::Call: lowerCall(llvm::cast(Instr)); break; case Inst::Cast: lowerCast(llvm::cast(Instr)); break; case Inst::ExtractElement: lowerExtractElement(llvm::cast(Instr)); break; case Inst::Fcmp: lowerFcmp(llvm::cast(Instr)); break; case Inst::Icmp: lowerIcmp(llvm::cast(Instr)); break; case Inst::InsertElement: lowerInsertElement(llvm::cast(Instr)); break; case Inst::IntrinsicCall: { auto *Call = llvm::cast(Instr); if (Call->getIntrinsicInfo().ReturnsTwice) setCallsReturnsTwice(true); lowerIntrinsicCall(Call); break; } case Inst::Load: lowerLoad(llvm::cast(Instr)); break; case Inst::Phi: lowerPhi(llvm::cast(Instr)); break; case Inst::Ret: lowerRet(llvm::cast(Instr)); break; case Inst::Select: lowerSelect(llvm::cast(Instr)); break; case Inst::ShuffleVector: lowerShuffleVector(llvm::cast(Instr)); break; case Inst::Store: lowerStore(llvm::cast(Instr)); break; case Inst::Switch: lowerSwitch(llvm::cast(Instr)); break; case Inst::Unreachable: lowerUnreachable(llvm::cast(Instr)); break; default: lowerOther(Instr); break; } postLower(); } Context.advanceCur(); Context.advanceNext(); } void TargetLowering::lowerInst(CfgNode *Node, InstList::iterator Next, InstHighLevel *Instr) { // TODO(stichnot): Consider modifying the design/implementation to avoid // multiple init() calls when using lowerInst() to lower several instructions // in the same node. Context.init(Node); Context.setNext(Next); Context.insert(Instr); --Next; assert(iteratorToInst(Next) == Instr); Context.setCur(Next); lower(); } void TargetLowering::lowerOther(const Inst *Instr) { (void)Instr; Func->setError("Can't lower unsupported instruction type"); } // Drives register allocation, allowing all physical registers (except perhaps // for the frame pointer) to be allocated. This set of registers could // potentially be parameterized if we want to restrict registers e.g. for // performance testing. void TargetLowering::regAlloc(RegAllocKind Kind) { TimerMarker T(TimerStack::TT_regAlloc, Func); LinearScan LinearScan(Func); RegSetMask RegInclude = RegSet_None; RegSetMask RegExclude = RegSet_None; RegInclude |= RegSet_CallerSave; RegInclude |= RegSet_CalleeSave; if (hasFramePointer()) RegExclude |= RegSet_FramePointer; SmallBitVector RegMask = getRegisterSet(RegInclude, RegExclude); bool Repeat = (Kind == RAK_Global && getFlags().getRepeatRegAlloc()); CfgSet EmptySet; do { LinearScan.init(Kind, EmptySet); LinearScan.scan(RegMask, getFlags().getRandomizeRegisterAllocation()); if (!LinearScan.hasEvictions()) Repeat = false; Kind = RAK_SecondChance; } while (Repeat); // TODO(stichnot): Run the register allocator one more time to do stack slot // coalescing. The idea would be to initialize the Unhandled list with the // set of Variables that have no register and a non-empty live range, and // model an infinite number of registers. Maybe use the register aliasing // mechanism to get better packing of narrower slots. if (getFlags().getSplitGlobalVars()) postRegallocSplitting(RegMask); } namespace { CfgVector getInstructionsInRange(CfgNode *Node, InstNumberT Start, InstNumberT End) { CfgVector Result; bool Started = false; auto Process = [&](InstList &Insts) { for (auto &Instr : Insts) { if (Instr.isDeleted()) { continue; } if (Instr.getNumber() == Start) { Started = true; } if (Started) { Result.emplace_back(&Instr); } if (Instr.getNumber() == End) { break; } } }; Process(Node->getPhis()); Process(Node->getInsts()); // TODO(manasijm): Investigate why checking >= End significantly changes // output. Should not happen when renumbering produces monotonically // increasing instruction numbers and live ranges begin and end on non-deleted // instructions. return Result; } } void TargetLowering::postRegallocSplitting(const SmallBitVector &RegMask) { // Splits the live ranges of global(/multi block) variables and runs the // register allocator to find registers for as many of the new variables as // possible. // TODO(manasijm): Merge the small liveranges back into multi-block ones when // the variables get the same register. This will reduce the amount of new // instructions inserted. This might involve a full dataflow analysis. // Also, modify the preference mechanism in the register allocator to match. TimerMarker _(TimerStack::TT_splitGlobalVars, Func); CfgSet SplitCandidates; // Find variables that do not have registers but are allowed to. Also skip // variables with single segment live ranges as they are not split further in // this function. for (Variable *Var : Func->getVariables()) { if (!Var->mustNotHaveReg() && !Var->hasReg()) { if (Var->getLiveRange().getNumSegments() > 1) SplitCandidates.insert(Var); } } if (SplitCandidates.empty()) return; CfgSet ExtraVars; struct UseInfo { Variable *Replacing = nullptr; Inst *FirstUse = nullptr; Inst *LastDef = nullptr; SizeT UseCount = 0; }; CfgUnorderedMap VarInfo; // Split the live ranges of the viable variables by node. // Compute metadata (UseInfo) for each of the resulting variables. for (auto *Var : SplitCandidates) { for (auto &Segment : Var->getLiveRange().getSegments()) { UseInfo Info; Info.Replacing = Var; auto *Node = Var->getLiveRange().getNodeForSegment(Segment.first); for (auto *Instr : getInstructionsInRange(Node, Segment.first, Segment.second)) { for (SizeT i = 0; i < Instr->getSrcSize(); ++i) { // It's safe to iterate over the top-level src operands rather than // using FOREACH_VAR_IN_INST(), because any variables inside e.g. // mem operands should already have registers. if (auto *Var = llvm::dyn_cast(Instr->getSrc(i))) { if (Var == Info.Replacing) { if (Info.FirstUse == nullptr && !llvm::isa(Instr)) { Info.FirstUse = Instr; } Info.UseCount++; } } } if (Instr->getDest() == Info.Replacing && !llvm::isa(Instr)) { Info.LastDef = Instr; } } static constexpr SizeT MinUseThreshold = 3; // Skip if variable has less than `MinUseThreshold` uses in the segment. if (Info.UseCount < MinUseThreshold) continue; if (!Info.FirstUse && !Info.LastDef) { continue; } LiveRange LR; LR.addSegment(Segment); Variable *NewVar = Func->makeVariable(Var->getType()); NewVar->setLiveRange(LR); VarInfo[NewVar] = Info; ExtraVars.insert(NewVar); } } // Run the register allocator with all these new variables included LinearScan RegAlloc(Func); RegAlloc.init(RAK_Global, SplitCandidates); RegAlloc.scan(RegMask, getFlags().getRandomizeRegisterAllocation()); // Modify the Cfg to use the new variables that now have registers. for (auto *ExtraVar : ExtraVars) { if (!ExtraVar->hasReg()) { continue; } auto &Info = VarInfo[ExtraVar]; assert(ExtraVar->getLiveRange().getSegments().size() == 1); auto Segment = ExtraVar->getLiveRange().getSegments()[0]; auto *Node = Info.Replacing->getLiveRange().getNodeForSegment(Segment.first); auto RelevantInsts = getInstructionsInRange(Node, Segment.first, Segment.second); if (RelevantInsts.empty()) continue; // Replace old variables for (auto *Instr : RelevantInsts) { if (llvm::isa(Instr)) continue; // TODO(manasijm): Figure out how to safely enable replacing phi dest // variables. The issue is that we can not insert low level mov // instructions into the PhiList. for (SizeT i = 0; i < Instr->getSrcSize(); ++i) { // FOREACH_VAR_IN_INST() not needed. Same logic as above. if (auto *Var = llvm::dyn_cast(Instr->getSrc(i))) { if (Var == Info.Replacing) { Instr->replaceSource(i, ExtraVar); } } } if (Instr->getDest() == Info.Replacing) { Instr->replaceDest(ExtraVar); } } assert(Info.FirstUse != Info.LastDef); assert(Info.FirstUse || Info.LastDef); // Insert spill code if (Info.FirstUse != nullptr) { auto *NewInst = Func->getTarget()->createLoweredMove(ExtraVar, Info.Replacing); Node->getInsts().insert(instToIterator(Info.FirstUse), NewInst); } if (Info.LastDef != nullptr) { auto *NewInst = Func->getTarget()->createLoweredMove(Info.Replacing, ExtraVar); Node->getInsts().insertAfter(instToIterator(Info.LastDef), NewInst); } } } void TargetLowering::markRedefinitions() { // Find (non-SSA) instructions where the Dest variable appears in some source // operand, and set the IsDestRedefined flag to keep liveness analysis // consistent. for (auto Instr = Context.getCur(), E = Context.getNext(); Instr != E; ++Instr) { if (Instr->isDeleted()) continue; Variable *Dest = Instr->getDest(); if (Dest == nullptr) continue; FOREACH_VAR_IN_INST(Var, *Instr) { if (Var == Dest) { Instr->setDestRedefined(); break; } } } } void TargetLowering::addFakeDefUses(const Inst *Instr) { FOREACH_VAR_IN_INST(Var, *Instr) { if (auto *Var64 = llvm::dyn_cast(Var)) { Context.insert(Var64->getLo()); Context.insert(Var64->getHi()); } else if (auto *VarVec = llvm::dyn_cast(Var)) { for (Variable *Var : VarVec->getContainers()) { Context.insert(Var); } } else { Context.insert(Var); } } Variable *Dest = Instr->getDest(); if (Dest == nullptr) return; if (auto *Var64 = llvm::dyn_cast(Dest)) { Context.insert(Var64->getLo()); Context.insert(Var64->getHi()); } else if (auto *VarVec = llvm::dyn_cast(Dest)) { for (Variable *Var : VarVec->getContainers()) { Context.insert(Var); } } else { Context.insert(Dest); } } void TargetLowering::sortVarsByAlignment(VarList &Dest, const VarList &Source) const { Dest = Source; // Instead of std::sort, we could do a bucket sort with log2(alignment) as // the buckets, if performance is an issue. std::sort(Dest.begin(), Dest.end(), [this](const Variable *V1, const Variable *V2) { const size_t WidthV1 = typeWidthInBytesOnStack(V1->getType()); const size_t WidthV2 = typeWidthInBytesOnStack(V2->getType()); if (WidthV1 == WidthV2) return V1->getIndex() < V2->getIndex(); return WidthV1 > WidthV2; }); } void TargetLowering::getVarStackSlotParams( VarList &SortedSpilledVariables, SmallBitVector &RegsUsed, size_t *GlobalsSize, size_t *SpillAreaSizeBytes, uint32_t *SpillAreaAlignmentBytes, uint32_t *LocalsSlotsAlignmentBytes, std::function TargetVarHook) { const VariablesMetadata *VMetadata = Func->getVMetadata(); BitVector IsVarReferenced(Func->getNumVariables()); for (CfgNode *Node : Func->getNodes()) { for (Inst &Instr : Node->getInsts()) { if (Instr.isDeleted()) continue; if (const Variable *Var = Instr.getDest()) IsVarReferenced[Var->getIndex()] = true; FOREACH_VAR_IN_INST(Var, Instr) { IsVarReferenced[Var->getIndex()] = true; } } } // If SimpleCoalescing is false, each variable without a register gets its // own unique stack slot, which leads to large stack frames. If // SimpleCoalescing is true, then each "global" variable without a register // gets its own slot, but "local" variable slots are reused across basic // blocks. E.g., if A and B are local to block 1 and C is local to block 2, // then C may share a slot with A or B. // // We cannot coalesce stack slots if this function calls a "returns twice" // function. In that case, basic blocks may be revisited, and variables local // to those basic blocks are actually live until after the called function // returns a second time. const bool SimpleCoalescing = !callsReturnsTwice(); CfgVector LocalsSize(Func->getNumNodes()); const VarList &Variables = Func->getVariables(); VarList SpilledVariables; for (Variable *Var : Variables) { if (Var->hasReg()) { // Don't consider a rematerializable variable to be an actual register use // (specifically of the frame pointer). Otherwise, the prolog may decide // to save the frame pointer twice - once because of the explicit need for // a frame pointer, and once because of an active use of a callee-save // register. if (!Var->isRematerializable()) RegsUsed[Var->getRegNum()] = true; continue; } // An argument either does not need a stack slot (if passed in a register) // or already has one (if passed on the stack). if (Var->getIsArg()) { if (!Var->hasReg()) { assert(!Var->hasStackOffset()); Var->setHasStackOffset(); } continue; } // An unreferenced variable doesn't need a stack slot. if (!IsVarReferenced[Var->getIndex()]) continue; // Check a target-specific variable (it may end up sharing stack slots) and // not need accounting here. if (TargetVarHook(Var)) continue; assert(!Var->hasStackOffset()); Var->setHasStackOffset(); SpilledVariables.push_back(Var); } SortedSpilledVariables.reserve(SpilledVariables.size()); sortVarsByAlignment(SortedSpilledVariables, SpilledVariables); for (Variable *Var : SortedSpilledVariables) { size_t Increment = typeWidthInBytesOnStack(Var->getType()); // We have sorted by alignment, so the first variable we encounter that is // located in each area determines the max alignment for the area. if (!*SpillAreaAlignmentBytes) *SpillAreaAlignmentBytes = Increment; if (SimpleCoalescing && VMetadata->isTracked(Var)) { if (VMetadata->isMultiBlock(Var)) { *GlobalsSize += Increment; } else { SizeT NodeIndex = VMetadata->getLocalUseNode(Var)->getIndex(); LocalsSize[NodeIndex] += Increment; if (LocalsSize[NodeIndex] > *SpillAreaSizeBytes) *SpillAreaSizeBytes = LocalsSize[NodeIndex]; if (!*LocalsSlotsAlignmentBytes) *LocalsSlotsAlignmentBytes = Increment; } } else { *SpillAreaSizeBytes += Increment; } } // For testing legalization of large stack offsets on targets with limited // offset bits in instruction encodings, add some padding. *SpillAreaSizeBytes += getFlags().getTestStackExtra(); } void TargetLowering::alignStackSpillAreas(uint32_t SpillAreaStartOffset, uint32_t SpillAreaAlignmentBytes, size_t GlobalsSize, uint32_t LocalsSlotsAlignmentBytes, uint32_t *SpillAreaPaddingBytes, uint32_t *LocalsSlotsPaddingBytes) { if (SpillAreaAlignmentBytes) { uint32_t PaddingStart = SpillAreaStartOffset; uint32_t SpillAreaStart = Utils::applyAlignment(PaddingStart, SpillAreaAlignmentBytes); *SpillAreaPaddingBytes = SpillAreaStart - PaddingStart; } // If there are separate globals and locals areas, make sure the locals area // is aligned by padding the end of the globals area. if (LocalsSlotsAlignmentBytes) { uint32_t GlobalsAndSubsequentPaddingSize = GlobalsSize; GlobalsAndSubsequentPaddingSize = Utils::applyAlignment(GlobalsSize, LocalsSlotsAlignmentBytes); *LocalsSlotsPaddingBytes = GlobalsAndSubsequentPaddingSize - GlobalsSize; } } void TargetLowering::assignVarStackSlots(VarList &SortedSpilledVariables, size_t SpillAreaPaddingBytes, size_t SpillAreaSizeBytes, size_t GlobalsAndSubsequentPaddingSize, bool UsesFramePointer) { const VariablesMetadata *VMetadata = Func->getVMetadata(); // For testing legalization of large stack offsets on targets with limited // offset bits in instruction encodings, add some padding. This assumes that // SpillAreaSizeBytes has accounted for the extra test padding. When // UseFramePointer is true, the offset depends on the padding, not just the // SpillAreaSizeBytes. On the other hand, when UseFramePointer is false, the // offsets depend on the gap between SpillAreaSizeBytes and // SpillAreaPaddingBytes, so we don't increment that. size_t TestPadding = getFlags().getTestStackExtra(); if (UsesFramePointer) SpillAreaPaddingBytes += TestPadding; size_t GlobalsSpaceUsed = SpillAreaPaddingBytes; size_t NextStackOffset = SpillAreaPaddingBytes; CfgVector LocalsSize(Func->getNumNodes()); const bool SimpleCoalescing = !callsReturnsTwice(); for (Variable *Var : SortedSpilledVariables) { size_t Increment = typeWidthInBytesOnStack(Var->getType()); if (SimpleCoalescing && VMetadata->isTracked(Var)) { if (VMetadata->isMultiBlock(Var)) { GlobalsSpaceUsed += Increment; NextStackOffset = GlobalsSpaceUsed; } else { SizeT NodeIndex = VMetadata->getLocalUseNode(Var)->getIndex(); LocalsSize[NodeIndex] += Increment; NextStackOffset = SpillAreaPaddingBytes + GlobalsAndSubsequentPaddingSize + LocalsSize[NodeIndex]; } } else { NextStackOffset += Increment; } if (UsesFramePointer) Var->setStackOffset(-NextStackOffset); else Var->setStackOffset(SpillAreaSizeBytes - NextStackOffset); } } InstCall *TargetLowering::makeHelperCall(RuntimeHelper FuncID, Variable *Dest, SizeT MaxSrcs) { constexpr bool HasTailCall = false; Constant *CallTarget = Ctx->getRuntimeHelperFunc(FuncID); InstCall *Call = InstCall::create(Func, MaxSrcs, Dest, CallTarget, HasTailCall); return Call; } bool TargetLowering::shouldOptimizeMemIntrins() { return Func->getOptLevel() >= Opt_1 || getFlags().getForceMemIntrinOpt(); } void TargetLowering::scalarizeArithmetic(InstArithmetic::OpKind Kind, Variable *Dest, Operand *Src0, Operand *Src1) { scalarizeInstruction( Dest, [this, Kind](Variable *Dest, Operand *Src0, Operand *Src1) { return Context.insert(Kind, Dest, Src0, Src1); }, Src0, Src1); } void TargetLowering::emitWithoutPrefix(const ConstantRelocatable *C, const char *Suffix) const { if (!BuildDefs::dump()) return; Ostream &Str = Ctx->getStrEmit(); const std::string &EmitStr = C->getEmitString(); if (!EmitStr.empty()) { // C has a custom emit string, so we use it instead of the canonical // Name + Offset form. Str << EmitStr; return; } Str << C->getName() << Suffix; RelocOffsetT Offset = C->getOffset(); if (Offset) { if (Offset > 0) Str << "+"; Str << Offset; } } std::unique_ptr TargetDataLowering::createLowering(GlobalContext *Ctx) { TargetArch Target = getFlags().getTargetArch(); switch (Target) { default: badTargetFatalError(Target); #define SUBZERO_TARGET(X) \ case TARGET_LOWERING_CLASS_FOR(X): \ return ::X::createTargetDataLowering(Ctx); #include "SZTargets.def" #undef SUBZERO_TARGET } } TargetDataLowering::~TargetDataLowering() = default; namespace { // dataSectionSuffix decides whether to use SectionSuffix or VarName as data // section suffix. Essentially, when using separate data sections for globals // SectionSuffix is not necessary. std::string dataSectionSuffix(const std::string &SectionSuffix, const std::string &VarName, const bool DataSections) { if (SectionSuffix.empty() && !DataSections) { return ""; } if (DataSections) { // With data sections we don't need to use the SectionSuffix. return "." + VarName; } assert(!SectionSuffix.empty()); return "." + SectionSuffix; } } // end of anonymous namespace void TargetDataLowering::emitGlobal(const VariableDeclaration &Var, const std::string &SectionSuffix) { if (!BuildDefs::dump()) return; // If external and not initialized, this must be a cross test. Don't generate // a declaration for such cases. const bool IsExternal = Var.isExternal() || getFlags().getDisableInternal(); if (IsExternal && !Var.hasInitializer()) return; Ostream &Str = Ctx->getStrEmit(); const bool HasNonzeroInitializer = Var.hasNonzeroInitializer(); const bool IsConstant = Var.getIsConstant(); const SizeT Size = Var.getNumBytes(); const std::string Name = Var.getName().toString(); Str << "\t.type\t" << Name << ",%object\n"; const bool UseDataSections = getFlags().getDataSections(); const bool UseNonsfi = getFlags().getUseNonsfi(); const std::string Suffix = dataSectionSuffix(SectionSuffix, Name, UseDataSections); if (IsConstant && UseNonsfi) Str << "\t.section\t.data.rel.ro" << Suffix << ",\"aw\",%progbits\n"; else if (IsConstant) Str << "\t.section\t.rodata" << Suffix << ",\"a\",%progbits\n"; else if (HasNonzeroInitializer) Str << "\t.section\t.data" << Suffix << ",\"aw\",%progbits\n"; else Str << "\t.section\t.bss" << Suffix << ",\"aw\",%nobits\n"; if (IsExternal) Str << "\t.globl\t" << Name << "\n"; const uint32_t Align = Var.getAlignment(); if (Align > 1) { assert(llvm::isPowerOf2_32(Align)); // Use the .p2align directive, since the .align N directive can either // interpret N as bytes, or power of 2 bytes, depending on the target. Str << "\t.p2align\t" << llvm::Log2_32(Align) << "\n"; } Str << Name << ":\n"; if (HasNonzeroInitializer) { for (const auto *Init : Var.getInitializers()) { switch (Init->getKind()) { case VariableDeclaration::Initializer::DataInitializerKind: { const auto &Data = llvm::cast(Init) ->getContents(); for (SizeT i = 0; i < Init->getNumBytes(); ++i) { Str << "\t.byte\t" << (((unsigned)Data[i]) & 0xff) << "\n"; } break; } case VariableDeclaration::Initializer::ZeroInitializerKind: Str << "\t.zero\t" << Init->getNumBytes() << "\n"; break; case VariableDeclaration::Initializer::RelocInitializerKind: { const auto *Reloc = llvm::cast(Init); Str << "\t" << getEmit32Directive() << "\t"; Str << Reloc->getDeclaration()->getName(); if (Reloc->hasFixup()) { // TODO(jpp): this is ARM32 specific. Str << "(GOTOFF)"; } if (RelocOffsetT Offset = Reloc->getOffset()) { if (Offset >= 0 || (Offset == INT32_MIN)) Str << " + " << Offset; else Str << " - " << -Offset; } Str << "\n"; break; } } } } else { // NOTE: for non-constant zero initializers, this is BSS (no bits), so an // ELF writer would not write to the file, and only track virtual offsets, // but the .s writer still needs this .zero and cannot simply use the .size // to advance offsets. Str << "\t.zero\t" << Size << "\n"; } Str << "\t.size\t" << Name << ", " << Size << "\n"; } std::unique_ptr TargetHeaderLowering::createLowering(GlobalContext *Ctx) { TargetArch Target = getFlags().getTargetArch(); switch (Target) { default: badTargetFatalError(Target); #define SUBZERO_TARGET(X) \ case TARGET_LOWERING_CLASS_FOR(X): \ return ::X::createTargetHeaderLowering(Ctx); #include "SZTargets.def" #undef SUBZERO_TARGET } } TargetHeaderLowering::~TargetHeaderLowering() = default; } // end of namespace Ice