blob: 07587524a46fe0a8dae40d25b3b03fc6e25b0db3 [file] [log] [blame]
// Copyright 2017 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/escape-analysis.h"
#include "src/codegen/tick-counter.h"
#include "src/compiler/linkage.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/operator-properties.h"
#include "src/compiler/simplified-operator.h"
#include "src/handles/handles-inl.h"
#include "src/init/bootstrapper.h"
#include "src/objects/map-inl.h"
#ifdef DEBUG
#define TRACE(...) \
do { \
if (FLAG_trace_turbo_escape) PrintF(__VA_ARGS__); \
} while (false)
#else
#define TRACE(...)
#endif
namespace v8 {
namespace internal {
namespace compiler {
template <class T>
class Sidetable {
public:
explicit Sidetable(Zone* zone) : map_(zone) {}
T& operator[](const Node* node) {
NodeId id = node->id();
if (id >= map_.size()) {
map_.resize(id + 1);
}
return map_[id];
}
private:
ZoneVector<T> map_;
};
template <class T>
class SparseSidetable {
public:
explicit SparseSidetable(Zone* zone, T def_value = T())
: def_value_(std::move(def_value)), map_(zone) {}
void Set(const Node* node, T value) {
auto iter = map_.find(node->id());
if (iter != map_.end()) {
iter->second = std::move(value);
} else if (value != def_value_) {
map_.insert(iter, std::make_pair(node->id(), std::move(value)));
}
}
const T& Get(const Node* node) const {
auto iter = map_.find(node->id());
return iter != map_.end() ? iter->second : def_value_;
}
private:
T def_value_;
ZoneUnorderedMap<NodeId, T> map_;
};
// Keeps track of the changes to the current node during reduction.
// Encapsulates the current state of the IR graph and the reducer state like
// side-tables. All access to the IR and the reducer state should happen through
// a ReduceScope to ensure that changes and dependencies are tracked and all
// necessary node revisitations happen.
class ReduceScope {
public:
using Reduction = EffectGraphReducer::Reduction;
explicit ReduceScope(Node* node, Reduction* reduction)
: current_node_(node), reduction_(reduction) {}
protected:
Node* current_node() const { return current_node_; }
Reduction* reduction() { return reduction_; }
private:
Node* current_node_;
Reduction* reduction_;
};
// A VariableTracker object keeps track of the values of variables at all points
// of the effect chain and introduces new phi nodes when necessary.
// Initially and by default, variables are mapped to nullptr, which means that
// the variable allocation point does not dominate the current point on the
// effect chain. We map variables that represent uninitialized memory to the
// Dead node to ensure it is not read.
// Unmapped values are impossible by construction, it is indistinguishable if a
// PersistentMap does not contain an element or maps it to the default element.
class VariableTracker {
private:
// The state of all variables at one point in the effect chain.
class State {
public:
using Map = PersistentMap<Variable, Node*>;
explicit State(Zone* zone) : map_(zone) {}
Node* Get(Variable var) const {
CHECK(var != Variable::Invalid());
return map_.Get(var);
}
void Set(Variable var, Node* node) {
CHECK(var != Variable::Invalid());
return map_.Set(var, node);
}
Map::iterator begin() const { return map_.begin(); }
Map::iterator end() const { return map_.end(); }
bool operator!=(const State& other) const { return map_ != other.map_; }
private:
Map map_;
};
public:
VariableTracker(JSGraph* graph, EffectGraphReducer* reducer, Zone* zone);
VariableTracker(const VariableTracker&) = delete;
VariableTracker& operator=(const VariableTracker&) = delete;
Variable NewVariable() { return Variable(next_variable_++); }
Node* Get(Variable var, Node* effect) { return table_.Get(effect).Get(var); }
Zone* zone() { return zone_; }
class Scope : public ReduceScope {
public:
Scope(VariableTracker* tracker, Node* node, Reduction* reduction);
~Scope();
Maybe<Node*> Get(Variable var) {
Node* node = current_state_.Get(var);
if (node && node->opcode() == IrOpcode::kDead) {
// TODO(tebbi): We use {Dead} as a sentinel for uninitialized memory.
// Reading uninitialized memory can only happen in unreachable code. In
// this case, we have to mark the object as escaping to avoid dead nodes
// in the graph. This is a workaround that should be removed once we can
// handle dead nodes everywhere.
return Nothing<Node*>();
}
return Just(node);
}
void Set(Variable var, Node* node) { current_state_.Set(var, node); }
private:
VariableTracker* states_;
State current_state_;
};
private:
State MergeInputs(Node* effect_phi);
Zone* zone_;
JSGraph* graph_;
SparseSidetable<State> table_;
ZoneVector<Node*> buffer_;
EffectGraphReducer* reducer_;
int next_variable_ = 0;
TickCounter* const tick_counter_;
};
// Encapsulates the current state of the escape analysis reducer to preserve
// invariants regarding changes and re-visitation.
class EscapeAnalysisTracker : public ZoneObject {
public:
EscapeAnalysisTracker(JSGraph* jsgraph, EffectGraphReducer* reducer,
Zone* zone)
: virtual_objects_(zone),
replacements_(zone),
variable_states_(jsgraph, reducer, zone),
jsgraph_(jsgraph),
zone_(zone) {}
EscapeAnalysisTracker(const EscapeAnalysisTracker&) = delete;
EscapeAnalysisTracker& operator=(const EscapeAnalysisTracker&) = delete;
class Scope : public VariableTracker::Scope {
public:
Scope(EffectGraphReducer* reducer, EscapeAnalysisTracker* tracker,
Node* node, Reduction* reduction)
: VariableTracker::Scope(&tracker->variable_states_, node, reduction),
tracker_(tracker),
reducer_(reducer) {}
const VirtualObject* GetVirtualObject(Node* node) {
VirtualObject* vobject = tracker_->virtual_objects_.Get(node);
if (vobject) vobject->AddDependency(current_node());
return vobject;
}
// Create or retrieve a virtual object for the current node.
const VirtualObject* InitVirtualObject(int size) {
DCHECK_EQ(IrOpcode::kAllocate, current_node()->opcode());
VirtualObject* vobject = tracker_->virtual_objects_.Get(current_node());
if (vobject) {
CHECK(vobject->size() == size);
} else {
vobject = tracker_->NewVirtualObject(size);
}
if (vobject) vobject->AddDependency(current_node());
vobject_ = vobject;
return vobject;
}
void SetVirtualObject(Node* object) {
vobject_ = tracker_->virtual_objects_.Get(object);
}
void SetEscaped(Node* node) {
if (VirtualObject* object = tracker_->virtual_objects_.Get(node)) {
if (object->HasEscaped()) return;
TRACE("Setting %s#%d to escaped because of use by %s#%d\n",
node->op()->mnemonic(), node->id(),
current_node()->op()->mnemonic(), current_node()->id());
object->SetEscaped();
object->RevisitDependants(reducer_);
}
}
// The inputs of the current node have to be accessed through the scope to
// ensure that they respect the node replacements.
Node* ValueInput(int i) {
return tracker_->ResolveReplacement(
NodeProperties::GetValueInput(current_node(), i));
}
Node* ContextInput() {
return tracker_->ResolveReplacement(
NodeProperties::GetContextInput(current_node()));
}
void SetReplacement(Node* replacement) {
replacement_ = replacement;
vobject_ =
replacement ? tracker_->virtual_objects_.Get(replacement) : nullptr;
if (replacement) {
TRACE("Set %s#%d as replacement.\n", replacement->op()->mnemonic(),
replacement->id());
} else {
TRACE("Set nullptr as replacement.\n");
}
}
void MarkForDeletion() { SetReplacement(tracker_->jsgraph_->Dead()); }
~Scope() {
if (replacement_ != tracker_->replacements_[current_node()] ||
vobject_ != tracker_->virtual_objects_.Get(current_node())) {
reduction()->set_value_changed();
}
tracker_->replacements_[current_node()] = replacement_;
tracker_->virtual_objects_.Set(current_node(), vobject_);
}
private:
EscapeAnalysisTracker* tracker_;
EffectGraphReducer* reducer_;
VirtualObject* vobject_ = nullptr;
Node* replacement_ = nullptr;
};
Node* GetReplacementOf(Node* node) { return replacements_[node]; }
Node* ResolveReplacement(Node* node) {
if (Node* replacement = GetReplacementOf(node)) {
return replacement;
}
return node;
}
private:
friend class EscapeAnalysisResult;
static const size_t kMaxTrackedObjects = 100;
VirtualObject* NewVirtualObject(int size) {
if (next_object_id_ >= kMaxTrackedObjects) return nullptr;
return zone_->New<VirtualObject>(&variable_states_, next_object_id_++,
size);
}
SparseSidetable<VirtualObject*> virtual_objects_;
Sidetable<Node*> replacements_;
VariableTracker variable_states_;
VirtualObject::Id next_object_id_ = 0;
JSGraph* const jsgraph_;
Zone* const zone_;
};
EffectGraphReducer::EffectGraphReducer(
Graph* graph, std::function<void(Node*, Reduction*)> reduce,
TickCounter* tick_counter, Zone* zone)
: graph_(graph),
state_(graph, kNumStates),
revisit_(zone),
stack_(zone),
reduce_(std::move(reduce)),
tick_counter_(tick_counter) {}
void EffectGraphReducer::ReduceFrom(Node* node) {
// Perform DFS and eagerly trigger revisitation as soon as possible.
// A stack element {node, i} indicates that input i of node should be visited
// next.
DCHECK(stack_.empty());
stack_.push({node, 0});
while (!stack_.empty()) {
tick_counter_->TickAndMaybeEnterSafepoint();
Node* current = stack_.top().node;
int& input_index = stack_.top().input_index;
if (input_index < current->InputCount()) {
Node* input = current->InputAt(input_index);
input_index++;
switch (state_.Get(input)) {
case State::kVisited:
// The input is already reduced.
break;
case State::kOnStack:
// The input is on the DFS stack right now, so it will be revisited
// later anyway.
break;
case State::kUnvisited:
case State::kRevisit: {
state_.Set(input, State::kOnStack);
stack_.push({input, 0});
break;
}
}
} else {
stack_.pop();
Reduction reduction;
reduce_(current, &reduction);
for (Edge edge : current->use_edges()) {
// Mark uses for revisitation.
Node* use = edge.from();
if (NodeProperties::IsEffectEdge(edge)) {
if (reduction.effect_changed()) Revisit(use);
} else {
if (reduction.value_changed()) Revisit(use);
}
}
state_.Set(current, State::kVisited);
// Process the revisitation buffer immediately. This improves performance
// of escape analysis. Using a stack for {revisit_} reverses the order in
// which the revisitation happens. This also seems to improve performance.
while (!revisit_.empty()) {
Node* revisit = revisit_.top();
if (state_.Get(revisit) == State::kRevisit) {
state_.Set(revisit, State::kOnStack);
stack_.push({revisit, 0});
}
revisit_.pop();
}
}
}
}
void EffectGraphReducer::Revisit(Node* node) {
if (state_.Get(node) == State::kVisited) {
TRACE(" Queueing for revisit: %s#%d\n", node->op()->mnemonic(),
node->id());
state_.Set(node, State::kRevisit);
revisit_.push(node);
}
}
VariableTracker::VariableTracker(JSGraph* graph, EffectGraphReducer* reducer,
Zone* zone)
: zone_(zone),
graph_(graph),
table_(zone, State(zone)),
buffer_(zone),
reducer_(reducer),
tick_counter_(reducer->tick_counter()) {}
VariableTracker::Scope::Scope(VariableTracker* states, Node* node,
Reduction* reduction)
: ReduceScope(node, reduction),
states_(states),
current_state_(states->zone_) {
switch (node->opcode()) {
case IrOpcode::kEffectPhi:
current_state_ = states_->MergeInputs(node);
break;
default:
int effect_inputs = node->op()->EffectInputCount();
if (effect_inputs == 1) {
current_state_ =
states_->table_.Get(NodeProperties::GetEffectInput(node, 0));
} else {
DCHECK_EQ(0, effect_inputs);
}
}
}
VariableTracker::Scope::~Scope() {
if (!reduction()->effect_changed() &&
states_->table_.Get(current_node()) != current_state_) {
reduction()->set_effect_changed();
}
states_->table_.Set(current_node(), current_state_);
}
VariableTracker::State VariableTracker::MergeInputs(Node* effect_phi) {
// A variable that is mapped to [nullptr] was not assigned a value on every
// execution path to the current effect phi. Relying on the invariant that
// every variable is initialized (at least with a sentinel like the Dead
// node), this means that the variable initialization does not dominate the
// current point. So for loop effect phis, we can keep nullptr for a variable
// as long as the first input of the loop has nullptr for this variable. For
// non-loop effect phis, we can even keep it nullptr as long as any input has
// nullptr.
DCHECK_EQ(IrOpcode::kEffectPhi, effect_phi->opcode());
int arity = effect_phi->op()->EffectInputCount();
Node* control = NodeProperties::GetControlInput(effect_phi, 0);
TRACE("control: %s#%d\n", control->op()->mnemonic(), control->id());
bool is_loop = control->opcode() == IrOpcode::kLoop;
buffer_.reserve(arity + 1);
State first_input = table_.Get(NodeProperties::GetEffectInput(effect_phi, 0));
State result = first_input;
for (std::pair<Variable, Node*> var_value : first_input) {
tick_counter_->TickAndMaybeEnterSafepoint();
if (Node* value = var_value.second) {
Variable var = var_value.first;
TRACE("var %i:\n", var.id_);
buffer_.clear();
buffer_.push_back(value);
bool identical_inputs = true;
int num_defined_inputs = 1;
TRACE(" input 0: %s#%d\n", value->op()->mnemonic(), value->id());
for (int i = 1; i < arity; ++i) {
Node* next_value =
table_.Get(NodeProperties::GetEffectInput(effect_phi, i)).Get(var);
if (next_value != value) identical_inputs = false;
if (next_value != nullptr) {
num_defined_inputs++;
TRACE(" input %i: %s#%d\n", i, next_value->op()->mnemonic(),
next_value->id());
} else {
TRACE(" input %i: nullptr\n", i);
}
buffer_.push_back(next_value);
}
Node* old_value = table_.Get(effect_phi).Get(var);
if (old_value) {
TRACE(" old: %s#%d\n", old_value->op()->mnemonic(), old_value->id());
} else {
TRACE(" old: nullptr\n");
}
// Reuse a previously created phi node if possible.
if (old_value && old_value->opcode() == IrOpcode::kPhi &&
NodeProperties::GetControlInput(old_value, 0) == control) {
// Since a phi node can never dominate its control node,
// [old_value] cannot originate from the inputs. Thus [old_value]
// must have been created by a previous reduction of this [effect_phi].
for (int i = 0; i < arity; ++i) {
Node* old_input = NodeProperties::GetValueInput(old_value, i);
Node* new_input = buffer_[i] ? buffer_[i] : graph_->Dead();
if (old_input != new_input) {
NodeProperties::ReplaceValueInput(old_value, new_input, i);
reducer_->Revisit(old_value);
}
}
result.Set(var, old_value);
} else {
if (num_defined_inputs == 1 && is_loop) {
// For loop effect phis, the variable initialization dominates iff it
// dominates the first input.
DCHECK_EQ(2, arity);
DCHECK_EQ(value, buffer_[0]);
result.Set(var, value);
} else if (num_defined_inputs < arity) {
// If the variable is undefined on some input of this non-loop effect
// phi, then its initialization does not dominate this point.
result.Set(var, nullptr);
} else {
DCHECK_EQ(num_defined_inputs, arity);
// We only create a phi if the values are different.
if (identical_inputs) {
result.Set(var, value);
} else {
TRACE("Creating new phi\n");
buffer_.push_back(control);
Node* phi = graph_->graph()->NewNode(
graph_->common()->Phi(MachineRepresentation::kTagged, arity),
arity + 1, &buffer_.front());
// TODO(tebbi): Computing precise types here is tricky, because of
// the necessary revisitations. If we really need this, we should
// probably do it afterwards.
NodeProperties::SetType(phi, Type::Any());
reducer_->AddRoot(phi);
result.Set(var, phi);
}
}
}
#ifdef DEBUG
if (Node* result_node = result.Get(var)) {
TRACE(" result: %s#%d\n", result_node->op()->mnemonic(),
result_node->id());
} else {
TRACE(" result: nullptr\n");
}
#endif
}
}
return result;
}
namespace {
int OffsetOfFieldAccess(const Operator* op) {
DCHECK(op->opcode() == IrOpcode::kLoadField ||
op->opcode() == IrOpcode::kStoreField);
FieldAccess access = FieldAccessOf(op);
return access.offset;
}
int OffsetOfElementAt(ElementAccess const& access, int index) {
DCHECK_GE(index, 0);
DCHECK_GE(ElementSizeLog2Of(access.machine_type.representation()),
kTaggedSizeLog2);
return access.header_size +
(index << ElementSizeLog2Of(access.machine_type.representation()));
}
Maybe<int> OffsetOfElementsAccess(const Operator* op, Node* index_node) {
DCHECK(op->opcode() == IrOpcode::kLoadElement ||
op->opcode() == IrOpcode::kStoreElement);
Type index_type = NodeProperties::GetType(index_node);
if (!index_type.Is(Type::OrderedNumber())) return Nothing<int>();
double max = index_type.Max();
double min = index_type.Min();
int index = static_cast<int>(min);
if (index < 0 || index != min || index != max) return Nothing<int>();
return Just(OffsetOfElementAt(ElementAccessOf(op), index));
}
Node* LowerCompareMapsWithoutLoad(Node* checked_map,
ZoneHandleSet<Map> const& checked_against,
JSGraph* jsgraph) {
Node* true_node = jsgraph->TrueConstant();
Node* false_node = jsgraph->FalseConstant();
Node* replacement = false_node;
for (Handle<Map> map : checked_against) {
Node* map_node = jsgraph->HeapConstant(map);
// We cannot create a HeapConstant type here as we are off-thread.
NodeProperties::SetType(map_node, Type::Internal());
Node* comparison = jsgraph->graph()->NewNode(
jsgraph->simplified()->ReferenceEqual(), checked_map, map_node);
NodeProperties::SetType(comparison, Type::Boolean());
if (replacement == false_node) {
replacement = comparison;
} else {
replacement = jsgraph->graph()->NewNode(
jsgraph->common()->Select(MachineRepresentation::kTaggedPointer),
comparison, true_node, replacement);
NodeProperties::SetType(replacement, Type::Boolean());
}
}
return replacement;
}
void ReduceNode(const Operator* op, EscapeAnalysisTracker::Scope* current,
JSGraph* jsgraph) {
switch (op->opcode()) {
case IrOpcode::kAllocate: {
NumberMatcher size(current->ValueInput(0));
if (!size.HasResolvedValue()) break;
int size_int = static_cast<int>(size.ResolvedValue());
if (size_int != size.ResolvedValue()) break;
if (const VirtualObject* vobject = current->InitVirtualObject(size_int)) {
// Initialize with dead nodes as a sentinel for uninitialized memory.
for (Variable field : *vobject) {
current->Set(field, jsgraph->Dead());
}
}
break;
}
case IrOpcode::kFinishRegion:
current->SetVirtualObject(current->ValueInput(0));
break;
case IrOpcode::kStoreField: {
Node* object = current->ValueInput(0);
Node* value = current->ValueInput(1);
const VirtualObject* vobject = current->GetVirtualObject(object);
Variable var;
if (vobject && !vobject->HasEscaped() &&
vobject->FieldAt(OffsetOfFieldAccess(op)).To(&var)) {
current->Set(var, value);
current->MarkForDeletion();
} else {
current->SetEscaped(object);
current->SetEscaped(value);
}
break;
}
case IrOpcode::kStoreElement: {
Node* object = current->ValueInput(0);
Node* index = current->ValueInput(1);
Node* value = current->ValueInput(2);
const VirtualObject* vobject = current->GetVirtualObject(object);
int offset;
Variable var;
if (vobject && !vobject->HasEscaped() &&
OffsetOfElementsAccess(op, index).To(&offset) &&
vobject->FieldAt(offset).To(&var)) {
current->Set(var, value);
current->MarkForDeletion();
} else {
current->SetEscaped(value);
current->SetEscaped(object);
}
break;
}
case IrOpcode::kLoadField: {
Node* object = current->ValueInput(0);
const VirtualObject* vobject = current->GetVirtualObject(object);
Variable var;
Node* value;
if (vobject && !vobject->HasEscaped() &&
vobject->FieldAt(OffsetOfFieldAccess(op)).To(&var) &&
current->Get(var).To(&value)) {
current->SetReplacement(value);
} else {
current->SetEscaped(object);
}
break;
}
case IrOpcode::kLoadElement: {
Node* object = current->ValueInput(0);
Node* index = current->ValueInput(1);
const VirtualObject* vobject = current->GetVirtualObject(object);
int offset;
Variable var;
Node* value;
if (vobject && !vobject->HasEscaped() &&
OffsetOfElementsAccess(op, index).To(&offset) &&
vobject->FieldAt(offset).To(&var) && current->Get(var).To(&value)) {
current->SetReplacement(value);
break;
} else if (vobject && !vobject->HasEscaped()) {
// Compute the known length (aka the number of elements) of {object}
// based on the virtual object information.
ElementAccess const& access = ElementAccessOf(op);
int const length =
(vobject->size() - access.header_size) >>
ElementSizeLog2Of(access.machine_type.representation());
Variable var0, var1;
Node* value0;
Node* value1;
if (length == 1 &&
vobject->FieldAt(OffsetOfElementAt(access, 0)).To(&var) &&
current->Get(var).To(&value) &&
(value == nullptr ||
NodeProperties::GetType(value).Is(access.type))) {
// The {object} has no elements, and we know that the LoadElement
// {index} must be within bounds, thus it must always yield this
// one element of {object}.
current->SetReplacement(value);
break;
} else if (length == 2 &&
vobject->FieldAt(OffsetOfElementAt(access, 0)).To(&var0) &&
current->Get(var0).To(&value0) &&
(value0 == nullptr ||
NodeProperties::GetType(value0).Is(access.type)) &&
vobject->FieldAt(OffsetOfElementAt(access, 1)).To(&var1) &&
current->Get(var1).To(&value1) &&
(value1 == nullptr ||
NodeProperties::GetType(value1).Is(access.type))) {
if (value0 && value1) {
// The {object} has exactly two elements, so the LoadElement
// must return one of them (i.e. either the element at index
// 0 or the one at index 1). So we can turn the LoadElement
// into a Select operation instead (still allowing the {object}
// to be scalar replaced). We must however mark the elements
// of the {object} itself as escaping.
Node* check =
jsgraph->graph()->NewNode(jsgraph->simplified()->NumberEqual(),
index, jsgraph->ZeroConstant());
NodeProperties::SetType(check, Type::Boolean());
Node* select = jsgraph->graph()->NewNode(
jsgraph->common()->Select(access.machine_type.representation()),
check, value0, value1);
NodeProperties::SetType(select, access.type);
current->SetReplacement(select);
current->SetEscaped(value0);
current->SetEscaped(value1);
break;
} else {
// If the variables have no values, we have
// not reached the fixed-point yet.
break;
}
}
}
current->SetEscaped(object);
break;
}
case IrOpcode::kTypeGuard: {
current->SetVirtualObject(current->ValueInput(0));
break;
}
case IrOpcode::kReferenceEqual: {
Node* left = current->ValueInput(0);
Node* right = current->ValueInput(1);
const VirtualObject* left_object = current->GetVirtualObject(left);
const VirtualObject* right_object = current->GetVirtualObject(right);
Node* replacement = nullptr;
if (left_object && !left_object->HasEscaped()) {
if (right_object && !right_object->HasEscaped() &&
left_object->id() == right_object->id()) {
replacement = jsgraph->TrueConstant();
} else {
replacement = jsgraph->FalseConstant();
}
} else if (right_object && !right_object->HasEscaped()) {
replacement = jsgraph->FalseConstant();
}
// TODO(tebbi) This is a workaround for uninhabited types. If we
// replaced a value of uninhabited type with a constant, we would
// widen the type of the node. This could produce inconsistent
// types (which might confuse representation selection). We get
// around this by refusing to constant-fold and escape-analyze
// if the type is not inhabited.
if (replacement && !NodeProperties::GetType(left).IsNone() &&
!NodeProperties::GetType(right).IsNone()) {
current->SetReplacement(replacement);
break;
}
current->SetEscaped(left);
current->SetEscaped(right);
break;
}
case IrOpcode::kCheckMaps: {
CheckMapsParameters params = CheckMapsParametersOf(op);
Node* checked = current->ValueInput(0);
const VirtualObject* vobject = current->GetVirtualObject(checked);
Variable map_field;
Node* map;
if (vobject && !vobject->HasEscaped() &&
vobject->FieldAt(HeapObject::kMapOffset).To(&map_field) &&
current->Get(map_field).To(&map)) {
if (map) {
Type const map_type = NodeProperties::GetType(map);
if (map_type.IsHeapConstant() &&
params.maps().contains(
map_type.AsHeapConstant()->Ref().AsMap().object())) {
current->MarkForDeletion();
break;
}
} else {
// If the variable has no value, we have not reached the fixed-point
// yet.
break;
}
}
current->SetEscaped(checked);
break;
}
case IrOpcode::kCompareMaps: {
Node* object = current->ValueInput(0);
const VirtualObject* vobject = current->GetVirtualObject(object);
Variable map_field;
Node* object_map;
if (vobject && !vobject->HasEscaped() &&
vobject->FieldAt(HeapObject::kMapOffset).To(&map_field) &&
current->Get(map_field).To(&object_map)) {
if (object_map) {
current->SetReplacement(LowerCompareMapsWithoutLoad(
object_map, CompareMapsParametersOf(op), jsgraph));
break;
} else {
// If the variable has no value, we have not reached the fixed-point
// yet.
break;
}
}
current->SetEscaped(object);
break;
}
case IrOpcode::kCheckHeapObject: {
Node* checked = current->ValueInput(0);
switch (checked->opcode()) {
case IrOpcode::kAllocate:
case IrOpcode::kFinishRegion:
case IrOpcode::kHeapConstant:
current->SetReplacement(checked);
break;
default:
current->SetEscaped(checked);
break;
}
break;
}
case IrOpcode::kMapGuard: {
Node* object = current->ValueInput(0);
const VirtualObject* vobject = current->GetVirtualObject(object);
if (vobject && !vobject->HasEscaped()) {
current->MarkForDeletion();
}
break;
}
case IrOpcode::kStateValues:
case IrOpcode::kFrameState:
// These uses are always safe.
break;
default: {
// For unknown nodes, treat all value inputs as escaping.
int value_input_count = op->ValueInputCount();
for (int i = 0; i < value_input_count; ++i) {
Node* input = current->ValueInput(i);
current->SetEscaped(input);
}
if (OperatorProperties::HasContextInput(op)) {
current->SetEscaped(current->ContextInput());
}
break;
}
}
}
} // namespace
void EscapeAnalysis::Reduce(Node* node, Reduction* reduction) {
const Operator* op = node->op();
TRACE("Reducing %s#%d\n", op->mnemonic(), node->id());
EscapeAnalysisTracker::Scope current(this, tracker_, node, reduction);
ReduceNode(op, &current, jsgraph());
}
EscapeAnalysis::EscapeAnalysis(JSGraph* jsgraph, TickCounter* tick_counter,
Zone* zone)
: EffectGraphReducer(
jsgraph->graph(),
[this](Node* node, Reduction* reduction) { Reduce(node, reduction); },
tick_counter, zone),
tracker_(zone->New<EscapeAnalysisTracker>(jsgraph, this, zone)),
jsgraph_(jsgraph) {}
Node* EscapeAnalysisResult::GetReplacementOf(Node* node) {
Node* replacement = tracker_->GetReplacementOf(node);
// Replacements cannot have replacements. This is important to ensure
// re-visitation: If a replacement is replaced, then all nodes accessing
// the replacement have to be updated.
if (replacement) DCHECK_NULL(tracker_->GetReplacementOf(replacement));
return replacement;
}
Node* EscapeAnalysisResult::GetVirtualObjectField(const VirtualObject* vobject,
int field, Node* effect) {
return tracker_->variable_states_.Get(vobject->FieldAt(field).FromJust(),
effect);
}
const VirtualObject* EscapeAnalysisResult::GetVirtualObject(Node* node) {
return tracker_->virtual_objects_.Get(node);
}
VirtualObject::VirtualObject(VariableTracker* var_states, VirtualObject::Id id,
int size)
: Dependable(var_states->zone()), id_(id), fields_(var_states->zone()) {
DCHECK(IsAligned(size, kTaggedSize));
TRACE("Creating VirtualObject id:%d size:%d\n", id, size);
int num_fields = size / kTaggedSize;
fields_.reserve(num_fields);
for (int i = 0; i < num_fields; ++i) {
fields_.push_back(var_states->NewVariable());
}
}
#undef TRACE
} // namespace compiler
} // namespace internal
} // namespace v8