1 // Copyright 2014 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 #include "src/compiler/graph-reducer.h"
6
7 #include <functional>
8 #include <limits>
9
10 #include "src/codegen/tick-counter.h"
11 #include "src/compiler/graph.h"
12 #include "src/compiler/js-heap-broker.h"
13 #include "src/compiler/node-properties.h"
14 #include "src/compiler/node.h"
15 #include "src/compiler/verifier.h"
16
17 namespace v8 {
18 namespace internal {
19 namespace compiler {
20
21 enum class GraphReducer::State : uint8_t {
22 kUnvisited,
23 kRevisit,
24 kOnStack,
25 kVisited
26 };
27
28
Finalize()29 void Reducer::Finalize() {}
30
GraphReducer(Zone * zone,Graph * graph,TickCounter * tick_counter,JSHeapBroker * broker,Node * dead)31 GraphReducer::GraphReducer(Zone* zone, Graph* graph, TickCounter* tick_counter,
32 JSHeapBroker* broker, Node* dead)
33 : graph_(graph),
34 dead_(dead),
35 state_(graph, 4),
36 reducers_(zone),
37 revisit_(zone),
38 stack_(zone),
39 tick_counter_(tick_counter),
40 broker_(broker) {
41 if (dead != nullptr) {
42 NodeProperties::SetType(dead_, Type::None());
43 }
44 }
45
46 GraphReducer::~GraphReducer() = default;
47
48
AddReducer(Reducer * reducer)49 void GraphReducer::AddReducer(Reducer* reducer) {
50 reducers_.push_back(reducer);
51 }
52
53
ReduceNode(Node * node)54 void GraphReducer::ReduceNode(Node* node) {
55 DCHECK(stack_.empty());
56 DCHECK(revisit_.empty());
57 Push(node);
58 for (;;) {
59 if (!stack_.empty()) {
60 // Process the node on the top of the stack, potentially pushing more or
61 // popping the node off the stack.
62 ReduceTop();
63 } else if (!revisit_.empty()) {
64 // If the stack becomes empty, revisit any nodes in the revisit queue.
65 Node* const node = revisit_.front();
66 revisit_.pop();
67 if (state_.Get(node) == State::kRevisit) {
68 // state can change while in queue.
69 Push(node);
70 }
71 } else {
72 // Run all finalizers.
73 for (Reducer* const reducer : reducers_) reducer->Finalize();
74
75 // Check if we have new nodes to revisit.
76 if (revisit_.empty()) break;
77 }
78 }
79 DCHECK(revisit_.empty());
80 DCHECK(stack_.empty());
81 }
82
83
ReduceGraph()84 void GraphReducer::ReduceGraph() { ReduceNode(graph()->end()); }
85
86
Reduce(Node * const node)87 Reduction GraphReducer::Reduce(Node* const node) {
88 auto skip = reducers_.end();
89 for (auto i = reducers_.begin(); i != reducers_.end();) {
90 if (i != skip) {
91 tick_counter_->TickAndMaybeEnterSafepoint();
92 Reduction reduction = (*i)->Reduce(node);
93 if (!reduction.Changed()) {
94 // No change from this reducer.
95 } else if (reduction.replacement() == node) {
96 // {replacement} == {node} represents an in-place reduction. Rerun
97 // all the other reducers for this node, as now there may be more
98 // opportunities for reduction.
99 if (FLAG_trace_turbo_reduction) {
100 UnparkedScopeIfNeeded unparked(broker_);
101 // TODO(neis): Disallow racy handle dereference once we stop
102 // supporting --no-local-heaps --no-turbo-direct-heap-access.
103 AllowHandleDereference allow_deref;
104 StdoutStream{} << "- In-place update of #" << *node << " by reducer "
105 << (*i)->reducer_name() << std::endl;
106 }
107 skip = i;
108 i = reducers_.begin();
109 continue;
110 } else {
111 // {node} was replaced by another node.
112 if (FLAG_trace_turbo_reduction) {
113 UnparkedScopeIfNeeded unparked(broker_);
114 // TODO(neis): Disallow racy handle dereference once we stop
115 // supporting --no-local-heaps --no-turbo-direct-heap-access.
116 AllowHandleDereference allow_deref;
117 StdoutStream{} << "- Replacement of #" << *node << " with #"
118 << *(reduction.replacement()) << " by reducer "
119 << (*i)->reducer_name() << std::endl;
120 }
121 return reduction;
122 }
123 }
124 ++i;
125 }
126 if (skip == reducers_.end()) {
127 // No change from any reducer.
128 return Reducer::NoChange();
129 }
130 // At least one reducer did some in-place reduction.
131 return Reducer::Changed(node);
132 }
133
134
ReduceTop()135 void GraphReducer::ReduceTop() {
136 NodeState& entry = stack_.top();
137 Node* node = entry.node;
138 DCHECK_EQ(State::kOnStack, state_.Get(node));
139
140 if (node->IsDead()) return Pop(); // Node was killed while on stack.
141
142 Node::Inputs node_inputs = node->inputs();
143
144 // Recurse on an input if necessary.
145 int start = entry.input_index < node_inputs.count() ? entry.input_index : 0;
146 for (int i = start; i < node_inputs.count(); ++i) {
147 Node* input = node_inputs[i];
148 if (input != node && Recurse(input)) {
149 entry.input_index = i + 1;
150 return;
151 }
152 }
153 for (int i = 0; i < start; ++i) {
154 Node* input = node_inputs[i];
155 if (input != node && Recurse(input)) {
156 entry.input_index = i + 1;
157 return;
158 }
159 }
160
161 // Remember the max node id before reduction.
162 NodeId const max_id = static_cast<NodeId>(graph()->NodeCount() - 1);
163
164 // All inputs should be visited or on stack. Apply reductions to node.
165 Reduction reduction = Reduce(node);
166
167 // If there was no reduction, pop {node} and continue.
168 if (!reduction.Changed()) return Pop();
169
170 // Check if the reduction is an in-place update of the {node}.
171 Node* const replacement = reduction.replacement();
172 if (replacement == node) {
173 for (Node* const user : node->uses()) {
174 DCHECK_IMPLIES(user == node, state_.Get(node) != State::kVisited);
175 Revisit(user);
176 }
177
178 // In-place update of {node}, may need to recurse on an input.
179 Node::Inputs node_inputs = node->inputs();
180 for (int i = 0; i < node_inputs.count(); ++i) {
181 Node* input = node_inputs[i];
182 if (input != node && Recurse(input)) {
183 entry.input_index = i + 1;
184 return;
185 }
186 }
187 }
188
189 // After reducing the node, pop it off the stack.
190 Pop();
191
192 // Check if we have a new replacement.
193 if (replacement != node) {
194 Replace(node, replacement, max_id);
195 }
196 }
197
198
Replace(Node * node,Node * replacement)199 void GraphReducer::Replace(Node* node, Node* replacement) {
200 Replace(node, replacement, std::numeric_limits<NodeId>::max());
201 }
202
203
Replace(Node * node,Node * replacement,NodeId max_id)204 void GraphReducer::Replace(Node* node, Node* replacement, NodeId max_id) {
205 if (node == graph()->start()) graph()->SetStart(replacement);
206 if (node == graph()->end()) graph()->SetEnd(replacement);
207 if (replacement->id() <= max_id) {
208 // {replacement} is an old node, so unlink {node} and assume that
209 // {replacement} was already reduced and finish.
210 for (Edge edge : node->use_edges()) {
211 Node* const user = edge.from();
212 Verifier::VerifyEdgeInputReplacement(edge, replacement);
213 edge.UpdateTo(replacement);
214 // Don't revisit this node if it refers to itself.
215 if (user != node) Revisit(user);
216 }
217 node->Kill();
218 } else {
219 // Replace all old uses of {node} with {replacement}, but allow new nodes
220 // created by this reduction to use {node}.
221 for (Edge edge : node->use_edges()) {
222 Node* const user = edge.from();
223 if (user->id() <= max_id) {
224 edge.UpdateTo(replacement);
225 // Don't revisit this node if it refers to itself.
226 if (user != node) Revisit(user);
227 }
228 }
229 // Unlink {node} if it's no longer used.
230 if (node->uses().empty()) node->Kill();
231
232 // If there was a replacement, reduce it after popping {node}.
233 Recurse(replacement);
234 }
235 }
236
237
ReplaceWithValue(Node * node,Node * value,Node * effect,Node * control)238 void GraphReducer::ReplaceWithValue(Node* node, Node* value, Node* effect,
239 Node* control) {
240 if (effect == nullptr && node->op()->EffectInputCount() > 0) {
241 effect = NodeProperties::GetEffectInput(node);
242 }
243 if (control == nullptr && node->op()->ControlInputCount() > 0) {
244 control = NodeProperties::GetControlInput(node);
245 }
246
247 // Requires distinguishing between value, effect and control edges.
248 for (Edge edge : node->use_edges()) {
249 Node* const user = edge.from();
250 DCHECK(!user->IsDead());
251 if (NodeProperties::IsControlEdge(edge)) {
252 if (user->opcode() == IrOpcode::kIfSuccess) {
253 Replace(user, control);
254 } else if (user->opcode() == IrOpcode::kIfException) {
255 DCHECK_NOT_NULL(dead_);
256 edge.UpdateTo(dead_);
257 Revisit(user);
258 } else {
259 DCHECK_NOT_NULL(control);
260 edge.UpdateTo(control);
261 Revisit(user);
262 }
263 } else if (NodeProperties::IsEffectEdge(edge)) {
264 DCHECK_NOT_NULL(effect);
265 edge.UpdateTo(effect);
266 Revisit(user);
267 } else {
268 DCHECK_NOT_NULL(value);
269 edge.UpdateTo(value);
270 Revisit(user);
271 }
272 }
273 }
274
275
Pop()276 void GraphReducer::Pop() {
277 Node* node = stack_.top().node;
278 state_.Set(node, State::kVisited);
279 stack_.pop();
280 }
281
282
Push(Node * const node)283 void GraphReducer::Push(Node* const node) {
284 DCHECK_NE(State::kOnStack, state_.Get(node));
285 state_.Set(node, State::kOnStack);
286 stack_.push({node, 0});
287 }
288
289
Recurse(Node * node)290 bool GraphReducer::Recurse(Node* node) {
291 if (state_.Get(node) > State::kRevisit) return false;
292 Push(node);
293 return true;
294 }
295
296
Revisit(Node * node)297 void GraphReducer::Revisit(Node* node) {
298 if (state_.Get(node) == State::kVisited) {
299 state_.Set(node, State::kRevisit);
300 revisit_.push(node);
301 }
302 }
303
304 } // namespace compiler
305 } // namespace internal
306 } // namespace v8
307