Commit 68f1a37f authored by leszeks's avatar leszeks Committed by Commit bot

[turbofan] Sparse representation for state values

Add a more efficient encoding for state values that have a large number of
optimized-out inputs.

Review-Url: https://codereview.chromium.org/2509623002
Cr-Commit-Position: refs/heads/master@{#42088}
parent 8bb2501e
......@@ -670,7 +670,7 @@ void AstGraphBuilder::Environment::UpdateStateValues(Node** state_values,
}
}
if (should_update) {
const Operator* op = common()->StateValues(count);
const Operator* op = common()->StateValues(count, SparseInputMask::Dense());
(*state_values) = graph()->NewNode(op, count, env_values);
}
}
......
......@@ -81,8 +81,8 @@ class BytecodeGraphBuilder::Environment : public ZoneObject {
bool StateValuesRequireUpdate(Node** state_values, Node** values, int count);
void UpdateStateValues(Node** state_values, Node** values, int count);
void UpdateStateValuesWithCache(Node** state_values, Node** values,
int count);
void UpdateStateValuesWithCache(Node** state_values, Node** values, int count,
const BitVector* liveness);
int RegisterToValuesIndex(interpreter::Register the_register) const;
......@@ -107,10 +107,6 @@ class BytecodeGraphBuilder::Environment : public ZoneObject {
Node* accumulator_state_values_;
int register_base_;
int accumulator_base_;
// A working area for writing maybe-dead values to when updating the state
// values for registers.
NodeVector state_value_working_area_;
};
......@@ -131,8 +127,7 @@ BytecodeGraphBuilder::Environment::Environment(BytecodeGraphBuilder* builder,
values_(builder->local_zone()),
parameters_state_values_(nullptr),
registers_state_values_(nullptr),
accumulator_state_values_(nullptr),
state_value_working_area_(builder->local_zone()) {
accumulator_state_values_(nullptr) {
// The layout of values_ is:
//
// [receiver] [parameters] [registers] [accumulator]
......@@ -157,8 +152,6 @@ BytecodeGraphBuilder::Environment::Environment(BytecodeGraphBuilder* builder,
// Accumulator
accumulator_base_ = static_cast<int>(values()->size());
values()->push_back(undefined_constant);
state_value_working_area_.resize(register_count_);
}
BytecodeGraphBuilder::Environment::Environment(
......@@ -174,9 +167,7 @@ BytecodeGraphBuilder::Environment::Environment(
registers_state_values_(nullptr),
accumulator_state_values_(nullptr),
register_base_(other->register_base_),
accumulator_base_(other->accumulator_base_),
// Environments can share their working area.
state_value_working_area_(other->state_value_working_area_) {
accumulator_base_(other->accumulator_base_) {
values_ = other->values_;
}
......@@ -412,15 +403,15 @@ void BytecodeGraphBuilder::Environment::UpdateStateValues(Node** state_values,
Node** values,
int count) {
if (StateValuesRequireUpdate(state_values, values, count)) {
const Operator* op = common()->StateValues(count);
const Operator* op = common()->StateValues(count, SparseInputMask::Dense());
(*state_values) = graph()->NewNode(op, count, values);
}
}
void BytecodeGraphBuilder::Environment::UpdateStateValuesWithCache(
Node** state_values, Node** values, int count) {
Node** state_values, Node** values, int count, const BitVector* liveness) {
*state_values = builder_->state_values_cache_.GetNodeForValues(
values, static_cast<size_t>(count));
values, static_cast<size_t>(count), liveness);
}
Node* BytecodeGraphBuilder::Environment::Checkpoint(
......@@ -429,31 +420,17 @@ Node* BytecodeGraphBuilder::Environment::Checkpoint(
UpdateStateValues(&parameters_state_values_, &values()->at(0),
parameter_count());
if (liveness) {
Node* optimized_out = builder()->jsgraph()->OptimizedOutConstant();
for (int i = 0; i < register_count(); ++i) {
state_value_working_area_[i] = liveness->RegisterIsLive(i)
? values()->at(register_base() + i)
: optimized_out;
}
Node* accumulator_value = liveness->AccumulatorIsLive()
? values()->at(accumulator_base())
: optimized_out;
UpdateStateValuesWithCache(&registers_state_values_,
state_value_working_area_.data(),
register_count());
UpdateStateValues(&accumulator_state_values_, &accumulator_value, 1);
} else {
UpdateStateValuesWithCache(&registers_state_values_,
&values()->at(register_base()),
register_count());
UpdateStateValues(&accumulator_state_values_,
&values()->at(accumulator_base()), 1);
}
// TODO(leszeks): We should pass a view of the liveness bitvector here, with
// offset and count, rather than passing the entire bitvector and assuming
// that register liveness starts at offset 0.
UpdateStateValuesWithCache(&registers_state_values_,
&values()->at(register_base()), register_count(),
liveness ? &liveness->bit_vector() : nullptr);
Node* accumulator_value = liveness == nullptr || liveness->AccumulatorIsLive()
? values()->at(accumulator_base())
: builder()->jsgraph()->OptimizedOutConstant();
UpdateStateValues(&accumulator_state_values_, &accumulator_value, 1);
const Operator* op = common()->FrameState(
bailout_id, combine, builder()->frame_state_function_info());
......
......@@ -7,6 +7,7 @@
#include "src/assembler.h"
#include "src/base/lazy-instance.h"
#include "src/compiler/linkage.h"
#include "src/compiler/node.h"
#include "src/compiler/opcodes.h"
#include "src/compiler/operator.h"
#include "src/handles-inl.h"
......@@ -171,6 +172,106 @@ std::ostream& operator<<(std::ostream& os,
return os << p.value() << "|" << p.rmode() << "|" << p.type();
}
SparseInputMask::InputIterator::InputIterator(
SparseInputMask::BitMaskType bit_mask, Node* parent)
: bit_mask_(bit_mask), parent_(parent), real_index_(0) {
#if DEBUG
if (bit_mask_ != SparseInputMask::kDenseBitMask) {
DCHECK_EQ(base::bits::CountPopulation(bit_mask_) -
base::bits::CountPopulation(kEndMarker),
parent->InputCount());
}
#endif
}
void SparseInputMask::InputIterator::Advance() {
DCHECK(!IsEnd());
if (IsReal()) {
++real_index_;
}
bit_mask_ >>= 1;
}
Node* SparseInputMask::InputIterator::GetReal() const {
DCHECK(IsReal());
return parent_->InputAt(real_index_);
}
bool SparseInputMask::InputIterator::IsReal() const {
return bit_mask_ == SparseInputMask::kDenseBitMask ||
(bit_mask_ & kEntryMask);
}
bool SparseInputMask::InputIterator::IsEnd() const {
return (bit_mask_ == kEndMarker) ||
(bit_mask_ == SparseInputMask::kDenseBitMask &&
real_index_ >= parent_->InputCount());
}
int SparseInputMask::CountReal() const {
DCHECK(!IsDense());
return base::bits::CountPopulation(bit_mask_) -
base::bits::CountPopulation(kEndMarker);
}
SparseInputMask::InputIterator SparseInputMask::IterateOverInputs(Node* node) {
DCHECK(IsDense() || CountReal() == node->InputCount());
return InputIterator(bit_mask_, node);
}
bool operator==(SparseInputMask const& lhs, SparseInputMask const& rhs) {
return lhs.mask() == rhs.mask();
}
bool operator!=(SparseInputMask const& lhs, SparseInputMask const& rhs) {
return !(lhs == rhs);
}
size_t hash_value(SparseInputMask const& p) {
return base::hash_value(p.mask());
}
std::ostream& operator<<(std::ostream& os, SparseInputMask const& p) {
if (p.IsDense()) {
return os << "dense";
} else {
SparseInputMask::BitMaskType mask = p.mask();
DCHECK_NE(mask, SparseInputMask::kDenseBitMask);
os << "sparse:";
while (mask != SparseInputMask::kEndMarker) {
if (mask & SparseInputMask::kEntryMask) {
os << "^";
} else {
os << ".";
}
mask >>= 1;
}
return os;
}
}
bool operator==(TypedStateValueInfo const& lhs,
TypedStateValueInfo const& rhs) {
return lhs.machine_types() == rhs.machine_types() &&
lhs.sparse_input_mask() == rhs.sparse_input_mask();
}
bool operator!=(TypedStateValueInfo const& lhs,
TypedStateValueInfo const& rhs) {
return !(lhs == rhs);
}
size_t hash_value(TypedStateValueInfo const& p) {
return base::hash_combine(p.machine_types(), p.sparse_input_mask());
}
std::ostream& operator<<(std::ostream& os, TypedStateValueInfo const& p) {
return os << p.machine_types() << "|" << p.sparse_input_mask();
}
size_t hash_value(RegionObservability observability) {
return static_cast<size_t>(observability);
}
......@@ -235,9 +336,23 @@ OsrGuardType OsrGuardTypeOf(Operator const* op) {
return OpParameter<OsrGuardType>(op);
}
SparseInputMask SparseInputMaskOf(Operator const* op) {
DCHECK(op->opcode() == IrOpcode::kStateValues ||
op->opcode() == IrOpcode::kTypedStateValues);
if (op->opcode() == IrOpcode::kTypedStateValues) {
return OpParameter<TypedStateValueInfo>(op).sparse_input_mask();
}
return OpParameter<SparseInputMask>(op);
}
ZoneVector<MachineType> const* MachineTypesOf(Operator const* op) {
DCHECK(op->opcode() == IrOpcode::kTypedObjectState ||
op->opcode() == IrOpcode::kTypedStateValues);
if (op->opcode() == IrOpcode::kTypedStateValues) {
return OpParameter<TypedStateValueInfo>(op).machine_types();
}
return OpParameter<const ZoneVector<MachineType>*>(op);
}
......@@ -635,13 +750,14 @@ struct CommonOperatorGlobalCache final {
#undef CACHED_PROJECTION
template <int kInputCount>
struct StateValuesOperator final : public Operator {
struct StateValuesOperator final : public Operator1<SparseInputMask> {
StateValuesOperator()
: Operator( // --
IrOpcode::kStateValues, // opcode
Operator::kPure, // flags
"StateValues", // name
kInputCount, 0, 0, 1, 0, 0) {} // counts
: Operator1<SparseInputMask>( // --
IrOpcode::kStateValues, // opcode
Operator::kPure, // flags
"StateValues", // name
kInputCount, 0, 0, 1, 0, 0, // counts
SparseInputMask::Dense()) {} // parameter
};
#define CACHED_STATE_VALUES(input_count) \
StateValuesOperator<input_count> kStateValues##input_count##Operator;
......@@ -1084,30 +1200,44 @@ const Operator* CommonOperatorBuilder::BeginRegion(
return nullptr;
}
const Operator* CommonOperatorBuilder::StateValues(int arguments) {
switch (arguments) {
const Operator* CommonOperatorBuilder::StateValues(int arguments,
SparseInputMask bitmask) {
if (bitmask.IsDense()) {
switch (arguments) {
#define CACHED_STATE_VALUES(arguments) \
case arguments: \
return &cache_.kStateValues##arguments##Operator;
CACHED_STATE_VALUES_LIST(CACHED_STATE_VALUES)
CACHED_STATE_VALUES_LIST(CACHED_STATE_VALUES)
#undef CACHED_STATE_VALUES
default:
break;
default:
break;
}
}
#if DEBUG
DCHECK(bitmask.IsDense() || bitmask.CountReal() == arguments);
#endif
// Uncached.
return new (zone()) Operator( // --
IrOpcode::kStateValues, Operator::kPure, // opcode
"StateValues", // name
arguments, 0, 0, 1, 0, 0); // counts
return new (zone()) Operator1<SparseInputMask>( // --
IrOpcode::kStateValues, Operator::kPure, // opcode
"StateValues", // name
arguments, 0, 0, 1, 0, 0, // counts
bitmask); // parameter
}
const Operator* CommonOperatorBuilder::TypedStateValues(
const ZoneVector<MachineType>* types) {
return new (zone()) Operator1<const ZoneVector<MachineType>*>( // --
IrOpcode::kTypedStateValues, Operator::kPure, // opcode
"TypedStateValues", // name
static_cast<int>(types->size()), 0, 0, 1, 0, 0, // counts
types); // parameter
const ZoneVector<MachineType>* types, SparseInputMask bitmask) {
#if DEBUG
DCHECK(bitmask.IsDense() ||
bitmask.CountReal() == static_cast<int>(types->size()));
#endif
return new (zone()) Operator1<TypedStateValueInfo>( // --
IrOpcode::kTypedStateValues, Operator::kPure, // opcode
"TypedStateValues", // name
static_cast<int>(types->size()), 0, 0, 1, 0, 0, // counts
TypedStateValueInfo(types, bitmask)); // parameters
}
const Operator* CommonOperatorBuilder::ObjectState(int pointer_slots) {
......
......@@ -22,6 +22,7 @@ class CallDescriptor;
struct CommonOperatorGlobalCache;
class Operator;
class Type;
class Node;
// Prediction hint for branches.
enum class BranchHint : uint8_t { kNone, kTrue, kFalse };
......@@ -158,6 +159,123 @@ std::ostream& operator<<(std::ostream&, RelocatablePtrConstantInfo const&);
size_t hash_value(RelocatablePtrConstantInfo const& p);
// Used to define a sparse set of inputs. This can be used to efficiently encode
// nodes that can have a lot of inputs, but where many inputs can have the same
// value.
class SparseInputMask final {
public:
typedef uint32_t BitMaskType;
// The mask representing a dense input set.
static const BitMaskType kDenseBitMask = 0x0;
// The bits representing the end of a sparse input set.
static const BitMaskType kEndMarker = 0x1;
// The mask for accessing a sparse input entry in the bitmask.
static const BitMaskType kEntryMask = 0x1;
// The number of bits in the mask, minus one for the end marker.
static const int kMaxSparseInputs = (sizeof(BitMaskType) * kBitsPerByte - 1);
// An iterator over a node's sparse inputs.
class InputIterator final {
public:
InputIterator() {}
InputIterator(BitMaskType bit_mask, Node* parent);
Node* parent() const { return parent_; }
int real_index() const { return real_index_; }
// Advance the iterator to the next sparse input. Only valid if the iterator
// has not reached the end.
void Advance();
// Get the current sparse input's real node value. Only valid if the
// current sparse input is real.
Node* GetReal() const;
// Get the current sparse input, returning either a real input node if
// the current sparse input is real, or the given {empty_value} if the
// current sparse input is empty.
Node* Get(Node* empty_value) const {
return IsReal() ? GetReal() : empty_value;
}
// True if the current sparse input is a real input node.
bool IsReal() const;
// True if the current sparse input is an empty value.
bool IsEmpty() const { return !IsReal(); }
// True if the iterator has reached the end of the sparse inputs.
bool IsEnd() const;
private:
BitMaskType bit_mask_;
Node* parent_;
int real_index_;
};
explicit SparseInputMask(BitMaskType bit_mask) : bit_mask_(bit_mask) {}
// Provides a SparseInputMask representing a dense input set.
static SparseInputMask Dense() { return SparseInputMask(kDenseBitMask); }
BitMaskType mask() const { return bit_mask_; }
bool IsDense() const { return bit_mask_ == SparseInputMask::kDenseBitMask; }
// Counts how many real values are in the sparse array. Only valid for
// non-dense masks.
int CountReal() const;
// Returns an iterator over the sparse inputs of {node}.
InputIterator IterateOverInputs(Node* node);
private:
//
// The sparse input mask has a bitmask specifying if the node's inputs are
// represented sparsely. If the bitmask value is 0, then the inputs are dense;
// otherwise, they should be interpreted as follows:
//
// * The bitmask represents which values are real, with 1 for real values
// and 0 for empty values.
// * The inputs to the node are the real values, in the order of the 1s from
// least- to most-significant.
// * The top bit of the bitmask is a guard indicating the end of the values,
// whether real or empty (and is not representative of a real input
// itself). This is used so that we don't have to additionally store a
// value count.
//
// So, for N 1s in the bitmask, there are N - 1 inputs into the node.
BitMaskType bit_mask_;
};
bool operator==(SparseInputMask const& lhs, SparseInputMask const& rhs);
bool operator!=(SparseInputMask const& lhs, SparseInputMask const& rhs);
class TypedStateValueInfo final {
public:
TypedStateValueInfo(ZoneVector<MachineType> const* machine_types,
SparseInputMask sparse_input_mask)
: machine_types_(machine_types), sparse_input_mask_(sparse_input_mask) {}
ZoneVector<MachineType> const* machine_types() const {
return machine_types_;
}
SparseInputMask sparse_input_mask() const { return sparse_input_mask_; }
private:
ZoneVector<MachineType> const* machine_types_;
SparseInputMask sparse_input_mask_;
};
bool operator==(TypedStateValueInfo const& lhs, TypedStateValueInfo const& rhs);
bool operator!=(TypedStateValueInfo const& lhs, TypedStateValueInfo const& rhs);
std::ostream& operator<<(std::ostream&, TypedStateValueInfo const&);
size_t hash_value(TypedStateValueInfo const& p);
// Used to mark a region (as identified by BeginRegion/FinishRegion) as either
// JavaScript-observable or not (i.e. allocations are not JavaScript observable
// themselves, but transitioning stores are).
......@@ -181,6 +299,8 @@ size_t hash_value(OsrGuardType type);
std::ostream& operator<<(std::ostream&, OsrGuardType);
OsrGuardType OsrGuardTypeOf(Operator const*);
SparseInputMask SparseInputMaskOf(Operator const*);
ZoneVector<MachineType> const* MachineTypesOf(Operator const*)
WARN_UNUSED_RESULT;
......@@ -245,8 +365,9 @@ class V8_EXPORT_PRIVATE CommonOperatorBuilder final
const Operator* Checkpoint();
const Operator* BeginRegion(RegionObservability);
const Operator* FinishRegion();
const Operator* StateValues(int arguments);
const Operator* TypedStateValues(const ZoneVector<MachineType>* types);
const Operator* StateValues(int arguments, SparseInputMask bitmask);
const Operator* TypedStateValues(const ZoneVector<MachineType>* types,
SparseInputMask bitmask);
const Operator* ObjectState(int pointer_slots);
const Operator* TypedObjectState(const ZoneVector<MachineType>* types);
const Operator* FrameState(BailoutId bailout_id,
......
......@@ -501,6 +501,11 @@ size_t InstructionSelector::AddOperandToStateValueDescriptor(
StateValueList* values, InstructionOperandVector* inputs,
OperandGenerator* g, StateObjectDeduplicator* deduplicator, Node* input,
MachineType type, FrameStateInputKind kind, Zone* zone) {
if (input == nullptr) {
values->PushOptimizedOut();
return 0;
}
switch (input->opcode()) {
case IrOpcode::kObjectState: {
UNREACHABLE();
......
......@@ -1030,6 +1030,7 @@ Node* JSCreateLowering::AllocateArguments(Node* effect, Node* control,
AllocationBuilder a(jsgraph(), effect, control);
a.AllocateArray(argument_count, factory()->fixed_array_map());
for (int i = 0; i < argument_count; ++i, ++parameters_it) {
DCHECK_NOT_NULL((*parameters_it).node);
a.Store(AccessBuilder::ForFixedArraySlot(i), (*parameters_it).node);
}
return a.Finish();
......@@ -1059,6 +1060,7 @@ Node* JSCreateLowering::AllocateRestArguments(Node* effect, Node* control,
AllocationBuilder a(jsgraph(), effect, control);
a.AllocateArray(num_elements, factory()->fixed_array_map());
for (int i = 0; i < num_elements; ++i, ++parameters_it) {
DCHECK_NOT_NULL((*parameters_it).node);
a.Store(AccessBuilder::ForFixedArraySlot(i), (*parameters_it).node);
}
return a.Finish();
......@@ -1088,18 +1090,19 @@ Node* JSCreateLowering::AllocateAliasedArguments(
// Prepare an iterator over argument values recorded in the frame state.
Node* const parameters = frame_state->InputAt(kFrameStateParametersInput);
StateValuesAccess parameters_access(parameters);
auto paratemers_it = ++parameters_access.begin();
auto parameters_it = ++parameters_access.begin();
// The unmapped argument values recorded in the frame state are stored yet
// another indirection away and then linked into the parameter map below,
// whereas mapped argument values are replaced with a hole instead.
AllocationBuilder aa(jsgraph(), effect, control);
aa.AllocateArray(argument_count, factory()->fixed_array_map());
for (int i = 0; i < mapped_count; ++i, ++paratemers_it) {
for (int i = 0; i < mapped_count; ++i, ++parameters_it) {
aa.Store(AccessBuilder::ForFixedArraySlot(i), jsgraph()->TheHoleConstant());
}
for (int i = mapped_count; i < argument_count; ++i, ++paratemers_it) {
aa.Store(AccessBuilder::ForFixedArraySlot(i), (*paratemers_it).node);
for (int i = mapped_count; i < argument_count; ++i, ++parameters_it) {
DCHECK_NOT_NULL((*parameters_it).node);
aa.Store(AccessBuilder::ForFixedArraySlot(i), (*parameters_it).node);
}
Node* arguments = aa.Finish();
......
......@@ -279,7 +279,8 @@ Node* JSGraph::ExternalConstant(Runtime::FunctionId function_id) {
}
Node* JSGraph::EmptyStateValues() {
return CACHED(kEmptyStateValues, graph()->NewNode(common()->StateValues(0)));
return CACHED(kEmptyStateValues, graph()->NewNode(common()->StateValues(
0, SparseInputMask::Dense())));
}
Node* JSGraph::Dead() {
......
......@@ -231,14 +231,14 @@ Node* JSInliner::CreateArtificialFrameState(Node* node, Node* outer_frame_state,
const Operator* op = common()->FrameState(
BailoutId(-1), OutputFrameStateCombine::Ignore(), state_info);
const Operator* op0 = common()->StateValues(0);
const Operator* op0 = common()->StateValues(0, SparseInputMask::Dense());
Node* node0 = graph()->NewNode(op0);
NodeVector params(local_zone_);
for (int parameter = 0; parameter < parameter_count + 1; ++parameter) {
params.push_back(node->InputAt(1 + parameter));
}
const Operator* op_param =
common()->StateValues(static_cast<int>(params.size()));
const Operator* op_param = common()->StateValues(
static_cast<int>(params.size()), SparseInputMask::Dense());
Node* params_node = graph()->NewNode(
op_param, static_cast<int>(params.size()), &params.front());
return graph()->NewNode(op, params_node, node0, node0,
......@@ -269,7 +269,7 @@ Node* JSInliner::CreateTailCallerFrameState(Node* node, Node* frame_state) {
const Operator* op = common()->FrameState(
BailoutId(-1), OutputFrameStateCombine::Ignore(), state_info);
const Operator* op0 = common()->StateValues(0);
const Operator* op0 = common()->StateValues(0, SparseInputMask::Dense());
Node* node0 = graph()->NewNode(op0);
return graph()->NewNode(op, node0, node0, node0,
jsgraph()->UndefinedConstant(), function,
......
......@@ -1001,7 +1001,8 @@ JSNativeContextSpecialization::BuildPropertyAccess(
common()->FrameState(BailoutId::None(),
OutputFrameStateCombine::Ignore(),
frame_info0),
graph()->NewNode(common()->StateValues(1), receiver),
graph()->NewNode(common()->StateValues(1, SparseInputMask::Dense()),
receiver),
jsgraph()->EmptyStateValues(), jsgraph()->EmptyStateValues(),
context, target, frame_state);
......@@ -1037,7 +1038,8 @@ JSNativeContextSpecialization::BuildPropertyAccess(
common()->FrameState(BailoutId::None(),
OutputFrameStateCombine::Ignore(),
frame_info0),
graph()->NewNode(common()->StateValues(2), receiver, value),
graph()->NewNode(common()->StateValues(2, SparseInputMask::Dense()),
receiver, value),
jsgraph()->EmptyStateValues(), jsgraph()->EmptyStateValues(),
context, target, frame_state);
......
......@@ -1010,8 +1010,9 @@ class RepresentationSelector {
machine_type.semantic() == MachineSemantic::kUint32);
(*types)[i] = machine_type;
}
NodeProperties::ChangeOp(node,
jsgraph_->common()->TypedStateValues(types));
SparseInputMask mask = SparseInputMaskOf(node->op());
NodeProperties::ChangeOp(
node, jsgraph_->common()->TypedStateValues(types, mask));
}
SetOutput(node, MachineRepresentation::kTagged);
}
......
This diff is collapsed.
......@@ -5,12 +5,16 @@
#ifndef V8_COMPILER_STATE_VALUES_UTILS_H_
#define V8_COMPILER_STATE_VALUES_UTILS_H_
#include <array>
#include "src/compiler/common-operator.h"
#include "src/compiler/js-graph.h"
#include "src/globals.h"
namespace v8 {
namespace internal {
class BitVector;
namespace compiler {
class Graph;
......@@ -19,10 +23,12 @@ class V8_EXPORT_PRIVATE StateValuesCache {
public:
explicit StateValuesCache(JSGraph* js_graph);
Node* GetNodeForValues(Node** values, size_t count);
Node* GetNodeForValues(Node** values, size_t count,
const BitVector* liveness = nullptr);
private:
static const size_t kMaxInputCount = 8;
typedef std::array<Node*, kMaxInputCount> WorkingBuffer;
struct NodeKey {
Node* node;
......@@ -33,22 +39,34 @@ class V8_EXPORT_PRIVATE StateValuesCache {
struct StateValuesKey : public NodeKey {
// ValueArray - array of nodes ({node} has to be nullptr).
size_t count;
SparseInputMask mask;
Node** values;
StateValuesKey(size_t count, Node** values)
: NodeKey(nullptr), count(count), values(values) {}
StateValuesKey(size_t count, SparseInputMask mask, Node** values)
: NodeKey(nullptr), count(count), mask(mask), 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);
// Fills {node_buffer}, starting from {node_count}, with {values}, starting
// at {values_idx}, sparsely encoding according to {liveness}. {node_count} is
// updated with the new number of inputs in {node_buffer}, and a bitmask of
// the sparse encoding is returned.
SparseInputMask::BitMaskType FillBufferWithValues(WorkingBuffer* node_buffer,
size_t* node_count,
size_t* values_idx,
Node** values, size_t count,
const BitVector* liveness);
Node* BuildTree(size_t* values_idx, Node** values, size_t count,
const BitVector* liveness, size_t level);
WorkingBuffer* GetWorkingSpace(size_t level);
Node* GetEmptyStateValues();
Node* GetValuesNodeFromCache(Node** nodes, size_t count);
Node* GetValuesNodeFromCache(Node** nodes, size_t count,
SparseInputMask mask);
Graph* graph() { return js_graph_->graph(); }
CommonOperatorBuilder* common() { return js_graph_->common(); }
......@@ -57,7 +75,7 @@ class V8_EXPORT_PRIVATE StateValuesCache {
JSGraph* js_graph_;
CustomMatcherZoneHashMap hash_map_;
ZoneVector<NodeVector*> working_space_; // One working space per level.
ZoneVector<WorkingBuffer> working_space_; // One working space per level.
Node* empty_state_values_;
};
......@@ -86,21 +104,14 @@ class V8_EXPORT_PRIVATE StateValuesAccess {
MachineType type();
bool done();
void Advance();
void EnsureValid();
struct StatePos {
Node* node;
int index;
explicit StatePos(Node* node) : node(node), index(0) {}
StatePos() {}
};
StatePos* Top();
SparseInputMask::InputIterator* Top();
void Push(Node* node);
void Pop();
static const int kMaxInlineDepth = 8;
StatePos stack_[kMaxInlineDepth];
SparseInputMask::InputIterator stack_[kMaxInlineDepth];
int current_depth_;
};
......
......@@ -77,9 +77,12 @@ class JSTypedLoweringTester : public HandleAndZoneScope {
}
Node* EmptyFrameState(Node* context) {
Node* parameters = graph.NewNode(common.StateValues(0));
Node* locals = graph.NewNode(common.StateValues(0));
Node* stack = graph.NewNode(common.StateValues(0));
Node* parameters =
graph.NewNode(common.StateValues(0, SparseInputMask::Dense()));
Node* locals =
graph.NewNode(common.StateValues(0, SparseInputMask::Dense()));
Node* stack =
graph.NewNode(common.StateValues(0, SparseInputMask::Dense()));
Node* state_node = graph.NewNode(
common.FrameState(BailoutId::None(), OutputFrameStateCombine::Ignore(),
......
......@@ -437,9 +437,12 @@ TEST_F(EscapeAnalysisTest, DeoptReplacement) {
Node* effect1 = Store(FieldAccessAtIndex(0), allocation, object1, finish);
Branch();
Node* ifFalse = IfFalse();
Node* state_values1 = graph()->NewNode(common()->StateValues(1), finish);
Node* state_values2 = graph()->NewNode(common()->StateValues(0));
Node* state_values3 = graph()->NewNode(common()->StateValues(0));
Node* state_values1 = graph()->NewNode(
common()->StateValues(1, SparseInputMask::Dense()), finish);
Node* state_values2 =
graph()->NewNode(common()->StateValues(0, SparseInputMask::Dense()));
Node* state_values3 =
graph()->NewNode(common()->StateValues(0, SparseInputMask::Dense()));
Node* frame_state = graph()->NewNode(
common()->FrameState(BailoutId::None(), OutputFrameStateCombine::Ignore(),
nullptr),
......@@ -477,9 +480,12 @@ TEST_F(EscapeAnalysisTest, DISABLED_DeoptReplacementIdentity) {
Node* effect1 = Store(FieldAccessAtIndex(0), allocation, object1, finish);
Branch();
Node* ifFalse = IfFalse();
Node* state_values1 = graph()->NewNode(common()->StateValues(1), finish);
Node* state_values2 = graph()->NewNode(common()->StateValues(1), finish);
Node* state_values3 = graph()->NewNode(common()->StateValues(0));
Node* state_values1 = graph()->NewNode(
common()->StateValues(1, SparseInputMask::Dense()), finish);
Node* state_values2 = graph()->NewNode(
common()->StateValues(1, SparseInputMask::Dense()), finish);
Node* state_values3 =
graph()->NewNode(common()->StateValues(0, SparseInputMask::Dense()));
Node* frame_state = graph()->NewNode(
common()->FrameState(BailoutId::None(), OutputFrameStateCombine::Ignore(),
nullptr),
......
......@@ -80,7 +80,8 @@ Node* GraphTest::UndefinedConstant() {
Node* GraphTest::EmptyFrameState() {
Node* state_values = graph()->NewNode(common()->StateValues(0));
Node* state_values =
graph()->NewNode(common()->StateValues(0, SparseInputMask::Dense()));
return graph()->NewNode(
common()->FrameState(BailoutId::None(), OutputFrameStateCombine::Ignore(),
nullptr),
......
......@@ -364,10 +364,13 @@ TARGET_TEST_F(InstructionSelectorTest, CallJSFunctionWithDeopt) {
zone(), false, 1, CallDescriptor::kNeedsFrameState);
// Build frame state for the state before the call.
Node* parameters =
m.AddNode(m.common()->TypedStateValues(&int32_type), m.Int32Constant(1));
Node* locals = m.AddNode(m.common()->TypedStateValues(&empty_types));
Node* stack = m.AddNode(m.common()->TypedStateValues(&empty_types));
Node* parameters = m.AddNode(
m.common()->TypedStateValues(&int32_type, SparseInputMask::Dense()),
m.Int32Constant(1));
Node* locals = m.AddNode(
m.common()->TypedStateValues(&empty_types, SparseInputMask::Dense()));
Node* stack = m.AddNode(
m.common()->TypedStateValues(&empty_types, SparseInputMask::Dense()));
Node* context_sentinel = m.Int32Constant(0);
Node* state_node = m.AddNode(
m.common()->FrameState(bailout_id, OutputFrameStateCombine::Push(),
......@@ -419,12 +422,15 @@ TARGET_TEST_F(InstructionSelectorTest, CallStubWithDeopt) {
CallDescriptor::kNeedsFrameState, Operator::kNoProperties);
// Build frame state for the state before the call.
Node* parameters =
m.AddNode(m.common()->TypedStateValues(&int32_type), m.Int32Constant(43));
Node* locals = m.AddNode(m.common()->TypedStateValues(&float64_type),
m.Float64Constant(0.5));
Node* stack = m.AddNode(m.common()->TypedStateValues(&tagged_type),
m.UndefinedConstant());
Node* parameters = m.AddNode(
m.common()->TypedStateValues(&int32_type, SparseInputMask::Dense()),
m.Int32Constant(43));
Node* locals = m.AddNode(
m.common()->TypedStateValues(&float64_type, SparseInputMask::Dense()),
m.Float64Constant(0.5));
Node* stack = m.AddNode(
m.common()->TypedStateValues(&tagged_type, SparseInputMask::Dense()),
m.UndefinedConstant());
Node* context_sentinel = m.Int32Constant(0);
Node* state_node = m.AddNode(
m.common()->FrameState(bailout_id_before, OutputFrameStateCombine::Push(),
......@@ -512,24 +518,30 @@ TARGET_TEST_F(InstructionSelectorTest, CallStubWithDeoptRecursiveFrameState) {
CallDescriptor::kNeedsFrameState, Operator::kNoProperties);
// Build frame state for the state before the call.
Node* parameters =
m.AddNode(m.common()->TypedStateValues(&int32_type), m.Int32Constant(63));
Node* locals =
m.AddNode(m.common()->TypedStateValues(&int32_type), m.Int32Constant(64));
Node* stack =
m.AddNode(m.common()->TypedStateValues(&int32_type), m.Int32Constant(65));
Node* parameters = m.AddNode(
m.common()->TypedStateValues(&int32_type, SparseInputMask::Dense()),
m.Int32Constant(63));
Node* locals = m.AddNode(
m.common()->TypedStateValues(&int32_type, SparseInputMask::Dense()),
m.Int32Constant(64));
Node* stack = m.AddNode(
m.common()->TypedStateValues(&int32_type, SparseInputMask::Dense()),
m.Int32Constant(65));
Node* frame_state_parent = m.AddNode(
m.common()->FrameState(bailout_id_parent,
OutputFrameStateCombine::Ignore(),
m.GetFrameStateFunctionInfo(1, 1)),
parameters, locals, stack, context, function_node, m.UndefinedConstant());
Node* parameters2 =
m.AddNode(m.common()->TypedStateValues(&int32_type), m.Int32Constant(43));
Node* locals2 = m.AddNode(m.common()->TypedStateValues(&float64_type),
m.Float64Constant(0.25));
Node* stack2 = m.AddNode(m.common()->TypedStateValues(&int32x2_type),
m.Int32Constant(44), m.Int32Constant(45));
Node* parameters2 = m.AddNode(
m.common()->TypedStateValues(&int32_type, SparseInputMask::Dense()),
m.Int32Constant(43));
Node* locals2 = m.AddNode(
m.common()->TypedStateValues(&float64_type, SparseInputMask::Dense()),
m.Float64Constant(0.25));
Node* stack2 = m.AddNode(
m.common()->TypedStateValues(&int32x2_type, SparseInputMask::Dense()),
m.Int32Constant(44), m.Int32Constant(45));
Node* state_node = m.AddNode(
m.common()->FrameState(bailout_id_before, OutputFrameStateCombine::Push(),
m.GetFrameStateFunctionInfo(1, 1)),
......
......@@ -46,7 +46,8 @@ class JSCreateLoweringTest : public TypedGraphTest {
}
Node* FrameState(Handle<SharedFunctionInfo> shared, Node* outer_frame_state) {
Node* state_values = graph()->NewNode(common()->StateValues(0));
Node* state_values =
graph()->NewNode(common()->StateValues(0, SparseInputMask::Dense()));
return graph()->NewNode(
common()->FrameState(
BailoutId::None(), OutputFrameStateCombine::Ignore(),
......
......@@ -28,7 +28,8 @@ class LivenessAnalysisTest : public GraphTest {
jsgraph_(isolate(), graph(), common(), &javascript_, nullptr,
&machine_),
analyzer_(locals_count, false, zone()),
empty_values_(graph()->NewNode(common()->StateValues(0), 0, nullptr)),
empty_values_(graph()->NewNode(
common()->StateValues(0, SparseInputMask::Dense()), 0, nullptr)),
next_checkpoint_id_(0),
current_block_(nullptr) {}
......@@ -48,7 +49,8 @@ class LivenessAnalysisTest : public GraphTest {
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_);
const Operator* locals_op =
common()->StateValues(locals_count_, SparseInputMask::Dense());
ZoneVector<Node*> local_inputs(locals_count_, nullptr, zone());
for (int i = 0; i < locals_count_; i++) {
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "src/compiler/state-values-utils.h"
#include "src/bit-vector.h"
#include "test/unittests/compiler/graph-unittest.h"
#include "test/unittests/compiler/node-test-utils.h"
#include "test/unittests/test-utils.h"
......@@ -18,8 +19,9 @@ class StateValuesIteratorTest : public GraphTest {
Node* StateValuesFromVector(NodeVector* nodes) {
int count = static_cast<int>(nodes->size());
return graph()->NewNode(common()->StateValues(count), count,
count == 0 ? nullptr : &(nodes->front()));
return graph()->NewNode(
common()->StateValues(count, SparseInputMask::Dense()), count,
count == 0 ? nullptr : &(nodes->front()));
}
};
......@@ -107,7 +109,8 @@ TEST_F(StateValuesIteratorTest, TreeFromVector) {
// Build the tree.
StateValuesCache builder(&jsgraph);
Node* values_node = builder.GetNodeForValues(
inputs.size() == 0 ? nullptr : &(inputs.front()), inputs.size());
inputs.size() == 0 ? nullptr : &(inputs.front()), inputs.size(),
nullptr);
// Check the tree contents with vector.
int i = 0;
......@@ -119,6 +122,46 @@ TEST_F(StateValuesIteratorTest, TreeFromVector) {
}
}
TEST_F(StateValuesIteratorTest, TreeFromVectorWithLiveness) {
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, nullptr,
&machine);
// Generate the input vector.
NodeVector inputs(zone());
for (int i = 0; i < count; i++) {
inputs.push_back(Int32Constant(i));
}
// Generate the input liveness.
BitVector liveness(count, zone());
for (int i = 0; i < count; i++) {
if (i % 3 == 0) {
liveness.Add(i);
}
}
// Build the tree.
StateValuesCache builder(&jsgraph);
Node* values_node = builder.GetNodeForValues(
inputs.size() == 0 ? nullptr : &(inputs.front()), inputs.size(),
&liveness);
// Check the tree contents with vector.
int i = 0;
for (StateValuesAccess::TypedNode node : StateValuesAccess(values_node)) {
if (liveness.Contains(i)) {
EXPECT_THAT(node.node, IsInt32Constant(i));
} else {
EXPECT_EQ(node.node, nullptr);
}
i++;
}
EXPECT_EQ(inputs.size(), static_cast<size_t>(i));
}
}
TEST_F(StateValuesIteratorTest, BuildTreeIdentical) {
int sizes[] = {0, 1, 2, 100, 5000, 30000};
......@@ -137,9 +180,46 @@ TEST_F(StateValuesIteratorTest, BuildTreeIdentical) {
// Build two trees from the same data.
StateValuesCache builder(&jsgraph);
Node* node1 = builder.GetNodeForValues(
inputs.size() == 0 ? nullptr : &(inputs.front()), inputs.size());
inputs.size() == 0 ? nullptr : &(inputs.front()), inputs.size(),
nullptr);
Node* node2 = builder.GetNodeForValues(
inputs.size() == 0 ? nullptr : &(inputs.front()), inputs.size(),
nullptr);
// The trees should be equal since the data was the same.
EXPECT_EQ(node1, node2);
}
}
TEST_F(StateValuesIteratorTest, BuildTreeWithLivenessIdentical) {
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, nullptr,
&machine);
// Generate the input vector.
NodeVector inputs(zone());
for (int i = 0; i < count; i++) {
inputs.push_back(Int32Constant(i));
}
// Generate the input liveness.
BitVector liveness(count, zone());
for (int i = 0; i < count; i++) {
if (i % 3 == 0) {
liveness.Add(i);
}
}
// Build two trees from the same data.
StateValuesCache builder(&jsgraph);
Node* node1 = builder.GetNodeForValues(
inputs.size() == 0 ? nullptr : &(inputs.front()), inputs.size(),
&liveness);
Node* node2 = builder.GetNodeForValues(
inputs.size() == 0 ? nullptr : &(inputs.front()), inputs.size());
inputs.size() == 0 ? nullptr : &(inputs.front()), inputs.size(),
&liveness);
// The trees should be equal since the data was the same.
EXPECT_EQ(node1, node2);
......
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