Use ScopedArenaAllocator in GVN.
Memory needed to compile the two most expensive methods for
aosp_angler-userdebug boot image:
BatteryStats.dumpCheckinLocked() : 20.0MiB -> 19.7MiB (-331KiB)
BatteryStats.dumpLocked(): 39.9MiB -> 39.4MiB (-458KiB)
Test: m test-art-host-gtest
Test: testrunner.py --host --optimizing
Bug: 64312607
Change-Id: I4bbb21bf3ecc4b91db7c374737c5f0e0cb7fa462
diff --git a/compiler/optimizing/gvn.cc b/compiler/optimizing/gvn.cc
index c09e5df..813772e 100644
--- a/compiler/optimizing/gvn.cc
+++ b/compiler/optimizing/gvn.cc
@@ -17,7 +17,8 @@
#include "gvn.h"
#include "base/arena_bit_vector.h"
-#include "base/arena_containers.h"
+#include "base/scoped_arena_allocator.h"
+#include "base/scoped_arena_containers.h"
#include "base/bit_vector-inl.h"
#include "side_effects_analysis.h"
#include "utils.h"
@@ -36,7 +37,7 @@
class ValueSet : public ArenaObject<kArenaAllocGvn> {
public:
// Constructs an empty ValueSet which owns all its buckets.
- explicit ValueSet(ArenaAllocator* allocator)
+ explicit ValueSet(ScopedArenaAllocator* allocator)
: allocator_(allocator),
num_buckets_(kMinimumNumberOfBuckets),
buckets_(allocator->AllocArray<Node*>(num_buckets_, kArenaAllocGvn)),
@@ -44,12 +45,13 @@
num_entries_(0u) {
// ArenaAllocator returns zeroed memory, so no need to set buckets to null.
DCHECK(IsPowerOfTwo(num_buckets_));
+ std::fill_n(buckets_, num_buckets_, nullptr);
buckets_owned_.SetInitialBits(num_buckets_);
}
// Copy constructor. Depending on the load factor, it will either make a deep
// copy (all buckets owned) or a shallow one (buckets pointing to the parent).
- ValueSet(ArenaAllocator* allocator, const ValueSet& other)
+ ValueSet(ScopedArenaAllocator* allocator, const ValueSet& other)
: allocator_(allocator),
num_buckets_(other.IdealBucketCount()),
buckets_(allocator->AllocArray<Node*>(num_buckets_, kArenaAllocGvn)),
@@ -58,7 +60,7 @@
// ArenaAllocator returns zeroed memory, so entries of buckets_ and
// buckets_owned_ are initialized to null and false, respectively.
DCHECK(IsPowerOfTwo(num_buckets_));
- PopulateFromInternal(other, /* is_dirty */ false);
+ PopulateFromInternal(other);
}
// Erases all values in this set and populates it with values from `other`.
@@ -66,7 +68,7 @@
if (this == &other) {
return;
}
- PopulateFromInternal(other, /* is_dirty */ true);
+ PopulateFromInternal(other);
}
// Returns true if `this` has enough buckets so that if `other` is copied into
@@ -159,33 +161,19 @@
private:
// Copies all entries from `other` to `this`.
- // If `is_dirty` is set to true, existing data will be wiped first. It is
- // assumed that `buckets_` and `buckets_owned_` are zero-allocated otherwise.
- void PopulateFromInternal(const ValueSet& other, bool is_dirty) {
+ void PopulateFromInternal(const ValueSet& other) {
DCHECK_NE(this, &other);
DCHECK_GE(num_buckets_, other.IdealBucketCount());
if (num_buckets_ == other.num_buckets_) {
// Hash table remains the same size. We copy the bucket pointers and leave
// all buckets_owned_ bits false.
- if (is_dirty) {
- buckets_owned_.ClearAllBits();
- } else {
- DCHECK_EQ(buckets_owned_.NumSetBits(), 0u);
- }
+ buckets_owned_.ClearAllBits();
memcpy(buckets_, other.buckets_, num_buckets_ * sizeof(Node*));
} else {
// Hash table size changes. We copy and rehash all entries, and set all
// buckets_owned_ bits to true.
- if (is_dirty) {
- memset(buckets_, 0, num_buckets_ * sizeof(Node*));
- } else {
- if (kIsDebugBuild) {
- for (size_t i = 0; i < num_buckets_; ++i) {
- DCHECK(buckets_[i] == nullptr) << i;
- }
- }
- }
+ std::fill_n(buckets_, num_buckets_, nullptr);
for (size_t i = 0; i < other.num_buckets_; ++i) {
for (Node* node = other.buckets_[i]; node != nullptr; node = node->GetNext()) {
size_t new_index = BucketIndex(node->GetHashCode());
@@ -208,7 +196,7 @@
Node* GetNext() const { return next_; }
void SetNext(Node* node) { next_ = node; }
- Node* Dup(ArenaAllocator* allocator, Node* new_next = nullptr) {
+ Node* Dup(ScopedArenaAllocator* allocator, Node* new_next = nullptr) {
return new (allocator) Node(instruction_, hash_code_, new_next);
}
@@ -326,7 +314,7 @@
return hash_code & (num_buckets_ - 1);
}
- ArenaAllocator* const allocator_;
+ ScopedArenaAllocator* const allocator_;
// The internal bucket implementation of the set.
size_t const num_buckets_;
@@ -350,15 +338,16 @@
*/
class GlobalValueNumberer : public ValueObject {
public:
- GlobalValueNumberer(ArenaAllocator* allocator,
- HGraph* graph,
+ GlobalValueNumberer(HGraph* graph,
const SideEffectsAnalysis& side_effects)
: graph_(graph),
- allocator_(allocator),
+ allocator_(graph->GetArenaStack()),
side_effects_(side_effects),
- sets_(graph->GetBlocks().size(), nullptr, allocator->Adapter(kArenaAllocGvn)),
+ sets_(graph->GetBlocks().size(), nullptr, allocator_.Adapter(kArenaAllocGvn)),
visited_blocks_(
- allocator, graph->GetBlocks().size(), /* expandable */ false, kArenaAllocGvn) {}
+ &allocator_, graph->GetBlocks().size(), /* expandable */ false, kArenaAllocGvn) {
+ visited_blocks_.ClearAllBits();
+ }
void Run();
@@ -368,7 +357,7 @@
void VisitBasicBlock(HBasicBlock* block);
HGraph* graph_;
- ArenaAllocator* const allocator_;
+ ScopedArenaAllocator allocator_;
const SideEffectsAnalysis& side_effects_;
ValueSet* FindSetFor(HBasicBlock* block) const {
@@ -396,7 +385,7 @@
// ValueSet for blocks. Initially null, but for an individual block they
// are allocated and populated by the dominator, and updated by all blocks
// in the path from the dominator to the block.
- ArenaVector<ValueSet*> sets_;
+ ScopedArenaVector<ValueSet*> sets_;
// BitVector which serves as a fast-access map from block id to
// visited/unvisited Boolean.
@@ -407,7 +396,7 @@
void GlobalValueNumberer::Run() {
DCHECK(side_effects_.HasRun());
- sets_[graph_->GetEntryBlock()->GetBlockId()] = new (allocator_) ValueSet(allocator_);
+ sets_[graph_->GetEntryBlock()->GetBlockId()] = new (&allocator_) ValueSet(&allocator_);
// Use the reverse post order to ensure the non back-edge predecessors of a block are
// visited before the block itself.
@@ -424,7 +413,7 @@
// The entry block should only accumulate constant instructions, and
// the builder puts constants only in the entry block.
// Therefore, there is no need to propagate the value set to the next block.
- set = new (allocator_) ValueSet(allocator_);
+ set = new (&allocator_) ValueSet(&allocator_);
} else {
HBasicBlock* dominator = block->GetDominator();
ValueSet* dominator_set = FindSetFor(dominator);
@@ -443,7 +432,7 @@
if (recyclable == nullptr) {
// No block with a suitable ValueSet found. Allocate a new one and
// copy `dominator_set` into it.
- set = new (allocator_) ValueSet(allocator_, *dominator_set);
+ set = new (&allocator_) ValueSet(&allocator_, *dominator_set);
} else {
// Block with a recyclable ValueSet found. Clone `dominator_set` into it.
set = FindSetFor(recyclable);
@@ -566,7 +555,7 @@
}
void GVNOptimization::Run() {
- GlobalValueNumberer gvn(graph_->GetAllocator(), graph_, side_effects_);
+ GlobalValueNumberer gvn(graph_, side_effects_);
gvn.Run();
}