b59d5a2be9
When mixin application is transformed, mixin is added to the end of interfaces list. This may cause assertion failure if the same class is used as mixin and mentioned among implemented interfaces. This CL relaxes that assertion for mixins as this duplication is very rare and seems harmless. Note that it would not be valid to remove duplicated interface/mixin from the list of interfaces, as it would break dart:mirrors in this case: dart:mirrors needs to reflect duplicated interface/mixin both in ClassMirror.superinterfaces (which is populated from the list of interfaces without last element) and ClassMirror.mixin (the last element in the list of interfaces). Fixes https://github.com/dart-lang/sdk/issues/35854 Change-Id: I9b986f7759a76cb6f963ebd837e9ea26f6242f8e Reviewed-on: https://dart-review.googlesource.com/c/91961 Reviewed-by: Régis Crelier <regis@google.com> Commit-Queue: Alexander Markov <alexmarkov@google.com>
2289 lines
77 KiB
C++
2289 lines
77 KiB
C++
// Copyright (c) 2016, 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.
|
|
|
|
#include "vm/isolate_reload.h"
|
|
|
|
#include "vm/bit_vector.h"
|
|
#include "vm/compiler/jit/compiler.h"
|
|
#include "vm/dart_api_impl.h"
|
|
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
#include "vm/hash.h"
|
|
#endif
|
|
#include "vm/hash_table.h"
|
|
#include "vm/heap/become.h"
|
|
#include "vm/heap/safepoint.h"
|
|
#include "vm/isolate.h"
|
|
#include "vm/kernel_isolate.h"
|
|
#include "vm/kernel_loader.h"
|
|
#include "vm/log.h"
|
|
#include "vm/object.h"
|
|
#include "vm/object_store.h"
|
|
#include "vm/parser.h"
|
|
#include "vm/runtime_entry.h"
|
|
#include "vm/service_event.h"
|
|
#include "vm/stack_frame.h"
|
|
#include "vm/thread.h"
|
|
#include "vm/timeline.h"
|
|
#include "vm/type_testing_stubs.h"
|
|
#include "vm/visitor.h"
|
|
|
|
namespace dart {
|
|
|
|
DEFINE_FLAG(int, reload_every, 0, "Reload every N stack overflow checks.");
|
|
DEFINE_FLAG(bool, trace_reload, false, "Trace isolate reloading");
|
|
|
|
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
DEFINE_FLAG(bool,
|
|
trace_reload_verbose,
|
|
false,
|
|
"trace isolate reloading verbose");
|
|
DEFINE_FLAG(bool, identity_reload, false, "Enable checks for identity reload.");
|
|
DEFINE_FLAG(bool, reload_every_optimized, true, "Only from optimized code.");
|
|
DEFINE_FLAG(bool,
|
|
reload_every_back_off,
|
|
false,
|
|
"Double the --reload-every value after each reload.");
|
|
DEFINE_FLAG(bool,
|
|
reload_force_rollback,
|
|
false,
|
|
"Force all reloads to fail and rollback.");
|
|
DEFINE_FLAG(bool,
|
|
check_reloaded,
|
|
false,
|
|
"Assert that an isolate has reloaded at least once.")
|
|
|
|
DECLARE_FLAG(bool, trace_deoptimization);
|
|
|
|
#define I (isolate())
|
|
#define Z (thread->zone())
|
|
|
|
#define TIMELINE_SCOPE(name) \
|
|
TimelineDurationScope tds##name(Thread::Current(), \
|
|
Timeline::GetIsolateStream(), #name)
|
|
|
|
InstanceMorpher::InstanceMorpher(Zone* zone, const Class& from, const Class& to)
|
|
: from_(Class::Handle(zone, from.raw())),
|
|
to_(Class::Handle(zone, to.raw())),
|
|
mapping_(zone, 0) {
|
|
before_ = new (zone) ZoneGrowableArray<const Instance*>(zone, 0);
|
|
after_ = new (zone) ZoneGrowableArray<const Instance*>(zone, 0);
|
|
new_fields_ = new (zone) ZoneGrowableArray<const Field*>(zone, 0);
|
|
ASSERT(from_.id() == to_.id());
|
|
cid_ = from_.id();
|
|
ComputeMapping();
|
|
}
|
|
|
|
void InstanceMorpher::AddObject(RawObject* object) const {
|
|
ASSERT(object->GetClassId() == cid());
|
|
const Instance& instance = Instance::Cast(Object::Handle(object));
|
|
before_->Add(&instance);
|
|
}
|
|
|
|
void InstanceMorpher::ComputeMapping() {
|
|
if (from_.NumTypeArguments()) {
|
|
// Add copying of the optional type argument field.
|
|
intptr_t from_offset = from_.type_arguments_field_offset();
|
|
ASSERT(from_offset != Class::kNoTypeArguments);
|
|
intptr_t to_offset = to_.type_arguments_field_offset();
|
|
ASSERT(to_offset != Class::kNoTypeArguments);
|
|
mapping_.Add(from_offset);
|
|
mapping_.Add(to_offset);
|
|
}
|
|
|
|
// Add copying of the instance fields if matching by name.
|
|
// Note: currently the type of the fields are ignored.
|
|
const Array& from_fields =
|
|
Array::Handle(from_.OffsetToFieldMap(true /* original classes */));
|
|
const Array& to_fields = Array::Handle(to_.OffsetToFieldMap());
|
|
Field& from_field = Field::Handle();
|
|
Field& to_field = Field::Handle();
|
|
String& from_name = String::Handle();
|
|
String& to_name = String::Handle();
|
|
|
|
// Scan across all the fields in the new class definition.
|
|
for (intptr_t i = 0; i < to_fields.Length(); i++) {
|
|
if (to_fields.At(i) == Field::null()) {
|
|
continue; // Ignore non-fields.
|
|
}
|
|
|
|
// Grab the field's name.
|
|
to_field = Field::RawCast(to_fields.At(i));
|
|
ASSERT(to_field.is_instance());
|
|
to_name = to_field.name();
|
|
|
|
// Did this field not exist in the old class definition?
|
|
bool new_field = true;
|
|
|
|
// Find this field in the old class.
|
|
for (intptr_t j = 0; j < from_fields.Length(); j++) {
|
|
if (from_fields.At(j) == Field::null()) {
|
|
continue; // Ignore non-fields.
|
|
}
|
|
from_field = Field::RawCast(from_fields.At(j));
|
|
ASSERT(from_field.is_instance());
|
|
from_name = from_field.name();
|
|
if (from_name.Equals(to_name)) {
|
|
// Success
|
|
mapping_.Add(from_field.Offset());
|
|
mapping_.Add(to_field.Offset());
|
|
// Field did exist in old class deifnition.
|
|
new_field = false;
|
|
}
|
|
}
|
|
|
|
if (new_field) {
|
|
if (to_field.has_initializer()) {
|
|
// This is a new field with an initializer.
|
|
const Field& field = Field::Handle(to_field.raw());
|
|
new_fields_->Add(&field);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
RawInstance* InstanceMorpher::Morph(const Instance& instance) const {
|
|
const Instance& result = Instance::Handle(Instance::New(to_));
|
|
// Morph the context from instance to result using mapping_.
|
|
for (intptr_t i = 0; i < mapping_.length(); i += 2) {
|
|
intptr_t from_offset = mapping_.At(i);
|
|
intptr_t to_offset = mapping_.At(i + 1);
|
|
const Object& value =
|
|
Object::Handle(instance.RawGetFieldAtOffset(from_offset));
|
|
result.RawSetFieldAtOffset(to_offset, value);
|
|
}
|
|
// Convert the instance into a filler object.
|
|
Become::MakeDummyObject(instance);
|
|
return result.raw();
|
|
}
|
|
|
|
void InstanceMorpher::RunNewFieldInitializers() const {
|
|
if ((new_fields_->length() == 0) || (after_->length() == 0)) {
|
|
return;
|
|
}
|
|
|
|
TIR_Print("Running new field initializers for class: %s\n", to_.ToCString());
|
|
Thread* thread = Thread::Current();
|
|
Zone* zone = thread->zone();
|
|
Function& eval_func = Function::Handle(zone);
|
|
Object& result = Object::Handle(zone);
|
|
// For each new field.
|
|
for (intptr_t i = 0; i < new_fields_->length(); i++) {
|
|
// Create a function that returns the expression.
|
|
const Field* field = new_fields_->At(i);
|
|
if (field->kernel_offset() > 0) {
|
|
eval_func ^= kernel::CreateFieldInitializerFunction(thread, zone, *field);
|
|
} else {
|
|
UNREACHABLE();
|
|
}
|
|
|
|
for (intptr_t j = 0; j < after_->length(); j++) {
|
|
const Instance* instance = after_->At(j);
|
|
TIR_Print("Initializing instance %" Pd " / %" Pd "\n", j + 1,
|
|
after_->length());
|
|
// Run the function and assign the field.
|
|
result = DartEntry::InvokeFunction(eval_func, Array::empty_array());
|
|
if (result.IsError()) {
|
|
// TODO(johnmccutchan): Report this error in the reload response?
|
|
OS::PrintErr(
|
|
"RELOAD: Running initializer for new field `%s` resulted in "
|
|
"an error: %s\n",
|
|
field->ToCString(), Error::Cast(result).ToErrorCString());
|
|
continue;
|
|
}
|
|
instance->RawSetFieldAtOffset(field->Offset(), result);
|
|
}
|
|
}
|
|
}
|
|
|
|
void InstanceMorpher::CreateMorphedCopies() const {
|
|
for (intptr_t i = 0; i < before()->length(); i++) {
|
|
const Instance& copy = Instance::Handle(Morph(*before()->At(i)));
|
|
after()->Add(©);
|
|
}
|
|
}
|
|
|
|
void InstanceMorpher::DumpFormatFor(const Class& cls) const {
|
|
THR_Print("%s\n", cls.ToCString());
|
|
if (cls.NumTypeArguments()) {
|
|
intptr_t field_offset = cls.type_arguments_field_offset();
|
|
ASSERT(field_offset != Class::kNoTypeArguments);
|
|
THR_Print(" - @%" Pd " <type arguments>\n", field_offset);
|
|
}
|
|
const Array& fields = Array::Handle(cls.OffsetToFieldMap());
|
|
Field& field = Field::Handle();
|
|
String& name = String::Handle();
|
|
for (intptr_t i = 0; i < fields.Length(); i++) {
|
|
if (fields.At(i) != Field::null()) {
|
|
field = Field::RawCast(fields.At(i));
|
|
ASSERT(field.is_instance());
|
|
name = field.name();
|
|
THR_Print(" - @%" Pd " %s\n", field.Offset(), name.ToCString());
|
|
}
|
|
}
|
|
|
|
THR_Print("Mapping: ");
|
|
for (int i = 0; i < mapping_.length(); i += 2) {
|
|
THR_Print(" %" Pd "->%" Pd, mapping_.At(i), mapping_.At(i + 1));
|
|
}
|
|
THR_Print("\n");
|
|
}
|
|
|
|
void InstanceMorpher::Dump() const {
|
|
LogBlock blocker;
|
|
THR_Print("Morphing from ");
|
|
DumpFormatFor(from_);
|
|
THR_Print("To ");
|
|
DumpFormatFor(to_);
|
|
THR_Print("\n");
|
|
}
|
|
|
|
void InstanceMorpher::AppendTo(JSONArray* array) {
|
|
JSONObject jsobj(array);
|
|
jsobj.AddProperty("type", "ShapeChangeMapping");
|
|
jsobj.AddProperty("class", to_);
|
|
jsobj.AddProperty("instanceCount", before()->length());
|
|
JSONArray map(&jsobj, "fieldOffsetMappings");
|
|
for (int i = 0; i < mapping_.length(); i += 2) {
|
|
JSONArray pair(&map);
|
|
pair.AddValue(mapping_.At(i));
|
|
pair.AddValue(mapping_.At(i + 1));
|
|
}
|
|
}
|
|
|
|
void ReasonForCancelling::Report(IsolateReloadContext* context) {
|
|
const Error& error = Error::Handle(ToError());
|
|
context->ReportError(error);
|
|
}
|
|
|
|
RawError* ReasonForCancelling::ToError() {
|
|
// By default create the error returned from ToString.
|
|
const String& message = String::Handle(ToString());
|
|
return LanguageError::New(message);
|
|
}
|
|
|
|
RawString* ReasonForCancelling::ToString() {
|
|
UNREACHABLE();
|
|
return NULL;
|
|
}
|
|
|
|
void ReasonForCancelling::AppendTo(JSONArray* array) {
|
|
JSONObject jsobj(array);
|
|
jsobj.AddProperty("type", "ReasonForCancelling");
|
|
const String& message = String::Handle(ToString());
|
|
jsobj.AddProperty("message", message.ToCString());
|
|
}
|
|
|
|
ClassReasonForCancelling::ClassReasonForCancelling(Zone* zone,
|
|
const Class& from,
|
|
const Class& to)
|
|
: ReasonForCancelling(zone),
|
|
from_(Class::ZoneHandle(zone, from.raw())),
|
|
to_(Class::ZoneHandle(zone, to.raw())) {}
|
|
|
|
void ClassReasonForCancelling::AppendTo(JSONArray* array) {
|
|
JSONObject jsobj(array);
|
|
jsobj.AddProperty("type", "ReasonForCancelling");
|
|
jsobj.AddProperty("class", from_);
|
|
const String& message = String::Handle(ToString());
|
|
jsobj.AddProperty("message", message.ToCString());
|
|
}
|
|
|
|
RawError* IsolateReloadContext::error() const {
|
|
ASSERT(reload_aborted());
|
|
// Report the first error to the surroundings.
|
|
return reasons_to_cancel_reload_.At(0)->ToError();
|
|
}
|
|
|
|
class ScriptUrlSetTraits {
|
|
public:
|
|
static bool ReportStats() { return false; }
|
|
static const char* Name() { return "ScriptUrlSetTraits"; }
|
|
|
|
static bool IsMatch(const Object& a, const Object& b) {
|
|
if (!a.IsString() || !b.IsString()) {
|
|
return false;
|
|
}
|
|
|
|
return String::Cast(a).Equals(String::Cast(b));
|
|
}
|
|
|
|
static uword Hash(const Object& obj) { return String::Cast(obj).Hash(); }
|
|
};
|
|
|
|
class ClassMapTraits {
|
|
public:
|
|
static bool ReportStats() { return false; }
|
|
static const char* Name() { return "ClassMapTraits"; }
|
|
|
|
static bool IsMatch(const Object& a, const Object& b) {
|
|
if (!a.IsClass() || !b.IsClass()) {
|
|
return false;
|
|
}
|
|
return IsolateReloadContext::IsSameClass(Class::Cast(a), Class::Cast(b));
|
|
}
|
|
|
|
static uword Hash(const Object& obj) {
|
|
uword class_name_hash = String::HashRawSymbol(Class::Cast(obj).Name());
|
|
RawLibrary* raw_library = Class::Cast(obj).library();
|
|
if (raw_library == Library::null()) {
|
|
return class_name_hash;
|
|
}
|
|
return FinalizeHash(
|
|
CombineHashes(class_name_hash,
|
|
String::Hash(Library::Handle(raw_library).private_key())),
|
|
/* hashbits= */ 30);
|
|
}
|
|
};
|
|
|
|
class LibraryMapTraits {
|
|
public:
|
|
static bool ReportStats() { return false; }
|
|
static const char* Name() { return "LibraryMapTraits"; }
|
|
|
|
static bool IsMatch(const Object& a, const Object& b) {
|
|
if (!a.IsLibrary() || !b.IsLibrary()) {
|
|
return false;
|
|
}
|
|
return IsolateReloadContext::IsSameLibrary(Library::Cast(a),
|
|
Library::Cast(b));
|
|
}
|
|
|
|
static uword Hash(const Object& obj) { return Library::Cast(obj).UrlHash(); }
|
|
};
|
|
|
|
class BecomeMapTraits {
|
|
public:
|
|
static bool ReportStats() { return false; }
|
|
static const char* Name() { return "BecomeMapTraits"; }
|
|
|
|
static bool IsMatch(const Object& a, const Object& b) {
|
|
return a.raw() == b.raw();
|
|
}
|
|
|
|
static uword Hash(const Object& obj) {
|
|
if (obj.IsLibrary()) {
|
|
return Library::Cast(obj).UrlHash();
|
|
} else if (obj.IsClass()) {
|
|
if (Class::Cast(obj).id() == kFreeListElement) {
|
|
return 0;
|
|
}
|
|
return String::HashRawSymbol(Class::Cast(obj).Name());
|
|
} else if (obj.IsField()) {
|
|
return String::HashRawSymbol(Field::Cast(obj).name());
|
|
} else if (obj.IsInstance()) {
|
|
Object& hashObj = Object::Handle(Instance::Cast(obj).HashCode());
|
|
if (hashObj.IsError()) {
|
|
Exceptions::PropagateError(Error::Cast(hashObj));
|
|
}
|
|
return Smi::Cast(hashObj).Value();
|
|
}
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
bool IsolateReloadContext::IsSameField(const Field& a, const Field& b) {
|
|
if (a.is_static() != b.is_static()) {
|
|
return false;
|
|
}
|
|
const Class& a_cls = Class::Handle(a.Owner());
|
|
const Class& b_cls = Class::Handle(b.Owner());
|
|
|
|
if (!IsSameClass(a_cls, b_cls)) {
|
|
return false;
|
|
}
|
|
|
|
const String& a_name = String::Handle(a.name());
|
|
const String& b_name = String::Handle(b.name());
|
|
|
|
return a_name.Equals(b_name);
|
|
}
|
|
|
|
bool IsolateReloadContext::IsSameClass(const Class& a, const Class& b) {
|
|
if (a.is_patch() != b.is_patch()) {
|
|
// TODO(johnmccutchan): Should we just check the class kind bits?
|
|
return false;
|
|
}
|
|
|
|
// TODO(turnidge): We need to look at generic type arguments for
|
|
// synthetic mixin classes. Their names are not necessarily unique
|
|
// currently.
|
|
const String& a_name = String::Handle(a.Name());
|
|
const String& b_name = String::Handle(b.Name());
|
|
|
|
if (!a_name.Equals(b_name)) {
|
|
return false;
|
|
}
|
|
|
|
const Library& a_lib = Library::Handle(a.library());
|
|
const Library& b_lib = Library::Handle(b.library());
|
|
|
|
if (a_lib.IsNull() || b_lib.IsNull()) {
|
|
return a_lib.raw() == b_lib.raw();
|
|
}
|
|
return (a_lib.private_key() == b_lib.private_key());
|
|
}
|
|
|
|
bool IsolateReloadContext::IsSameLibrary(const Library& a_lib,
|
|
const Library& b_lib) {
|
|
const String& a_lib_url =
|
|
String::Handle(a_lib.IsNull() ? String::null() : a_lib.url());
|
|
const String& b_lib_url =
|
|
String::Handle(b_lib.IsNull() ? String::null() : b_lib.url());
|
|
return a_lib_url.Equals(b_lib_url);
|
|
}
|
|
|
|
IsolateReloadContext::IsolateReloadContext(Isolate* isolate, JSONStream* js)
|
|
: zone_(Thread::Current()->zone()),
|
|
start_time_micros_(OS::GetCurrentMonotonicMicros()),
|
|
reload_timestamp_(OS::GetCurrentTimeMillis()),
|
|
isolate_(isolate),
|
|
reload_skipped_(false),
|
|
reload_aborted_(false),
|
|
reload_finalized_(false),
|
|
js_(js),
|
|
saved_num_cids_(-1),
|
|
saved_class_table_(NULL),
|
|
num_saved_libs_(-1),
|
|
instance_morphers_(zone_, 0),
|
|
reasons_to_cancel_reload_(zone_, 0),
|
|
cid_mapper_(),
|
|
modified_libs_(NULL),
|
|
script_url_(String::null()),
|
|
error_(Error::null()),
|
|
old_classes_set_storage_(Array::null()),
|
|
class_map_storage_(Array::null()),
|
|
old_libraries_set_storage_(Array::null()),
|
|
library_map_storage_(Array::null()),
|
|
become_map_storage_(Array::null()),
|
|
become_enum_mappings_(GrowableObjectArray::null()),
|
|
saved_root_library_(Library::null()),
|
|
saved_libraries_(GrowableObjectArray::null()),
|
|
root_url_prefix_(String::null()),
|
|
old_root_url_prefix_(String::null()) {
|
|
// NOTE: DO NOT ALLOCATE ANY RAW OBJECTS HERE. The IsolateReloadContext is not
|
|
// associated with the isolate yet and if a GC is triggered here the raw
|
|
// objects will not be properly accounted for.
|
|
ASSERT(zone_ != NULL);
|
|
}
|
|
|
|
IsolateReloadContext::~IsolateReloadContext() {
|
|
ASSERT(zone_ == Thread::Current()->zone());
|
|
ASSERT(saved_class_table_ == NULL);
|
|
}
|
|
|
|
void IsolateReloadContext::ReportError(const Error& error) {
|
|
if (!FLAG_support_service || Isolate::IsVMInternalIsolate(I)) {
|
|
return;
|
|
}
|
|
if (FLAG_trace_reload) {
|
|
THR_Print("ISO-RELOAD: Error: %s\n", error.ToErrorCString());
|
|
}
|
|
ServiceEvent service_event(I, ServiceEvent::kIsolateReload);
|
|
service_event.set_reload_error(&error);
|
|
Service::HandleEvent(&service_event);
|
|
}
|
|
|
|
void IsolateReloadContext::ReportSuccess() {
|
|
if (!FLAG_support_service || Isolate::IsVMInternalIsolate(I)) {
|
|
return;
|
|
}
|
|
ServiceEvent service_event(I, ServiceEvent::kIsolateReload);
|
|
Service::HandleEvent(&service_event);
|
|
}
|
|
|
|
class Aborted : public ReasonForCancelling {
|
|
public:
|
|
Aborted(Zone* zone, const Error& error)
|
|
: ReasonForCancelling(zone),
|
|
error_(Error::ZoneHandle(zone, error.raw())) {}
|
|
|
|
private:
|
|
const Error& error_;
|
|
|
|
RawError* ToError() { return error_.raw(); }
|
|
RawString* ToString() {
|
|
return String::NewFormatted("%s", error_.ToErrorCString());
|
|
}
|
|
};
|
|
|
|
static intptr_t CommonSuffixLength(const char* a, const char* b) {
|
|
const intptr_t a_length = strlen(a);
|
|
const intptr_t b_length = strlen(b);
|
|
intptr_t a_cursor = a_length;
|
|
intptr_t b_cursor = b_length;
|
|
|
|
while ((a_cursor >= 0) && (b_cursor >= 0)) {
|
|
if (a[a_cursor] != b[b_cursor]) {
|
|
break;
|
|
}
|
|
a_cursor--;
|
|
b_cursor--;
|
|
}
|
|
|
|
ASSERT((a_length - a_cursor) == (b_length - b_cursor));
|
|
return (a_length - a_cursor);
|
|
}
|
|
|
|
template <class T>
|
|
class ResourceHolder : ValueObject {
|
|
T* resource_;
|
|
|
|
public:
|
|
ResourceHolder() : resource_(NULL) {}
|
|
void set(T* resource) { resource_ = resource; }
|
|
T* get() { return resource_; }
|
|
~ResourceHolder() { delete (resource_); }
|
|
};
|
|
|
|
static void AcceptCompilation(Thread* thread) {
|
|
TransitionVMToNative transition(thread);
|
|
Dart_KernelCompilationResult result = KernelIsolate::AcceptCompilation();
|
|
if (result.status != Dart_KernelCompilationStatus_Ok) {
|
|
FATAL1(
|
|
"An error occurred in the CFE while accepting the most recent"
|
|
" compilation results: %s",
|
|
result.error);
|
|
}
|
|
}
|
|
|
|
// NOTE: This function returns *after* FinalizeLoading is called.
|
|
// If [root_script_url] is null, attempt to load from [kernel_buffer].
|
|
void IsolateReloadContext::Reload(bool force_reload,
|
|
const char* root_script_url,
|
|
const char* packages_url_,
|
|
const uint8_t* kernel_buffer,
|
|
intptr_t kernel_buffer_size) {
|
|
TIMELINE_SCOPE(Reload);
|
|
Thread* thread = Thread::Current();
|
|
ASSERT(isolate() == thread->isolate());
|
|
|
|
// Grab root library before calling CheckpointBeforeReload.
|
|
const Library& old_root_lib = Library::Handle(object_store()->root_library());
|
|
ASSERT(!old_root_lib.IsNull());
|
|
const String& old_root_lib_url = String::Handle(old_root_lib.url());
|
|
// Root library url.
|
|
const String& root_lib_url =
|
|
(root_script_url == NULL) ? old_root_lib_url
|
|
: String::Handle(String::New(root_script_url));
|
|
|
|
// Check to see if the base url of the loaded libraries has moved.
|
|
if (!old_root_lib_url.Equals(root_lib_url)) {
|
|
const char* old_root_library_url_c = old_root_lib_url.ToCString();
|
|
const char* root_library_url_c = root_lib_url.ToCString();
|
|
const intptr_t common_suffix_length =
|
|
CommonSuffixLength(root_library_url_c, old_root_library_url_c);
|
|
root_url_prefix_ = String::SubString(
|
|
root_lib_url, 0, root_lib_url.Length() - common_suffix_length + 1);
|
|
old_root_url_prefix_ =
|
|
String::SubString(old_root_lib_url, 0,
|
|
old_root_lib_url.Length() - common_suffix_length + 1);
|
|
}
|
|
|
|
Object& result = Object::Handle(thread->zone());
|
|
ResourceHolder<kernel::Program> kernel_program;
|
|
String& packages_url = String::Handle();
|
|
if (packages_url_ != NULL) {
|
|
packages_url = String::New(packages_url_);
|
|
}
|
|
|
|
// Reset stats.
|
|
num_received_libs_ = 0;
|
|
bytes_received_libs_ = 0;
|
|
num_received_classes_ = 0;
|
|
num_received_procedures_ = 0;
|
|
|
|
bool did_kernel_compilation = false;
|
|
bool skip_reload = false;
|
|
{
|
|
// Load the kernel program and figure out the modified libraries.
|
|
const GrowableObjectArray& libs =
|
|
GrowableObjectArray::Handle(object_store()->libraries());
|
|
intptr_t num_libs = libs.Length();
|
|
modified_libs_ = new (Z) BitVector(Z, num_libs);
|
|
intptr_t* p_num_received_classes = nullptr;
|
|
intptr_t* p_num_received_procedures = nullptr;
|
|
|
|
// ReadKernelFromFile checks to see if the file at
|
|
// root_script_url is a valid .dill file. If that's the case, a Program*
|
|
// is returned. Otherwise, this is likely a source file that needs to be
|
|
// compiled, so ReadKernelFromFile returns NULL.
|
|
kernel_program.set(kernel::Program::ReadFromFile(root_script_url));
|
|
if (kernel_program.get() != NULL) {
|
|
num_received_libs_ = kernel_program.get()->library_count();
|
|
bytes_received_libs_ = kernel_program.get()->kernel_data_size();
|
|
p_num_received_classes = &num_received_classes_;
|
|
p_num_received_procedures = &num_received_procedures_;
|
|
} else {
|
|
Dart_KernelCompilationResult retval;
|
|
if (kernel_buffer != NULL && kernel_buffer_size != 0) {
|
|
retval.kernel = const_cast<uint8_t*>(kernel_buffer);
|
|
retval.kernel_size = kernel_buffer_size;
|
|
retval.status = Dart_KernelCompilationStatus_Ok;
|
|
} else {
|
|
Dart_SourceFile* modified_scripts = NULL;
|
|
intptr_t modified_scripts_count = 0;
|
|
|
|
FindModifiedSources(thread, force_reload, &modified_scripts,
|
|
&modified_scripts_count, packages_url_);
|
|
|
|
{
|
|
TransitionVMToNative transition(thread);
|
|
retval = KernelIsolate::CompileToKernel(
|
|
root_lib_url.ToCString(), NULL, 0, modified_scripts_count,
|
|
modified_scripts, true, NULL);
|
|
did_kernel_compilation = true;
|
|
}
|
|
}
|
|
|
|
if (retval.status != Dart_KernelCompilationStatus_Ok) {
|
|
TIR_Print("---- LOAD FAILED, ABORTING RELOAD\n");
|
|
const String& error_str = String::Handle(String::New(retval.error));
|
|
free(retval.error);
|
|
const ApiError& error = ApiError::Handle(ApiError::New(error_str));
|
|
if (retval.kernel != NULL) {
|
|
free(const_cast<uint8_t*>(retval.kernel));
|
|
}
|
|
AddReasonForCancelling(new Aborted(zone_, error));
|
|
ReportReasonsForCancelling();
|
|
CommonFinalizeTail();
|
|
return;
|
|
}
|
|
|
|
// The ownership of the kernel buffer goes now to the VM.
|
|
const ExternalTypedData& typed_data = ExternalTypedData::Handle(
|
|
Z,
|
|
ExternalTypedData::New(kExternalTypedDataUint8ArrayCid, retval.kernel,
|
|
retval.kernel_size, Heap::kOld));
|
|
typed_data.AddFinalizer(
|
|
retval.kernel,
|
|
[](void* isolate_callback_data, Dart_WeakPersistentHandle handle,
|
|
void* data) { free(data); },
|
|
retval.kernel_size);
|
|
|
|
// TODO(dartbug.com/33973): Change the heap objects to have a proper
|
|
// retaining path to the kernel blob and ensure the finalizer will free it
|
|
// once there are no longer references to it.
|
|
// (The [ExternalTypedData] currently referenced by e.g. functions point
|
|
// into the middle of c-allocated buffer and don't have a finalizer).
|
|
I->RetainKernelBlob(typed_data);
|
|
|
|
kernel_program.set(kernel::Program::ReadFromTypedData(typed_data));
|
|
}
|
|
|
|
kernel::KernelLoader::FindModifiedLibraries(
|
|
kernel_program.get(), I, modified_libs_, force_reload, &skip_reload,
|
|
p_num_received_classes, p_num_received_procedures);
|
|
}
|
|
if (skip_reload) {
|
|
ASSERT(modified_libs_->IsEmpty());
|
|
reload_skipped_ = true;
|
|
// Inform GetUnusedChangesInLastReload that a reload has happened.
|
|
I->object_store()->set_changed_in_last_reload(
|
|
GrowableObjectArray::Handle(GrowableObjectArray::New()));
|
|
ReportOnJSON(js_);
|
|
|
|
// If we use the CFE and performed a compilation, we need to notify that
|
|
// we have accepted the compilation to clear some state in the incremental
|
|
// compiler.
|
|
if (did_kernel_compilation) {
|
|
AcceptCompilation(thread);
|
|
}
|
|
TIR_Print("---- SKIPPING RELOAD (No libraries were modified)\n");
|
|
return;
|
|
}
|
|
|
|
TIR_Print("---- STARTING RELOAD\n");
|
|
|
|
// Preallocate storage for maps.
|
|
old_classes_set_storage_ =
|
|
HashTables::New<UnorderedHashSet<ClassMapTraits> >(4);
|
|
class_map_storage_ = HashTables::New<UnorderedHashMap<ClassMapTraits> >(4);
|
|
old_libraries_set_storage_ =
|
|
HashTables::New<UnorderedHashSet<LibraryMapTraits> >(4);
|
|
library_map_storage_ =
|
|
HashTables::New<UnorderedHashMap<LibraryMapTraits> >(4);
|
|
become_map_storage_ = HashTables::New<UnorderedHashMap<BecomeMapTraits> >(4);
|
|
// Keep a separate array for enum mappings to avoid having to invoke
|
|
// hashCode on the instances.
|
|
become_enum_mappings_ = GrowableObjectArray::New(Heap::kOld);
|
|
|
|
// Disable the background compiler while we are performing the reload.
|
|
BackgroundCompiler::Disable(I);
|
|
|
|
// Wait for any concurrent marking tasks to finish and turn off the
|
|
// concurrent marker during reload as we might be allocating new instances
|
|
// (constants) when loading the new kernel file and this could cause
|
|
// inconsistency between the saved class table and the new class table.
|
|
Heap* heap = thread->heap();
|
|
const bool old_concurrent_mark_flag =
|
|
heap->old_space()->enable_concurrent_mark();
|
|
if (old_concurrent_mark_flag) {
|
|
heap->WaitForMarkerTasks(thread);
|
|
heap->old_space()->set_enable_concurrent_mark(false);
|
|
}
|
|
|
|
// Ensure all functions on the stack have unoptimized code.
|
|
EnsuredUnoptimizedCodeForStack();
|
|
// Deoptimize all code that had optimizing decisions that are dependent on
|
|
// assumptions from field guards or CHA or deferred library prefixes.
|
|
// TODO(johnmccutchan): Deoptimizing dependent code here (before the reload)
|
|
// is paranoid. This likely can be moved to the commit phase.
|
|
DeoptimizeDependentCode();
|
|
Checkpoint();
|
|
|
|
// WEIRD CONTROL FLOW BEGINS.
|
|
//
|
|
// The flow of execution until we return from the tag handler can be complex.
|
|
//
|
|
// On a successful load, the following will occur:
|
|
// 1) Tag Handler is invoked and the embedder is in control.
|
|
// 2) All sources and libraries are loaded.
|
|
// 3) Dart_FinalizeLoading is called by the embedder.
|
|
// 4) Dart_FinalizeLoading invokes IsolateReloadContext::FinalizeLoading
|
|
// and we are temporarily back in control.
|
|
// This is where we validate the reload and commit or reject.
|
|
// 5) Dart_FinalizeLoading invokes Dart code related to deferred libraries.
|
|
// 6) The tag handler returns and we move on.
|
|
//
|
|
// Even after a successful reload the Dart code invoked in (5) can result
|
|
// in an Unwind error or an UnhandledException error. This error will be
|
|
// returned by the tag handler. The tag handler can return other errors,
|
|
// for example, top level parse errors. We want to capture these errors while
|
|
// propagating the UnwindError or an UnhandledException error.
|
|
|
|
{
|
|
const Object& tmp =
|
|
kernel::KernelLoader::LoadEntireProgram(kernel_program.get());
|
|
if (!tmp.IsError()) {
|
|
Library& lib = Library::Handle(thread->zone());
|
|
lib ^= tmp.raw();
|
|
// If main method disappeared or were not there to begin with then
|
|
// KernelLoader will return null. In this case lookup library by
|
|
// URL.
|
|
if (lib.IsNull()) {
|
|
lib = Library::LookupLibrary(thread, root_lib_url);
|
|
}
|
|
isolate()->object_store()->set_root_library(lib);
|
|
FinalizeLoading();
|
|
result = Object::null();
|
|
|
|
// If we use the CFE and performed a compilation, we need to notify that
|
|
// we have accepted the compilation to clear some state in the incremental
|
|
// compiler.
|
|
if (did_kernel_compilation) {
|
|
AcceptCompilation(thread);
|
|
}
|
|
} else {
|
|
result = tmp.raw();
|
|
}
|
|
}
|
|
//
|
|
// WEIRD CONTROL FLOW ENDS.
|
|
|
|
// Re-enable the background compiler. Do this before propagating any errors.
|
|
BackgroundCompiler::Enable(I);
|
|
|
|
// Reenable concurrent marking if it was initially on.
|
|
heap->old_space()->set_enable_concurrent_mark(old_concurrent_mark_flag);
|
|
|
|
if (result.IsUnwindError()) {
|
|
if (thread->top_exit_frame_info() == 0) {
|
|
// We can only propagate errors when there are Dart frames on the stack.
|
|
// In this case there are no Dart frames on the stack and we set the
|
|
// thread's sticky error. This error will be returned to the message
|
|
// handler.
|
|
thread->set_sticky_error(Error::Cast(result));
|
|
} else {
|
|
// If the tag handler returns with an UnwindError error, propagate it and
|
|
// give up.
|
|
Exceptions::PropagateError(Error::Cast(result));
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
// Other errors (e.g. a parse error) are captured by the reload system.
|
|
if (result.IsError()) {
|
|
FinalizeFailedLoad(Error::Cast(result));
|
|
}
|
|
}
|
|
|
|
void IsolateReloadContext::RegisterClass(const Class& new_cls) {
|
|
const Class& old_cls = Class::Handle(OldClassOrNull(new_cls));
|
|
if (old_cls.IsNull()) {
|
|
I->class_table()->Register(new_cls);
|
|
|
|
if (FLAG_identity_reload) {
|
|
TIR_Print("Could not find replacement class for %s\n",
|
|
new_cls.ToCString());
|
|
UNREACHABLE();
|
|
}
|
|
|
|
// New class maps to itself.
|
|
AddClassMapping(new_cls, new_cls);
|
|
return;
|
|
}
|
|
VTIR_Print("Registering class: %s\n", new_cls.ToCString());
|
|
new_cls.set_id(old_cls.id());
|
|
isolate()->class_table()->SetAt(old_cls.id(), new_cls.raw());
|
|
if (!old_cls.is_enum_class()) {
|
|
new_cls.CopyCanonicalConstants(old_cls);
|
|
}
|
|
new_cls.CopyDeclarationType(old_cls);
|
|
AddBecomeMapping(old_cls, new_cls);
|
|
AddClassMapping(new_cls, old_cls);
|
|
}
|
|
|
|
// FinalizeLoading will be called *before* Reload() returns but will not be
|
|
// called if the embedder fails to load sources.
|
|
void IsolateReloadContext::FinalizeLoading() {
|
|
if (reload_skipped_ || reload_finalized_) {
|
|
return;
|
|
}
|
|
BuildLibraryMapping();
|
|
TIR_Print("---- LOAD SUCCEEDED\n");
|
|
if (ValidateReload()) {
|
|
Commit();
|
|
PostCommit();
|
|
isolate()->set_last_reload_timestamp(reload_timestamp_);
|
|
} else {
|
|
ReportReasonsForCancelling();
|
|
Rollback();
|
|
}
|
|
// ValidateReload mutates the direct subclass information and does
|
|
// not remove dead subclasses. Rebuild the direct subclass
|
|
// information from scratch.
|
|
RebuildDirectSubclasses();
|
|
CommonFinalizeTail();
|
|
}
|
|
|
|
// FinalizeFailedLoad will be called *before* Reload() returns and will only
|
|
// be called if the embedder fails to load sources.
|
|
void IsolateReloadContext::FinalizeFailedLoad(const Error& error) {
|
|
TIR_Print("---- LOAD FAILED, ABORTING RELOAD\n");
|
|
AddReasonForCancelling(new Aborted(zone_, error));
|
|
ReportReasonsForCancelling();
|
|
if (!reload_finalized_) {
|
|
Rollback();
|
|
}
|
|
CommonFinalizeTail();
|
|
}
|
|
|
|
void IsolateReloadContext::CommonFinalizeTail() {
|
|
ReportOnJSON(js_);
|
|
reload_finalized_ = true;
|
|
}
|
|
|
|
void IsolateReloadContext::ReportOnJSON(JSONStream* stream) {
|
|
JSONObject jsobj(stream);
|
|
jsobj.AddProperty("type", "ReloadReport");
|
|
jsobj.AddProperty("success", reload_skipped_ || !HasReasonsForCancelling());
|
|
{
|
|
if (HasReasonsForCancelling()) {
|
|
// Reload was rejected.
|
|
JSONArray array(&jsobj, "notices");
|
|
for (intptr_t i = 0; i < reasons_to_cancel_reload_.length(); i++) {
|
|
ReasonForCancelling* reason = reasons_to_cancel_reload_.At(i);
|
|
reason->AppendTo(&array);
|
|
}
|
|
return;
|
|
}
|
|
|
|
JSONObject details(&jsobj, "details");
|
|
const GrowableObjectArray& libs =
|
|
GrowableObjectArray::Handle(object_store()->libraries());
|
|
const intptr_t final_library_count = libs.Length();
|
|
details.AddProperty("finalLibraryCount", final_library_count);
|
|
details.AddProperty("receivedLibraryCount", num_received_libs_);
|
|
details.AddProperty("receivedLibrariesBytes", bytes_received_libs_);
|
|
details.AddProperty("receivedClassesCount", num_received_classes_);
|
|
details.AddProperty("receivedProceduresCount", num_received_procedures_);
|
|
if (reload_skipped_) {
|
|
// Reload was skipped.
|
|
details.AddProperty("savedLibraryCount", final_library_count);
|
|
details.AddProperty("loadedLibraryCount", static_cast<intptr_t>(0));
|
|
} else {
|
|
// Reload was successful.
|
|
const intptr_t loaded_library_count =
|
|
final_library_count - num_saved_libs_;
|
|
details.AddProperty("savedLibraryCount", num_saved_libs_);
|
|
details.AddProperty("loadedLibraryCount", loaded_library_count);
|
|
JSONArray array(&jsobj, "shapeChangeMappings");
|
|
for (intptr_t i = 0; i < instance_morphers_.length(); i++) {
|
|
instance_morphers_.At(i)->AppendTo(&array);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void IsolateReloadContext::EnsuredUnoptimizedCodeForStack() {
|
|
TIMELINE_SCOPE(EnsuredUnoptimizedCodeForStack);
|
|
StackFrameIterator it(ValidationPolicy::kDontValidateFrames,
|
|
Thread::Current(),
|
|
StackFrameIterator::kNoCrossThreadIteration);
|
|
|
|
Function& func = Function::Handle();
|
|
while (it.HasNextFrame()) {
|
|
StackFrame* frame = it.NextFrame();
|
|
if (frame->IsDartFrame() && !frame->is_interpreted()) {
|
|
func = frame->LookupDartFunction();
|
|
ASSERT(!func.IsNull());
|
|
func.EnsureHasCompiledUnoptimizedCode();
|
|
}
|
|
}
|
|
}
|
|
|
|
void IsolateReloadContext::DeoptimizeDependentCode() {
|
|
TIMELINE_SCOPE(DeoptimizeDependentCode);
|
|
ClassTable* class_table = I->class_table();
|
|
|
|
const intptr_t bottom = Dart::vm_isolate()->class_table()->NumCids();
|
|
const intptr_t top = I->class_table()->NumCids();
|
|
Class& cls = Class::Handle();
|
|
Array& fields = Array::Handle();
|
|
Field& field = Field::Handle();
|
|
for (intptr_t cls_idx = bottom; cls_idx < top; cls_idx++) {
|
|
if (!class_table->HasValidClassAt(cls_idx)) {
|
|
// Skip.
|
|
continue;
|
|
}
|
|
|
|
// Deoptimize CHA code.
|
|
cls = class_table->At(cls_idx);
|
|
ASSERT(!cls.IsNull());
|
|
|
|
cls.DisableAllCHAOptimizedCode();
|
|
|
|
// Deoptimize field guard code.
|
|
fields = cls.fields();
|
|
ASSERT(!fields.IsNull());
|
|
for (intptr_t field_idx = 0; field_idx < fields.Length(); field_idx++) {
|
|
field = Field::RawCast(fields.At(field_idx));
|
|
ASSERT(!field.IsNull());
|
|
field.DeoptimizeDependentCode();
|
|
}
|
|
}
|
|
|
|
DeoptimizeTypeTestingStubs();
|
|
|
|
// TODO(johnmccutchan): Also call LibraryPrefix::InvalidateDependentCode.
|
|
}
|
|
|
|
void IsolateReloadContext::CheckpointClasses() {
|
|
TIMELINE_SCOPE(CheckpointClasses);
|
|
TIR_Print("---- CHECKPOINTING CLASSES\n");
|
|
// Checkpoint classes before a reload. We need to copy the following:
|
|
// 1) The size of the class table.
|
|
// 2) The class table itself.
|
|
// For efficiency, we build a set of classes before the reload. This set
|
|
// is used to pair new classes with old classes.
|
|
|
|
ClassTable* class_table = I->class_table();
|
|
|
|
// Copy the size of the class table.
|
|
saved_num_cids_ = I->class_table()->NumCids();
|
|
|
|
// Copy of the class table.
|
|
ClassAndSize* local_saved_class_table = reinterpret_cast<ClassAndSize*>(
|
|
malloc(sizeof(ClassAndSize) * saved_num_cids_));
|
|
|
|
Class& cls = Class::Handle();
|
|
UnorderedHashSet<ClassMapTraits> old_classes_set(old_classes_set_storage_);
|
|
for (intptr_t i = 0; i < saved_num_cids_; i++) {
|
|
if (class_table->IsValidIndex(i) && class_table->HasValidClassAt(i)) {
|
|
// Copy the class into the saved class table and add it to the set.
|
|
local_saved_class_table[i] = class_table->PairAt(i);
|
|
if (i != kFreeListElement && i != kForwardingCorpse) {
|
|
cls = class_table->At(i);
|
|
bool already_present = old_classes_set.Insert(cls);
|
|
ASSERT(!already_present);
|
|
}
|
|
} else {
|
|
// No class at this index, mark it as NULL.
|
|
local_saved_class_table[i] = ClassAndSize(NULL);
|
|
}
|
|
}
|
|
old_classes_set_storage_ = old_classes_set.Release().raw();
|
|
// Assigning the field must be done after saving the class table.
|
|
saved_class_table_ = local_saved_class_table;
|
|
TIR_Print("---- System had %" Pd " classes\n", saved_num_cids_);
|
|
}
|
|
|
|
Dart_FileModifiedCallback IsolateReloadContext::file_modified_callback_ = NULL;
|
|
|
|
bool IsolateReloadContext::ScriptModifiedSince(const Script& script,
|
|
int64_t since) {
|
|
if (file_modified_callback_ == NULL) {
|
|
return true;
|
|
}
|
|
// We use the resolved url to determine if the script has been modified.
|
|
const String& url = String::Handle(script.resolved_url());
|
|
const char* url_chars = url.ToCString();
|
|
return (*file_modified_callback_)(url_chars, since);
|
|
}
|
|
|
|
static void PropagateLibraryModified(
|
|
const ZoneGrowableArray<ZoneGrowableArray<intptr_t>*>* imported_by,
|
|
intptr_t lib_index,
|
|
BitVector* modified_libs) {
|
|
ZoneGrowableArray<intptr_t>* dep_libs = (*imported_by)[lib_index];
|
|
for (intptr_t i = 0; i < dep_libs->length(); i++) {
|
|
intptr_t dep_lib_index = (*dep_libs)[i];
|
|
if (!modified_libs->Contains(dep_lib_index)) {
|
|
modified_libs->Add(dep_lib_index);
|
|
PropagateLibraryModified(imported_by, dep_lib_index, modified_libs);
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool ContainsScriptUri(const GrowableArray<const char*>& seen_uris,
|
|
const char* uri) {
|
|
for (intptr_t i = 0; i < seen_uris.length(); i++) {
|
|
const char* seen_uri = seen_uris.At(i);
|
|
size_t seen_len = strlen(seen_uri);
|
|
if (seen_len != strlen(uri)) {
|
|
continue;
|
|
} else if (strncmp(seen_uri, uri, seen_len) == 0) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void IsolateReloadContext::FindModifiedSources(
|
|
Thread* thread,
|
|
bool force_reload,
|
|
Dart_SourceFile** modified_sources,
|
|
intptr_t* count,
|
|
const char* packages_url) {
|
|
Zone* zone = thread->zone();
|
|
int64_t last_reload = I->last_reload_timestamp();
|
|
GrowableArray<const char*> modified_sources_uris;
|
|
const GrowableObjectArray& libs =
|
|
GrowableObjectArray::Handle(object_store()->libraries());
|
|
Library& lib = Library::Handle(zone);
|
|
Array& scripts = Array::Handle(zone);
|
|
Script& script = Script::Handle(zone);
|
|
String& uri = String::Handle(zone);
|
|
|
|
for (intptr_t lib_idx = 0; lib_idx < libs.Length(); lib_idx++) {
|
|
lib ^= libs.At(lib_idx);
|
|
if (lib.is_dart_scheme()) {
|
|
// We don't consider dart scheme libraries during reload.
|
|
continue;
|
|
}
|
|
scripts = lib.LoadedScripts();
|
|
for (intptr_t script_idx = 0; script_idx < scripts.Length(); script_idx++) {
|
|
script ^= scripts.At(script_idx);
|
|
uri ^= script.url();
|
|
if (ContainsScriptUri(modified_sources_uris, uri.ToCString())) {
|
|
// We've already accounted for this script in a prior library.
|
|
continue;
|
|
}
|
|
|
|
if (force_reload || ScriptModifiedSince(script, last_reload)) {
|
|
modified_sources_uris.Add(uri.ToCString());
|
|
}
|
|
}
|
|
}
|
|
|
|
// In addition to all sources, we need to check if the .packages file
|
|
// contents have been modified.
|
|
if (packages_url != NULL) {
|
|
if (file_modified_callback_ == NULL ||
|
|
(*file_modified_callback_)(packages_url, last_reload)) {
|
|
modified_sources_uris.Add(packages_url);
|
|
}
|
|
}
|
|
|
|
*count = modified_sources_uris.length();
|
|
if (*count == 0) {
|
|
return;
|
|
}
|
|
|
|
*modified_sources = zone_->Alloc<Dart_SourceFile>(*count);
|
|
for (intptr_t i = 0; i < *count; ++i) {
|
|
(*modified_sources)[i].uri = modified_sources_uris[i];
|
|
(*modified_sources)[i].source = NULL;
|
|
}
|
|
}
|
|
|
|
BitVector* IsolateReloadContext::FindModifiedLibraries(bool force_reload,
|
|
bool root_lib_modified) {
|
|
Thread* thread = Thread::Current();
|
|
int64_t last_reload = I->last_reload_timestamp();
|
|
|
|
const GrowableObjectArray& libs =
|
|
GrowableObjectArray::Handle(object_store()->libraries());
|
|
Library& lib = Library::Handle();
|
|
Array& scripts = Array::Handle();
|
|
Script& script = Script::Handle();
|
|
intptr_t num_libs = libs.Length();
|
|
|
|
// Construct the imported-by graph.
|
|
ZoneGrowableArray<ZoneGrowableArray<intptr_t>*>* imported_by = new (zone_)
|
|
ZoneGrowableArray<ZoneGrowableArray<intptr_t>*>(zone_, num_libs);
|
|
imported_by->SetLength(num_libs);
|
|
for (intptr_t i = 0; i < num_libs; i++) {
|
|
(*imported_by)[i] = new (zone_) ZoneGrowableArray<intptr_t>(zone_, 0);
|
|
}
|
|
Array& ports = Array::Handle();
|
|
Namespace& ns = Namespace::Handle();
|
|
Library& target = Library::Handle();
|
|
|
|
for (intptr_t lib_idx = 0; lib_idx < num_libs; lib_idx++) {
|
|
lib ^= libs.At(lib_idx);
|
|
ASSERT(lib_idx == lib.index());
|
|
if (lib.is_dart_scheme()) {
|
|
// We don't care about imports among dart scheme libraries.
|
|
continue;
|
|
}
|
|
|
|
// Add imports to the import-by graph.
|
|
ports = lib.imports();
|
|
for (intptr_t import_idx = 0; import_idx < ports.Length(); import_idx++) {
|
|
ns ^= ports.At(import_idx);
|
|
if (!ns.IsNull()) {
|
|
target = ns.library();
|
|
(*imported_by)[target.index()]->Add(lib.index());
|
|
}
|
|
}
|
|
|
|
// Add exports to the import-by graph.
|
|
ports = lib.exports();
|
|
for (intptr_t export_idx = 0; export_idx < ports.Length(); export_idx++) {
|
|
ns ^= ports.At(export_idx);
|
|
if (!ns.IsNull()) {
|
|
target = ns.library();
|
|
(*imported_by)[target.index()]->Add(lib.index());
|
|
}
|
|
}
|
|
|
|
// Add prefixed imports to the import-by graph.
|
|
DictionaryIterator entries(lib);
|
|
Object& entry = Object::Handle();
|
|
LibraryPrefix& prefix = LibraryPrefix::Handle();
|
|
while (entries.HasNext()) {
|
|
entry = entries.GetNext();
|
|
if (entry.IsLibraryPrefix()) {
|
|
prefix ^= entry.raw();
|
|
ports = prefix.imports();
|
|
for (intptr_t import_idx = 0; import_idx < ports.Length();
|
|
import_idx++) {
|
|
ns ^= ports.At(import_idx);
|
|
if (!ns.IsNull()) {
|
|
target = ns.library();
|
|
(*imported_by)[target.index()]->Add(lib.index());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
BitVector* modified_libs = new (Z) BitVector(Z, num_libs);
|
|
|
|
if (root_lib_modified) {
|
|
// The root library was either moved or replaced. Mark it as modified to
|
|
// force a reload of the potential root library replacement.
|
|
lib ^= object_store()->root_library();
|
|
modified_libs->Add(lib.index());
|
|
}
|
|
|
|
for (intptr_t lib_idx = 0; lib_idx < num_libs; lib_idx++) {
|
|
lib ^= libs.At(lib_idx);
|
|
if (lib.is_dart_scheme() || modified_libs->Contains(lib_idx)) {
|
|
// We don't consider dart scheme libraries during reload. If
|
|
// the modified libs set already contains this library, then we
|
|
// have already visited it.
|
|
continue;
|
|
}
|
|
scripts = lib.LoadedScripts();
|
|
for (intptr_t script_idx = 0; script_idx < scripts.Length(); script_idx++) {
|
|
script ^= scripts.At(script_idx);
|
|
if (force_reload || ScriptModifiedSince(script, last_reload)) {
|
|
modified_libs->Add(lib_idx);
|
|
PropagateLibraryModified(imported_by, lib_idx, modified_libs);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return modified_libs;
|
|
}
|
|
|
|
void IsolateReloadContext::CheckpointLibraries() {
|
|
TIMELINE_SCOPE(CheckpointLibraries);
|
|
TIR_Print("---- CHECKPOINTING LIBRARIES\n");
|
|
// Save the root library in case we abort the reload.
|
|
const Library& root_lib = Library::Handle(object_store()->root_library());
|
|
set_saved_root_library(root_lib);
|
|
|
|
// Save the old libraries array in case we abort the reload.
|
|
const GrowableObjectArray& libs =
|
|
GrowableObjectArray::Handle(object_store()->libraries());
|
|
set_saved_libraries(libs);
|
|
|
|
// Make a filtered copy of the old libraries array. Keep "clean" libraries
|
|
// that we will use instead of reloading.
|
|
const GrowableObjectArray& new_libs =
|
|
GrowableObjectArray::Handle(GrowableObjectArray::New(Heap::kOld));
|
|
Library& lib = Library::Handle();
|
|
UnorderedHashSet<LibraryMapTraits> old_libraries_set(
|
|
old_libraries_set_storage_);
|
|
num_saved_libs_ = 0;
|
|
for (intptr_t i = 0; i < libs.Length(); i++) {
|
|
lib ^= libs.At(i);
|
|
if (modified_libs_->Contains(i)) {
|
|
// We are going to reload this library. Clear the index.
|
|
lib.set_index(-1);
|
|
} else {
|
|
// We are preserving this library across the reload, assign its new index
|
|
lib.set_index(new_libs.Length());
|
|
new_libs.Add(lib, Heap::kOld);
|
|
num_saved_libs_++;
|
|
}
|
|
// Add old library to old libraries set.
|
|
bool already_present = old_libraries_set.Insert(lib);
|
|
ASSERT(!already_present);
|
|
}
|
|
modified_libs_ = NULL; // Renumbering the libraries has invalidated this.
|
|
old_libraries_set_storage_ = old_libraries_set.Release().raw();
|
|
|
|
// Reset the registered libraries to the filtered array.
|
|
Library::RegisterLibraries(Thread::Current(), new_libs);
|
|
// Reset the root library to null.
|
|
object_store()->set_root_library(Library::Handle());
|
|
}
|
|
|
|
// While reloading everything we do must be reversible so that we can abort
|
|
// safely if the reload fails. This function stashes things to the side and
|
|
// prepares the isolate for the reload attempt.
|
|
void IsolateReloadContext::Checkpoint() {
|
|
TIMELINE_SCOPE(Checkpoint);
|
|
CheckpointClasses();
|
|
CheckpointLibraries();
|
|
}
|
|
|
|
void IsolateReloadContext::RollbackClasses() {
|
|
TIR_Print("---- ROLLING BACK CLASS TABLE\n");
|
|
ASSERT(saved_num_cids_ > 0);
|
|
ASSERT(saved_class_table_ != NULL);
|
|
ClassTable* class_table = I->class_table();
|
|
class_table->SetNumCids(saved_num_cids_);
|
|
// Overwrite classes in class table with the saved classes.
|
|
for (intptr_t i = 0; i < saved_num_cids_; i++) {
|
|
if (class_table->IsValidIndex(i)) {
|
|
class_table->SetAt(i, saved_class_table_[i].get_raw_class());
|
|
}
|
|
}
|
|
|
|
DiscardSavedClassTable();
|
|
}
|
|
|
|
void IsolateReloadContext::RollbackLibraries() {
|
|
TIR_Print("---- ROLLING BACK LIBRARY CHANGES\n");
|
|
Thread* thread = Thread::Current();
|
|
Library& lib = Library::Handle();
|
|
GrowableObjectArray& saved_libs =
|
|
GrowableObjectArray::Handle(Z, saved_libraries());
|
|
if (!saved_libs.IsNull()) {
|
|
for (intptr_t i = 0; i < saved_libs.Length(); i++) {
|
|
lib = Library::RawCast(saved_libs.At(i));
|
|
// Restore indexes that were modified in CheckpointLibraries.
|
|
lib.set_index(i);
|
|
}
|
|
|
|
// Reset the registered libraries to the filtered array.
|
|
Library::RegisterLibraries(thread, saved_libs);
|
|
}
|
|
|
|
Library& saved_root_lib = Library::Handle(Z, saved_root_library());
|
|
if (!saved_root_lib.IsNull()) {
|
|
object_store()->set_root_library(saved_root_lib);
|
|
}
|
|
|
|
set_saved_root_library(Library::Handle());
|
|
set_saved_libraries(GrowableObjectArray::Handle());
|
|
}
|
|
|
|
void IsolateReloadContext::Rollback() {
|
|
TIR_Print("---- ROLLING BACK");
|
|
RollbackClasses();
|
|
RollbackLibraries();
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void IsolateReloadContext::VerifyMaps() {
|
|
TIMELINE_SCOPE(VerifyMaps);
|
|
Class& cls = Class::Handle();
|
|
Class& new_cls = Class::Handle();
|
|
Class& cls2 = Class::Handle();
|
|
|
|
// Verify that two old classes aren't both mapped to the same new
|
|
// class. This could happen is the IsSameClass function is broken.
|
|
UnorderedHashMap<ClassMapTraits> class_map(class_map_storage_);
|
|
UnorderedHashMap<ClassMapTraits> reverse_class_map(
|
|
HashTables::New<UnorderedHashMap<ClassMapTraits> >(
|
|
class_map.NumOccupied()));
|
|
{
|
|
UnorderedHashMap<ClassMapTraits>::Iterator it(&class_map);
|
|
while (it.MoveNext()) {
|
|
const intptr_t entry = it.Current();
|
|
new_cls = Class::RawCast(class_map.GetKey(entry));
|
|
cls = Class::RawCast(class_map.GetPayload(entry, 0));
|
|
cls2 ^= reverse_class_map.GetOrNull(new_cls);
|
|
if (!cls2.IsNull()) {
|
|
OS::PrintErr(
|
|
"Classes '%s' and '%s' are distinct classes but both map "
|
|
" to class '%s'\n",
|
|
cls.ToCString(), cls2.ToCString(), new_cls.ToCString());
|
|
UNREACHABLE();
|
|
}
|
|
bool update = reverse_class_map.UpdateOrInsert(cls, new_cls);
|
|
ASSERT(!update);
|
|
}
|
|
}
|
|
class_map.Release();
|
|
reverse_class_map.Release();
|
|
}
|
|
#endif
|
|
|
|
static void RecordChanges(const GrowableObjectArray& changed_in_last_reload,
|
|
const Class& old_cls,
|
|
const Class& new_cls) {
|
|
// All members of enum classes are synthetic, so nothing to report here.
|
|
if (new_cls.is_enum_class()) {
|
|
return;
|
|
}
|
|
|
|
// Don't report `typedef bool Predicate(Object o)` as unused. There is nothing
|
|
// to execute.
|
|
if (new_cls.IsTypedefClass()) {
|
|
return;
|
|
}
|
|
|
|
if (new_cls.raw() == old_cls.raw()) {
|
|
// A new class maps to itself. All its functions, field initizers, and so
|
|
// on are new.
|
|
changed_in_last_reload.Add(new_cls);
|
|
return;
|
|
}
|
|
|
|
ASSERT(new_cls.is_finalized() == old_cls.is_finalized());
|
|
if (!new_cls.is_finalized()) {
|
|
if (new_cls.SourceFingerprint() == old_cls.SourceFingerprint()) {
|
|
return;
|
|
}
|
|
// We don't know the members. Register interest in the whole class. Creates
|
|
// false positives.
|
|
changed_in_last_reload.Add(new_cls);
|
|
return;
|
|
}
|
|
|
|
Zone* zone = Thread::Current()->zone();
|
|
const Array& functions = Array::Handle(zone, new_cls.functions());
|
|
const Array& fields = Array::Handle(zone, new_cls.fields());
|
|
Function& new_function = Function::Handle(zone);
|
|
Function& old_function = Function::Handle(zone);
|
|
Field& new_field = Field::Handle(zone);
|
|
Field& old_field = Field::Handle(zone);
|
|
String& selector = String::Handle(zone);
|
|
for (intptr_t i = 0; i < functions.Length(); i++) {
|
|
new_function ^= functions.At(i);
|
|
selector = new_function.name();
|
|
old_function = old_cls.LookupFunction(selector);
|
|
// If we made live changes with proper structed edits, this would just be
|
|
// old != new.
|
|
if (old_function.IsNull() || (new_function.SourceFingerprint() !=
|
|
old_function.SourceFingerprint())) {
|
|
ASSERT(!new_function.HasCode());
|
|
ASSERT(new_function.usage_counter() == 0);
|
|
changed_in_last_reload.Add(new_function);
|
|
}
|
|
}
|
|
for (intptr_t i = 0; i < fields.Length(); i++) {
|
|
new_field ^= fields.At(i);
|
|
if (!new_field.is_static()) continue;
|
|
selector = new_field.name();
|
|
old_field = old_cls.LookupField(selector);
|
|
if (old_field.IsNull() || !old_field.is_static()) {
|
|
// New field.
|
|
changed_in_last_reload.Add(new_field);
|
|
} else if (new_field.SourceFingerprint() != old_field.SourceFingerprint()) {
|
|
// Changed field.
|
|
changed_in_last_reload.Add(new_field);
|
|
if (!old_field.IsUninitialized()) {
|
|
new_field.set_initializer_changed_after_initialization(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void IsolateReloadContext::Commit() {
|
|
TIMELINE_SCOPE(Commit);
|
|
TIR_Print("---- COMMITTING RELOAD\n");
|
|
|
|
#ifdef DEBUG
|
|
VerifyMaps();
|
|
#endif
|
|
|
|
const GrowableObjectArray& changed_in_last_reload =
|
|
GrowableObjectArray::Handle(GrowableObjectArray::New());
|
|
|
|
{
|
|
TIMELINE_SCOPE(CopyStaticFieldsAndPatchFieldsAndFunctions);
|
|
// Copy static field values from the old classes to the new classes.
|
|
// Patch fields and functions in the old classes so that they retain
|
|
// the old script.
|
|
Class& old_cls = Class::Handle();
|
|
Class& new_cls = Class::Handle();
|
|
UnorderedHashMap<ClassMapTraits> class_map(class_map_storage_);
|
|
|
|
{
|
|
UnorderedHashMap<ClassMapTraits>::Iterator it(&class_map);
|
|
while (it.MoveNext()) {
|
|
const intptr_t entry = it.Current();
|
|
new_cls = Class::RawCast(class_map.GetKey(entry));
|
|
old_cls = Class::RawCast(class_map.GetPayload(entry, 0));
|
|
if (new_cls.raw() != old_cls.raw()) {
|
|
ASSERT(new_cls.is_enum_class() == old_cls.is_enum_class());
|
|
if (new_cls.is_enum_class() && new_cls.is_finalized()) {
|
|
new_cls.ReplaceEnum(old_cls);
|
|
} else {
|
|
new_cls.CopyStaticFieldValues(old_cls);
|
|
}
|
|
old_cls.PatchFieldsAndFunctions();
|
|
old_cls.MigrateImplicitStaticClosures(this, new_cls);
|
|
}
|
|
RecordChanges(changed_in_last_reload, old_cls, new_cls);
|
|
}
|
|
}
|
|
|
|
class_map.Release();
|
|
}
|
|
|
|
if (FLAG_identity_reload) {
|
|
Object& changed = Object::Handle();
|
|
for (intptr_t i = 0; i < changed_in_last_reload.Length(); i++) {
|
|
changed = changed_in_last_reload.At(i);
|
|
ASSERT(changed.IsClass()); // Only fuzzy from lazy finalization.
|
|
}
|
|
}
|
|
I->object_store()->set_changed_in_last_reload(changed_in_last_reload);
|
|
|
|
// Copy over certain properties of libraries, e.g. is the library
|
|
// debuggable?
|
|
{
|
|
TIMELINE_SCOPE(CopyLibraryBits);
|
|
Library& lib = Library::Handle();
|
|
Library& new_lib = Library::Handle();
|
|
|
|
UnorderedHashMap<LibraryMapTraits> lib_map(library_map_storage_);
|
|
|
|
{
|
|
// Reload existing libraries.
|
|
UnorderedHashMap<LibraryMapTraits>::Iterator it(&lib_map);
|
|
|
|
while (it.MoveNext()) {
|
|
const intptr_t entry = it.Current();
|
|
ASSERT(entry != -1);
|
|
new_lib = Library::RawCast(lib_map.GetKey(entry));
|
|
lib = Library::RawCast(lib_map.GetPayload(entry, 0));
|
|
new_lib.set_debuggable(lib.IsDebuggable());
|
|
// Native extension support.
|
|
new_lib.set_native_entry_resolver(lib.native_entry_resolver());
|
|
new_lib.set_native_entry_symbol_resolver(
|
|
lib.native_entry_symbol_resolver());
|
|
}
|
|
}
|
|
|
|
// Release the library map.
|
|
lib_map.Release();
|
|
}
|
|
|
|
{
|
|
TIMELINE_SCOPE(UpdateLibrariesArray);
|
|
// Update the libraries array.
|
|
Library& lib = Library::Handle();
|
|
const GrowableObjectArray& libs =
|
|
GrowableObjectArray::Handle(I->object_store()->libraries());
|
|
for (intptr_t i = 0; i < libs.Length(); i++) {
|
|
lib = Library::RawCast(libs.At(i));
|
|
VTIR_Print("Lib '%s' at index %" Pd "\n", lib.ToCString(), i);
|
|
lib.set_index(i);
|
|
}
|
|
|
|
// Initialize library side table.
|
|
library_infos_.SetLength(libs.Length());
|
|
for (intptr_t i = 0; i < libs.Length(); i++) {
|
|
lib = Library::RawCast(libs.At(i));
|
|
// Mark the library dirty if it comes after the libraries we saved.
|
|
library_infos_[i].dirty = i >= num_saved_libs_;
|
|
}
|
|
}
|
|
|
|
{
|
|
MorphInstancesAndApplyNewClassTable();
|
|
|
|
const GrowableObjectArray& become_enum_mappings =
|
|
GrowableObjectArray::Handle(become_enum_mappings_);
|
|
UnorderedHashMap<BecomeMapTraits> become_map(become_map_storage_);
|
|
intptr_t replacement_count =
|
|
become_map.NumOccupied() + become_enum_mappings.Length() / 2;
|
|
const Array& before =
|
|
Array::Handle(Array::New(replacement_count, Heap::kOld));
|
|
const Array& after =
|
|
Array::Handle(Array::New(replacement_count, Heap::kOld));
|
|
Object& obj = Object::Handle();
|
|
intptr_t replacement_index = 0;
|
|
UnorderedHashMap<BecomeMapTraits>::Iterator it(&become_map);
|
|
while (it.MoveNext()) {
|
|
const intptr_t entry = it.Current();
|
|
obj = become_map.GetKey(entry);
|
|
before.SetAt(replacement_index, obj);
|
|
obj = become_map.GetPayload(entry, 0);
|
|
after.SetAt(replacement_index, obj);
|
|
replacement_index++;
|
|
}
|
|
for (intptr_t i = 0; i < become_enum_mappings.Length(); i += 2) {
|
|
obj = become_enum_mappings.At(i);
|
|
before.SetAt(replacement_index, obj);
|
|
obj = become_enum_mappings.At(i + 1);
|
|
after.SetAt(replacement_index, obj);
|
|
replacement_index++;
|
|
}
|
|
ASSERT(replacement_index == replacement_count);
|
|
become_map.Release();
|
|
|
|
Become::ElementsForwardIdentity(before, after);
|
|
}
|
|
|
|
// Rehash constants map for all classes. Constants are hashed by content, and
|
|
// content may have changed from fields being added or removed.
|
|
{
|
|
TIMELINE_SCOPE(RehashConstants);
|
|
I->RehashConstants();
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
I->ValidateConstants();
|
|
#endif
|
|
|
|
if (FLAG_identity_reload) {
|
|
if (saved_num_cids_ != I->class_table()->NumCids()) {
|
|
TIR_Print("Identity reload failed! B#C=%" Pd " A#C=%" Pd "\n",
|
|
saved_num_cids_, I->class_table()->NumCids());
|
|
}
|
|
const GrowableObjectArray& saved_libs =
|
|
GrowableObjectArray::Handle(saved_libraries());
|
|
const GrowableObjectArray& libs =
|
|
GrowableObjectArray::Handle(I->object_store()->libraries());
|
|
if (saved_libs.Length() != libs.Length()) {
|
|
TIR_Print("Identity reload failed! B#L=%" Pd " A#L=%" Pd "\n",
|
|
saved_libs.Length(), libs.Length());
|
|
}
|
|
}
|
|
|
|
// Run the initializers for new instance fields.
|
|
RunNewFieldInitializers();
|
|
}
|
|
|
|
bool IsolateReloadContext::IsDirty(const Library& lib) {
|
|
const intptr_t index = lib.index();
|
|
if (index == static_cast<classid_t>(-1)) {
|
|
// Treat deleted libraries as dirty.
|
|
return true;
|
|
}
|
|
ASSERT((index >= 0) && (index < library_infos_.length()));
|
|
return library_infos_[index].dirty;
|
|
}
|
|
|
|
void IsolateReloadContext::PostCommit() {
|
|
TIMELINE_SCOPE(PostCommit);
|
|
set_saved_root_library(Library::Handle());
|
|
set_saved_libraries(GrowableObjectArray::Handle());
|
|
InvalidateWorld();
|
|
TIR_Print("---- DONE COMMIT\n");
|
|
}
|
|
|
|
void IsolateReloadContext::AddReasonForCancelling(ReasonForCancelling* reason) {
|
|
reload_aborted_ = true;
|
|
reasons_to_cancel_reload_.Add(reason);
|
|
}
|
|
|
|
void IsolateReloadContext::AddInstanceMorpher(InstanceMorpher* morpher) {
|
|
instance_morphers_.Add(morpher);
|
|
cid_mapper_.Insert(morpher);
|
|
}
|
|
|
|
void IsolateReloadContext::ReportReasonsForCancelling() {
|
|
ASSERT(FLAG_reload_force_rollback || HasReasonsForCancelling());
|
|
for (int i = 0; i < reasons_to_cancel_reload_.length(); i++) {
|
|
reasons_to_cancel_reload_.At(i)->Report(this);
|
|
}
|
|
}
|
|
|
|
// The ObjectLocator is used for collecting instances that
|
|
// needs to be morphed.
|
|
class ObjectLocator : public ObjectVisitor {
|
|
public:
|
|
explicit ObjectLocator(IsolateReloadContext* context)
|
|
: context_(context), count_(0) {}
|
|
|
|
void VisitObject(RawObject* obj) {
|
|
InstanceMorpher* morpher =
|
|
context_->cid_mapper_.LookupValue(obj->GetClassId());
|
|
if (morpher != NULL) {
|
|
morpher->AddObject(obj);
|
|
count_++;
|
|
}
|
|
}
|
|
|
|
// Return the number of located objects for morphing.
|
|
intptr_t count() { return count_; }
|
|
|
|
private:
|
|
IsolateReloadContext* context_;
|
|
intptr_t count_;
|
|
};
|
|
|
|
static bool HasNoTasks(Heap* heap) {
|
|
MonitorLocker ml(heap->old_space()->tasks_lock());
|
|
return heap->old_space()->tasks() == 0;
|
|
}
|
|
|
|
void IsolateReloadContext::MorphInstancesAndApplyNewClassTable() {
|
|
TIMELINE_SCOPE(MorphInstances);
|
|
if (!HasInstanceMorphers()) {
|
|
// Fast path: no class had a shape change.
|
|
DiscardSavedClassTable();
|
|
return;
|
|
}
|
|
|
|
if (FLAG_trace_reload) {
|
|
LogBlock blocker;
|
|
TIR_Print("MorphInstance: \n");
|
|
for (intptr_t i = 0; i < instance_morphers_.length(); i++) {
|
|
instance_morphers_.At(i)->Dump();
|
|
}
|
|
}
|
|
|
|
// Find all objects that need to be morphed (reallocated to a new size).
|
|
ObjectLocator locator(this);
|
|
{
|
|
HeapIterationScope iteration(Thread::Current());
|
|
iteration.IterateObjects(&locator);
|
|
}
|
|
|
|
intptr_t count = locator.count();
|
|
if (count == 0) {
|
|
// Fast path: classes with shape change have no instances.
|
|
DiscardSavedClassTable();
|
|
return;
|
|
}
|
|
|
|
TIR_Print("Found %" Pd " object%s subject to morphing.\n", count,
|
|
(count > 1) ? "s" : "");
|
|
|
|
// While we are reallocating instances to their new size, the heap will
|
|
// contain a mix of instances with the old and new sizes that have the same
|
|
// cid. This makes the heap unwalkable until the "become" operation below
|
|
// replaces all the instances of the old size with forwarding corpses. Force
|
|
// heap growth to prevent size confusion during this period.
|
|
NoHeapGrowthControlScope scope;
|
|
// The HeapIterationScope above ensures no other GC tasks can be active.
|
|
ASSERT(HasNoTasks(I->heap()));
|
|
|
|
for (intptr_t i = 0; i < instance_morphers_.length(); i++) {
|
|
instance_morphers_.At(i)->CreateMorphedCopies();
|
|
}
|
|
|
|
// Create the inputs for Become.
|
|
intptr_t index = 0;
|
|
const Array& before = Array::Handle(Array::New(count));
|
|
const Array& after = Array::Handle(Array::New(count));
|
|
for (intptr_t i = 0; i < instance_morphers_.length(); i++) {
|
|
InstanceMorpher* morpher = instance_morphers_.At(i);
|
|
for (intptr_t j = 0; j < morpher->before()->length(); j++) {
|
|
before.SetAt(index, *morpher->before()->At(j));
|
|
after.SetAt(index, *morpher->after()->At(j));
|
|
index++;
|
|
}
|
|
}
|
|
ASSERT(index == count);
|
|
|
|
// Apply the new class table before "become". Become will replace all the
|
|
// instances of the old size with forwarding corpses, then perform a heap walk
|
|
// to fix references to the forwarding corpses. During this heap walk, it will
|
|
// encounter instances of the new size, so it requires the new class table.
|
|
ASSERT(HasNoTasks(I->heap()));
|
|
#if defined(DEBUG)
|
|
for (intptr_t i = 0; i < saved_num_cids_; i++) {
|
|
saved_class_table_[i] = ClassAndSize(nullptr, -1);
|
|
}
|
|
#endif
|
|
free(saved_class_table_);
|
|
saved_class_table_ = nullptr;
|
|
|
|
Become::ElementsForwardIdentity(before, after);
|
|
// The heap now contains only instances with the new size. Ordinary GC is safe
|
|
// again.
|
|
}
|
|
|
|
void IsolateReloadContext::RunNewFieldInitializers() {
|
|
// Run new field initializers on all instances.
|
|
for (intptr_t i = 0; i < instance_morphers_.length(); i++) {
|
|
instance_morphers_.At(i)->RunNewFieldInitializers();
|
|
}
|
|
}
|
|
|
|
bool IsolateReloadContext::ValidateReload() {
|
|
TIMELINE_SCOPE(ValidateReload);
|
|
if (reload_aborted()) return false;
|
|
|
|
TIR_Print("---- VALIDATING RELOAD\n");
|
|
|
|
// Validate libraries.
|
|
{
|
|
ASSERT(library_map_storage_ != Array::null());
|
|
UnorderedHashMap<LibraryMapTraits> map(library_map_storage_);
|
|
UnorderedHashMap<LibraryMapTraits>::Iterator it(&map);
|
|
Library& lib = Library::Handle();
|
|
Library& new_lib = Library::Handle();
|
|
while (it.MoveNext()) {
|
|
const intptr_t entry = it.Current();
|
|
new_lib = Library::RawCast(map.GetKey(entry));
|
|
lib = Library::RawCast(map.GetPayload(entry, 0));
|
|
if (new_lib.raw() != lib.raw()) {
|
|
lib.CheckReload(new_lib, this);
|
|
}
|
|
}
|
|
map.Release();
|
|
}
|
|
|
|
// Validate classes.
|
|
{
|
|
ASSERT(class_map_storage_ != Array::null());
|
|
UnorderedHashMap<ClassMapTraits> map(class_map_storage_);
|
|
UnorderedHashMap<ClassMapTraits>::Iterator it(&map);
|
|
Class& cls = Class::Handle();
|
|
Class& new_cls = Class::Handle();
|
|
while (it.MoveNext()) {
|
|
const intptr_t entry = it.Current();
|
|
new_cls = Class::RawCast(map.GetKey(entry));
|
|
cls = Class::RawCast(map.GetPayload(entry, 0));
|
|
if (new_cls.raw() != cls.raw()) {
|
|
cls.CheckReload(new_cls, this);
|
|
}
|
|
}
|
|
map.Release();
|
|
}
|
|
|
|
return !FLAG_reload_force_rollback && !HasReasonsForCancelling();
|
|
}
|
|
|
|
RawClass* IsolateReloadContext::FindOriginalClass(const Class& cls) {
|
|
return MappedClass(cls);
|
|
}
|
|
|
|
RawClass* IsolateReloadContext::GetClassForHeapWalkAt(intptr_t cid) {
|
|
ClassAndSize* class_table =
|
|
AtomicOperations::LoadRelaxed(&saved_class_table_);
|
|
if (class_table != NULL) {
|
|
ASSERT(cid > 0);
|
|
ASSERT(cid < saved_num_cids_);
|
|
return class_table[cid].get_raw_class();
|
|
} else {
|
|
return isolate_->class_table()->At(cid);
|
|
}
|
|
}
|
|
|
|
intptr_t IsolateReloadContext::GetClassSizeForHeapWalkAt(intptr_t cid) {
|
|
ClassAndSize* class_table =
|
|
AtomicOperations::LoadRelaxed(&saved_class_table_);
|
|
if (class_table != NULL) {
|
|
ASSERT(cid > 0);
|
|
ASSERT(cid < saved_num_cids_);
|
|
return class_table[cid].size();
|
|
} else {
|
|
return isolate_->class_table()->SizeAt(cid);
|
|
}
|
|
}
|
|
|
|
void IsolateReloadContext::DiscardSavedClassTable() {
|
|
ClassAndSize* local_saved_class_table = saved_class_table_;
|
|
saved_class_table_ = nullptr;
|
|
// Can't free this table immediately as another thread (e.g., concurrent
|
|
// marker or sweeper) may be between loading the table pointer and loading the
|
|
// table element. The table will be freed at the next major GC or isolate
|
|
// shutdown.
|
|
I->class_table()->AddOldTable(local_saved_class_table);
|
|
}
|
|
|
|
RawLibrary* IsolateReloadContext::saved_root_library() const {
|
|
return saved_root_library_;
|
|
}
|
|
|
|
void IsolateReloadContext::set_saved_root_library(const Library& value) {
|
|
saved_root_library_ = value.raw();
|
|
}
|
|
|
|
RawGrowableObjectArray* IsolateReloadContext::saved_libraries() const {
|
|
return saved_libraries_;
|
|
}
|
|
|
|
void IsolateReloadContext::set_saved_libraries(
|
|
const GrowableObjectArray& value) {
|
|
saved_libraries_ = value.raw();
|
|
}
|
|
|
|
void IsolateReloadContext::VisitObjectPointers(ObjectPointerVisitor* visitor) {
|
|
visitor->VisitPointers(from(), to());
|
|
if (saved_class_table_ != NULL) {
|
|
visitor->VisitPointers(
|
|
reinterpret_cast<RawObject**>(&saved_class_table_[0]), saved_num_cids_);
|
|
}
|
|
}
|
|
|
|
ObjectStore* IsolateReloadContext::object_store() {
|
|
return isolate_->object_store();
|
|
}
|
|
|
|
void IsolateReloadContext::ResetUnoptimizedICsOnStack() {
|
|
Thread* thread = Thread::Current();
|
|
StackZone stack_zone(thread);
|
|
Zone* zone = stack_zone.GetZone();
|
|
|
|
Code& code = Code::Handle(zone);
|
|
Bytecode& bytecode = Bytecode::Handle(zone);
|
|
Function& function = Function::Handle(zone);
|
|
DartFrameIterator iterator(thread,
|
|
StackFrameIterator::kNoCrossThreadIteration);
|
|
StackFrame* frame = iterator.NextFrame();
|
|
while (frame != NULL) {
|
|
if (frame->is_interpreted()) {
|
|
bytecode = frame->LookupDartBytecode();
|
|
bytecode.ResetICDatas(zone);
|
|
} else {
|
|
code = frame->LookupDartCode();
|
|
if (code.is_optimized()) {
|
|
// If this code is optimized, we need to reset the ICs in the
|
|
// corresponding unoptimized code, which will be executed when the stack
|
|
// unwinds to the optimized code.
|
|
function = code.function();
|
|
code = function.unoptimized_code();
|
|
ASSERT(!code.IsNull());
|
|
code.ResetICDatas(zone);
|
|
} else {
|
|
code.ResetICDatas(zone);
|
|
}
|
|
}
|
|
frame = iterator.NextFrame();
|
|
}
|
|
}
|
|
|
|
void IsolateReloadContext::ResetMegamorphicCaches() {
|
|
object_store()->set_megamorphic_cache_table(GrowableObjectArray::Handle());
|
|
// Since any current optimized code will not make any more calls, it may be
|
|
// better to clear the table instead of clearing each of the caches, allow
|
|
// the current megamorphic caches get GC'd and any new optimized code allocate
|
|
// new ones.
|
|
}
|
|
|
|
class MarkFunctionsForRecompilation : public ObjectVisitor {
|
|
public:
|
|
MarkFunctionsForRecompilation(Isolate* isolate,
|
|
IsolateReloadContext* reload_context,
|
|
Zone* zone)
|
|
: ObjectVisitor(),
|
|
handle_(Object::Handle(zone)),
|
|
owning_class_(Class::Handle(zone)),
|
|
owning_lib_(Library::Handle(zone)),
|
|
code_(Code::Handle(zone)),
|
|
bytecode_(Bytecode::Handle(zone)),
|
|
reload_context_(reload_context),
|
|
zone_(zone) {}
|
|
|
|
virtual void VisitObject(RawObject* obj) {
|
|
if (obj->IsPseudoObject()) {
|
|
// Cannot even be wrapped in handles.
|
|
return;
|
|
}
|
|
handle_ = obj;
|
|
if (handle_.IsFunction()) {
|
|
const Function& func = Function::Cast(handle_);
|
|
if (func.IsSignatureFunction()) {
|
|
return;
|
|
}
|
|
|
|
// Switch to unoptimized code or the lazy compilation stub.
|
|
func.SwitchToLazyCompiledUnoptimizedCode();
|
|
|
|
// Grab the current code.
|
|
code_ = func.CurrentCode();
|
|
ASSERT(!code_.IsNull());
|
|
bytecode_ = func.bytecode();
|
|
const bool clear_code = IsFromDirtyLibrary(func);
|
|
const bool stub_code = code_.IsStubCode();
|
|
|
|
// Zero edge counters.
|
|
func.ZeroEdgeCounters();
|
|
|
|
if (!stub_code || !bytecode_.IsNull()) {
|
|
if (clear_code) {
|
|
VTIR_Print("Marking %s for recompilation, clearing code\n",
|
|
func.ToCString());
|
|
ClearAllCode(func);
|
|
} else {
|
|
if (!stub_code) {
|
|
PreserveUnoptimizedCode();
|
|
}
|
|
if (!bytecode_.IsNull()) {
|
|
PreserveBytecode();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Clear counters.
|
|
func.set_usage_counter(0);
|
|
func.set_deoptimization_counter(0);
|
|
func.set_optimized_instruction_count(0);
|
|
func.set_optimized_call_site_count(0);
|
|
}
|
|
}
|
|
|
|
private:
|
|
void ClearAllCode(const Function& func) {
|
|
// Null out the ICData array and code.
|
|
func.ClearICDataArray();
|
|
func.ClearCode();
|
|
func.SetWasCompiled(false);
|
|
}
|
|
|
|
void PreserveUnoptimizedCode() {
|
|
ASSERT(!code_.IsNull());
|
|
// We are preserving the unoptimized code, fill all ICData arrays with
|
|
// the sentinel values so that we have no stale type feedback.
|
|
code_.ResetICDatas(zone_);
|
|
}
|
|
|
|
void PreserveBytecode() {
|
|
ASSERT(!bytecode_.IsNull());
|
|
// We are preserving the bytecode, fill all ICData arrays with
|
|
// the sentinel values so that we have no stale type feedback.
|
|
bytecode_.ResetICDatas(zone_);
|
|
}
|
|
|
|
bool IsFromDirtyLibrary(const Function& func) {
|
|
owning_class_ = func.Owner();
|
|
owning_lib_ = owning_class_.library();
|
|
return reload_context_->IsDirty(owning_lib_);
|
|
}
|
|
|
|
Object& handle_;
|
|
Class& owning_class_;
|
|
Library& owning_lib_;
|
|
Code& code_;
|
|
Bytecode& bytecode_;
|
|
IsolateReloadContext* reload_context_;
|
|
Zone* zone_;
|
|
};
|
|
|
|
typedef UnorderedHashMap<SmiTraits> IntHashMap;
|
|
|
|
class InvalidateKernelInfoCaches : public ObjectVisitor {
|
|
public:
|
|
explicit InvalidateKernelInfoCaches(Zone* zone)
|
|
: ObjectVisitor(),
|
|
handle_(Object::Handle(zone)),
|
|
data_(Array::Handle(zone)),
|
|
key_(Object::Handle(zone)),
|
|
value_(Smi::Handle(zone)) {}
|
|
|
|
virtual void VisitObject(RawObject* obj) {
|
|
if (obj->IsPseudoObject()) {
|
|
// Cannot even be wrapped in handles.
|
|
return;
|
|
}
|
|
handle_ = obj;
|
|
if (!handle_.IsKernelProgramInfo()) {
|
|
return;
|
|
}
|
|
const KernelProgramInfo& info = KernelProgramInfo::Cast(handle_);
|
|
// Clear the libraries cache.
|
|
{
|
|
data_ ^= info.libraries_cache();
|
|
ASSERT(!data_.IsNull());
|
|
IntHashMap table(&key_, &value_, &data_);
|
|
table.Clear();
|
|
info.set_libraries_cache(table.Release());
|
|
}
|
|
// Clear the classes cache.
|
|
{
|
|
data_ ^= info.classes_cache();
|
|
ASSERT(!data_.IsNull());
|
|
IntHashMap table(&key_, &value_, &data_);
|
|
table.Clear();
|
|
info.set_classes_cache(table.Release());
|
|
}
|
|
}
|
|
|
|
private:
|
|
Object& handle_;
|
|
Array& data_;
|
|
Object& key_;
|
|
Smi& value_;
|
|
};
|
|
|
|
void IsolateReloadContext::RunInvalidationVisitors() {
|
|
TIMELINE_SCOPE(MarkAllFunctionsForRecompilation);
|
|
TIR_Print("---- RUNNING INVALIDATION HEAP VISITORS\n");
|
|
Thread* thread = Thread::Current();
|
|
StackZone stack_zone(thread);
|
|
Zone* zone = stack_zone.GetZone();
|
|
|
|
HeapIterationScope iteration(thread);
|
|
GrowableArray<ObjectVisitor*> arr(zone, 2);
|
|
ExtensibleObjectVisitor visitor(&arr);
|
|
MarkFunctionsForRecompilation function_visitor(isolate_, this, zone);
|
|
InvalidateKernelInfoCaches kernel_info_visitor(zone);
|
|
|
|
visitor.Add(&function_visitor);
|
|
visitor.Add(&kernel_info_visitor);
|
|
iteration.IterateObjects(&visitor);
|
|
}
|
|
|
|
void IsolateReloadContext::InvalidateWorld() {
|
|
TIR_Print("---- INVALIDATING WORLD\n");
|
|
ResetMegamorphicCaches();
|
|
if (FLAG_trace_deoptimization) {
|
|
THR_Print("Deopt for reload\n");
|
|
}
|
|
DeoptimizeFunctionsOnStack();
|
|
ResetUnoptimizedICsOnStack();
|
|
RunInvalidationVisitors();
|
|
}
|
|
|
|
RawClass* IsolateReloadContext::MappedClass(const Class& replacement_or_new) {
|
|
UnorderedHashMap<ClassMapTraits> map(class_map_storage_);
|
|
Class& cls = Class::Handle();
|
|
cls ^= map.GetOrNull(replacement_or_new);
|
|
// No need to update storage address because no mutation occurred.
|
|
map.Release();
|
|
return cls.raw();
|
|
}
|
|
|
|
RawLibrary* IsolateReloadContext::MappedLibrary(
|
|
const Library& replacement_or_new) {
|
|
return Library::null();
|
|
}
|
|
|
|
RawClass* IsolateReloadContext::OldClassOrNull(
|
|
const Class& replacement_or_new) {
|
|
UnorderedHashSet<ClassMapTraits> old_classes_set(old_classes_set_storage_);
|
|
Class& cls = Class::Handle();
|
|
cls ^= old_classes_set.GetOrNull(replacement_or_new);
|
|
old_classes_set_storage_ = old_classes_set.Release().raw();
|
|
return cls.raw();
|
|
}
|
|
|
|
RawString* IsolateReloadContext::FindLibraryPrivateKey(
|
|
const Library& replacement_or_new) {
|
|
const Library& old = Library::Handle(OldLibraryOrNull(replacement_or_new));
|
|
if (old.IsNull()) {
|
|
return String::null();
|
|
}
|
|
#if defined(DEBUG)
|
|
VTIR_Print("`%s` is getting `%s`'s private key.\n",
|
|
String::Handle(replacement_or_new.url()).ToCString(),
|
|
String::Handle(old.url()).ToCString());
|
|
#endif
|
|
return old.private_key();
|
|
}
|
|
|
|
RawLibrary* IsolateReloadContext::OldLibraryOrNull(
|
|
const Library& replacement_or_new) {
|
|
UnorderedHashSet<LibraryMapTraits> old_libraries_set(
|
|
old_libraries_set_storage_);
|
|
Library& lib = Library::Handle();
|
|
lib ^= old_libraries_set.GetOrNull(replacement_or_new);
|
|
old_libraries_set.Release();
|
|
if (lib.IsNull() && (root_url_prefix_ != String::null()) &&
|
|
(old_root_url_prefix_ != String::null())) {
|
|
return OldLibraryOrNullBaseMoved(replacement_or_new);
|
|
}
|
|
return lib.raw();
|
|
}
|
|
|
|
// Attempt to find the pair to |replacement_or_new| with the knowledge that
|
|
// the base url prefix has moved.
|
|
RawLibrary* IsolateReloadContext::OldLibraryOrNullBaseMoved(
|
|
const Library& replacement_or_new) {
|
|
const String& url_prefix = String::Handle(root_url_prefix_);
|
|
const String& old_url_prefix = String::Handle(old_root_url_prefix_);
|
|
const intptr_t prefix_length = url_prefix.Length();
|
|
const intptr_t old_prefix_length = old_url_prefix.Length();
|
|
const String& new_url = String::Handle(replacement_or_new.url());
|
|
const String& suffix =
|
|
String::Handle(String::SubString(new_url, prefix_length));
|
|
if (!new_url.StartsWith(url_prefix)) {
|
|
return Library::null();
|
|
}
|
|
Library& old = Library::Handle();
|
|
String& old_url = String::Handle();
|
|
String& old_suffix = String::Handle();
|
|
GrowableObjectArray& saved_libs =
|
|
GrowableObjectArray::Handle(saved_libraries());
|
|
ASSERT(!saved_libs.IsNull());
|
|
for (intptr_t i = 0; i < saved_libs.Length(); i++) {
|
|
old = Library::RawCast(saved_libs.At(i));
|
|
old_url = old.url();
|
|
if (!old_url.StartsWith(old_url_prefix)) {
|
|
continue;
|
|
}
|
|
old_suffix ^= String::SubString(old_url, old_prefix_length);
|
|
if (old_suffix.IsNull()) {
|
|
continue;
|
|
}
|
|
if (old_suffix.Equals(suffix)) {
|
|
TIR_Print("`%s` is moving to `%s`\n", old_url.ToCString(),
|
|
new_url.ToCString());
|
|
return old.raw();
|
|
}
|
|
}
|
|
return Library::null();
|
|
}
|
|
|
|
void IsolateReloadContext::BuildLibraryMapping() {
|
|
const GrowableObjectArray& libs =
|
|
GrowableObjectArray::Handle(object_store()->libraries());
|
|
|
|
Library& replacement_or_new = Library::Handle();
|
|
Library& old = Library::Handle();
|
|
for (intptr_t i = num_saved_libs_; i < libs.Length(); i++) {
|
|
replacement_or_new = Library::RawCast(libs.At(i));
|
|
old ^= OldLibraryOrNull(replacement_or_new);
|
|
if (old.IsNull()) {
|
|
if (FLAG_identity_reload) {
|
|
TIR_Print("Could not find original library for %s\n",
|
|
replacement_or_new.ToCString());
|
|
UNREACHABLE();
|
|
}
|
|
// New library.
|
|
AddLibraryMapping(replacement_or_new, replacement_or_new);
|
|
} else {
|
|
ASSERT(!replacement_or_new.is_dart_scheme());
|
|
// Replaced class.
|
|
AddLibraryMapping(replacement_or_new, old);
|
|
|
|
AddBecomeMapping(old, replacement_or_new);
|
|
}
|
|
}
|
|
}
|
|
|
|
void IsolateReloadContext::AddClassMapping(const Class& replacement_or_new,
|
|
const Class& original) {
|
|
UnorderedHashMap<ClassMapTraits> map(class_map_storage_);
|
|
bool update = map.UpdateOrInsert(replacement_or_new, original);
|
|
ASSERT(!update);
|
|
// The storage given to the map may have been reallocated, remember the new
|
|
// address.
|
|
class_map_storage_ = map.Release().raw();
|
|
}
|
|
|
|
void IsolateReloadContext::AddLibraryMapping(const Library& replacement_or_new,
|
|
const Library& original) {
|
|
UnorderedHashMap<LibraryMapTraits> map(library_map_storage_);
|
|
bool update = map.UpdateOrInsert(replacement_or_new, original);
|
|
ASSERT(!update);
|
|
// The storage given to the map may have been reallocated, remember the new
|
|
// address.
|
|
library_map_storage_ = map.Release().raw();
|
|
}
|
|
|
|
void IsolateReloadContext::AddStaticFieldMapping(const Field& old_field,
|
|
const Field& new_field) {
|
|
ASSERT(old_field.is_static());
|
|
ASSERT(new_field.is_static());
|
|
|
|
AddBecomeMapping(old_field, new_field);
|
|
}
|
|
|
|
void IsolateReloadContext::AddBecomeMapping(const Object& old,
|
|
const Object& neu) {
|
|
ASSERT(become_map_storage_ != Array::null());
|
|
UnorderedHashMap<BecomeMapTraits> become_map(become_map_storage_);
|
|
bool update = become_map.UpdateOrInsert(old, neu);
|
|
ASSERT(!update);
|
|
become_map_storage_ = become_map.Release().raw();
|
|
}
|
|
|
|
void IsolateReloadContext::AddEnumBecomeMapping(const Object& old,
|
|
const Object& neu) {
|
|
const GrowableObjectArray& become_enum_mappings =
|
|
GrowableObjectArray::Handle(become_enum_mappings_);
|
|
become_enum_mappings.Add(old);
|
|
become_enum_mappings.Add(neu);
|
|
ASSERT((become_enum_mappings.Length() % 2) == 0);
|
|
}
|
|
|
|
void IsolateReloadContext::RebuildDirectSubclasses() {
|
|
ClassTable* class_table = I->class_table();
|
|
intptr_t num_cids = class_table->NumCids();
|
|
|
|
// Clear the direct subclasses for all classes.
|
|
Class& cls = Class::Handle();
|
|
GrowableObjectArray& subclasses = GrowableObjectArray::Handle();
|
|
for (intptr_t i = 1; i < num_cids; i++) {
|
|
if (class_table->HasValidClassAt(i)) {
|
|
cls = class_table->At(i);
|
|
subclasses = cls.direct_subclasses();
|
|
if (!subclasses.IsNull()) {
|
|
cls.ClearDirectSubclasses();
|
|
}
|
|
subclasses = cls.direct_implementors();
|
|
if (!subclasses.IsNull()) {
|
|
cls.ClearDirectImplementors();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Recompute the direct subclasses / implementors.
|
|
|
|
AbstractType& super_type = AbstractType::Handle();
|
|
Class& super_cls = Class::Handle();
|
|
|
|
Array& interface_types = Array::Handle();
|
|
AbstractType& interface_type = AbstractType::Handle();
|
|
Class& interface_class = Class::Handle();
|
|
|
|
for (intptr_t i = 1; i < num_cids; i++) {
|
|
if (class_table->HasValidClassAt(i)) {
|
|
cls = class_table->At(i);
|
|
super_type = cls.super_type();
|
|
if (!super_type.IsNull() && !super_type.IsObjectType()) {
|
|
super_cls = cls.SuperClass();
|
|
ASSERT(!super_cls.IsNull());
|
|
super_cls.AddDirectSubclass(cls);
|
|
}
|
|
|
|
interface_types = cls.interfaces();
|
|
if (!interface_types.IsNull()) {
|
|
const intptr_t mixin_index = cls.is_transformed_mixin_application()
|
|
? interface_types.Length() - 1
|
|
: -1;
|
|
for (intptr_t j = 0; j < interface_types.Length(); ++j) {
|
|
interface_type ^= interface_types.At(j);
|
|
interface_class = interface_type.type_class();
|
|
interface_class.AddDirectImplementor(
|
|
cls, /* is_mixin = */ i == mixin_index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
} // namespace dart
|