#include "src/compiler/escape-analysis.h"
#include "src/codegen/tick-counter.h"
#include "src/compiler/frame-states.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/operator-properties.h"
#include "src/compiler/simplified-operator.h"
#include "src/compiler/state-values-utils.h"
#include "src/deoptimizer/translated-state.h"
#include "src/handles/handles-inl.h"
#include "src/objects/map-inl.h"
#ifdef DEBUG
#define TRACE(...) \
do { \
if (v8_flags.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_;
};
class ReduceScope {
public:
using Reduction = EffectGraphReducer::Reduction;
explicit ReduceScope(Node* node, Reduction* reduction)
: current_node_(node), reduction_(reduction) {}
void SetValueChanged() { reduction()->set_value_changed(); }
protected:
Node* current_node() const { return current_node_; }
Reduction* reduction() { return reduction_; }
private:
Node* current_node_;
Reduction* reduction_;
};
class VariableTracker {
private:
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 V8_NODISCARD 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) {
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_;
};
class EscapeAnalysisTracker : public ZoneObject {
public:
EscapeAnalysisTracker(JSGraph* jsgraph, EffectGraphReducer* reducer,
Zone* zone)
: virtual_objects_(zone),
replacements_(zone),
framestate_might_lazy_deopt_(zone),
variable_states_(jsgraph, reducer, zone),
jsgraph_(jsgraph),
zone_(zone) {}
EscapeAnalysisTracker(const EscapeAnalysisTracker&) = delete;
EscapeAnalysisTracker& operator=(const EscapeAnalysisTracker&) = delete;
class V8_NODISCARD 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;
}
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_);
}
}
Node* ValueInput(int i) {
return tracker_->ResolveReplacement(
NodeProperties::GetValueInput(current_node(), i));
}
Node* ContextInput() {
return tracker_->ResolveReplacement(
NodeProperties::GetContextInput(current_node()));
}
Node* CurrentNode() {
DCHECK_EQ(current_node()->opcode(), IrOpcode::kFrameState);
return 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()); }
bool FrameStateMightLazyDeopt(Node* framestate) {
DCHECK_EQ(IrOpcode::kFrameState, framestate->opcode());
if (auto it = tracker_->framestate_might_lazy_deopt_.find(framestate);
it != tracker_->framestate_might_lazy_deopt_.end()) {
return it->second;
}
for (Node* use : framestate->uses()) {
switch (use->opcode()) {
case IrOpcode::kCheckpoint:
case IrOpcode::kDeoptimize:
case IrOpcode::kDeoptimizeIf:
case IrOpcode::kDeoptimizeUnless:
break;
default:
if (use->opcode() == IrOpcode::kFrameState &&
!FrameStateMightLazyDeopt(use)) {
break;
}
return tracker_->framestate_might_lazy_deopt_[framestate] = true;
}
}
return tracker_->framestate_might_lazy_deopt_[framestate] = false;
}
~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 constexpr int kTrackingBudget = 1300;
VirtualObject* NewVirtualObject(int size) {
if (number_of_tracked_bytes_ + size >= kTrackingBudget) {
if (V8_UNLIKELY(v8_flags.trace_turbo_bailouts)) {
std::cout
<< "Bailing out in Escape Analysis because of kTrackingBudget\n";
}
return nullptr;
}
number_of_tracked_bytes_ += size;
return zone_->New<VirtualObject>(&variable_states_, next_object_id_++,
size);
}
SparseSidetable<VirtualObject*> virtual_objects_;
Sidetable<Node*> replacements_;
ZoneUnorderedMap<Node*, bool> framestate_might_lazy_deopt_;
VariableTracker variable_states_;
VirtualObject::Id next_object_id_ = 0;
int number_of_tracked_bytes_ = 0;
JSGraph* const jsgraph_;
Zone* const zone_;
};
EffectGraphReducer::EffectGraphReducer(
TFGraph* 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) {
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:
break;
case State::kOnStack:
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()) {
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);
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) {
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");
}
if (old_value && old_value->opcode() == IrOpcode::kPhi &&
NodeProperties::GetControlInput(old_value, 0) == control) {
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) {
DCHECK_EQ(2, arity);
DCHECK_EQ(value, buffer_[0]);
result.Set(var, value);
} else if (num_defined_inputs < arity) {
result.Set(var, nullptr);
} else {
DCHECK_EQ(num_defined_inputs, arity);
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());
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;
}
Maybe<int> OffsetOfElementAt(ElementAccess const& access, int index) {
MachineRepresentation representation = access.machine_type.representation();
if (representation == MachineRepresentation::kFloat64) return Nothing<int>();
DCHECK_GE(index, 0);
DCHECK_GE(ElementSizeLog2Of(representation), kTaggedSizeLog2);
return Just(access.header_size +
(index << ElementSizeLog2Of(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 OffsetOfElementAt(ElementAccessOf(op), index);
}
Node* LowerCompareMapsWithoutLoad(Node* checked_map,
ZoneRefSet<Map> const& checked_against,
JSGraph* jsgraph) {
Node* true_node = jsgraph->TrueConstant();
Node* false_node = jsgraph->FalseConstant();
Node* replacement = false_node;
for (MapRef map : checked_against) {
Node* map_node = jsgraph->HeapConstantMaybeHole(map.object());
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;
}
namespace {
bool CheckMapsHelper(EscapeAnalysisTracker::Scope* current, Node* checked,
ZoneRefSet<Map> target) {
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() &&
target.contains(map_type.AsHeapConstant()->Ref().AsMap())) {
current->MarkForDeletion();
return true;
}
} else {
return true;
}
}
return false;
}
}
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)) {
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 (value->opcode() == IrOpcode::kTrustedHeapConstant) {
current->SetEscaped(object);
current->SetEscaped(value);
break;
}
if (vobject && !vobject->HasEscaped() &&
vobject->FieldAt(OffsetOfFieldAccess(op)).To(&var) &&
!FieldAccessOf(op).is_bounded_size_access) {
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()) {
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))) {
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) {
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 {
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();
}
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);
if (CheckMapsHelper(current, checked, params.maps())) {
break;
}
current->SetEscaped(checked);
break;
}
case IrOpcode::kTransitionElementsKindOrCheckMap: {
ElementsTransitionWithMultipleSources params =
ElementsTransitionWithMultipleSourcesOf(op);
Node* checked = current->ValueInput(0);
if (CheckMapsHelper(current, checked, ZoneRefSet<Map>(params.target()))) {
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 {
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:
current->SetValueChanged();
break;
case IrOpcode::kFrameState: {
FrameState frame_state{current->CurrentNode()};
FrameStateType type = frame_state.frame_state_info().type();
if (type != FrameStateType::kUnoptimizedFunction &&
type != FrameStateType::kJavaScriptBuiltinContinuation &&
type != FrameStateType::kJavaScriptBuiltinContinuationWithCatch) {
break;
}
if (!current->FrameStateMightLazyDeopt(current->CurrentNode())) {
break;
}
StateValuesAccess::iterator it =
StateValuesAccess(frame_state.parameters()).begin();
if (!it.done()) {
static_assert(TranslatedFrame::kReceiverIsFirstParameterInJSFrames);
if (Node* receiver = it.node()) {
current->SetEscaped(receiver);
}
current->SetEscaped(frame_state.function());
}
break;
}
default: {
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;
}
}
}
}
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, ¤t, 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);
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
}
}
}