"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",
#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"
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);
}
// 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();
}
}
+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 };
: 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),
}
-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_),
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;
}
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;
}
// 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();
}
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_;
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(),
#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 {
// 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;
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; }
// 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) {
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); }
// 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()); }
AstGraphBuilder* builder_;
int parameters_count_;
int locals_count_;
+ LivenessAnalyzerBlock* liveness_block_;
NodeVector values_;
NodeVector contexts_;
Node* control_dependency_;
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(); }
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);
--- /dev/null
+// 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
--- /dev/null
+// 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_
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();
}
"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")
--- /dev/null
+// 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
'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',
'../../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',