v8
V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++.
Loading...
Searching...
No Matches
memory-optimizer.cc
Go to the documentation of this file.
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
6
7#include "src/base/logging.h"
13#include "src/compiler/node.h"
14#include "src/roots/roots-inl.h"
15
16namespace v8 {
17namespace internal {
18namespace compiler {
19
20namespace {
21
22bool CanAllocate(const Node* node) {
23 switch (node->opcode()) {
24 case IrOpcode::kAbortCSADcheck:
25 case IrOpcode::kBitcastTaggedToWord:
26 case IrOpcode::kBitcastWordToTagged:
27 case IrOpcode::kCheckTurboshaftTypeOf:
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::kMemoryBarrier:
41 case IrOpcode::kProtectedLoad:
42 case IrOpcode::kLoadTrapOnNull:
43 case IrOpcode::kProtectedStore:
44 case IrOpcode::kStoreTrapOnNull:
45 case IrOpcode::kRetain:
46 case IrOpcode::kStackPointerGreaterThan:
47#if V8_ENABLE_WEBASSEMBLY
48 case IrOpcode::kLoadLane:
49 case IrOpcode::kLoadTransform:
50 case IrOpcode::kStoreLane:
51 case IrOpcode::kLoadStackPointer:
52 case IrOpcode::kSetStackPointer:
53#endif // V8_ENABLE_WEBASSEMBLY
54 case IrOpcode::kStaticAssert:
55 // TODO(turbofan): Store nodes might do a bump-pointer allocation.
56 // We should introduce a special bump-pointer store node to
57 // differentiate that.
58 case IrOpcode::kStore:
59 case IrOpcode::kStoreElement:
60 case IrOpcode::kStoreField:
61 case IrOpcode::kStoreToObject:
62 case IrOpcode::kTraceInstruction:
63 case IrOpcode::kInitializeImmutableInObject:
64 case IrOpcode::kTrapIf:
65 case IrOpcode::kTrapUnless:
66 case IrOpcode::kUnalignedLoad:
67 case IrOpcode::kUnalignedStore:
68 case IrOpcode::kUnreachable:
69 case IrOpcode::kWord32AtomicAdd:
70 case IrOpcode::kWord32AtomicAnd:
71 case IrOpcode::kWord32AtomicCompareExchange:
72 case IrOpcode::kWord32AtomicExchange:
73 case IrOpcode::kWord32AtomicLoad:
74 case IrOpcode::kWord32AtomicOr:
75 case IrOpcode::kWord32AtomicPairAdd:
76 case IrOpcode::kWord32AtomicPairAnd:
77 case IrOpcode::kWord32AtomicPairCompareExchange:
78 case IrOpcode::kWord32AtomicPairExchange:
79 case IrOpcode::kWord32AtomicPairLoad:
80 case IrOpcode::kWord32AtomicPairOr:
81 case IrOpcode::kWord32AtomicPairStore:
82 case IrOpcode::kWord32AtomicPairSub:
83 case IrOpcode::kWord32AtomicPairXor:
84 case IrOpcode::kWord32AtomicStore:
85 case IrOpcode::kWord32AtomicSub:
86 case IrOpcode::kWord32AtomicXor:
87 case IrOpcode::kWord64AtomicAdd:
88 case IrOpcode::kWord64AtomicAnd:
89 case IrOpcode::kWord64AtomicCompareExchange:
90 case IrOpcode::kWord64AtomicExchange:
91 case IrOpcode::kWord64AtomicLoad:
92 case IrOpcode::kWord64AtomicOr:
93 case IrOpcode::kWord64AtomicStore:
94 case IrOpcode::kWord64AtomicSub:
95 case IrOpcode::kWord64AtomicXor:
96 return false;
97
98 case IrOpcode::kCall:
99 return !(CallDescriptorOf(node->op())->flags() &
101 default:
102 break;
103 }
104 return true;
105}
106
107Node* SearchAllocatingNode(Node* start, Node* limit, Zone* temp_zone) {
108 ZoneQueue<Node*> queue(temp_zone);
109 ZoneSet<Node*> visited(temp_zone);
110 visited.insert(limit);
111 queue.push(start);
112
113 while (!queue.empty()) {
114 Node* const current = queue.front();
115 queue.pop();
116 if (visited.find(current) == visited.end()) {
117 visited.insert(current);
118
119 if (CanAllocate(current)) {
120 return current;
121 }
122
123 for (int i = 0; i < current->op()->EffectInputCount(); ++i) {
124 queue.push(NodeProperties::GetEffectInput(current, i));
125 }
126 }
127 }
128 return nullptr;
129}
130
131bool CanLoopAllocate(Node* loop_effect_phi, Zone* temp_zone) {
132 Node* const control = NodeProperties::GetControlInput(loop_effect_phi);
133 // Start the effect chain walk from the loop back edges.
134 for (int i = 1; i < control->InputCount(); ++i) {
135 if (SearchAllocatingNode(loop_effect_phi->InputAt(i), loop_effect_phi,
136 temp_zone) != nullptr) {
137 return true;
138 }
139 }
140 return false;
141}
142
143Node* EffectPhiForPhi(Node* phi) {
144 Node* control = NodeProperties::GetControlInput(phi);
145 for (Node* use : control->uses()) {
146 if (use->opcode() == IrOpcode::kEffectPhi) {
147 return use;
148 }
149 }
150 return nullptr;
151}
152
153void WriteBarrierAssertFailed(Node* node, Node* object, const char* name,
154 Zone* temp_zone) {
155 std::stringstream str;
156 str << "MemoryOptimizer could not remove write barrier for node #"
157 << node->id() << "\n";
158 str << " Run mksnapshot with --csa-trap-on-node=" << name << ","
159 << node->id() << " to break in CSA code.\n";
160 Node* object_position = object;
161 if (object_position->opcode() == IrOpcode::kPhi) {
162 object_position = EffectPhiForPhi(object_position);
163 }
164 Node* allocating_node = nullptr;
165 if (object_position && object_position->op()->EffectOutputCount() > 0) {
166 allocating_node = SearchAllocatingNode(node, object_position, temp_zone);
167 }
168 if (allocating_node) {
169 str << "\n There is a potentially allocating node in between:\n";
170 str << " " << *allocating_node << "\n";
171 str << " Run mksnapshot with --csa-trap-on-node=" << name << ","
172 << allocating_node->id() << " to break there.\n";
173 if (allocating_node->opcode() == IrOpcode::kCall) {
174 str << " If this is a never-allocating runtime call, you can add an "
175 "exception to Runtime::MayAllocate.\n";
176 }
177 } else {
178 str << "\n It seems the store happened to something different than a "
179 "direct "
180 "allocation:\n";
181 str << " " << *object << "\n";
182 str << " Run mksnapshot with --csa-trap-on-node=" << name << ","
183 << object->id() << " to break there.\n";
184 }
185 FATAL("%s", str.str().c_str());
186}
187
188} // namespace
189
192 MemoryLowering::AllocationFolding allocation_folding,
193 const char* function_debug_name, TickCounter* tick_counter, bool is_wasm)
194 : graph_assembler_(broker, jsgraph, zone, BranchSemantics::kMachine),
195 memory_lowering_(jsgraph, zone, &graph_assembler_, is_wasm,
196 allocation_folding, WriteBarrierAssertFailed,
197 function_debug_name),
198 wasm_address_reassociation_(jsgraph, zone),
199 jsgraph_(jsgraph),
200 empty_state_(AllocationState::Empty(zone)),
201 pending_(zone),
202 tokens_(zone),
203 zone_(zone),
204 tick_counter_(tick_counter) {}
205
207 EnqueueUses(graph()->start(), empty_state(), graph()->start()->id());
208 while (!tokens_.empty()) {
209 Token const token = tokens_.front();
210 tokens_.pop();
211 VisitNode(token.node, token.state, token.effect_chain);
212 }
213 if (v8_flags.turbo_wasm_address_reassociation) {
215 }
216 DCHECK(pending_.empty());
217 DCHECK(tokens_.empty());
218}
219
221 NodeId effect_chain) {
223 DCHECK(!node->IsDead());
224 DCHECK_LT(0, node->op()->EffectInputCount());
225 switch (node->opcode()) {
226 case IrOpcode::kAllocate:
227 // Allocate nodes were purged from the graph in effect-control
228 // linearization.
229 UNREACHABLE();
230 case IrOpcode::kAllocateRaw:
231 return VisitAllocateRaw(node, state, effect_chain);
232 case IrOpcode::kCall:
233 return VisitCall(node, state, effect_chain);
234 case IrOpcode::kLoadFromObject:
235 case IrOpcode::kLoadImmutableFromObject:
236 return VisitLoadFromObject(node, state, effect_chain);
237 case IrOpcode::kLoadElement:
238 return VisitLoadElement(node, state, effect_chain);
239 case IrOpcode::kLoadField:
240 return VisitLoadField(node, state, effect_chain);
241 case IrOpcode::kProtectedLoad:
242 return VisitProtectedLoad(node, state, effect_chain);
243 case IrOpcode::kProtectedStore:
244 return VisitProtectedStore(node, state, effect_chain);
245 case IrOpcode::kStoreToObject:
246 case IrOpcode::kInitializeImmutableInObject:
247 return VisitStoreToObject(node, state, effect_chain);
248 case IrOpcode::kStoreElement:
249 return VisitStoreElement(node, state, effect_chain);
250 case IrOpcode::kStoreField:
251 return VisitStoreField(node, state, effect_chain);
252 case IrOpcode::kStore:
253 return VisitStore(node, state, effect_chain);
254 case IrOpcode::kStorePair:
255 // Store pairing should happen after this pass.
256 UNREACHABLE();
257 default:
258 if (!CanAllocate(node)) {
259 // These operations cannot trigger GC.
260 return VisitOtherEffect(node, state, effect_chain);
261 }
262 }
263 DCHECK_EQ(0, node->op()->EffectOutputCount());
264}
265
267 const Edge edge) {
268 // Test to see if we need to update the AllocationType.
269 if (node->opcode() == IrOpcode::kStoreField && edge.index() == 1) {
270 Node* parent = node->InputAt(0);
271 if (parent->opcode() == IrOpcode::kAllocateRaw &&
273 return true;
274 }
275 }
276
277 return false;
278}
279
281 // Replace all uses of node and kill the node to make sure we don't leave
282 // dangling dead uses.
283 DCHECK_NE(replacement, node);
286 node->Kill();
287}
288
290 NodeId effect_chain) {
291 DCHECK_EQ(IrOpcode::kAllocateRaw, node->opcode());
292 const AllocateParameters& allocation = AllocateParametersOf(node->op());
293 AllocationType allocation_type = allocation.allocation_type();
294
295 // Propagate tenuring from outer allocations to inner allocations, i.e.
296 // when we allocate an object in old space and store a newly allocated
297 // child object into the pretenured object, then the newly allocated
298 // child object also should get pretenured to old space.
299 if (allocation_type == AllocationType::kOld) {
300 for (Edge const edge : node->use_edges()) {
301 Node* const user = edge.from();
302 if (user->opcode() == IrOpcode::kStoreField && edge.index() == 0) {
303 Node* child = user->InputAt(1);
304 if (child->opcode() == IrOpcode::kAllocateRaw &&
306 NodeProperties::ChangeOp(child, node->op());
307 break;
308 }
309 }
310 }
311 } else {
312 DCHECK_EQ(AllocationType::kYoung, allocation_type);
313 for (Edge const edge : node->use_edges()) {
314 Node* const user = edge.from();
315 if (AllocationTypeNeedsUpdateToOld(user, edge)) {
316 allocation_type = AllocationType::kOld;
317 break;
318 }
319 }
320 }
321
322 Reduction reduction =
323 memory_lowering()->ReduceAllocateRaw(node, allocation_type, &state);
324 CHECK(reduction.Changed() && reduction.replacement() != node);
325
326 ReplaceUsesAndKillNode(node, reduction.replacement());
327
328 EnqueueUses(state->effect(), state, effect_chain);
329}
330
332 AllocationState const* state,
333 NodeId effect_chain) {
334 DCHECK(node->opcode() == IrOpcode::kLoadFromObject ||
335 node->opcode() == IrOpcode::kLoadImmutableFromObject);
337 EnqueueUses(node, state, effect_chain);
338 if (V8_MAP_PACKING_BOOL && reduction.replacement() != node) {
339 ReplaceUsesAndKillNode(node, reduction.replacement());
340 }
341}
342
344 AllocationState const* state,
345 NodeId effect_chain) {
346 DCHECK(node->opcode() == IrOpcode::kStoreToObject ||
347 node->opcode() == IrOpcode::kInitializeImmutableInObject);
348 memory_lowering()->ReduceStoreToObject(node, state);
349 EnqueueUses(node, state, effect_chain);
350}
351
353 NodeId effect_chain) {
354 DCHECK_EQ(IrOpcode::kLoadElement, node->opcode());
356 EnqueueUses(node, state, effect_chain);
357}
358
360 NodeId effect_chain) {
361 DCHECK_EQ(IrOpcode::kLoadField, node->opcode());
362 Reduction reduction = memory_lowering()->ReduceLoadField(node);
363 DCHECK(reduction.Changed());
364 // In case of replacement, the replacement graph should not require futher
365 // lowering, so we can proceed iterating the graph from the node uses.
366 EnqueueUses(node, state, effect_chain);
367
368 // Node can be replaced under two cases:
369 // 1. V8_ENABLE_SANDBOX is true and loading an external pointer value.
370 // 2. V8_MAP_PACKING_BOOL is enabled.
372 reduction.replacement() == node);
374 reduction.replacement() != node) {
375 ReplaceUsesAndKillNode(node, reduction.replacement());
376 }
377}
378
380 AllocationState const* state,
381 NodeId effect_chain) {
382 DCHECK_EQ(IrOpcode::kProtectedLoad, node->opcode());
383 if (v8_flags.turbo_wasm_address_reassociation) {
384 wasm_address_reassociation()->VisitProtectedMemOp(node, effect_chain);
385 EnqueueUses(node, state, effect_chain);
386 } else {
387 VisitOtherEffect(node, state, effect_chain);
388 }
389}
390
392 AllocationState const* state,
393 NodeId effect_chain) {
394 DCHECK_EQ(IrOpcode::kProtectedStore, node->opcode());
395 if (v8_flags.turbo_wasm_address_reassociation) {
396 wasm_address_reassociation()->VisitProtectedMemOp(node, effect_chain);
397 EnqueueUses(node, state, effect_chain);
398 } else {
399 VisitOtherEffect(node, state, effect_chain);
400 }
401}
402
404 AllocationState const* state,
405 NodeId effect_chain) {
406 DCHECK_EQ(IrOpcode::kStoreElement, node->opcode());
407 memory_lowering()->ReduceStoreElement(node, state);
408 EnqueueUses(node, state, effect_chain);
409}
410
412 NodeId effect_chain) {
413 DCHECK_EQ(IrOpcode::kStoreField, node->opcode());
414 memory_lowering()->ReduceStoreField(node, state);
415 EnqueueUses(node, state, effect_chain);
416}
418 NodeId effect_chain) {
419 DCHECK_EQ(IrOpcode::kStore, node->opcode());
420 memory_lowering()->ReduceStore(node, state);
421 EnqueueUses(node, state, effect_chain);
422}
423
425 NodeId effect_chain) {
426 DCHECK_EQ(IrOpcode::kCall, node->opcode());
427 // If the call can allocate, we start with a fresh state.
428 if (!(CallDescriptorOf(node->op())->flags() & CallDescriptor::kNoAllocate)) {
429 state = empty_state();
430 }
431 EnqueueUses(node, state, effect_chain);
432}
433
435 NodeId effect_chain) {
436 EnqueueUses(node, state, effect_chain);
437}
438
440 AllocationStates const& states) {
441 // Check if all states are the same; or at least if all allocation
442 // states belong to the same allocation group.
443 AllocationState const* state = states.front();
444 MemoryLowering::AllocationGroup* group = state->group();
445 for (size_t i = 1; i < states.size(); ++i) {
446 if (states[i] != state) state = nullptr;
447 if (states[i]->group() != group) group = nullptr;
448 }
449 if (state == nullptr) {
450 if (group != nullptr) {
451 // We cannot fold any more allocations into this group, but we can still
452 // eliminate write barriers on stores to this group.
453 // TODO(bmeurer): We could potentially just create a Phi here to merge
454 // the various tops; but we need to pay special attention not to create
455 // an unschedulable graph.
456 state = AllocationState::Closed(group, nullptr, zone());
457 } else {
458 // The states are from different allocation groups.
459 state = empty_state();
460 }
461 }
462 return state;
463}
464
466 AllocationState const* state) {
467 DCHECK_EQ(IrOpcode::kEffectPhi, node->opcode());
468 NodeId effect_chain = node->id();
469 int const input_count = node->InputCount() - 1;
470 DCHECK_LT(0, input_count);
471 Node* const control = node->InputAt(input_count);
472 if (control->opcode() == IrOpcode::kLoop) {
473 if (index == 0) {
474 if (CanLoopAllocate(node, zone())) {
475 // If the loop can allocate, we start with an empty state at the
476 // beginning.
477 EnqueueUses(node, empty_state(), effect_chain);
478 } else {
479 // If the loop cannot allocate, we can just propagate the state from
480 // before the loop.
481 EnqueueUses(node, state, effect_chain);
482 }
483 } else {
484 // Do not revisit backedges.
485 }
486 } else {
487 DCHECK_EQ(IrOpcode::kMerge, control->opcode());
488 // Check if we already know about this pending merge.
489 NodeId const id = node->id();
490 auto it = pending_.find(id);
491 if (it == pending_.end()) {
492 // Insert a new pending merge.
493 it = pending_.insert(std::make_pair(id, AllocationStates(zone()))).first;
494 }
495 // Add the next input state.
496 it->second.push_back(state);
497 // Check if states for all inputs are available by now.
498 if (it->second.size() == static_cast<size_t>(input_count)) {
499 // All inputs to this effect merge are done, merge the states given all
500 // input constraints, drop the pending merge and enqueue uses of the
501 // EffectPhi {node}.
502 state = MergeStates(it->second);
503 EnqueueUses(node, state, effect_chain);
504 pending_.erase(it);
505 }
506 }
507}
508
510 NodeId effect_chain) {
511 for (Edge const edge : node->use_edges()) {
513 EnqueueUse(edge.from(), edge.index(), state, effect_chain);
514 }
515 }
516}
517
518void MemoryOptimizer::EnqueueUse(Node* node, int index,
519 AllocationState const* state,
520 NodeId effect_chain) {
521 if (node->opcode() == IrOpcode::kEffectPhi) {
522 // An EffectPhi represents a merge of different effect chains, which
523 // needs special handling depending on whether the merge is part of a
524 // loop or just a normal control join.
525 EnqueueMerge(node, index, state);
526 } else {
527 Token token = {node, state, effect_chain};
528 tokens_.push(token);
529 }
530}
531
533
534} // namespace compiler
535} // namespace internal
536} // namespace v8
JSGraph * jsgraph
friend Zone
Definition asm-types.cc:195
static AllocationState const * Closed(AllocationGroup *group, Node *effect, Zone *zone)
Reduction ReduceStoreToObject(Node *node, AllocationState const *state=nullptr)
Reduction ReduceStoreElement(Node *node, AllocationState const *state=nullptr)
Reduction ReduceStoreField(Node *node, AllocationState const *state=nullptr)
Reduction ReduceAllocateRaw(Node *node, AllocationType allocation_type, AllocationState const **state)
Reduction ReduceStore(Node *node, AllocationState const *state=nullptr)
void EnqueueMerge(Node *, int, AllocationState const *)
void VisitOtherEffect(Node *, AllocationState const *, NodeId)
void VisitAllocateRaw(Node *, AllocationState const *, NodeId)
ZoneMap< NodeId, AllocationStates > pending_
AllocationState const * MergeStates(AllocationStates const &states)
void VisitNode(Node *, AllocationState const *, NodeId)
void VisitProtectedLoad(Node *, AllocationState const *, NodeId)
void VisitStoreToObject(Node *, AllocationState const *, NodeId)
MemoryOptimizer(JSHeapBroker *broker, JSGraph *jsgraph, Zone *zone, MemoryLowering::AllocationFolding allocation_folding, const char *function_debug_name, TickCounter *tick_counter, bool is_wasm)
void VisitLoadElement(Node *, AllocationState const *, NodeId)
void VisitLoadField(Node *, AllocationState const *, NodeId)
void EnqueueUses(Node *, AllocationState const *, NodeId)
void VisitLoadFromObject(Node *, AllocationState const *, NodeId)
AllocationState const * empty_state() const
void VisitCall(Node *, AllocationState const *, NodeId)
void VisitStore(Node *, AllocationState const *, NodeId)
WasmAddressReassociation * wasm_address_reassociation()
void VisitProtectedStore(Node *, AllocationState const *, NodeId)
void EnqueueUse(Node *, int, AllocationState const *, NodeId)
ZoneVector< AllocationState const * > AllocationStates
void VisitStoreElement(Node *, AllocationState const *, NodeId)
void ReplaceUsesAndKillNode(Node *node, Node *replacement)
void VisitStoreField(Node *, AllocationState const *, NodeId)
bool AllocationTypeNeedsUpdateToOld(Node *const user, const Edge edge)
static void ChangeOp(Node *node, const Operator *new_op)
static void ReplaceUses(Node *node, Node *value, Node *effect=nullptr, Node *success=nullptr, Node *exception=nullptr)
static Node * GetEffectInput(Node *node, int index=0)
static Node * GetControlInput(Node *node, int index=0)
constexpr IrOpcode::Value opcode() const
Definition node.h:52
const Operator * op() const
Definition node.h:50
Node * InputAt(int index) const
Definition node.h:70
void VisitProtectedMemOp(Node *node, uint32_t effect_chain)
Zone * zone_
#define V8_MAP_PACKING_BOOL
Definition globals.h:93
#define V8_ENABLE_SANDBOX_BOOL
Definition globals.h:160
int start
LineAndColumn current
JSHeapBroker * broker
Node * node
LiftoffAssembler::CacheState state
CallDescriptor const * CallDescriptorOf(const Operator *const op)
const AllocateParameters & AllocateParametersOf(const Operator *op)
AllocationType AllocationTypeOf(const Operator *op)
V8_EXPORT_PRIVATE FlagValues v8_flags
other heap size generate builtins concurrently on separate threads in mksnapshot track concurrent recompilation artificial compilation delay in ms max number of threads that concurrent Turbofan can use(0 for unbounded)") DEFINE_BOOL( stress_concurrent_inlining
#define FATAL(...)
Definition logging.h:47
#define CHECK(condition)
Definition logging.h:124
#define DCHECK_IMPLIES(v1, v2)
Definition logging.h:493
#define DCHECK_NE(v1, v2)
Definition logging.h:486
#define DCHECK(condition)
Definition logging.h:482
#define DCHECK_LT(v1, v2)
Definition logging.h:489
#define DCHECK_EQ(v1, v2)
Definition logging.h:485