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