Commit fb5c7c87 authored by Jakob Gruber's avatar Jakob Gruber Committed by Commit Bot

Revert "[regalloc] Use an adaptive data structure for live sets"

This reverts commit b3d748a2.

Reason for revert: Regressions, see https://crbug.com/1023423.

Original change's description:
> [regalloc] Use an adaptive data structure for live sets
> 
> Live sets represent sets of live virtual registers at block entry and
> exit points. They are usually sparsely populated; for example, a sample
> taken from Octane2 shows 80% of sampled live sets with a fill ratio of
> 10% or less.
> 
> Prior to this CL, live sets were implemented as a statically-sized bit
> vector. This is fine for low-ish virtual register counts, but becomes
> wasteful at higher numbers.
> 
> This CL attempts to address this issue through an adaptive
> implementation. Small live sets remain bit vectors, while larger sets
> switch to a PersistentMap-based implementation. PersistentMap has very
> memory-efficient add/remove/copy operations.
> 
> Of course, with adaptive data structures we enter the territory of
> parameter fiddling. In this case, two parameters are used:
> kMaxSmallSetSize controls when to switch implementations, and
> kMaxDeletionsBeforePrune controls when pruning (= managing the # of
> deleted entries in the map) sets in.
> 
> On the (degenerate) test case from the linked bug, the register
> allocation zone shrinks from 1008MB to 475MB. For more realistic cases
> I expect savings on the order of 10s of KB.
> 
> Bug: v8:9574
> Change-Id: Id903bbe23f030b418e8d887ef4839c8d65126c52
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1891693
> Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
> Reviewed-by: Thibaud Michaud <thibaudm@chromium.org>
> Commit-Queue: Jakob Gruber <jgruber@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#64872}

TBR=jgruber@chromium.org,tebbi@chromium.org,thibaudm@chromium.org

# Not skipping CQ checks because original CL landed > 1 day ago.

Bug: v8:9574
Change-Id: I5d684198f9c4575a0c892076459cc2c20dce9aec
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1910944Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Commit-Queue: Jakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#64912}
parent c91284ee
...@@ -11,7 +11,6 @@ ...@@ -11,7 +11,6 @@
#include "src/codegen/assembler-inl.h" #include "src/codegen/assembler-inl.h"
#include "src/codegen/tick-counter.h" #include "src/codegen/tick-counter.h"
#include "src/compiler/linkage.h" #include "src/compiler/linkage.h"
#include "src/compiler/persistent-map.h"
#include "src/strings/string-stream.h" #include "src/strings/string-stream.h"
#include "src/utils/vector.h" #include "src/utils/vector.h"
...@@ -96,178 +95,6 @@ int GetByteWidth(MachineRepresentation rep) { ...@@ -96,178 +95,6 @@ int GetByteWidth(MachineRepresentation rep) {
} // namespace } // namespace
// Represents a set of live virtual registers.
// Implemented as an adaptive data structure to handle two extremes of usage
// patterns:
//
// 1. Low-ish virtual register counts should use a statically-sized bit vector
// for constant-time insertions/lookups and compact memory-representation.
// This is expected to be the common case.
// 2. For high virtual register counts, the set is expected to be very sparsely
// populated. In this case, a bit vector would lead to unacceptable memory
// overhead (since it reserves O(capacity) memory instead of O(size)), and
// we use a set of indices instead. The PersistentMap was chosen for its
// memory-efficient add/remove/copy operations.
//
// The maximal capacity of the set is determined at construction.
class LiveSet : public ZoneObject {
private:
// Switch to a set backing store when this limit is exceeded.
// The threshold is fairly arbitrary, picked s.t. benchmarks do not regress.
// It should be a good tradeoff between wasted space and fast/simple bit
// operations on small bit vectors sizes.
static constexpr int kMaxSmallSetSize = 16 * kBitsPerSystemPointer;
using KeyT = int;
using ValueT = bool; // Emulates set semantics in the map.
static constexpr ValueT kNotPresent = false;
static constexpr ValueT kPresent = true;
using PersistentLiveSet = PersistentMap<KeyT, ValueT>;
static PersistentLiveSet* NewPersistentLiveSet(Zone* zone) {
return new (zone->New(sizeof(PersistentLiveSet)))
PersistentLiveSet(zone, kNotPresent);
}
public:
LiveSet(int size, Zone* zone)
: vector_(size <= kMaxSmallSetSize ? new (zone) BitVector(size, zone)
: nullptr),
map_(size <= kMaxSmallSetSize ? nullptr : NewPersistentLiveSet(zone)) {}
void Add(int v) {
if (is_small()) {
vector_->Add(v);
} else {
map_->Set(v, kPresent);
}
}
bool Contains(int v) const {
if (is_small()) return vector_->Contains(v);
return map_->Get(v) == kPresent;
}
void Remove(int v) {
if (is_small()) {
vector_->Remove(v);
} else {
map_->Set(v, kNotPresent);
deletions_++;
MaybePrune();
}
}
void Union(const LiveSet& that) {
if (is_small()) {
vector_->Union(*that.vector_);
} else {
DCHECK(!that.is_small());
// The other map is empty, nothing to do.
if (that.map_->begin() == that.map_->end()) return;
// This map is empty, copy the other map. Note that PersistentMap copies
// have only low, constant memory cost.
if (map_->begin() == map_->end()) {
*map_ = *that.map_;
deletions_ = that.deletions_;
return;
}
// Both are non-empty.
for (const auto& entry : *that.map_) {
if (entry.second == kPresent) map_->Set(entry.first, kPresent);
}
}
}
int Count() const {
if (is_small()) return vector_->Count();
// Slow. Use only for debugging purposes.
int count = 0;
for (const auto& entry : *map_) {
if (entry.second == kPresent) count++;
}
return count;
}
class Iterator {
private:
BitVector empty_vector_;
public:
explicit Iterator(LiveSet* target)
: is_small_(target->is_small()),
small_it_(is_small_ ? target->vector_ : &empty_vector_),
large_it_(is_small_ ? PersistentLiveSet::iterator::end(kNotPresent)
: target->map_->begin()) {}
~Iterator() = default;
bool Done() const {
return is_small_ ? small_it_.Done() : large_it_.is_end();
}
void Advance() {
if (is_small_) {
small_it_.Advance();
} else {
++large_it_;
}
}
int Current() const {
if (is_small_) return small_it_.Current();
return (*large_it_).first;
}
private:
const bool is_small_;
BitVector::Iterator small_it_;
PersistentLiveSet::iterator large_it_;
};
private:
bool is_small() const { return vector_ != nullptr; }
void MaybePrune() {
DCHECK(!is_small());
// The PersistentMap data structure never shrinks by itself; deletions are
// internally treated as insertions of the kNotPresent value. This becomes
// problematic when the data structure begins to drag along more deleted
// keys than non-deleted keys. Pruning addresses this by creating a fresh
// deep copy of the map after a threshold is reached.
//
// Note: This is where the adaptive data structure starts to get hacky.
// Ideally we'd be able to avoid arbitrary parametrization such as
// kMaxDeletions and kMaxSmallSetSize. Maybe it's just a complexity cost we
// have to pay.
// Fairly arbitrary constant, chosen s.t. our tests do not regress.
static constexpr uint16_t kMaxDeletionsBeforePrune = 128;
if (deletions_ < kMaxDeletionsBeforePrune) return;
PersistentLiveSet* new_map = NewPersistentLiveSet(map_->zone());
for (const auto& entry : *map_) {
if (entry.second == kPresent) new_map->Set(entry.first, kPresent);
}
map_ = new_map;
deletions_ = 0;
}
// The decision between backing stores is made once when the LiveSet is
// constructed. The chosen backing store is set while the other remains
// nullptr.
BitVector* const vector_;
PersistentLiveSet* map_;
uint16_t deletions_ = 0;
};
class LiveRangeBound { class LiveRangeBound {
public: public:
explicit LiveRangeBound(LiveRange* range, bool skip) explicit LiveRangeBound(LiveRange* range, bool skip)
...@@ -1766,7 +1593,7 @@ RegisterAllocationData::PhiMapValue* RegisterAllocationData::GetPhiMapValueFor( ...@@ -1766,7 +1593,7 @@ RegisterAllocationData::PhiMapValue* RegisterAllocationData::GetPhiMapValueFor(
bool RegisterAllocationData::ExistsUseWithoutDefinition() { bool RegisterAllocationData::ExistsUseWithoutDefinition() {
bool found = false; bool found = false;
LiveSet::Iterator iterator(live_in_sets()[0]); BitVector::Iterator iterator(live_in_sets()[0]);
while (!iterator.Done()) { while (!iterator.Done()) {
found = true; found = true;
int operand_index = iterator.Current(); int operand_index = iterator.Current();
...@@ -2194,23 +2021,23 @@ LiveRangeBuilder::LiveRangeBuilder(RegisterAllocationData* data, ...@@ -2194,23 +2021,23 @@ LiveRangeBuilder::LiveRangeBuilder(RegisterAllocationData* data,
Zone* local_zone) Zone* local_zone)
: data_(data), phi_hints_(local_zone) {} : data_(data), phi_hints_(local_zone) {}
LiveSet* LiveRangeBuilder::ComputeLiveOut(const InstructionBlock* block, BitVector* LiveRangeBuilder::ComputeLiveOut(const InstructionBlock* block,
RegisterAllocationData* data) { RegisterAllocationData* data) {
size_t block_index = block->rpo_number().ToSize(); size_t block_index = block->rpo_number().ToSize();
LiveSet* live_out = data->live_out_sets()[block_index]; BitVector* live_out = data->live_out_sets()[block_index];
if (live_out == nullptr) { if (live_out == nullptr) {
// Compute live out for the given block, except not including backward // Compute live out for the given block, except not including backward
// successor edges. // successor edges.
Zone* zone = data->allocation_zone(); Zone* zone = data->allocation_zone();
const InstructionSequence* code = data->code(); const InstructionSequence* code = data->code();
live_out = new (zone) LiveSet(code->VirtualRegisterCount(), zone); live_out = new (zone) BitVector(code->VirtualRegisterCount(), zone);
// Process all successor blocks. // Process all successor blocks.
for (const RpoNumber& succ : block->successors()) { for (const RpoNumber& succ : block->successors()) {
// Add values live on entry to the successor. // Add values live on entry to the successor.
if (succ <= block->rpo_number()) continue; if (succ <= block->rpo_number()) continue;
LiveSet* live_in = data->live_in_sets()[succ.ToSize()]; BitVector* live_in = data->live_in_sets()[succ.ToSize()];
if (live_in != nullptr) live_out->Union(*live_in); if (live_in != nullptr) live_out->Union(*live_in);
// All phi input operands corresponding to this successor edge are live // All phi input operands corresponding to this successor edge are live
...@@ -2228,7 +2055,7 @@ LiveSet* LiveRangeBuilder::ComputeLiveOut(const InstructionBlock* block, ...@@ -2228,7 +2055,7 @@ LiveSet* LiveRangeBuilder::ComputeLiveOut(const InstructionBlock* block,
} }
void LiveRangeBuilder::AddInitialIntervals(const InstructionBlock* block, void LiveRangeBuilder::AddInitialIntervals(const InstructionBlock* block,
LiveSet* live_out) { BitVector* live_out) {
// Add an interval that includes the entire block to the live range for // Add an interval that includes the entire block to the live range for
// each live_out value. // each live_out value.
LifetimePosition start = LifetimePosition::GapFromInstructionIndex( LifetimePosition start = LifetimePosition::GapFromInstructionIndex(
...@@ -2236,7 +2063,7 @@ void LiveRangeBuilder::AddInitialIntervals(const InstructionBlock* block, ...@@ -2236,7 +2063,7 @@ void LiveRangeBuilder::AddInitialIntervals(const InstructionBlock* block,
LifetimePosition end = LifetimePosition::InstructionFromInstructionIndex( LifetimePosition end = LifetimePosition::InstructionFromInstructionIndex(
block->last_instruction_index()) block->last_instruction_index())
.NextStart(); .NextStart();
LiveSet::Iterator iterator(live_out); BitVector::Iterator iterator(live_out);
while (!iterator.Done()) { while (!iterator.Done()) {
int operand_index = iterator.Current(); int operand_index = iterator.Current();
TopLevelLiveRange* range = data()->GetOrCreateLiveRangeFor(operand_index); TopLevelLiveRange* range = data()->GetOrCreateLiveRangeFor(operand_index);
...@@ -2396,7 +2223,7 @@ UsePosition* LiveRangeBuilder::Use(LifetimePosition block_start, ...@@ -2396,7 +2223,7 @@ UsePosition* LiveRangeBuilder::Use(LifetimePosition block_start,
} }
void LiveRangeBuilder::ProcessInstructions(const InstructionBlock* block, void LiveRangeBuilder::ProcessInstructions(const InstructionBlock* block,
LiveSet* live) { BitVector* live) {
int block_start = block->first_instruction_index(); int block_start = block->first_instruction_index();
LifetimePosition block_start_position = LifetimePosition block_start_position =
LifetimePosition::GapFromInstructionIndex(block_start); LifetimePosition::GapFromInstructionIndex(block_start);
...@@ -2612,7 +2439,7 @@ void LiveRangeBuilder::ProcessInstructions(const InstructionBlock* block, ...@@ -2612,7 +2439,7 @@ void LiveRangeBuilder::ProcessInstructions(const InstructionBlock* block,
} }
void LiveRangeBuilder::ProcessPhis(const InstructionBlock* block, void LiveRangeBuilder::ProcessPhis(const InstructionBlock* block,
LiveSet* live) { BitVector* live) {
for (PhiInstruction* phi : block->phis()) { for (PhiInstruction* phi : block->phis()) {
// The live range interval already ends at the first instruction of the // The live range interval already ends at the first instruction of the
// block. // block.
...@@ -2734,11 +2561,11 @@ void LiveRangeBuilder::ProcessPhis(const InstructionBlock* block, ...@@ -2734,11 +2561,11 @@ void LiveRangeBuilder::ProcessPhis(const InstructionBlock* block,
} }
void LiveRangeBuilder::ProcessLoopHeader(const InstructionBlock* block, void LiveRangeBuilder::ProcessLoopHeader(const InstructionBlock* block,
LiveSet* live) { BitVector* live) {
DCHECK(block->IsLoopHeader()); DCHECK(block->IsLoopHeader());
// Add a live range stretching from the first loop instruction to the last // Add a live range stretching from the first loop instruction to the last
// for each value live on entry to the header. // for each value live on entry to the header.
LiveSet::Iterator iterator(live); BitVector::Iterator iterator(live);
LifetimePosition start = LifetimePosition::GapFromInstructionIndex( LifetimePosition start = LifetimePosition::GapFromInstructionIndex(
block->first_instruction_index()); block->first_instruction_index());
LifetimePosition end = LifetimePosition::GapFromInstructionIndex( LifetimePosition end = LifetimePosition::GapFromInstructionIndex(
...@@ -2765,7 +2592,7 @@ void LiveRangeBuilder::BuildLiveRanges() { ...@@ -2765,7 +2592,7 @@ void LiveRangeBuilder::BuildLiveRanges() {
data_->tick_counter()->DoTick(); data_->tick_counter()->DoTick();
InstructionBlock* block = InstructionBlock* block =
code()->InstructionBlockAt(RpoNumber::FromInt(block_id)); code()->InstructionBlockAt(RpoNumber::FromInt(block_id));
LiveSet* live = ComputeLiveOut(block, data()); BitVector* live = ComputeLiveOut(block, data());
// Initially consider all live_out values live for the entire block. We // Initially consider all live_out values live for the entire block. We
// will shorten these intervals if necessary. // will shorten these intervals if necessary.
AddInitialIntervals(block, live); AddInitialIntervals(block, live);
...@@ -5121,11 +4948,11 @@ bool LiveRangeConnector::CanEagerlyResolveControlFlow( ...@@ -5121,11 +4948,11 @@ bool LiveRangeConnector::CanEagerlyResolveControlFlow(
void LiveRangeConnector::ResolveControlFlow(Zone* local_zone) { void LiveRangeConnector::ResolveControlFlow(Zone* local_zone) {
// Lazily linearize live ranges in memory for fast lookup. // Lazily linearize live ranges in memory for fast lookup.
LiveRangeFinder finder(data(), local_zone); LiveRangeFinder finder(data(), local_zone);
ZoneVector<LiveSet*>& live_in_sets = data()->live_in_sets(); ZoneVector<BitVector*>& live_in_sets = data()->live_in_sets();
for (const InstructionBlock* block : code()->instruction_blocks()) { for (const InstructionBlock* block : code()->instruction_blocks()) {
if (CanEagerlyResolveControlFlow(block)) continue; if (CanEagerlyResolveControlFlow(block)) continue;
LiveSet* live = live_in_sets[block->rpo_number().ToInt()]; BitVector* live = live_in_sets[block->rpo_number().ToInt()];
LiveSet::Iterator iterator(live); BitVector::Iterator iterator(live);
while (!iterator.Done()) { while (!iterator.Done()) {
data()->tick_counter()->DoTick(); data()->tick_counter()->DoTick();
int vreg = iterator.Current(); int vreg = iterator.Current();
......
...@@ -21,8 +21,6 @@ class TickCounter; ...@@ -21,8 +21,6 @@ class TickCounter;
namespace compiler { namespace compiler {
class LiveSet;
static const int32_t kUnassignedRegister = RegisterConfiguration::kMaxRegisters; static const int32_t kUnassignedRegister = RegisterConfiguration::kMaxRegisters;
enum RegisterKind { GENERAL_REGISTERS, FP_REGISTERS }; enum RegisterKind { GENERAL_REGISTERS, FP_REGISTERS };
...@@ -279,8 +277,8 @@ class RegisterAllocationData final : public ZoneObject { ...@@ -279,8 +277,8 @@ class RegisterAllocationData final : public ZoneObject {
const ZoneVector<TopLevelLiveRange*>& fixed_simd128_live_ranges() const { const ZoneVector<TopLevelLiveRange*>& fixed_simd128_live_ranges() const {
return fixed_simd128_live_ranges_; return fixed_simd128_live_ranges_;
} }
ZoneVector<LiveSet*>& live_in_sets() { return live_in_sets_; } ZoneVector<BitVector*>& live_in_sets() { return live_in_sets_; }
ZoneVector<LiveSet*>& live_out_sets() { return live_out_sets_; } ZoneVector<BitVector*>& live_out_sets() { return live_out_sets_; }
ZoneVector<SpillRange*>& spill_ranges() { return spill_ranges_; } ZoneVector<SpillRange*>& spill_ranges() { return spill_ranges_; }
DelayedReferences& delayed_references() { return delayed_references_; } DelayedReferences& delayed_references() { return delayed_references_; }
InstructionSequence* code() const { return code_; } InstructionSequence* code() const { return code_; }
...@@ -354,8 +352,8 @@ class RegisterAllocationData final : public ZoneObject { ...@@ -354,8 +352,8 @@ class RegisterAllocationData final : public ZoneObject {
const char* const debug_name_; const char* const debug_name_;
const RegisterConfiguration* const config_; const RegisterConfiguration* const config_;
PhiMap phi_map_; PhiMap phi_map_;
ZoneVector<LiveSet*> live_in_sets_; ZoneVector<BitVector*> live_in_sets_;
ZoneVector<LiveSet*> live_out_sets_; ZoneVector<BitVector*> live_out_sets_;
ZoneVector<TopLevelLiveRange*> live_ranges_; ZoneVector<TopLevelLiveRange*> live_ranges_;
ZoneVector<TopLevelLiveRange*> fixed_live_ranges_; ZoneVector<TopLevelLiveRange*> fixed_live_ranges_;
ZoneVector<TopLevelLiveRange*> fixed_float_live_ranges_; ZoneVector<TopLevelLiveRange*> fixed_float_live_ranges_;
...@@ -1105,8 +1103,8 @@ class LiveRangeBuilder final : public ZoneObject { ...@@ -1105,8 +1103,8 @@ class LiveRangeBuilder final : public ZoneObject {
// Phase 3: compute liveness of all virtual register. // Phase 3: compute liveness of all virtual register.
void BuildLiveRanges(); void BuildLiveRanges();
static LiveSet* ComputeLiveOut(const InstructionBlock* block, static BitVector* ComputeLiveOut(const InstructionBlock* block,
RegisterAllocationData* data); RegisterAllocationData* data);
private: private:
using SpillMode = RegisterAllocationData::SpillMode; using SpillMode = RegisterAllocationData::SpillMode;
...@@ -1118,7 +1116,9 @@ class LiveRangeBuilder final : public ZoneObject { ...@@ -1118,7 +1116,9 @@ class LiveRangeBuilder final : public ZoneObject {
Zone* allocation_zone() const { return data()->allocation_zone(); } Zone* allocation_zone() const { return data()->allocation_zone(); }
Zone* code_zone() const { return code()->zone(); } Zone* code_zone() const { return code()->zone(); }
const RegisterConfiguration* config() const { return data()->config(); } const RegisterConfiguration* config() const { return data()->config(); }
ZoneVector<LiveSet*>& live_in_sets() const { return data()->live_in_sets(); } ZoneVector<BitVector*>& live_in_sets() const {
return data()->live_in_sets();
}
// Verification. // Verification.
void Verify() const; void Verify() const;
...@@ -1128,10 +1128,10 @@ class LiveRangeBuilder final : public ZoneObject { ...@@ -1128,10 +1128,10 @@ class LiveRangeBuilder final : public ZoneObject {
bool NextIntervalStartsInDifferentBlocks(const UseInterval* interval) const; bool NextIntervalStartsInDifferentBlocks(const UseInterval* interval) const;
// Liveness analysis support. // Liveness analysis support.
void AddInitialIntervals(const InstructionBlock* block, LiveSet* live_out); void AddInitialIntervals(const InstructionBlock* block, BitVector* live_out);
void ProcessInstructions(const InstructionBlock* block, LiveSet* live); void ProcessInstructions(const InstructionBlock* block, BitVector* live);
void ProcessPhis(const InstructionBlock* block, LiveSet* live); void ProcessPhis(const InstructionBlock* block, BitVector* live);
void ProcessLoopHeader(const InstructionBlock* block, LiveSet* live); void ProcessLoopHeader(const InstructionBlock* block, BitVector* live);
static int FixedLiveRangeID(int index) { return -index - 1; } static int FixedLiveRangeID(int index) { return -index - 1; }
int FixedFPLiveRangeID(int index, MachineRepresentation rep); int FixedFPLiveRangeID(int index, MachineRepresentation rep);
......
...@@ -88,8 +88,6 @@ class PersistentMap { ...@@ -88,8 +88,6 @@ class PersistentMap {
return !(*this == other); return !(*this == other);
} }
Zone* zone() const { return zone_; }
// The iterator produces key-value pairs in the lexicographical order of // The iterator produces key-value pairs in the lexicographical order of
// hash value and key. It produces exactly the key-value pairs where the value // hash value and key. It produces exactly the key-value pairs where the value
// is not the default value. // is not the default value.
......
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