| Index: src/compiler/memory-optimizer.cc
|
| diff --git a/src/compiler/memory-optimizer.cc b/src/compiler/memory-optimizer.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..59fd8994053c6dd23caeca33eee7dc318e0cb063
|
| --- /dev/null
|
| +++ b/src/compiler/memory-optimizer.cc
|
| @@ -0,0 +1,494 @@
|
| +// 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"
|
| +
|
| +#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"
|
| +
|
| +namespace v8 {
|
| +namespace internal {
|
| +namespace compiler {
|
| +
|
| +MemoryOptimizer::MemoryOptimizer(JSGraph* jsgraph, Zone* zone)
|
| + : jsgraph_(jsgraph),
|
| + empty_state_(AllocationState::Empty(zone)),
|
| + pending_(zone),
|
| + tokens_(zone),
|
| + zone_(zone) {}
|
| +
|
| +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,
|
| + PretenureFlag pretenure,
|
| + Zone* zone)
|
| + : node_ids_(zone), pretenure_(pretenure), size_(nullptr) {
|
| + node_ids_.insert(node->id());
|
| +}
|
| +
|
| +MemoryOptimizer::AllocationGroup::AllocationGroup(Node* node,
|
| + PretenureFlag pretenure,
|
| + Node* size, Zone* zone)
|
| + : node_ids_(zone), pretenure_(pretenure), size_(size) {
|
| + node_ids_.insert(node->id());
|
| +}
|
| +
|
| +void MemoryOptimizer::AllocationGroup::Add(Node* node) {
|
| + node_ids_.insert(node->id());
|
| +}
|
| +
|
| +bool MemoryOptimizer::AllocationGroup::Contains(Node* node) const {
|
| + return node_ids_.find(node->id()) != node_ids_.end();
|
| +}
|
| +
|
| +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,
|
| + int size, Node* top)
|
| + : group_(group), size_(size), top_(top) {}
|
| +
|
| +bool MemoryOptimizer::AllocationState::IsNewSpaceAllocation() const {
|
| + return group() && group()->IsNewSpaceAllocation();
|
| +}
|
| +
|
| +void MemoryOptimizer::VisitNode(Node* node, AllocationState const* state) {
|
| + DCHECK(!node->IsDead());
|
| + DCHECK_LT(0, node->op()->EffectInputCount());
|
| + switch (node->opcode()) {
|
| + case IrOpcode::kAllocate:
|
| + return VisitAllocate(node, state);
|
| + case IrOpcode::kCall:
|
| + return VisitCall(node, state);
|
| + case IrOpcode::kLoadElement:
|
| + return VisitLoadElement(node, state);
|
| + case IrOpcode::kLoadField:
|
| + return VisitLoadField(node, state);
|
| + case IrOpcode::kStoreElement:
|
| + return VisitStoreElement(node, state);
|
| + case IrOpcode::kStoreField:
|
| + return VisitStoreField(node, state);
|
| + case IrOpcode::kCheckedLoad:
|
| + case IrOpcode::kCheckedStore:
|
| + case IrOpcode::kIfException:
|
| + case IrOpcode::kLoad:
|
| + case IrOpcode::kStore:
|
| + return VisitOtherEffect(node, state);
|
| + default:
|
| + break;
|
| + }
|
| + DCHECK_EQ(0, node->op()->EffectOutputCount());
|
| +}
|
| +
|
| +void MemoryOptimizer::VisitAllocate(Node* node, AllocationState const* state) {
|
| + DCHECK_EQ(IrOpcode::kAllocate, node->opcode());
|
| + Node* value;
|
| + Node* size = node->InputAt(0);
|
| + Node* effect = node->InputAt(1);
|
| + Node* control = node->InputAt(2);
|
| + PretenureFlag pretenure = OpParameter<PretenureFlag>(node->op());
|
| +
|
| + // Determine the top/limit addresses.
|
| + Node* top_address = jsgraph()->ExternalConstant(
|
| + pretenure == NOT_TENURED
|
| + ? ExternalReference::new_space_allocation_top_address(isolate())
|
| + : ExternalReference::old_space_allocation_top_address(isolate()));
|
| + Node* limit_address = jsgraph()->ExternalConstant(
|
| + pretenure == NOT_TENURED
|
| + ? 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}.
|
| + Int32Matcher m(size);
|
| + if (m.HasValue() && m.Value() < Page::kMaxRegularHeapObjectSize) {
|
| + int32_t const object_size = m.Value();
|
| + if (state->size() <= Page::kMaxRegularHeapObjectSize - object_size &&
|
| + state->group()->pretenure() == pretenure) {
|
| + // We can fold this Allocate {node} into the allocation {group}
|
| + // represented by the given {state}. Compute the upper bound for
|
| + // the new {state}.
|
| + int32_t const state_size = state->size() + object_size;
|
| +
|
| + // Update the reservation check to the actual maximum upper bound.
|
| + AllocationGroup* const group = state->group();
|
| + if (OpParameter<int32_t>(group->size()) < state_size) {
|
| + NodeProperties::ChangeOp(group->size(),
|
| + common()->Int32Constant(state_size));
|
| + }
|
| +
|
| + // Update the allocation top with the new object allocation.
|
| + // TODO(bmeurer): Defer writing back top as much as possible.
|
| + Node* top = graph()->NewNode(machine()->IntAdd(), state->top(),
|
| + jsgraph()->IntPtrConstant(object_size));
|
| + effect = graph()->NewNode(
|
| + machine()->Store(StoreRepresentation(
|
| + MachineType::PointerRepresentation(), kNoWriteBarrier)),
|
| + top_address, jsgraph()->IntPtrConstant(0), top, effect, control);
|
| +
|
| + // Compute the effective inner allocated address.
|
| + value = graph()->NewNode(
|
| + machine()->BitcastWordToTagged(),
|
| + graph()->NewNode(machine()->IntAdd(), state->top(),
|
| + jsgraph()->IntPtrConstant(kHeapObjectTag)));
|
| +
|
| + // Extend the allocation {group}.
|
| + group->Add(value);
|
| + state = AllocationState::Open(group, state_size, top, zone());
|
| + } else {
|
| + // Setup a mutable reservation size node; will be patched as we fold
|
| + // additional allocations into this new group.
|
| + Node* size = graph()->NewNode(common()->Int32Constant(object_size));
|
| +
|
| + // Load allocation top and limit.
|
| + Node* top = effect =
|
| + graph()->NewNode(machine()->Load(MachineType::Pointer()), top_address,
|
| + jsgraph()->IntPtrConstant(0), effect, control);
|
| + Node* limit = effect = graph()->NewNode(
|
| + machine()->Load(MachineType::Pointer()), limit_address,
|
| + jsgraph()->IntPtrConstant(0), effect, control);
|
| +
|
| + // Check if we need to collect garbage before we can start bump pointer
|
| + // allocation (always done for folded allocations).
|
| + Node* check = graph()->NewNode(
|
| + machine()->UintLessThan(),
|
| + graph()->NewNode(
|
| + machine()->IntAdd(), top,
|
| + machine()->Is64()
|
| + ? graph()->NewNode(machine()->ChangeInt32ToInt64(), size)
|
| + : size),
|
| + limit);
|
| + Node* branch =
|
| + graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
|
| +
|
| + Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
|
| + Node* etrue = effect;
|
| + Node* vtrue = top;
|
| +
|
| + Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
|
| + Node* efalse = effect;
|
| + Node* vfalse;
|
| + {
|
| + Node* target = pretenure == NOT_TENURED
|
| + ? jsgraph()->AllocateInNewSpaceStubConstant()
|
| + : jsgraph()->AllocateInOldSpaceStubConstant();
|
| + if (!allocate_operator_.is_set()) {
|
| + CallDescriptor* descriptor =
|
| + Linkage::GetAllocateCallDescriptor(graph()->zone());
|
| + allocate_operator_.set(common()->Call(descriptor));
|
| + }
|
| + vfalse = efalse = graph()->NewNode(allocate_operator_.get(), target,
|
| + size, efalse, if_false);
|
| + vfalse = graph()->NewNode(machine()->IntSub(), vfalse,
|
| + jsgraph()->IntPtrConstant(kHeapObjectTag));
|
| + }
|
| +
|
| + control = graph()->NewNode(common()->Merge(2), if_true, if_false);
|
| + effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
|
| + value = graph()->NewNode(
|
| + common()->Phi(MachineType::PointerRepresentation(), 2), vtrue, vfalse,
|
| + control);
|
| +
|
| + // Compute the new top and write it back.
|
| + top = graph()->NewNode(machine()->IntAdd(), value,
|
| + jsgraph()->IntPtrConstant(object_size));
|
| + effect = graph()->NewNode(
|
| + machine()->Store(StoreRepresentation(
|
| + MachineType::PointerRepresentation(), kNoWriteBarrier)),
|
| + top_address, jsgraph()->IntPtrConstant(0), top, effect, control);
|
| +
|
| + // Compute the initial object address.
|
| + value = graph()->NewNode(
|
| + machine()->BitcastWordToTagged(),
|
| + graph()->NewNode(machine()->IntAdd(), value,
|
| + jsgraph()->IntPtrConstant(kHeapObjectTag)));
|
| +
|
| + // Start a new allocation group.
|
| + AllocationGroup* group =
|
| + new (zone()) AllocationGroup(value, pretenure, size, zone());
|
| + state = AllocationState::Open(group, object_size, top, zone());
|
| + }
|
| + } else {
|
| + // Load allocation top and limit.
|
| + Node* top = effect =
|
| + graph()->NewNode(machine()->Load(MachineType::Pointer()), top_address,
|
| + jsgraph()->IntPtrConstant(0), effect, control);
|
| + Node* limit = effect =
|
| + graph()->NewNode(machine()->Load(MachineType::Pointer()), limit_address,
|
| + jsgraph()->IntPtrConstant(0), effect, control);
|
| +
|
| + // Compute the new top.
|
| + Node* new_top = graph()->NewNode(
|
| + machine()->IntAdd(), top,
|
| + machine()->Is64()
|
| + ? graph()->NewNode(machine()->ChangeInt32ToInt64(), size)
|
| + : size);
|
| +
|
| + // Check if we can do bump pointer allocation here.
|
| + Node* check = graph()->NewNode(machine()->UintLessThan(), new_top, limit);
|
| + Node* branch =
|
| + graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
|
| +
|
| + Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
|
| + Node* etrue = effect;
|
| + Node* vtrue;
|
| + {
|
| + etrue = graph()->NewNode(
|
| + machine()->Store(StoreRepresentation(
|
| + MachineType::PointerRepresentation(), kNoWriteBarrier)),
|
| + top_address, jsgraph()->IntPtrConstant(0), new_top, etrue, if_true);
|
| + vtrue = graph()->NewNode(
|
| + machine()->BitcastWordToTagged(),
|
| + graph()->NewNode(machine()->IntAdd(), top,
|
| + jsgraph()->IntPtrConstant(kHeapObjectTag)));
|
| + }
|
| +
|
| + Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
|
| + Node* efalse = effect;
|
| + Node* vfalse;
|
| + {
|
| + Node* target = pretenure == NOT_TENURED
|
| + ? jsgraph()->AllocateInNewSpaceStubConstant()
|
| + : jsgraph()->AllocateInOldSpaceStubConstant();
|
| + if (!allocate_operator_.is_set()) {
|
| + CallDescriptor* descriptor =
|
| + Linkage::GetAllocateCallDescriptor(graph()->zone());
|
| + allocate_operator_.set(common()->Call(descriptor));
|
| + }
|
| + vfalse = efalse = graph()->NewNode(allocate_operator_.get(), target, size,
|
| + efalse, if_false);
|
| + }
|
| +
|
| + control = graph()->NewNode(common()->Merge(2), if_true, if_false);
|
| + effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
|
| + value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
|
| + vtrue, vfalse, control);
|
| +
|
| + // Create an unfoldable allocation group.
|
| + AllocationGroup* group =
|
| + new (zone()) AllocationGroup(value, pretenure, zone());
|
| + state = AllocationState::Closed(group, zone());
|
| + }
|
| +
|
| + // 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);
|
| + } else {
|
| + DCHECK(NodeProperties::IsValueEdge(edge));
|
| + edge.UpdateTo(value);
|
| + }
|
| + }
|
| +
|
| + // Kill the {node} to make sure we don't leave dangling dead uses.
|
| + node->Kill();
|
| +}
|
| +
|
| +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);
|
| +}
|
| +
|
| +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));
|
| + NodeProperties::ChangeOp(node, machine()->Load(access.machine_type));
|
| + 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);
|
| + NodeProperties::ChangeOp(node, machine()->Load(access.machine_type));
|
| + 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);
|
| + WriteBarrierKind write_barrier_kind =
|
| + ComputeWriteBarrierKind(object, state, access.write_barrier_kind);
|
| + 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);
|
| + WriteBarrierKind write_barrier_kind =
|
| + ComputeWriteBarrierKind(object, state, access.write_barrier_kind);
|
| + 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);
|
| +}
|
| +
|
| +void MemoryOptimizer::VisitOtherEffect(Node* node,
|
| + AllocationState const* state) {
|
| + EnqueueUses(node, state);
|
| +}
|
| +
|
| +Node* MemoryOptimizer::ComputeIndex(ElementAccess const& access, Node* key) {
|
| + Node* index = key;
|
| + int element_size_shift =
|
| + ElementSizeLog2Of(access.machine_type.representation());
|
| + if (element_size_shift) {
|
| + index = graph()->NewNode(machine()->Word32Shl(), index,
|
| + jsgraph()->Int32Constant(element_size_shift));
|
| + }
|
| + const int fixed_offset = access.header_size - access.tag();
|
| + if (fixed_offset) {
|
| + index = graph()->NewNode(machine()->Int32Add(), index,
|
| + jsgraph()->Int32Constant(fixed_offset));
|
| + }
|
| + if (machine()->Is64()) {
|
| + // TODO(turbofan): This is probably only correct for typed arrays, and only
|
| + // if the typed arrays are at most 2GiB in size, which happens to match
|
| + // exactly our current situation.
|
| + index = graph()->NewNode(machine()->ChangeUint32ToUint64(), index);
|
| + }
|
| + return index;
|
| +}
|
| +
|
| +WriteBarrierKind MemoryOptimizer::ComputeWriteBarrierKind(
|
| + Node* object, AllocationState const* state,
|
| + WriteBarrierKind write_barrier_kind) {
|
| + if (state->IsNewSpaceAllocation() && state->group()->Contains(object)) {
|
| + write_barrier_kind = kNoWriteBarrier;
|
| + }
|
| + 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) {
|
| + // For loops we always start with an empty state at the beginning.
|
| + if (index == 0) EnqueueUses(node, empty_state());
|
| + } 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();
|
| +}
|
| +
|
| +} // namespace compiler
|
| +} // namespace internal
|
| +} // namespace v8
|
|
|