[turbofan] Variable liveness analysis for deopt.
authorjarin <jarin@chromium.org>
Tue, 17 Mar 2015 09:38:37 +0000 (02:38 -0700)
committerCommit bot <commit-bot@chromium.org>
Tue, 17 Mar 2015 09:38:43 +0000 (09:38 +0000)
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}

BUILD.gn
src/compiler/ast-graph-builder.cc
src/compiler/ast-graph-builder.h
src/compiler/liveness-analyzer.cc [new file with mode: 0644]
src/compiler/liveness-analyzer.h [new file with mode: 0644]
src/compiler/state-values-utils.cc
src/flag-definitions.h
test/unittests/compiler/liveness-analyzer-unittest.cc [new file with mode: 0644]
test/unittests/unittests.gyp
tools/gyp/v8.gyp

index 49fd009..509d78a 100644 (file)
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -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",
index 63696e4..00d09f3 100644 (file)
@@ -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(),
index 4739e03..85d4930 100644 (file)
@@ -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);
diff --git a/src/compiler/liveness-analyzer.cc b/src/compiler/liveness-analyzer.cc
new file mode 100644 (file)
index 0000000..8aeb8d5
--- /dev/null
@@ -0,0 +1,200 @@
+// 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
diff --git a/src/compiler/liveness-analyzer.h b/src/compiler/liveness-analyzer.h
new file mode 100644 (file)
index 0000000..1e2f85b
--- /dev/null
@@ -0,0 +1,146 @@
+// 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_
index d5b7a28..2d643d7 100644 (file)
@@ -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();
   }
index d965984..ffc8282 100644 (file)
@@ -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")
diff --git a/test/unittests/compiler/liveness-analyzer-unittest.cc b/test/unittests/compiler/liveness-analyzer-unittest.cc
new file mode 100644 (file)
index 0000000..b59c71a
--- /dev/null
@@ -0,0 +1,373 @@
+// 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
index 6045589..2ed05b8 100644 (file)
@@ -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',
index 750db47..a6ee03a 100644 (file)
         '../../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',