memory-optimizer.cc 30.4 KB
Newer Older
1 2 3 4 5 6
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/compiler/memory-optimizer.h"

7
#include "src/codegen/interface-descriptors.h"
8 9 10 11 12 13
#include "src/compiler/js-graph.h"
#include "src/compiler/linkage.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/node-properties.h"
#include "src/compiler/node.h"
#include "src/compiler/simplified-operator.h"
14
#include "src/roots/roots-inl.h"
15 16 17 18 19

namespace v8 {
namespace internal {
namespace compiler {

20
MemoryOptimizer::MemoryOptimizer(JSGraph* jsgraph, Zone* zone,
21
                                 PoisoningMitigationLevel poisoning_level,
22 23
                                 AllocationFolding allocation_folding,
                                 const char* function_debug_name)
24 25 26 27
    : jsgraph_(jsgraph),
      empty_state_(AllocationState::Empty(zone)),
      pending_(zone),
      tokens_(zone),
28
      zone_(zone),
29
      graph_assembler_(jsgraph, nullptr, nullptr, zone),
30
      poisoning_level_(poisoning_level),
31 32
      allocation_folding_(allocation_folding),
      function_debug_name_(function_debug_name) {}
33 34 35 36 37 38 39 40 41 42 43 44 45

void MemoryOptimizer::Optimize() {
  EnqueueUses(graph()->start(), empty_state());
  while (!tokens_.empty()) {
    Token const token = tokens_.front();
    tokens_.pop();
    VisitNode(token.node, token.state);
  }
  DCHECK(pending_.empty());
  DCHECK(tokens_.empty());
}

MemoryOptimizer::AllocationGroup::AllocationGroup(Node* node,
46
                                                  AllocationType allocation,
47
                                                  Zone* zone)
48
    : node_ids_(zone), allocation_(allocation), size_(nullptr) {
49 50 51 52
  node_ids_.insert(node->id());
}

MemoryOptimizer::AllocationGroup::AllocationGroup(Node* node,
53
                                                  AllocationType allocation,
54
                                                  Node* size, Zone* zone)
55
    : node_ids_(zone), allocation_(allocation), size_(size) {
56 57 58 59 60 61 62 63
  node_ids_.insert(node->id());
}

void MemoryOptimizer::AllocationGroup::Add(Node* node) {
  node_ids_.insert(node->id());
}

bool MemoryOptimizer::AllocationGroup::Contains(Node* node) const {
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
  // Additions should stay within the same allocated object, so it's safe to
  // ignore them.
  while (node_ids_.find(node->id()) == node_ids_.end()) {
    switch (node->opcode()) {
      case IrOpcode::kBitcastTaggedToWord:
      case IrOpcode::kBitcastWordToTagged:
      case IrOpcode::kInt32Add:
      case IrOpcode::kInt64Add:
        node = NodeProperties::GetValueInput(node, 0);
        break;
      default:
        return false;
    }
  }
  return true;
79 80 81 82 83 84 85 86 87
}

MemoryOptimizer::AllocationState::AllocationState()
    : group_(nullptr), size_(std::numeric_limits<int>::max()), top_(nullptr) {}

MemoryOptimizer::AllocationState::AllocationState(AllocationGroup* group)
    : group_(group), size_(std::numeric_limits<int>::max()), top_(nullptr) {}

MemoryOptimizer::AllocationState::AllocationState(AllocationGroup* group,
88
                                                  intptr_t size, Node* top)
89 90
    : group_(group), size_(size), top_(top) {}

91 92
bool MemoryOptimizer::AllocationState::IsYoungGenerationAllocation() const {
  return group() && group()->IsYoungGenerationAllocation();
93 94
}

95 96 97
namespace {

bool CanAllocate(const Node* node) {
98
  switch (node->opcode()) {
99 100 101
    case IrOpcode::kBitcastTaggedToWord:
    case IrOpcode::kBitcastWordToTagged:
    case IrOpcode::kComment:
102
    case IrOpcode::kAbortCSAAssert:
103
    case IrOpcode::kDebugBreak:
104 105
    case IrOpcode::kDeoptimizeIf:
    case IrOpcode::kDeoptimizeUnless:
106
    case IrOpcode::kEffectPhi:
107 108
    case IrOpcode::kIfException:
    case IrOpcode::kLoad:
109 110
    case IrOpcode::kLoadElement:
    case IrOpcode::kLoadField:
111
    case IrOpcode::kLoadFromObject:
112
    case IrOpcode::kPoisonedLoad:
113 114
    case IrOpcode::kProtectedLoad:
    case IrOpcode::kProtectedStore:
115
    case IrOpcode::kRetain:
116 117 118 119
    // TODO(tebbi): Store nodes might do a bump-pointer allocation.
    //              We should introduce a special bump-pointer store node to
    //              differentiate that.
    case IrOpcode::kStore:
120 121
    case IrOpcode::kStoreElement:
    case IrOpcode::kStoreField:
122
    case IrOpcode::kStoreToObject:
123 124 125
    case IrOpcode::kTaggedPoisonOnSpeculation:
    case IrOpcode::kUnalignedLoad:
    case IrOpcode::kUnalignedStore:
126
    case IrOpcode::kUnsafePointerAdd:
127
    case IrOpcode::kUnreachable:
128
    case IrOpcode::kStaticAssert:
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
    case IrOpcode::kWord32AtomicAdd:
    case IrOpcode::kWord32AtomicAnd:
    case IrOpcode::kWord32AtomicCompareExchange:
    case IrOpcode::kWord32AtomicExchange:
    case IrOpcode::kWord32AtomicLoad:
    case IrOpcode::kWord32AtomicOr:
    case IrOpcode::kWord32AtomicPairAdd:
    case IrOpcode::kWord32AtomicPairAnd:
    case IrOpcode::kWord32AtomicPairCompareExchange:
    case IrOpcode::kWord32AtomicPairExchange:
    case IrOpcode::kWord32AtomicPairLoad:
    case IrOpcode::kWord32AtomicPairOr:
    case IrOpcode::kWord32AtomicPairStore:
    case IrOpcode::kWord32AtomicPairSub:
    case IrOpcode::kWord32AtomicPairXor:
    case IrOpcode::kWord32AtomicStore:
    case IrOpcode::kWord32AtomicSub:
    case IrOpcode::kWord32AtomicXor:
147
    case IrOpcode::kWord32PoisonOnSpeculation:
148 149 150 151 152 153 154 155 156
    case IrOpcode::kWord64AtomicAdd:
    case IrOpcode::kWord64AtomicAnd:
    case IrOpcode::kWord64AtomicCompareExchange:
    case IrOpcode::kWord64AtomicExchange:
    case IrOpcode::kWord64AtomicLoad:
    case IrOpcode::kWord64AtomicOr:
    case IrOpcode::kWord64AtomicStore:
    case IrOpcode::kWord64AtomicSub:
    case IrOpcode::kWord64AtomicXor:
157
    case IrOpcode::kWord64PoisonOnSpeculation:
158 159 160 161 162 163
      return false;

    case IrOpcode::kCall:
    case IrOpcode::kCallWithCallerSavedRegisters:
      return !(CallDescriptorOf(node->op())->flags() &
               CallDescriptor::kNoAllocate);
164 165 166
    default:
      break;
  }
167 168 169
  return true;
}

170
Node* SearchAllocatingNode(Node* start, Node* limit, Zone* temp_zone) {
171 172
  ZoneQueue<Node*> queue(temp_zone);
  ZoneSet<Node*> visited(temp_zone);
173 174
  visited.insert(limit);
  queue.push(start);
175 176 177 178 179 180 181

  while (!queue.empty()) {
    Node* const current = queue.front();
    queue.pop();
    if (visited.find(current) == visited.end()) {
      visited.insert(current);

182 183 184
      if (CanAllocate(current)) {
        return current;
      }
185 186 187 188 189 190

      for (int i = 0; i < current->op()->EffectInputCount(); ++i) {
        queue.push(NodeProperties::GetEffectInput(current, i));
      }
    }
  }
191 192 193 194 195 196 197 198 199 200 201 202
  return nullptr;
}

bool CanLoopAllocate(Node* loop_effect_phi, Zone* temp_zone) {
  Node* const control = NodeProperties::GetControlInput(loop_effect_phi);
  // Start the effect chain walk from the loop back edges.
  for (int i = 1; i < control->InputCount(); ++i) {
    if (SearchAllocatingNode(loop_effect_phi->InputAt(i), loop_effect_phi,
                             temp_zone) != nullptr) {
      return true;
    }
  }
203 204 205
  return false;
}

206 207 208 209 210 211 212 213 214 215
Node* EffectPhiForPhi(Node* phi) {
  Node* control = NodeProperties::GetControlInput(phi);
  for (Node* use : control->uses()) {
    if (use->opcode() == IrOpcode::kEffectPhi) {
      return use;
    }
  }
  return nullptr;
}

216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
}  // namespace

void MemoryOptimizer::VisitNode(Node* node, AllocationState const* state) {
  DCHECK(!node->IsDead());
  DCHECK_LT(0, node->op()->EffectInputCount());
  switch (node->opcode()) {
    case IrOpcode::kAllocate:
      // Allocate nodes were purged from the graph in effect-control
      // linearization.
      UNREACHABLE();
    case IrOpcode::kAllocateRaw:
      return VisitAllocateRaw(node, state);
    case IrOpcode::kCall:
      return VisitCall(node, state);
    case IrOpcode::kCallWithCallerSavedRegisters:
      return VisitCallWithCallerSavedRegisters(node, state);
232 233
    case IrOpcode::kLoadFromObject:
      return VisitLoadFromObject(node, state);
234 235 236 237
    case IrOpcode::kLoadElement:
      return VisitLoadElement(node, state);
    case IrOpcode::kLoadField:
      return VisitLoadField(node, state);
238 239
    case IrOpcode::kStoreToObject:
      return VisitStoreToObject(node, state);
240 241 242 243 244 245 246 247 248 249 250 251
    case IrOpcode::kStoreElement:
      return VisitStoreElement(node, state);
    case IrOpcode::kStoreField:
      return VisitStoreField(node, state);
    case IrOpcode::kStore:
      return VisitStore(node, state);
    default:
      if (!CanAllocate(node)) {
        // These operations cannot trigger GC.
        return VisitOtherEffect(node, state);
      }
  }
252 253 254
  DCHECK_EQ(0, node->op()->EffectOutputCount());
}

255 256
#define __ gasm()->

257 258 259
void MemoryOptimizer::VisitAllocateRaw(Node* node,
                                       AllocationState const* state) {
  DCHECK_EQ(IrOpcode::kAllocateRaw, node->opcode());
260 261 262 263
  Node* value;
  Node* size = node->InputAt(0);
  Node* effect = node->InputAt(1);
  Node* control = node->InputAt(2);
264 265 266

  gasm()->Reset(effect, control);

267 268
  const AllocateParameters& allocation = AllocateParametersOf(node->op());
  AllocationType allocation_type = allocation.allocation_type();
269

270 271 272 273
  // Propagate tenuring from outer allocations to inner allocations, i.e.
  // when we allocate an object in old space and store a newly allocated
  // child object into the pretenured object, then the newly allocated
  // child object also should get pretenured to old space.
274
  if (allocation_type == AllocationType::kOld) {
275 276 277 278
    for (Edge const edge : node->use_edges()) {
      Node* const user = edge.from();
      if (user->opcode() == IrOpcode::kStoreField && edge.index() == 0) {
        Node* const child = user->InputAt(1);
279
        if (child->opcode() == IrOpcode::kAllocateRaw &&
280
            AllocationTypeOf(child->op()) == AllocationType::kYoung) {
281 282 283 284 285 286
          NodeProperties::ChangeOp(child, node->op());
          break;
        }
      }
    }
  } else {
287
    DCHECK_EQ(AllocationType::kYoung, allocation_type);
288 289 290 291
    for (Edge const edge : node->use_edges()) {
      Node* const user = edge.from();
      if (user->opcode() == IrOpcode::kStoreField && edge.index() == 1) {
        Node* const parent = user->InputAt(0);
292
        if (parent->opcode() == IrOpcode::kAllocateRaw &&
293
            AllocationTypeOf(parent->op()) == AllocationType::kOld) {
294
          allocation_type = AllocationType::kOld;
295 296 297 298 299 300
          break;
        }
      }
    }
  }

301 302 303 304 305 306 307 308 309 310 311 312 313 314 315
  Node* allocate_builtin;
  if (allocation_type == AllocationType::kYoung) {
    if (allocation.allow_large_objects() == AllowLargeObjects::kTrue) {
      allocate_builtin = __ AllocateInYoungGenerationStubConstant();
    } else {
      allocate_builtin = __ AllocateRegularInYoungGenerationStubConstant();
    }
  } else {
    if (allocation.allow_large_objects() == AllowLargeObjects::kTrue) {
      allocate_builtin = __ AllocateInOldGenerationStubConstant();
    } else {
      allocate_builtin = __ AllocateRegularInOldGenerationStubConstant();
    }
  }

316
  // Determine the top/limit addresses.
317
  Node* top_address = __ ExternalConstant(
318
      allocation_type == AllocationType::kYoung
319 320
          ? ExternalReference::new_space_allocation_top_address(isolate())
          : ExternalReference::old_space_allocation_top_address(isolate()));
321
  Node* limit_address = __ ExternalConstant(
322
      allocation_type == AllocationType::kYoung
323 324 325 326 327
          ? ExternalReference::new_space_allocation_limit_address(isolate())
          : ExternalReference::old_space_allocation_limit_address(isolate()));

  // Check if we can fold this allocation into a previous allocation represented
  // by the incoming {state}.
328
  IntPtrMatcher m(size);
329
  if (m.IsInRange(0, kMaxRegularHeapObjectSize) && FLAG_inline_new) {
330
    intptr_t const object_size = m.Value();
331 332
    if (allocation_folding_ == AllocationFolding::kDoAllocationFolding &&
        state->size() <= kMaxRegularHeapObjectSize - object_size &&
333
        state->group()->allocation() == allocation_type) {
334 335 336
      // We can fold this Allocate {node} into the allocation {group}
      // represented by the given {state}. Compute the upper bound for
      // the new {state}.
337
      intptr_t const state_size = state->size() + object_size;
338 339 340

      // Update the reservation check to the actual maximum upper bound.
      AllocationGroup* const group = state->group();
341 342 343 344 345 346 347 348 349 350 351
      if (machine()->Is64()) {
        if (OpParameter<int64_t>(group->size()->op()) < state_size) {
          NodeProperties::ChangeOp(group->size(),
                                   common()->Int64Constant(state_size));
        }
      } else {
        if (OpParameter<int32_t>(group->size()->op()) < state_size) {
          NodeProperties::ChangeOp(
              group->size(),
              common()->Int32Constant(static_cast<int32_t>(state_size)));
        }
352 353 354 355
      }

      // Update the allocation top with the new object allocation.
      // TODO(bmeurer): Defer writing back top as much as possible.
356
      Node* top = __ IntAdd(state->top(), size);
357 358 359
      __ Store(StoreRepresentation(MachineType::PointerRepresentation(),
                                   kNoWriteBarrier),
               top_address, __ IntPtrConstant(0), top);
360 361

      // Compute the effective inner allocated address.
362 363
      value = __ BitcastWordToTagged(
          __ IntAdd(state->top(), __ IntPtrConstant(kHeapObjectTag)));
364 365 366 367 368

      // Extend the allocation {group}.
      group->Add(value);
      state = AllocationState::Open(group, state_size, top, zone());
    } else {
369 370
      auto call_runtime = __ MakeDeferredLabel();
      auto done = __ MakeLabel(MachineType::PointerRepresentation());
371

372 373
      // Setup a mutable reservation size node; will be patched as we fold
      // additional allocations into this new group.
374
      Node* size = __ UniqueIntPtrConstant(object_size);
375 376

      // Load allocation top and limit.
377 378 379 380
      Node* top =
          __ Load(MachineType::Pointer(), top_address, __ IntPtrConstant(0));
      Node* limit =
          __ Load(MachineType::Pointer(), limit_address, __ IntPtrConstant(0));
381 382 383

      // Check if we need to collect garbage before we can start bump pointer
      // allocation (always done for folded allocations).
384
      Node* check = __ UintLessThan(__ IntAdd(top, size), limit);
385

386
      __ GotoIfNot(check, &call_runtime);
387
      __ Goto(&done, top);
388

389
      __ Bind(&call_runtime);
390 391
      {
        if (!allocate_operator_.is_set()) {
392
          auto descriptor = AllocateDescriptor{};
393
          auto call_descriptor = Linkage::GetStubCallDescriptor(
394
              graph()->zone(), descriptor, descriptor.GetStackParameterCount(),
395
              CallDescriptor::kCanUseRoots, Operator::kNoThrow);
396
          allocate_operator_.set(common()->Call(call_descriptor));
397
        }
398
        Node* vfalse = __ BitcastTaggedToWord(
399
            __ Call(allocate_operator_.get(), allocate_builtin, size));
400 401
        vfalse = __ IntSub(vfalse, __ IntPtrConstant(kHeapObjectTag));
        __ Goto(&done, vfalse);
402 403
      }

404
      __ Bind(&done);
405 406

      // Compute the new top and write it back.
407 408 409 410
      top = __ IntAdd(done.PhiAt(0), __ IntPtrConstant(object_size));
      __ Store(StoreRepresentation(MachineType::PointerRepresentation(),
                                   kNoWriteBarrier),
               top_address, __ IntPtrConstant(0), top);
411 412

      // Compute the initial object address.
413 414
      value = __ BitcastWordToTagged(
          __ IntAdd(done.PhiAt(0), __ IntPtrConstant(kHeapObjectTag)));
415 416 417

      // Start a new allocation group.
      AllocationGroup* group =
418
          new (zone()) AllocationGroup(value, allocation_type, size, zone());
419 420 421
      state = AllocationState::Open(group, object_size, top, zone());
    }
  } else {
422 423
    auto call_runtime = __ MakeDeferredLabel();
    auto done = __ MakeLabel(MachineRepresentation::kTaggedPointer);
424

425
    // Load allocation top and limit.
426 427 428 429
    Node* top =
        __ Load(MachineType::Pointer(), top_address, __ IntPtrConstant(0));
    Node* limit =
        __ Load(MachineType::Pointer(), limit_address, __ IntPtrConstant(0));
430 431

    // Compute the new top.
432
    Node* new_top = __ IntAdd(top, size);
433 434

    // Check if we can do bump pointer allocation here.
435
    Node* check = __ UintLessThan(new_top, limit);
436
    __ GotoIfNot(check, &call_runtime);
437 438 439 440 441
    if (allocation.allow_large_objects() == AllowLargeObjects::kTrue) {
      __ GotoIfNot(
          __ UintLessThan(size, __ IntPtrConstant(kMaxRegularHeapObjectSize)),
          &call_runtime);
    }
442 443 444 445 446 447 448 449
    __ Store(StoreRepresentation(MachineType::PointerRepresentation(),
                                 kNoWriteBarrier),
             top_address, __ IntPtrConstant(0), new_top);
    __ Goto(&done, __ BitcastWordToTagged(
                       __ IntAdd(top, __ IntPtrConstant(kHeapObjectTag))));

    __ Bind(&call_runtime);
    if (!allocate_operator_.is_set()) {
450
      auto descriptor = AllocateDescriptor{};
451
      auto call_descriptor = Linkage::GetStubCallDescriptor(
452
          graph()->zone(), descriptor, descriptor.GetStackParameterCount(),
453
          CallDescriptor::kCanUseRoots, Operator::kNoThrow);
454
      allocate_operator_.set(common()->Call(call_descriptor));
455
    }
456
    __ Goto(&done, __ Call(allocate_operator_.get(), allocate_builtin, size));
457

458 459
    __ Bind(&done);
    value = done.PhiAt(0);
460 461 462

    // Create an unfoldable allocation group.
    AllocationGroup* group =
463
        new (zone()) AllocationGroup(value, allocation_type, zone());
464 465 466
    state = AllocationState::Closed(group, zone());
  }

467 468 469
  effect = __ ExtractCurrentEffect();
  control = __ ExtractCurrentControl();

470 471 472 473 474 475 476
  // Replace all effect uses of {node} with the {effect}, enqueue the
  // effect uses for further processing, and replace all value uses of
  // {node} with the {value}.
  for (Edge edge : node->use_edges()) {
    if (NodeProperties::IsEffectEdge(edge)) {
      EnqueueUse(edge.from(), edge.index(), state);
      edge.UpdateTo(effect);
477
    } else if (NodeProperties::IsValueEdge(edge)) {
478
      edge.UpdateTo(value);
479 480 481
    } else {
      DCHECK(NodeProperties::IsControlEdge(edge));
      edge.UpdateTo(control);
482 483 484 485 486 487 488
    }
  }

  // Kill the {node} to make sure we don't leave dangling dead uses.
  node->Kill();
}

489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510
void MemoryOptimizer::VisitLoadFromObject(Node* node,
                                          AllocationState const* state) {
  DCHECK_EQ(IrOpcode::kLoadFromObject, node->opcode());
  ObjectAccess const& access = ObjectAccessOf(node->op());
  NodeProperties::ChangeOp(node, machine()->Load(access.machine_type));
  EnqueueUses(node, state);
}

void MemoryOptimizer::VisitStoreToObject(Node* node,
                                         AllocationState const* state) {
  DCHECK_EQ(IrOpcode::kStoreToObject, node->opcode());
  ObjectAccess const& access = ObjectAccessOf(node->op());
  Node* object = node->InputAt(0);
  Node* value = node->InputAt(2);
  WriteBarrierKind write_barrier_kind = ComputeWriteBarrierKind(
      node, object, value, state, access.write_barrier_kind);
  NodeProperties::ChangeOp(
      node, machine()->Store(StoreRepresentation(
                access.machine_type.representation(), write_barrier_kind)));
  EnqueueUses(node, state);
}

511 512
#undef __

513 514 515 516 517 518 519 520 521
void MemoryOptimizer::VisitCall(Node* node, AllocationState const* state) {
  DCHECK_EQ(IrOpcode::kCall, node->opcode());
  // If the call can allocate, we start with a fresh state.
  if (!(CallDescriptorOf(node->op())->flags() & CallDescriptor::kNoAllocate)) {
    state = empty_state();
  }
  EnqueueUses(node, state);
}

522 523 524 525 526 527 528 529 530 531
void MemoryOptimizer::VisitCallWithCallerSavedRegisters(
    Node* node, AllocationState const* state) {
  DCHECK_EQ(IrOpcode::kCallWithCallerSavedRegisters, node->opcode());
  // If the call can allocate, we start with a fresh state.
  if (!(CallDescriptorOf(node->op())->flags() & CallDescriptor::kNoAllocate)) {
    state = empty_state();
  }
  EnqueueUses(node, state);
}

532 533 534 535 536 537
void MemoryOptimizer::VisitLoadElement(Node* node,
                                       AllocationState const* state) {
  DCHECK_EQ(IrOpcode::kLoadElement, node->opcode());
  ElementAccess const& access = ElementAccessOf(node->op());
  Node* index = node->InputAt(1);
  node->ReplaceInput(1, ComputeIndex(access, index));
538
  MachineType type = access.machine_type;
539
  if (NeedsPoisoning(access.load_sensitivity) &&
540 541 542
      type.representation() != MachineRepresentation::kTaggedPointer &&
      type.representation() != MachineRepresentation::kCompressedPointer) {
    NodeProperties::ChangeOp(node, machine()->PoisonedLoad(type));
543
  } else {
544
    NodeProperties::ChangeOp(node, machine()->Load(type));
545
  }
546 547 548 549 550 551 552 553
  EnqueueUses(node, state);
}

void MemoryOptimizer::VisitLoadField(Node* node, AllocationState const* state) {
  DCHECK_EQ(IrOpcode::kLoadField, node->opcode());
  FieldAccess const& access = FieldAccessOf(node->op());
  Node* offset = jsgraph()->IntPtrConstant(access.offset - access.tag());
  node->InsertInput(graph()->zone(), 1, offset);
554
  MachineType type = access.machine_type;
555
  if (NeedsPoisoning(access.load_sensitivity) &&
556 557 558
      type.representation() != MachineRepresentation::kTaggedPointer &&
      type.representation() != MachineRepresentation::kCompressedPointer) {
    NodeProperties::ChangeOp(node, machine()->PoisonedLoad(type));
559
  } else {
560
    NodeProperties::ChangeOp(node, machine()->Load(type));
561
  }
562 563 564 565 566 567 568 569 570
  EnqueueUses(node, state);
}

void MemoryOptimizer::VisitStoreElement(Node* node,
                                        AllocationState const* state) {
  DCHECK_EQ(IrOpcode::kStoreElement, node->opcode());
  ElementAccess const& access = ElementAccessOf(node->op());
  Node* object = node->InputAt(0);
  Node* index = node->InputAt(1);
571 572 573
  Node* value = node->InputAt(2);
  WriteBarrierKind write_barrier_kind = ComputeWriteBarrierKind(
      node, object, value, state, access.write_barrier_kind);
574 575 576 577 578 579 580 581 582 583 584 585
  node->ReplaceInput(1, ComputeIndex(access, index));
  NodeProperties::ChangeOp(
      node, machine()->Store(StoreRepresentation(
                access.machine_type.representation(), write_barrier_kind)));
  EnqueueUses(node, state);
}

void MemoryOptimizer::VisitStoreField(Node* node,
                                      AllocationState const* state) {
  DCHECK_EQ(IrOpcode::kStoreField, node->opcode());
  FieldAccess const& access = FieldAccessOf(node->op());
  Node* object = node->InputAt(0);
586 587 588
  Node* value = node->InputAt(1);
  WriteBarrierKind write_barrier_kind = ComputeWriteBarrierKind(
      node, object, value, state, access.write_barrier_kind);
589 590 591 592 593 594 595 596
  Node* offset = jsgraph()->IntPtrConstant(access.offset - access.tag());
  node->InsertInput(graph()->zone(), 1, offset);
  NodeProperties::ChangeOp(
      node, machine()->Store(StoreRepresentation(
                access.machine_type.representation(), write_barrier_kind)));
  EnqueueUses(node, state);
}

597 598 599 600
void MemoryOptimizer::VisitStore(Node* node, AllocationState const* state) {
  DCHECK_EQ(IrOpcode::kStore, node->opcode());
  StoreRepresentation representation = StoreRepresentationOf(node->op());
  Node* object = node->InputAt(0);
601
  Node* value = node->InputAt(2);
602
  WriteBarrierKind write_barrier_kind = ComputeWriteBarrierKind(
603
      node, object, value, state, representation.write_barrier_kind());
604 605 606 607 608 609 610 611
  if (write_barrier_kind != representation.write_barrier_kind()) {
    NodeProperties::ChangeOp(
        node, machine()->Store(StoreRepresentation(
                  representation.representation(), write_barrier_kind)));
  }
  EnqueueUses(node, state);
}

612 613 614 615 616
void MemoryOptimizer::VisitOtherEffect(Node* node,
                                       AllocationState const* state) {
  EnqueueUses(node, state);
}

617
Node* MemoryOptimizer::ComputeIndex(ElementAccess const& access, Node* index) {
618
  int const element_size_shift =
619 620
      ElementSizeLog2Of(access.machine_type.representation());
  if (element_size_shift) {
621 622
    index = graph()->NewNode(machine()->WordShl(), index,
                             jsgraph()->IntPtrConstant(element_size_shift));
623
  }
624
  int const fixed_offset = access.header_size - access.tag();
625
  if (fixed_offset) {
626 627
    index = graph()->NewNode(machine()->IntAdd(), index,
                             jsgraph()->IntPtrConstant(fixed_offset));
628 629 630 631
  }
  return index;
}

632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697
namespace {

bool ValueNeedsWriteBarrier(Node* value, Isolate* isolate) {
  while (true) {
    switch (value->opcode()) {
      case IrOpcode::kBitcastWordToTaggedSigned:
      case IrOpcode::kChangeTaggedSignedToCompressedSigned:
      case IrOpcode::kChangeTaggedToCompressedSigned:
        return false;
      case IrOpcode::kChangeTaggedPointerToCompressedPointer:
      case IrOpcode::kChangeTaggedToCompressed:
        value = NodeProperties::GetValueInput(value, 0);
        continue;
      case IrOpcode::kHeapConstant: {
        RootIndex root_index;
        if (isolate->roots_table().IsRootHandle(HeapConstantOf(value->op()),
                                                &root_index) &&
            RootsTable::IsImmortalImmovable(root_index)) {
          return false;
        }
        break;
      }
      default:
        break;
    }
    return true;
  }
}

void WriteBarrierAssertFailed(Node* node, Node* object, const char* name,
                              Zone* temp_zone) {
  std::stringstream str;
  str << "MemoryOptimizer could not remove write barrier for node #"
      << node->id() << "\n";
  str << "  Run mksnapshot with --csa-trap-on-node=" << name << ","
      << node->id() << " to break in CSA code.\n";
  Node* object_position = object;
  if (object_position->opcode() == IrOpcode::kPhi) {
    object_position = EffectPhiForPhi(object_position);
  }
  Node* allocating_node = nullptr;
  if (object_position && object_position->op()->EffectOutputCount() > 0) {
    allocating_node = SearchAllocatingNode(node, object_position, temp_zone);
  }
  if (allocating_node) {
    str << "\n  There is a potentially allocating node in between:\n";
    str << "    " << *allocating_node << "\n";
    str << "  Run mksnapshot with --csa-trap-on-node=" << name << ","
        << allocating_node->id() << " to break there.\n";
    if (allocating_node->opcode() == IrOpcode::kCall) {
      str << "  If this is a never-allocating runtime call, you can add an "
             "exception to Runtime::MayAllocate.\n";
    }
  } else {
    str << "\n  It seems the store happened to something different than a "
           "direct "
           "allocation:\n";
    str << "    " << *object << "\n";
    str << "  Run mksnapshot with --csa-trap-on-node=" << name << ","
        << object->id() << " to break there.\n";
  }
  FATAL("%s", str.str().c_str());
}

}  // namespace

698
WriteBarrierKind MemoryOptimizer::ComputeWriteBarrierKind(
699
    Node* node, Node* object, Node* value, AllocationState const* state,
700
    WriteBarrierKind write_barrier_kind) {
701 702
  if (state->IsYoungGenerationAllocation() &&
      state->group()->Contains(object)) {
703 704
    write_barrier_kind = kNoWriteBarrier;
  }
705 706 707 708 709 710
  if (!ValueNeedsWriteBarrier(value, isolate())) {
    write_barrier_kind = kNoWriteBarrier;
  }
  if (write_barrier_kind == WriteBarrierKind::kAssertNoWriteBarrier) {
    WriteBarrierAssertFailed(node, object, function_debug_name_, zone());
  }
711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746
  return write_barrier_kind;
}

MemoryOptimizer::AllocationState const* MemoryOptimizer::MergeStates(
    AllocationStates const& states) {
  // Check if all states are the same; or at least if all allocation
  // states belong to the same allocation group.
  AllocationState const* state = states.front();
  AllocationGroup* group = state->group();
  for (size_t i = 1; i < states.size(); ++i) {
    if (states[i] != state) state = nullptr;
    if (states[i]->group() != group) group = nullptr;
  }
  if (state == nullptr) {
    if (group != nullptr) {
      // We cannot fold any more allocations into this group, but we can still
      // eliminate write barriers on stores to this group.
      // TODO(bmeurer): We could potentially just create a Phi here to merge
      // the various tops; but we need to pay special attention not to create
      // an unschedulable graph.
      state = AllocationState::Closed(group, zone());
    } else {
      // The states are from different allocation groups.
      state = empty_state();
    }
  }
  return state;
}

void MemoryOptimizer::EnqueueMerge(Node* node, int index,
                                   AllocationState const* state) {
  DCHECK_EQ(IrOpcode::kEffectPhi, node->opcode());
  int const input_count = node->InputCount() - 1;
  DCHECK_LT(0, input_count);
  Node* const control = node->InputAt(input_count);
  if (control->opcode() == IrOpcode::kLoop) {
747 748 749 750 751 752 753 754 755 756 757 758 759
    if (index == 0) {
      if (CanLoopAllocate(node, zone())) {
        // If the loop can allocate,  we start with an empty state at the
        // beginning.
        EnqueueUses(node, empty_state());
      } else {
        // If the loop cannot allocate, we can just propagate the state from
        // before the loop.
        EnqueueUses(node, state);
      }
    } else {
      // Do not revisit backedges.
    }
760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815
  } else {
    DCHECK_EQ(IrOpcode::kMerge, control->opcode());
    // Check if we already know about this pending merge.
    NodeId const id = node->id();
    auto it = pending_.find(id);
    if (it == pending_.end()) {
      // Insert a new pending merge.
      it = pending_.insert(std::make_pair(id, AllocationStates(zone()))).first;
    }
    // Add the next input state.
    it->second.push_back(state);
    // Check if states for all inputs are available by now.
    if (it->second.size() == static_cast<size_t>(input_count)) {
      // All inputs to this effect merge are done, merge the states given all
      // input constraints, drop the pending merge and enqueue uses of the
      // EffectPhi {node}.
      state = MergeStates(it->second);
      EnqueueUses(node, state);
      pending_.erase(it);
    }
  }
}

void MemoryOptimizer::EnqueueUses(Node* node, AllocationState const* state) {
  for (Edge const edge : node->use_edges()) {
    if (NodeProperties::IsEffectEdge(edge)) {
      EnqueueUse(edge.from(), edge.index(), state);
    }
  }
}

void MemoryOptimizer::EnqueueUse(Node* node, int index,
                                 AllocationState const* state) {
  if (node->opcode() == IrOpcode::kEffectPhi) {
    // An EffectPhi represents a merge of different effect chains, which
    // needs special handling depending on whether the merge is part of a
    // loop or just a normal control join.
    EnqueueMerge(node, index, state);
  } else {
    Token token = {node, state};
    tokens_.push(token);
  }
}

Graph* MemoryOptimizer::graph() const { return jsgraph()->graph(); }

Isolate* MemoryOptimizer::isolate() const { return jsgraph()->isolate(); }

CommonOperatorBuilder* MemoryOptimizer::common() const {
  return jsgraph()->common();
}

MachineOperatorBuilder* MemoryOptimizer::machine() const {
  return jsgraph()->machine();
}

816 817 818 819 820 821 822 823 824 825 826 827 828 829 830
bool MemoryOptimizer::NeedsPoisoning(LoadSensitivity load_sensitivity) const {
  // Safe loads do not need poisoning.
  if (load_sensitivity == LoadSensitivity::kSafe) return false;

  switch (poisoning_level_) {
    case PoisoningMitigationLevel::kDontPoison:
      return false;
    case PoisoningMitigationLevel::kPoisonAll:
      return true;
    case PoisoningMitigationLevel::kPoisonCriticalOnly:
      return load_sensitivity == LoadSensitivity::kCritical;
  }
  UNREACHABLE();
}

831 832 833
}  // namespace compiler
}  // namespace internal
}  // namespace v8