diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /js/src/jit/BacktrackingAllocator.cpp | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | js/src/jit/BacktrackingAllocator.cpp | 4676 |
1 files changed, 4676 insertions, 0 deletions
diff --git a/js/src/jit/BacktrackingAllocator.cpp b/js/src/jit/BacktrackingAllocator.cpp new file mode 100644 index 0000000000..d93431795a --- /dev/null +++ b/js/src/jit/BacktrackingAllocator.cpp @@ -0,0 +1,4676 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/////////////////////////////////////////////////////////////////////////////// +// // +// Documentation. Code starts about 670 lines down from here. // +// // +/////////////////////////////////////////////////////////////////////////////// + +// [SMDOC] An overview of Ion's register allocator +// +// The intent of this documentation is to give maintainers a map with which to +// navigate the allocator. As further understanding is obtained, it should be +// added to this overview. +// +// Where possible, invariants are stated and are marked "(INVAR)". Many +// details are omitted because their workings are currently unknown. In +// particular, this overview doesn't explain how Intel-style "modify" (tied) +// operands are handled. Facts or invariants that are speculative -- believed +// to be true, but not verified at the time of writing -- are marked "(SPEC)". +// +// The various concepts are interdependent, so a single forwards reading of the +// following won't make much sense. Many concepts are explained only after +// they are mentioned. +// +// Where possible examples are shown. Without those the description is +// excessively abstract. +// +// Names of the form ::name mean BacktrackingAllocator::name. +// +// The description falls into two sections: +// +// * Section 1: A tour of the data structures +// * Section 2: The core allocation loop, and bundle splitting +// +// The allocator sometimes produces poor allocations, with excessive spilling +// and register-to-register moves (bugs 1752520, bug 1714280 bug 1746596). +// Work in bug 1752582 shows we can get better quality allocations from this +// framework without having to make any large (conceptual) changes, by having +// better splitting heuristics. +// +// At https://bugzilla.mozilla.org/show_bug.cgi?id=1758274#c17 +// (https://bugzilla.mozilla.org/attachment.cgi?id=9288467) is a document +// written at the same time as these comments. It describes some improvements +// we could make to our splitting heuristics, particularly in the presence of +// loops and calls, and shows why the current implementation sometimes produces +// excessive spilling. It builds on the commentary in this SMDOC. +// +// +// Top level pipeline +// ~~~~~~~~~~~~~~~~~~ +// There are three major phases in allocation. They run sequentially, at a +// per-function granularity. +// +// (1) Liveness analysis and bundle formation +// (2) Bundle allocation and last-chance allocation +// (3) Rewriting the function to create MoveGroups and to "install" +// the allocation +// +// The input language (LIR) is in SSA form, and phases (1) and (3) depend on +// that SSAness. Without it the allocator wouldn't work. +// +// The top level function is ::go. The phases are divided into functions as +// follows: +// +// (1) ::buildLivenessInfo, ::mergeAndQueueRegisters +// (2) ::processBundle, ::tryAllocatingRegistersForSpillBundles, +// ::pickStackSlots +// (3) ::createMoveGroupsFromLiveRangeTransitions, ::installAllocationsInLIR, +// ::populateSafepoints, ::annotateMoveGroups +// +// The code in this file is structured as much as possible in the same sequence +// as flow through the pipeline. Hence, top level function ::go is right at +// the end. Where a function depends on helper function(s), the helpers appear +// first. +// +// +// ======================================================================== +// ==== ==== +// ==== Section 1: A tour of the data structures ==== +// ==== ==== +// ======================================================================== +// +// Here are the key data structures necessary for understanding what follows. +// +// Some basic data structures +// ~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// CodePosition +// ------------ +// A CodePosition is an unsigned 32-bit int that indicates an instruction index +// in the incoming LIR. Each LIR actually has two code positions, one to +// denote the "input" point (where, one might imagine, the operands are read, +// at least useAtStart ones) and the "output" point, where operands are +// written. Eg: +// +// Block 0 [successor 2] [successor 1] +// 2-3 WasmParameter [def v1<g>:r14] +// 4-5 WasmCall [use v1:F:r14] +// 6-7 WasmLoadTls [def v2<o>] [use v1:R] +// 8-9 WasmNullConstant [def v3<o>] +// 10-11 Compare [def v4<i>] [use v2:R] [use v3:A] +// 12-13 TestIAndBranch [use v4:R] +// +// So for example the WasmLoadTls insn has its input CodePosition as 6 and +// output point as 7. Input points are even numbered, output points are odd +// numbered. CodePositions 0 and 1 never appear, because LIR instruction IDs +// start at 1. Indeed, CodePosition 0 is assumed to be invalid and hence is +// used as a marker for "unusual conditions" in various places. +// +// Phi nodes exist in the instruction stream too. They always appear at the +// start of blocks (of course) (SPEC), but their start and end points are +// printed for the group as a whole. This is to emphasise that they are really +// parallel assignments and that printing them sequentially would misleadingly +// imply that they are executed sequentially. Example: +// +// Block 6 [successor 7] [successor 8] +// 56-59 Phi [def v19<o>] [use v2:A] [use v5:A] [use v13:A] +// 56-59 Phi [def v20<o>] [use v7:A] [use v14:A] [use v12:A] +// 60-61 WasmLoadSlot [def v21<o>] [use v1:R] +// 62-63 Compare [def v22<i>] [use v20:R] [use v21:A] +// 64-65 TestIAndBranch [use v22:R] +// +// See that both Phis are printed with limits 56-59, even though they are +// stored in the LIR array like regular LIRs and so have code points 56-57 and +// 58-59 in reality. +// +// The process of allocation adds MoveGroup LIRs to the function. Each +// incoming LIR has its own private list of MoveGroups (actually, 3 lists; two +// for moves that conceptually take place before the instruction, and one for +// moves after it). Hence the CodePositions for LIRs (the "62-63", etc, above) +// do not change as a result of allocation. +// +// Virtual registers (vregs) in LIR +// -------------------------------- +// The MIR from which the LIR is derived, is standard SSA. That SSAness is +// carried through into the LIR (SPEC). In the examples here, LIR SSA names +// (virtual registers, a.k.a. vregs) are printed as "v<number>". v0 never +// appears and is presumed to be a special value, perhaps "invalid" (SPEC). +// +// The allocator core has a type VirtualRegister, but this is private to the +// allocator and not part of the LIR. It carries far more information than +// merely the name of the vreg. The allocator creates one VirtualRegister +// structure for each vreg in the LIR. +// +// LDefinition and LUse +// -------------------- +// These are part of the incoming LIR. Each LIR instruction defines zero or +// more values, and contains one LDefinition for each defined value (SPEC). +// Each instruction has zero or more input operands, each of which has its own +// LUse (SPEC). +// +// Both LDefinition and LUse hold both a virtual register name and, in general, +// a real (physical) register identity. The incoming LIR has the real register +// fields unset, except in places where the incoming LIR has fixed register +// constraints (SPEC). Phase 3 of allocation will visit all of the +// LDefinitions and LUses so as to write into the real register fields the +// decisions made by the allocator. For LUses, this is done by overwriting the +// complete LUse with a different LAllocation, for example LStackSlot. That's +// possible because LUse is a child class of LAllocation. +// +// This action of reading and then later updating LDefinition/LUses is the core +// of the allocator's interface to the outside world. +// +// To make visiting of LDefinitions/LUses possible, the allocator doesn't work +// with LDefinition and LUse directly. Rather it has pointers to them +// (VirtualRegister::def_, UsePosition::use_). Hence Phase 3 can modify the +// LIR in-place. +// +// (INVARs, all SPEC): +// +// - The collective VirtualRegister::def_ values should be unique, and there +// should be a 1:1 mapping between the VirtualRegister::def_ values and the +// LDefinitions in the LIR. (So that the LIR LDefinition has exactly one +// VirtualRegister::def_ to track it). But only for the valid LDefinitions. +// If isBogusTemp() is true, the definition is invalid and doesn't have a +// vreg. +// +// - The same for uses: there must be a 1:1 correspondence between the +// CodePosition::use_ values and the LIR LUses. +// +// - The allocation process must preserve these 1:1 mappings. That implies +// (weaker) that the number of VirtualRegisters and of UsePositions must +// remain constant through allocation. (Eg: losing them would mean that some +// LIR def or use would necessarily not get annotated with its final +// allocation decision. Duplicating them would lead to the possibility of +// conflicting allocation decisions.) +// +// Other comments regarding LIR +// ---------------------------- +// The incoming LIR is structured into basic blocks and a CFG, as one would +// expect. These (insns, block boundaries, block edges etc) are available +// through the BacktrackingAllocator object. They are important for Phases 1 +// and 3 but not for Phase 2. +// +// Phase 3 "rewrites" the input LIR so as to "install" the final allocation. +// It has to insert MoveGroup instructions, but that isn't done by pushing them +// into the instruction array. Rather, each LIR has 3 auxiliary sets of +// MoveGroups (SPEC): two that "happen" conceptually before the LIR, and one +// that happens after it. The rewriter inserts MoveGroups into one of these 3 +// sets, and later code generation phases presumably insert the sets (suitably +// translated) into the final machine code (SPEC). +// +// +// Key data structures: LiveRange, VirtualRegister and LiveBundle +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// These three have central roles in allocation. Of them, LiveRange is the +// most central. VirtualRegister is conceptually important throughout, but +// appears less frequently in the allocator code. LiveBundle is important only +// in Phase 2 (where it is central) and at the end of Phase 1, but plays no +// role in Phase 3. +// +// It's important to understand that LiveRange and VirtualRegister correspond +// to concepts visible in the incoming LIR, which is in SSA form. LiveBundle +// by comparison is related to neither the structure of LIR nor its SSA +// properties. Instead, LiveBundle is an essentially adminstrative structure +// used to accelerate allocation and to implement a crude form of +// move-coalescing. +// +// VirtualRegisters and LiveRanges are (almost) static throughout the process, +// because they reflect aspects of the incoming LIR, which does not change. +// LiveBundles by contrast come and go; they are created, but may be split up +// into new bundles, and old ones abandoned. +// +// Each LiveRange is a member of two different linked lists, chained through +// fields registerLink and bundleLink. +// +// A VirtualRegister (described in detail below) has a list of LiveRanges that +// it "owns". These are chained through LiveRange::registerLink. +// +// A LiveBundle (also described below) also has a list LiveRanges that it +// "owns", chained through LiveRange::bundleLink. +// +// Hence each LiveRange is "owned" by one VirtualRegister and one LiveBundle. +// LiveRanges may have their owning LiveBundle changed as a result of +// splitting. By contrast a LiveRange stays with its "owning" VirtualRegister +// for ever. +// +// A few LiveRanges have no VirtualRegister. This is used to implement +// register spilling for calls. Each physical register that's not preserved +// across a call has a small range that covers the call. It is +// ::buildLivenessInfo that adds these small ranges. +// +// Iterating over every VirtualRegister in the system is a common operation and +// is straightforward because (somewhat redundantly?) the LIRGraph knows the +// number of vregs, and more importantly because BacktrackingAllocator::vregs +// is a vector of all VirtualRegisters. By contrast iterating over every +// LiveBundle in the system is more complex because there is no top-level +// registry of them. It is still possible though. See ::dumpLiveRangesByVReg +// and ::dumpLiveRangesByBundle for example code. +// +// LiveRange +// --------- +// Fundamentally, a LiveRange (often written just "range") is a request for +// storage of a LIR vreg for some contiguous sequence of LIRs. A LiveRange +// generally covers only a fraction of a vreg's overall lifetime, so multiple +// LiveRanges are generally needed to cover the whole lifetime. +// +// A LiveRange contains (amongst other things): +// +// * the vreg for which it is for, as a VirtualRegister* +// +// * the range of CodePositions for which it is for, as a LiveRange::Range +// +// * auxiliary information: +// +// - a boolean that indicates whether this LiveRange defines a value for the +// vreg. If so, that definition is regarded as taking place at the first +// CodePoint of the range. +// +// - a linked list of uses of the vreg within this range. Each use is a pair +// of a CodePosition and an LUse*. (INVAR): the uses are maintained in +// increasing order of CodePosition. Multiple uses at the same +// CodePosition are permitted, since that is necessary to represent an LIR +// that uses the same vreg in more than one of its operand slots. +// +// Some important facts about LiveRanges are best illustrated with examples: +// +// v25 75-82 { 75_def:R 78_v25:A 82_v25:A } +// +// This LiveRange is for vreg v25. The value is defined at CodePosition 75, +// with the LIR requiring that it be in a register. It is used twice at +// positions 78 and 82, both with no constraints (A meaning "any"). The range +// runs from position 75 to 82 inclusive. Note however that LiveRange::Range +// uses non-inclusive range ends; hence its .to field would be 83, not 82. +// +// v26 84-85 { 85_v26:R } +// +// This LiveRange is for vreg v26. Here, there's only a single use of it at +// position 85. Presumably it is defined in some other LiveRange. +// +// v19 74-79 { } +// +// This LiveRange is for vreg v19. There is no def and no uses, so at first +// glance this seems redundant. But it isn't: it still expresses a request for +// storage for v19 across 74-79, because Phase 1 regards v19 as being live in +// this range (meaning: having a value that, if changed in this range, would +// cause the program to fail). +// +// Other points: +// +// * (INVAR) Each vreg/VirtualRegister has at least one LiveRange. +// +// * (INVAR) Exactly one LiveRange of a vreg gives a definition for the value. +// All other LiveRanges must consist only of uses (including zero uses, for a +// "flow-though" range as mentioned above). This requirement follows from +// the fact that LIR is in SSA form. +// +// * It follows from this, that the LiveRanges for a VirtualRegister must form +// a tree, where the parent-child relationship is "control flows directly +// from a parent LiveRange (anywhere in the LiveRange) to a child LiveRange +// (start)". The entire tree carries only one value. This is a use of +// SSAness in the allocator which is fundamental: without SSA input, this +// design would not work. +// +// The root node (LiveRange) in the tree must be one that defines the value, +// and all other nodes must only use or be flow-throughs for the value. It's +// OK for LiveRanges in the tree to overlap, providing that there is a unique +// root node -- otherwise it would be unclear which LiveRange provides the +// value. +// +// The function ::createMoveGroupsFromLiveRangeTransitions runs after all +// LiveBundles have been allocated. It visits each VirtualRegister tree in +// turn. For every parent->child edge in a tree, it creates a MoveGroup that +// copies the value from the parent into the child -- this is how the +// allocator decides where to put MoveGroups. There are various other +// details not described here. +// +// * It's important to understand that a LiveRange carries no meaning about +// control flow beyond that implied by the SSA (hence, dominance) +// relationship between a def and its uses. In particular, there's no +// implication that execution "flowing into" the start of the range implies +// that it will "flow out" of the end. Or that any particular use will or +// will not be executed. +// +// * (very SPEC) Indeed, even if a range has a def, there's no implication that +// a use later in the range will have been immediately preceded by execution +// of the def. It could be that the def is executed, flow jumps somewhere +// else, and later jumps back into the middle of the range, where there are +// then some uses. +// +// * Uses of a vreg by a phi node are not mentioned in the use list of a +// LiveRange. The reasons for this are unknown, but it is speculated that +// this is because we don't need to know about phi uses where we use the list +// of positions. See comments on VirtualRegister::usedByPhi_. +// +// * Similarly, a definition of a vreg by a phi node is not regarded as being a +// definition point (why not?), at least as the view of +// LiveRange::hasDefinition_. +// +// * LiveRanges that nevertheless include a phi-defined value have their first +// point set to the first of the block of phis, even if the var isn't defined +// by that specific phi. Eg: +// +// Block 6 [successor 7] [successor 8] +// 56-59 Phi [def v19<o>] [use v2:A] [use v5:A] [use v13:A] +// 56-59 Phi [def v20<o>] [use v7:A] [use v14:A] [use v12:A] +// 60-61 WasmLoadSlot [def v21<o>] [use v1:R] +// 62-63 Compare [def v22<i>] [use v20:R] [use v21:A] +// +// The relevant live range for v20 is +// +// v20 56-65 { 63_v20:R } +// +// Observe that it starts at 56, not 58. +// +// VirtualRegister +// --------------- +// Each VirtualRegister is associated with an SSA value created by the LIR. +// Fundamentally it is a container to hold all of the LiveRanges that together +// indicate where the value must be kept live. This is a linked list beginning +// at VirtualRegister::ranges_, and which, as described above, is chained +// through LiveRange::registerLink. The set of LiveRanges must logically form +// a tree, rooted at the LiveRange which defines the value. +// +// For adminstrative convenience, the linked list must contain the LiveRanges +// in order of increasing start point. +// +// There are various auxiliary fields, most importantly the LIR node and the +// associated LDefinition that define the value. +// +// It is OK, and quite common, for LiveRanges of a VirtualRegister to overlap. +// The effect will be that, in an overlapped area, there are two storage +// locations holding the value. This is OK -- although wasteful of storage +// resources -- because the SSAness means the value must be the same in both +// locations. Hence there's no questions like "which LiveRange holds the most +// up-to-date value?", since it's all just one value anyway. +// +// Note by contrast, it is *not* OK for the LiveRanges of a LiveBundle to +// overlap. +// +// LiveBundle +// ---------- +// Similar to VirtualRegister, a LiveBundle is also, fundamentally, a container +// for a set of LiveRanges. The set is stored as a linked list, rooted at +// LiveBundle::ranges_ and chained through LiveRange::bundleLink. +// +// However, the similarity ends there: +// +// * The LiveRanges in a LiveBundle absolutely must not overlap. They must +// indicate disjoint sets of CodePositions, and must be stored in the list in +// order of increasing CodePosition. Because of the no-overlap requirement, +// these ranges form a total ordering regardless of whether one uses the +// LiveRange::Range::from_ or ::to_ fields for comparison. +// +// * The LiveRanges in a LiveBundle can otherwise be entirely arbitrary and +// unrelated. They can be from different VirtualRegisters and can have no +// particular mutual significance w.r.t. the SSAness or structure of the +// input LIR. +// +// LiveBundles are the fundamental unit of allocation. The allocator attempts +// to find a single storage location that will work for all LiveRanges in the +// bundle. That's why the ranges must not overlap. If no such location can be +// found, the allocator may decide to split the bundle into multiple smaller +// bundles. Each of those may be allocated independently. +// +// The other really important field is LiveBundle::alloc_, indicating the +// chosen storage location. +// +// Here's an example, for a LiveBundle before it has been allocated: +// +// LB2(parent=none v3 8-21 { 16_v3:A } ## v3 24-25 { 25_v3:F:xmm0 }) +// +// LB merely indicates "LiveBundle", and the 2 is the debugId_ value (see +// below). This bundle has two LiveRanges +// +// v3 8-21 { 16_v3:A } +// v3 24-25 { 25_v3:F:xmm0 } +// +// both of which (coincidentally) are for the same VirtualRegister, v3.The +// second LiveRange has a fixed use in `xmm0`, whilst the first one doesn't +// care (A meaning "any location") so the allocator *could* choose `xmm0` for +// the bundle as a whole. +// +// One might ask: why bother with LiveBundle at all? After all, it would be +// possible to get correct allocations by allocating each LiveRange +// individually, then leaving ::createMoveGroupsFromLiveRangeTransitions to add +// MoveGroups to join up LiveRanges that form each SSA value tree (that is, +// LiveRanges belonging to each VirtualRegister). +// +// There are two reasons: +// +// (1) By putting multiple LiveRanges into each LiveBundle, we can end up with +// many fewer LiveBundles than LiveRanges. Since the cost of allocating a +// LiveBundle is substantially less than the cost of allocating each of its +// LiveRanges individually, the allocator will run faster. +// +// (2) It provides a crude form of move-coalescing. There are situations where +// it would be beneficial, although not mandatory, to have two LiveRanges +// assigned to the same storage unit. Most importantly: (a) LiveRanges +// that form all of the inputs, and the output, of a phi node. (b) +// LiveRanges for the output and first-operand values in the case where we +// are targetting Intel-style instructions. +// +// In such cases, if the bundle can be allocated as-is, then no extra moves +// are necessary. If not (and the bundle is split), then +// ::createMoveGroupsFromLiveRangeTransitions will later fix things up by +// inserting MoveGroups in the right places. +// +// Merging of LiveRanges into LiveBundles is done in Phase 1, by +// ::mergeAndQueueRegisters, after liveness analysis (which generates only +// LiveRanges). +// +// For the bundle mentioned above, viz +// +// LB2(parent=none v3 8-21 { 16_v3:A } ## v3 24-25 { 25_v3:F:xmm0 }) +// +// the allocator did not in the end choose to allocate it to `xmm0`. Instead +// it was split into two bundles, LB6 (a "spill parent", or root node in the +// trees described above), and LB9, a leaf node, that points to its parent LB6: +// +// LB6(parent=none v3 8-21 %xmm1.s { 16_v3:A } ## v3 24-25 %xmm1.s { }) +// LB9(parent=LB6 v3 24-25 %xmm0.s { 25_v3:F:rax }) +// +// Note that both bundles now have an allocation, and that is printed, +// redundantly, for each LiveRange in the bundle -- hence the repeated +// `%xmm1.s` in the lines above. Since all LiveRanges in a LiveBundle must be +// allocated to the same storage location, we never expect to see output like +// this +// +// LB6(parent=none v3 8-21 %xmm1.s { 16_v3:A } ## v3 24-25 %xmm2.s { }) +// +// and that is in any case impossible, since a LiveRange doesn't have an +// LAllocation field. Instead it has a pointer to its LiveBundle, and the +// LAllocation lives in the LiveBundle. +// +// For the resulting allocation (LB6 and LB9), all the LiveRanges are use-only +// or flow-through. There are no definitions. But they are all for the same +// VirtualReg, v3, so they all have the same value. An important question is +// where they "get their value from". The answer is that +// ::createMoveGroupsFromLiveRangeTransitions will have to insert suitable +// MoveGroups so that each LiveRange for v3 can "acquire" the value from a +// previously-executed LiveRange, except for the range that defines it. The +// defining LiveRange is not visible here; either it is in some other +// LiveBundle, not shown, or (more likely) the value is defined by a phi-node, +// in which case, as mentioned previously, it is not shown as having an +// explicit definition in any LiveRange. +// +// LiveBundles also have a `SpillSet* spill_` field (see below) and a +// `LiveBundle* spillParent_`. The latter is used to ensure that all bundles +// originating from an "original" bundle share the same spill slot. The +// `spillParent_` pointers can be thought of creating a 1-level tree, where +// each node points at its parent. Since the tree can be only 1-level, the +// following invariant (INVAR) must be maintained: +// +// * if a LiveBundle has a non-null spillParent_ field, then it is a leaf node, +// and no other LiveBundle can point at this one. +// +// * else (it has a null spillParent_ field) it is a root node, and so other +// LiveBundles may point at it. +// +// When compiled with JS_JITSPEW, LiveBundle has a 32-bit `debugId_` field. +// This is used only for debug printing, and makes it easier to see +// parent-child relationships induced by the `spillParent_` pointers. +// +// The "life cycle" of LiveBundles is described in Section 2 below. +// +// +// Secondary data structures: SpillSet, Requirement +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// +// SpillSet +// -------- +// A SpillSet is a container for a set of LiveBundles that have been spilled, +// all of which are assigned the same spill slot. The set is represented as a +// vector of points to LiveBundles. SpillSet also contains the identity of the +// spill slot (its LAllocation). +// +// A LiveBundle, if it is to be spilled, has a pointer to the relevant +// SpillSet, and the SpillSet in turn has a pointer back to the LiveBundle in +// its vector thereof. So LiveBundles (that are to be spilled) and SpillSets +// point at each other. +// +// (SPEC) LiveBundles that are not to be spilled (or for which the decision has +// yet to be made, have their SpillSet pointers as null. (/SPEC) +// +// Requirement +// ----------- +// Requirements are used transiently during the main allocation loop. It +// summarises the set of constraints on storage location (must be any register, +// must be this specific register, must be stack, etc) for a LiveBundle. This +// is so that the main allocation loop knows what kind of storage location it +// must choose in order to satisfy all of the defs and uses within the bundle. +// +// What Requirement provides is (a) a partially ordered set of locations, and +// (b) a constraint-merging method `merge`. +// +// Requirement needs a rewrite (and, in fact, that has already happened in +// un-landed code in bug 1758274) for the following reasons: +// +// * it's potentially buggy (bug 1761654), although that doesn't currently +// affect us, for reasons which are unclear. +// +// * the partially ordered set has a top point, meaning "no constraint", but it +// doesn't have a corresponding bottom point, meaning "impossible +// constraints". (So it's a partially ordered set, but not a lattice). This +// leads to awkward coding in some places, which would be simplified if there +// were an explicit way to indicate "impossible constraint". +// +// +// Some ASCII art +// ~~~~~~~~~~~~~~ +// +// Here's some not-very-good ASCII art that tries to summarise the data +// structures that persist for the entire allocation of a function: +// +// BacktrackingAllocator +// | +// (vregs) +// | +// v +// | +// VirtualRegister -->--(ins)--> LNode +// | | `->--(def)--> LDefinition +// v ^ +// | | +// (ranges) | +// | (vreg) +// `--v->--. | ,-->--v-->-------------->--v-->--. ,--NULL +// \ | / \ / +// LiveRange LiveRange LiveRange +// / | \ / \. +// ,--b->--' / `-->--b-->--' `--NULL +// | (bundle) +// ^ / +// | v +// (ranges) / +// | / +// LiveBundle --s-->- LiveBundle +// | \ / | +// | \ / | +// (spill) ^ ^ (spill) +// | \ / | +// v \ / ^ +// | (list) | +// \ | / +// `--->---> SpillSet <--' +// +// --b-- LiveRange::bundleLink: links in the list of LiveRanges that belong to +// a LiveBundle +// +// --v-- LiveRange::registerLink: links in the list of LiveRanges that belong +// to a VirtualRegister +// +// --s-- LiveBundle::spillParent: a possible link to my "spill parent bundle" +// +// +// * LiveRange is in the center. Each LiveRange is a member of two different +// linked lists, the --b-- list and the --v-- list. +// +// * VirtualRegister has a pointer `ranges` that points to the start of its +// --v-- list of LiveRanges. +// +// * LiveBundle has a pointer `ranges` that points to the start of its --b-- +// list of LiveRanges. +// +// * LiveRange points back at both its owning VirtualRegister (`vreg`) and its +// owning LiveBundle (`bundle`). +// +// * LiveBundle has a pointer --s-- `spillParent`, which may be null, to its +// conceptual "spill parent bundle", as discussed in detail above. +// +// * LiveBundle has a pointer `spill` to its SpillSet. +// +// * SpillSet has a vector `list` of pointers back to the LiveBundles that +// point at it. +// +// * VirtualRegister has pointers `ins` to the LNode that defines the value and +// `def` to the LDefinition within that LNode. +// +// * BacktrackingAllocator has a vector `vregs` of pointers to all the +// VirtualRegisters for the function. There is no equivalent top-level table +// of all the LiveBundles for the function. +// +// Note that none of these pointers are "owning" in the C++-storage-management +// sense. Rather, everything is stored in single arena which is freed when +// compilation of the function is complete. For this reason, +// BacktrackingAllocator.{h,cpp} is almost completely free of the usual C++ +// storage-management artefacts one would normally expect to see. +// +// +// ======================================================================== +// ==== ==== +// ==== Section 2: The core allocation loop, and bundle splitting ==== +// ==== ==== +// ======================================================================== +// +// Phase 1 of the allocator (described at the start of this SMDOC) computes +// live ranges, merges them into bundles, and places the bundles in a priority +// queue ::allocationQueue, ordered by what ::computePriority computes. +// +// +// The allocation loops +// ~~~~~~~~~~~~~~~~~~~~ +// The core of the allocation machinery consists of two loops followed by a +// call to ::pickStackSlots. The latter is uninteresting. The two loops live +// in ::go and are documented in detail there. +// +// +// Bundle splitting +// ~~~~~~~~~~~~~~~~ +// If the first of the two abovementioned loops cannot find a register for a +// bundle, either directly or as a result of evicting conflicting bundles, then +// it will have to either split or spill the bundle. The entry point to the +// split/spill subsystem is ::chooseBundleSplit. See comments there. + +/////////////////////////////////////////////////////////////////////////////// +// // +// End of documentation // +// // +/////////////////////////////////////////////////////////////////////////////// + +#include "jit/BacktrackingAllocator.h" + +#include <algorithm> + +#include "jit/BitSet.h" +#include "jit/CompileInfo.h" +#include "js/Printf.h" + +using namespace js; +using namespace js::jit; + +using mozilla::DebugOnly; + +// This is a big, complex file. Code is grouped into various sections, each +// preceded by a box comment. Sections not marked as "Misc helpers" are +// pretty much the top level flow, and are presented roughly in the same order +// in which the allocation pipeline operates. BacktrackingAllocator::go, +// right at the end of the file, is a good starting point. + +/////////////////////////////////////////////////////////////////////////////// +// // +// Misc helpers: linked-list management // +// // +/////////////////////////////////////////////////////////////////////////////// + +static inline bool SortBefore(UsePosition* a, UsePosition* b) { + return a->pos <= b->pos; +} + +static inline bool SortBefore(LiveRange::BundleLink* a, + LiveRange::BundleLink* b) { + LiveRange* rangea = LiveRange::get(a); + LiveRange* rangeb = LiveRange::get(b); + MOZ_ASSERT(!rangea->intersects(rangeb)); + return rangea->from() < rangeb->from(); +} + +static inline bool SortBefore(LiveRange::RegisterLink* a, + LiveRange::RegisterLink* b) { + return LiveRange::get(a)->from() <= LiveRange::get(b)->from(); +} + +template <typename T> +static inline void InsertSortedList(InlineForwardList<T>& list, T* value) { + if (list.empty()) { + list.pushFront(value); + return; + } + + if (SortBefore(list.back(), value)) { + list.pushBack(value); + return; + } + + T* prev = nullptr; + for (InlineForwardListIterator<T> iter = list.begin(); iter; iter++) { + if (SortBefore(value, *iter)) { + break; + } + prev = *iter; + } + + if (prev) { + list.insertAfter(prev, value); + } else { + list.pushFront(value); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// Misc helpers: methods for class SpillSet // +// // +/////////////////////////////////////////////////////////////////////////////// + +void SpillSet::setAllocation(LAllocation alloc) { + for (size_t i = 0; i < numSpilledBundles(); i++) { + spilledBundle(i)->setAllocation(alloc); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// Misc helpers: methods for class LiveRange // +// // +/////////////////////////////////////////////////////////////////////////////// + +static size_t SpillWeightFromUsePolicy(LUse::Policy policy) { + switch (policy) { + case LUse::ANY: + return 1000; + + case LUse::REGISTER: + case LUse::FIXED: + return 2000; + + default: + return 0; + } +} + +inline void LiveRange::noteAddedUse(UsePosition* use) { + LUse::Policy policy = use->usePolicy(); + usesSpillWeight_ += SpillWeightFromUsePolicy(policy); + if (policy == LUse::FIXED) { + ++numFixedUses_; + } +} + +inline void LiveRange::noteRemovedUse(UsePosition* use) { + LUse::Policy policy = use->usePolicy(); + usesSpillWeight_ -= SpillWeightFromUsePolicy(policy); + if (policy == LUse::FIXED) { + --numFixedUses_; + } + MOZ_ASSERT_IF(!hasUses(), !usesSpillWeight_ && !numFixedUses_); +} + +void LiveRange::addUse(UsePosition* use) { + MOZ_ASSERT(covers(use->pos)); + InsertSortedList(uses_, use); + noteAddedUse(use); +} + +UsePosition* LiveRange::popUse() { + UsePosition* ret = uses_.popFront(); + noteRemovedUse(ret); + return ret; +} + +void LiveRange::tryToMoveDefAndUsesInto(LiveRange* other) { + MOZ_ASSERT(&other->vreg() == &vreg()); + MOZ_ASSERT(this != other); + + // Move over all uses which fit in |other|'s boundaries. + for (UsePositionIterator iter = usesBegin(); iter;) { + UsePosition* use = *iter; + if (other->covers(use->pos)) { + uses_.removeAndIncrement(iter); + noteRemovedUse(use); + other->addUse(use); + } else { + iter++; + } + } + + // Distribute the definition to |other| as well, if possible. + if (hasDefinition() && from() == other->from()) { + other->setHasDefinition(); + } +} + +bool LiveRange::contains(LiveRange* other) const { + return from() <= other->from() && to() >= other->to(); +} + +void LiveRange::intersect(LiveRange* other, Range* pre, Range* inside, + Range* post) const { + MOZ_ASSERT(pre->empty() && inside->empty() && post->empty()); + + CodePosition innerFrom = from(); + if (from() < other->from()) { + if (to() < other->from()) { + *pre = range_; + return; + } + *pre = Range(from(), other->from()); + innerFrom = other->from(); + } + + CodePosition innerTo = to(); + if (to() > other->to()) { + if (from() >= other->to()) { + *post = range_; + return; + } + *post = Range(other->to(), to()); + innerTo = other->to(); + } + + if (innerFrom != innerTo) { + *inside = Range(innerFrom, innerTo); + } +} + +bool LiveRange::intersects(LiveRange* other) const { + Range pre, inside, post; + intersect(other, &pre, &inside, &post); + return !inside.empty(); +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// Misc helpers: methods for class LiveBundle // +// // +/////////////////////////////////////////////////////////////////////////////// + +#ifdef DEBUG +size_t LiveBundle::numRanges() const { + size_t count = 0; + for (LiveRange::BundleLinkIterator iter = rangesBegin(); iter; iter++) { + count++; + } + return count; +} +#endif + +LiveRange* LiveBundle::rangeFor(CodePosition pos) const { + for (LiveRange::BundleLinkIterator iter = rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + if (range->covers(pos)) { + return range; + } + } + return nullptr; +} + +void LiveBundle::addRange(LiveRange* range) { + MOZ_ASSERT(!range->bundle()); + range->setBundle(this); + InsertSortedList(ranges_, &range->bundleLink); +} + +bool LiveBundle::addRange(TempAllocator& alloc, VirtualRegister* vreg, + CodePosition from, CodePosition to) { + LiveRange* range = LiveRange::FallibleNew(alloc, vreg, from, to); + if (!range) { + return false; + } + addRange(range); + return true; +} + +bool LiveBundle::addRangeAndDistributeUses(TempAllocator& alloc, + LiveRange* oldRange, + CodePosition from, CodePosition to) { + LiveRange* range = LiveRange::FallibleNew(alloc, &oldRange->vreg(), from, to); + if (!range) { + return false; + } + addRange(range); + oldRange->tryToMoveDefAndUsesInto(range); + return true; +} + +LiveRange* LiveBundle::popFirstRange() { + LiveRange::BundleLinkIterator iter = rangesBegin(); + if (!iter) { + return nullptr; + } + + LiveRange* range = LiveRange::get(*iter); + ranges_.removeAt(iter); + + range->setBundle(nullptr); + return range; +} + +void LiveBundle::removeRange(LiveRange* range) { + for (LiveRange::BundleLinkIterator iter = rangesBegin(); iter; iter++) { + LiveRange* existing = LiveRange::get(*iter); + if (existing == range) { + ranges_.removeAt(iter); + return; + } + } + MOZ_CRASH(); +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// Misc helpers: methods for class VirtualRegister // +// // +/////////////////////////////////////////////////////////////////////////////// + +bool VirtualRegister::addInitialRange(TempAllocator& alloc, CodePosition from, + CodePosition to, size_t* numRanges) { + MOZ_ASSERT(from < to); + + // Mark [from,to) as a live range for this register during the initial + // liveness analysis, coalescing with any existing overlapping ranges. + + // On some pathological graphs there might be a huge number of different + // live ranges. Allow non-overlapping live range to be merged if the + // number of ranges exceeds the cap below. + static const size_t CoalesceLimit = 100000; + + LiveRange* prev = nullptr; + LiveRange* merged = nullptr; + for (LiveRange::RegisterLinkIterator iter(rangesBegin()); iter;) { + LiveRange* existing = LiveRange::get(*iter); + + if (from > existing->to() && *numRanges < CoalesceLimit) { + // The new range should go after this one. + prev = existing; + iter++; + continue; + } + + if (to.next() < existing->from()) { + // The new range should go before this one. + break; + } + + if (!merged) { + // This is the first old range we've found that overlaps the new + // range. Extend this one to cover its union with the new range. + merged = existing; + + if (from < existing->from()) { + existing->setFrom(from); + } + if (to > existing->to()) { + existing->setTo(to); + } + + // Continue searching to see if any other old ranges can be + // coalesced with the new merged range. + iter++; + continue; + } + + // Coalesce this range into the previous range we merged into. + MOZ_ASSERT(existing->from() >= merged->from()); + if (existing->to() > merged->to()) { + merged->setTo(existing->to()); + } + + MOZ_ASSERT(!existing->hasDefinition()); + existing->tryToMoveDefAndUsesInto(merged); + MOZ_ASSERT(!existing->hasUses()); + + ranges_.removeAndIncrement(iter); + } + + if (!merged) { + // The new range does not overlap any existing range for the vreg. + LiveRange* range = LiveRange::FallibleNew(alloc, this, from, to); + if (!range) { + return false; + } + + if (prev) { + ranges_.insertAfter(&prev->registerLink, &range->registerLink); + } else { + ranges_.pushFront(&range->registerLink); + } + + (*numRanges)++; + } + + return true; +} + +void VirtualRegister::addInitialUse(UsePosition* use) { + LiveRange::get(*rangesBegin())->addUse(use); +} + +void VirtualRegister::setInitialDefinition(CodePosition from) { + LiveRange* first = LiveRange::get(*rangesBegin()); + MOZ_ASSERT(from >= first->from()); + first->setFrom(from); + first->setHasDefinition(); +} + +LiveRange* VirtualRegister::rangeFor(CodePosition pos, + bool preferRegister /* = false */) const { + LiveRange* found = nullptr; + for (LiveRange::RegisterLinkIterator iter = rangesBegin(); iter; iter++) { + LiveRange* range = LiveRange::get(*iter); + if (range->covers(pos)) { + if (!preferRegister || range->bundle()->allocation().isRegister()) { + return range; + } + if (!found) { + found = range; + } + } + } + return found; +} + +void VirtualRegister::addRange(LiveRange* range) { + InsertSortedList(ranges_, &range->registerLink); +} + +void VirtualRegister::removeRange(LiveRange* range) { + for (LiveRange::RegisterLinkIterator iter = rangesBegin(); iter; iter++) { + LiveRange* existing = LiveRange::get(*iter); + if (existing == range) { + ranges_.removeAt(iter); + return; + } + } + MOZ_CRASH(); +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// Misc helpers: queries about uses // +// // +/////////////////////////////////////////////////////////////////////////////// + +static inline LDefinition* FindReusingDefOrTemp(LNode* node, + LAllocation* alloc) { + if (node->isPhi()) { + MOZ_ASSERT(node->toPhi()->numDefs() == 1); + MOZ_ASSERT(node->toPhi()->getDef(0)->policy() != + LDefinition::MUST_REUSE_INPUT); + return nullptr; + } + + LInstruction* ins = node->toInstruction(); + + for (size_t i = 0; i < ins->numDefs(); i++) { + LDefinition* def = ins->getDef(i); + if (def->policy() == LDefinition::MUST_REUSE_INPUT && + ins->getOperand(def->getReusedInput()) == alloc) { + return def; + } + } + for (size_t i = 0; i < ins->numTemps(); i++) { + LDefinition* def = ins->getTemp(i); + if (def->policy() == LDefinition::MUST_REUSE_INPUT && + ins->getOperand(def->getReusedInput()) == alloc) { + return def; + } + } + return nullptr; +} + +bool BacktrackingAllocator::isReusedInput(LUse* use, LNode* ins, + bool considerCopy) { + if (LDefinition* def = FindReusingDefOrTemp(ins, use)) { + return considerCopy || !vregs[def->virtualRegister()].mustCopyInput(); + } + return false; +} + +bool BacktrackingAllocator::isRegisterUse(UsePosition* use, LNode* ins, + bool considerCopy) { + switch (use->usePolicy()) { + case LUse::ANY: + return isReusedInput(use->use(), ins, considerCopy); + + case LUse::REGISTER: + case LUse::FIXED: + return true; + + default: + return false; + } +} + +bool BacktrackingAllocator::isRegisterDefinition(LiveRange* range) { + if (!range->hasDefinition()) { + return false; + } + + VirtualRegister& reg = range->vreg(); + if (reg.ins()->isPhi()) { + return false; + } + + if (reg.def()->policy() == LDefinition::FIXED && + !reg.def()->output()->isRegister()) { + return false; + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// Misc helpers: atomic LIR groups // +// // +/////////////////////////////////////////////////////////////////////////////// + +// The following groupings contain implicit (invisible to ::buildLivenessInfo) +// value flows, and therefore no split points may be requested inside them. +// This is an otherwise unstated part of the contract between LIR generation +// and the allocator. +// +// (1) (any insn) ; OsiPoint +// +// [Further group definitions and supporting code to come, pending rework +// of the wasm atomic-group situation.] + +CodePosition RegisterAllocator::minimalDefEnd(LNode* ins) const { + // Compute the shortest interval that captures vregs defined by ins. + // Watch for instructions that are followed by an OSI point. + // If moves are introduced between the instruction and the OSI point then + // safepoint information for the instruction may be incorrect. + while (true) { + LNode* next = insData[ins->id() + 1]; + if (!next->isOsiPoint()) { + break; + } + ins = next; + } + + return outputOf(ins); +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// Misc helpers: computation of bundle priorities and spill weights // +// // +/////////////////////////////////////////////////////////////////////////////// + +size_t BacktrackingAllocator::computePriority(LiveBundle* bundle) { + // The priority of a bundle is its total length, so that longer lived + // bundles will be processed before shorter ones (even if the longer ones + // have a low spill weight). See processBundle(). + size_t lifetimeTotal = 0; + + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + lifetimeTotal += range->to() - range->from(); + } + + return lifetimeTotal; +} + +bool BacktrackingAllocator::minimalDef(LiveRange* range, LNode* ins) { + // Whether this is a minimal range capturing a definition at ins. + return (range->to() <= minimalDefEnd(ins).next()) && + ((!ins->isPhi() && range->from() == inputOf(ins)) || + range->from() == outputOf(ins)); +} + +bool BacktrackingAllocator::minimalUse(LiveRange* range, UsePosition* use) { + // Whether this is a minimal range capturing |use|. + LNode* ins = insData[use->pos]; + return (range->from() == inputOf(ins)) && + (range->to() == + (use->use()->usedAtStart() ? outputOf(ins) : outputOf(ins).next())); +} + +bool BacktrackingAllocator::minimalBundle(LiveBundle* bundle, bool* pfixed) { + LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); + LiveRange* range = LiveRange::get(*iter); + + if (!range->hasVreg()) { + *pfixed = true; + return true; + } + + // If a bundle contains multiple ranges, splitAtAllRegisterUses will split + // each range into a separate bundle. + if (++iter) { + return false; + } + + if (range->hasDefinition()) { + VirtualRegister& reg = range->vreg(); + if (pfixed) { + *pfixed = reg.def()->policy() == LDefinition::FIXED && + reg.def()->output()->isRegister(); + } + return minimalDef(range, reg.ins()); + } + + bool fixed = false, minimal = false, multiple = false; + + for (UsePositionIterator iter = range->usesBegin(); iter; iter++) { + if (iter != range->usesBegin()) { + multiple = true; + } + + switch (iter->usePolicy()) { + case LUse::FIXED: + if (fixed) { + return false; + } + fixed = true; + if (minimalUse(range, *iter)) { + minimal = true; + } + break; + + case LUse::REGISTER: + if (minimalUse(range, *iter)) { + minimal = true; + } + break; + + default: + break; + } + } + + // If a range contains a fixed use and at least one other use, + // splitAtAllRegisterUses will split each use into a different bundle. + if (multiple && fixed) { + minimal = false; + } + + if (pfixed) { + *pfixed = fixed; + } + return minimal; +} + +size_t BacktrackingAllocator::computeSpillWeight(LiveBundle* bundle) { + // Minimal bundles have an extremely high spill weight, to ensure they + // can evict any other bundles and be allocated to a register. + bool fixed; + if (minimalBundle(bundle, &fixed)) { + return fixed ? 2000000 : 1000000; + } + + size_t usesTotal = 0; + fixed = false; + + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + + if (range->hasDefinition()) { + VirtualRegister& reg = range->vreg(); + if (reg.def()->policy() == LDefinition::FIXED && + reg.def()->output()->isRegister()) { + usesTotal += 2000; + fixed = true; + } else if (!reg.ins()->isPhi()) { + usesTotal += 2000; + } + } + + usesTotal += range->usesSpillWeight(); + if (range->numFixedUses() > 0) { + fixed = true; + } + } + + // Bundles with fixed uses are given a higher spill weight, since they must + // be allocated to a specific register. + if (testbed && fixed) { + usesTotal *= 2; + } + + // Compute spill weight as a use density, lowering the weight for long + // lived bundles with relatively few uses. + size_t lifetimeTotal = computePriority(bundle); + return lifetimeTotal ? usesTotal / lifetimeTotal : 0; +} + +size_t BacktrackingAllocator::maximumSpillWeight( + const LiveBundleVector& bundles) { + size_t maxWeight = 0; + for (size_t i = 0; i < bundles.length(); i++) { + maxWeight = std::max(maxWeight, computeSpillWeight(bundles[i])); + } + return maxWeight; +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// Initialization of the allocator // +// // +/////////////////////////////////////////////////////////////////////////////// + +// This function pre-allocates and initializes as much global state as possible +// to avoid littering the algorithms with memory management cruft. +bool BacktrackingAllocator::init() { + if (!RegisterAllocator::init()) { + return false; + } + + liveIn = mir->allocate<BitSet>(graph.numBlockIds()); + if (!liveIn) { + return false; + } + + size_t numVregs = graph.numVirtualRegisters(); + if (!vregs.init(mir->alloc(), numVregs)) { + return false; + } + for (uint32_t i = 0; i < numVregs; i++) { + new (&vregs[i]) VirtualRegister(); + } + + // Build virtual register objects. + for (size_t i = 0; i < graph.numBlocks(); i++) { + if (mir->shouldCancel("Create data structures (main loop)")) { + return false; + } + + LBlock* block = graph.getBlock(i); + for (LInstructionIterator ins = block->begin(); ins != block->end(); + ins++) { + if (mir->shouldCancel("Create data structures (inner loop 1)")) { + return false; + } + + for (size_t j = 0; j < ins->numDefs(); j++) { + LDefinition* def = ins->getDef(j); + if (def->isBogusTemp()) { + continue; + } + vreg(def).init(*ins, def, /* isTemp = */ false); + } + + for (size_t j = 0; j < ins->numTemps(); j++) { + LDefinition* def = ins->getTemp(j); + if (def->isBogusTemp()) { + continue; + } + vreg(def).init(*ins, def, /* isTemp = */ true); + } + } + for (size_t j = 0; j < block->numPhis(); j++) { + LPhi* phi = block->getPhi(j); + LDefinition* def = phi->getDef(0); + vreg(def).init(phi, def, /* isTemp = */ false); + } + } + + LiveRegisterSet remainingRegisters(allRegisters_.asLiveSet()); + while (!remainingRegisters.emptyGeneral()) { + AnyRegister reg = AnyRegister(remainingRegisters.takeAnyGeneral()); + registers[reg.code()].allocatable = true; + } + while (!remainingRegisters.emptyFloat()) { + AnyRegister reg = + AnyRegister(remainingRegisters.takeAnyFloat<RegTypeName::Any>()); + registers[reg.code()].allocatable = true; + } + + LifoAlloc* lifoAlloc = mir->alloc().lifoAlloc(); + for (size_t i = 0; i < AnyRegister::Total; i++) { + registers[i].reg = AnyRegister::FromCode(i); + registers[i].allocations.setAllocator(lifoAlloc); + } + + hotcode.setAllocator(lifoAlloc); + callRanges.setAllocator(lifoAlloc); + + // Partition the graph into hot and cold sections, for helping to make + // splitting decisions. Since we don't have any profiling data this is a + // crapshoot, so just mark the bodies of inner loops as hot and everything + // else as cold. + + LBlock* backedge = nullptr; + for (size_t i = 0; i < graph.numBlocks(); i++) { + LBlock* block = graph.getBlock(i); + + // If we see a loop header, mark the backedge so we know when we have + // hit the end of the loop. Don't process the loop immediately, so that + // if there is an inner loop we will ignore the outer backedge. + if (block->mir()->isLoopHeader()) { + backedge = block->mir()->backedge()->lir(); + } + + if (block == backedge) { + LBlock* header = block->mir()->loopHeaderOfBackedge()->lir(); + LiveRange* range = LiveRange::FallibleNew( + alloc(), nullptr, entryOf(header), exitOf(block).next()); + if (!range || !hotcode.insert(range)) { + return false; + } + } + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// Liveness analysis // +// // +/////////////////////////////////////////////////////////////////////////////// + +// Helper for ::buildLivenessInfo +bool BacktrackingAllocator::addInitialFixedRange(AnyRegister reg, + CodePosition from, + CodePosition to) { + LiveRange* range = LiveRange::FallibleNew(alloc(), nullptr, from, to); + if (!range) { + return false; + } + LiveRangePlus rangePlus(range); + return registers[reg.code()].allocations.insert(rangePlus); +} + +// Helper for ::buildLivenessInfo +#ifdef DEBUG +// Returns true iff ins has a def/temp reusing the input allocation. +static bool IsInputReused(LInstruction* ins, LUse* use) { + for (size_t i = 0; i < ins->numDefs(); i++) { + if (ins->getDef(i)->policy() == LDefinition::MUST_REUSE_INPUT && + ins->getOperand(ins->getDef(i)->getReusedInput())->toUse() == use) { + return true; + } + } + + for (size_t i = 0; i < ins->numTemps(); i++) { + if (ins->getTemp(i)->policy() == LDefinition::MUST_REUSE_INPUT && + ins->getOperand(ins->getTemp(i)->getReusedInput())->toUse() == use) { + return true; + } + } + + return false; +} +#endif + +/* + * This function builds up liveness ranges for all virtual registers + * defined in the function. + * + * The algorithm is based on the one published in: + * + * Wimmer, Christian, and Michael Franz. "Linear Scan Register Allocation on + * SSA Form." Proceedings of the International Symposium on Code Generation + * and Optimization. Toronto, Ontario, Canada, ACM. 2010. 170-79. PDF. + * + * The algorithm operates on blocks ordered such that dominators of a block + * are before the block itself, and such that all blocks of a loop are + * contiguous. It proceeds backwards over the instructions in this order, + * marking registers live at their uses, ending their live ranges at + * definitions, and recording which registers are live at the top of every + * block. To deal with loop backedges, registers live at the beginning of + * a loop gain a range covering the entire loop. + */ +bool BacktrackingAllocator::buildLivenessInfo() { + JitSpew(JitSpew_RegAlloc, "Beginning liveness analysis"); + + Vector<MBasicBlock*, 1, SystemAllocPolicy> loopWorkList; + BitSet loopDone(graph.numBlockIds()); + if (!loopDone.init(alloc())) { + return false; + } + + size_t numRanges = 0; + + for (size_t i = graph.numBlocks(); i > 0; i--) { + if (mir->shouldCancel("Build Liveness Info (main loop)")) { + return false; + } + + LBlock* block = graph.getBlock(i - 1); + MBasicBlock* mblock = block->mir(); + + BitSet& live = liveIn[mblock->id()]; + new (&live) BitSet(graph.numVirtualRegisters()); + if (!live.init(alloc())) { + return false; + } + + // Propagate liveIn from our successors to us. + for (size_t i = 0; i < mblock->lastIns()->numSuccessors(); i++) { + MBasicBlock* successor = mblock->lastIns()->getSuccessor(i); + // Skip backedges, as we fix them up at the loop header. + if (mblock->id() < successor->id()) { + live.insertAll(liveIn[successor->id()]); + } + } + + // Add successor phis. + if (mblock->successorWithPhis()) { + LBlock* phiSuccessor = mblock->successorWithPhis()->lir(); + for (unsigned int j = 0; j < phiSuccessor->numPhis(); j++) { + LPhi* phi = phiSuccessor->getPhi(j); + LAllocation* use = phi->getOperand(mblock->positionInPhiSuccessor()); + uint32_t reg = use->toUse()->virtualRegister(); + live.insert(reg); + vreg(use).setUsedByPhi(); + } + } + + // Registers are assumed alive for the entire block, a define shortens + // the range to the point of definition. + for (BitSet::Iterator liveRegId(live); liveRegId; ++liveRegId) { + if (!vregs[*liveRegId].addInitialRange(alloc(), entryOf(block), + exitOf(block).next(), &numRanges)) + return false; + } + + // Shorten the front end of ranges for live variables to their point of + // definition, if found. + for (LInstructionReverseIterator ins = block->rbegin(); + ins != block->rend(); ins++) { + // Calls may clobber registers, so force a spill and reload around the + // callsite. + if (ins->isCall()) { + for (AnyRegisterIterator iter(allRegisters_.asLiveSet()); iter.more(); + ++iter) { + bool found = false; + for (size_t i = 0; i < ins->numDefs(); i++) { + if (ins->getDef(i)->isFixed() && + ins->getDef(i)->output()->aliases(LAllocation(*iter))) { + found = true; + break; + } + } + // If this register doesn't have an explicit def above, mark + // it as clobbered by the call unless it is actually + // call-preserved. + if (!found && !ins->isCallPreserved(*iter)) { + if (!addInitialFixedRange(*iter, outputOf(*ins), + outputOf(*ins).next())) { + return false; + } + } + } + + CallRange* callRange = new (alloc().fallible()) + CallRange(outputOf(*ins), outputOf(*ins).next()); + if (!callRange) { + return false; + } + + callRangesList.pushFront(callRange); + if (!callRanges.insert(callRange)) { + return false; + } + } + + for (size_t i = 0; i < ins->numDefs(); i++) { + LDefinition* def = ins->getDef(i); + if (def->isBogusTemp()) { + continue; + } + + CodePosition from = outputOf(*ins); + + if (def->policy() == LDefinition::MUST_REUSE_INPUT) { + // MUST_REUSE_INPUT is implemented by allocating an output + // register and moving the input to it. Register hints are + // used to avoid unnecessary moves. We give the input an + // LUse::ANY policy to avoid allocating a register for the + // input. + LUse* inputUse = ins->getOperand(def->getReusedInput())->toUse(); + MOZ_ASSERT(inputUse->policy() == LUse::REGISTER); + MOZ_ASSERT(inputUse->usedAtStart()); + *inputUse = LUse(inputUse->virtualRegister(), LUse::ANY, + /* usedAtStart = */ true); + } + + if (!vreg(def).addInitialRange(alloc(), from, from.next(), + &numRanges)) { + return false; + } + vreg(def).setInitialDefinition(from); + live.remove(def->virtualRegister()); + } + + for (size_t i = 0; i < ins->numTemps(); i++) { + LDefinition* temp = ins->getTemp(i); + if (temp->isBogusTemp()) { + continue; + } + + // Normally temps are considered to cover both the input + // and output of the associated instruction. In some cases + // though we want to use a fixed register as both an input + // and clobbered register in the instruction, so watch for + // this and shorten the temp to cover only the output. + CodePosition from = inputOf(*ins); + if (temp->policy() == LDefinition::FIXED) { + AnyRegister reg = temp->output()->toRegister(); + for (LInstruction::InputIterator alloc(**ins); alloc.more(); + alloc.next()) { + if (alloc->isUse()) { + LUse* use = alloc->toUse(); + if (use->isFixedRegister()) { + if (GetFixedRegister(vreg(use).def(), use) == reg) { + from = outputOf(*ins); + } + } + } + } + } + + // * For non-call instructions, temps cover both the input and output, + // so temps never alias uses (even at-start uses) or defs. + // * For call instructions, temps only cover the input (the output is + // used for the force-spill ranges added above). This means temps + // still don't alias uses but they can alias the (fixed) defs. For now + // we conservatively require temps to have a fixed register for call + // instructions to prevent a footgun. + MOZ_ASSERT_IF(ins->isCall(), temp->policy() == LDefinition::FIXED); + CodePosition to = + ins->isCall() ? outputOf(*ins) : outputOf(*ins).next(); + + if (!vreg(temp).addInitialRange(alloc(), from, to, &numRanges)) { + return false; + } + vreg(temp).setInitialDefinition(from); + } + + DebugOnly<bool> hasUseRegister = false; + DebugOnly<bool> hasUseRegisterAtStart = false; + + for (LInstruction::InputIterator inputAlloc(**ins); inputAlloc.more(); + inputAlloc.next()) { + if (inputAlloc->isUse()) { + LUse* use = inputAlloc->toUse(); + + // Call uses should always be at-start, since calls use all + // registers. + MOZ_ASSERT_IF(ins->isCall() && !inputAlloc.isSnapshotInput(), + use->usedAtStart()); + +#ifdef DEBUG + // If there are both useRegisterAtStart(x) and useRegister(y) + // uses, we may assign the same register to both operands + // (bug 772830). Don't allow this for now. + if (use->policy() == LUse::REGISTER) { + if (use->usedAtStart()) { + if (!IsInputReused(*ins, use)) { + hasUseRegisterAtStart = true; + } + } else { + hasUseRegister = true; + } + } + MOZ_ASSERT(!(hasUseRegister && hasUseRegisterAtStart)); +#endif + + // Don't treat RECOVERED_INPUT uses as keeping the vreg alive. + if (use->policy() == LUse::RECOVERED_INPUT) { + continue; + } + + CodePosition to = use->usedAtStart() ? inputOf(*ins) : outputOf(*ins); + if (use->isFixedRegister()) { + LAllocation reg(AnyRegister::FromCode(use->registerCode())); + for (size_t i = 0; i < ins->numDefs(); i++) { + LDefinition* def = ins->getDef(i); + if (def->policy() == LDefinition::FIXED && + *def->output() == reg) { + to = inputOf(*ins); + } + } + } + + if (!vreg(use).addInitialRange(alloc(), entryOf(block), to.next(), + &numRanges)) { + return false; + } + UsePosition* usePosition = + new (alloc().fallible()) UsePosition(use, to); + if (!usePosition) { + return false; + } + vreg(use).addInitialUse(usePosition); + live.insert(use->virtualRegister()); + } + } + } + + // Phis have simultaneous assignment semantics at block begin, so at + // the beginning of the block we can be sure that liveIn does not + // contain any phi outputs. + for (unsigned int i = 0; i < block->numPhis(); i++) { + LDefinition* def = block->getPhi(i)->getDef(0); + if (live.contains(def->virtualRegister())) { + live.remove(def->virtualRegister()); + } else { + // This is a dead phi, so add a dummy range over all phis. This + // can go away if we have an earlier dead code elimination pass. + CodePosition entryPos = entryOf(block); + if (!vreg(def).addInitialRange(alloc(), entryPos, entryPos.next(), + &numRanges)) { + return false; + } + } + } + + if (mblock->isLoopHeader()) { + // A divergence from the published algorithm is required here, as + // our block order does not guarantee that blocks of a loop are + // contiguous. As a result, a single live range spanning the + // loop is not possible. Additionally, we require liveIn in a later + // pass for resolution, so that must also be fixed up here. + MBasicBlock* loopBlock = mblock->backedge(); + while (true) { + // Blocks must already have been visited to have a liveIn set. + MOZ_ASSERT(loopBlock->id() >= mblock->id()); + + // Add a range for this entire loop block + CodePosition from = entryOf(loopBlock->lir()); + CodePosition to = exitOf(loopBlock->lir()).next(); + + for (BitSet::Iterator liveRegId(live); liveRegId; ++liveRegId) { + if (!vregs[*liveRegId].addInitialRange(alloc(), from, to, + &numRanges)) { + return false; + } + } + + // Fix up the liveIn set. + liveIn[loopBlock->id()].insertAll(live); + + // Make sure we don't visit this node again + loopDone.insert(loopBlock->id()); + + // If this is the loop header, any predecessors are either the + // backedge or out of the loop, so skip any predecessors of + // this block + if (loopBlock != mblock) { + for (size_t i = 0; i < loopBlock->numPredecessors(); i++) { + MBasicBlock* pred = loopBlock->getPredecessor(i); + if (loopDone.contains(pred->id())) { + continue; + } + if (!loopWorkList.append(pred)) { + return false; + } + } + } + + // Terminate loop if out of work. + if (loopWorkList.empty()) { + break; + } + + // Grab the next block off the work list, skipping any OSR block. + MBasicBlock* osrBlock = graph.mir().osrBlock(); + while (!loopWorkList.empty()) { + loopBlock = loopWorkList.popCopy(); + if (loopBlock != osrBlock) { + break; + } + } + + // If end is reached without finding a non-OSR block, then no more work + // items were found. + if (loopBlock == osrBlock) { + MOZ_ASSERT(loopWorkList.empty()); + break; + } + } + + // Clear the done set for other loops + loopDone.clear(); + } + + MOZ_ASSERT_IF(!mblock->numPredecessors(), live.empty()); + } + + JitSpew(JitSpew_RegAlloc, "Completed liveness analysis"); + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// Merging and queueing of LiveRange groups // +// // +/////////////////////////////////////////////////////////////////////////////// + +// Helper for ::tryMergeBundles +static bool IsArgumentSlotDefinition(LDefinition* def) { + return def->policy() == LDefinition::FIXED && def->output()->isArgument(); +} + +// Helper for ::tryMergeBundles +static bool IsThisSlotDefinition(LDefinition* def) { + return IsArgumentSlotDefinition(def) && + def->output()->toArgument()->index() < + THIS_FRAME_ARGSLOT + sizeof(Value); +} + +// Helper for ::tryMergeBundles +static bool HasStackPolicy(LDefinition* def) { + return def->policy() == LDefinition::STACK; +} + +// Helper for ::tryMergeBundles +static bool CanMergeTypesInBundle(LDefinition::Type a, LDefinition::Type b) { + // Fast path for the common case. + if (a == b) { + return true; + } + + // Only merge if the sizes match, so that we don't get confused about the + // width of spill slots. + return StackSlotAllocator::width(a) == StackSlotAllocator::width(b); +} + +// Helper for ::tryMergeReusedRegister +bool BacktrackingAllocator::tryMergeBundles(LiveBundle* bundle0, + LiveBundle* bundle1) { + // See if bundle0 and bundle1 can be merged together. + if (bundle0 == bundle1) { + return true; + } + + // Get a representative virtual register from each bundle. + VirtualRegister& reg0 = bundle0->firstRange()->vreg(); + VirtualRegister& reg1 = bundle1->firstRange()->vreg(); + + MOZ_ASSERT(CanMergeTypesInBundle(reg0.type(), reg1.type())); + MOZ_ASSERT(reg0.isCompatible(reg1)); + + // Registers which might spill to the frame's |this| slot can only be + // grouped with other such registers. The frame's |this| slot must always + // hold the |this| value, as required by JitFrame tracing and by the Ion + // constructor calling convention. + if (IsThisSlotDefinition(reg0.def()) || IsThisSlotDefinition(reg1.def())) { + if (*reg0.def()->output() != *reg1.def()->output()) { + return true; + } + } + + // Registers which might spill to the frame's argument slots can only be + // grouped with other such registers if the frame might access those + // arguments through a lazy arguments object or rest parameter. + if (IsArgumentSlotDefinition(reg0.def()) || + IsArgumentSlotDefinition(reg1.def())) { + if (graph.mir().entryBlock()->info().mayReadFrameArgsDirectly()) { + if (*reg0.def()->output() != *reg1.def()->output()) { + return true; + } + } + } + + // When we make a call to a WebAssembly function that returns multiple + // results, some of those results can go on the stack. The callee is passed a + // pointer to this stack area, which is represented as having policy + // LDefinition::STACK (with type LDefinition::STACKRESULTS). Individual + // results alias parts of the stack area with a value-appropriate type, but + // policy LDefinition::STACK. This aliasing between allocations makes it + // unsound to merge anything with a LDefinition::STACK policy. + if (HasStackPolicy(reg0.def()) || HasStackPolicy(reg1.def())) { + return true; + } + + // Limit the number of times we compare ranges if there are many ranges in + // one of the bundles, to avoid quadratic behavior. + static const size_t MAX_RANGES = 200; + + // Make sure that ranges in the bundles do not overlap. + LiveRange::BundleLinkIterator iter0 = bundle0->rangesBegin(), + iter1 = bundle1->rangesBegin(); + size_t count = 0; + while (iter0 && iter1) { + if (++count >= MAX_RANGES) { + return true; + } + + LiveRange* range0 = LiveRange::get(*iter0); + LiveRange* range1 = LiveRange::get(*iter1); + + if (range0->from() >= range1->to()) { + iter1++; + } else if (range1->from() >= range0->to()) { + iter0++; + } else { + return true; + } + } + + // Move all ranges from bundle1 into bundle0. + while (LiveRange* range = bundle1->popFirstRange()) { + bundle0->addRange(range); + } + + return true; +} + +// Helper for ::mergeAndQueueRegisters +void BacktrackingAllocator::allocateStackDefinition(VirtualRegister& reg) { + LInstruction* ins = reg.ins()->toInstruction(); + if (reg.def()->type() == LDefinition::STACKRESULTS) { + LStackArea alloc(ins->toInstruction()); + stackSlotAllocator.allocateStackArea(&alloc); + reg.def()->setOutput(alloc); + } else { + // Because the definitions are visited in order, the area has been allocated + // before we reach this result, so we know the operand is an LStackArea. + const LUse* use = ins->getOperand(0)->toUse(); + VirtualRegister& area = vregs[use->virtualRegister()]; + const LStackArea* areaAlloc = area.def()->output()->toStackArea(); + reg.def()->setOutput(areaAlloc->resultAlloc(ins, reg.def())); + } +} + +// Helper for ::mergeAndQueueRegisters +bool BacktrackingAllocator::tryMergeReusedRegister(VirtualRegister& def, + VirtualRegister& input) { + // def is a vreg which reuses input for its output physical register. Try + // to merge ranges for def with those of input if possible, as avoiding + // copies before def's instruction is crucial for generated code quality + // (MUST_REUSE_INPUT is used for all arithmetic on x86/x64). + + if (def.rangeFor(inputOf(def.ins()))) { + MOZ_ASSERT(def.isTemp()); + def.setMustCopyInput(); + return true; + } + + if (!CanMergeTypesInBundle(def.type(), input.type())) { + def.setMustCopyInput(); + return true; + } + + LiveRange* inputRange = input.rangeFor(outputOf(def.ins())); + if (!inputRange) { + // The input is not live after the instruction, either in a safepoint + // for the instruction or in subsequent code. The input and output + // can thus be in the same group. + return tryMergeBundles(def.firstBundle(), input.firstBundle()); + } + + // Avoid merging in very large live ranges as merging has non-linear + // complexity. The cutoff value is hard to gauge. 1M was chosen because it + // is "large" and yet usefully caps compile time on AutoCad-for-the-web to + // something reasonable on a 2017-era desktop system. + const uint32_t RANGE_SIZE_CUTOFF = 1000000; + if (inputRange->to() - inputRange->from() > RANGE_SIZE_CUTOFF) { + def.setMustCopyInput(); + return true; + } + + // The input is live afterwards, either in future instructions or in a + // safepoint for the reusing instruction. This is impossible to satisfy + // without copying the input. + // + // It may or may not be better to split the input into two bundles at the + // point of the definition, which may permit merging. One case where it is + // definitely better to split is if the input never has any register uses + // after the instruction. Handle this splitting eagerly. + + LBlock* block = def.ins()->block(); + + // The input's lifetime must end within the same block as the definition, + // otherwise it could live on in phis elsewhere. + if (inputRange != input.lastRange() || inputRange->to() > exitOf(block)) { + def.setMustCopyInput(); + return true; + } + + // If we already split the input for some other register, don't make a + // third bundle. + if (inputRange->bundle() != input.firstRange()->bundle()) { + def.setMustCopyInput(); + return true; + } + + // If the input will start out in memory then adding a separate bundle for + // memory uses after the def won't help. + if (input.def()->isFixed() && !input.def()->output()->isRegister()) { + def.setMustCopyInput(); + return true; + } + + // The input cannot have register or reused uses after the definition. + for (UsePositionIterator iter = inputRange->usesBegin(); iter; iter++) { + if (iter->pos <= inputOf(def.ins())) { + continue; + } + + LUse* use = iter->use(); + if (FindReusingDefOrTemp(insData[iter->pos], use)) { + def.setMustCopyInput(); + return true; + } + if (iter->usePolicy() != LUse::ANY && + iter->usePolicy() != LUse::KEEPALIVE) { + def.setMustCopyInput(); + return true; + } + } + + LiveRange* preRange = LiveRange::FallibleNew( + alloc(), &input, inputRange->from(), outputOf(def.ins())); + if (!preRange) { + return false; + } + + // The new range starts at reg's input position, which means it overlaps + // with the old range at one position. This is what we want, because we + // need to copy the input before the instruction. + LiveRange* postRange = LiveRange::FallibleNew( + alloc(), &input, inputOf(def.ins()), inputRange->to()); + if (!postRange) { + return false; + } + + inputRange->tryToMoveDefAndUsesInto(preRange); + inputRange->tryToMoveDefAndUsesInto(postRange); + MOZ_ASSERT(!inputRange->hasUses()); + + JitSpewIfEnabled(JitSpew_RegAlloc, + " splitting reused input at %u to try to help grouping", + inputOf(def.ins()).bits()); + + LiveBundle* firstBundle = inputRange->bundle(); + input.removeRange(inputRange); + input.addRange(preRange); + input.addRange(postRange); + + firstBundle->removeRange(inputRange); + firstBundle->addRange(preRange); + + // The new range goes in a separate bundle, where it will be spilled during + // allocation. + LiveBundle* secondBundle = LiveBundle::FallibleNew(alloc(), nullptr, nullptr); + if (!secondBundle) { + return false; + } + secondBundle->addRange(postRange); + + return tryMergeBundles(def.firstBundle(), input.firstBundle()); +} + +bool BacktrackingAllocator::mergeAndQueueRegisters() { + MOZ_ASSERT(!vregs[0u].hasRanges()); + + // Create a bundle for each register containing all its ranges. + for (size_t i = 1; i < graph.numVirtualRegisters(); i++) { + VirtualRegister& reg = vregs[i]; + if (!reg.hasRanges()) { + continue; + } + + LiveBundle* bundle = LiveBundle::FallibleNew(alloc(), nullptr, nullptr); + if (!bundle) { + return false; + } + for (LiveRange::RegisterLinkIterator iter = reg.rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + bundle->addRange(range); + } + } + + // If there is an OSR block, merge parameters in that block with the + // corresponding parameters in the initial block. + if (MBasicBlock* osr = graph.mir().osrBlock()) { + size_t original = 1; + for (LInstructionIterator iter = osr->lir()->begin(); + iter != osr->lir()->end(); iter++) { + if (iter->isParameter()) { + for (size_t i = 0; i < iter->numDefs(); i++) { + DebugOnly<bool> found = false; + VirtualRegister& paramVreg = vreg(iter->getDef(i)); + for (; original < paramVreg.vreg(); original++) { + VirtualRegister& originalVreg = vregs[original]; + if (*originalVreg.def()->output() == *iter->getDef(i)->output()) { + MOZ_ASSERT(originalVreg.ins()->isParameter()); + if (!tryMergeBundles(originalVreg.firstBundle(), + paramVreg.firstBundle())) { + return false; + } + found = true; + break; + } + } + MOZ_ASSERT(found); + } + } + } + } + + // Try to merge registers with their reused inputs. + for (size_t i = 1; i < graph.numVirtualRegisters(); i++) { + VirtualRegister& reg = vregs[i]; + if (!reg.hasRanges()) { + continue; + } + + if (reg.def()->policy() == LDefinition::MUST_REUSE_INPUT) { + LUse* use = reg.ins() + ->toInstruction() + ->getOperand(reg.def()->getReusedInput()) + ->toUse(); + if (!tryMergeReusedRegister(reg, vreg(use))) { + return false; + } + } + } + + // Try to merge phis with their inputs. + for (size_t i = 0; i < graph.numBlocks(); i++) { + LBlock* block = graph.getBlock(i); + for (size_t j = 0; j < block->numPhis(); j++) { + LPhi* phi = block->getPhi(j); + VirtualRegister& outputVreg = vreg(phi->getDef(0)); + for (size_t k = 0, kend = phi->numOperands(); k < kend; k++) { + VirtualRegister& inputVreg = vreg(phi->getOperand(k)->toUse()); + if (!tryMergeBundles(inputVreg.firstBundle(), + outputVreg.firstBundle())) { + return false; + } + } + } + } + + // Add all bundles to the allocation queue, and create spill sets for them. + for (size_t i = 1; i < graph.numVirtualRegisters(); i++) { + VirtualRegister& reg = vregs[i]; + + // Eagerly allocate stack result areas and their component stack results. + if (reg.def() && reg.def()->policy() == LDefinition::STACK) { + allocateStackDefinition(reg); + } + + for (LiveRange::RegisterLinkIterator iter = reg.rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + LiveBundle* bundle = range->bundle(); + if (range == bundle->firstRange()) { + if (!alloc().ensureBallast()) { + return false; + } + + SpillSet* spill = SpillSet::New(alloc()); + if (!spill) { + return false; + } + bundle->setSpillSet(spill); + + size_t priority = computePriority(bundle); + if (!allocationQueue.insert(QueueItem(bundle, priority))) { + return false; + } + } + } + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// Code for the splitting/spilling subsystem begins here. // +// // +// The code that follows is structured in the following sequence: // +// // +// (1) Routines that are helpers for ::splitAt. // +// (2) ::splitAt itself, which implements splitting decisions. // +// (3) heuristic routines (eg ::splitAcrossCalls), which decide where // +// splits should be made. They then call ::splitAt to perform the // +// chosen split. // +// (4) The top level driver, ::chooseBundleSplit. // +// // +// There are further comments on ::splitAt and ::chooseBundleSplit below. // +// // +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// // +// Implementation of splitting decisions, but not the making of those // +// decisions: various helper functions // +// // +/////////////////////////////////////////////////////////////////////////////// + +bool BacktrackingAllocator::updateVirtualRegisterListsThenRequeueBundles( + LiveBundle* bundle, const LiveBundleVector& newBundles) { +#ifdef DEBUG + if (newBundles.length() == 1) { + LiveBundle* newBundle = newBundles[0]; + if (newBundle->numRanges() == bundle->numRanges() && + computePriority(newBundle) == computePriority(bundle)) { + bool different = false; + LiveRange::BundleLinkIterator oldRanges = bundle->rangesBegin(); + LiveRange::BundleLinkIterator newRanges = newBundle->rangesBegin(); + while (oldRanges) { + LiveRange* oldRange = LiveRange::get(*oldRanges); + LiveRange* newRange = LiveRange::get(*newRanges); + if (oldRange->from() != newRange->from() || + oldRange->to() != newRange->to()) { + different = true; + break; + } + oldRanges++; + newRanges++; + } + + // This is likely to trigger an infinite loop in register allocation. This + // can be the result of invalid register constraints, making regalloc + // impossible; consider relaxing those. + MOZ_ASSERT(different, + "Split results in the same bundle with the same priority"); + } + } +#endif + + if (JitSpewEnabled(JitSpew_RegAlloc)) { + JitSpew(JitSpew_RegAlloc, " .. into:"); + for (size_t i = 0; i < newBundles.length(); i++) { + JitSpew(JitSpew_RegAlloc, " %s", newBundles[i]->toString().get()); + } + } + + // Remove all ranges in the old bundle from their register's list. + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + range->vreg().removeRange(range); + } + + // Add all ranges in the new bundles to their register's list. + for (size_t i = 0; i < newBundles.length(); i++) { + LiveBundle* newBundle = newBundles[i]; + for (LiveRange::BundleLinkIterator iter = newBundle->rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + range->vreg().addRange(range); + } + } + + // Queue the new bundles for register assignment. + for (size_t i = 0; i < newBundles.length(); i++) { + LiveBundle* newBundle = newBundles[i]; + size_t priority = computePriority(newBundle); + if (!allocationQueue.insert(QueueItem(newBundle, priority))) { + return false; + } + } + + return true; +} + +// Helper for ::splitAt +// When splitting a bundle according to a list of split positions, return +// whether a use or range at |pos| should use a different bundle than the last +// position this was called for. +static bool UseNewBundle(const SplitPositionVector& splitPositions, + CodePosition pos, size_t* activeSplitPosition) { + if (splitPositions.empty()) { + // When the split positions are empty we are splitting at all uses. + return true; + } + + if (*activeSplitPosition == splitPositions.length()) { + // We've advanced past all split positions. + return false; + } + + if (splitPositions[*activeSplitPosition] > pos) { + // We haven't gotten to the next split position yet. + return false; + } + + // We've advanced past the next split position, find the next one which we + // should split at. + while (*activeSplitPosition < splitPositions.length() && + splitPositions[*activeSplitPosition] <= pos) { + (*activeSplitPosition)++; + } + return true; +} + +// Helper for ::splitAt +static bool HasPrecedingRangeSharingVreg(LiveBundle* bundle, LiveRange* range) { + MOZ_ASSERT(range->bundle() == bundle); + + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; + iter++) { + LiveRange* prevRange = LiveRange::get(*iter); + if (prevRange == range) { + return false; + } + if (&prevRange->vreg() == &range->vreg()) { + return true; + } + } + + MOZ_CRASH(); +} + +// Helper for ::splitAt +static bool HasFollowingRangeSharingVreg(LiveBundle* bundle, LiveRange* range) { + MOZ_ASSERT(range->bundle() == bundle); + + bool foundRange = false; + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; + iter++) { + LiveRange* prevRange = LiveRange::get(*iter); + if (foundRange && &prevRange->vreg() == &range->vreg()) { + return true; + } + if (prevRange == range) { + foundRange = true; + } + } + + MOZ_ASSERT(foundRange); + return false; +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// Implementation of splitting decisions, but not the making of those // +// decisions: // +// ::splitAt // +// // +/////////////////////////////////////////////////////////////////////////////// + +// ::splitAt +// --------- +// It would be nice to be able to interpret ::splitAt as simply performing +// whatever split the heuristic routines decide on. Unfortunately it +// tries to "improve" on the initial locations, which as +// https://bugzilla.mozilla.org/show_bug.cgi?id=1758274#c17 shows, often +// leads to excessive spilling. So there is no clean distinction between +// policy (where to split, computed by the heuristic routines) and +// implementation (done by ::splitAt). +// +// ::splitAt -- creation of spill parent bundles +// --------------------------------------------- +// To understand what ::splitAt does, we must refer back to Section 1's +// description of LiveBundle::spillParent_. +// +// Initially (as created by Phase 1), all bundles have `spillParent_` being +// NULL. If ::splitAt is asked to split such a bundle, it will first create a +// "spill bundle" or "spill parent" bundle. This is a copy of the original, +// with two changes: +// +// * all register uses have been removed, so that only stack-compatible uses +// remain. +// +// * for all LiveRanges in the bundle that define a register, the start point +// of the range is moved one CodePosition forwards, thusly: +// +// from = minimalDefEnd(insData[from]).next(); +// +// The reason for the latter relates to the idea described in Section 1, that +// all LiveRanges for any given VirtualRegister must form a tree rooted at the +// defining LiveRange. If the spill-bundle definition range start points are +// the same as those in the original bundle, then we will end up with two roots +// for the tree, and it is then unclear which one should supply "the value". +// +// Putting the spill-bundle start point one CodePosition further along causes +// the range containing the register def (after splitting) to still be the +// defining point. ::createMoveGroupsFromLiveRangeTransitions will observe the +// equivalent spill-bundle range starting one point later and add a MoveGroup +// to move the value into it. Since the spill bundle is intended to be stack +// resident, the effect is to force creation of the MoveGroup that will +// actually spill this value onto the stack. +// +// If the bundle provided to ::splitAt already has a spill parent, then +// ::splitAt doesn't create a new spill parent. This situation will happen +// when the bundle to be split was itself created by splitting. The effect is +// that *all* bundles created from an "original bundle" share the same spill +// parent, and hence they will share the same spill slot, which guarantees that +// all the spilled fragments of a VirtualRegister share the same spill slot, +// which means we'll never have to move a VirtualRegister between different +// spill slots during its lifetime. +// +// ::splitAt -- creation of other bundles +// -------------------------------------- +// With the spill parent bundle question out of the way, ::splitAt then goes on +// to create the remaining new bundles, using near-incomprehensible logic +// steered by `UseNewBundle`. +// +// This supposedly splits the bundle at the positions given by the +// `SplitPositionVector` parameter to ::splitAt, putting them in a temporary +// vector `newBundles`. Whether it really splits at the requested positions or +// not is hard to say; more important is what happens next. +// +// ::splitAt -- "improvement" ("filtering") of the split bundles +// ------------------------------------------------------------- +// ::splitAt now tries to reduce the length of the LiveRanges that make up the +// new bundles (not including the "spill parent"). I assume this is to remove +// sections of the bundles that carry no useful value (eg, extending after the +// last using a range), thereby removing the demand for registers in those +// parts. This does however mean that ::splitAt is no longer really splitting +// where the heuristic routines wanted, and that can lead to a big increase in +// spilling in loops, as +// https://bugzilla.mozilla.org/show_bug.cgi?id=1758274#c17 describes. +// +// ::splitAt -- meaning of the incoming `SplitPositionVector` +// ---------------------------------------------------------- +// ::splitAt has one last mystery which is important to document. The split +// positions are specified as CodePositions, but this leads to ambiguity +// because, in a sequence of N (LIR) instructions, there are 2N valid +// CodePositions. For example: +// +// 6-7 WasmLoadTls [def v2<o>] [use v1:R] +// 8-9 WasmNullConstant [def v3<o>] +// +// Consider splitting the range for `v2`, which starts at CodePosition 7. +// What's the difference between saying "split it at 7" and "split it at 8" ? +// Not much really, since in both cases what we intend is for the range to be +// split in between the two instructions. +// +// Hence I believe the semantics is: +// +// * splitting at an even numbered CodePosition (eg, 8), which is an input-side +// position, means "split before the instruction containing this position". +// +// * splitting at an odd numbered CodePositin (eg, 7), which is an output-side +// position, means "split after the instruction containing this position". +// +// Hence in the example, we could specify either 7 or 8 to mean the same +// placement of the split. Well, almost true, but actually: +// +// (SPEC) specifying 8 means +// +// "split between these two insns, and any resulting MoveGroup goes in the +// list to be emitted before the start of the second insn" +// +// (SPEC) specifying 7 means +// +// "split between these two insns, and any resulting MoveGroup goes in the +// list to be emitted after the end of the first insn" +// +// In most cases we don't care on which "side of the valley" the MoveGroup ends +// up, in which case we can use either convention. +// +// (SPEC) I believe these semantics are implied by the logic in +// ::createMoveGroupsFromLiveRangeTransitions. They are certainly not +// documented anywhere in the code. + +bool BacktrackingAllocator::splitAt(LiveBundle* bundle, + const SplitPositionVector& splitPositions) { + // Split the bundle at the given split points. Register uses which have no + // intervening split points are consolidated into the same bundle. If the + // list of split points is empty, then all register uses are placed in + // minimal bundles. + + // splitPositions should be sorted. + for (size_t i = 1; i < splitPositions.length(); ++i) { + MOZ_ASSERT(splitPositions[i - 1] < splitPositions[i]); + } + + // We don't need to create a new spill bundle if there already is one. + bool spillBundleIsNew = false; + LiveBundle* spillBundle = bundle->spillParent(); + if (!spillBundle) { + spillBundle = LiveBundle::FallibleNew(alloc(), bundle->spillSet(), nullptr); + if (!spillBundle) { + return false; + } + spillBundleIsNew = true; + + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + + CodePosition from = range->from(); + if (isRegisterDefinition(range)) { + from = minimalDefEnd(insData[from]).next(); + } + + if (from < range->to()) { + if (!spillBundle->addRange(alloc(), &range->vreg(), from, + range->to())) { + return false; + } + + if (range->hasDefinition() && !isRegisterDefinition(range)) { + spillBundle->lastRange()->setHasDefinition(); + } + } + } + } + + LiveBundleVector newBundles; + + // The bundle which ranges are currently being added to. + LiveBundle* activeBundle = + LiveBundle::FallibleNew(alloc(), bundle->spillSet(), spillBundle); + if (!activeBundle || !newBundles.append(activeBundle)) { + return false; + } + + // State for use by UseNewBundle. + size_t activeSplitPosition = 0; + + // Make new bundles according to the split positions, and distribute ranges + // and uses to them. + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + + if (UseNewBundle(splitPositions, range->from(), &activeSplitPosition)) { + activeBundle = + LiveBundle::FallibleNew(alloc(), bundle->spillSet(), spillBundle); + if (!activeBundle || !newBundles.append(activeBundle)) { + return false; + } + } + + LiveRange* activeRange = LiveRange::FallibleNew(alloc(), &range->vreg(), + range->from(), range->to()); + if (!activeRange) { + return false; + } + activeBundle->addRange(activeRange); + + if (isRegisterDefinition(range)) { + activeRange->setHasDefinition(); + } + + while (range->hasUses()) { + UsePosition* use = range->popUse(); + LNode* ins = insData[use->pos]; + + // Any uses of a register that appear before its definition has + // finished must be associated with the range for that definition. + if (isRegisterDefinition(range) && + use->pos <= minimalDefEnd(insData[range->from()])) { + activeRange->addUse(use); + } else if (isRegisterUse(use, ins)) { + // Place this register use into a different bundle from the + // last one if there are any split points between the two uses. + // UseNewBundle always returns true if we are splitting at all + // register uses, but we can still reuse the last range and + // bundle if they have uses at the same position, except when + // either use is fixed (the two uses might require incompatible + // registers.) + if (UseNewBundle(splitPositions, use->pos, &activeSplitPosition) && + (!activeRange->hasUses() || + activeRange->usesBegin()->pos != use->pos || + activeRange->usesBegin()->usePolicy() == LUse::FIXED || + use->usePolicy() == LUse::FIXED)) { + activeBundle = + LiveBundle::FallibleNew(alloc(), bundle->spillSet(), spillBundle); + if (!activeBundle || !newBundles.append(activeBundle)) { + return false; + } + activeRange = LiveRange::FallibleNew(alloc(), &range->vreg(), + range->from(), range->to()); + if (!activeRange) { + return false; + } + activeBundle->addRange(activeRange); + } + + activeRange->addUse(use); + } else { + MOZ_ASSERT(spillBundleIsNew); + spillBundle->rangeFor(use->pos)->addUse(use); + } + } + } + + LiveBundleVector filteredBundles; + + // Trim the ends of ranges in each new bundle when there are no other + // earlier or later ranges in the same bundle with the same vreg. + for (size_t i = 0; i < newBundles.length(); i++) { + LiveBundle* bundle = newBundles[i]; + + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter;) { + LiveRange* range = LiveRange::get(*iter); + + if (!range->hasDefinition()) { + if (!HasPrecedingRangeSharingVreg(bundle, range)) { + if (range->hasUses()) { + UsePosition* use = *range->usesBegin(); + range->setFrom(inputOf(insData[use->pos])); + } else { + bundle->removeRangeAndIncrementIterator(iter); + continue; + } + } + } + + if (!HasFollowingRangeSharingVreg(bundle, range)) { + if (range->hasUses()) { + UsePosition* use = range->lastUse(); + range->setTo(use->pos.next()); + } else if (range->hasDefinition()) { + range->setTo(minimalDefEnd(insData[range->from()]).next()); + } else { + bundle->removeRangeAndIncrementIterator(iter); + continue; + } + } + + iter++; + } + + if (bundle->hasRanges() && !filteredBundles.append(bundle)) { + return false; + } + } + + if (spillBundleIsNew && !filteredBundles.append(spillBundle)) { + return false; + } + + return updateVirtualRegisterListsThenRequeueBundles(bundle, filteredBundles); +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// Creation of splitting decisions, but not their implementation: // +// ::splitAcrossCalls // +// ::trySplitAcrossHotcode // +// ::trySplitAfterLastRegisterUse // +// ::trySplitBeforeFirstRegisterUse // +// // +/////////////////////////////////////////////////////////////////////////////// + +bool BacktrackingAllocator::splitAcrossCalls(LiveBundle* bundle) { + // Split the bundle to separate register uses and non-register uses and + // allow the vreg to be spilled across its range. + + // Find the locations of all calls in the bundle's range. + SplitPositionVector callPositions; + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + CallRange searchRange(range->from(), range->to()); + CallRange* callRange; + if (!callRanges.contains(&searchRange, &callRange)) { + // There are no calls inside this range. + continue; + } + MOZ_ASSERT(range->covers(callRange->range.from)); + + // The search above returns an arbitrary call within the range. Walk + // backwards to find the first call in the range. + for (CallRangeList::reverse_iterator riter = + callRangesList.rbegin(callRange); + riter != callRangesList.rend(); ++riter) { + CodePosition pos = riter->range.from; + if (range->covers(pos)) { + callRange = *riter; + } else { + break; + } + } + + // Add all call positions within the range, by walking forwards. + for (CallRangeList::iterator iter = callRangesList.begin(callRange); + iter != callRangesList.end(); ++iter) { + CodePosition pos = iter->range.from; + if (!range->covers(pos)) { + break; + } + + // Calls at the beginning of the range are ignored; there is no splitting + // to do. + if (range->covers(pos.previous())) { + MOZ_ASSERT_IF(callPositions.length(), pos > callPositions.back()); + if (!callPositions.append(pos)) { + return false; + } + } + } + } + MOZ_ASSERT(callPositions.length()); + +#ifdef JS_JITSPEW + JitSpewStart(JitSpew_RegAlloc, " .. split across calls at "); + for (size_t i = 0; i < callPositions.length(); ++i) { + JitSpewCont(JitSpew_RegAlloc, "%s%u", i != 0 ? ", " : "", + callPositions[i].bits()); + } + JitSpewFin(JitSpew_RegAlloc); +#endif + + return splitAt(bundle, callPositions); +} + +bool BacktrackingAllocator::trySplitAcrossHotcode(LiveBundle* bundle, + bool* success) { + // If this bundle has portions that are hot and portions that are cold, + // split it at the boundaries between hot and cold code. + + LiveRange* hotRange = nullptr; + + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + if (hotcode.contains(range, &hotRange)) { + break; + } + } + + // Don't split if there is no hot code in the bundle. + if (!hotRange) { + JitSpew(JitSpew_RegAlloc, " .. bundle does not contain hot code"); + return true; + } + + // Don't split if there is no cold code in the bundle. + bool coldCode = false; + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + if (!hotRange->contains(range)) { + coldCode = true; + break; + } + } + if (!coldCode) { + JitSpew(JitSpew_RegAlloc, " .. bundle does not contain cold code"); + return true; + } + + JitSpewIfEnabled(JitSpew_RegAlloc, " .. split across hot range %s", + hotRange->toString().get()); + + // Tweak the splitting method when compiling wasm code to look at actual + // uses within the hot/cold code. This heuristic is in place as the below + // mechanism regresses several asm.js tests. Hopefully this will be fixed + // soon and this special case removed. See bug 948838. + if (compilingWasm()) { + SplitPositionVector splitPositions; + if (!splitPositions.append(hotRange->from()) || + !splitPositions.append(hotRange->to())) { + return false; + } + *success = true; + return splitAt(bundle, splitPositions); + } + + LiveBundle* hotBundle = LiveBundle::FallibleNew(alloc(), bundle->spillSet(), + bundle->spillParent()); + if (!hotBundle) { + return false; + } + LiveBundle* preBundle = nullptr; + LiveBundle* postBundle = nullptr; + LiveBundle* coldBundle = nullptr; + + if (testbed) { + coldBundle = LiveBundle::FallibleNew(alloc(), bundle->spillSet(), + bundle->spillParent()); + if (!coldBundle) { + return false; + } + } + + // Accumulate the ranges of hot and cold code in the bundle. Note that + // we are only comparing with the single hot range found, so the cold code + // may contain separate hot ranges. + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + LiveRange::Range hot, coldPre, coldPost; + range->intersect(hotRange, &coldPre, &hot, &coldPost); + + if (!hot.empty()) { + if (!hotBundle->addRangeAndDistributeUses(alloc(), range, hot.from, + hot.to)) { + return false; + } + } + + if (!coldPre.empty()) { + if (testbed) { + if (!coldBundle->addRangeAndDistributeUses(alloc(), range, coldPre.from, + coldPre.to)) { + return false; + } + } else { + if (!preBundle) { + preBundle = LiveBundle::FallibleNew(alloc(), bundle->spillSet(), + bundle->spillParent()); + if (!preBundle) { + return false; + } + } + if (!preBundle->addRangeAndDistributeUses(alloc(), range, coldPre.from, + coldPre.to)) { + return false; + } + } + } + + if (!coldPost.empty()) { + if (testbed) { + if (!coldBundle->addRangeAndDistributeUses( + alloc(), range, coldPost.from, coldPost.to)) { + return false; + } + } else { + if (!postBundle) { + postBundle = LiveBundle::FallibleNew(alloc(), bundle->spillSet(), + bundle->spillParent()); + if (!postBundle) { + return false; + } + } + if (!postBundle->addRangeAndDistributeUses( + alloc(), range, coldPost.from, coldPost.to)) { + return false; + } + } + } + } + + MOZ_ASSERT(hotBundle->numRanges() != 0); + + LiveBundleVector newBundles; + if (!newBundles.append(hotBundle)) { + return false; + } + + if (testbed) { + MOZ_ASSERT(coldBundle->numRanges() != 0); + if (!newBundles.append(coldBundle)) { + return false; + } + } else { + MOZ_ASSERT(preBundle || postBundle); + if (preBundle && !newBundles.append(preBundle)) { + return false; + } + if (postBundle && !newBundles.append(postBundle)) { + return false; + } + } + + *success = true; + return updateVirtualRegisterListsThenRequeueBundles(bundle, newBundles); +} + +bool BacktrackingAllocator::trySplitAfterLastRegisterUse(LiveBundle* bundle, + LiveBundle* conflict, + bool* success) { + // If this bundle's later uses do not require it to be in a register, + // split it after the last use which does require a register. If conflict + // is specified, only consider register uses before the conflict starts. + + CodePosition lastRegisterFrom, lastRegisterTo, lastUse; + + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + + // If the range defines a register, consider that a register use for + // our purposes here. + if (isRegisterDefinition(range)) { + CodePosition spillStart = minimalDefEnd(insData[range->from()]).next(); + if (!conflict || spillStart < conflict->firstRange()->from()) { + lastUse = lastRegisterFrom = range->from(); + lastRegisterTo = spillStart; + } + } + + for (UsePositionIterator iter(range->usesBegin()); iter; iter++) { + LNode* ins = insData[iter->pos]; + + // Uses in the bundle should be sorted. + MOZ_ASSERT(iter->pos >= lastUse); + lastUse = inputOf(ins); + + if (!conflict || outputOf(ins) < conflict->firstRange()->from()) { + if (isRegisterUse(*iter, ins, /* considerCopy = */ true)) { + lastRegisterFrom = inputOf(ins); + lastRegisterTo = iter->pos.next(); + } + } + } + } + + // Can't trim non-register uses off the end by splitting. + if (!lastRegisterFrom.bits()) { + JitSpew(JitSpew_RegAlloc, " .. bundle has no register uses"); + return true; + } + if (lastUse < lastRegisterTo) { + JitSpew(JitSpew_RegAlloc, " .. bundle's last use is a register use"); + return true; + } + + JitSpewIfEnabled(JitSpew_RegAlloc, " .. split after last register use at %u", + lastRegisterTo.bits()); + + SplitPositionVector splitPositions; + if (!splitPositions.append(lastRegisterTo)) { + return false; + } + *success = true; + return splitAt(bundle, splitPositions); +} + +bool BacktrackingAllocator::trySplitBeforeFirstRegisterUse(LiveBundle* bundle, + LiveBundle* conflict, + bool* success) { + // If this bundle's earlier uses do not require it to be in a register, + // split it before the first use which does require a register. If conflict + // is specified, only consider register uses after the conflict ends. + + if (isRegisterDefinition(bundle->firstRange())) { + JitSpew(JitSpew_RegAlloc, " .. bundle is defined by a register"); + return true; + } + if (!bundle->firstRange()->hasDefinition()) { + JitSpew(JitSpew_RegAlloc, " .. bundle does not have definition"); + return true; + } + + CodePosition firstRegisterFrom; + + CodePosition conflictEnd; + if (conflict) { + for (LiveRange::BundleLinkIterator iter = conflict->rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + if (range->to() > conflictEnd) { + conflictEnd = range->to(); + } + } + } + + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + + if (!conflict || range->from() > conflictEnd) { + if (range->hasDefinition() && isRegisterDefinition(range)) { + firstRegisterFrom = range->from(); + break; + } + } + + for (UsePositionIterator iter(range->usesBegin()); iter; iter++) { + LNode* ins = insData[iter->pos]; + + if (!conflict || outputOf(ins) >= conflictEnd) { + if (isRegisterUse(*iter, ins, /* considerCopy = */ true)) { + firstRegisterFrom = inputOf(ins); + break; + } + } + } + if (firstRegisterFrom.bits()) { + break; + } + } + + if (!firstRegisterFrom.bits()) { + // Can't trim non-register uses off the beginning by splitting. + JitSpew(JitSpew_RegAlloc, " bundle has no register uses"); + return true; + } + + JitSpewIfEnabled(JitSpew_RegAlloc, + " .. split before first register use at %u", + firstRegisterFrom.bits()); + + SplitPositionVector splitPositions; + if (!splitPositions.append(firstRegisterFrom)) { + return false; + } + *success = true; + return splitAt(bundle, splitPositions); +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// The top level driver for the splitting machinery // +// // +/////////////////////////////////////////////////////////////////////////////// + +// ::chooseBundleSplit +// ------------------- +// If the first allocation loop (in ::go) can't allocate a bundle, it hands it +// off to ::chooseBundleSplit, which is the "entry point" of the bundle-split +// machinery. This tries four heuristics in turn, to see if any can split the +// bundle: +// +// * ::trySplitAcrossHotcode +// * ::splitAcrossCalls (in some cases) +// * ::trySplitBeforeFirstRegisterUse +// * ::trySplitAfterLastRegisterUse +// +// These routines have similar structure: they try to decide on one or more +// CodePositions at which to split the bundle, using whatever heuristics they +// have to hand. If suitable CodePosition(s) are found, they are put into a +// `SplitPositionVector`, and the bundle and the vector are handed off to +// ::splitAt, which performs the split (at least in theory) at the chosen +// positions. It also arranges for the new bundles to be added to +// ::allocationQueue. +// +// ::trySplitAcrossHotcode has a special case for JS -- it modifies the +// bundle(s) itself, rather than using ::splitAt. +// +// If none of the heuristic routines apply, then ::splitAt is called with an +// empty vector of split points. This is interpreted to mean "split at all +// register uses". When combined with how ::splitAt works, the effect is to +// spill the bundle. + +bool BacktrackingAllocator::chooseBundleSplit(LiveBundle* bundle, bool fixed, + LiveBundle* conflict) { + bool success = false; + + JitSpewIfEnabled(JitSpew_RegAlloc, " Splitting %s ..", + bundle->toString().get()); + + if (!trySplitAcrossHotcode(bundle, &success)) { + return false; + } + if (success) { + return true; + } + + if (fixed) { + return splitAcrossCalls(bundle); + } + + if (!trySplitBeforeFirstRegisterUse(bundle, conflict, &success)) { + return false; + } + if (success) { + return true; + } + + if (!trySplitAfterLastRegisterUse(bundle, conflict, &success)) { + return false; + } + if (success) { + return true; + } + + // Split at all register uses. + SplitPositionVector emptyPositions; + return splitAt(bundle, emptyPositions); +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// Bundle allocation // +// // +/////////////////////////////////////////////////////////////////////////////// + +static const size_t MAX_ATTEMPTS = 2; + +bool BacktrackingAllocator::computeRequirement(LiveBundle* bundle, + Requirement* requirement, + Requirement* hint) { + // Set any requirement or hint on bundle according to its definition and + // uses. Return false if there are conflicting requirements which will + // require the bundle to be split. + + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + VirtualRegister& reg = range->vreg(); + + if (range->hasDefinition()) { + // Deal with any definition constraints/hints. + LDefinition::Policy policy = reg.def()->policy(); + if (policy == LDefinition::FIXED || policy == LDefinition::STACK) { + // Fixed and stack policies get a FIXED requirement. (In the stack + // case, the allocation should have been performed already by + // mergeAndQueueRegisters.) + JitSpewIfEnabled(JitSpew_RegAlloc, + " Requirement %s, fixed by definition", + reg.def()->output()->toString().get()); + if (!requirement->merge(Requirement(*reg.def()->output()))) { + return false; + } + } else if (reg.ins()->isPhi()) { + // Phis don't have any requirements, but they should prefer their + // input allocations. This is captured by the group hints above. + } else { + // Non-phis get a REGISTER requirement. + if (!requirement->merge(Requirement(Requirement::REGISTER))) { + return false; + } + } + } + + // Search uses for requirements. + for (UsePositionIterator iter = range->usesBegin(); iter; iter++) { + LUse::Policy policy = iter->usePolicy(); + if (policy == LUse::FIXED) { + AnyRegister required = GetFixedRegister(reg.def(), iter->use()); + + JitSpewIfEnabled(JitSpew_RegAlloc, " Requirement %s, due to use at %u", + required.name(), iter->pos.bits()); + + // If there are multiple fixed registers which the bundle is + // required to use, fail. The bundle will need to be split before + // it can be allocated. + if (!requirement->merge(Requirement(LAllocation(required)))) { + return false; + } + } else if (policy == LUse::REGISTER) { + if (!requirement->merge(Requirement(Requirement::REGISTER))) { + return false; + } + } else if (policy == LUse::ANY) { + // ANY differs from KEEPALIVE by actively preferring a register. + if (!hint->merge(Requirement(Requirement::REGISTER))) { + return false; + } + } + + // The only case of STACK use policies is individual stack results using + // their containing stack result area, which is given a fixed allocation + // above. + MOZ_ASSERT_IF(policy == LUse::STACK, + requirement->kind() == Requirement::FIXED); + MOZ_ASSERT_IF(policy == LUse::STACK, + requirement->allocation().isStackArea()); + } + } + + return true; +} + +bool BacktrackingAllocator::tryAllocateRegister(PhysicalRegister& r, + LiveBundle* bundle, + bool* success, bool* pfixed, + LiveBundleVector& conflicting) { + *success = false; + + if (!r.allocatable) { + return true; + } + + LiveBundleVector aliasedConflicting; + + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + LiveRangePlus rangePlus(range); + + // All ranges in the bundle must be compatible with the physical register. + MOZ_ASSERT(range->vreg().isCompatible(r.reg)); + + for (size_t a = 0; a < r.reg.numAliased(); a++) { + PhysicalRegister& rAlias = registers[r.reg.aliased(a).code()]; + LiveRangePlus existingPlus; + if (!rAlias.allocations.contains(rangePlus, &existingPlus)) { + continue; + } + const LiveRange* existing = existingPlus.liveRange(); + if (existing->hasVreg()) { + MOZ_ASSERT(existing->bundle()->allocation().toRegister() == rAlias.reg); + bool duplicate = false; + for (size_t i = 0; i < aliasedConflicting.length(); i++) { + if (aliasedConflicting[i] == existing->bundle()) { + duplicate = true; + break; + } + } + if (!duplicate && !aliasedConflicting.append(existing->bundle())) { + return false; + } + } else { + JitSpewIfEnabled(JitSpew_RegAlloc, " %s collides with fixed use %s", + rAlias.reg.name(), existing->toString().get()); + *pfixed = true; + return true; + } + } + } + + if (!aliasedConflicting.empty()) { + // One or more aliased registers is allocated to another bundle + // overlapping this one. Keep track of the conflicting set, and in the + // case of multiple conflicting sets keep track of the set with the + // lowest maximum spill weight. + + // The #ifdef guards against "unused variable 'existing'" bustage. +#ifdef JS_JITSPEW + if (JitSpewEnabled(JitSpew_RegAlloc)) { + if (aliasedConflicting.length() == 1) { + LiveBundle* existing = aliasedConflicting[0]; + JitSpew(JitSpew_RegAlloc, " %s collides with %s [weight %zu]", + r.reg.name(), existing->toString().get(), + computeSpillWeight(existing)); + } else { + JitSpew(JitSpew_RegAlloc, " %s collides with the following", + r.reg.name()); + for (size_t i = 0; i < aliasedConflicting.length(); i++) { + LiveBundle* existing = aliasedConflicting[i]; + JitSpew(JitSpew_RegAlloc, " %s [weight %zu]", + existing->toString().get(), computeSpillWeight(existing)); + } + } + } +#endif + + if (conflicting.empty()) { + conflicting = std::move(aliasedConflicting); + } else { + if (maximumSpillWeight(aliasedConflicting) < + maximumSpillWeight(conflicting)) { + conflicting = std::move(aliasedConflicting); + } + } + return true; + } + + JitSpewIfEnabled(JitSpew_RegAlloc, " allocated to %s", r.reg.name()); + + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + if (!alloc().ensureBallast()) { + return false; + } + LiveRangePlus rangePlus(range); + if (!r.allocations.insert(rangePlus)) { + return false; + } + } + + bundle->setAllocation(LAllocation(r.reg)); + *success = true; + return true; +} + +bool BacktrackingAllocator::tryAllocateAnyRegister( + LiveBundle* bundle, bool* success, bool* pfixed, + LiveBundleVector& conflicting) { + // Search for any available register which the bundle can be allocated to. + + LDefinition::Type type = bundle->firstRange()->vreg().type(); + + if (LDefinition::isFloatReg(type)) { + for (size_t i = AnyRegister::FirstFloatReg; i < AnyRegister::Total; i++) { + if (!LDefinition::isFloatRegCompatible(type, registers[i].reg.fpu())) { + continue; + } + if (!tryAllocateRegister(registers[i], bundle, success, pfixed, + conflicting)) { + return false; + } + if (*success) { + break; + } + } + return true; + } + + for (size_t i = 0; i < AnyRegister::FirstFloatReg; i++) { + if (!tryAllocateRegister(registers[i], bundle, success, pfixed, + conflicting)) { + return false; + } + if (*success) { + break; + } + } + return true; +} + +bool BacktrackingAllocator::evictBundle(LiveBundle* bundle) { + JitSpewIfEnabled(JitSpew_RegAlloc, + " Evicting %s [priority %zu] [weight %zu]", + bundle->toString().get(), computePriority(bundle), + computeSpillWeight(bundle)); + + AnyRegister reg(bundle->allocation().toRegister()); + PhysicalRegister& physical = registers[reg.code()]; + MOZ_ASSERT(physical.reg == reg && physical.allocatable); + + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + LiveRangePlus rangePlus(range); + physical.allocations.remove(rangePlus); + } + + bundle->setAllocation(LAllocation()); + + size_t priority = computePriority(bundle); + return allocationQueue.insert(QueueItem(bundle, priority)); +} + +bool BacktrackingAllocator::tryAllocateFixed(LiveBundle* bundle, + Requirement requirement, + bool* success, bool* pfixed, + LiveBundleVector& conflicting) { + // Spill bundles which are required to be in a certain stack slot. + if (!requirement.allocation().isRegister()) { + JitSpew(JitSpew_RegAlloc, " stack allocation requirement"); + bundle->setAllocation(requirement.allocation()); + *success = true; + return true; + } + + AnyRegister reg = requirement.allocation().toRegister(); + return tryAllocateRegister(registers[reg.code()], bundle, success, pfixed, + conflicting); +} + +bool BacktrackingAllocator::tryAllocateNonFixed(LiveBundle* bundle, + Requirement requirement, + Requirement hint, bool* success, + bool* pfixed, + LiveBundleVector& conflicting) { + // If we want, but do not require a bundle to be in a specific register, + // only look at that register for allocating and evict or spill if it is + // not available. Picking a separate register may be even worse than + // spilling, as it will still necessitate moves and will tie up more + // registers than if we spilled. + if (hint.kind() == Requirement::FIXED) { + AnyRegister reg = hint.allocation().toRegister(); + if (!tryAllocateRegister(registers[reg.code()], bundle, success, pfixed, + conflicting)) { + return false; + } + if (*success) { + return true; + } + } + + // Spill bundles which have no hint or register requirement. + if (requirement.kind() == Requirement::NONE && + hint.kind() != Requirement::REGISTER) { + JitSpew(JitSpew_RegAlloc, + " postponed spill (no hint or register requirement)"); + if (!spilledBundles.append(bundle)) { + return false; + } + *success = true; + return true; + } + + if (conflicting.empty() || minimalBundle(bundle)) { + if (!tryAllocateAnyRegister(bundle, success, pfixed, conflicting)) { + return false; + } + if (*success) { + return true; + } + } + + // Spill bundles which have no register requirement if they didn't get + // allocated. + if (requirement.kind() == Requirement::NONE) { + JitSpew(JitSpew_RegAlloc, " postponed spill (no register requirement)"); + if (!spilledBundles.append(bundle)) { + return false; + } + *success = true; + return true; + } + + // We failed to allocate this bundle. + MOZ_ASSERT(!*success); + return true; +} + +bool BacktrackingAllocator::processBundle(MIRGenerator* mir, + LiveBundle* bundle) { + JitSpewIfEnabled(JitSpew_RegAlloc, + "Allocating %s [priority %zu] [weight %zu]", + bundle->toString().get(), computePriority(bundle), + computeSpillWeight(bundle)); + + // A bundle can be processed by doing any of the following: + // + // - Assigning the bundle a register. The bundle cannot overlap any other + // bundle allocated for that physical register. + // + // - Spilling the bundle, provided it has no register uses. + // + // - Splitting the bundle into two or more bundles which cover the original + // one. The new bundles are placed back onto the priority queue for later + // processing. + // + // - Evicting one or more existing allocated bundles, and then doing one + // of the above operations. Evicted bundles are placed back on the + // priority queue. Any evicted bundles must have a lower spill weight + // than the bundle being processed. + // + // As long as this structure is followed, termination is guaranteed. + // In general, we want to minimize the amount of bundle splitting (which + // generally necessitates spills), so allocate longer lived, lower weight + // bundles first and evict and split them later if they prevent allocation + // for higher weight bundles. + + Requirement requirement, hint; + bool canAllocate = computeRequirement(bundle, &requirement, &hint); + + bool fixed; + LiveBundleVector conflicting; + for (size_t attempt = 0;; attempt++) { + if (mir->shouldCancel("Backtracking Allocation (processBundle loop)")) { + return false; + } + + if (canAllocate) { + bool success = false; + fixed = false; + conflicting.clear(); + + // Ok, let's try allocating for this bundle. + if (requirement.kind() == Requirement::FIXED) { + if (!tryAllocateFixed(bundle, requirement, &success, &fixed, + conflicting)) { + return false; + } + } else { + if (!tryAllocateNonFixed(bundle, requirement, hint, &success, &fixed, + conflicting)) { + return false; + } + } + + // If that worked, we're done! + if (success) { + return true; + } + + // If that didn't work, but we have one or more non-fixed bundles + // known to be conflicting, maybe we can evict them and try again. + if ((attempt < MAX_ATTEMPTS || minimalBundle(bundle)) && !fixed && + !conflicting.empty() && + maximumSpillWeight(conflicting) < computeSpillWeight(bundle)) { + for (size_t i = 0; i < conflicting.length(); i++) { + if (!evictBundle(conflicting[i])) { + return false; + } + } + continue; + } + } + + // A minimal bundle cannot be split any further. If we try to split it + // it at this point we will just end up with the same bundle and will + // enter an infinite loop. Weights and the initial live ranges must + // be constructed so that any minimal bundle is allocatable. + MOZ_ASSERT(!minimalBundle(bundle)); + + LiveBundle* conflict = conflicting.empty() ? nullptr : conflicting[0]; + return chooseBundleSplit(bundle, canAllocate && fixed, conflict); + } +} + +// Helper for ::tryAllocatingRegistersForSpillBundles +bool BacktrackingAllocator::spill(LiveBundle* bundle) { + JitSpew(JitSpew_RegAlloc, " Spilling bundle"); + MOZ_ASSERT(bundle->allocation().isBogus()); + + if (LiveBundle* spillParent = bundle->spillParent()) { + JitSpew(JitSpew_RegAlloc, " Using existing spill bundle"); + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + LiveRange* parentRange = spillParent->rangeFor(range->from()); + MOZ_ASSERT(parentRange->contains(range)); + MOZ_ASSERT(&range->vreg() == &parentRange->vreg()); + range->tryToMoveDefAndUsesInto(parentRange); + MOZ_ASSERT(!range->hasUses()); + range->vreg().removeRange(range); + } + return true; + } + + return bundle->spillSet()->addSpilledBundle(bundle); +} + +bool BacktrackingAllocator::tryAllocatingRegistersForSpillBundles() { + for (auto it = spilledBundles.begin(); it != spilledBundles.end(); it++) { + LiveBundle* bundle = *it; + LiveBundleVector conflicting; + bool fixed = false; + bool success = false; + + if (mir->shouldCancel("Backtracking Try Allocating Spilled Bundles")) { + return false; + } + + JitSpewIfEnabled(JitSpew_RegAlloc, "Spill or allocate %s", + bundle->toString().get()); + + if (!tryAllocateAnyRegister(bundle, &success, &fixed, conflicting)) { + return false; + } + + // If the bundle still has no register, spill the bundle. + if (!success && !spill(bundle)) { + return false; + } + } + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// Rewriting of the LIR after bundle processing is done: // +// ::pickStackSlots // +// ::createMoveGroupsFromLiveRangeTransitions // +// ::installAllocationsInLIR // +// ::populateSafepoints // +// ::annotateMoveGroups // +// // +/////////////////////////////////////////////////////////////////////////////// + +// Helper for ::pickStackSlot +bool BacktrackingAllocator::insertAllRanges(LiveRangePlusSet& set, + LiveBundle* bundle) { + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + if (!alloc().ensureBallast()) { + return false; + } + LiveRangePlus rangePlus(range); + if (!set.insert(rangePlus)) { + return false; + } + } + return true; +} + +// Helper for ::pickStackSlots +bool BacktrackingAllocator::pickStackSlot(SpillSet* spillSet) { + // Look through all ranges that have been spilled in this set for a + // register definition which is fixed to a stack or argument slot. If we + // find one, use it for all bundles that have been spilled. tryMergeBundles + // makes sure this reuse is possible when an initial bundle contains ranges + // from multiple virtual registers. + for (size_t i = 0; i < spillSet->numSpilledBundles(); i++) { + LiveBundle* bundle = spillSet->spilledBundle(i); + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + if (range->hasDefinition()) { + LDefinition* def = range->vreg().def(); + if (def->policy() == LDefinition::FIXED) { + MOZ_ASSERT(!def->output()->isRegister()); + MOZ_ASSERT(!def->output()->isStackSlot()); + spillSet->setAllocation(*def->output()); + return true; + } + } + } + } + + LDefinition::Type type = + spillSet->spilledBundle(0)->firstRange()->vreg().type(); + + SpillSlotList* slotList; + switch (StackSlotAllocator::width(type)) { + case 4: + slotList = &normalSlots; + break; + case 8: + slotList = &doubleSlots; + break; + case 16: + slotList = &quadSlots; + break; + default: + MOZ_CRASH("Bad width"); + } + + // Maximum number of existing spill slots we will look at before giving up + // and allocating a new slot. + static const size_t MAX_SEARCH_COUNT = 10; + + size_t searches = 0; + SpillSlot* stop = nullptr; + while (!slotList->empty()) { + SpillSlot* spillSlot = *slotList->begin(); + if (!stop) { + stop = spillSlot; + } else if (stop == spillSlot) { + // We looked through every slot in the list. + break; + } + + bool success = true; + for (size_t i = 0; i < spillSet->numSpilledBundles(); i++) { + LiveBundle* bundle = spillSet->spilledBundle(i); + for (LiveRange::BundleLinkIterator iter = bundle->rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + LiveRangePlus rangePlus(range); + LiveRangePlus existingPlus; + if (spillSlot->allocated.contains(rangePlus, &existingPlus)) { + success = false; + break; + } + } + if (!success) { + break; + } + } + if (success) { + // We can reuse this physical stack slot for the new bundles. + // Update the allocated ranges for the slot. + for (size_t i = 0; i < spillSet->numSpilledBundles(); i++) { + LiveBundle* bundle = spillSet->spilledBundle(i); + if (!insertAllRanges(spillSlot->allocated, bundle)) { + return false; + } + } + spillSet->setAllocation(spillSlot->alloc); + return true; + } + + // On a miss, move the spill to the end of the list. This will cause us + // to make fewer attempts to allocate from slots with a large and + // highly contended range. + slotList->popFront(); + slotList->pushBack(spillSlot); + + if (++searches == MAX_SEARCH_COUNT) { + break; + } + } + + // We need a new physical stack slot. + uint32_t stackSlot = stackSlotAllocator.allocateSlot(type); + + SpillSlot* spillSlot = + new (alloc().fallible()) SpillSlot(stackSlot, alloc().lifoAlloc()); + if (!spillSlot) { + return false; + } + + for (size_t i = 0; i < spillSet->numSpilledBundles(); i++) { + LiveBundle* bundle = spillSet->spilledBundle(i); + if (!insertAllRanges(spillSlot->allocated, bundle)) { + return false; + } + } + + spillSet->setAllocation(spillSlot->alloc); + + slotList->pushFront(spillSlot); + return true; +} + +bool BacktrackingAllocator::pickStackSlots() { + for (size_t i = 1; i < graph.numVirtualRegisters(); i++) { + VirtualRegister& reg = vregs[i]; + + if (mir->shouldCancel("Backtracking Pick Stack Slots")) { + return false; + } + + for (LiveRange::RegisterLinkIterator iter = reg.rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + LiveBundle* bundle = range->bundle(); + + if (bundle->allocation().isBogus()) { + if (!pickStackSlot(bundle->spillSet())) { + return false; + } + MOZ_ASSERT(!bundle->allocation().isBogus()); + } + } + } + + return true; +} + +// Helper for ::createMoveGroupsFromLiveRangeTransitions +bool BacktrackingAllocator::moveAtEdge(LBlock* predecessor, LBlock* successor, + LiveRange* from, LiveRange* to, + LDefinition::Type type) { + if (successor->mir()->numPredecessors() > 1) { + MOZ_ASSERT(predecessor->mir()->numSuccessors() == 1); + return moveAtExit(predecessor, from, to, type); + } + + return moveAtEntry(successor, from, to, type); +} + +// Helper for ::createMoveGroupsFromLiveRangeTransitions +bool BacktrackingAllocator::deadRange(LiveRange* range) { + // Check for direct uses of this range. + if (range->hasUses() || range->hasDefinition()) { + return false; + } + + CodePosition start = range->from(); + LNode* ins = insData[start]; + if (start == entryOf(ins->block())) { + return false; + } + + VirtualRegister& reg = range->vreg(); + + // Check if there are later ranges for this vreg. + LiveRange::RegisterLinkIterator iter = reg.rangesBegin(range); + for (iter++; iter; iter++) { + LiveRange* laterRange = LiveRange::get(*iter); + if (laterRange->from() > range->from()) { + return false; + } + } + + // Check if this range ends at a loop backedge. + LNode* last = insData[range->to().previous()]; + if (last->isGoto() && + last->toGoto()->target()->id() < last->block()->mir()->id()) { + return false; + } + + // Check if there are phis which this vreg flows to. + if (reg.usedByPhi()) { + return false; + } + + return true; +} + +bool BacktrackingAllocator::createMoveGroupsFromLiveRangeTransitions() { + // Add moves to handle changing assignments for vregs over their lifetime. + JitSpew(JitSpew_RegAlloc, "ResolveControlFlow: begin"); + + JitSpew(JitSpew_RegAlloc, + " ResolveControlFlow: adding MoveGroups within blocks"); + + // Look for places where a register's assignment changes in the middle of a + // basic block. + MOZ_ASSERT(!vregs[0u].hasRanges()); + for (size_t i = 1; i < graph.numVirtualRegisters(); i++) { + VirtualRegister& reg = vregs[i]; + + if (mir->shouldCancel( + "Backtracking Resolve Control Flow (vreg outer loop)")) { + return false; + } + + for (LiveRange::RegisterLinkIterator iter = reg.rangesBegin(); iter;) { + LiveRange* range = LiveRange::get(*iter); + + if (mir->shouldCancel( + "Backtracking Resolve Control Flow (vreg inner loop)")) { + return false; + } + + // Remove ranges which will never be used. + if (deadRange(range)) { + reg.removeRangeAndIncrement(iter); + continue; + } + + // The range which defines the register does not have a predecessor + // to add moves from. + if (range->hasDefinition()) { + iter++; + continue; + } + + // Ignore ranges that start at block boundaries. We will handle + // these in the next phase. + CodePosition start = range->from(); + LNode* ins = insData[start]; + if (start == entryOf(ins->block())) { + iter++; + continue; + } + + // If we already saw a range which covers the start of this range + // and has the same allocation, we don't need an explicit move at + // the start of this range. + bool skip = false; + for (LiveRange::RegisterLinkIterator prevIter = reg.rangesBegin(); + prevIter != iter; prevIter++) { + LiveRange* prevRange = LiveRange::get(*prevIter); + if (prevRange->covers(start) && prevRange->bundle()->allocation() == + range->bundle()->allocation()) { + skip = true; + break; + } + } + if (skip) { + iter++; + continue; + } + + if (!alloc().ensureBallast()) { + return false; + } + + LiveRange* predecessorRange = + reg.rangeFor(start.previous(), /* preferRegister = */ true); + if (start.subpos() == CodePosition::INPUT) { + JitSpewIfEnabled(JitSpew_RegAlloc, " moveInput (%s) <- (%s)", + range->toString().get(), + predecessorRange->toString().get()); + if (!moveInput(ins->toInstruction(), predecessorRange, range, + reg.type())) { + return false; + } + } else { + JitSpew(JitSpew_RegAlloc, " (moveAfter)"); + if (!moveAfter(ins->toInstruction(), predecessorRange, range, + reg.type())) { + return false; + } + } + + iter++; + } + } + + JitSpew(JitSpew_RegAlloc, + " ResolveControlFlow: adding MoveGroups for phi nodes"); + + for (size_t i = 0; i < graph.numBlocks(); i++) { + if (mir->shouldCancel("Backtracking Resolve Control Flow (block loop)")) { + return false; + } + + LBlock* successor = graph.getBlock(i); + MBasicBlock* mSuccessor = successor->mir(); + if (mSuccessor->numPredecessors() < 1) { + continue; + } + + // Resolve phis to moves. + for (size_t j = 0; j < successor->numPhis(); j++) { + LPhi* phi = successor->getPhi(j); + MOZ_ASSERT(phi->numDefs() == 1); + LDefinition* def = phi->getDef(0); + VirtualRegister& reg = vreg(def); + LiveRange* to = reg.rangeFor(entryOf(successor)); + MOZ_ASSERT(to); + + for (size_t k = 0; k < mSuccessor->numPredecessors(); k++) { + LBlock* predecessor = mSuccessor->getPredecessor(k)->lir(); + MOZ_ASSERT(predecessor->mir()->numSuccessors() == 1); + + LAllocation* input = phi->getOperand(k); + LiveRange* from = vreg(input).rangeFor(exitOf(predecessor), + /* preferRegister = */ true); + MOZ_ASSERT(from); + + if (!alloc().ensureBallast()) { + return false; + } + + // Note: we have to use moveAtEdge both here and below (for edge + // resolution) to avoid conflicting moves. See bug 1493900. + JitSpew(JitSpew_RegAlloc, " (moveAtEdge#1)"); + if (!moveAtEdge(predecessor, successor, from, to, def->type())) { + return false; + } + } + } + } + + JitSpew(JitSpew_RegAlloc, + " ResolveControlFlow: adding MoveGroups to fix conflicted edges"); + + // Add moves to resolve graph edges with different allocations at their + // source and target. + for (size_t i = 1; i < graph.numVirtualRegisters(); i++) { + VirtualRegister& reg = vregs[i]; + for (LiveRange::RegisterLinkIterator iter = reg.rangesBegin(); iter; + iter++) { + LiveRange* targetRange = LiveRange::get(*iter); + + size_t firstBlockId = insData[targetRange->from()]->block()->mir()->id(); + if (!targetRange->covers(entryOf(graph.getBlock(firstBlockId)))) { + firstBlockId++; + } + for (size_t id = firstBlockId; id < graph.numBlocks(); id++) { + LBlock* successor = graph.getBlock(id); + if (!targetRange->covers(entryOf(successor))) { + break; + } + + BitSet& live = liveIn[id]; + if (!live.contains(i)) { + continue; + } + + for (size_t j = 0; j < successor->mir()->numPredecessors(); j++) { + LBlock* predecessor = successor->mir()->getPredecessor(j)->lir(); + if (targetRange->covers(exitOf(predecessor))) { + continue; + } + + if (!alloc().ensureBallast()) { + return false; + } + JitSpew(JitSpew_RegAlloc, " (moveAtEdge#2)"); + LiveRange* from = reg.rangeFor(exitOf(predecessor), true); + if (!moveAtEdge(predecessor, successor, from, targetRange, + reg.type())) { + return false; + } + } + } + } + } + + JitSpew(JitSpew_RegAlloc, "ResolveControlFlow: end"); + return true; +} + +// Helper for ::addLiveRegistersForRange +size_t BacktrackingAllocator::findFirstNonCallSafepoint(CodePosition from) { + size_t i = 0; + for (; i < graph.numNonCallSafepoints(); i++) { + const LInstruction* ins = graph.getNonCallSafepoint(i); + if (from <= inputOf(ins)) { + break; + } + } + return i; +} + +// Helper for ::installAllocationsInLIR +void BacktrackingAllocator::addLiveRegistersForRange(VirtualRegister& reg, + LiveRange* range) { + // Fill in the live register sets for all non-call safepoints. + LAllocation a = range->bundle()->allocation(); + if (!a.isRegister()) { + return; + } + + // Don't add output registers to the safepoint. + CodePosition start = range->from(); + if (range->hasDefinition() && !reg.isTemp()) { +#ifdef CHECK_OSIPOINT_REGISTERS + // We don't add the output register to the safepoint, + // but it still might get added as one of the inputs. + // So eagerly add this reg to the safepoint clobbered registers. + if (reg.ins()->isInstruction()) { + if (LSafepoint* safepoint = reg.ins()->toInstruction()->safepoint()) { + safepoint->addClobberedRegister(a.toRegister()); + } + } +#endif + start = start.next(); + } + + size_t i = findFirstNonCallSafepoint(start); + for (; i < graph.numNonCallSafepoints(); i++) { + LInstruction* ins = graph.getNonCallSafepoint(i); + CodePosition pos = inputOf(ins); + + // Safepoints are sorted, so we can shortcut out of this loop + // if we go out of range. + if (range->to() <= pos) { + break; + } + + MOZ_ASSERT(range->covers(pos)); + + LSafepoint* safepoint = ins->safepoint(); + safepoint->addLiveRegister(a.toRegister()); + +#ifdef CHECK_OSIPOINT_REGISTERS + if (reg.isTemp()) { + safepoint->addClobberedRegister(a.toRegister()); + } +#endif + } +} + +// Helper for ::installAllocationsInLIR +static inline size_t NumReusingDefs(LInstruction* ins) { + size_t num = 0; + for (size_t i = 0; i < ins->numDefs(); i++) { + LDefinition* def = ins->getDef(i); + if (def->policy() == LDefinition::MUST_REUSE_INPUT) { + num++; + } + } + return num; +} + +bool BacktrackingAllocator::installAllocationsInLIR() { + JitSpew(JitSpew_RegAlloc, "Installing Allocations"); + + MOZ_ASSERT(!vregs[0u].hasRanges()); + for (size_t i = 1; i < graph.numVirtualRegisters(); i++) { + VirtualRegister& reg = vregs[i]; + + if (mir->shouldCancel("Backtracking Install Allocations (main loop)")) { + return false; + } + + for (LiveRange::RegisterLinkIterator iter = reg.rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + + if (range->hasDefinition()) { + reg.def()->setOutput(range->bundle()->allocation()); + if (reg.ins()->recoversInput()) { + LSnapshot* snapshot = reg.ins()->toInstruction()->snapshot(); + for (size_t i = 0; i < snapshot->numEntries(); i++) { + LAllocation* entry = snapshot->getEntry(i); + if (entry->isUse() && + entry->toUse()->policy() == LUse::RECOVERED_INPUT) { + *entry = *reg.def()->output(); + } + } + } + } + + for (UsePositionIterator iter(range->usesBegin()); iter; iter++) { + LAllocation* alloc = iter->use(); + *alloc = range->bundle()->allocation(); + + // For any uses which feed into MUST_REUSE_INPUT definitions, + // add copies if the use and def have different allocations. + LNode* ins = insData[iter->pos]; + if (LDefinition* def = FindReusingDefOrTemp(ins, alloc)) { + LiveRange* outputRange = vreg(def).rangeFor(outputOf(ins)); + LAllocation res = outputRange->bundle()->allocation(); + LAllocation sourceAlloc = range->bundle()->allocation(); + + if (res != *alloc) { + if (!this->alloc().ensureBallast()) { + return false; + } + if (NumReusingDefs(ins->toInstruction()) <= 1) { + LMoveGroup* group = getInputMoveGroup(ins->toInstruction()); + if (!group->addAfter(sourceAlloc, res, reg.type())) { + return false; + } + } else { + LMoveGroup* group = getFixReuseMoveGroup(ins->toInstruction()); + if (!group->add(sourceAlloc, res, reg.type())) { + return false; + } + } + *alloc = res; + } + } + } + + addLiveRegistersForRange(reg, range); + } + } + + graph.setLocalSlotsSize(stackSlotAllocator.stackHeight()); + return true; +} + +// Helper for ::populateSafepoints +size_t BacktrackingAllocator::findFirstSafepoint(CodePosition pos, + size_t startFrom) { + size_t i = startFrom; + for (; i < graph.numSafepoints(); i++) { + LInstruction* ins = graph.getSafepoint(i); + if (pos <= inputOf(ins)) { + break; + } + } + return i; +} + +// Helper for ::populateSafepoints +static inline bool IsNunbox(VirtualRegister& reg) { +#ifdef JS_NUNBOX32 + return reg.type() == LDefinition::TYPE || reg.type() == LDefinition::PAYLOAD; +#else + return false; +#endif +} + +// Helper for ::populateSafepoints +static inline bool IsSlotsOrElements(VirtualRegister& reg) { + return reg.type() == LDefinition::SLOTS; +} + +// Helper for ::populateSafepoints +static inline bool IsTraceable(VirtualRegister& reg) { + if (reg.type() == LDefinition::OBJECT) { + return true; + } +#ifdef JS_PUNBOX64 + if (reg.type() == LDefinition::BOX) { + return true; + } +#endif + if (reg.type() == LDefinition::STACKRESULTS) { + MOZ_ASSERT(reg.def()); + const LStackArea* alloc = reg.def()->output()->toStackArea(); + for (auto iter = alloc->results(); iter; iter.next()) { + if (iter.isGcPointer()) { + return true; + } + } + } + return false; +} + +bool BacktrackingAllocator::populateSafepoints() { + JitSpew(JitSpew_RegAlloc, "Populating Safepoints"); + + size_t firstSafepoint = 0; + + MOZ_ASSERT(!vregs[0u].def()); + for (uint32_t i = 1; i < graph.numVirtualRegisters(); i++) { + VirtualRegister& reg = vregs[i]; + + if (!reg.def() || + (!IsTraceable(reg) && !IsSlotsOrElements(reg) && !IsNunbox(reg))) { + continue; + } + + firstSafepoint = findFirstSafepoint(inputOf(reg.ins()), firstSafepoint); + if (firstSafepoint >= graph.numSafepoints()) { + break; + } + + for (LiveRange::RegisterLinkIterator iter = reg.rangesBegin(); iter; + iter++) { + LiveRange* range = LiveRange::get(*iter); + + for (size_t j = firstSafepoint; j < graph.numSafepoints(); j++) { + LInstruction* ins = graph.getSafepoint(j); + + if (!range->covers(inputOf(ins))) { + if (inputOf(ins) >= range->to()) { + break; + } + continue; + } + + // Include temps but not instruction outputs. Also make sure + // MUST_REUSE_INPUT is not used with gcthings or nunboxes, or + // we would have to add the input reg to this safepoint. + if (ins == reg.ins() && !reg.isTemp()) { + DebugOnly<LDefinition*> def = reg.def(); + MOZ_ASSERT_IF(def->policy() == LDefinition::MUST_REUSE_INPUT, + def->type() == LDefinition::GENERAL || + def->type() == LDefinition::INT32 || + def->type() == LDefinition::FLOAT32 || + def->type() == LDefinition::DOUBLE || + def->type() == LDefinition::SIMD128); + continue; + } + + LSafepoint* safepoint = ins->safepoint(); + + LAllocation a = range->bundle()->allocation(); + if (a.isGeneralReg() && ins->isCall()) { + continue; + } + + switch (reg.type()) { + case LDefinition::OBJECT: + if (!safepoint->addGcPointer(a)) { + return false; + } + break; + case LDefinition::SLOTS: + if (!safepoint->addSlotsOrElementsPointer(a)) { + return false; + } + break; + case LDefinition::STACKRESULTS: { + MOZ_ASSERT(a.isStackArea()); + for (auto iter = a.toStackArea()->results(); iter; iter.next()) { + if (iter.isGcPointer()) { + if (!safepoint->addGcPointer(iter.alloc())) { + return false; + } + } + } + break; + } +#ifdef JS_NUNBOX32 + case LDefinition::TYPE: + if (!safepoint->addNunboxType(i, a)) { + return false; + } + break; + case LDefinition::PAYLOAD: + if (!safepoint->addNunboxPayload(i, a)) { + return false; + } + break; +#else + case LDefinition::BOX: + if (!safepoint->addBoxedValue(a)) { + return false; + } + break; +#endif + default: + MOZ_CRASH("Bad register type"); + } + } + } + } + + return true; +} + +bool BacktrackingAllocator::annotateMoveGroups() { + // Annotate move groups in the LIR graph with any register that is not + // allocated at that point and can be used as a scratch register. This is + // only required for x86, as other platforms always have scratch registers + // available for use. +#ifdef JS_CODEGEN_X86 + LiveRange* range = LiveRange::FallibleNew(alloc(), nullptr, CodePosition(), + CodePosition().next()); + if (!range) { + return false; + } + + for (size_t i = 0; i < graph.numBlocks(); i++) { + if (mir->shouldCancel("Backtracking Annotate Move Groups")) { + return false; + } + + LBlock* block = graph.getBlock(i); + LInstruction* last = nullptr; + for (LInstructionIterator iter = block->begin(); iter != block->end(); + ++iter) { + if (iter->isMoveGroup()) { + CodePosition from = last ? outputOf(last) : entryOf(block); + range->setTo(from.next()); + range->setFrom(from); + + for (size_t i = 0; i < AnyRegister::Total; i++) { + PhysicalRegister& reg = registers[i]; + if (reg.reg.isFloat() || !reg.allocatable) { + continue; + } + + // This register is unavailable for use if (a) it is in use + // by some live range immediately before the move group, + // or (b) it is an operand in one of the group's moves. The + // latter case handles live ranges which end immediately + // before the move group or start immediately after. + // For (b) we need to consider move groups immediately + // preceding or following this one. + + if (iter->toMoveGroup()->uses(reg.reg.gpr())) { + continue; + } + bool found = false; + LInstructionIterator niter(iter); + for (niter++; niter != block->end(); niter++) { + if (niter->isMoveGroup()) { + if (niter->toMoveGroup()->uses(reg.reg.gpr())) { + found = true; + break; + } + } else { + break; + } + } + if (iter != block->begin()) { + LInstructionIterator riter(iter); + do { + riter--; + if (riter->isMoveGroup()) { + if (riter->toMoveGroup()->uses(reg.reg.gpr())) { + found = true; + break; + } + } else { + break; + } + } while (riter != block->begin()); + } + + if (found) { + continue; + } + LiveRangePlus existingPlus; + LiveRangePlus rangePlus(range); + if (reg.allocations.contains(rangePlus, &existingPlus)) { + continue; + } + + iter->toMoveGroup()->setScratchRegister(reg.reg.gpr()); + break; + } + } else { + last = *iter; + } + } + } +#endif + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// // +// Debug-printing support // +// // +/////////////////////////////////////////////////////////////////////////////// + +#ifdef JS_JITSPEW + +UniqueChars LiveRange::toString() const { + AutoEnterOOMUnsafeRegion oomUnsafe; + + UniqueChars buf = JS_smprintf("v%u %u-%u", hasVreg() ? vreg().vreg() : 0, + from().bits(), to().bits() - 1); + + if (buf && bundle() && !bundle()->allocation().isBogus()) { + buf = JS_sprintf_append(std::move(buf), " %s", + bundle()->allocation().toString().get()); + } + + buf = JS_sprintf_append(std::move(buf), " {"); + + if (buf && hasDefinition()) { + buf = JS_sprintf_append(std::move(buf), " %u_def", from().bits()); + if (hasVreg()) { + // If the definition has a fixed requirement, print it too. + const LDefinition* def = vreg().def(); + LDefinition::Policy policy = def->policy(); + if (policy == LDefinition::FIXED || policy == LDefinition::STACK) { + if (buf) { + buf = JS_sprintf_append(std::move(buf), ":F:%s", + def->output()->toString().get()); + } + } + } + } + + for (UsePositionIterator iter = usesBegin(); buf && iter; iter++) { + buf = JS_sprintf_append(std::move(buf), " %u_%s", iter->pos.bits(), + iter->use()->toString().get()); + } + + buf = JS_sprintf_append(std::move(buf), " }"); + + if (!buf) { + oomUnsafe.crash("LiveRange::toString()"); + } + + return buf; +} + +UniqueChars LiveBundle::toString() const { + AutoEnterOOMUnsafeRegion oomUnsafe; + + UniqueChars buf = JS_smprintf("LB%u(", debugId()); + + if (buf) { + if (spillParent()) { + buf = JS_sprintf_append(std::move(buf), "parent=LB%u", + spillParent()->debugId()); + } else { + buf = JS_sprintf_append(std::move(buf), "parent=none"); + } + } + + for (LiveRange::BundleLinkIterator iter = rangesBegin(); buf && iter; + iter++) { + if (buf) { + buf = JS_sprintf_append(std::move(buf), "%s %s", + (iter == rangesBegin()) ? "" : " ##", + LiveRange::get(*iter)->toString().get()); + } + } + + if (buf) { + buf = JS_sprintf_append(std::move(buf), ")"); + } + + if (!buf) { + oomUnsafe.crash("LiveBundle::toString()"); + } + + return buf; +} + +void BacktrackingAllocator::dumpLiveRangesByVReg(const char* who) { + MOZ_ASSERT(!vregs[0u].hasRanges()); + + JitSpewCont(JitSpew_RegAlloc, "\n"); + JitSpew(JitSpew_RegAlloc, "Live ranges by virtual register (%s):", who); + + for (uint32_t i = 1; i < graph.numVirtualRegisters(); i++) { + JitSpewHeader(JitSpew_RegAlloc); + JitSpewCont(JitSpew_RegAlloc, " "); + VirtualRegister& reg = vregs[i]; + for (LiveRange::RegisterLinkIterator iter = reg.rangesBegin(); iter; + iter++) { + if (iter != reg.rangesBegin()) { + JitSpewCont(JitSpew_RegAlloc, " ## "); + } + JitSpewCont(JitSpew_RegAlloc, "%s", + LiveRange::get(*iter)->toString().get()); + } + JitSpewCont(JitSpew_RegAlloc, "\n"); + } +} + +void BacktrackingAllocator::dumpLiveRangesByBundle(const char* who) { + MOZ_ASSERT(!vregs[0u].hasRanges()); + + JitSpewCont(JitSpew_RegAlloc, "\n"); + JitSpew(JitSpew_RegAlloc, "Live ranges by bundle (%s):", who); + + for (uint32_t i = 1; i < graph.numVirtualRegisters(); i++) { + VirtualRegister& reg = vregs[i]; + for (LiveRange::RegisterLinkIterator baseIter = reg.rangesBegin(); baseIter; + baseIter++) { + LiveRange* range = LiveRange::get(*baseIter); + LiveBundle* bundle = range->bundle(); + if (range == bundle->firstRange()) { + JitSpew(JitSpew_RegAlloc, " %s", bundle->toString().get()); + } + } + } +} + +void BacktrackingAllocator::dumpAllocations() { + JitSpew(JitSpew_RegAlloc, "Allocations:"); + + dumpLiveRangesByBundle("in dumpAllocations()"); + + JitSpewCont(JitSpew_RegAlloc, "\n"); + JitSpew(JitSpew_RegAlloc, "Allocations by physical register:"); + + for (size_t i = 0; i < AnyRegister::Total; i++) { + if (registers[i].allocatable && !registers[i].allocations.empty()) { + JitSpewHeader(JitSpew_RegAlloc); + JitSpewCont(JitSpew_RegAlloc, " %s:", AnyRegister::FromCode(i).name()); + bool first = true; + LiveRangePlusSet::Iter lrpIter(®isters[i].allocations); + while (lrpIter.hasMore()) { + LiveRange* range = lrpIter.next().liveRange(); + if (first) { + first = false; + } else { + fprintf(stderr, " /"); + } + fprintf(stderr, " %s", range->toString().get()); + } + JitSpewCont(JitSpew_RegAlloc, "\n"); + } + } + + JitSpewCont(JitSpew_RegAlloc, "\n"); +} + +#endif // JS_JITSPEW + +/////////////////////////////////////////////////////////////////////////////// +// // +// Top level of the register allocation machinery // +// // +/////////////////////////////////////////////////////////////////////////////// + +bool BacktrackingAllocator::go() { + JitSpewCont(JitSpew_RegAlloc, "\n"); + JitSpew(JitSpew_RegAlloc, "Beginning register allocation"); + + JitSpewCont(JitSpew_RegAlloc, "\n"); + if (JitSpewEnabled(JitSpew_RegAlloc)) { + dumpInstructions("(Pre-allocation LIR)"); + } + + if (!init()) { + return false; + } + + if (!buildLivenessInfo()) { + return false; + } + +#ifdef JS_JITSPEW + if (JitSpewEnabled(JitSpew_RegAlloc)) { + dumpLiveRangesByVReg("after liveness analysis"); + } +#endif + + if (!allocationQueue.reserve(graph.numVirtualRegisters() * 3 / 2)) { + return false; + } + + JitSpewCont(JitSpew_RegAlloc, "\n"); + JitSpew(JitSpew_RegAlloc, "Beginning grouping and queueing registers"); + if (!mergeAndQueueRegisters()) { + return false; + } + JitSpew(JitSpew_RegAlloc, "Completed grouping and queueing registers"); + +#ifdef JS_JITSPEW + if (JitSpewEnabled(JitSpew_RegAlloc)) { + dumpLiveRangesByBundle("after grouping/queueing regs"); + } +#endif + + // There now follow two allocation loops, which are really the heart of the + // allocator. First, the "main" allocation loop. This does almost all of + // the allocation work, by repeatedly pulling bundles out of + // ::allocationQueue and calling ::processBundle on it, until there are no + // bundles left in the queue. Note that ::processBundle can add new smaller + // bundles to the queue if it needs to split or spill a bundle. + // + // For each bundle in turn pulled out of ::allocationQueue, ::processBundle: + // + // * calls ::computeRequirement to discover the overall constraint for the + // bundle. + // + // * tries to find a register for it, by calling either ::tryAllocateFixed or + // ::tryAllocateNonFixed. + // + // * if that fails, but ::tryAllocateFixed / ::tryAllocateNonFixed indicate + // that there is some other bundle with lower spill weight that can be + // evicted, then that bundle is evicted (hence, put back into + // ::allocationQueue), and we try again. + // + // * at most MAX_ATTEMPTS may be made. + // + // * If that still fails to find a register, then the bundle is handed off to + // ::chooseBundleSplit. That will choose to either split the bundle, + // yielding multiple pieces which are put back into ::allocationQueue, or + // it will spill the bundle. Note that the same mechanism applies to both; + // there's no clear boundary between splitting and spilling, because + // spilling can be interpreted as an extreme form of splitting. + // + // ::processBundle and its callees contains much gnarly and logic which isn't + // easy to understand, particularly in the area of how eviction candidates + // are chosen. But it works well enough, and tinkering doesn't seem to + // improve the resulting allocations. More important is the splitting logic, + // because that controls where spill/reload instructions are placed. + // + // Eventually ::allocationQueue becomes empty, and each LiveBundle has either + // been allocated a register or is marked for spilling. In the latter case + // it will have been added to ::spilledBundles. + + JitSpewCont(JitSpew_RegAlloc, "\n"); + JitSpew(JitSpew_RegAlloc, "Beginning main allocation loop"); + JitSpewCont(JitSpew_RegAlloc, "\n"); + + // Allocate, spill and split bundles until finished. + while (!allocationQueue.empty()) { + if (mir->shouldCancel("Backtracking Allocation")) { + return false; + } + + QueueItem item = allocationQueue.removeHighest(); + if (!processBundle(mir, item.bundle)) { + return false; + } + } + + // And here's the second allocation loop (hidden inside + // ::tryAllocatingRegistersForSpillBundles). It makes one last attempt to + // find a register for each spill bundle. There's no attempt to free up + // registers by eviction. In at least 99% of cases this attempt fails, in + // which case the bundle is handed off to ::spill. The lucky remaining 1% + // get a register. Unfortunately this scheme interacts badly with the + // splitting strategy, leading to excessive register-to-register copying in + // some very simple cases. See bug 1752520. + // + // A modest but probably worthwhile amount of allocation time can be saved by + // making ::tryAllocatingRegistersForSpillBundles use specialised versions of + // ::tryAllocateAnyRegister and its callees, that don't bother to create sets + // of conflicting bundles. Creating those sets is expensive and, here, + // pointless, since we're not going to do any eviction based on them. This + // refinement is implemented in the un-landed patch at bug 1758274 comment + // 15. + + JitSpewCont(JitSpew_RegAlloc, "\n"); + JitSpew(JitSpew_RegAlloc, + "Main allocation loop complete; " + "beginning spill-bundle allocation loop"); + JitSpewCont(JitSpew_RegAlloc, "\n"); + + if (!tryAllocatingRegistersForSpillBundles()) { + return false; + } + + JitSpewCont(JitSpew_RegAlloc, "\n"); + JitSpew(JitSpew_RegAlloc, "Spill-bundle allocation loop complete"); + JitSpewCont(JitSpew_RegAlloc, "\n"); + + if (!pickStackSlots()) { + return false; + } + +#ifdef JS_JITSPEW + if (JitSpewEnabled(JitSpew_RegAlloc)) { + dumpAllocations(); + } +#endif + + if (!createMoveGroupsFromLiveRangeTransitions()) { + return false; + } + + if (!installAllocationsInLIR()) { + return false; + } + + if (!populateSafepoints()) { + return false; + } + + if (!annotateMoveGroups()) { + return false; + } + + JitSpewCont(JitSpew_RegAlloc, "\n"); + if (JitSpewEnabled(JitSpew_RegAlloc)) { + dumpInstructions("(Post-allocation LIR)"); + } + + JitSpew(JitSpew_RegAlloc, "Finished register allocation"); + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// // +/////////////////////////////////////////////////////////////////////////////// |