[vm] Defer object pools and object pool entries.

This defers literals, to effectively split binary size for libraries that are more data than code, such as localizations.

TEST=ci
Bug: https://github.com/dart-lang/sdk/issues/41974
Change-Id: I506722037cc0247b90756959e6a6f12bb5021b5c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/183781
Reviewed-by: Alexander Markov <alexmarkov@google.com>
Reviewed-by: Siva Annamalai <asiva@google.com>
Commit-Queue: Ryan Macnak <rmacnak@google.com>
This commit is contained in:
Ryan Macnak 2021-03-04 23:19:30 +00:00 committed by commit-bot@chromium.org
parent 82ad11aeb9
commit dce0b514d4
15 changed files with 851 additions and 309 deletions

View file

@ -0,0 +1,21 @@
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import "split_literals_deferred.dart" deferred as lib;
class Box {
final contents;
const Box(this.contents);
String toString() => "Box($contents)";
}
main() async {
print("Root literal!");
print(const <String>["Root literal in a list!"]);
print(const <String, String>{"key": "Root literal in a map!"});
print(const Box("Root literal in a box!"));
await lib.loadLibrary();
lib.foo();
}

View file

@ -0,0 +1,12 @@
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import "split_literals.dart";
void foo() {
print("Deferred literal!");
print(const <String>["Deferred literal in a list!"]);
print(const <String, String>{"key": "Deferred literal in a map!"});
print(const Box("Deferred literal in a box!"));
}

View file

@ -0,0 +1,115 @@
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import "dart:convert";
import "dart:io";
import "package:expect/expect.dart";
import "package:path/path.dart" as path;
import "use_flag_test_helper.dart";
main(List<String> args) async {
if (!isAOTRuntime) {
return; // Running in JIT: AOT binaries not available.
}
if (Platform.isAndroid) {
return; // SDK tree not available on the test device.
}
// These are the tools we need to be available to run on a given platform:
if (!File(platformDill).existsSync()) {
throw "Cannot run test as $platformDill does not exist";
}
if (!await testExecutable(genSnapshot)) {
throw "Cannot run test as $genSnapshot not available";
}
sanitizedPartitioning(manifest) {
// Filter core libraries, relativize URIs, and sort to make the results less
// sensitive to compiler or test harness changes.
print(manifest);
var units = <List<String>>[];
for (var unit in manifest['loadingUnits']) {
var uris = <String>[];
for (var uri in unit['libraries']) {
if (uri.startsWith("dart:")) continue;
uris.add(Uri.parse(uri).pathSegments.last);
}
uris.sort();
units.add(uris);
}
units.sort((a, b) => a.first.compareTo(b.first));
print(units);
return units;
}
await withTempDir("split-literals-test", (String tempDir) async {
final source =
path.join(sdkDir, "runtime/tests/vm/dart_2/split_literals.dart");
final dill = path.join(tempDir, "split_literals.dart.dill");
final snapshot = path.join(tempDir, "split_literals.so");
final manifest = path.join(tempDir, "split_literals.txt");
final deferredSnapshot = snapshot + "-2.part.so";
// Compile source to kernel.
await run(genKernel, <String>[
"--aot",
"--platform=$platformDill",
"-o",
dill,
source,
]);
// Compile kernel to ELF.
await run(genSnapshot, <String>[
"--use_bare_instructions=false", //# object: ok
"--use_bare_instructions=true", //# bare: ok
"--snapshot-kind=app-aot-elf",
"--elf=$snapshot",
"--loading-unit-manifest=$manifest",
dill,
]);
var manifestContent = jsonDecode(await new File(manifest).readAsString());
Expect.equals(2, manifestContent["loadingUnits"].length);
// Note package:expect doesn't do deep equals on collections.
Expect.equals(
"[[split_literals.dart],"
" [split_literals_deferred.dart]]",
sanitizedPartitioning(manifestContent).toString());
Expect.isTrue(await new File(deferredSnapshot).exists());
bool containsSubsequence(haystack, needle) {
outer:
for (var i = 0, n = haystack.length - needle.length; i < n; i++) {
for (var j = 0; j < needle.length; j++) {
if (haystack[i + j] != needle.codeUnitAt(j)) continue outer;
}
return true;
}
return false;
}
var unit_1 = await new File(snapshot).readAsBytes();
Expect.isTrue(containsSubsequence(unit_1, "Root literal!"));
Expect.isTrue(containsSubsequence(unit_1, "Root literal in a list!"));
Expect.isTrue(containsSubsequence(unit_1, "Root literal in a map!"));
Expect.isTrue(containsSubsequence(unit_1, "Root literal in a box!"));
Expect.isTrue(!containsSubsequence(unit_1, "Deferred literal!"));
Expect.isTrue(!containsSubsequence(unit_1, "Deferred literal in a list!"));
Expect.isTrue(!containsSubsequence(unit_1, "Deferred literal in a map!"));
Expect.isTrue(!containsSubsequence(unit_1, "Deferred literal in a box!"));
var unit_2 = await new File(deferredSnapshot).readAsBytes();
Expect.isTrue(!containsSubsequence(unit_2, "Root literal!"));
Expect.isTrue(!containsSubsequence(unit_2, "Root literal in a list!"));
Expect.isTrue(!containsSubsequence(unit_2, "Root literal in a map!"));
Expect.isTrue(!containsSubsequence(unit_2, "Root literal in a box!"));
Expect.isTrue(containsSubsequence(unit_2, "Deferred literal!"));
Expect.isTrue(containsSubsequence(unit_2, "Deferred literal in a list!"));
Expect.isTrue(containsSubsequence(unit_2, "Deferred literal in a map!"));
Expect.isTrue(containsSubsequence(unit_2, "Deferred literal in a box!"));
});
}

View file

@ -0,0 +1,21 @@
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import "split_literals_deferred.dart" deferred as lib;
class Box {
final contents;
const Box(this.contents);
String toString() => "Box($contents)";
}
main() async {
print("Root literal!");
print(const <String>["Root literal in a list!"]);
print(const <String, String>{"key": "Root literal in a map!"});
print(const Box("Root literal in a box!"));
await lib.loadLibrary();
lib.foo();
}

View file

@ -0,0 +1,12 @@
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import "split_literals.dart";
void foo() {
print("Deferred literal!");
print(const <String>["Deferred literal in a list!"]);
print(const <String, String>{"key": "Deferred literal in a map!"});
print(const Box("Deferred literal in a box!"));
}

View file

@ -0,0 +1,115 @@
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import "dart:convert";
import "dart:io";
import "package:expect/expect.dart";
import "package:path/path.dart" as path;
import "use_flag_test_helper.dart";
main(List<String> args) async {
if (!isAOTRuntime) {
return; // Running in JIT: AOT binaries not available.
}
if (Platform.isAndroid) {
return; // SDK tree not available on the test device.
}
// These are the tools we need to be available to run on a given platform:
if (!File(platformDill).existsSync()) {
throw "Cannot run test as $platformDill does not exist";
}
if (!await testExecutable(genSnapshot)) {
throw "Cannot run test as $genSnapshot not available";
}
sanitizedPartitioning(manifest) {
// Filter core libraries, relativize URIs, and sort to make the results less
// sensitive to compiler or test harness changes.
print(manifest);
var units = <List<String>>[];
for (var unit in manifest['loadingUnits']) {
var uris = <String>[];
for (var uri in unit['libraries']) {
if (uri.startsWith("dart:")) continue;
uris.add(Uri.parse(uri).pathSegments.last);
}
uris.sort();
units.add(uris);
}
units.sort((a, b) => a.first.compareTo(b.first));
print(units);
return units;
}
await withTempDir("split-literals-test", (String tempDir) async {
final source =
path.join(sdkDir, "runtime/tests/vm/dart_2/split_literals.dart");
final dill = path.join(tempDir, "split_literals.dart.dill");
final snapshot = path.join(tempDir, "split_literals.so");
final manifest = path.join(tempDir, "split_literals.txt");
final deferredSnapshot = snapshot + "-2.part.so";
// Compile source to kernel.
await run(genKernel, <String>[
"--aot",
"--platform=$platformDill",
"-o",
dill,
source,
]);
// Compile kernel to ELF.
await run(genSnapshot, <String>[
"--use_bare_instructions=false", //# object: ok
"--use_bare_instructions=true", //# bare: ok
"--snapshot-kind=app-aot-elf",
"--elf=$snapshot",
"--loading-unit-manifest=$manifest",
dill,
]);
var manifestContent = jsonDecode(await new File(manifest).readAsString());
Expect.equals(2, manifestContent["loadingUnits"].length);
// Note package:expect doesn't do deep equals on collections.
Expect.equals(
"[[split_literals.dart],"
" [split_literals_deferred.dart]]",
sanitizedPartitioning(manifestContent).toString());
Expect.isTrue(await new File(deferredSnapshot).exists());
bool containsSubsequence(haystack, needle) {
outer:
for (var i = 0, n = haystack.length - needle.length; i < n; i++) {
for (var j = 0; j < needle.length; j++) {
if (haystack[i + j] != needle.codeUnitAt(j)) continue outer;
}
return true;
}
return false;
}
var unit_1 = await new File(snapshot).readAsBytes();
Expect.isTrue(containsSubsequence(unit_1, "Root literal!"));
Expect.isTrue(containsSubsequence(unit_1, "Root literal in a list!"));
Expect.isTrue(containsSubsequence(unit_1, "Root literal in a map!"));
Expect.isTrue(containsSubsequence(unit_1, "Root literal in a box!"));
Expect.isTrue(!containsSubsequence(unit_1, "Deferred literal!"));
Expect.isTrue(!containsSubsequence(unit_1, "Deferred literal in a list!"));
Expect.isTrue(!containsSubsequence(unit_1, "Deferred literal in a map!"));
Expect.isTrue(!containsSubsequence(unit_1, "Deferred literal in a box!"));
var unit_2 = await new File(deferredSnapshot).readAsBytes();
Expect.isTrue(!containsSubsequence(unit_2, "Root literal!"));
Expect.isTrue(!containsSubsequence(unit_2, "Root literal in a list!"));
Expect.isTrue(!containsSubsequence(unit_2, "Root literal in a map!"));
Expect.isTrue(!containsSubsequence(unit_2, "Root literal in a box!"));
Expect.isTrue(containsSubsequence(unit_2, "Deferred literal!"));
Expect.isTrue(containsSubsequence(unit_2, "Deferred literal in a list!"));
Expect.isTrue(containsSubsequence(unit_2, "Deferred literal in a map!"));
Expect.isTrue(containsSubsequence(unit_2, "Deferred literal in a box!"));
});
}

File diff suppressed because it is too large Load diff

View file

@ -132,16 +132,20 @@ class DeserializationCluster : public ZoneAllocated {
// Allocate memory for all objects in the cluster and write their addresses
// into the ref array. Do not touch this memory.
virtual void ReadAlloc(Deserializer* deserializer, bool is_canonical) = 0;
virtual void ReadAlloc(Deserializer* deserializer, bool stamp_canonical) = 0;
// Initialize the cluster's objects. Do not touch the memory of other objects.
virtual void ReadFill(Deserializer* deserializer, bool is_canonical) = 0;
virtual void ReadFill(Deserializer* deserializer, bool stamp_canonical) = 0;
// Complete any action that requires the full graph to be deserialized, such
// as rehashing.
virtual void PostLoad(Deserializer* deserializer,
const Array& refs,
bool is_canonical) {}
bool canonicalize) {
if (canonicalize) {
FATAL1("%s needs canonicalization but doesn't define PostLoad", name());
}
}
const char* name() const { return name_; }
@ -368,7 +372,7 @@ class Serializer : public ThreadStackResource {
Write<int32_t>(cid);
}
void PrepareInstructions(GrowableArray<CodePtr>* codes);
void PrepareInstructions();
void WriteInstructions(InstructionsPtr instr,
uint32_t unchecked_offset,
CodePtr code,
@ -401,6 +405,7 @@ class Serializer : public ThreadStackResource {
void set_loading_units(GrowableArray<LoadingUnitSerializationData*>* units) {
loading_units_ = units;
}
intptr_t current_loading_unit_id() { return current_loading_unit_id_; }
void set_current_loading_unit_id(intptr_t id) {
current_loading_unit_id_ = id;
}
@ -426,6 +431,13 @@ class Serializer : public ThreadStackResource {
FATAL("Missing ref");
}
bool HasRef(ObjectPtr object) const {
return heap_->GetObjectId(object) != kUnreachableReference;
}
bool IsWritten(ObjectPtr object) const {
return heap_->GetObjectId(object) > num_base_objects_;
}
private:
const char* ReadOnlyObjectType(intptr_t cid);
@ -586,6 +598,8 @@ class Deserializer : public ThreadStackResource {
uword ReadWordWith32BitReads() { return stream_.ReadWordWith32BitReads(); }
intptr_t position() const { return stream_.Position(); }
void set_position(intptr_t p) { stream_.SetPosition(p); }
const uint8_t* CurrentBufferAddress() const {
return stream_.AddressOfCurrentPosition();
}
@ -653,6 +667,7 @@ class Deserializer : public ThreadStackResource {
Zone* zone() const { return zone_; }
Snapshot::Kind kind() const { return kind_; }
FieldTable* initial_field_table() const { return initial_field_table_; }
bool is_non_root_unit() const { return is_non_root_unit_; }
private:
Heap* heap_;

View file

@ -71,8 +71,15 @@ void CodeRelocator::Relocate(bool is_vm_isolate) {
// We're guaranteed to have all calls resolved, since
// * backwards calls are resolved eagerly
// * forward calls are resolved once the target is written
ASSERT(all_unresolved_calls_.IsEmpty());
ASSERT(unresolved_calls_by_destination_.IsEmpty());
if (!all_unresolved_calls_.IsEmpty()) {
for (auto call : all_unresolved_calls_) {
OS::PrintErr("Unresolved call to %s from %s\n",
Object::Handle(call->callee).ToCString(),
Object::Handle(call->caller).ToCString());
}
}
RELEASE_ASSERT(all_unresolved_calls_.IsEmpty());
RELEASE_ASSERT(unresolved_calls_by_destination_.IsEmpty());
// Any trampolines we created must be patched with the right offsets.
auto it = trampolines_by_destination_.GetIterator();

View file

@ -20,16 +20,8 @@ void TypeTestingStubGenerator::BuildOptimizedTypeTestStub(
const Type& type,
const Class& type_class) {
BuildOptimizedTypeTestStubFastCases(assembler, hi, type, type_class);
if (!compiler::IsSameObject(
compiler::NullObject(),
compiler::CastHandle<Object>(slow_type_test_stub))) {
__ GenerateUnRelocatedPcRelativeTailCall();
unresolved_calls->Add(new compiler::UnresolvedPcRelativeCall(
__ CodeSize(), slow_type_test_stub, /*is_tail_call=*/true));
} else {
__ Branch(compiler::Address(
THR, compiler::target::Thread::slow_type_test_entry_point_offset()));
}
__ Branch(compiler::Address(
THR, compiler::target::Thread::slow_type_test_entry_point_offset()));
}
} // namespace dart

View file

@ -20,19 +20,11 @@ void TypeTestingStubGenerator::BuildOptimizedTypeTestStub(
const Type& type,
const Class& type_class) {
BuildOptimizedTypeTestStubFastCases(assembler, hi, type, type_class);
if (!compiler::IsSameObject(
compiler::NullObject(),
compiler::CastHandle<Object>(slow_type_test_stub))) {
__ GenerateUnRelocatedPcRelativeTailCall();
unresolved_calls->Add(new compiler::UnresolvedPcRelativeCall(
__ CodeSize(), slow_type_test_stub, /*is_tail_call=*/true));
} else {
__ ldr(TMP,
compiler::Address(
THR,
compiler::target::Thread::slow_type_test_entry_point_offset()));
__ br(TMP);
}
__ ldr(
TMP,
compiler::Address(
THR, compiler::target::Thread::slow_type_test_entry_point_offset()));
__ br(TMP);
}
} // namespace dart

View file

@ -20,16 +20,8 @@ void TypeTestingStubGenerator::BuildOptimizedTypeTestStub(
const Type& type,
const Class& type_class) {
BuildOptimizedTypeTestStubFastCases(assembler, hi, type, type_class);
if (!compiler::IsSameObject(
compiler::NullObject(),
compiler::CastHandle<Object>(slow_type_test_stub))) {
__ GenerateUnRelocatedPcRelativeTailCall();
unresolved_calls->Add(new compiler::UnresolvedPcRelativeCall(
__ CodeSize(), slow_type_test_stub, /*is_tail_call=*/true));
} else {
__ jmp(compiler::Address(
THR, compiler::target::Thread::slow_type_test_entry_point_offset()));
}
__ jmp(compiler::Address(
THR, compiler::target::Thread::slow_type_test_entry_point_offset()));
}
} // namespace dart

View file

@ -20401,9 +20401,10 @@ AbstractTypePtr Type::Canonicalize(Thread* thread, TrailPtr trail) const {
}
}
ASSERT(this->Equals(type));
ASSERT(type.IsCanonical());
ASSERT(type.IsOld());
return type.ptr();
if (type.IsCanonical()) {
return type.ptr();
}
}
Type& type = Type::Handle(zone);

View file

@ -1371,9 +1371,15 @@ class AssignLoadingUnitsCodeVisitor : public CodeVisitor {
MergeAssignment(obj_, id);
obj_ = code.compressed_stackmaps();
MergeAssignment(obj_, id);
if (!FLAG_use_bare_instructions) {
obj_ = code.object_pool();
MergeAssignment(obj_, id);
}
}
void MergeAssignment(const Object& obj, intptr_t id) {
if (obj.IsNull()) return;
intptr_t old_id = heap_->GetLoadingUnit(obj_.ptr());
if (old_id == WeakTable::kNoValue) {
heap_->SetLoadingUnit(obj_.ptr(), id);

View file

@ -1663,6 +1663,8 @@ class UntaggedObjectPool : public UntaggedObject {
friend class Object;
friend class CodeSerializationCluster;
friend class UnitSerializationRoots;
friend class UnitDeserializationRoots;
};
class UntaggedInstructions : public UntaggedObject {