[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}
This commit is contained in:
parent
55d05404b7
commit
ca3abde2fa
2
BUILD.gn
2
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",
|
||||
|
@ -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);
|
||||
|
200
src/compiler/liveness-analyzer.cc
Normal file
200
src/compiler/liveness-analyzer.cc
Normal file
@ -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
|
146
src/compiler/liveness-analyzer.h
Normal file
146
src/compiler/liveness-analyzer.h
Normal file
@ -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_
|
@ -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")
|
||||
|
373
test/unittests/compiler/liveness-analyzer-unittest.cc
Normal file
373
test/unittests/compiler/liveness-analyzer-unittest.cc
Normal file
@ -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
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user