Commit ca3abde2 authored by jarin's avatar jarin Committed by Commit bot

[turbofan] Variable liveness analysis for deopt.

This change introduces a liveness analyzer for local variables in frame states.

The main idea is to use the AstGraphBuilder::Environment class to build the control flow graph, and record local variable loads, stores and checkpoints in the CFG basic blocks (LivenessAnalyzerBlock class).

After the graph building finishes, we run a simple data flow analysis over the CFG to figure out liveness of each local variable at each checkpoint. Finally, we run a pass over all the checkpoints and replace dead local variables in the frame states with the 'undefined' value.

Performance numbers for Embenchen are below.

----------- box2d.js
Current --turbo-deoptimization: EmbenchenBox2d(RunTime): 11265 ms.
d8-master --turbo-deoptimization: EmbenchenBox2d(RunTime): 11768 ms.
d8-master: EmbenchenBox2d(RunTime): 10996 ms.
----------- bullet.js
Current --turbo-deoptimization: EmbenchenBullet(RunTime): 17049 ms.
d8-master --turbo-deoptimization: EmbenchenBullet(RunTime): 17384 ms.
d8-master: EmbenchenBullet(RunTime): 16153 ms.
----------- copy.js
Current --turbo-deoptimization: EmbenchenCopy(RunTime): 4877 ms.
d8-master --turbo-deoptimization: EmbenchenCopy(RunTime): 4938 ms.
d8-master: EmbenchenCopy(RunTime): 4940 ms.
----------- corrections.js
Current --turbo-deoptimization: EmbenchenCorrections(RunTime): 7068 ms.
d8-master --turbo-deoptimization: EmbenchenCorrections(RunTime): 6718 ms.
d8-master: EmbenchenCorrections(RunTime): 6858 ms.
----------- fannkuch.js
Current --turbo-deoptimization: EmbenchenFannkuch(RunTime): 4167 ms.
d8-master --turbo-deoptimization: EmbenchenFannkuch(RunTime): 4608 ms.
d8-master: EmbenchenFannkuch(RunTime): 4149 ms.
----------- fasta.js
Current --turbo-deoptimization: EmbenchenFasta(RunTime): 9981 ms.
d8-master --turbo-deoptimization: EmbenchenFasta(RunTime): 9848 ms.
d8-master: EmbenchenFasta(RunTime): 9640 ms.
----------- lua_binarytrees.js
Current --turbo-deoptimization: EmbenchenLuaBinaryTrees(RunTime): 11571 ms.
d8-master --turbo-deoptimization: EmbenchenLuaBinaryTrees(RunTime): 13089 ms.
d8-master: EmbenchenLuaBinaryTrees(RunTime): 10957 ms.
----------- memops.js
Current --turbo-deoptimization: EmbenchenMemOps(RunTime): 7766 ms.
d8-master --turbo-deoptimization: EmbenchenMemOps(RunTime): 7346 ms.
d8-master: EmbenchenMemOps(RunTime): 7738 ms.
----------- primes.js
Current --turbo-deoptimization: EmbenchenPrimes(RunTime): 7459 ms.
d8-master --turbo-deoptimization: EmbenchenPrimes(RunTime): 7453 ms.
d8-master: EmbenchenPrimes(RunTime): 7451 ms.
----------- skinning.js
Current --turbo-deoptimization: EmbenchenSkinning(RunTime): 15564 ms.
d8-master --turbo-deoptimization: EmbenchenSkinning(RunTime): 15611 ms.
d8-master: EmbenchenSkinning(RunTime): 15583 ms.
----------- zlib.js
Current --turbo-deoptimization: EmbenchenZLib(RunTime): 10825 ms.
d8-master --turbo-deoptimization: EmbenchenZLib(RunTime): 11180 ms.
d8-master: EmbenchenZLib(RunTime): 10823 ms.

BUG=

Review URL: https://codereview.chromium.org/949743002

Cr-Commit-Position: refs/heads/master@{#27232}
parent 55d05404
......@@ -594,6 +594,8 @@ source_set("v8_base") {
"src/compiler/linkage-impl.h",
"src/compiler/linkage.cc",
"src/compiler/linkage.h",
"src/compiler/liveness-analyzer.cc",
"src/compiler/liveness-analyzer.h",
"src/compiler/load-elimination.cc",
"src/compiler/load-elimination.h",
"src/compiler/loop-peeling.cc",
......
......@@ -8,6 +8,7 @@
#include "src/compiler/ast-loop-assignment-analyzer.h"
#include "src/compiler/control-builders.h"
#include "src/compiler/linkage.h"
#include "src/compiler/liveness-analyzer.h"
#include "src/compiler/machine-operator.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/node-properties.h"
......@@ -394,7 +395,9 @@ AstGraphBuilder::AstGraphBuilder(Zone* local_zone, CompilationInfo* info,
input_buffer_(nullptr),
exit_control_(nullptr),
loop_assignment_analysis_(loop),
state_values_cache_(jsgraph) {
state_values_cache_(jsgraph),
liveness_analyzer_(static_cast<size_t>(info->scope()->num_stack_slots()),
local_zone) {
InitializeAstVisitor(info->isolate(), local_zone);
}
......@@ -480,6 +483,10 @@ bool AstGraphBuilder::CreateGraph(bool constant_context, bool stack_check) {
// Finish the basic structure of the graph.
graph()->SetEnd(graph()->NewNode(common()->End(), exit_control()));
// Compute local variable liveness information and use it to relax
// frame states.
ClearNonLiveSlotsInFrameStates();
// Failures indicated by stack overflow.
return !HasStackOverflow();
}
......@@ -530,6 +537,24 @@ void AstGraphBuilder::CreateGraphBody(bool stack_check) {
}
void AstGraphBuilder::ClearNonLiveSlotsInFrameStates() {
if (!FLAG_analyze_environment_liveness) return;
NonLiveFrameStateSlotReplacer replacer(
&state_values_cache_, jsgraph()->UndefinedConstant(),
liveness_analyzer()->local_count(), local_zone());
Variable* arguments = info()->scope()->arguments();
if (arguments != nullptr && arguments->IsStackAllocated()) {
replacer.MarkPermanentlyLive(arguments->index());
}
liveness_analyzer()->Run(&replacer);
if (FLAG_trace_environment_liveness) {
OFStream os(stdout);
liveness_analyzer()->Print(os);
}
}
// Left-hand side can only be a property, a global or a variable slot.
enum LhsKind { VARIABLE, NAMED_PROPERTY, KEYED_PROPERTY };
......@@ -552,6 +577,7 @@ AstGraphBuilder::Environment::Environment(AstGraphBuilder* builder,
: builder_(builder),
parameters_count_(scope->num_parameters() + 1),
locals_count_(scope->num_stack_slots()),
liveness_block_(builder_->liveness_analyzer()->NewBlock()),
values_(builder_->local_zone()),
contexts_(builder_->local_zone()),
control_dependency_(control_dependency),
......@@ -580,8 +606,7 @@ AstGraphBuilder::Environment::Environment(AstGraphBuilder* builder,
}
AstGraphBuilder::Environment::Environment(
const AstGraphBuilder::Environment* copy)
AstGraphBuilder::Environment::Environment(AstGraphBuilder::Environment* copy)
: builder_(copy->builder_),
parameters_count_(copy->parameters_count_),
locals_count_(copy->locals_count_),
......@@ -598,6 +623,65 @@ AstGraphBuilder::Environment::Environment(
contexts_.reserve(copy->contexts_.size());
contexts_.insert(contexts_.begin(), copy->contexts_.begin(),
copy->contexts_.end());
if (FLAG_analyze_environment_liveness) {
// Split the liveness blocks.
copy->liveness_block_ =
builder_->liveness_analyzer()->NewBlock(copy->liveness_block());
liveness_block_ =
builder_->liveness_analyzer()->NewBlock(copy->liveness_block());
}
}
void AstGraphBuilder::Environment::Bind(Variable* variable, Node* node) {
DCHECK(variable->IsStackAllocated());
if (variable->IsParameter()) {
// The parameter indices are shifted by 1 (receiver is parameter
// index -1 but environment index 0).
values()->at(variable->index() + 1) = node;
} else {
DCHECK(variable->IsStackLocal());
values()->at(variable->index() + parameters_count_) = node;
if (FLAG_analyze_environment_liveness) {
liveness_block()->Bind(variable->index());
}
}
}
Node* AstGraphBuilder::Environment::Lookup(Variable* variable) {
DCHECK(variable->IsStackAllocated());
if (variable->IsParameter()) {
// The parameter indices are shifted by 1 (receiver is parameter
// index -1 but environment index 0).
return values()->at(variable->index() + 1);
} else {
DCHECK(variable->IsStackLocal());
if (FLAG_analyze_environment_liveness) {
liveness_block()->Lookup(variable->index());
}
return values()->at(variable->index() + parameters_count_);
}
}
void AstGraphBuilder::Environment::MarkAllLocalsLive() {
if (FLAG_analyze_environment_liveness) {
for (int i = 0; i < locals_count_; i++) {
liveness_block()->Lookup(i);
}
}
}
AstGraphBuilder::Environment*
AstGraphBuilder::Environment::CopyAndShareLiveness() {
Environment* env = new (zone()) Environment(this);
if (FLAG_analyze_environment_liveness) {
env->liveness_block_ = liveness_block();
}
return env;
}
......@@ -642,9 +726,13 @@ Node* AstGraphBuilder::Environment::Checkpoint(
const Operator* op = common()->FrameState(JS_FRAME, ast_id, combine);
return graph()->NewNode(op, parameters_node_, locals_node_, stack_node_,
builder()->current_context(),
builder()->jsgraph()->UndefinedConstant());
Node* result = graph()->NewNode(op, parameters_node_, locals_node_,
stack_node_, builder()->current_context(),
builder()->jsgraph()->UndefinedConstant());
if (FLAG_analyze_environment_liveness) {
liveness_block()->Checkpoint(result);
}
return result;
}
......@@ -1333,6 +1421,7 @@ void AstGraphBuilder::VisitDebuggerStatement(DebuggerStatement* stmt) {
// TODO(turbofan): Do we really need a separate reloc-info for this?
Node* node = NewNode(javascript()->CallRuntime(Runtime::kDebugBreak, 0));
PrepareFrameState(node, stmt->DebugBreakId());
environment()->MarkAllLocalsLive();
}
......@@ -3174,6 +3263,7 @@ void AstGraphBuilder::Environment::Merge(Environment* other) {
if (this->IsMarkedAsUnreachable()) {
Node* other_control = other->control_dependency_;
Node* inputs[] = {other_control};
liveness_block_ = other->liveness_block_;
control_dependency_ =
graph()->NewNode(common()->Merge(1), arraysize(inputs), inputs, true);
effect_dependency_ = other->effect_dependency_;
......@@ -3185,6 +3275,18 @@ void AstGraphBuilder::Environment::Merge(Environment* other) {
return;
}
// Record the merge for the local variable liveness calculation.
// Unfortunately, we have to mirror the logic in the MergeControl method:
// connect before merge or loop, or create a new merge otherwise.
if (FLAG_analyze_environment_liveness) {
if (GetControlDependency()->opcode() != IrOpcode::kLoop &&
GetControlDependency()->opcode() != IrOpcode::kMerge) {
liveness_block_ =
builder_->liveness_analyzer()->NewBlock(liveness_block());
}
liveness_block()->AddPredecessor(other->liveness_block());
}
// Create a merge of the control dependencies of both environments and update
// the current environment's control dependency accordingly.
Node* control = builder_->MergeControl(this->GetControlDependency(),
......
......@@ -7,6 +7,7 @@
#include "src/ast.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/liveness-analyzer.h"
#include "src/compiler/state-values-utils.h"
namespace v8 {
......@@ -101,6 +102,9 @@ class AstGraphBuilder : public AstVisitor {
// Cache for StateValues nodes for frame states.
StateValuesCache state_values_cache_;
// Analyzer of local variable liveness.
LivenessAnalyzer liveness_analyzer_;
// Growth increment for the temporary buffer used to construct input lists to
// new nodes.
static const int kInputBufferSizeIncrement = 64;
......@@ -121,6 +125,7 @@ class AstGraphBuilder : public AstVisitor {
Scope* current_scope() const;
Node* current_context() const;
Node* exit_control() const { return exit_control_; }
LivenessAnalyzer* liveness_analyzer() { return &liveness_analyzer_; }
void set_environment(Environment* env) { environment_ = env; }
void set_ast_context(AstContext* ctx) { ast_context_ = ctx; }
......@@ -212,6 +217,10 @@ class AstGraphBuilder : public AstVisitor {
// If so, record the stack height into the compilation and return {true}.
bool CheckOsrEntry(IterationStatement* stmt);
// Computes local variable liveness and replaces dead variables in
// frame states with the undefined values.
void ClearNonLiveSlotsInFrameStates();
// Helper to wrap a Handle<T> into a Unique<T>.
template <class T>
Unique<T> MakeUnique(Handle<T> object) {
......@@ -372,26 +381,10 @@ class AstGraphBuilder::Environment : public ZoneObject {
locals_count_;
}
// Operations on parameter or local variables. The parameter indices are
// shifted by 1 (receiver is parameter index -1 but environment index 0).
void Bind(Variable* variable, Node* node) {
DCHECK(variable->IsStackAllocated());
if (variable->IsParameter()) {
values()->at(variable->index() + 1) = node;
} else {
DCHECK(variable->IsStackLocal());
values()->at(variable->index() + parameters_count_) = node;
}
}
Node* Lookup(Variable* variable) {
DCHECK(variable->IsStackAllocated());
if (variable->IsParameter()) {
return values()->at(variable->index() + 1);
} else {
DCHECK(variable->IsStackLocal());
return values()->at(variable->index() + parameters_count_);
}
}
// Operations on parameter or local variables.
void Bind(Variable* variable, Node* node);
Node* Lookup(Variable* variable);
void MarkAllLocalsLive();
Node* Context() const { return contexts_.back(); }
void PushContext(Node* context) { contexts()->push_back(context); }
......@@ -474,7 +467,7 @@ class AstGraphBuilder::Environment : public ZoneObject {
// Copies this environment at a loop header control-flow point.
Environment* CopyForLoop(BitVector* assigned, bool is_osr = false) {
PrepareForLoop(assigned, is_osr);
return Copy();
return CopyAndShareLiveness();
}
int ContextStackDepth() { return static_cast<int>(contexts_.size()); }
......@@ -483,6 +476,7 @@ class AstGraphBuilder::Environment : public ZoneObject {
AstGraphBuilder* builder_;
int parameters_count_;
int locals_count_;
LivenessAnalyzerBlock* liveness_block_;
NodeVector values_;
NodeVector contexts_;
Node* control_dependency_;
......@@ -491,8 +485,9 @@ class AstGraphBuilder::Environment : public ZoneObject {
Node* locals_node_;
Node* stack_node_;
explicit Environment(const Environment* copy);
explicit Environment(Environment* copy);
Environment* Copy() { return new (zone()) Environment(this); }
Environment* CopyAndShareLiveness();
void UpdateStateValues(Node** state_values, int offset, int count);
void UpdateStateValuesWithCache(Node** state_values, int offset, int count);
Zone* zone() const { return builder_->local_zone(); }
......@@ -501,6 +496,7 @@ class AstGraphBuilder::Environment : public ZoneObject {
CommonOperatorBuilder* common() { return builder_->common(); }
NodeVector* values() { return &values_; }
NodeVector* contexts() { return &contexts_; }
LivenessAnalyzerBlock* liveness_block() { return liveness_block_; }
// Prepare environment to be used as loop header.
void PrepareForLoop(BitVector* assigned, bool is_osr = false);
......
// Copyright 2014 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/liveness-analyzer.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/node.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/state-values-utils.h"
namespace v8 {
namespace internal {
namespace compiler {
LivenessAnalyzer::LivenessAnalyzer(size_t local_count, Zone* zone)
: zone_(zone), blocks_(zone), local_count_(local_count), queue_(zone) {}
void LivenessAnalyzer::Print(std::ostream& os) {
for (auto block : blocks_) {
block->Print(os);
os << std::endl;
}
}
LivenessAnalyzerBlock* LivenessAnalyzer::NewBlock() {
LivenessAnalyzerBlock* result =
new (zone()->New(sizeof(LivenessAnalyzerBlock)))
LivenessAnalyzerBlock(blocks_.size(), local_count_, zone());
blocks_.push_back(result);
return result;
}
LivenessAnalyzerBlock* LivenessAnalyzer::NewBlock(
LivenessAnalyzerBlock* predecessor) {
LivenessAnalyzerBlock* result = NewBlock();
result->AddPredecessor(predecessor);
return result;
}
void LivenessAnalyzer::Queue(LivenessAnalyzerBlock* block) {
if (!block->IsQueued()) {
block->SetQueued();
queue_.push(block);
}
}
void LivenessAnalyzer::Run(NonLiveFrameStateSlotReplacer* replacer) {
if (local_count_ == 0) {
// No local variables => nothing to do.
return;
}
// Put all blocks into the queue.
DCHECK(queue_.empty());
for (auto block : blocks_) {
Queue(block);
}
// Compute the fix-point.
BitVector working_area(static_cast<int>(local_count_), zone_);
while (!queue_.empty()) {
LivenessAnalyzerBlock* block = queue_.front();
queue_.pop();
block->Process(&working_area, nullptr);
for (auto i = block->pred_begin(); i != block->pred_end(); i++) {
if ((*i)->UpdateLive(&working_area)) {
Queue(*i);
}
}
}
// Update the frame states according to the liveness.
for (auto block : blocks_) {
block->Process(&working_area, replacer);
}
}
LivenessAnalyzerBlock::LivenessAnalyzerBlock(size_t id, size_t local_count,
Zone* zone)
: entries_(zone),
predecessors_(zone),
live_(local_count == 0 ? 1 : static_cast<int>(local_count), zone),
queued_(false),
id_(id) {}
void LivenessAnalyzerBlock::Process(BitVector* result,
NonLiveFrameStateSlotReplacer* replacer) {
queued_ = false;
// Copy the bitvector to the target bit vector.
result->CopyFrom(live_);
for (auto i = entries_.rbegin(); i != entries_.rend(); i++) {
auto entry = *i;
switch (entry.kind()) {
case Entry::kLookup:
result->Add(entry.var());
break;
case Entry::kBind:
result->Remove(entry.var());
break;
case Entry::kCheckpoint:
if (replacer != nullptr) {
replacer->ClearNonLiveFrameStateSlots(entry.node(), result);
}
break;
}
}
}
bool LivenessAnalyzerBlock::UpdateLive(BitVector* working_area) {
return live_.UnionIsChanged(*working_area);
}
void NonLiveFrameStateSlotReplacer::ClearNonLiveFrameStateSlots(
Node* frame_state, BitVector* liveness) {
DCHECK_EQ(frame_state->opcode(), IrOpcode::kFrameState);
Node* locals_state = frame_state->InputAt(1);
DCHECK_EQ(locals_state->opcode(), IrOpcode::kStateValues);
int count = static_cast<int>(StateValuesAccess(locals_state).size());
DCHECK_EQ(count == 0 ? 1 : count, liveness->length());
for (int i = 0; i < count; i++) {
bool live = liveness->Contains(i) || permanently_live_.Contains(i);
if (!live || locals_state->InputAt(i) != replacement_node_) {
Node* new_values = ClearNonLiveStateValues(locals_state, liveness);
frame_state->ReplaceInput(1, new_values);
break;
}
}
}
Node* NonLiveFrameStateSlotReplacer::ClearNonLiveStateValues(
Node* values, BitVector* liveness) {
DCHECK(inputs_buffer_.empty());
for (Node* node : StateValuesAccess(values)) {
// Index of the next variable is its furure index in the inputs buffer,
// i.e., the buffer's size.
int var = static_cast<int>(inputs_buffer_.size());
bool live = liveness->Contains(var) || permanently_live_.Contains(var);
inputs_buffer_.push_back(live ? node : replacement_node_);
}
Node* result = state_values_cache()->GetNodeForValues(
inputs_buffer_.empty() ? nullptr : &(inputs_buffer_.front()),
inputs_buffer_.size());
inputs_buffer_.clear();
return result;
}
void LivenessAnalyzerBlock::Print(std::ostream& os) {
os << "Block " << id();
bool first = true;
for (LivenessAnalyzerBlock* pred : predecessors_) {
if (!first) {
os << ", ";
} else {
os << "; predecessors: ";
first = false;
}
os << pred->id();
}
os << std::endl;
for (auto entry : entries_) {
os << " ";
switch (entry.kind()) {
case Entry::kLookup:
os << "- Lookup " << entry.var() << std::endl;
break;
case Entry::kBind:
os << "- Bind " << entry.var() << std::endl;
break;
case Entry::kCheckpoint:
os << "- Checkpoint " << entry.node()->id() << std::endl;
break;
}
}
if (live_.length() > 0) {
os << " Live set: ";
for (int i = 0; i < live_.length(); i++) {
os << (live_.Contains(i) ? "L" : ".");
}
os << std::endl;
}
}
} // namespace compiler
} // namespace internal
} // namespace v8
// Copyright 2015 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.
#ifndef V8_COMPILER_LIVENESS_ANAYZER_H_
#define V8_COMPILER_LIVENESS_ANAYZER_H_
#include "src/bit-vector.h"
#include "src/compiler/node.h"
#include "src/zone-containers.h"
namespace v8 {
namespace internal {
namespace compiler {
class LivenessAnalyzerBlock;
class Node;
class StateValuesCache;
class NonLiveFrameStateSlotReplacer {
public:
void ClearNonLiveFrameStateSlots(Node* frame_state, BitVector* liveness);
NonLiveFrameStateSlotReplacer(StateValuesCache* state_values_cache,
Node* replacement, size_t local_count,
Zone* local_zone)
: replacement_node_(replacement),
state_values_cache_(state_values_cache),
local_zone_(local_zone),
permanently_live_(local_count == 0 ? 1 : static_cast<int>(local_count),
local_zone),
inputs_buffer_(local_zone) {}
void MarkPermanentlyLive(int var) { permanently_live_.Add(var); }
private:
Node* ClearNonLiveStateValues(Node* frame_state, BitVector* liveness);
StateValuesCache* state_values_cache() { return state_values_cache_; }
Zone* local_zone() { return local_zone_; }
// Node that replaces dead values.
Node* replacement_node_;
// Reference to state values cache so that we can create state values
// nodes.
StateValuesCache* state_values_cache_;
Zone* local_zone_;
BitVector permanently_live_;
NodeVector inputs_buffer_;
};
class LivenessAnalyzer {
public:
LivenessAnalyzer(size_t local_count, Zone* zone);
LivenessAnalyzerBlock* NewBlock();
LivenessAnalyzerBlock* NewBlock(LivenessAnalyzerBlock* predecessor);
void Run(NonLiveFrameStateSlotReplacer* relaxer);
Zone* zone() { return zone_; }
void Print(std::ostream& os);
size_t local_count() { return local_count_; }
private:
void Queue(LivenessAnalyzerBlock* block);
Zone* zone_;
ZoneDeque<LivenessAnalyzerBlock*> blocks_;
size_t local_count_;
ZoneQueue<LivenessAnalyzerBlock*> queue_;
};
class LivenessAnalyzerBlock {
public:
friend class LivenessAnalyzer;
void Lookup(int var) { entries_.push_back(Entry(Entry::kLookup, var)); }
void Bind(int var) { entries_.push_back(Entry(Entry::kBind, var)); }
void Checkpoint(Node* node) { entries_.push_back(Entry(node)); }
void AddPredecessor(LivenessAnalyzerBlock* b) { predecessors_.push_back(b); }
private:
class Entry {
public:
enum Kind { kBind, kLookup, kCheckpoint };
Kind kind() const { return kind_; }
Node* node() const {
DCHECK(kind() == kCheckpoint);
return node_;
}
int var() const {
DCHECK(kind() != kCheckpoint);
return var_;
}
explicit Entry(Node* node) : kind_(kCheckpoint), var_(-1), node_(node) {}
Entry(Kind kind, int var) : kind_(kind), var_(var), node_(nullptr) {
DCHECK(kind != kCheckpoint);
}
private:
Kind kind_;
int var_;
Node* node_;
};
LivenessAnalyzerBlock(size_t id, size_t local_count, Zone* zone);
void Process(BitVector* result, NonLiveFrameStateSlotReplacer* relaxer);
bool UpdateLive(BitVector* working_area);
void SetQueued() { queued_ = true; }
bool IsQueued() { return queued_; }
ZoneDeque<LivenessAnalyzerBlock*>::const_iterator pred_begin() {
return predecessors_.begin();
}
ZoneDeque<LivenessAnalyzerBlock*>::const_iterator pred_end() {
return predecessors_.end();
}
size_t id() { return id_; }
void Print(std::ostream& os);
ZoneDeque<Entry> entries_;
ZoneDeque<LivenessAnalyzerBlock*> predecessors_;
BitVector live_;
bool queued_;
size_t id_;
};
} // namespace compiler
} // namespace internal
} // namespace v8
#endif // V8_COMPILER_AST_GRAPH_BUILDER_H_
......@@ -170,6 +170,11 @@ Node* StateValuesCache::BuildTree(ValueArrayIterator* it, size_t max_height) {
Node* StateValuesCache::GetNodeForValues(Node** values, size_t count) {
#if DEBUG
for (size_t i = 0; i < count; i++) {
DCHECK_NE(values[i]->opcode(), IrOpcode::kStateValues);
}
#endif
if (count == 0) {
return GetEmptyStateValues();
}
......
......@@ -297,6 +297,8 @@ DEFINE_BOOL(collect_megamorphic_maps_from_stub_cache, true,
"crankshaft harvests type feedback from stub cache")
DEFINE_BOOL(hydrogen_stats, false, "print statistics for hydrogen")
DEFINE_BOOL(trace_check_elimination, false, "trace check elimination phase")
DEFINE_BOOL(trace_environment_liveness, false,
"trace liveness of local variable slots")
DEFINE_BOOL(trace_hydrogen, false, "trace generated hydrogen to file")
DEFINE_STRING(trace_hydrogen_filter, "*", "hydrogen tracing filter")
DEFINE_BOOL(trace_hydrogen_stubs, false, "trace generated hydrogen for stubs")
......
// Copyright 2014 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/js-graph.h"
#include "src/compiler/linkage.h"
#include "src/compiler/liveness-analyzer.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/state-values-utils.h"
#include "test/unittests/compiler/graph-unittest.h"
#include "test/unittests/compiler/node-test-utils.h"
using testing::MakeMatcher;
using testing::MatcherInterface;
using testing::MatchResultListener;
using testing::StringMatchResultListener;
namespace v8 {
namespace internal {
namespace compiler {
class LivenessAnalysisTest : public GraphTest {
public:
explicit LivenessAnalysisTest(int locals_count = 4)
: locals_count_(locals_count),
machine_(zone(), kRepWord32),
javascript_(zone()),
jsgraph_(isolate(), graph(), common(), &javascript_, &machine_),
analyzer_(locals_count, zone()),
empty_values_(graph()->NewNode(common()->StateValues(0), 0, nullptr)),
next_checkpoint_id_(0),
current_block_(nullptr) {}
protected:
JSGraph* jsgraph() { return &jsgraph_; }
LivenessAnalyzer* analyzer() { return &analyzer_; }
void Run() {
StateValuesCache cache(jsgraph());
NonLiveFrameStateSlotReplacer replacer(&cache,
jsgraph()->UndefinedConstant(),
analyzer()->local_count(), zone());
analyzer()->Run(&replacer);
}
Node* Checkpoint() {
int ast_num = next_checkpoint_id_++;
int first_const = intconst_from_bailout_id(ast_num, locals_count_);
const Operator* locals_op = common()->StateValues(locals_count_);
ZoneVector<Node*> local_inputs(locals_count_, nullptr, zone());
for (int i = 0; i < locals_count_; i++) {
local_inputs[i] = jsgraph()->Int32Constant(i + first_const);
}
Node* locals =
graph()->NewNode(locals_op, locals_count_, &local_inputs.front());
const Operator* op = common()->FrameState(
JS_FRAME, BailoutId(ast_num), OutputFrameStateCombine::Ignore());
Node* result = graph()->NewNode(op, empty_values_, locals, empty_values_,
jsgraph()->UndefinedConstant(),
jsgraph()->UndefinedConstant());
current_block_->Checkpoint(result);
return result;
}
void Bind(int var) { current_block()->Bind(var); }
void Lookup(int var) { current_block()->Lookup(var); }
class CheckpointMatcher : public MatcherInterface<Node*> {
public:
explicit CheckpointMatcher(const char* liveness, Node* empty_values,
int locals_count, Node* replacement)
: liveness_(liveness),
empty_values_(empty_values),
locals_count_(locals_count),
replacement_(replacement) {}
void DescribeTo(std::ostream* os) const OVERRIDE {
*os << "is a frame state with '" << liveness_
<< "' liveness, empty "
"parameters and empty expression stack";
}
bool MatchAndExplain(Node* frame_state,
MatchResultListener* listener) const OVERRIDE {
if (frame_state == NULL) {
*listener << "which is NULL";
return false;
}
DCHECK(frame_state->opcode() == IrOpcode::kFrameState);
FrameStateCallInfo state_info =
OpParameter<FrameStateCallInfo>(frame_state);
int ast_num = state_info.bailout_id().ToInt();
int first_const = intconst_from_bailout_id(ast_num, locals_count_);
if (empty_values_ != frame_state->InputAt(0)) {
*listener << "whose parameters are " << frame_state->InputAt(0)
<< " but should have been " << empty_values_ << " (empty)";
return false;
}
if (empty_values_ != frame_state->InputAt(2)) {
*listener << "whose expression stack is " << frame_state->InputAt(2)
<< " but should have been " << empty_values_ << " (empty)";
return false;
}
StateValuesAccess locals(frame_state->InputAt(1));
if (locals_count_ != static_cast<int>(locals.size())) {
*listener << "whose number of locals is " << locals.size()
<< " but should have been " << locals_count_;
return false;
}
int i = 0;
for (Node* value : locals) {
if (liveness_[i] == 'L') {
StringMatchResultListener value_listener;
if (value == replacement_) {
*listener << "whose local #" << i << " was " << value->opcode()
<< " but should have been 'undefined'";
return false;
} else if (!IsInt32Constant(first_const + i)
.MatchAndExplain(value, &value_listener)) {
*listener << "whose local #" << i << " does not match";
if (value_listener.str() != "") {
*listener << ", " << value_listener.str();
}
return false;
}
} else if (liveness_[i] == '.') {
if (value != replacement_) {
*listener << "whose local #" << i << " is " << value
<< " but should have been " << replacement_
<< " (undefined)";
return false;
}
} else {
UNREACHABLE();
}
i++;
}
return true;
}
private:
const char* liveness_;
Node* empty_values_;
int locals_count_;
Node* replacement_;
};
Matcher<Node*> IsCheckpointModuloLiveness(const char* liveness) {
return MakeMatcher(new CheckpointMatcher(liveness, empty_values_,
locals_count_,
jsgraph()->UndefinedConstant()));
}
LivenessAnalyzerBlock* current_block() { return current_block_; }
void set_current_block(LivenessAnalyzerBlock* block) {
current_block_ = block;
}
private:
static int intconst_from_bailout_id(int ast_num, int locals_count) {
return (locals_count + 1) * ast_num + 1;
}
int locals_count_;
MachineOperatorBuilder machine_;
JSOperatorBuilder javascript_;
JSGraph jsgraph_;
LivenessAnalyzer analyzer_;
Node* empty_values_;
int next_checkpoint_id_;
LivenessAnalyzerBlock* current_block_;
};
TEST_F(LivenessAnalysisTest, EmptyBlock) {
set_current_block(analyzer()->NewBlock());
Node* c1 = Checkpoint();
Run();
// Nothing is live.
EXPECT_THAT(c1, IsCheckpointModuloLiveness("...."));
}
TEST_F(LivenessAnalysisTest, SimpleLookup) {
set_current_block(analyzer()->NewBlock());
Node* c1 = Checkpoint();
Lookup(1);
Node* c2 = Checkpoint();
Run();
EXPECT_THAT(c1, IsCheckpointModuloLiveness(".L.."));
EXPECT_THAT(c2, IsCheckpointModuloLiveness("...."));
}
TEST_F(LivenessAnalysisTest, DiamondLookups) {
// Start block.
LivenessAnalyzerBlock* start = analyzer()->NewBlock();
set_current_block(start);
Node* c1_start = Checkpoint();
// First branch.
LivenessAnalyzerBlock* b1 = analyzer()->NewBlock(start);
set_current_block(b1);
Node* c1_b1 = Checkpoint();
Lookup(1);
Node* c2_b1 = Checkpoint();
Lookup(3);
Node* c3_b1 = Checkpoint();
// Second branch.
LivenessAnalyzerBlock* b2 = analyzer()->NewBlock(start);
set_current_block(b2);
Node* c1_b2 = Checkpoint();
Lookup(3);
Node* c2_b2 = Checkpoint();
Lookup(2);
Node* c3_b2 = Checkpoint();
// Merge block.
LivenessAnalyzerBlock* m = analyzer()->NewBlock(b1);
m->AddPredecessor(b2);
set_current_block(m);
Node* c1_m = Checkpoint();
Lookup(0);
Node* c2_m = Checkpoint();
Run();
EXPECT_THAT(c1_start, IsCheckpointModuloLiveness("LLLL"));
EXPECT_THAT(c1_b1, IsCheckpointModuloLiveness("LL.L"));
EXPECT_THAT(c2_b1, IsCheckpointModuloLiveness("L..L"));
EXPECT_THAT(c3_b1, IsCheckpointModuloLiveness("L..."));
EXPECT_THAT(c1_b2, IsCheckpointModuloLiveness("L.LL"));
EXPECT_THAT(c2_b2, IsCheckpointModuloLiveness("L.L."));
EXPECT_THAT(c3_b2, IsCheckpointModuloLiveness("L..."));
EXPECT_THAT(c1_m, IsCheckpointModuloLiveness("L..."));
EXPECT_THAT(c2_m, IsCheckpointModuloLiveness("...."));
}
TEST_F(LivenessAnalysisTest, DiamondLookupsAndBinds) {
// Start block.
LivenessAnalyzerBlock* start = analyzer()->NewBlock();
set_current_block(start);
Node* c1_start = Checkpoint();
Bind(0);
Node* c2_start = Checkpoint();
// First branch.
LivenessAnalyzerBlock* b1 = analyzer()->NewBlock(start);
set_current_block(b1);
Node* c1_b1 = Checkpoint();
Bind(2);
Bind(1);
Node* c2_b1 = Checkpoint();
Bind(3);
Node* c3_b1 = Checkpoint();
// Second branch.
LivenessAnalyzerBlock* b2 = analyzer()->NewBlock(start);
set_current_block(b2);
Node* c1_b2 = Checkpoint();
Lookup(2);
Node* c2_b2 = Checkpoint();
Bind(2);
Bind(3);
Node* c3_b2 = Checkpoint();
// Merge block.
LivenessAnalyzerBlock* m = analyzer()->NewBlock(b1);
m->AddPredecessor(b2);
set_current_block(m);
Node* c1_m = Checkpoint();
Lookup(0);
Lookup(1);
Lookup(2);
Lookup(3);
Node* c2_m = Checkpoint();
Run();
EXPECT_THAT(c1_start, IsCheckpointModuloLiveness(".LL."));
EXPECT_THAT(c2_start, IsCheckpointModuloLiveness("LLL."));
EXPECT_THAT(c1_b1, IsCheckpointModuloLiveness("L..."));
EXPECT_THAT(c2_b1, IsCheckpointModuloLiveness("LLL."));
EXPECT_THAT(c3_b1, IsCheckpointModuloLiveness("LLLL"));
EXPECT_THAT(c1_b2, IsCheckpointModuloLiveness("LLL."));
EXPECT_THAT(c2_b2, IsCheckpointModuloLiveness("LL.."));
EXPECT_THAT(c3_b2, IsCheckpointModuloLiveness("LLLL"));
EXPECT_THAT(c1_m, IsCheckpointModuloLiveness("LLLL"));
EXPECT_THAT(c2_m, IsCheckpointModuloLiveness("...."));
}
TEST_F(LivenessAnalysisTest, SimpleLoop) {
// Start block.
LivenessAnalyzerBlock* start = analyzer()->NewBlock();
set_current_block(start);
Node* c1_start = Checkpoint();
Bind(0);
Bind(1);
Bind(2);
Bind(3);
Node* c2_start = Checkpoint();
// Loop header block.
LivenessAnalyzerBlock* header = analyzer()->NewBlock(start);
set_current_block(header);
Node* c1_header = Checkpoint();
Lookup(0);
Bind(2);
Node* c2_header = Checkpoint();
// Inside-loop block.
LivenessAnalyzerBlock* in_loop = analyzer()->NewBlock(header);
set_current_block(in_loop);
Node* c1_in_loop = Checkpoint();
Bind(0);
Lookup(3);
Node* c2_in_loop = Checkpoint();
// Add back edge.
header->AddPredecessor(in_loop);
// After-loop block.
LivenessAnalyzerBlock* end = analyzer()->NewBlock(header);
set_current_block(end);
Node* c1_end = Checkpoint();
Lookup(1);
Lookup(2);
Node* c2_end = Checkpoint();
Run();
EXPECT_THAT(c1_start, IsCheckpointModuloLiveness("...."));
EXPECT_THAT(c2_start, IsCheckpointModuloLiveness("LL.L"));
EXPECT_THAT(c1_header, IsCheckpointModuloLiveness("LL.L"));
EXPECT_THAT(c2_header, IsCheckpointModuloLiveness(".LLL"));
EXPECT_THAT(c1_in_loop, IsCheckpointModuloLiveness(".L.L"));
EXPECT_THAT(c2_in_loop, IsCheckpointModuloLiveness("LL.L"));
EXPECT_THAT(c1_end, IsCheckpointModuloLiveness(".LL."));
EXPECT_THAT(c2_end, IsCheckpointModuloLiveness("...."));
}
} // namespace compiler
} // namespace internal
} // namespace v8
......@@ -57,6 +57,7 @@
'compiler/js-intrinsic-lowering-unittest.cc',
'compiler/js-operator-unittest.cc',
'compiler/js-typed-lowering-unittest.cc',
'compiler/liveness-analyzer-unittest.cc',
'compiler/load-elimination-unittest.cc',
'compiler/loop-peeling-unittest.cc',
'compiler/machine-operator-reducer-unittest.cc',
......
......@@ -478,6 +478,8 @@
'../../src/compiler/linkage-impl.h',
'../../src/compiler/linkage.cc',
'../../src/compiler/linkage.h',
'../../src/compiler/liveness-analyzer.cc',
'../../src/compiler/liveness-analyzer.h',
'../../src/compiler/load-elimination.cc',
'../../src/compiler/load-elimination.h',
'../../src/compiler/loop-analysis.cc',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment