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