Index: runtime/vm/isolate_reload.cc |
diff --git a/runtime/vm/isolate_reload.cc b/runtime/vm/isolate_reload.cc |
index 75102c9d1f089b71142cf02708a03df53f6d9a95..158f6554921d899f059b21deeb932de42515485f 100644 |
--- a/runtime/vm/isolate_reload.cc |
+++ b/runtime/vm/isolate_reload.cc |
@@ -44,6 +44,152 @@ DEFINE_FLAG(bool, check_reloaded, false, |
#name) |
+InstanceMorpher::InstanceMorpher(const Class& from, const Class& to) |
+ : from_(from), to_(to), mapping_() { |
+ ComputeMapping(); |
+ before_ = new ZoneGrowableArray<const Instance*>(); |
+ after_ = new ZoneGrowableArray<const Instance*>(); |
+ ASSERT(from_.id() == to_.id()); |
+ cid_ = from_.id(); |
+} |
+ |
+ |
+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()); |
+ 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(); |
+ for (intptr_t i = 0; i < from_fields.Length(); i++) { |
+ if (from_fields.At(i) == Field::null()) continue; // Ignore non-fields. |
+ from_field = Field::RawCast(from_fields.At(i)); |
+ ASSERT(from_field.is_instance()); |
+ from_name = from_field.name(); |
+ // We now have to find where this field is in the to class. |
+ for (intptr_t j = 0; j < to_fields.Length(); j++) { |
+ if (to_fields.At(j) == Field::null()) continue; // Ignore non-fields. |
+ to_field = Field::RawCast(to_fields.At(j)); |
+ ASSERT(to_field.is_instance()); |
+ to_name = to_field.name(); |
+ if (from_name.Equals(to_name)) { |
+ // Success |
+ mapping_.Add(from_field.Offset()); |
+ mapping_.Add(to_field.Offset()); |
+ } |
+ } |
+ } |
+} |
+ |
+ |
+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::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 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; |
+} |
+ |
+ |
+RawError* IsolateReloadContext::error() const { |
+ ASSERT(has_error()); |
+ // Report the first error to the surroundings. |
+ const Error& error = |
+ Error::Handle(reasons_to_cancel_reload_.At(0)->ToError()); |
+ OS::Print("[[%s]]\n", error.ToCString()); |
+ return error.raw(); |
+} |
+ |
class ScriptUrlSetTraits { |
public: |
static bool ReportStats() { return false; } |
@@ -180,10 +326,12 @@ bool IsolateReloadContext::IsSameLibrary( |
IsolateReloadContext::IsolateReloadContext(Isolate* isolate) |
: start_time_micros_(OS::GetCurrentMonotonicMicros()), |
isolate_(isolate), |
- has_error_(false), |
saved_num_cids_(-1), |
saved_class_table_(NULL), |
num_saved_libs_(-1), |
+ instance_morphers_(), |
+ reasons_to_cancel_reload_(), |
+ cid_mapper_(), |
script_uri_(String::null()), |
error_(Error::null()), |
old_classes_set_storage_(Array::null()), |
@@ -205,8 +353,6 @@ IsolateReloadContext::~IsolateReloadContext() { |
void IsolateReloadContext::ReportError(const Error& error) { |
- has_error_ = true; |
- error_ = error.raw(); |
if (FLAG_trace_reload) { |
THR_Print("ISO-RELOAD: Error: %s\n", error.ToErrorCString()); |
} |
@@ -216,17 +362,27 @@ void IsolateReloadContext::ReportError(const Error& error) { |
} |
-void IsolateReloadContext::ReportError(const String& error_msg) { |
- ReportError(LanguageError::Handle(LanguageError::New(error_msg))); |
-} |
- |
- |
void IsolateReloadContext::ReportSuccess() { |
ServiceEvent service_event(I, ServiceEvent::kIsolateReload); |
Service::HandleEvent(&service_event); |
} |
+class Aborted : public ReasonForCancelling { |
+ public: |
+ explicit Aborted(const Error& error) |
+ : ReasonForCancelling(), error_(error) { } |
+ |
+ private: |
+ const Error& error_; |
+ |
+ RawError* ToError() { return error_.raw(); } |
+ RawString* ToString() { |
+ return String::NewFormatted("%s", error_.ToErrorCString()); |
+ } |
+}; |
+ |
+ |
void IsolateReloadContext::StartReload() { |
TIMELINE_SCOPE(Reload); |
Thread* thread = Thread::Current(); |
@@ -281,7 +437,8 @@ void IsolateReloadContext::StartReload() { |
result = Api::UnwrapHandle(retval); |
} |
if (result.IsError()) { |
- ReportError(Error::Cast(result)); |
+ const Error& error = Error::Cast(result); |
+ AddReasonForCancelling(new Aborted(error)); |
} |
} |
@@ -319,6 +476,7 @@ void IsolateReloadContext::FinishReload() { |
Commit(); |
PostCommit(); |
} else { |
+ ReportReasonsForCancelling(); |
Rollback(); |
} |
// ValidateReload mutates the direct subclass information and does |
@@ -336,7 +494,8 @@ void IsolateReloadContext::FinishReload() { |
void IsolateReloadContext::AbortReload(const Error& error) { |
- ReportError(error); |
+ AddReasonForCancelling(new Aborted(error)); |
+ ReportReasonsForCancelling(); |
Rollback(); |
} |
@@ -585,6 +744,15 @@ void IsolateReloadContext::Commit() { |
TIMELINE_SCOPE(Commit); |
TIR_Print("---- COMMITTING REVERSE MAP\n"); |
+ // Note that the object heap contains before and after instances |
+ // used for morphing. It is therefore important that morphing takes |
+ // place prior to any heap walking. |
+ // So please keep this code at the top of Commit(). |
+ if (HasInstanceMorphers()) { |
+ // Perform shape shifting of instances if necessary. |
+ MorphInstances(); |
+ } |
+ |
#ifdef DEBUG |
VerifyMaps(); |
#endif |
@@ -740,11 +908,108 @@ void IsolateReloadContext::PostCommit() { |
} |
+void IsolateReloadContext::AddReasonForCancelling(ReasonForCancelling* reason) { |
+ reasons_to_cancel_reload_.Add(reason); |
+} |
+ |
+ |
+void IsolateReloadContext::AddInstanceMorpher(InstanceMorpher* morpher) { |
+ instance_morphers_.Add(morpher); |
+ cid_mapper_.Insert(morpher); |
+} |
+ |
+ |
+void IsolateReloadContext::ReportReasonsForCancelling() { |
+ ASSERT(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_; |
+}; |
+ |
+ |
+void IsolateReloadContext::MorphInstances() { |
+ TIMELINE_SCOPE(MorphInstances); |
+ ASSERT(HasInstanceMorphers()); |
+ 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. |
+ ObjectLocator locator(this); |
+ isolate()->heap()->VisitObjects(&locator); |
+ |
+ // Return if no objects are located. |
+ intptr_t count = locator.count(); |
+ if (count == 0) return; |
+ |
+ TIR_Print("Found %" Pd " object%s subject to morphing.\n", |
+ count, (count > 1) ? "s" : ""); |
+ |
+ Array& before = Array::Handle(); |
+ Array& after = Array::Handle(); |
+ { // Prevent GC to take place due object format confusion. |
+ // Hint: More than one class share the same cid. |
+ NoHeapGrowthControlScope scope; |
+ for (intptr_t i = 0; i < instance_morphers_.length(); i++) { |
+ instance_morphers_.At(i)->CreateMorphedCopies(); |
+ } |
+ // Create the inputs for Become. |
+ intptr_t index = 0; |
+ before = Array::New(count); |
+ after = 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); |
+ } |
+ |
+ // This is important: The saved class table (describing before objects) |
+ // must be zapped to prevent the forwarding in GetClassForHeapWalkAt. |
+ // Instance will from now be described by the isolate's class table. |
+ free(saved_class_table_); |
+ saved_class_table_ = NULL; |
+ Become::ElementsForwardIdentity(before, after); |
+} |
+ |
+ |
bool IsolateReloadContext::ValidateReload() { |
TIMELINE_SCOPE(ValidateReload); |
- if (has_error_) { |
- return false; |
- } |
+ if (has_error()) return false; |
// Validate libraries. |
{ |
@@ -758,10 +1023,7 @@ bool IsolateReloadContext::ValidateReload() { |
new_lib = Library::RawCast(map.GetKey(entry)); |
lib = Library::RawCast(map.GetPayload(entry, 0)); |
if (new_lib.raw() != lib.raw()) { |
- if (!lib.CanReload(new_lib)) { |
- map.Release(); |
- return false; |
- } |
+ lib.CheckReload(new_lib, this); |
} |
} |
map.Release(); |
@@ -779,15 +1041,13 @@ bool IsolateReloadContext::ValidateReload() { |
new_cls = Class::RawCast(map.GetKey(entry)); |
cls = Class::RawCast(map.GetPayload(entry, 0)); |
if (new_cls.raw() != cls.raw()) { |
- if (!cls.CanReload(new_cls)) { |
- map.Release(); |
- return false; |
- } |
+ cls.CheckReload(new_cls, this); |
} |
} |
map.Release(); |
} |
- return true; |
+ |
+ return !HasReasonsForCancelling(); |
} |