Reland "[vm, compiler] Prune dictionaries to only contain elements needed at runtime."

This is a reland of commit 6de162e014

Original change's description:
> [vm, compiler] Prune dictionaries to only contain elements needed at runtime.
>
> flutter_gallery
> Isolate(CodeSize): 2116400 -> 1981238 (-6.28%)
>   Total(CodeSize): 7217938 -> 7082600 (-1.87%)
>
> TEST=ci
> Bug: https://github.com/dart-lang/sdk/issues/48910
> Change-Id: I8cd285ddab3a611cd7a2a91d50414be402f8543a
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/244303
> Reviewed-by: Siva Annamalai <asiva@google.com>
> Commit-Queue: Ryan Macnak <rmacnak@google.com>

TEST=ci
Bug: https://github.com/dart-lang/sdk/issues/48910
Change-Id: I3d3ecd04369585547963fb3efff1b3ff0723d8f8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/246990
Commit-Queue: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Siva Annamalai <asiva@google.com>
This commit is contained in:
Ryan Macnak 2022-06-03 00:09:25 +00:00 committed by Commit Bot
parent 3d1c991fcf
commit 4c82eb5836
9 changed files with 259 additions and 23 deletions

View file

@ -1900,12 +1900,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);
}
}
@ -1973,7 +1973,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;
}
@ -1984,8 +1984,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));
}
}
}
}

View file

@ -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);
@ -662,6 +663,8 @@ void Precompiler::DoCompileAll() {
ProgramVisitor::Dedup(T);
}
PruneDictionaries();
if (retained_reasons_writer_ != nullptr) {
reasons_writer.Write();
retained_reasons_writer_ = nullptr;
@ -752,6 +755,7 @@ void Precompiler::AddRoots() {
}
}
if (!main.IsNull()) {
AddApiUse(main);
if (lib.LookupLocalFunction(name) == Function::null()) {
retain_root_library_caches_ = true;
}
@ -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);
@ -1523,6 +1535,7 @@ void Precompiler::AddAnnotatedRoots() {
&reusable_object_handle) ==
EntryPointPragma::kAlways) {
AddInstantiatedClass(cls);
AddApiUse(cls);
}
}
@ -1541,6 +1554,7 @@ void Precompiler::AddAnnotatedRoots() {
if (pragma == EntryPointPragma::kNever) continue;
AddField(field);
AddApiUse(field);
if (!field.is_static()) {
if (pragma != EntryPointPragma::kSetterOnly) {
@ -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 ||
@ -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 &&
@ -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);
}
}
}
@ -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);
}
}
}
@ -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();
@ -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(); }

View file

@ -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.
@ -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();
@ -343,6 +376,7 @@ class Precompiler : public ValueObject {
void DropClasses();
void DropLibraries();
void DiscardCodeObjects();
void PruneDictionaries();
DEBUG_ONLY(FunctionPtr FindUnvisitedRetainedFunction());
@ -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_;

View file

@ -1891,9 +1891,11 @@ void LoadingUnitsMetadataHelper::ReadMetadata(intptr_t node_offset) {
Zone* zone = thread->zone();
intptr_t unit_count = helper_->ReadUInt();
Array& loading_units = Array::Handle(zone, Array::New(unit_count + 1));
Array& loading_unit_uris = Array::Handle(zone, Array::New(unit_count + 1));
LoadingUnit& unit = LoadingUnit::Handle(zone);
LoadingUnit& parent = LoadingUnit::Handle(zone);
Library& lib = Library::Handle(zone);
Array& uris = Array::Handle(zone);
for (int i = 0; i < unit_count; i++) {
intptr_t id = helper_->ReadUInt();
@ -1907,6 +1909,7 @@ void LoadingUnitsMetadataHelper::ReadMetadata(intptr_t node_offset) {
unit.set_parent(parent);
intptr_t library_count = helper_->ReadUInt();
uris = Array::New(library_count);
for (intptr_t j = 0; j < library_count; j++) {
const String& uri =
translation_helper_.DartSymbolPlain(helper_->ReadStringReference());
@ -1915,14 +1918,18 @@ void LoadingUnitsMetadataHelper::ReadMetadata(intptr_t node_offset) {
FATAL1("Missing library: %s\n", uri.ToCString());
}
lib.set_loading_unit(unit);
uris.SetAt(j, uri);
}
loading_units.SetAt(id, unit);
loading_unit_uris.SetAt(id, uris);
}
ObjectStore* object_store = IG->object_store();
ASSERT(object_store->loading_units() == Array::null());
object_store->set_loading_units(loading_units);
ASSERT(object_store->loading_unit_uris() == Array::null());
object_store->set_loading_unit_uris(loading_unit_uris);
}
CallSiteAttributesMetadataHelper::CallSiteAttributesMetadataHelper(

View file

@ -6728,24 +6728,12 @@ DART_EXPORT Dart_Handle Dart_LoadingUnitLibraryUris(intptr_t loading_unit_id) {
DARTSCOPE(Thread::Current());
API_TIMELINE_DURATION(T);
const GrowableObjectArray& result =
GrowableObjectArray::Handle(Z, GrowableObjectArray::New());
const GrowableObjectArray& libs = GrowableObjectArray::Handle(
Z, T->isolate_group()->object_store()->libraries());
Library& lib = Library::Handle(Z);
LoadingUnit& unit = LoadingUnit::Handle(Z);
String& uri = String::Handle(Z);
for (intptr_t i = 0; i < libs.Length(); i++) {
lib ^= libs.At(i);
unit = lib.loading_unit();
if (unit.IsNull() || (unit.id() != loading_unit_id)) {
continue;
}
uri = lib.url();
result.Add(uri);
const Array& loading_units =
Array::Handle(Z, T->isolate_group()->object_store()->loading_unit_uris());
if (loading_unit_id >= 0 && loading_unit_id < loading_units.Length()) {
return Api::NewHandle(T, loading_units.At(loading_unit_id));
}
return Api::NewHandle(T, Array::MakeFixedLength(result));
return Api::NewError("Invalid loading_unit_id");
#endif
}

View file

@ -12460,8 +12460,12 @@ void Library::set_name(const String& name) const {
untag()->set_name(name.ptr());
}
void Library::set_url(const String& name) const {
untag()->set_url(name.ptr());
void Library::set_url(const String& url) const {
untag()->set_url(url.ptr());
}
void Library::set_private_key(const String& key) const {
untag()->set_private_key(key.ptr());
}
void Library::set_kernel_data(const ExternalTypedData& data) const {

View file

@ -5120,6 +5120,7 @@ class Library : public Object {
// the name and url.
void set_name(const String& name) const;
void set_url(const String& url) const;
void set_private_key(const String& key) const;
void set_num_imports(intptr_t value) const;
void set_flags(uint8_t flags) const;

View file

@ -262,6 +262,7 @@ class ObjectPointerVisitor;
RW(Array, dispatch_table_code_entries) \
RW(GrowableObjectArray, instructions_tables) \
RW(Array, obfuscation_map) \
RW(Array, loading_unit_uris) \
RW(GrowableObjectArray, ffi_callback_functions) \
RW(Class, ffi_pointer_class) \
RW(Class, ffi_native_type_class) \

View file

@ -2543,6 +2543,7 @@ Datagram _makeDatagram(
}
@patch
@pragma("vm:entry-point")
class ResourceHandle {
factory ResourceHandle.fromFile(RandomAccessFile file) {
int fd = (file as _RandomAccessFile).fd;