1 // Copyright 2015 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/escape-analysis-reducer.h"
6
7 #include "src/compiler/all-nodes.h"
8 #include "src/compiler/js-graph.h"
9 #include "src/counters.h"
10
11 namespace v8 {
12 namespace internal {
13 namespace compiler {
14
15 #ifdef DEBUG
16 #define TRACE(...) \
17 do { \
18 if (FLAG_trace_turbo_escape) PrintF(__VA_ARGS__); \
19 } while (false)
20 #else
21 #define TRACE(...)
22 #endif // DEBUG
23
EscapeAnalysisReducer(Editor * editor,JSGraph * jsgraph,EscapeAnalysis * escape_analysis,Zone * zone)24 EscapeAnalysisReducer::EscapeAnalysisReducer(Editor* editor, JSGraph* jsgraph,
25 EscapeAnalysis* escape_analysis,
26 Zone* zone)
27 : AdvancedReducer(editor),
28 jsgraph_(jsgraph),
29 escape_analysis_(escape_analysis),
30 zone_(zone),
31 fully_reduced_(static_cast<int>(jsgraph->graph()->NodeCount() * 2), zone),
32 exists_virtual_allocate_(escape_analysis->ExistsVirtualAllocate()) {}
33
ReduceNode(Node * node)34 Reduction EscapeAnalysisReducer::ReduceNode(Node* node) {
35 if (node->id() < static_cast<NodeId>(fully_reduced_.length()) &&
36 fully_reduced_.Contains(node->id())) {
37 return NoChange();
38 }
39
40 switch (node->opcode()) {
41 case IrOpcode::kLoadField:
42 case IrOpcode::kLoadElement:
43 return ReduceLoad(node);
44 case IrOpcode::kStoreField:
45 case IrOpcode::kStoreElement:
46 return ReduceStore(node);
47 case IrOpcode::kAllocate:
48 return ReduceAllocate(node);
49 case IrOpcode::kFinishRegion:
50 return ReduceFinishRegion(node);
51 case IrOpcode::kReferenceEqual:
52 return ReduceReferenceEqual(node);
53 case IrOpcode::kObjectIsSmi:
54 return ReduceObjectIsSmi(node);
55 // FrameStates and Value nodes are preprocessed here,
56 // and visited via ReduceFrameStateUses from their user nodes.
57 case IrOpcode::kFrameState:
58 case IrOpcode::kStateValues: {
59 if (node->id() >= static_cast<NodeId>(fully_reduced_.length()) ||
60 fully_reduced_.Contains(node->id())) {
61 break;
62 }
63 bool depends_on_object_state = false;
64 for (Node* input : node->inputs()) {
65 switch (input->opcode()) {
66 case IrOpcode::kAllocate:
67 case IrOpcode::kFinishRegion:
68 depends_on_object_state =
69 depends_on_object_state || escape_analysis()->IsVirtual(input);
70 break;
71 case IrOpcode::kFrameState:
72 case IrOpcode::kStateValues:
73 depends_on_object_state =
74 depends_on_object_state ||
75 input->id() >= static_cast<NodeId>(fully_reduced_.length()) ||
76 !fully_reduced_.Contains(input->id());
77 break;
78 default:
79 break;
80 }
81 }
82 if (!depends_on_object_state) {
83 fully_reduced_.Add(node->id());
84 }
85 return NoChange();
86 }
87 default:
88 // TODO(sigurds): Change this to GetFrameStateInputCount once
89 // it is working. For now we use EffectInputCount > 0 to determine
90 // whether a node might have a frame state input.
91 if (exists_virtual_allocate_ && node->op()->EffectInputCount() > 0) {
92 return ReduceFrameStateUses(node);
93 }
94 break;
95 }
96 return NoChange();
97 }
98
Reduce(Node * node)99 Reduction EscapeAnalysisReducer::Reduce(Node* node) {
100 Reduction reduction = ReduceNode(node);
101 if (reduction.Changed() && node != reduction.replacement()) {
102 escape_analysis()->SetReplacement(node, reduction.replacement());
103 }
104 return reduction;
105 }
106
107 namespace {
108
MaybeGuard(JSGraph * jsgraph,Zone * zone,Node * original,Node * replacement)109 Node* MaybeGuard(JSGraph* jsgraph, Zone* zone, Node* original,
110 Node* replacement) {
111 // We might need to guard the replacement if the type of the {replacement}
112 // node is not in a sub-type relation to the type of the the {original} node.
113 Type* const replacement_type = NodeProperties::GetType(replacement);
114 Type* const original_type = NodeProperties::GetType(original);
115 if (!replacement_type->Is(original_type)) {
116 Node* const control = NodeProperties::GetControlInput(original);
117 replacement = jsgraph->graph()->NewNode(
118 jsgraph->common()->TypeGuard(original_type), replacement, control);
119 NodeProperties::SetType(replacement, original_type);
120 }
121 return replacement;
122 }
123
SkipTypeGuards(Node * node)124 Node* SkipTypeGuards(Node* node) {
125 while (node->opcode() == IrOpcode::kTypeGuard) {
126 node = NodeProperties::GetValueInput(node, 0);
127 }
128 return node;
129 }
130
131 } // namespace
132
ReduceLoad(Node * node)133 Reduction EscapeAnalysisReducer::ReduceLoad(Node* node) {
134 DCHECK(node->opcode() == IrOpcode::kLoadField ||
135 node->opcode() == IrOpcode::kLoadElement);
136 if (node->id() < static_cast<NodeId>(fully_reduced_.length())) {
137 fully_reduced_.Add(node->id());
138 }
139 if (escape_analysis()->IsVirtual(
140 SkipTypeGuards(NodeProperties::GetValueInput(node, 0)))) {
141 if (Node* rep = escape_analysis()->GetReplacement(node)) {
142 TRACE("Replaced #%d (%s) with #%d (%s)\n", node->id(),
143 node->op()->mnemonic(), rep->id(), rep->op()->mnemonic());
144 rep = MaybeGuard(jsgraph(), zone(), node, rep);
145 ReplaceWithValue(node, rep);
146 return Replace(rep);
147 }
148 }
149 return NoChange();
150 }
151
152
ReduceStore(Node * node)153 Reduction EscapeAnalysisReducer::ReduceStore(Node* node) {
154 DCHECK(node->opcode() == IrOpcode::kStoreField ||
155 node->opcode() == IrOpcode::kStoreElement);
156 if (node->id() < static_cast<NodeId>(fully_reduced_.length())) {
157 fully_reduced_.Add(node->id());
158 }
159 if (escape_analysis()->IsVirtual(
160 SkipTypeGuards(NodeProperties::GetValueInput(node, 0)))) {
161 TRACE("Removed #%d (%s) from effect chain\n", node->id(),
162 node->op()->mnemonic());
163 RelaxEffectsAndControls(node);
164 return Changed(node);
165 }
166 return NoChange();
167 }
168
169
ReduceAllocate(Node * node)170 Reduction EscapeAnalysisReducer::ReduceAllocate(Node* node) {
171 DCHECK_EQ(node->opcode(), IrOpcode::kAllocate);
172 if (node->id() < static_cast<NodeId>(fully_reduced_.length())) {
173 fully_reduced_.Add(node->id());
174 }
175 if (escape_analysis()->IsVirtual(node)) {
176 RelaxEffectsAndControls(node);
177 TRACE("Removed allocate #%d from effect chain\n", node->id());
178 return Changed(node);
179 }
180 return NoChange();
181 }
182
183
ReduceFinishRegion(Node * node)184 Reduction EscapeAnalysisReducer::ReduceFinishRegion(Node* node) {
185 DCHECK_EQ(node->opcode(), IrOpcode::kFinishRegion);
186 Node* effect = NodeProperties::GetEffectInput(node, 0);
187 if (effect->opcode() == IrOpcode::kBeginRegion) {
188 // We only add it now to remove empty Begin/Finish region pairs
189 // in the process.
190 if (node->id() < static_cast<NodeId>(fully_reduced_.length())) {
191 fully_reduced_.Add(node->id());
192 }
193 RelaxEffectsAndControls(effect);
194 RelaxEffectsAndControls(node);
195 #ifdef DEBUG
196 if (FLAG_trace_turbo_escape) {
197 PrintF("Removed region #%d / #%d from effect chain,", effect->id(),
198 node->id());
199 PrintF(" %d user(s) of #%d remain(s):", node->UseCount(), node->id());
200 for (Edge edge : node->use_edges()) {
201 PrintF(" #%d", edge.from()->id());
202 }
203 PrintF("\n");
204 }
205 #endif // DEBUG
206 return Changed(node);
207 }
208 return NoChange();
209 }
210
211
ReduceReferenceEqual(Node * node)212 Reduction EscapeAnalysisReducer::ReduceReferenceEqual(Node* node) {
213 DCHECK_EQ(node->opcode(), IrOpcode::kReferenceEqual);
214 Node* left = SkipTypeGuards(NodeProperties::GetValueInput(node, 0));
215 Node* right = SkipTypeGuards(NodeProperties::GetValueInput(node, 1));
216 if (escape_analysis()->IsVirtual(left)) {
217 if (escape_analysis()->IsVirtual(right) &&
218 escape_analysis()->CompareVirtualObjects(left, right)) {
219 ReplaceWithValue(node, jsgraph()->TrueConstant());
220 TRACE("Replaced ref eq #%d with true\n", node->id());
221 return Replace(jsgraph()->TrueConstant());
222 }
223 // Right-hand side is not a virtual object, or a different one.
224 ReplaceWithValue(node, jsgraph()->FalseConstant());
225 TRACE("Replaced ref eq #%d with false\n", node->id());
226 return Replace(jsgraph()->FalseConstant());
227 } else if (escape_analysis()->IsVirtual(right)) {
228 // Left-hand side is not a virtual object.
229 ReplaceWithValue(node, jsgraph()->FalseConstant());
230 TRACE("Replaced ref eq #%d with false\n", node->id());
231 return Replace(jsgraph()->FalseConstant());
232 }
233 return NoChange();
234 }
235
236
ReduceObjectIsSmi(Node * node)237 Reduction EscapeAnalysisReducer::ReduceObjectIsSmi(Node* node) {
238 DCHECK_EQ(node->opcode(), IrOpcode::kObjectIsSmi);
239 Node* input = SkipTypeGuards(NodeProperties::GetValueInput(node, 0));
240 if (escape_analysis()->IsVirtual(input)) {
241 ReplaceWithValue(node, jsgraph()->FalseConstant());
242 TRACE("Replaced ObjectIsSmi #%d with false\n", node->id());
243 return Replace(jsgraph()->FalseConstant());
244 }
245 return NoChange();
246 }
247
248
ReduceFrameStateUses(Node * node)249 Reduction EscapeAnalysisReducer::ReduceFrameStateUses(Node* node) {
250 DCHECK_GE(node->op()->EffectInputCount(), 1);
251 if (node->id() < static_cast<NodeId>(fully_reduced_.length())) {
252 fully_reduced_.Add(node->id());
253 }
254 bool changed = false;
255 for (int i = 0; i < node->InputCount(); ++i) {
256 Node* input = node->InputAt(i);
257 if (input->opcode() == IrOpcode::kFrameState) {
258 if (Node* ret = ReduceDeoptState(input, node, false)) {
259 node->ReplaceInput(i, ret);
260 changed = true;
261 }
262 }
263 }
264 if (changed) {
265 return Changed(node);
266 }
267 return NoChange();
268 }
269
270
271 // Returns the clone if it duplicated the node, and null otherwise.
ReduceDeoptState(Node * node,Node * effect,bool multiple_users)272 Node* EscapeAnalysisReducer::ReduceDeoptState(Node* node, Node* effect,
273 bool multiple_users) {
274 DCHECK(node->opcode() == IrOpcode::kFrameState ||
275 node->opcode() == IrOpcode::kStateValues);
276 if (node->id() < static_cast<NodeId>(fully_reduced_.length()) &&
277 fully_reduced_.Contains(node->id())) {
278 return nullptr;
279 }
280 TRACE("Reducing %s %d\n", node->op()->mnemonic(), node->id());
281 Node* clone = nullptr;
282 bool node_multiused = node->UseCount() > 1;
283 bool multiple_users_rec = multiple_users || node_multiused;
284 for (int i = 0; i < node->op()->ValueInputCount(); ++i) {
285 Node* input = NodeProperties::GetValueInput(node, i);
286 if (input->opcode() == IrOpcode::kStateValues) {
287 if (Node* ret = ReduceDeoptState(input, effect, multiple_users_rec)) {
288 if (node_multiused || (multiple_users && !clone)) {
289 TRACE(" Cloning #%d", node->id());
290 node = clone = jsgraph()->graph()->CloneNode(node);
291 TRACE(" to #%d\n", node->id());
292 node_multiused = false;
293 }
294 NodeProperties::ReplaceValueInput(node, ret, i);
295 }
296 } else {
297 if (Node* ret = ReduceStateValueInput(node, i, effect, node_multiused,
298 clone, multiple_users)) {
299 DCHECK_NULL(clone);
300 node_multiused = false; // Don't clone anymore.
301 node = clone = ret;
302 }
303 }
304 }
305 if (node->opcode() == IrOpcode::kFrameState) {
306 Node* outer_frame_state = NodeProperties::GetFrameStateInput(node);
307 if (outer_frame_state->opcode() == IrOpcode::kFrameState) {
308 if (Node* ret =
309 ReduceDeoptState(outer_frame_state, effect, multiple_users_rec)) {
310 if (node_multiused || (multiple_users && !clone)) {
311 TRACE(" Cloning #%d", node->id());
312 node = clone = jsgraph()->graph()->CloneNode(node);
313 TRACE(" to #%d\n", node->id());
314 }
315 NodeProperties::ReplaceFrameStateInput(node, ret);
316 }
317 }
318 }
319 if (node->id() < static_cast<NodeId>(fully_reduced_.length())) {
320 fully_reduced_.Add(node->id());
321 }
322 return clone;
323 }
324
325
326 // Returns the clone if it duplicated the node, and null otherwise.
ReduceStateValueInput(Node * node,int node_index,Node * effect,bool node_multiused,bool already_cloned,bool multiple_users)327 Node* EscapeAnalysisReducer::ReduceStateValueInput(Node* node, int node_index,
328 Node* effect,
329 bool node_multiused,
330 bool already_cloned,
331 bool multiple_users) {
332 Node* input = SkipTypeGuards(NodeProperties::GetValueInput(node, node_index));
333 if (node->id() < static_cast<NodeId>(fully_reduced_.length()) &&
334 fully_reduced_.Contains(node->id())) {
335 return nullptr;
336 }
337 TRACE("Reducing State Input #%d (%s)\n", input->id(),
338 input->op()->mnemonic());
339 Node* clone = nullptr;
340 if (input->opcode() == IrOpcode::kFinishRegion ||
341 input->opcode() == IrOpcode::kAllocate) {
342 if (escape_analysis()->IsVirtual(input)) {
343 if (escape_analysis()->IsCyclicObjectState(effect, input)) {
344 // TODO(mstarzinger): Represent cyclic object states differently to
345 // ensure the scheduler can properly handle such object states.
346 compilation_failed_ = true;
347 return nullptr;
348 }
349 if (Node* object_state =
350 escape_analysis()->GetOrCreateObjectState(effect, input)) {
351 if (node_multiused || (multiple_users && !already_cloned)) {
352 TRACE("Cloning #%d", node->id());
353 node = clone = jsgraph()->graph()->CloneNode(node);
354 TRACE(" to #%d\n", node->id());
355 node_multiused = false;
356 already_cloned = true;
357 }
358 NodeProperties::ReplaceValueInput(node, object_state, node_index);
359 TRACE("Replaced state #%d input #%d with object state #%d\n",
360 node->id(), input->id(), object_state->id());
361 } else {
362 TRACE("No object state replacement for #%d at effect #%d available.\n",
363 input->id(), effect->id());
364 UNREACHABLE();
365 }
366 }
367 }
368 return clone;
369 }
370
371
VerifyReplacement() const372 void EscapeAnalysisReducer::VerifyReplacement() const {
373 #ifdef DEBUG
374 AllNodes all(zone(), jsgraph()->graph());
375 for (Node* node : all.reachable) {
376 if (node->opcode() == IrOpcode::kAllocate) {
377 CHECK(!escape_analysis_->IsVirtual(node));
378 }
379 }
380 #endif // DEBUG
381 }
382
383 } // namespace compiler
384 } // namespace internal
385 } // namespace v8
386