v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
spill-placer.h
Go to the documentation of this file.
1// Copyright 2020 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#ifndef V8_COMPILER_BACKEND_SPILL_PLACER_H_
6#define V8_COMPILER_BACKEND_SPILL_PLACER_H_
7
9
10namespace v8 {
11namespace internal {
12
13namespace compiler {
14
15class LiveRangeFinder;
16class TopLevelLiveRange;
17class RegisterAllocationData;
18
19// SpillPlacer is an implementation of an algorithm to find optimal spill
20// insertion positions, where optimal is defined as:
21//
22// 1. Spills needed by deferred code don't affect non-deferred code.
23// 2. No control-flow path spills the same value more than once in non-deferred
24// blocks.
25// 3. Where possible based on #2, control-flow paths through non-deferred code
26// that don't need the value to be on the stack don't execute any spills.
27// 4. The fewest number of spill instructions is written to meet these rules.
28// 5. Spill instructions are placed as early as possible.
29//
30// These rules are an attempt to make code paths that don't need to spill faster
31// while not increasing code size too much.
32//
33// Considering just one value at a time for now, the steps are:
34//
35// 1. If the value is defined in a deferred block, or needs its value to be on
36// the stack during the definition block, emit a move right after the
37// definition and exit.
38// 2. Build an array representing the state at each block, where the state can
39// be any of the following:
40// - unmarked (default/initial state)
41// - definition
42// - spill required
43// - spill required in non-deferred successor
44// - spill required in deferred successor
45// 3. Mark the block containing the definition.
46// 4. Mark as "spill required" all blocks that contain any part of a spilled
47// LiveRange, or any use that requires the value to be on the stack.
48// 5. Walk the block list backward, setting the "spill required in successor"
49// values where appropriate. If both deferred and non-deferred successors
50// require a spill, then the result should be "spill required in non-deferred
51// successor".
52// 6. Walk the block list forward, updating marked blocks to "spill required" if
53// all of their predecessors agree that a spill is required. Furthermore, if
54// a block is marked as "spill required in non-deferred successor" and any
55// non-deferred predecessor is marked as "spill required", then the current
56// block is updated to "spill required". We must mark these merge points as
57// "spill required" to obey rule #2 above: if we didn't, then there would
58// exist a control-flow path through two different spilled regions.
59// 7. Walk the block list backward again, updating blocks to "spill required" if
60// all of their successors agree that a spill is required, or if the current
61// block is deferred and any of its successors require spills. If only some
62// successors of a non-deferred block require spills, then insert spill moves
63// at the beginning of those successors. If we manage to smear the "spill
64// required" value all the way to the definition block, then insert a spill
65// move at the definition instead. (Spilling at the definition implies that
66// we didn't emit any other spill moves, and there is a DCHECK mechanism to
67// ensure that invariant.)
68//
69// Loop back-edges can be safely ignored in every step. Anything that the loop
70// header needs on-stack will be spilled either in the loop header itself or
71// sometime before entering the loop, so its back-edge predecessors don't need
72// to contain any data about the loop header.
73//
74// The operations described in those steps are simple Boolean logic, so we can
75// easily process a batch of values at the same time as an optimization.
77 public:
79
81
82 SpillPlacer(const SpillPlacer&) = delete;
84
85 // Adds the given TopLevelLiveRange to the SpillPlacer's state. Will
86 // eventually commit spill moves for that range and mark the range to indicate
87 // whether its value is spilled at the definition or some later point, so that
88 // subsequent phases can know whether to assume the value is always on-stack.
89 // However, those steps may happen during a later call to Add or during the
90 // destructor.
91 void Add(TopLevelLiveRange* range);
92
93 private:
94 RegisterAllocationData* data() const { return data_; }
95
96 // While initializing data for a range, returns the index within each Entry
97 // where data about that range should be stored. May cause data about previous
98 // ranges to be committed to make room if the table is full.
100
101 bool IsLatestVreg(int vreg) const {
102 return assigned_indices_ > 0 &&
103 vreg_numbers_[assigned_indices_ - 1] == vreg;
104 }
105
106 // Processes all of the ranges which have been added, inserts spill moves for
107 // them to the instruction sequence, and marks the ranges with whether they
108 // are spilled at the definition or later.
109 void CommitSpills();
110
111 void ClearData();
112
113 // Updates the iteration bounds first_block_ and last_block_ so that they
114 // include the new value.
116
117 void SetSpillRequired(InstructionBlock* block, int vreg,
118 RpoNumber top_start_block);
119
120 void SetDefinition(RpoNumber block, int vreg);
121
122 // The first backward pass is responsible for marking blocks which do not
123 // themselves need the value to be on the stack, but which do have successors
124 // requiring the value to be on the stack.
125 void FirstBackwardPass();
126
127 // The forward pass is responsible for selecting merge points that should
128 // require the value to be on the stack.
129 void ForwardPass();
130
131 // The second backward pass is responsible for propagating the spill
132 // requirements to the earliest block where all successors can agree a spill
133 // is required. It also emits the actual spill instructions.
134 void SecondBackwardPass();
135
136 void CommitSpill(int vreg, InstructionBlock* predecessor,
137 InstructionBlock* successor);
138
139 // Each Entry represents the state for 64 values at a block, so that we can
140 // compute a batch of values in parallel.
141 class Entry;
142 static constexpr int kValueIndicesPerEntry = 64;
143
144 // Objects provided to the constructor, which all outlive this SpillPlacer.
147
148 // An array of one Entry per block, where blocks are in reverse post-order.
149 Entry* entries_ = nullptr;
150
151 // An array representing which TopLevelLiveRange is in each bit.
152 int* vreg_numbers_ = nullptr;
153
154 // The number of vreg_numbers_ that have been assigned.
156
157 // The first and last block that have any definitions or uses in the current
158 // batch of values. In large functions, tracking these bounds can help prevent
159 // additional work.
162};
163
164} // namespace compiler
165} // namespace internal
166} // namespace v8
167
168#endif // V8_COMPILER_BACKEND_SPILL_PLACER_H_
void ExpandBoundsToInclude(RpoNumber block)
void SetDefinition(RpoNumber block, int vreg)
void SetSpillRequired(InstructionBlock *block, int vreg, RpoNumber top_start_block)
RegisterAllocationData * data() const
SpillPlacer(const SpillPlacer &)=delete
void CommitSpill(int vreg, InstructionBlock *predecessor, InstructionBlock *successor)
void Add(TopLevelLiveRange *range)
RegisterAllocationData * data_
SpillPlacer(RegisterAllocationData *data, Zone *zone)
SpillPlacer & operator=(const SpillPlacer &)=delete
static constexpr int kValueIndicesPerEntry