V8 API Reference, 7.2.502.16 (for Deno 0.2.4)
memory-optimizer.cc
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/compiler/js-graph.h"
8 #include "src/compiler/linkage.h"
9 #include "src/compiler/node-matchers.h"
10 #include "src/compiler/node-properties.h"
11 #include "src/compiler/node.h"
12 #include "src/compiler/simplified-operator.h"
13 
14 namespace v8 {
15 namespace internal {
16 namespace compiler {
17 
18 MemoryOptimizer::MemoryOptimizer(JSGraph* jsgraph, Zone* zone,
19  PoisoningMitigationLevel poisoning_level,
20  AllocationFolding allocation_folding)
21  : jsgraph_(jsgraph),
22  empty_state_(AllocationState::Empty(zone)),
23  pending_(zone),
24  tokens_(zone),
25  zone_(zone),
26  graph_assembler_(jsgraph, nullptr, nullptr, zone),
27  poisoning_level_(poisoning_level),
28  allocation_folding_(allocation_folding) {}
29 
30 void MemoryOptimizer::Optimize() {
31  EnqueueUses(graph()->start(), empty_state());
32  while (!tokens_.empty()) {
33  Token const token = tokens_.front();
34  tokens_.pop();
35  VisitNode(token.node, token.state);
36  }
37  DCHECK(pending_.empty());
38  DCHECK(tokens_.empty());
39 }
40 
41 MemoryOptimizer::AllocationGroup::AllocationGroup(Node* node,
42  PretenureFlag pretenure,
43  Zone* zone)
44  : node_ids_(zone), pretenure_(pretenure), size_(nullptr) {
45  node_ids_.insert(node->id());
46 }
47 
48 MemoryOptimizer::AllocationGroup::AllocationGroup(Node* node,
49  PretenureFlag pretenure,
50  Node* size, Zone* zone)
51  : node_ids_(zone), pretenure_(pretenure), size_(size) {
52  node_ids_.insert(node->id());
53 }
54 
55 void MemoryOptimizer::AllocationGroup::Add(Node* node) {
56  node_ids_.insert(node->id());
57 }
58 
59 bool MemoryOptimizer::AllocationGroup::Contains(Node* node) const {
60  return node_ids_.find(node->id()) != node_ids_.end();
61 }
62 
63 MemoryOptimizer::AllocationState::AllocationState()
64  : group_(nullptr), size_(std::numeric_limits<int>::max()), top_(nullptr) {}
65 
66 MemoryOptimizer::AllocationState::AllocationState(AllocationGroup* group)
67  : group_(group), size_(std::numeric_limits<int>::max()), top_(nullptr) {}
68 
69 MemoryOptimizer::AllocationState::AllocationState(AllocationGroup* group,
70  intptr_t size, Node* top)
71  : group_(group), size_(size), top_(top) {}
72 
73 bool MemoryOptimizer::AllocationState::IsNewSpaceAllocation() const {
74  return group() && group()->IsNewSpaceAllocation();
75 }
76 
77 void MemoryOptimizer::VisitNode(Node* node, AllocationState const* state) {
78  DCHECK(!node->IsDead());
79  DCHECK_LT(0, node->op()->EffectInputCount());
80  switch (node->opcode()) {
81  case IrOpcode::kAllocate:
82  // Allocate nodes were purged from the graph in effect-control
83  // linearization.
84  UNREACHABLE();
85  case IrOpcode::kAllocateRaw:
86  return VisitAllocateRaw(node, state);
87  case IrOpcode::kCall:
88  return VisitCall(node, state);
89  case IrOpcode::kCallWithCallerSavedRegisters:
90  return VisitCallWithCallerSavedRegisters(node, state);
91  case IrOpcode::kLoadElement:
92  return VisitLoadElement(node, state);
93  case IrOpcode::kLoadField:
94  return VisitLoadField(node, state);
95  case IrOpcode::kStoreElement:
96  return VisitStoreElement(node, state);
97  case IrOpcode::kStoreField:
98  return VisitStoreField(node, state);
99  case IrOpcode::kDeoptimizeIf:
100  case IrOpcode::kDeoptimizeUnless:
101  case IrOpcode::kIfException:
102  case IrOpcode::kLoad:
103  case IrOpcode::kProtectedLoad:
104  case IrOpcode::kUnalignedLoad:
105  case IrOpcode::kStore:
106  case IrOpcode::kProtectedStore:
107  case IrOpcode::kUnalignedStore:
108  case IrOpcode::kRetain:
109  case IrOpcode::kUnsafePointerAdd:
110  case IrOpcode::kDebugBreak:
111  case IrOpcode::kUnreachable:
112  case IrOpcode::kWord32PoisonOnSpeculation:
113  case IrOpcode::kWord64PoisonOnSpeculation:
114  return VisitOtherEffect(node, state);
115  default:
116  break;
117  }
118  DCHECK_EQ(0, node->op()->EffectOutputCount());
119 }
120 
121 #define __ gasm()->
122 
123 void MemoryOptimizer::VisitAllocateRaw(Node* node,
124  AllocationState const* state) {
125  DCHECK_EQ(IrOpcode::kAllocateRaw, node->opcode());
126  Node* value;
127  Node* size = node->InputAt(0);
128  Node* effect = node->InputAt(1);
129  Node* control = node->InputAt(2);
130 
131  gasm()->Reset(effect, control);
132 
133  PretenureFlag pretenure = PretenureFlagOf(node->op());
134 
135  // Propagate tenuring from outer allocations to inner allocations, i.e.
136  // when we allocate an object in old space and store a newly allocated
137  // child object into the pretenured object, then the newly allocated
138  // child object also should get pretenured to old space.
139  if (pretenure == TENURED) {
140  for (Edge const edge : node->use_edges()) {
141  Node* const user = edge.from();
142  if (user->opcode() == IrOpcode::kStoreField && edge.index() == 0) {
143  Node* const child = user->InputAt(1);
144  if (child->opcode() == IrOpcode::kAllocateRaw &&
145  PretenureFlagOf(child->op()) == NOT_TENURED) {
146  NodeProperties::ChangeOp(child, node->op());
147  break;
148  }
149  }
150  }
151  } else {
152  DCHECK_EQ(NOT_TENURED, pretenure);
153  for (Edge const edge : node->use_edges()) {
154  Node* const user = edge.from();
155  if (user->opcode() == IrOpcode::kStoreField && edge.index() == 1) {
156  Node* const parent = user->InputAt(0);
157  if (parent->opcode() == IrOpcode::kAllocateRaw &&
158  PretenureFlagOf(parent->op()) == TENURED) {
159  pretenure = TENURED;
160  break;
161  }
162  }
163  }
164  }
165 
166  // Determine the top/limit addresses.
167  Node* top_address = __ ExternalConstant(
168  pretenure == NOT_TENURED
169  ? ExternalReference::new_space_allocation_top_address(isolate())
170  : ExternalReference::old_space_allocation_top_address(isolate()));
171  Node* limit_address = __ ExternalConstant(
172  pretenure == NOT_TENURED
173  ? ExternalReference::new_space_allocation_limit_address(isolate())
174  : ExternalReference::old_space_allocation_limit_address(isolate()));
175 
176  // Check if we can fold this allocation into a previous allocation represented
177  // by the incoming {state}.
178  IntPtrMatcher m(size);
179  if (m.IsInRange(0, kMaxRegularHeapObjectSize)) {
180  intptr_t const object_size = m.Value();
181  if (allocation_folding_ == AllocationFolding::kDoAllocationFolding &&
182  state->size() <= kMaxRegularHeapObjectSize - object_size &&
183  state->group()->pretenure() == pretenure) {
184  // We can fold this Allocate {node} into the allocation {group}
185  // represented by the given {state}. Compute the upper bound for
186  // the new {state}.
187  intptr_t const state_size = state->size() + object_size;
188 
189  // Update the reservation check to the actual maximum upper bound.
190  AllocationGroup* const group = state->group();
191  if (machine()->Is64()) {
192  if (OpParameter<int64_t>(group->size()->op()) < state_size) {
193  NodeProperties::ChangeOp(group->size(),
194  common()->Int64Constant(state_size));
195  }
196  } else {
197  if (OpParameter<int32_t>(group->size()->op()) < state_size) {
198  NodeProperties::ChangeOp(
199  group->size(),
200  common()->Int32Constant(static_cast<int32_t>(state_size)));
201  }
202  }
203 
204  // Update the allocation top with the new object allocation.
205  // TODO(bmeurer): Defer writing back top as much as possible.
206  Node* top = __ IntAdd(state->top(), size);
207  __ Store(StoreRepresentation(MachineType::PointerRepresentation(),
208  kNoWriteBarrier),
209  top_address, __ IntPtrConstant(0), top);
210 
211  // Compute the effective inner allocated address.
212  value = __ BitcastWordToTagged(
213  __ IntAdd(state->top(), __ IntPtrConstant(kHeapObjectTag)));
214 
215  // Extend the allocation {group}.
216  group->Add(value);
217  state = AllocationState::Open(group, state_size, top, zone());
218  } else {
219  auto call_runtime = __ MakeDeferredLabel();
220  auto done = __ MakeLabel(MachineType::PointerRepresentation());
221 
222  // Setup a mutable reservation size node; will be patched as we fold
223  // additional allocations into this new group.
224  Node* size = __ UniqueIntPtrConstant(object_size);
225 
226  // Load allocation top and limit.
227  Node* top =
228  __ Load(MachineType::Pointer(), top_address, __ IntPtrConstant(0));
229  Node* limit =
230  __ Load(MachineType::Pointer(), limit_address, __ IntPtrConstant(0));
231 
232  // Check if we need to collect garbage before we can start bump pointer
233  // allocation (always done for folded allocations).
234  Node* check = __ UintLessThan(__ IntAdd(top, size), limit);
235 
236  __ GotoIfNot(check, &call_runtime);
237  __ Goto(&done, top);
238 
239  __ Bind(&call_runtime);
240  {
241  Node* target =
242  pretenure == NOT_TENURED ? __ AllocateInNewSpaceStubConstant()
243  : __
244  AllocateInOldSpaceStubConstant();
245  if (!allocate_operator_.is_set()) {
246  auto descriptor = AllocateDescriptor{};
247  auto call_descriptor = Linkage::GetStubCallDescriptor(
248  graph()->zone(), descriptor, descriptor.GetStackParameterCount(),
249  CallDescriptor::kCanUseRoots, Operator::kNoThrow);
250  allocate_operator_.set(common()->Call(call_descriptor));
251  }
252  Node* vfalse = __ Call(allocate_operator_.get(), target, size);
253  vfalse = __ IntSub(vfalse, __ IntPtrConstant(kHeapObjectTag));
254  __ Goto(&done, vfalse);
255  }
256 
257  __ Bind(&done);
258 
259  // Compute the new top and write it back.
260  top = __ IntAdd(done.PhiAt(0), __ IntPtrConstant(object_size));
261  __ Store(StoreRepresentation(MachineType::PointerRepresentation(),
262  kNoWriteBarrier),
263  top_address, __ IntPtrConstant(0), top);
264 
265  // Compute the initial object address.
266  value = __ BitcastWordToTagged(
267  __ IntAdd(done.PhiAt(0), __ IntPtrConstant(kHeapObjectTag)));
268 
269  // Start a new allocation group.
270  AllocationGroup* group =
271  new (zone()) AllocationGroup(value, pretenure, size, zone());
272  state = AllocationState::Open(group, object_size, top, zone());
273  }
274  } else {
275  auto call_runtime = __ MakeDeferredLabel();
276  auto done = __ MakeLabel(MachineRepresentation::kTaggedPointer);
277 
278  // Load allocation top and limit.
279  Node* top =
280  __ Load(MachineType::Pointer(), top_address, __ IntPtrConstant(0));
281  Node* limit =
282  __ Load(MachineType::Pointer(), limit_address, __ IntPtrConstant(0));
283 
284  // Compute the new top.
285  Node* new_top = __ IntAdd(top, size);
286 
287  // Check if we can do bump pointer allocation here.
288  Node* check = __ UintLessThan(new_top, limit);
289  __ GotoIfNot(check, &call_runtime);
290  __ Store(StoreRepresentation(MachineType::PointerRepresentation(),
291  kNoWriteBarrier),
292  top_address, __ IntPtrConstant(0), new_top);
293  __ Goto(&done, __ BitcastWordToTagged(
294  __ IntAdd(top, __ IntPtrConstant(kHeapObjectTag))));
295 
296  __ Bind(&call_runtime);
297  Node* target =
298  pretenure == NOT_TENURED ? __ AllocateInNewSpaceStubConstant()
299  : __
300  AllocateInOldSpaceStubConstant();
301  if (!allocate_operator_.is_set()) {
302  auto descriptor = AllocateDescriptor{};
303  auto call_descriptor = Linkage::GetStubCallDescriptor(
304  graph()->zone(), descriptor, descriptor.GetStackParameterCount(),
305  CallDescriptor::kCanUseRoots, Operator::kNoThrow);
306  allocate_operator_.set(common()->Call(call_descriptor));
307  }
308  __ Goto(&done, __ Call(allocate_operator_.get(), target, size));
309 
310  __ Bind(&done);
311  value = done.PhiAt(0);
312 
313  // Create an unfoldable allocation group.
314  AllocationGroup* group =
315  new (zone()) AllocationGroup(value, pretenure, zone());
316  state = AllocationState::Closed(group, zone());
317  }
318 
319  effect = __ ExtractCurrentEffect();
320  control = __ ExtractCurrentControl();
321 
322  // Replace all effect uses of {node} with the {effect}, enqueue the
323  // effect uses for further processing, and replace all value uses of
324  // {node} with the {value}.
325  for (Edge edge : node->use_edges()) {
326  if (NodeProperties::IsEffectEdge(edge)) {
327  EnqueueUse(edge.from(), edge.index(), state);
328  edge.UpdateTo(effect);
329  } else if (NodeProperties::IsValueEdge(edge)) {
330  edge.UpdateTo(value);
331  } else {
332  DCHECK(NodeProperties::IsControlEdge(edge));
333  edge.UpdateTo(control);
334  }
335  }
336 
337  // Kill the {node} to make sure we don't leave dangling dead uses.
338  node->Kill();
339 }
340 
341 #undef __
342 
343 void MemoryOptimizer::VisitCall(Node* node, AllocationState const* state) {
344  DCHECK_EQ(IrOpcode::kCall, node->opcode());
345  // If the call can allocate, we start with a fresh state.
346  if (!(CallDescriptorOf(node->op())->flags() & CallDescriptor::kNoAllocate)) {
347  state = empty_state();
348  }
349  EnqueueUses(node, state);
350 }
351 
352 void MemoryOptimizer::VisitCallWithCallerSavedRegisters(
353  Node* node, AllocationState const* state) {
354  DCHECK_EQ(IrOpcode::kCallWithCallerSavedRegisters, node->opcode());
355  // If the call can allocate, we start with a fresh state.
356  if (!(CallDescriptorOf(node->op())->flags() & CallDescriptor::kNoAllocate)) {
357  state = empty_state();
358  }
359  EnqueueUses(node, state);
360 }
361 
362 void MemoryOptimizer::VisitLoadElement(Node* node,
363  AllocationState const* state) {
364  DCHECK_EQ(IrOpcode::kLoadElement, node->opcode());
365  ElementAccess const& access = ElementAccessOf(node->op());
366  Node* index = node->InputAt(1);
367  node->ReplaceInput(1, ComputeIndex(access, index));
368  if (NeedsPoisoning(access.load_sensitivity) &&
369  access.machine_type.representation() !=
370  MachineRepresentation::kTaggedPointer) {
371  NodeProperties::ChangeOp(node,
372  machine()->PoisonedLoad(access.machine_type));
373  } else {
374  NodeProperties::ChangeOp(node, machine()->Load(access.machine_type));
375  }
376  EnqueueUses(node, state);
377 }
378 
379 void MemoryOptimizer::VisitLoadField(Node* node, AllocationState const* state) {
380  DCHECK_EQ(IrOpcode::kLoadField, node->opcode());
381  FieldAccess const& access = FieldAccessOf(node->op());
382  Node* offset = jsgraph()->IntPtrConstant(access.offset - access.tag());
383  node->InsertInput(graph()->zone(), 1, offset);
384  if (NeedsPoisoning(access.load_sensitivity) &&
385  access.machine_type.representation() !=
386  MachineRepresentation::kTaggedPointer) {
387  NodeProperties::ChangeOp(node,
388  machine()->PoisonedLoad(access.machine_type));
389  } else {
390  NodeProperties::ChangeOp(node, machine()->Load(access.machine_type));
391  }
392  EnqueueUses(node, state);
393 }
394 
395 void MemoryOptimizer::VisitStoreElement(Node* node,
396  AllocationState const* state) {
397  DCHECK_EQ(IrOpcode::kStoreElement, node->opcode());
398  ElementAccess const& access = ElementAccessOf(node->op());
399  Node* object = node->InputAt(0);
400  Node* index = node->InputAt(1);
401  WriteBarrierKind write_barrier_kind =
402  ComputeWriteBarrierKind(object, state, access.write_barrier_kind);
403  node->ReplaceInput(1, ComputeIndex(access, index));
404  NodeProperties::ChangeOp(
405  node, machine()->Store(StoreRepresentation(
406  access.machine_type.representation(), write_barrier_kind)));
407  EnqueueUses(node, state);
408 }
409 
410 void MemoryOptimizer::VisitStoreField(Node* node,
411  AllocationState const* state) {
412  DCHECK_EQ(IrOpcode::kStoreField, node->opcode());
413  FieldAccess const& access = FieldAccessOf(node->op());
414  Node* object = node->InputAt(0);
415  WriteBarrierKind write_barrier_kind =
416  ComputeWriteBarrierKind(object, state, access.write_barrier_kind);
417  Node* offset = jsgraph()->IntPtrConstant(access.offset - access.tag());
418  node->InsertInput(graph()->zone(), 1, offset);
419  NodeProperties::ChangeOp(
420  node, machine()->Store(StoreRepresentation(
421  access.machine_type.representation(), write_barrier_kind)));
422  EnqueueUses(node, state);
423 }
424 
425 void MemoryOptimizer::VisitOtherEffect(Node* node,
426  AllocationState const* state) {
427  EnqueueUses(node, state);
428 }
429 
430 Node* MemoryOptimizer::ComputeIndex(ElementAccess const& access, Node* index) {
431  int const element_size_shift =
432  ElementSizeLog2Of(access.machine_type.representation());
433  if (element_size_shift) {
434  index = graph()->NewNode(machine()->WordShl(), index,
435  jsgraph()->IntPtrConstant(element_size_shift));
436  }
437  int const fixed_offset = access.header_size - access.tag();
438  if (fixed_offset) {
439  index = graph()->NewNode(machine()->IntAdd(), index,
440  jsgraph()->IntPtrConstant(fixed_offset));
441  }
442  return index;
443 }
444 
445 WriteBarrierKind MemoryOptimizer::ComputeWriteBarrierKind(
446  Node* object, AllocationState const* state,
447  WriteBarrierKind write_barrier_kind) {
448  if (state->IsNewSpaceAllocation() && state->group()->Contains(object)) {
449  write_barrier_kind = kNoWriteBarrier;
450  }
451  return write_barrier_kind;
452 }
453 
454 MemoryOptimizer::AllocationState const* MemoryOptimizer::MergeStates(
455  AllocationStates const& states) {
456  // Check if all states are the same; or at least if all allocation
457  // states belong to the same allocation group.
458  AllocationState const* state = states.front();
459  AllocationGroup* group = state->group();
460  for (size_t i = 1; i < states.size(); ++i) {
461  if (states[i] != state) state = nullptr;
462  if (states[i]->group() != group) group = nullptr;
463  }
464  if (state == nullptr) {
465  if (group != nullptr) {
466  // We cannot fold any more allocations into this group, but we can still
467  // eliminate write barriers on stores to this group.
468  // TODO(bmeurer): We could potentially just create a Phi here to merge
469  // the various tops; but we need to pay special attention not to create
470  // an unschedulable graph.
471  state = AllocationState::Closed(group, zone());
472  } else {
473  // The states are from different allocation groups.
474  state = empty_state();
475  }
476  }
477  return state;
478 }
479 
480 void MemoryOptimizer::EnqueueMerge(Node* node, int index,
481  AllocationState const* state) {
482  DCHECK_EQ(IrOpcode::kEffectPhi, node->opcode());
483  int const input_count = node->InputCount() - 1;
484  DCHECK_LT(0, input_count);
485  Node* const control = node->InputAt(input_count);
486  if (control->opcode() == IrOpcode::kLoop) {
487  // For loops we always start with an empty state at the beginning.
488  if (index == 0) EnqueueUses(node, empty_state());
489  } else {
490  DCHECK_EQ(IrOpcode::kMerge, control->opcode());
491  // Check if we already know about this pending merge.
492  NodeId const id = node->id();
493  auto it = pending_.find(id);
494  if (it == pending_.end()) {
495  // Insert a new pending merge.
496  it = pending_.insert(std::make_pair(id, AllocationStates(zone()))).first;
497  }
498  // Add the next input state.
499  it->second.push_back(state);
500  // Check if states for all inputs are available by now.
501  if (it->second.size() == static_cast<size_t>(input_count)) {
502  // All inputs to this effect merge are done, merge the states given all
503  // input constraints, drop the pending merge and enqueue uses of the
504  // EffectPhi {node}.
505  state = MergeStates(it->second);
506  EnqueueUses(node, state);
507  pending_.erase(it);
508  }
509  }
510 }
511 
512 void MemoryOptimizer::EnqueueUses(Node* node, AllocationState const* state) {
513  for (Edge const edge : node->use_edges()) {
514  if (NodeProperties::IsEffectEdge(edge)) {
515  EnqueueUse(edge.from(), edge.index(), state);
516  }
517  }
518 }
519 
520 void MemoryOptimizer::EnqueueUse(Node* node, int index,
521  AllocationState const* state) {
522  if (node->opcode() == IrOpcode::kEffectPhi) {
523  // An EffectPhi represents a merge of different effect chains, which
524  // needs special handling depending on whether the merge is part of a
525  // loop or just a normal control join.
526  EnqueueMerge(node, index, state);
527  } else {
528  Token token = {node, state};
529  tokens_.push(token);
530  }
531 }
532 
533 Graph* MemoryOptimizer::graph() const { return jsgraph()->graph(); }
534 
535 Isolate* MemoryOptimizer::isolate() const { return jsgraph()->isolate(); }
536 
537 CommonOperatorBuilder* MemoryOptimizer::common() const {
538  return jsgraph()->common();
539 }
540 
541 MachineOperatorBuilder* MemoryOptimizer::machine() const {
542  return jsgraph()->machine();
543 }
544 
545 bool MemoryOptimizer::NeedsPoisoning(LoadSensitivity load_sensitivity) const {
546  // Safe loads do not need poisoning.
547  if (load_sensitivity == LoadSensitivity::kSafe) return false;
548 
549  switch (poisoning_level_) {
550  case PoisoningMitigationLevel::kDontPoison:
551  return false;
552  case PoisoningMitigationLevel::kPoisonAll:
553  return true;
554  case PoisoningMitigationLevel::kPoisonCriticalOnly:
555  return load_sensitivity == LoadSensitivity::kCritical;
556  }
557  UNREACHABLE();
558 }
559 
560 } // namespace compiler
561 } // namespace internal
562 } // namespace v8
STL namespace.
Definition: libplatform.h:13