Version 2.13.0-137.0.dev
Merge commit 'dc62c9c3c22453277d357cfcc7f9b010840ac5ca' into 'dev'
diff --git a/runtime/tests/vm/dart/use_trace_precompiler_flag_test.dart b/runtime/tests/vm/dart/use_trace_precompiler_flag_test.dart
index 7d41a71..7ea2d60 100644
--- a/runtime/tests/vm/dart/use_trace_precompiler_flag_test.dart
+++ b/runtime/tests/vm/dart/use_trace_precompiler_flag_test.dart
@@ -67,47 +67,44 @@
buffer.write(retentionFlags[j]);
flags.add(buffer.toString());
}
- await testTracePrecompiler(scriptDill, flags);
+ await testTracePrecompiler(tempDir, scriptDill, flags);
}
});
}
-const _jsonHeaders = {'JSON for function decisions: '};
-
-Future<void> testTracePrecompiler(String scriptDill, List<String> flags) async {
- final result = await runHelper(genSnapshot, <String>[
+Future<void> testTracePrecompiler(
+ String tempDir, String scriptDill, List<String> flags) async {
+ final reasonsFile = path.join(tempDir, 'reasons.json');
+ final snapshot = path.join(tempDir, 'snapshot.so');
+ final result = await run(genSnapshot, <String>[
...flags,
- '--trace-precompiler',
+ '--write-retained-reasons-to=$reasonsFile',
'--snapshot-kind=app-aot-elf',
- '--elf=snapshot.so',
+ '--elf=$snapshot',
scriptDill,
]);
- Expect.equals(result.exitCode, 0);
-
- // Tracing output is on stderr.
- Expect.isTrue(result.stdout.isEmpty);
- Expect.isTrue(result.stderr.isNotEmpty);
-
- final seenHeaders = <String>{};
- final lines =
- Stream.value(result.stderr as String).transform(const LineSplitter());
- await for (final s in lines) {
- for (final header in _jsonHeaders) {
- if (s.startsWith(header)) {
- // We only expect a single instance of each header.
- Expect.isFalse(seenHeaders.contains(header),
- 'multiple instances of \"$header\" seen');
- seenHeaders.add(header);
- final j = s.substring(header.length);
- // For now, just test that the JSON parses and that we get back a list.
- Expect.isTrue(json.decode(j) is List, 'not a list of decisions');
+ final stream = Stream.fromFuture(File(reasonsFile).readAsString());
+ final decisionsJson = await json.decoder.bind(stream).first;
+ Expect.isTrue(decisionsJson is List, 'not a list of decisions');
+ Expect.isTrue((decisionsJson as List).every((o) => o is Map),
+ 'not a list of decision objects');
+ final decisions = (decisionsJson as List).map((o) => o as Map);
+ for (final m in decisions) {
+ Expect.isTrue(m.containsKey("name"), 'no name field in decision');
+ Expect.isTrue(m["name"] is String, 'name field is not a string');
+ Expect.isTrue(m.containsKey("type"), 'no type field in decision');
+ Expect.isTrue(m["type"] is String, 'type field is not a string');
+ Expect.isTrue(m.containsKey("retained"), 'no retained field in decision');
+ Expect.isTrue(m["retained"] is bool, 'retained field is not a boolean');
+ if (m["retained"] as bool) {
+ Expect.isTrue(m.containsKey("reasons"), 'no reasons field in decision');
+ Expect.isTrue(m["reasons"] is List, 'reasons field is not a list');
+ final reasons = m["reasons"] as List;
+ Expect.isFalse(reasons.isEmpty, 'reasons list should not be empty');
+ for (final o in reasons) {
+ Expect.isTrue(o is String, 'reason is not a string');
}
}
}
- // Check that all headers were seen in the output.
- for (final header in _jsonHeaders) {
- Expect.isTrue(
- seenHeaders.contains(header), 'no instance of \"$header\" seen');
- }
}
diff --git a/runtime/tests/vm/dart_2/use_trace_precompiler_flag_test.dart b/runtime/tests/vm/dart_2/use_trace_precompiler_flag_test.dart
index 7d41a71..7ea2d60 100644
--- a/runtime/tests/vm/dart_2/use_trace_precompiler_flag_test.dart
+++ b/runtime/tests/vm/dart_2/use_trace_precompiler_flag_test.dart
@@ -67,47 +67,44 @@
buffer.write(retentionFlags[j]);
flags.add(buffer.toString());
}
- await testTracePrecompiler(scriptDill, flags);
+ await testTracePrecompiler(tempDir, scriptDill, flags);
}
});
}
-const _jsonHeaders = {'JSON for function decisions: '};
-
-Future<void> testTracePrecompiler(String scriptDill, List<String> flags) async {
- final result = await runHelper(genSnapshot, <String>[
+Future<void> testTracePrecompiler(
+ String tempDir, String scriptDill, List<String> flags) async {
+ final reasonsFile = path.join(tempDir, 'reasons.json');
+ final snapshot = path.join(tempDir, 'snapshot.so');
+ final result = await run(genSnapshot, <String>[
...flags,
- '--trace-precompiler',
+ '--write-retained-reasons-to=$reasonsFile',
'--snapshot-kind=app-aot-elf',
- '--elf=snapshot.so',
+ '--elf=$snapshot',
scriptDill,
]);
- Expect.equals(result.exitCode, 0);
-
- // Tracing output is on stderr.
- Expect.isTrue(result.stdout.isEmpty);
- Expect.isTrue(result.stderr.isNotEmpty);
-
- final seenHeaders = <String>{};
- final lines =
- Stream.value(result.stderr as String).transform(const LineSplitter());
- await for (final s in lines) {
- for (final header in _jsonHeaders) {
- if (s.startsWith(header)) {
- // We only expect a single instance of each header.
- Expect.isFalse(seenHeaders.contains(header),
- 'multiple instances of \"$header\" seen');
- seenHeaders.add(header);
- final j = s.substring(header.length);
- // For now, just test that the JSON parses and that we get back a list.
- Expect.isTrue(json.decode(j) is List, 'not a list of decisions');
+ final stream = Stream.fromFuture(File(reasonsFile).readAsString());
+ final decisionsJson = await json.decoder.bind(stream).first;
+ Expect.isTrue(decisionsJson is List, 'not a list of decisions');
+ Expect.isTrue((decisionsJson as List).every((o) => o is Map),
+ 'not a list of decision objects');
+ final decisions = (decisionsJson as List).map((o) => o as Map);
+ for (final m in decisions) {
+ Expect.isTrue(m.containsKey("name"), 'no name field in decision');
+ Expect.isTrue(m["name"] is String, 'name field is not a string');
+ Expect.isTrue(m.containsKey("type"), 'no type field in decision');
+ Expect.isTrue(m["type"] is String, 'type field is not a string');
+ Expect.isTrue(m.containsKey("retained"), 'no retained field in decision');
+ Expect.isTrue(m["retained"] is bool, 'retained field is not a boolean');
+ if (m["retained"] as bool) {
+ Expect.isTrue(m.containsKey("reasons"), 'no reasons field in decision');
+ Expect.isTrue(m["reasons"] is List, 'reasons field is not a list');
+ final reasons = m["reasons"] as List;
+ Expect.isFalse(reasons.isEmpty, 'reasons list should not be empty');
+ for (final o in reasons) {
+ Expect.isTrue(o is String, 'reason is not a string');
}
}
}
- // Check that all headers were seen in the output.
- for (final header in _jsonHeaders) {
- Expect.isTrue(
- seenHeaders.contains(header), 'no instance of \"$header\" seen');
- }
}
diff --git a/runtime/vm/compiler/aot/precompiler.cc b/runtime/vm/compiler/aot/precompiler.cc
index 0beffef..9f62f59 100644
--- a/runtime/vm/compiler/aot/precompiler.cc
+++ b/runtime/vm/compiler/aot/precompiler.cc
@@ -5,6 +5,7 @@
#include "vm/compiler/aot/precompiler.h"
#include "platform/unicode.h"
+#include "platform/utils.h"
#include "vm/canonical_tables.h"
#include "vm/class_finalizer.h"
#include "vm/closure_functions_cache.h"
@@ -67,6 +68,10 @@
max_speculative_inlining_attempts,
1,
"Max number of attempts with speculative inlining (precompilation only)");
+DEFINE_FLAG(charp,
+ write_retained_reasons_to,
+ nullptr,
+ "Print reasons for retaining objects to the given file");
DECLARE_FLAG(bool, print_flow_graph);
DECLARE_FLAG(bool, print_flow_graph_optimized);
@@ -109,6 +114,9 @@
// The object is the initializer for a static field.
static constexpr const char* kStaticFieldInitializer =
"static field initializer";
+ // The object is the initializer for a instance field.
+ static constexpr const char* kInstanceFieldInitializer =
+ "instance field initializer";
// The object is the initializer for a late field.
static constexpr const char* kLateFieldInitializer = "late field initializer";
// The object is an implicit getter.
@@ -136,6 +144,149 @@
static constexpr const char* kEntryPointPragma = "entry point pragma";
};
+class RetainedReasonsWriter : public ValueObject {
+ public:
+ explicit RetainedReasonsWriter(Zone* zone)
+ : zone_(zone), retained_reasons_map_(zone) {}
+
+ void Init(const char* filename) {
+ if (filename == nullptr) return;
+ const auto file_open = Dart::file_open_callback();
+ if (file_open == nullptr) return;
+
+ const auto file = file_open(filename, /*write=*/true);
+ if (file == nullptr) {
+ OS::PrintErr("Failed to open file %s\n", filename);
+ return;
+ }
+
+ file_ = file;
+ // We open the array here so that we can also print some objects to the
+ // JSON as we go, instead of requiring all information be collected
+ // and printed at one point. This avoids having to keep otherwise
+ // unneeded information around.
+ writer_.OpenArray();
+ }
+
+ void AddDropped(const Object& obj) {
+ if (HasReason(obj)) {
+ FATAL("dropped object has reasons to retain");
+ }
+ writer_.OpenObject();
+ WriteRetainedObjectSpecificFields(obj);
+ writer_.PrintPropertyBool("retained", false);
+ writer_.CloseObject();
+ }
+
+ bool HasReason(const Object& obj) const {
+ return retained_reasons_map_.HasKey(&obj);
+ }
+
+ void AddReason(const Object& obj, const char* reason) {
+ if (auto const kv = retained_reasons_map_.Lookup(&obj)) {
+ if (kv->value->Lookup(reason) == nullptr) {
+ kv->value->Insert(reason);
+ }
+ return;
+ }
+ auto const key = &Object::ZoneHandle(zone_, obj.ptr());
+ auto const value = new (zone_) ZoneCStringSet(zone_);
+ value->Insert(reason);
+ retained_reasons_map_.Insert(RetainedReasonsTrait::Pair(key, value));
+ }
+
+ // Finalizes the JSON output and writes it.
+ void Write() {
+ if (file_ == nullptr) return;
+
+ // Add all the objects for which we have reasons to retain.
+ auto it = retained_reasons_map_.GetIterator();
+
+ for (auto kv = it.Next(); kv != nullptr; kv = it.Next()) {
+ writer_.OpenObject();
+ WriteRetainedObjectSpecificFields(*kv->key);
+ writer_.PrintPropertyBool("retained", true);
+
+ writer_.OpenArray("reasons");
+ auto it = kv->value->GetIterator();
+ for (auto cstrp = it.Next(); cstrp != nullptr; cstrp = it.Next()) {
+ ASSERT(*cstrp != nullptr);
+ writer_.PrintValue(*cstrp);
+ }
+ writer_.CloseArray();
+
+ writer_.CloseObject();
+ }
+
+ writer_.CloseArray();
+ char* output = nullptr;
+ intptr_t length = -1;
+ writer_.Steal(&output, &length);
+
+ if (const auto file_write = Dart::file_write_callback()) {
+ file_write(output, length, file_);
+ }
+
+ if (const auto file_close = Dart::file_close_callback()) {
+ file_close(file_);
+ }
+
+ free(output);
+ }
+
+ private:
+ struct RetainedReasonsTrait {
+ using Key = const Object*;
+ using Value = ZoneCStringSet*;
+
+ struct Pair {
+ Key key;
+ Value value;
+
+ Pair() : key(nullptr), value(nullptr) {}
+ Pair(Key key, Value value) : key(key), value(value) {}
+ };
+
+ static Key KeyOf(Pair kv) { return kv.key; }
+
+ static Value ValueOf(Pair kv) { return kv.value; }
+
+ static inline intptr_t Hashcode(Key key) {
+ if (key->IsFunction()) {
+ return Function::Cast(*key).Hash();
+ }
+ if (key->IsClass()) {
+ return Utils::WordHash(Class::Cast(*key).id());
+ }
+ return Utils::WordHash(key->GetClassId());
+ }
+
+ static inline bool IsKeyEqual(Pair pair, Key key) {
+ return pair.key->ptr() == key->ptr();
+ }
+ };
+
+ using RetainedReasonsMap = DirectChainedHashMap<RetainedReasonsTrait>;
+
+ void WriteRetainedObjectSpecificFields(const Object& obj) {
+ if (obj.IsFunction()) {
+ writer_.PrintProperty("type", "Function");
+ const auto& function = Function::Cast(obj);
+ writer_.PrintProperty("name",
+ function.ToLibNamePrefixedQualifiedCString());
+ writer_.PrintProperty("kind",
+ UntaggedFunction::KindToCString(function.kind()));
+ return;
+ }
+ FATAL("Unexpected object %s", obj.ToCString());
+ }
+
+ Zone* const zone_;
+ RetainedReasonsMap retained_reasons_map_;
+ JSONWriter writer_;
+ void* file_;
+};
+
class PrecompileParsedFunctionHelper : public ValueObject {
public:
PrecompileParsedFunctionHelper(Precompiler* precompiler,
@@ -243,11 +394,11 @@
{
StackZone stack_zone(T);
zone_ = stack_zone.GetZone();
+ RetainedReasonsWriter reasons_writer(zone_);
- if (FLAG_trace_precompiler) {
- // Set up the retained reasons map now that the precompiler has an
- // appropriate zone.
- retained_reasons_map_ = new (Z) RetainedReasonsMap(Z);
+ if (FLAG_write_retained_reasons_to != nullptr) {
+ reasons_writer.Init(FLAG_write_retained_reasons_to);
+ retained_reasons_writer_ = &reasons_writer;
}
if (FLAG_use_bare_instructions) {
@@ -428,6 +579,11 @@
DiscardCodeObjects();
ProgramVisitor::Dedup(T);
+ if (FLAG_write_retained_reasons_to != nullptr) {
+ reasons_writer.Write();
+ retained_reasons_writer_ = nullptr;
+ }
+
zone_ = NULL;
}
@@ -798,23 +954,14 @@
}
void Precompiler::AddRetainReason(const Object& obj, const char* reason) {
- if (!FLAG_trace_precompiler || reason == nullptr) return;
- if (auto const kv = retained_reasons_map_->Lookup(&obj)) {
- if (kv->value->Lookup(reason) == nullptr) {
- kv->value->Insert(reason);
- }
- return;
- }
- auto const key = &Object::ZoneHandle(Z, obj.ptr());
- auto const value = new (Z) ZoneCStringSet(Z);
- value->Insert(reason);
- retained_reasons_map_->Insert(RetainedReasonsTrait::Pair(key, value));
+ if (retained_reasons_writer_ == nullptr || reason == nullptr) return;
+ retained_reasons_writer_->AddReason(obj, reason);
}
void Precompiler::AddTypesOf(const Function& function) {
if (function.IsNull()) return;
- if (FLAG_trace_precompiler &&
- retained_reasons_map_->Lookup(&function) == nullptr) {
+ if (retained_reasons_writer_ != nullptr &&
+ !retained_reasons_writer_->HasReason(function)) {
FATAL("no retaining reasons given");
}
if (functions_to_retain_.ContainsKey(function)) return;
@@ -1838,38 +1985,6 @@
Code& code = Code::Handle(Z);
Object& owner = Object::Handle(Z);
GrowableObjectArray& retained_functions = GrowableObjectArray::Handle(Z);
- JSONWriter json;
-
- if (FLAG_trace_precompiler) {
- json.OpenArray();
- }
-
- auto retain_function = [&](const Function& function) {
- if (FLAG_trace_precompiler) {
- auto const name = function.ToLibNamePrefixedQualifiedCString();
- auto const kind = UntaggedFunction::KindToCString(function.kind());
- json.OpenObject();
- json.PrintProperty("name", name);
- json.PrintProperty("kind", kind);
- json.PrintPropertyBool("retained", true);
- LogBlock lb;
- THR_Print("Retaining %s function %s\n", kind, name);
- json.OpenArray("reasons");
- if (auto const kv = retained_reasons_map_->Lookup(&function)) {
- auto it = kv->value->GetIterator();
- for (auto cstrp = it.Next(); cstrp != nullptr; cstrp = it.Next()) {
- ASSERT(*cstrp != nullptr);
- json.PrintValue(*cstrp);
- THR_Print("Reason: %s\n", *cstrp);
- }
- } else {
- THR_Print("No reasons recorded\n");
- }
- json.CloseArray();
- json.CloseObject();
- }
- retained_functions.Add(function);
- };
auto drop_function = [&](const Function& function) {
if (function.HasCode()) {
@@ -1884,14 +1999,11 @@
}
dropped_function_count_++;
if (FLAG_trace_precompiler) {
- auto const name = function.ToLibNamePrefixedQualifiedCString();
- auto const kind = UntaggedFunction::KindToCString(function.kind());
- json.OpenObject();
- json.PrintProperty("name", name);
- json.PrintProperty("kind", kind);
- json.PrintPropertyBool("retained", false);
- json.CloseObject();
- THR_Print("Dropping %s function %s\n", kind, name);
+ THR_Print("Dropping function %s\n",
+ function.ToLibNamePrefixedQualifiedCString());
+ }
+ if (retained_reasons_writer_ != nullptr) {
+ retained_reasons_writer_->AddDropped(function);
}
};
@@ -1910,7 +2022,7 @@
function ^= functions.At(j);
function.DropUncompiledImplicitClosureFunction();
if (functions_to_retain_.ContainsKey(function)) {
- retain_function(function);
+ retained_functions.Add(function);
} else {
drop_function(function);
}
@@ -1935,7 +2047,7 @@
if (functions_to_retain_.ContainsKey(function)) {
retained_functions.Add(name);
retained_functions.Add(desc);
- retain_function(function);
+ retained_functions.Add(function);
} else {
drop_function(function);
}
@@ -1957,18 +2069,13 @@
retained_functions = GrowableObjectArray::New();
ClosureFunctionsCache::ForAllClosureFunctions([&](const Function& function) {
if (functions_to_retain_.ContainsKey(function)) {
- retain_function(function);
+ retained_functions.Add(function);
} else {
drop_function(function);
}
return true; // Continue iteration.
});
IG->object_store()->set_closure_functions(retained_functions);
-
- if (FLAG_trace_precompiler) {
- json.CloseArray();
- THR_Print("JSON for function decisions: %s\n", json.ToCString());
- }
}
void Precompiler::DropFields() {
@@ -1976,7 +2083,6 @@
Class& cls = Class::Handle(Z);
Array& fields = Array::Handle(Z);
Field& field = Field::Handle(Z);
- Function& function = Function::Handle(Z);
GrowableObjectArray& retained_fields = GrowableObjectArray::Handle(Z);
AbstractType& type = AbstractType::Handle(Z);
@@ -1999,12 +2105,6 @@
#endif
if (retain) {
if (FLAG_trace_precompiler) {
- function = field.InitializerFunction();
- if (!function.IsNull()) {
- THR_Print("Retaining initializer function for %s field %s\n",
- field.is_static() ? "static" : "instance",
- function.ToLibNamePrefixedQualifiedCString());
- }
THR_Print("Retaining %s field %s\n",
field.is_static() ? "static" : "instance",
field.ToCString());
@@ -2015,12 +2115,6 @@
} else {
dropped_field_count_++;
if (FLAG_trace_precompiler) {
- function = field.InitializerFunction();
- if (!function.IsNull()) {
- THR_Print("Dropping initializer function for %s field %s\n",
- field.is_static() ? "static" : "instance",
- function.ToLibNamePrefixedQualifiedCString());
- }
THR_Print("Dropping %s field %s\n",
field.is_static() ? "static" : "instance",
field.ToCString());
diff --git a/runtime/vm/compiler/aot/precompiler.h b/runtime/vm/compiler/aot/precompiler.h
index 48613ee..2af72af 100644
--- a/runtime/vm/compiler/aot/precompiler.h
+++ b/runtime/vm/compiler/aot/precompiler.h
@@ -29,6 +29,7 @@
class Precompiler;
class FlowGraph;
class PrecompilerTracer;
+class RetainedReasonsWriter;
class TableSelectorKeyValueTrait {
public:
@@ -351,39 +352,6 @@
Isolate* isolate() const { return isolate_; }
IsolateGroup* isolate_group() const { return thread_->isolate_group(); }
- struct RetainedReasonsTrait {
- using Key = const Object*;
- using Value = ZoneCStringSet*;
-
- struct Pair {
- Key key;
- Value value;
-
- Pair() : key(nullptr), value(nullptr) {}
- Pair(Key key, Value value) : key(key), value(value) {}
- };
-
- static Key KeyOf(Pair kv) { return kv.key; }
-
- static Value ValueOf(Pair kv) { return kv.value; }
-
- static inline intptr_t Hashcode(Key key) {
- if (key->IsFunction()) {
- return Function::Cast(*key).Hash();
- }
- if (key->IsClass()) {
- return Utils::WordHash(Class::Cast(*key).id());
- }
- return Utils::WordHash(key->GetClassId());
- }
-
- static inline bool IsKeyEqual(Pair pair, Key key) {
- return pair.key->ptr() == key->ptr();
- }
- };
-
- using RetainedReasonsMap = ZoneDirectChainedHashMap<RetainedReasonsTrait>;
-
Thread* thread_;
Zone* zone_;
Isolate* isolate_;
@@ -412,7 +380,6 @@
FunctionSet possibly_retained_functions_;
FieldSet fields_to_retain_;
FunctionSet functions_to_retain_;
- RetainedReasonsMap* retained_reasons_map_ = nullptr;
ClassSet classes_to_retain_;
TypeArgumentsSet typeargs_to_retain_;
AbstractTypeSet types_to_retain_;
@@ -428,6 +395,7 @@
Phase phase_ = Phase::kPreparation;
PrecompilerTracer* tracer_ = nullptr;
+ RetainedReasonsWriter* retained_reasons_writer_ = nullptr;
bool is_tracing_ = false;
};
diff --git a/tools/VERSION b/tools/VERSION
index 751bc90..f036980 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 13
PATCH 0
-PRERELEASE 136
+PRERELEASE 137
PRERELEASE_PATCH 0
\ No newline at end of file