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

[turbofan] Cache for reusing parts of value vector nodes in frame states.

Instead of the current approach of storing flat vectors in frame states (and possibly reusing the last vector in AST graph builder), this change list builds a tree for the values and tries to reuse the nodes for different frame states. At the moment, we only use this for the local variable part of frame state, but nothing prevents us from using this for all parts.

This change provides two new classes: one for creating the tree (StateValuesCache) and one for iterating the trees (StateValuesAccess).

BUG=

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

Cr-Commit-Position: refs/heads/master@{#27222}
parent 3e29f36b
......@@ -653,6 +653,8 @@ source_set("v8_base") {
"src/compiler/simplified-operator.h",
"src/compiler/source-position.cc",
"src/compiler/source-position.h",
"src/compiler/state-values-utils.cc",
"src/compiler/state-values-utils.h",
"src/compiler/typer.cc",
"src/compiler/typer.h",
"src/compiler/value-numbering-reducer.cc",
......
......@@ -12,6 +12,7 @@
#include "src/compiler/node-matchers.h"
#include "src/compiler/node-properties.h"
#include "src/compiler/operator-properties.h"
#include "src/compiler/state-values-utils.h"
#include "src/full-codegen.h"
#include "src/parser.h"
#include "src/scopes.h"
......@@ -392,7 +393,8 @@ AstGraphBuilder::AstGraphBuilder(Zone* local_zone, CompilationInfo* info,
input_buffer_size_(0),
input_buffer_(nullptr),
exit_control_(nullptr),
loop_assignment_analysis_(loop) {
loop_assignment_analysis_(loop),
state_values_cache_(jsgraph) {
InitializeAstVisitor(info->isolate(), local_zone);
}
......@@ -602,7 +604,7 @@ AstGraphBuilder::Environment::Environment(
void AstGraphBuilder::Environment::UpdateStateValues(Node** state_values,
int offset, int count) {
bool should_update = false;
Node** env_values = (count == 0) ? NULL : &values()->at(offset);
Node** env_values = (count == 0) ? nullptr : &values()->at(offset);
if (*state_values == NULL || (*state_values)->InputCount() != count) {
should_update = true;
} else {
......@@ -621,12 +623,20 @@ void AstGraphBuilder::Environment::UpdateStateValues(Node** state_values,
}
void AstGraphBuilder::Environment::UpdateStateValuesWithCache(
Node** state_values, int offset, int count) {
Node** env_values = (count == 0) ? nullptr : &values()->at(offset);
*state_values = builder_->state_values_cache_.GetNodeForValues(
env_values, static_cast<size_t>(count));
}
Node* AstGraphBuilder::Environment::Checkpoint(
BailoutId ast_id, OutputFrameStateCombine combine) {
if (!FLAG_turbo_deoptimization) return nullptr;
UpdateStateValues(&parameters_node_, 0, parameters_count());
UpdateStateValues(&locals_node_, parameters_count(), locals_count());
UpdateStateValuesWithCache(&locals_node_, parameters_count(), locals_count());
UpdateStateValues(&stack_node_, parameters_count() + locals_count(),
stack_height());
......
......@@ -7,6 +7,7 @@
#include "src/ast.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/state-values-utils.h"
namespace v8 {
namespace internal {
......@@ -97,6 +98,9 @@ class AstGraphBuilder : public AstVisitor {
// Result of loop assignment analysis performed before graph creation.
LoopAssignmentAnalysis* loop_assignment_analysis_;
// Cache for StateValues nodes for frame states.
StateValuesCache state_values_cache_;
// Growth increment for the temporary buffer used to construct input lists to
// new nodes.
static const int kInputBufferSizeIncrement = 64;
......@@ -490,6 +494,7 @@ class AstGraphBuilder::Environment : public ZoneObject {
explicit Environment(const Environment* copy);
Environment* Copy() { return new (zone()) Environment(this); }
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(); }
Graph* graph() const { return builder_->graph(); }
AstGraphBuilder* builder() const { return builder_; }
......
......@@ -11,6 +11,7 @@
#include "src/compiler/node-properties.h"
#include "src/compiler/pipeline.h"
#include "src/compiler/schedule.h"
#include "src/compiler/state-values-utils.h"
namespace v8 {
namespace internal {
......@@ -1144,17 +1145,6 @@ void InstructionSelector::VisitThrow(Node* value) {
}
void InstructionSelector::FillTypeVectorFromStateValues(
ZoneVector<MachineType>* types, Node* state_values) {
DCHECK(state_values->opcode() == IrOpcode::kStateValues);
int count = state_values->InputCount();
types->reserve(static_cast<size_t>(count));
for (int i = 0; i < count; i++) {
types->push_back(GetMachineType(state_values->InputAt(i)));
}
}
FrameStateDescriptor* InstructionSelector::GetFrameStateDescriptor(
Node* state) {
DCHECK(state->opcode() == IrOpcode::kFrameState);
......@@ -1164,9 +1154,10 @@ FrameStateDescriptor* InstructionSelector::GetFrameStateDescriptor(
DCHECK_EQ(IrOpcode::kStateValues, state->InputAt(2)->opcode());
FrameStateCallInfo state_info = OpParameter<FrameStateCallInfo>(state);
int parameters = state->InputAt(0)->InputCount();
int locals = state->InputAt(1)->InputCount();
int stack = state->InputAt(2)->InputCount();
int parameters =
static_cast<int>(StateValuesAccess(state->InputAt(0)).size());
int locals = static_cast<int>(StateValuesAccess(state->InputAt(1)).size());
int stack = static_cast<int>(StateValuesAccess(state->InputAt(2)).size());
FrameStateDescriptor* outer_state = NULL;
Node* outer_node = state->InputAt(4);
......@@ -1210,18 +1201,17 @@ void InstructionSelector::AddFrameStateInputs(
DCHECK_EQ(IrOpcode::kStateValues, locals->op()->opcode());
DCHECK_EQ(IrOpcode::kStateValues, stack->op()->opcode());
DCHECK_EQ(static_cast<int>(descriptor->parameters_count()),
parameters->InputCount());
DCHECK_EQ(static_cast<int>(descriptor->locals_count()), locals->InputCount());
DCHECK_EQ(static_cast<int>(descriptor->stack_count()), stack->InputCount());
DCHECK_EQ(descriptor->parameters_count(),
StateValuesAccess(parameters).size());
DCHECK_EQ(descriptor->locals_count(), StateValuesAccess(locals).size());
DCHECK_EQ(descriptor->stack_count(), StateValuesAccess(stack).size());
ZoneVector<MachineType> types(instruction_zone());
types.reserve(descriptor->GetSize());
OperandGenerator g(this);
size_t value_index = 0;
for (int i = 0; i < static_cast<int>(descriptor->parameters_count()); i++) {
Node* input_node = parameters->InputAt(i);
for (Node* input_node : StateValuesAccess(parameters)) {
inputs->push_back(UseOrImmediate(&g, input_node));
descriptor->SetType(value_index++, GetMachineType(input_node));
}
......@@ -1229,13 +1219,11 @@ void InstructionSelector::AddFrameStateInputs(
inputs->push_back(UseOrImmediate(&g, context));
descriptor->SetType(value_index++, kMachAnyTagged);
}
for (int i = 0; i < static_cast<int>(descriptor->locals_count()); i++) {
Node* input_node = locals->InputAt(i);
for (Node* input_node : StateValuesAccess(locals)) {
inputs->push_back(UseOrImmediate(&g, input_node));
descriptor->SetType(value_index++, GetMachineType(input_node));
}
for (int i = 0; i < static_cast<int>(descriptor->stack_count()); i++) {
Node* input_node = stack->InputAt(i);
for (Node* input_node : StateValuesAccess(stack)) {
inputs->push_back(UseOrImmediate(&g, input_node));
descriptor->SetType(value_index++, GetMachineType(input_node));
}
......
......@@ -171,8 +171,6 @@ class InstructionSelector FINAL {
bool call_address_immediate);
FrameStateDescriptor* GetFrameStateDescriptor(Node* node);
void FillTypeVectorFromStateValues(ZoneVector<MachineType>* parameters,
Node* state_values);
void AddFrameStateInputs(Node* state, InstructionOperandVector* inputs,
FrameStateDescriptor* descriptor);
MachineType GetMachineType(Node* node);
......
// 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.
#include "src/compiler/state-values-utils.h"
namespace v8 {
namespace internal {
namespace compiler {
StateValuesCache::StateValuesCache(JSGraph* js_graph)
: js_graph_(js_graph),
hash_map_(AreKeysEqual, ZoneHashMap::kDefaultHashMapCapacity,
ZoneAllocationPolicy(zone())),
working_space_(zone()),
empty_state_values_(nullptr) {}
// static
bool StateValuesCache::AreKeysEqual(void* key1, void* key2) {
NodeKey* node_key1 = reinterpret_cast<NodeKey*>(key1);
NodeKey* node_key2 = reinterpret_cast<NodeKey*>(key2);
if (node_key1->node == nullptr) {
if (node_key2->node == nullptr) {
return AreValueKeysEqual(reinterpret_cast<StateValuesKey*>(key1),
reinterpret_cast<StateValuesKey*>(key2));
} else {
return IsKeysEqualToNode(reinterpret_cast<StateValuesKey*>(key1),
node_key2->node);
}
} else {
if (node_key2->node == nullptr) {
// If the nodes are already processed, they must be the same.
return IsKeysEqualToNode(reinterpret_cast<StateValuesKey*>(key2),
node_key1->node);
} else {
return node_key1->node == node_key2->node;
}
}
UNREACHABLE();
}
// static
bool StateValuesCache::IsKeysEqualToNode(StateValuesKey* key, Node* node) {
if (key->count != static_cast<size_t>(node->InputCount())) {
return false;
}
for (size_t i = 0; i < key->count; i++) {
if (key->values[i] != node->InputAt(static_cast<int>(i))) {
return false;
}
}
return true;
}
// static
bool StateValuesCache::AreValueKeysEqual(StateValuesKey* key1,
StateValuesKey* key2) {
if (key1->count != key2->count) {
return false;
}
for (size_t i = 0; i < key1->count; i++) {
if (key1->values[i] != key2->values[i]) {
return false;
}
}
return true;
}
Node* StateValuesCache::GetEmptyStateValues() {
if (empty_state_values_ == nullptr) {
empty_state_values_ = graph()->NewNode(common()->StateValues(0));
}
return empty_state_values_;
}
NodeVector* StateValuesCache::GetWorkingSpace(size_t level) {
while (working_space_.size() <= level) {
void* space = zone()->New(sizeof(NodeVector));
working_space_.push_back(new (space)
NodeVector(kMaxInputCount, nullptr, zone()));
}
return working_space_[level];
}
namespace {
int StateValuesHashKey(Node** nodes, size_t count) {
size_t hash = count;
for (size_t i = 0; i < count; i++) {
hash = hash * 23 + nodes[i]->id();
}
return static_cast<int>(hash & 0x7fffffff);
}
} // namespace
Node* StateValuesCache::GetValuesNodeFromCache(Node** nodes, size_t count) {
StateValuesKey key(count, nodes);
int hash = StateValuesHashKey(nodes, count);
ZoneHashMap::Entry* lookup =
hash_map_.Lookup(&key, hash, true, ZoneAllocationPolicy(zone()));
DCHECK_NOT_NULL(lookup);
Node* node;
if (lookup->value == nullptr) {
int input_count = static_cast<int>(count);
node = graph()->NewNode(common()->StateValues(input_count), input_count,
nodes);
NodeKey* new_key = new (zone()->New(sizeof(NodeKey))) NodeKey(node);
lookup->key = new_key;
lookup->value = node;
} else {
node = reinterpret_cast<Node*>(lookup->value);
}
return node;
}
class StateValuesCache::ValueArrayIterator {
public:
ValueArrayIterator(Node** values, size_t count)
: values_(values), count_(count), current_(0) {}
void Advance() {
if (!done()) {
current_++;
}
}
bool done() { return current_ >= count_; }
Node* node() {
DCHECK(!done());
return values_[current_];
}
private:
Node** values_;
size_t count_;
size_t current_;
};
Node* StateValuesCache::BuildTree(ValueArrayIterator* it, size_t max_height) {
if (max_height == 0) {
Node* node = it->node();
it->Advance();
return node;
}
DCHECK(!it->done());
NodeVector* buffer = GetWorkingSpace(max_height);
size_t count = 0;
for (; count < kMaxInputCount; count++) {
if (it->done()) break;
(*buffer)[count] = BuildTree(it, max_height - 1);
}
if (count == 1) {
return (*buffer)[0];
} else {
return GetValuesNodeFromCache(&(buffer->front()), count);
}
}
Node* StateValuesCache::GetNodeForValues(Node** values, size_t count) {
if (count == 0) {
return GetEmptyStateValues();
}
size_t height = 0;
size_t max_nodes = 1;
while (count > max_nodes) {
height++;
max_nodes *= kMaxInputCount;
}
ValueArrayIterator it(values, count);
Node* tree = BuildTree(&it, height);
// If the 'tree' is a single node, equip it with a StateValues wrapper.
if (tree->opcode() != IrOpcode::kStateValues) {
tree = GetValuesNodeFromCache(&tree, 1);
}
return tree;
}
StateValuesAccess::iterator::iterator(Node* node) : current_depth_(0) {
// A hacky way initialize - just set the index before the node we want
// to process and then advance to it.
stack_[current_depth_].node = node;
stack_[current_depth_].index = -1;
Advance();
}
StateValuesAccess::iterator::StatePos* StateValuesAccess::iterator::Top() {
DCHECK(current_depth_ >= 0);
DCHECK(current_depth_ < kMaxInlineDepth);
return &(stack_[current_depth_]);
}
void StateValuesAccess::iterator::Push(Node* node) {
current_depth_++;
CHECK(current_depth_ < kMaxInlineDepth);
stack_[current_depth_].node = node;
stack_[current_depth_].index = 0;
}
void StateValuesAccess::iterator::Pop() {
DCHECK(current_depth_ >= 0);
current_depth_--;
}
bool StateValuesAccess::iterator::done() { return current_depth_ < 0; }
void StateValuesAccess::iterator::Advance() {
// Advance the current index.
Top()->index++;
// Fix up the position to point to a valid node.
while (true) {
// TODO(jarin): Factor to a separate method.
Node* node = Top()->node;
int index = Top()->index;
if (index >= node->InputCount()) {
// Pop stack and move to the next sibling.
Pop();
if (done()) {
// Stack is exhausted, we have reached the end.
return;
}
Top()->index++;
} else if (node->InputAt(index)->opcode() == IrOpcode::kStateValues) {
// Nested state, we need to push to the stack.
Push(node->InputAt(index));
} else {
// We are on a valid node, we can stop the iteration.
return;
}
}
}
Node* StateValuesAccess::iterator::node() {
return Top()->node->InputAt(Top()->index);
}
bool StateValuesAccess::iterator::operator!=(iterator& other) {
// We only allow comparison with end().
CHECK(other.done());
return !done();
}
StateValuesAccess::iterator& StateValuesAccess::iterator::operator++() {
Advance();
return *this;
}
Node* StateValuesAccess::iterator::operator*() { return node(); }
size_t StateValuesAccess::size() {
size_t count = 0;
for (int i = 0; i < node_->InputCount(); i++) {
if (node_->InputAt(i)->opcode() == IrOpcode::kStateValues) {
count += StateValuesAccess(node_->InputAt(i)).size();
} else {
count++;
}
}
return count;
}
} // namespace compiler
} // namespace internal
} // namespace v8
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_COMPILER_STATE_VALUES_UTILS_H_
#define V8_COMPILER_STATE_VALUES_UTILS_H_
#include "src/compiler/js-graph.h"
namespace v8 {
namespace internal {
namespace compiler {
class Graph;
class StateValuesCache {
public:
explicit StateValuesCache(JSGraph* js_graph);
Node* GetNodeForValues(Node** values, size_t count);
private:
static const size_t kMaxInputCount = 8;
struct NodeKey {
Node* node;
explicit NodeKey(Node* node) : node(node) {}
};
struct StateValuesKey : public NodeKey {
// ValueArray - array of nodes ({node} has to be nullptr).
size_t count;
Node** values;
StateValuesKey(size_t count, Node** values)
: NodeKey(nullptr), count(count), values(values) {}
};
class ValueArrayIterator;
static bool AreKeysEqual(void* key1, void* key2);
static bool IsKeysEqualToNode(StateValuesKey* key, Node* node);
static bool AreValueKeysEqual(StateValuesKey* key1, StateValuesKey* key2);
Node* BuildTree(ValueArrayIterator* it, size_t max_height);
NodeVector* GetWorkingSpace(size_t level);
Node* GetEmptyStateValues();
Node* GetValuesNodeFromCache(Node** nodes, size_t count);
Graph* graph() { return js_graph_->graph(); }
CommonOperatorBuilder* common() { return js_graph_->common(); }
Zone* zone() { return graph()->zone(); }
JSGraph* js_graph_;
ZoneHashMap hash_map_;
ZoneVector<NodeVector*> working_space_; // One working space per level.
Node* empty_state_values_;
};
class StateValuesAccess {
public:
class iterator {
public:
// Bare minimum of operators needed for range iteration.
bool operator!=(iterator& other);
iterator& operator++();
Node* operator*();
private:
friend class StateValuesAccess;
iterator() : current_depth_(-1) {}
explicit iterator(Node* node);
Node* node();
bool done();
void Advance();
struct StatePos {
Node* node;
int index;
explicit StatePos(Node* node) : node(node), index(0) {}
StatePos() {}
};
StatePos* Top();
void Push(Node* node);
void Pop();
static const int kMaxInlineDepth = 8;
StatePos stack_[kMaxInlineDepth];
int current_depth_;
};
explicit StateValuesAccess(Node* node) : node_(node) {}
size_t size();
iterator begin() { return iterator(node_); }
iterator end() { return iterator(); }
private:
Node* node_;
};
} // namespace compiler
} // namespace internal
} // namespace v8
#endif // V8_COMPILER_STATE_VALUES_UTILS_H_
// 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/state-values-utils.h"
#include "test/unittests/compiler/graph-unittest.h"
#include "test/unittests/compiler/node-test-utils.h"
#include "test/unittests/test-utils.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace v8 {
namespace internal {
namespace compiler {
class StateValuesIteratorTest : public GraphTest {
public:
StateValuesIteratorTest() : GraphTest(3) {}
Node* StateValuesFromVector(NodeVector* nodes) {
int count = static_cast<int>(nodes->size());
return graph()->NewNode(common()->StateValues(count), count,
count == 0 ? nullptr : &(nodes->front()));
}
};
TEST_F(StateValuesIteratorTest, SimpleIteration) {
NodeVector inputs(zone());
const int count = 10;
for (int i = 0; i < count; i++) {
inputs.push_back(Int32Constant(i));
}
Node* state_values = StateValuesFromVector(&inputs);
int i = 0;
for (Node* node : StateValuesAccess(state_values)) {
EXPECT_THAT(node, IsInt32Constant(i));
i++;
}
EXPECT_EQ(count, i);
}
TEST_F(StateValuesIteratorTest, EmptyIteration) {
NodeVector inputs(zone());
Node* state_values = StateValuesFromVector(&inputs);
for (Node* node : StateValuesAccess(state_values)) {
USE(node);
FAIL();
}
}
TEST_F(StateValuesIteratorTest, NestedIteration) {
NodeVector inputs(zone());
int count = 0;
for (int i = 0; i < 8; i++) {
if (i == 2) {
// Single nested in index 2.
NodeVector nested_inputs(zone());
for (int j = 0; j < 8; j++) {
nested_inputs.push_back(Int32Constant(count++));
}
inputs.push_back(StateValuesFromVector(&nested_inputs));
} else if (i == 5) {
// Double nested at index 5.
NodeVector nested_inputs(zone());
for (int j = 0; j < 8; j++) {
if (j == 7) {
NodeVector doubly_nested_inputs(zone());
for (int k = 0; k < 2; k++) {
doubly_nested_inputs.push_back(Int32Constant(count++));
}
nested_inputs.push_back(StateValuesFromVector(&doubly_nested_inputs));
} else {
nested_inputs.push_back(Int32Constant(count++));
}
}
inputs.push_back(StateValuesFromVector(&nested_inputs));
} else {
inputs.push_back(Int32Constant(count++));
}
}
Node* state_values = StateValuesFromVector(&inputs);
int i = 0;
for (Node* node : StateValuesAccess(state_values)) {
EXPECT_THAT(node, IsInt32Constant(i));
i++;
}
EXPECT_EQ(count, i);
}
TEST_F(StateValuesIteratorTest, TreeFromVector) {
int sizes[] = {0, 1, 2, 100, 5000, 30000};
TRACED_FOREACH(int, count, sizes) {
JSOperatorBuilder javascript(zone());
MachineOperatorBuilder machine(zone());
JSGraph jsgraph(isolate(), graph(), common(), &javascript, &machine);
// Generate the input vector.
NodeVector inputs(zone());
for (int i = 0; i < count; i++) {
inputs.push_back(Int32Constant(i));
}
// Build the tree.
StateValuesCache builder(&jsgraph);
Node* values_node = builder.GetNodeForValues(
inputs.size() == 0 ? nullptr : &(inputs.front()), inputs.size());
// Check the tree contents with vector.
int i = 0;
for (Node* node : StateValuesAccess(values_node)) {
EXPECT_THAT(node, IsInt32Constant(i));
i++;
}
EXPECT_EQ(inputs.size(), static_cast<size_t>(i));
}
}
TEST_F(StateValuesIteratorTest, BuildTreeIdentical) {
int sizes[] = {0, 1, 2, 100, 5000, 30000};
TRACED_FOREACH(int, count, sizes) {
JSOperatorBuilder javascript(zone());
MachineOperatorBuilder machine(zone());
JSGraph jsgraph(isolate(), graph(), common(), &javascript, &machine);
// Generate the input vector.
NodeVector inputs(zone());
for (int i = 0; i < count; i++) {
inputs.push_back(Int32Constant(i));
}
// Build two trees from the same data.
StateValuesCache builder(&jsgraph);
Node* node1 = builder.GetNodeForValues(
inputs.size() == 0 ? nullptr : &(inputs.front()), inputs.size());
Node* node2 = builder.GetNodeForValues(
inputs.size() == 0 ? nullptr : &(inputs.front()), inputs.size());
// The trees should be equal since the data was the same.
EXPECT_EQ(node1, node2);
}
}
} // namespace compiler
} // namespace internal
} // namespace v8
......@@ -74,6 +74,7 @@
'compiler/scheduler-unittest.cc',
'compiler/simplified-operator-reducer-unittest.cc',
'compiler/simplified-operator-unittest.cc',
'compiler/state-values-utils-unittest.cc',
'compiler/typer-unittest.cc',
'compiler/value-numbering-reducer-unittest.cc',
'compiler/zone-pool-unittest.cc',
......
......@@ -538,6 +538,8 @@
'../../src/compiler/simplified-operator.h',
'../../src/compiler/source-position.cc',
'../../src/compiler/source-position.h',
'../../src/compiler/state-values-utils.cc',
'../../src/compiler/state-values-utils.h',
'../../src/compiler/typer.cc',
'../../src/compiler/typer.h',
'../../src/compiler/value-numbering-reducer.cc',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment