Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[vm/compiler] Further compress the information previously in StackMaps.
Lifting the PC offset in a2bb730 was a small, lightweight change that
gave us big gains, at least on 32-bit architectures. Here, we make
much more invasive changes that will improve the amount of memory used
by the information previously stored in StackMap objects.

Instead of allocating separate objects for StackMaps, we instead compress
all StackMap information for a given Code object into a single object
(CompressedStackMaps, or CSM for short). This replaces the Array used to
store PC offsets (as Smis) and the individual StackMap objects.

While we lose all canonicalization for individual StackMap entries, the
drop in space required to store stack map information more than offsets that.

-----

The impact on AOT snapshot size when compiling the Flutter Gallery
in release mode:

   armv7: Total size -2.58% (Isolate RO: +14.46%, Isolate snapshot: -22.93%)
   armv8: Total size -1.85% (Isolate RO: +15.69%, Isolate snapshot: -22.97%)

The impact on in-memory, not on-disk, size for the Flutter Gallery as seen
in the Observatory while running a profile (not release) build:

   armv7: Drops from 7.1 MB to 6.2MB (-0.9 MB)
   armv8: Drops from 13.5MB to 11.7MB (-1.8 MB)

-----

Bug: #35274
Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-linux-debug-x64-try,vm-kernel-precomp-linux-debug-simarm_x64-try
Change-Id: Ie3bb898d557215146260a560423f5fa27bdff512
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/119640
Commit-Queue: Teagan Strickland <sstrickl@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
  • Loading branch information
sstrickl authored and commit-bot@chromium.org committed Oct 11, 2019
1 parent 377409f commit d77f491
Show file tree
Hide file tree
Showing 24 changed files with 454 additions and 365 deletions.
32 changes: 32 additions & 0 deletions runtime/vm/bitmap.cc
Expand Up @@ -77,6 +77,38 @@ void BitmapBuilder::Print() const {
}
}

void BitmapBuilder::AppendAsBytesTo(GrowableArray<uint8_t>* bytes) const {
// Early return if there are no bits in the payload to copy.
if (Length() == 0) return;

const intptr_t total_size =
Utils::RoundUp(Length(), kBitsPerByte) / kBitsPerByte;
intptr_t payload_size;
intptr_t extra_size;
if (total_size > data_size_in_bytes_) {
// A [BitmapBuilder] does not allocate storage for the trailing 0 bits in
// the backing store, so we need to add additional empty bytes here.
payload_size = data_size_in_bytes_;
extra_size = total_size - data_size_in_bytes_;
} else {
payload_size = total_size;
extra_size = 0;
}
for (intptr_t i = 0; i < payload_size; i++) {
bytes->Add(data_[i]);
}
for (intptr_t i = 0; i < extra_size; i++) {
bytes->Add(0U);
}
// Make sure any bits in the payload beyond the bit length are cleared to
// ensure deterministic snapshots.
#if defined(DEBUG)
if (Length() % kBitsPerByte == 0) return;
const int8_t mask = (1 << (Length() % kBitsPerByte)) - 1;
ASSERT(bytes->Last() == (bytes->Last() & mask));
#endif
}

bool BitmapBuilder::GetBit(intptr_t bit_offset) const {
if (!InRange(bit_offset)) {
return false;
Expand Down
2 changes: 2 additions & 0 deletions runtime/vm/bitmap.h
Expand Up @@ -6,6 +6,7 @@
#define RUNTIME_VM_BITMAP_H_

#include "vm/allocation.h"
#include "vm/growable_array.h"
#include "vm/thread_state.h"
#include "vm/zone.h"

Expand Down Expand Up @@ -43,6 +44,7 @@ class BitmapBuilder : public ZoneAllocated {
void SetRange(intptr_t min, intptr_t max, bool value);

void Print() const;
void AppendAsBytesTo(GrowableArray<uint8_t>* bytes) const;

private:
static const intptr_t kInitialSizeInBytes = 16;
Expand Down
48 changes: 38 additions & 10 deletions runtime/vm/bitmap_test.cc
Expand Up @@ -3,12 +3,25 @@
// BSD-style license that can be found in the LICENSE file.

#include "vm/bitmap.h"

#include "platform/assert.h"
#include "vm/code_descriptors.h"
#include "vm/object.h"
#include "vm/unit_test.h"

namespace dart {

// 0x4 is just a placeholder PC offset because no entry of a CSM should
// have a PC offset of 0, otherwise internal assumptions break.
static const intptr_t kTestPcOffset = 0x4;
static const intptr_t kTestSpillSlotBitCount = 0;

static RawCompressedStackMaps* MapsFromBuilder(BitmapBuilder* bmap) {
CompressedStackMapsBuilder builder;
builder.AddEntry(kTestPcOffset, bmap, kTestSpillSlotBitCount);
return builder.Finalize();
}

ISOLATE_UNIT_TEST_CASE(BitmapBuilder) {
// Test basic bit map builder operations.
BitmapBuilder* builder1 = new BitmapBuilder();
Expand Down Expand Up @@ -36,16 +49,23 @@ ISOLATE_UNIT_TEST_CASE(BitmapBuilder) {
EXPECT_EQ(value, builder1->Get(i));
value = !value;
}
// Create a StackMap object from the builder and verify its contents.
const StackMap& stackmap1 = StackMap::Handle(StackMap::New(builder1, 0));
EXPECT_EQ(1024, stackmap1.Length());
OS::PrintErr("%s\n", stackmap1.ToCString());

// Create a CompressedStackMaps object and verify its contents.
const auto& maps1 = CompressedStackMaps::Handle(MapsFromBuilder(builder1));
CompressedStackMapsIterator it1(maps1);
EXPECT(it1.MoveNext());

EXPECT_EQ(kTestPcOffset, it1.pc_offset());
EXPECT_EQ(kTestSpillSlotBitCount, it1.spill_slot_bit_count());
EXPECT_EQ(1024, it1.length());
value = true;
for (int32_t i = 0; i < 1024; i++) {
EXPECT_EQ(value, stackmap1.IsObject(i));
EXPECT_EQ(value, it1.IsObject(i));
value = !value;
}

EXPECT(!it1.MoveNext());

// Test the SetRange function in the builder.
builder1->SetRange(0, 256, false);
EXPECT_EQ(1024, builder1->Length());
Expand All @@ -62,18 +82,26 @@ ISOLATE_UNIT_TEST_CASE(BitmapBuilder) {
for (int32_t i = 1025; i <= 2048; i++) {
EXPECT(!builder1->Get(i));
}
const StackMap& stackmap2 = StackMap::Handle(StackMap::New(builder1, 0));
EXPECT_EQ(2049, stackmap2.Length());

const auto& maps2 = CompressedStackMaps::Handle(MapsFromBuilder(builder1));
CompressedStackMapsIterator it2(maps2);
EXPECT(it2.MoveNext());

EXPECT_EQ(kTestPcOffset, it2.pc_offset());
EXPECT_EQ(kTestSpillSlotBitCount, it2.spill_slot_bit_count());
EXPECT_EQ(2049, it2.length());
for (int32_t i = 0; i <= 256; i++) {
EXPECT(!stackmap2.IsObject(i));
EXPECT(!it2.IsObject(i));
}
for (int32_t i = 257; i <= 1024; i++) {
EXPECT(stackmap2.IsObject(i));
EXPECT(it2.IsObject(i));
}
for (int32_t i = 1025; i <= 2048; i++) {
EXPECT(!stackmap2.IsObject(i));
EXPECT(!it2.IsObject(i));
}

EXPECT(!it2.MoveNext());

// Test using SetLength to shorten the builder, followed by lengthening.
builder1->SetLength(747);
EXPECT_EQ(747, builder1->Length());
Expand Down
2 changes: 1 addition & 1 deletion runtime/vm/class_id.h
Expand Up @@ -31,7 +31,7 @@ namespace dart {
V(ObjectPool) \
V(PcDescriptors) \
V(CodeSourceMap) \
V(StackMap) \
V(CompressedStackMaps) \
V(LocalVarDescriptors) \
V(ExceptionHandlers) \
V(Context) \
Expand Down
16 changes: 9 additions & 7 deletions runtime/vm/clustered_snapshot.cc
Expand Up @@ -1387,7 +1387,7 @@ class CodeSerializationCluster : public SerializationCluster {
#else
s->Push(code->ptr()->catch_entry_.variables_);
#endif
s->Push(code->ptr()->stackmaps_);
s->Push(code->ptr()->compressed_stackmaps_);
if (!FLAG_dwarf_stack_traces) {
s->Push(code->ptr()->inlined_id_to_function_);
s->Push(code->ptr()->code_source_map_);
Expand Down Expand Up @@ -1445,7 +1445,7 @@ class CodeSerializationCluster : public SerializationCluster {
#else
WriteField(code, catch_entry_.variables_);
#endif
WriteField(code, stackmaps_);
WriteField(code, compressed_stackmaps_);
if (FLAG_dwarf_stack_traces) {
WriteFieldValue(inlined_id_to_function_, Array::null());
WriteFieldValue(code_source_map_, CodeSourceMap::null());
Expand Down Expand Up @@ -1555,7 +1555,8 @@ class CodeDeserializationCluster : public DeserializationCluster {
code->ptr()->catch_entry_.variables_ =
reinterpret_cast<RawSmi*>(d->ReadRef());
#endif
code->ptr()->stackmaps_ = reinterpret_cast<RawArray*>(d->ReadRef());
code->ptr()->compressed_stackmaps_ =
reinterpret_cast<RawCompressedStackMaps*>(d->ReadRef());
code->ptr()->inlined_id_to_function_ =
reinterpret_cast<RawArray*>(d->ReadRef());
code->ptr()->code_source_map_ =
Expand Down Expand Up @@ -1902,7 +1903,7 @@ class PcDescriptorsDeserializationCluster : public DeserializationCluster {
};

#if !defined(DART_PRECOMPILED_RUNTIME)
// PcDescriptor, StackMap, OneByteString, TwoByteString
// PcDescriptor, CompressedStackMaps, OneByteString, TwoByteString
class RODataSerializationCluster : public SerializationCluster {
public:
RODataSerializationCluster(const char* name, intptr_t cid)
Expand Down Expand Up @@ -4447,8 +4448,9 @@ SerializationCluster* Serializer::NewClusterForClass(intptr_t cid) {
return new (Z) RODataSerializationCluster("(RO)PcDescriptors", cid);
case kCodeSourceMapCid:
return new (Z) RODataSerializationCluster("(RO)CodeSourceMap", cid);
case kStackMapCid:
return new (Z) RODataSerializationCluster("(RO)StackMap", cid);
case kCompressedStackMapsCid:
return new (Z)
RODataSerializationCluster("(RO)CompressedStackMaps", cid);
case kOneByteStringCid:
return new (Z) RODataSerializationCluster("(RO)OneByteString", cid);
case kTwoByteStringCid:
Expand Down Expand Up @@ -5070,7 +5072,7 @@ DeserializationCluster* Deserializer::ReadCluster() {
switch (cid) {
case kPcDescriptorsCid:
case kCodeSourceMapCid:
case kStackMapCid:
case kCompressedStackMapsCid:
case kOneByteStringCid:
case kTwoByteStringCid:
return new (Z) RODataDeserializationCluster();
Expand Down
122 changes: 89 additions & 33 deletions runtime/vm/code_descriptors.cc
Expand Up @@ -46,46 +46,102 @@ RawPcDescriptors* DescriptorList::FinalizePcDescriptors(uword entry_point) {
return PcDescriptors::New(&encoded_data_);
}

void StackMapTableBuilder::AddEntry(intptr_t pc_offset,
BitmapBuilder* bitmap,
intptr_t register_bit_count) {
ASSERT(Smi::IsValid(pc_offset));
pc_offset_ = Smi::New(pc_offset);
stack_map_ = StackMap::New(bitmap, register_bit_count);
list_.Add(pc_offset_, Heap::kOld);
list_.Add(stack_map_, Heap::kOld);
}

bool StackMapTableBuilder::Verify() {
intptr_t num_entries = Length();
for (intptr_t i = 1; i < num_entries; i++) {
pc_offset_ = OffsetAt(i - 1);
auto const offset1 = pc_offset_.Value();
pc_offset_ = OffsetAt(i);
auto const offset2 = pc_offset_.Value();
// Ensure there are no duplicates and the entries are sorted.
if (offset1 >= offset2) return false;
// Encode unsigned integer |value| in LEB128 format and store into |data|.
static void EncodeLEB128(GrowableArray<uint8_t>* data, uintptr_t value) {
while (true) {
uint8_t part = value & 0x7f;
value >>= 7;
if (value != 0) part |= 0x80;
data->Add(part);
if (value == 0) break;
}
return true;
}

RawArray* StackMapTableBuilder::FinalizeStackMaps(const Code& code) {
ASSERT(Verify());
intptr_t num_entries = Length();
if (num_entries == 0) {
return Object::empty_array().raw();
}
return Array::MakeFixedLength(list_);
void CompressedStackMapsBuilder::AddEntry(intptr_t pc_offset,
BitmapBuilder* bitmap,
intptr_t spill_slot_bit_count) {
ASSERT(bitmap != nullptr);
ASSERT(pc_offset > last_pc_offset_);
ASSERT(spill_slot_bit_count >= 0 && spill_slot_bit_count <= bitmap->Length());
auto const pc_delta = pc_offset - last_pc_offset_;
auto const non_spill_slot_bit_count = bitmap->Length() - spill_slot_bit_count;
EncodeLEB128(&encoded_bytes_, pc_delta);
EncodeLEB128(&encoded_bytes_, spill_slot_bit_count);
EncodeLEB128(&encoded_bytes_, non_spill_slot_bit_count);
bitmap->AppendAsBytesTo(&encoded_bytes_);
last_pc_offset_ = pc_offset;
}

RawCompressedStackMaps* CompressedStackMapsBuilder::Finalize() const {
if (encoded_bytes_.length() == 0) return CompressedStackMaps::null();
return CompressedStackMaps::New(encoded_bytes_);
}

RawSmi* StackMapTableBuilder::OffsetAt(intptr_t index) const {
pc_offset_ ^= list_.At(2 * index);
return pc_offset_.raw();
// Decode unsigned integer in LEB128 format from |data| and update |byte_index|.
static uintptr_t DecodeLEB128(const uint8_t* data,
const intptr_t data_length,
intptr_t* byte_index) {
ASSERT(*byte_index < data_length);
uword shift = 0;
uintptr_t value = 0;
uint8_t part = 0;
do {
part = data[(*byte_index)++];
value |= static_cast<uintptr_t>(part & 0x7f) << shift;
shift += 7;
} while ((part & 0x80) != 0);

return value;
}

bool CompressedStackMapsIterator::MoveNext() {
// Empty CompressedStackMaps are represented as null values.
if (maps_.IsNull() || next_offset_ >= maps_.payload_size()) return false;
intptr_t offset = next_offset_;

// We decode three LEB128 encoded integers after this, so there should be
// at least three bytes remaining in the payload.
ASSERT(offset <= maps_.payload_size() - 3);
auto const pc_delta =
DecodeLEB128(maps_.Payload(), maps_.payload_size(), &offset);
ASSERT(pc_delta <= kIntptrMax);

ASSERT(offset <= maps_.payload_size() - 2);
auto const spill_slot_bit_count =
DecodeLEB128(maps_.Payload(), maps_.payload_size(), &offset);
ASSERT(spill_slot_bit_count <= kIntptrMax);

ASSERT(offset <= maps_.payload_size() - 1);
auto const non_spill_slot_bit_count =
DecodeLEB128(maps_.Payload(), maps_.payload_size(), &offset);
ASSERT(non_spill_slot_bit_count <= kIntptrMax);

const auto stackmap_bits = spill_slot_bit_count + non_spill_slot_bit_count;
const intptr_t stackmap_size =
Utils::RoundUp(stackmap_bits, kBitsPerByte) >> kBitsPerByteLog2;
const intptr_t space_remaining = maps_.payload_size() - offset;
if (stackmap_size > space_remaining) return false;

// Now that the current entry has been completely decoded without errors, set
// the fields appropriately.
ASSERT(current_pc_offset_ < (kIntptrMax - static_cast<intptr_t>(pc_delta)));
current_pc_offset_ += pc_delta;
current_spill_slot_bit_count_ = spill_slot_bit_count;
current_non_spill_slot_bit_count_ = non_spill_slot_bit_count;
current_bits_offset_ = offset;
next_offset_ = offset + stackmap_size;

return true;
}

RawStackMap* StackMapTableBuilder::MapAt(intptr_t index) const {
stack_map_ ^= list_.At(2 * index + 1);
return stack_map_.raw();
bool CompressedStackMapsIterator::IsObject(intptr_t bit_index) const {
ASSERT(HasLoadedEntry());
ASSERT(bit_index >= 0 && bit_index < length());
const intptr_t byte_index = bit_index >> kBitsPerByteLog2;
const intptr_t bit_remainder = bit_index & (kBitsPerByte - 1);
uint8_t byte_mask = 1U << bit_remainder;
uint8_t byte = maps_.Payload()[current_bits_offset_ + byte_index];
return (byte & byte_mask) != 0;
}

RawExceptionHandlers* ExceptionHandlerList::FinalizeExceptionHandlers(
Expand Down

0 comments on commit d77f491

Please sign in to comment.