File: | out/../deps/v8/src/compiler/memory-optimizer.cc |
Warning: | line 314, column 13 Value stored to 'reduction' during its initialization is never read |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | // Copyright 2016 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/memory-optimizer.h" |
6 | |
7 | #include "src/base/logging.h" |
8 | #include "src/codegen/interface-descriptors.h" |
9 | #include "src/codegen/tick-counter.h" |
10 | #include "src/compiler/js-graph.h" |
11 | #include "src/compiler/linkage.h" |
12 | #include "src/compiler/node-matchers.h" |
13 | #include "src/compiler/node-properties.h" |
14 | #include "src/compiler/node.h" |
15 | #include "src/roots/roots-inl.h" |
16 | |
17 | namespace v8 { |
18 | namespace internal { |
19 | namespace compiler { |
20 | |
21 | namespace { |
22 | |
23 | bool CanAllocate(const Node* node) { |
24 | switch (node->opcode()) { |
25 | case IrOpcode::kAbortCSADcheck: |
26 | case IrOpcode::kBitcastTaggedToWord: |
27 | case IrOpcode::kBitcastWordToTagged: |
28 | case IrOpcode::kComment: |
29 | case IrOpcode::kDebugBreak: |
30 | case IrOpcode::kDeoptimizeIf: |
31 | case IrOpcode::kDeoptimizeUnless: |
32 | case IrOpcode::kEffectPhi: |
33 | case IrOpcode::kIfException: |
34 | case IrOpcode::kLoad: |
35 | case IrOpcode::kLoadImmutable: |
36 | case IrOpcode::kLoadElement: |
37 | case IrOpcode::kLoadField: |
38 | case IrOpcode::kLoadFromObject: |
39 | case IrOpcode::kLoadImmutableFromObject: |
40 | case IrOpcode::kLoadLane: |
41 | case IrOpcode::kLoadTransform: |
42 | case IrOpcode::kMemoryBarrier: |
43 | case IrOpcode::kProtectedLoad: |
44 | case IrOpcode::kProtectedStore: |
45 | case IrOpcode::kRetain: |
46 | case IrOpcode::kStackPointerGreaterThan: |
47 | case IrOpcode::kStaticAssert: |
48 | // TODO(turbofan): Store nodes might do a bump-pointer allocation. |
49 | // We should introduce a special bump-pointer store node to |
50 | // differentiate that. |
51 | case IrOpcode::kStore: |
52 | case IrOpcode::kStoreElement: |
53 | case IrOpcode::kStoreField: |
54 | case IrOpcode::kStoreLane: |
55 | case IrOpcode::kStoreToObject: |
56 | case IrOpcode::kInitializeImmutableInObject: |
57 | case IrOpcode::kUnalignedLoad: |
58 | case IrOpcode::kUnalignedStore: |
59 | case IrOpcode::kUnreachable: |
60 | case IrOpcode::kUnsafePointerAdd: |
61 | case IrOpcode::kWord32AtomicAdd: |
62 | case IrOpcode::kWord32AtomicAnd: |
63 | case IrOpcode::kWord32AtomicCompareExchange: |
64 | case IrOpcode::kWord32AtomicExchange: |
65 | case IrOpcode::kWord32AtomicLoad: |
66 | case IrOpcode::kWord32AtomicOr: |
67 | case IrOpcode::kWord32AtomicPairAdd: |
68 | case IrOpcode::kWord32AtomicPairAnd: |
69 | case IrOpcode::kWord32AtomicPairCompareExchange: |
70 | case IrOpcode::kWord32AtomicPairExchange: |
71 | case IrOpcode::kWord32AtomicPairLoad: |
72 | case IrOpcode::kWord32AtomicPairOr: |
73 | case IrOpcode::kWord32AtomicPairStore: |
74 | case IrOpcode::kWord32AtomicPairSub: |
75 | case IrOpcode::kWord32AtomicPairXor: |
76 | case IrOpcode::kWord32AtomicStore: |
77 | case IrOpcode::kWord32AtomicSub: |
78 | case IrOpcode::kWord32AtomicXor: |
79 | case IrOpcode::kWord64AtomicAdd: |
80 | case IrOpcode::kWord64AtomicAnd: |
81 | case IrOpcode::kWord64AtomicCompareExchange: |
82 | case IrOpcode::kWord64AtomicExchange: |
83 | case IrOpcode::kWord64AtomicLoad: |
84 | case IrOpcode::kWord64AtomicOr: |
85 | case IrOpcode::kWord64AtomicStore: |
86 | case IrOpcode::kWord64AtomicSub: |
87 | case IrOpcode::kWord64AtomicXor: |
88 | return false; |
89 | |
90 | case IrOpcode::kCall: |
91 | return !(CallDescriptorOf(node->op())->flags() & |
92 | CallDescriptor::kNoAllocate); |
93 | default: |
94 | break; |
95 | } |
96 | return true; |
97 | } |
98 | |
99 | Node* SearchAllocatingNode(Node* start, Node* limit, Zone* temp_zone) { |
100 | ZoneQueue<Node*> queue(temp_zone); |
101 | ZoneSet<Node*> visited(temp_zone); |
102 | visited.insert(limit); |
103 | queue.push(start); |
104 | |
105 | while (!queue.empty()) { |
106 | Node* const current = queue.front(); |
107 | queue.pop(); |
108 | if (visited.find(current) == visited.end()) { |
109 | visited.insert(current); |
110 | |
111 | if (CanAllocate(current)) { |
112 | return current; |
113 | } |
114 | |
115 | for (int i = 0; i < current->op()->EffectInputCount(); ++i) { |
116 | queue.push(NodeProperties::GetEffectInput(current, i)); |
117 | } |
118 | } |
119 | } |
120 | return nullptr; |
121 | } |
122 | |
123 | bool CanLoopAllocate(Node* loop_effect_phi, Zone* temp_zone) { |
124 | Node* const control = NodeProperties::GetControlInput(loop_effect_phi); |
125 | // Start the effect chain walk from the loop back edges. |
126 | for (int i = 1; i < control->InputCount(); ++i) { |
127 | if (SearchAllocatingNode(loop_effect_phi->InputAt(i), loop_effect_phi, |
128 | temp_zone) != nullptr) { |
129 | return true; |
130 | } |
131 | } |
132 | return false; |
133 | } |
134 | |
135 | Node* EffectPhiForPhi(Node* phi) { |
136 | Node* control = NodeProperties::GetControlInput(phi); |
137 | for (Node* use : control->uses()) { |
138 | if (use->opcode() == IrOpcode::kEffectPhi) { |
139 | return use; |
140 | } |
141 | } |
142 | return nullptr; |
143 | } |
144 | |
145 | void WriteBarrierAssertFailed(Node* node, Node* object, const char* name, |
146 | Zone* temp_zone) { |
147 | std::stringstream str; |
148 | str << "MemoryOptimizer could not remove write barrier for node #" |
149 | << node->id() << "\n"; |
150 | str << " Run mksnapshot with --csa-trap-on-node=" << name << "," |
151 | << node->id() << " to break in CSA code.\n"; |
152 | Node* object_position = object; |
153 | if (object_position->opcode() == IrOpcode::kPhi) { |
154 | object_position = EffectPhiForPhi(object_position); |
155 | } |
156 | Node* allocating_node = nullptr; |
157 | if (object_position && object_position->op()->EffectOutputCount() > 0) { |
158 | allocating_node = SearchAllocatingNode(node, object_position, temp_zone); |
159 | } |
160 | if (allocating_node) { |
161 | str << "\n There is a potentially allocating node in between:\n"; |
162 | str << " " << *allocating_node << "\n"; |
163 | str << " Run mksnapshot with --csa-trap-on-node=" << name << "," |
164 | << allocating_node->id() << " to break there.\n"; |
165 | if (allocating_node->opcode() == IrOpcode::kCall) { |
166 | str << " If this is a never-allocating runtime call, you can add an " |
167 | "exception to Runtime::MayAllocate.\n"; |
168 | } |
169 | } else { |
170 | str << "\n It seems the store happened to something different than a " |
171 | "direct " |
172 | "allocation:\n"; |
173 | str << " " << *object << "\n"; |
174 | str << " Run mksnapshot with --csa-trap-on-node=" << name << "," |
175 | << object->id() << " to break there.\n"; |
176 | } |
177 | FATAL("%s", str.str().c_str())V8_Fatal("%s", str.str().c_str()); |
178 | } |
179 | |
180 | } // namespace |
181 | |
182 | MemoryOptimizer::MemoryOptimizer( |
183 | JSGraph* jsgraph, Zone* zone, |
184 | MemoryLowering::AllocationFolding allocation_folding, |
185 | const char* function_debug_name, TickCounter* tick_counter) |
186 | : graph_assembler_(jsgraph, zone), |
187 | memory_lowering_(jsgraph, zone, &graph_assembler_, allocation_folding, |
188 | WriteBarrierAssertFailed, function_debug_name), |
189 | jsgraph_(jsgraph), |
190 | empty_state_(AllocationState::Empty(zone)), |
191 | pending_(zone), |
192 | tokens_(zone), |
193 | zone_(zone), |
194 | tick_counter_(tick_counter) {} |
195 | |
196 | void MemoryOptimizer::Optimize() { |
197 | EnqueueUses(graph()->start(), empty_state()); |
198 | while (!tokens_.empty()) { |
199 | Token const token = tokens_.front(); |
200 | tokens_.pop(); |
201 | VisitNode(token.node, token.state); |
202 | } |
203 | DCHECK(pending_.empty())((void) 0); |
204 | DCHECK(tokens_.empty())((void) 0); |
205 | } |
206 | |
207 | void MemoryOptimizer::VisitNode(Node* node, AllocationState const* state) { |
208 | tick_counter_->TickAndMaybeEnterSafepoint(); |
209 | DCHECK(!node->IsDead())((void) 0); |
210 | DCHECK_LT(0, node->op()->EffectInputCount())((void) 0); |
211 | switch (node->opcode()) { |
212 | case IrOpcode::kAllocate: |
213 | // Allocate nodes were purged from the graph in effect-control |
214 | // linearization. |
215 | UNREACHABLE()V8_Fatal("unreachable code"); |
216 | case IrOpcode::kAllocateRaw: |
217 | return VisitAllocateRaw(node, state); |
218 | case IrOpcode::kCall: |
219 | return VisitCall(node, state); |
220 | case IrOpcode::kLoadFromObject: |
221 | case IrOpcode::kLoadImmutableFromObject: |
222 | return VisitLoadFromObject(node, state); |
223 | case IrOpcode::kLoadElement: |
224 | return VisitLoadElement(node, state); |
225 | case IrOpcode::kLoadField: |
226 | return VisitLoadField(node, state); |
227 | case IrOpcode::kStoreToObject: |
228 | case IrOpcode::kInitializeImmutableInObject: |
229 | return VisitStoreToObject(node, state); |
230 | case IrOpcode::kStoreElement: |
231 | return VisitStoreElement(node, state); |
232 | case IrOpcode::kStoreField: |
233 | return VisitStoreField(node, state); |
234 | case IrOpcode::kStore: |
235 | return VisitStore(node, state); |
236 | default: |
237 | if (!CanAllocate(node)) { |
238 | // These operations cannot trigger GC. |
239 | return VisitOtherEffect(node, state); |
240 | } |
241 | } |
242 | DCHECK_EQ(0, node->op()->EffectOutputCount())((void) 0); |
243 | } |
244 | |
245 | bool MemoryOptimizer::AllocationTypeNeedsUpdateToOld(Node* const node, |
246 | const Edge edge) { |
247 | // Test to see if we need to update the AllocationType. |
248 | if (node->opcode() == IrOpcode::kStoreField && edge.index() == 1) { |
249 | Node* parent = node->InputAt(0); |
250 | if (parent->opcode() == IrOpcode::kAllocateRaw && |
251 | AllocationTypeOf(parent->op()) == AllocationType::kOld) { |
252 | return true; |
253 | } |
254 | } |
255 | |
256 | return false; |
257 | } |
258 | |
259 | void MemoryOptimizer::ReplaceUsesAndKillNode(Node* node, Node* replacement) { |
260 | // Replace all uses of node and kill the node to make sure we don't leave |
261 | // dangling dead uses. |
262 | DCHECK_NE(replacement, node)((void) 0); |
263 | NodeProperties::ReplaceUses(node, replacement, graph_assembler_.effect(), |
264 | graph_assembler_.control()); |
265 | node->Kill(); |
266 | } |
267 | |
268 | void MemoryOptimizer::VisitAllocateRaw(Node* node, |
269 | AllocationState const* state) { |
270 | DCHECK_EQ(IrOpcode::kAllocateRaw, node->opcode())((void) 0); |
271 | const AllocateParameters& allocation = AllocateParametersOf(node->op()); |
272 | AllocationType allocation_type = allocation.allocation_type(); |
273 | |
274 | // Propagate tenuring from outer allocations to inner allocations, i.e. |
275 | // when we allocate an object in old space and store a newly allocated |
276 | // child object into the pretenured object, then the newly allocated |
277 | // child object also should get pretenured to old space. |
278 | if (allocation_type == AllocationType::kOld) { |
279 | for (Edge const edge : node->use_edges()) { |
280 | Node* const user = edge.from(); |
281 | if (user->opcode() == IrOpcode::kStoreField && edge.index() == 0) { |
282 | Node* child = user->InputAt(1); |
283 | if (child->opcode() == IrOpcode::kAllocateRaw && |
284 | AllocationTypeOf(child->op()) == AllocationType::kYoung) { |
285 | NodeProperties::ChangeOp(child, node->op()); |
286 | break; |
287 | } |
288 | } |
289 | } |
290 | } else { |
291 | DCHECK_EQ(AllocationType::kYoung, allocation_type)((void) 0); |
292 | for (Edge const edge : node->use_edges()) { |
293 | Node* const user = edge.from(); |
294 | if (AllocationTypeNeedsUpdateToOld(user, edge)) { |
295 | allocation_type = AllocationType::kOld; |
296 | break; |
297 | } |
298 | } |
299 | } |
300 | |
301 | Reduction reduction = memory_lowering()->ReduceAllocateRaw( |
302 | node, allocation_type, allocation.allow_large_objects(), &state); |
303 | CHECK(reduction.Changed() && reduction.replacement() != node)do { if ((__builtin_expect(!!(!(reduction.Changed() && reduction.replacement() != node)), 0))) { V8_Fatal("Check failed: %s." , "reduction.Changed() && reduction.replacement() != node" ); } } while (false); |
304 | |
305 | ReplaceUsesAndKillNode(node, reduction.replacement()); |
306 | |
307 | EnqueueUses(state->effect(), state); |
308 | } |
309 | |
310 | void MemoryOptimizer::VisitLoadFromObject(Node* node, |
311 | AllocationState const* state) { |
312 | DCHECK(node->opcode() == IrOpcode::kLoadFromObject ||((void) 0) |
313 | node->opcode() == IrOpcode::kLoadImmutableFromObject)((void) 0); |
314 | Reduction reduction = memory_lowering()->ReduceLoadFromObject(node); |
Value stored to 'reduction' during its initialization is never read | |
315 | EnqueueUses(node, state); |
316 | if (V8_MAP_PACKING_BOOLfalse && reduction.replacement() != node) { |
317 | ReplaceUsesAndKillNode(node, reduction.replacement()); |
318 | } |
319 | } |
320 | |
321 | void MemoryOptimizer::VisitStoreToObject(Node* node, |
322 | AllocationState const* state) { |
323 | DCHECK(node->opcode() == IrOpcode::kStoreToObject ||((void) 0) |
324 | node->opcode() == IrOpcode::kInitializeImmutableInObject)((void) 0); |
325 | memory_lowering()->ReduceStoreToObject(node, state); |
326 | EnqueueUses(node, state); |
327 | } |
328 | |
329 | void MemoryOptimizer::VisitLoadElement(Node* node, |
330 | AllocationState const* state) { |
331 | DCHECK_EQ(IrOpcode::kLoadElement, node->opcode())((void) 0); |
332 | memory_lowering()->ReduceLoadElement(node); |
333 | EnqueueUses(node, state); |
334 | } |
335 | |
336 | void MemoryOptimizer::VisitLoadField(Node* node, AllocationState const* state) { |
337 | DCHECK_EQ(IrOpcode::kLoadField, node->opcode())((void) 0); |
338 | Reduction reduction = memory_lowering()->ReduceLoadField(node); |
339 | DCHECK(reduction.Changed())((void) 0); |
340 | // In case of replacement, the replacement graph should not require futher |
341 | // lowering, so we can proceed iterating the graph from the node uses. |
342 | EnqueueUses(node, state); |
343 | |
344 | // Node can be replaced under two cases: |
345 | // 1. V8_SANDBOXED_EXTERNAL_POINTERS_BOOL is enabled and loading an external |
346 | // pointer value. |
347 | // 2. V8_MAP_PACKING_BOOL is enabled. |
348 | DCHECK_IMPLIES(!V8_SANDBOXED_EXTERNAL_POINTERS_BOOL && !V8_MAP_PACKING_BOOL,((void) 0) |
349 | reduction.replacement() == node)((void) 0); |
350 | if ((V8_SANDBOXED_EXTERNAL_POINTERS_BOOLfalse || V8_MAP_PACKING_BOOLfalse) && |
351 | reduction.replacement() != node) { |
352 | ReplaceUsesAndKillNode(node, reduction.replacement()); |
353 | } |
354 | } |
355 | |
356 | void MemoryOptimizer::VisitStoreElement(Node* node, |
357 | AllocationState const* state) { |
358 | DCHECK_EQ(IrOpcode::kStoreElement, node->opcode())((void) 0); |
359 | memory_lowering()->ReduceStoreElement(node, state); |
360 | EnqueueUses(node, state); |
361 | } |
362 | |
363 | void MemoryOptimizer::VisitStoreField(Node* node, |
364 | AllocationState const* state) { |
365 | DCHECK_EQ(IrOpcode::kStoreField, node->opcode())((void) 0); |
366 | memory_lowering()->ReduceStoreField(node, state); |
367 | EnqueueUses(node, state); |
368 | } |
369 | void MemoryOptimizer::VisitStore(Node* node, AllocationState const* state) { |
370 | DCHECK_EQ(IrOpcode::kStore, node->opcode())((void) 0); |
371 | memory_lowering()->ReduceStore(node, state); |
372 | EnqueueUses(node, state); |
373 | } |
374 | |
375 | void MemoryOptimizer::VisitCall(Node* node, AllocationState const* state) { |
376 | DCHECK_EQ(IrOpcode::kCall, node->opcode())((void) 0); |
377 | // If the call can allocate, we start with a fresh state. |
378 | if (!(CallDescriptorOf(node->op())->flags() & CallDescriptor::kNoAllocate)) { |
379 | state = empty_state(); |
380 | } |
381 | EnqueueUses(node, state); |
382 | } |
383 | |
384 | void MemoryOptimizer::VisitOtherEffect(Node* node, |
385 | AllocationState const* state) { |
386 | EnqueueUses(node, state); |
387 | } |
388 | |
389 | MemoryOptimizer::AllocationState const* MemoryOptimizer::MergeStates( |
390 | AllocationStates const& states) { |
391 | // Check if all states are the same; or at least if all allocation |
392 | // states belong to the same allocation group. |
393 | AllocationState const* state = states.front(); |
394 | MemoryLowering::AllocationGroup* group = state->group(); |
395 | for (size_t i = 1; i < states.size(); ++i) { |
396 | if (states[i] != state) state = nullptr; |
397 | if (states[i]->group() != group) group = nullptr; |
398 | } |
399 | if (state == nullptr) { |
400 | if (group != nullptr) { |
401 | // We cannot fold any more allocations into this group, but we can still |
402 | // eliminate write barriers on stores to this group. |
403 | // TODO(bmeurer): We could potentially just create a Phi here to merge |
404 | // the various tops; but we need to pay special attention not to create |
405 | // an unschedulable graph. |
406 | state = AllocationState::Closed(group, nullptr, zone()); |
407 | } else { |
408 | // The states are from different allocation groups. |
409 | state = empty_state(); |
410 | } |
411 | } |
412 | return state; |
413 | } |
414 | |
415 | void MemoryOptimizer::EnqueueMerge(Node* node, int index, |
416 | AllocationState const* state) { |
417 | DCHECK_EQ(IrOpcode::kEffectPhi, node->opcode())((void) 0); |
418 | int const input_count = node->InputCount() - 1; |
419 | DCHECK_LT(0, input_count)((void) 0); |
420 | Node* const control = node->InputAt(input_count); |
421 | if (control->opcode() == IrOpcode::kLoop) { |
422 | if (index == 0) { |
423 | if (CanLoopAllocate(node, zone())) { |
424 | // If the loop can allocate, we start with an empty state at the |
425 | // beginning. |
426 | EnqueueUses(node, empty_state()); |
427 | } else { |
428 | // If the loop cannot allocate, we can just propagate the state from |
429 | // before the loop. |
430 | EnqueueUses(node, state); |
431 | } |
432 | } else { |
433 | // Do not revisit backedges. |
434 | } |
435 | } else { |
436 | DCHECK_EQ(IrOpcode::kMerge, control->opcode())((void) 0); |
437 | // Check if we already know about this pending merge. |
438 | NodeId const id = node->id(); |
439 | auto it = pending_.find(id); |
440 | if (it == pending_.end()) { |
441 | // Insert a new pending merge. |
442 | it = pending_.insert(std::make_pair(id, AllocationStates(zone()))).first; |
443 | } |
444 | // Add the next input state. |
445 | it->second.push_back(state); |
446 | // Check if states for all inputs are available by now. |
447 | if (it->second.size() == static_cast<size_t>(input_count)) { |
448 | // All inputs to this effect merge are done, merge the states given all |
449 | // input constraints, drop the pending merge and enqueue uses of the |
450 | // EffectPhi {node}. |
451 | state = MergeStates(it->second); |
452 | EnqueueUses(node, state); |
453 | pending_.erase(it); |
454 | } |
455 | } |
456 | } |
457 | |
458 | void MemoryOptimizer::EnqueueUses(Node* node, AllocationState const* state) { |
459 | for (Edge const edge : node->use_edges()) { |
460 | if (NodeProperties::IsEffectEdge(edge)) { |
461 | EnqueueUse(edge.from(), edge.index(), state); |
462 | } |
463 | } |
464 | } |
465 | |
466 | void MemoryOptimizer::EnqueueUse(Node* node, int index, |
467 | AllocationState const* state) { |
468 | if (node->opcode() == IrOpcode::kEffectPhi) { |
469 | // An EffectPhi represents a merge of different effect chains, which |
470 | // needs special handling depending on whether the merge is part of a |
471 | // loop or just a normal control join. |
472 | EnqueueMerge(node, index, state); |
473 | } else { |
474 | Token token = {node, state}; |
475 | tokens_.push(token); |
476 | } |
477 | } |
478 | |
479 | Graph* MemoryOptimizer::graph() const { return jsgraph()->graph(); } |
480 | |
481 | } // namespace compiler |
482 | } // namespace internal |
483 | } // namespace v8 |