• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
CanAllocate(const Node * node)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 
SearchAllocatingNode(Node * start,Node * limit,Zone * temp_zone)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 
CanLoopAllocate(Node * loop_effect_phi,Zone * temp_zone)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 
EffectPhiForPhi(Node * phi)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 
WriteBarrierAssertFailed(Node * node,Node * object,const char * name,Zone * temp_zone)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());
178 }
179 
180 }  // namespace
181 
MemoryOptimizer(JSGraph * jsgraph,Zone * zone,MemoryLowering::AllocationFolding allocation_folding,const char * function_debug_name,TickCounter * tick_counter)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 
Optimize()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());
204   DCHECK(tokens_.empty());
205 }
206 
VisitNode(Node * node,AllocationState const * state)207 void MemoryOptimizer::VisitNode(Node* node, AllocationState const* state) {
208   tick_counter_->TickAndMaybeEnterSafepoint();
209   DCHECK(!node->IsDead());
210   DCHECK_LT(0, node->op()->EffectInputCount());
211   switch (node->opcode()) {
212     case IrOpcode::kAllocate:
213       // Allocate nodes were purged from the graph in effect-control
214       // linearization.
215       UNREACHABLE();
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());
243 }
244 
AllocationTypeNeedsUpdateToOld(Node * const node,const Edge edge)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 
ReplaceUsesAndKillNode(Node * node,Node * replacement)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);
263   NodeProperties::ReplaceUses(node, replacement, graph_assembler_.effect(),
264                               graph_assembler_.control());
265   node->Kill();
266 }
267 
VisitAllocateRaw(Node * node,AllocationState const * state)268 void MemoryOptimizer::VisitAllocateRaw(Node* node,
269                                        AllocationState const* state) {
270   DCHECK_EQ(IrOpcode::kAllocateRaw, node->opcode());
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);
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);
304 
305   ReplaceUsesAndKillNode(node, reduction.replacement());
306 
307   EnqueueUses(state->effect(), state);
308 }
309 
VisitLoadFromObject(Node * node,AllocationState const * state)310 void MemoryOptimizer::VisitLoadFromObject(Node* node,
311                                           AllocationState const* state) {
312   DCHECK(node->opcode() == IrOpcode::kLoadFromObject ||
313          node->opcode() == IrOpcode::kLoadImmutableFromObject);
314   Reduction reduction = memory_lowering()->ReduceLoadFromObject(node);
315   EnqueueUses(node, state);
316   if (V8_MAP_PACKING_BOOL && reduction.replacement() != node) {
317     ReplaceUsesAndKillNode(node, reduction.replacement());
318   }
319 }
320 
VisitStoreToObject(Node * node,AllocationState const * state)321 void MemoryOptimizer::VisitStoreToObject(Node* node,
322                                          AllocationState const* state) {
323   DCHECK(node->opcode() == IrOpcode::kStoreToObject ||
324          node->opcode() == IrOpcode::kInitializeImmutableInObject);
325   memory_lowering()->ReduceStoreToObject(node, state);
326   EnqueueUses(node, state);
327 }
328 
VisitLoadElement(Node * node,AllocationState const * state)329 void MemoryOptimizer::VisitLoadElement(Node* node,
330                                        AllocationState const* state) {
331   DCHECK_EQ(IrOpcode::kLoadElement, node->opcode());
332   memory_lowering()->ReduceLoadElement(node);
333   EnqueueUses(node, state);
334 }
335 
VisitLoadField(Node * node,AllocationState const * state)336 void MemoryOptimizer::VisitLoadField(Node* node, AllocationState const* state) {
337   DCHECK_EQ(IrOpcode::kLoadField, node->opcode());
338   Reduction reduction = memory_lowering()->ReduceLoadField(node);
339   DCHECK(reduction.Changed());
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,
349                  reduction.replacement() == node);
350   if ((V8_SANDBOXED_EXTERNAL_POINTERS_BOOL || V8_MAP_PACKING_BOOL) &&
351       reduction.replacement() != node) {
352     ReplaceUsesAndKillNode(node, reduction.replacement());
353   }
354 }
355 
VisitStoreElement(Node * node,AllocationState const * state)356 void MemoryOptimizer::VisitStoreElement(Node* node,
357                                         AllocationState const* state) {
358   DCHECK_EQ(IrOpcode::kStoreElement, node->opcode());
359   memory_lowering()->ReduceStoreElement(node, state);
360   EnqueueUses(node, state);
361 }
362 
VisitStoreField(Node * node,AllocationState const * state)363 void MemoryOptimizer::VisitStoreField(Node* node,
364                                       AllocationState const* state) {
365   DCHECK_EQ(IrOpcode::kStoreField, node->opcode());
366   memory_lowering()->ReduceStoreField(node, state);
367   EnqueueUses(node, state);
368 }
VisitStore(Node * node,AllocationState const * state)369 void MemoryOptimizer::VisitStore(Node* node, AllocationState const* state) {
370   DCHECK_EQ(IrOpcode::kStore, node->opcode());
371   memory_lowering()->ReduceStore(node, state);
372   EnqueueUses(node, state);
373 }
374 
VisitCall(Node * node,AllocationState const * state)375 void MemoryOptimizer::VisitCall(Node* node, AllocationState const* state) {
376   DCHECK_EQ(IrOpcode::kCall, node->opcode());
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 
VisitOtherEffect(Node * node,AllocationState const * state)384 void MemoryOptimizer::VisitOtherEffect(Node* node,
385                                        AllocationState const* state) {
386   EnqueueUses(node, state);
387 }
388 
MergeStates(AllocationStates const & states)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 
EnqueueMerge(Node * node,int index,AllocationState const * state)415 void MemoryOptimizer::EnqueueMerge(Node* node, int index,
416                                    AllocationState const* state) {
417   DCHECK_EQ(IrOpcode::kEffectPhi, node->opcode());
418   int const input_count = node->InputCount() - 1;
419   DCHECK_LT(0, input_count);
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());
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 
EnqueueUses(Node * node,AllocationState const * state)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 
EnqueueUse(Node * node,int index,AllocationState const * state)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 
graph() const479 Graph* MemoryOptimizer::graph() const { return jsgraph()->graph(); }
480 
481 }  // namespace compiler
482 }  // namespace internal
483 }  // namespace v8
484