Skip to content

Commit

Permalink
[vm, compiler] Prune dictionaries to only contain elements needed at …
Browse files Browse the repository at this point in the history
…runtime.

flutter_gallery
Isolate(CodeSize): 2116400 -> 1981238 (-6.28%)
  Total(CodeSize): 7217938 -> 7082600 (-1.87%)

TEST=ci
Bug: #48910
Change-Id: I8cd285ddab3a611cd7a2a91d50414be402f8543a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/244303
Reviewed-by: Siva Annamalai <[email protected]>
Commit-Queue: Ryan Macnak <[email protected]>
  • Loading branch information
rmacnak-google authored and Commit Bot committed May 25, 2022
1 parent c1811a9 commit 6de162e
Show file tree
Hide file tree
Showing 9 changed files with 259 additions and 23 deletions.
18 changes: 14 additions & 4 deletions runtime/vm/app_snapshot.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1873,12 +1873,12 @@ class CodeSerializationCluster : public SerializationCluster {
// in the current loading unit).
ObjectPoolPtr pool = code->untag()->object_pool_;
if (s->kind() == Snapshot::kFullAOT) {
TracePool(s, pool, /*only_code=*/is_deferred);
TracePool(s, pool, /*only_call_targets=*/is_deferred);
} else {
if (s->InCurrentLoadingUnitOrRoot(pool)) {
s->Push(pool);
} else {
TracePool(s, pool, /*only_code=*/true);
TracePool(s, pool, /*only_call_targets=*/true);
}
}

Expand Down Expand Up @@ -1946,7 +1946,7 @@ class CodeSerializationCluster : public SerializationCluster {
#endif
}

void TracePool(Serializer* s, ObjectPoolPtr pool, bool only_code) {
void TracePool(Serializer* s, ObjectPoolPtr pool, bool only_call_targets) {
if (pool == ObjectPool::null()) {
return;
}
Expand All @@ -1957,8 +1957,18 @@ class CodeSerializationCluster : public SerializationCluster {
auto entry_type = ObjectPool::TypeBits::decode(entry_bits[i]);
if (entry_type == ObjectPool::EntryType::kTaggedObject) {
const ObjectPtr target = pool->untag()->data()[i].raw_obj_;
if (!only_code || target->IsCode()) {
// A field is a call target because its initializer may be called
// indirectly by passing the field to the runtime. A const closure
// is a call target because its function may be called indirectly
// via a closure call.
if (!only_call_targets || target->IsCode() || target->IsFunction() ||
target->IsField() || target->IsClosure()) {
s->Push(target);
} else {
intptr_t cid = target->GetClassIdMayBeSmi();
if (cid >= kNumPredefinedCids) {
s->Push(s->isolate_group()->class_table()->At(cid));
}
}
}
}
Expand Down
189 changes: 189 additions & 0 deletions runtime/vm/compiler/aot/precompiler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ Precompiler::Precompiler(Thread* thread)
typeparams_to_retain_(),
consts_to_retain_(),
seen_table_selectors_(),
api_uses_(),
error_(Error::Handle()),
get_runtime_type_is_unique_(false) {
ASSERT(Precompiler::singleton_ == NULL);
Expand Down Expand Up @@ -662,6 +663,8 @@ void Precompiler::DoCompileAll() {
ProgramVisitor::Dedup(T);
}

PruneDictionaries();

if (retained_reasons_writer_ != nullptr) {
reasons_writer.Write();
retained_reasons_writer_ = nullptr;
Expand Down Expand Up @@ -752,6 +755,7 @@ void Precompiler::AddRoots() {
}
}
if (!main.IsNull()) {
AddApiUse(main);
if (lib.LookupLocalFunction(name) == Function::null()) {
retain_root_library_caches_ = true;
}
Expand Down Expand Up @@ -1464,6 +1468,14 @@ bool Precompiler::IsHitByTableSelector(const Function& function) {
return seen_table_selectors_.HasKey(selector_id);
}

void Precompiler::AddApiUse(const Object& obj) {
api_uses_.Insert(&Object::ZoneHandle(Z, obj.ptr()));
}

bool Precompiler::HasApiUse(const Object& obj) {
return api_uses_.HasKey(&obj);
}

void Precompiler::AddInstantiatedClass(const Class& cls) {
if (is_tracing()) {
tracer_->WriteClassInstantiationRef(cls);
Expand Down Expand Up @@ -1523,6 +1535,7 @@ void Precompiler::AddAnnotatedRoots() {
&reusable_object_handle) ==
EntryPointPragma::kAlways) {
AddInstantiatedClass(cls);
AddApiUse(cls);
}
}

Expand All @@ -1541,6 +1554,7 @@ void Precompiler::AddAnnotatedRoots() {
if (pragma == EntryPointPragma::kNever) continue;

AddField(field);
AddApiUse(field);

if (!field.is_static()) {
if (pragma != EntryPointPragma::kSetterOnly) {
Expand Down Expand Up @@ -1569,6 +1583,7 @@ void Precompiler::AddAnnotatedRoots() {
type == EntryPointPragma::kCallOnly) {
functions_with_entry_point_pragmas_.Insert(function);
AddFunction(function, RetainReasons::kEntryPointPragma);
AddApiUse(function);
}

if ((type == EntryPointPragma::kAlways ||
Expand All @@ -1578,10 +1593,16 @@ void Precompiler::AddAnnotatedRoots() {
function2 = function.ImplicitClosureFunction();
functions_with_entry_point_pragmas_.Insert(function2);
AddFunction(function2, RetainReasons::kEntryPointPragma);

// Not `function2`: Dart_GetField will lookup the regular function
// and get the implicit closure function from that.
AddApiUse(function);
}

if (function.IsGenerativeConstructor()) {
AddInstantiatedClass(cls);
AddApiUse(function);
AddApiUse(cls);
}
}
if (function.kind() == UntaggedFunction::kImplicitGetter &&
Expand All @@ -1591,6 +1612,7 @@ void Precompiler::AddAnnotatedRoots() {
if (function.accessor_field() == field.ptr()) {
functions_with_entry_point_pragmas_.Insert(function);
AddFunction(function, RetainReasons::kImplicitGetter);
AddApiUse(function);
}
}
}
Expand All @@ -1601,6 +1623,7 @@ void Precompiler::AddAnnotatedRoots() {
if (function.accessor_field() == field.ptr()) {
functions_with_entry_point_pragmas_.Insert(function);
AddFunction(function, RetainReasons::kImplicitSetter);
AddApiUse(function);
}
}
}
Expand All @@ -1611,9 +1634,15 @@ void Precompiler::AddAnnotatedRoots() {
if (function.accessor_field() == field.ptr()) {
functions_with_entry_point_pragmas_.Insert(function);
AddFunction(function, RetainReasons::kImplicitStaticGetter);
AddApiUse(function);
}
}
}
if (function.is_native()) {
// The embedder will need to lookup this library to provide the native
// resolver, even if there are no embedder calls into the library.
AddApiUse(lib);
}
}

implicit_getters = GrowableObjectArray::null();
Expand Down Expand Up @@ -3069,6 +3098,166 @@ void Precompiler::DiscardCodeObjects() {
}
}

void Precompiler::PruneDictionaries() {
// PRODUCT-only: pruning interferes with various uses of the service protocol,
// including heap analysis tools.
#if defined(PRODUCT)
class PruneDictionariesVisitor {
public:
GrowableObjectArrayPtr PruneLibraries(
const GrowableObjectArray& libraries) {
for (intptr_t i = 0; i < libraries.Length(); i++) {
lib_ ^= libraries.At(i);
bool retain = PruneLibrary(lib_);
if (retain) {
lib_.set_index(retained_libraries_.Length());
retained_libraries_.Add(lib_);
} else {
lib_.set_index(-1);
lib_.set_private_key(null_string_);
}
}

Library::RegisterLibraries(Thread::Current(), retained_libraries_);
return retained_libraries_.ptr();
}

bool PruneLibrary(const Library& lib) {
dict_ = lib.dictionary();
intptr_t dict_size = dict_.Length() - 1;
intptr_t used = 0;
for (intptr_t i = 0; i < dict_size; i++) {
entry_ = dict_.At(i);
if (entry_.IsNull()) continue;

bool retain = false;
if (entry_.IsClass()) {
// dart:async: Fix async stack trace lookups in dart:async to annotate
// entry points or fail gracefully.
// dart:core, dart:collection, dart:typed_data: Isolate messaging
// between groups allows any class in these libraries.
retain = PruneClass(Class::Cast(entry_)) ||
(lib.url() == Symbols::DartAsync().ptr()) ||
(lib.url() == Symbols::DartCore().ptr()) ||
(lib.url() == Symbols::DartCollection().ptr()) ||
(lib.url() == Symbols::DartTypedData().ptr());
} else if (entry_.IsFunction() || entry_.IsField()) {
retain = precompiler_->HasApiUse(entry_);
} else {
FATAL("Unexpected library entry: %s", entry_.ToCString());
}
if (retain) {
used++;
} else {
dict_.SetAt(i, Object::null_object());
}
}
lib.RehashDictionary(dict_, used * 4 / 3 + 1);

bool retain = used > 0;
cls_ = lib.toplevel_class();
if (PruneClass(cls_)) {
retain = true;
}
if (lib.is_dart_scheme()) {
retain = true;
}
if (lib.ptr() == root_lib_.ptr()) {
retain = true;
}
if (precompiler_->HasApiUse(lib)) {
retain = true;
}
return retain;
}

bool PruneClass(const Class& cls) {
bool retain = precompiler_->HasApiUse(cls);

functions_ = cls.functions();
retained_functions_ = GrowableObjectArray::New();
for (intptr_t i = 0; i < functions_.Length(); i++) {
function_ ^= functions_.At(i);
if (precompiler_->HasApiUse(function_)) {
retained_functions_.Add(function_);
retain = true;
} else if (precompiler_->functions_called_dynamically_.ContainsKey(
function_)) {
retained_functions_.Add(function_);
// No `retain = true`: the function must appear in the method
// dictionary for lookup, but the class may still be removed from the
// library.
}
}
if (retained_functions_.Length() > 0) {
functions_ = Array::MakeFixedLength(retained_functions_);
cls.SetFunctions(functions_);
} else {
cls.SetFunctions(Object::empty_array());
}

fields_ = cls.fields();
retained_fields_ = GrowableObjectArray::New();
for (intptr_t i = 0; i < fields_.Length(); i++) {
field_ ^= fields_.At(i);
if (precompiler_->HasApiUse(field_)) {
retained_fields_.Add(field_);
retain = true;
}
}
if (retained_fields_.Length() > 0) {
fields_ = Array::MakeFixedLength(retained_fields_);
cls.SetFields(fields_);
} else {
cls.SetFields(Object::empty_array());
}

return retain;
}

explicit PruneDictionariesVisitor(Precompiler* precompiler, Zone* zone)
: precompiler_(precompiler),
lib_(Library::Handle(zone)),
dict_(Array::Handle(zone)),
entry_(Object::Handle(zone)),
cls_(Class::Handle(zone)),
functions_(Array::Handle(zone)),
fields_(Array::Handle(zone)),
function_(Function::Handle(zone)),
field_(Field::Handle(zone)),
retained_functions_(GrowableObjectArray::Handle(zone)),
retained_fields_(GrowableObjectArray::Handle(zone)),
retained_libraries_(
GrowableObjectArray::Handle(zone, GrowableObjectArray::New())),
root_lib_(Library::Handle(
zone,
precompiler->isolate_group()->object_store()->root_library())),
null_string_(String::Handle(zone)) {}

private:
Precompiler* const precompiler_;
Library& lib_;
Array& dict_;
Object& entry_;
Class& cls_;
Array& functions_;
Array& fields_;
Function& function_;
Field& field_;
GrowableObjectArray& retained_functions_;
GrowableObjectArray& retained_fields_;
const GrowableObjectArray& retained_libraries_;
const Library& root_lib_;
const String& null_string_;
};

HANDLESCOPE(T);
SafepointWriteRwLocker ml(T, T->isolate_group()->program_lock());
PruneDictionariesVisitor visitor(this, Z);
libraries_ = visitor.PruneLibraries(libraries_);
#endif // defined(PRODUCT)
}

// Traits for the HashTable template.
struct CodeKeyTraits {
static uint32_t Hash(const Object& key) { return Code::Cast(key).Size(); }
Expand Down
35 changes: 35 additions & 0 deletions runtime/vm/compiler/aot/precompiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,37 @@ class TypeArgumentsKeyValueTrait {

typedef DirectChainedHashMap<TypeArgumentsKeyValueTrait> TypeArgumentsSet;

class ProgramElementKeyValueTrait {
public:
// Typedefs needed for the DirectChainedHashMap template.
typedef const Object* Key;
typedef const Object* Value;
typedef const Object* Pair;

static Key KeyOf(Pair kv) { return kv; }

static Value ValueOf(Pair kv) { return kv; }

static inline uword Hash(Key key) {
if (key->IsFunction()) {
return Function::Cast(*key).Hash();
} else if (key->IsField()) {
return Field::Cast(*key).kernel_offset();
} else if (key->IsClass()) {
return Class::Cast(*key).kernel_offset();
} else if (key->IsLibrary()) {
return Library::Cast(*key).index();
}
FATAL("Unexpected type: %s\n", key->ToCString());
}

static inline bool IsKeyEqual(Pair pair, Key key) {
return pair->ptr() == key->ptr();
}
};

typedef DirectChainedHashMap<ProgramElementKeyValueTrait> ProgramElementSet;

class InstanceKeyValueTrait {
public:
// Typedefs needed for the DirectChainedHashMap template.
Expand Down Expand Up @@ -321,6 +352,8 @@ class Precompiler : public ValueObject {
bool IsHitByTableSelector(const Function& function);
// Returns the reason if the function must be retained, otherwise nullptr.
const char* MustRetainFunction(const Function& function);
void AddApiUse(const Object& obj);
bool HasApiUse(const Object& obj);

void ProcessFunction(const Function& function);
void CheckForNewDynamicFunctions();
Expand All @@ -343,6 +376,7 @@ class Precompiler : public ValueObject {
void DropClasses();
void DropLibraries();
void DiscardCodeObjects();
void PruneDictionaries();

DEBUG_ONLY(FunctionPtr FindUnvisitedRetainedFunction());

Expand Down Expand Up @@ -393,6 +427,7 @@ class Precompiler : public ValueObject {
TypeParameterSet typeparams_to_retain_;
InstanceSet consts_to_retain_;
TableSelectorSet seen_table_selectors_;
ProgramElementSet api_uses_;
Error& error_;

compiler::DispatchTableGenerator* dispatch_table_generator_;
Expand Down
Loading

0 comments on commit 6de162e

Please sign in to comment.