| // 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_); |
| } |
| |
| 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); |
| |
| // 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) { |
| 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); |
| } |
| 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); |
| |
| // 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); |
| |
| 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.CopyCanonicalType(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()); |
| { |
| JSONObject details(&jsobj, "details"); |
| if (reload_skipped_) { |
| // Reload was skipped. |
| const GrowableObjectArray& libs = |
| GrowableObjectArray::Handle(object_store()->libraries()); |
| const intptr_t final_library_count = libs.Length(); |
| details.AddProperty("savedLibraryCount", final_library_count); |
| details.AddProperty("loadedLibraryCount", static_cast<intptr_t>(0)); |
| details.AddProperty("finalLibraryCount", final_library_count); |
| } else 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); |
| } |
| } else { |
| // Reload was successful. |
| const GrowableObjectArray& libs = |
| GrowableObjectArray::Handle(object_store()->libraries()); |
| const intptr_t final_library_count = libs.Length(); |
| const intptr_t loaded_library_count = |
| final_library_count - num_saved_libs_; |
| details.AddProperty("savedLibraryCount", num_saved_libs_); |
| details.AddProperty("loadedLibraryCount", loaded_library_count); |
| details.AddProperty("finalLibraryCount", final_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 synthetic classes like the superclass of |
| // `class MA extends S with M {}` or `class MA = S with M'. The relevant |
| // changes with be reported as changes in M. |
| if (new_cls.IsMixinApplication() || new_cls.is_mixin_app_alias()) { |
| 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()) { |
| 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); |
| } |
| } |
| } |
| } |
| } |
| |
| #endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME) |
| |
| } // namespace dart |