| Index: runtime/vm/object_reload.cc
|
| diff --git a/runtime/vm/object_reload.cc b/runtime/vm/object_reload.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..2cf57e48e9f88a0cf04b919959631d9fdb572491
|
| --- /dev/null
|
| +++ b/runtime/vm/object_reload.cc
|
| @@ -0,0 +1,548 @@
|
| +// 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/object.h"
|
| +
|
| +#include "vm/isolate_reload.h"
|
| +#include "vm/log.h"
|
| +#include "vm/resolver.h"
|
| +#include "vm/symbols.h"
|
| +
|
| +namespace dart {
|
| +
|
| +#ifndef PRODUCT
|
| +
|
| +DECLARE_FLAG(bool, trace_reload);
|
| +DECLARE_FLAG(bool, two_args_smi_icd);
|
| +
|
| +#define IRC (Isolate::Current()->reload_context())
|
| +
|
| +class ObjectReloadUtils : public AllStatic {
|
| + static void DumpLibraryDictionary(const Library& lib) {
|
| + DictionaryIterator it(lib);
|
| + Object& entry = Object::Handle();
|
| + String& name = String::Handle();
|
| + TIR_Print("Dumping dictionary for %s\n", lib.ToCString());
|
| + while (it.HasNext()) {
|
| + entry = it.GetNext();
|
| + name = entry.DictionaryName();
|
| + TIR_Print("%s -> %s\n", name.ToCString(), entry.ToCString());
|
| + }
|
| + }
|
| +};
|
| +
|
| +
|
| +void Function::Reparent(const Class& new_cls) const {
|
| + set_owner(new_cls);
|
| +}
|
| +
|
| +
|
| +void Function::ZeroEdgeCounters() const {
|
| + const Array& saved_ic_data = Array::Handle(ic_data_array());
|
| + if (saved_ic_data.IsNull()) {
|
| + return;
|
| + }
|
| + const intptr_t saved_ic_datalength = saved_ic_data.Length();
|
| + ASSERT(saved_ic_datalength > 0);
|
| + const Array& edge_counters_array =
|
| + Array::Handle(Array::RawCast(saved_ic_data.At(0)));
|
| + ASSERT(!edge_counters_array.IsNull());
|
| + // Fill edge counters array with zeros.
|
| + const Smi& zero = Smi::Handle(Smi::New(0));
|
| + for (intptr_t i = 0; i < edge_counters_array.Length(); i++) {
|
| + edge_counters_array.SetAt(i, zero);
|
| + }
|
| +}
|
| +
|
| +
|
| +static void ClearICs(const Function& function, const Code& code) {
|
| + if (function.ic_data_array() == Array::null()) {
|
| + return; // Already reset in an earlier round.
|
| + }
|
| +
|
| + Thread* thread = Thread::Current();
|
| + Zone* zone = thread->zone();
|
| +
|
| + ZoneGrowableArray<const ICData*>* ic_data_array =
|
| + new(zone) ZoneGrowableArray<const ICData*>();
|
| + function.RestoreICDataMap(ic_data_array, false /* clone ic-data */);
|
| + if (ic_data_array->length() == 0) {
|
| + return;
|
| + }
|
| + const PcDescriptors& descriptors =
|
| + PcDescriptors::Handle(code.pc_descriptors());
|
| + PcDescriptors::Iterator iter(descriptors, RawPcDescriptors::kIcCall |
|
| + RawPcDescriptors::kUnoptStaticCall);
|
| + while (iter.MoveNext()) {
|
| + const ICData* ic_data = (*ic_data_array)[iter.DeoptId()];
|
| + if (ic_data == NULL) {
|
| + continue;
|
| + }
|
| + bool is_static_call = iter.Kind() == RawPcDescriptors::kUnoptStaticCall;
|
| + ic_data->Reset(is_static_call);
|
| + }
|
| +}
|
| +
|
| +
|
| +void Function::FillICDataWithSentinels(const Code& code) const {
|
| + ASSERT(code.raw() == CurrentCode());
|
| + ClearICs(*this, code);
|
| +}
|
| +
|
| +
|
| +void Class::CopyStaticFieldValues(const Class& old_cls) const {
|
| + // We only update values for non-enum classes.
|
| + const bool update_values = !is_enum_class();
|
| +
|
| + IsolateReloadContext* reload_context = Isolate::Current()->reload_context();
|
| + ASSERT(reload_context != NULL);
|
| +
|
| + const Array& old_field_list = Array::Handle(old_cls.fields());
|
| + Field& old_field = Field::Handle();
|
| + String& old_name = String::Handle();
|
| +
|
| + const Array& field_list = Array::Handle(fields());
|
| + Field& field = Field::Handle();
|
| + String& name = String::Handle();
|
| +
|
| + Instance& value = Instance::Handle();
|
| + for (intptr_t i = 0; i < field_list.Length(); i++) {
|
| + field = Field::RawCast(field_list.At(i));
|
| + name = field.name();
|
| + if (field.is_static()) {
|
| + // Find the corresponding old field, if it exists, and migrate
|
| + // over the field value.
|
| + for (intptr_t j = 0; j < old_field_list.Length(); j++) {
|
| + old_field = Field::RawCast(old_field_list.At(j));
|
| + old_name = old_field.name();
|
| + if (name.Equals(old_name)) {
|
| + if (update_values) {
|
| + value = old_field.StaticValue();
|
| + field.SetStaticValue(value);
|
| + }
|
| + reload_context->AddStaticFieldMapping(old_field, field);
|
| + }
|
| + }
|
| + }
|
| + }
|
| +}
|
| +
|
| +
|
| +void Class::CopyCanonicalConstants(const Class& old_cls) const {
|
| + if (is_enum_class()) {
|
| + return;
|
| + }
|
| +#if defined(DEBUG)
|
| + {
|
| + // Class has no canonical constants allocated.
|
| + const Array& my_constants = Array::Handle(constants());
|
| + ASSERT(my_constants.Length() == 0);
|
| + }
|
| +#endif // defined(DEBUG).
|
| + // Copy old constants into new class.
|
| + const Array& old_constants = Array::Handle(old_cls.constants());
|
| + if (old_constants.IsNull() || old_constants.Length() == 0) {
|
| + return;
|
| + }
|
| + TIR_Print("Copied %" Pd " canonical constants for class `%s`\n",
|
| + old_constants.Length(),
|
| + ToCString());
|
| + set_constants(old_constants);
|
| +}
|
| +
|
| +
|
| +void Class::CopyCanonicalTypes(const Class& old_cls) const {
|
| + const Object& old_canonical_types = Object::Handle(old_cls.canonical_types());
|
| + if (old_canonical_types.IsNull()) {
|
| + return;
|
| + }
|
| + set_canonical_types(old_canonical_types);
|
| +}
|
| +
|
| +
|
| +static intptr_t IndexOfEnum(const Array& enum_names, const String& name) {
|
| + ASSERT(!enum_names.IsNull());
|
| + ASSERT(!name.IsNull());
|
| + String& enum_name = String::Handle();
|
| + for (intptr_t i = 0; i < enum_names.Length(); i++) {
|
| + enum_name = String::RawCast(enum_names.At(i));
|
| + ASSERT(!enum_name.IsNull());
|
| + if (enum_name.Equals(name)) {
|
| + return i;
|
| + }
|
| + }
|
| +
|
| + return -1;
|
| +}
|
| +
|
| +
|
| +static void UpdateEnumIndex(const Instance& enum_value,
|
| + const Field& enum_index_field,
|
| + const intptr_t index) {
|
| + enum_value.SetField(enum_index_field, Smi::Handle(Smi::New(index)));
|
| +}
|
| +
|
| +
|
| +// TODO(johnmccutchan): The code in the class finalizer canonicalizes all
|
| +// instances and the values array. We probably should do the same thing.
|
| +void Class::ReplaceEnum(const Class& old_enum) const {
|
| + // We only do this for finalized enum classes.
|
| + ASSERT(is_enum_class());
|
| + ASSERT(old_enum.is_enum_class());
|
| + ASSERT(is_finalized());
|
| + ASSERT(old_enum.is_finalized());
|
| +
|
| + Thread* thread = Thread::Current();
|
| + IsolateReloadContext* reload_context = Isolate::Current()->reload_context();
|
| + ASSERT(reload_context != NULL);
|
| +
|
| + TIR_Print("ReplaceEnum `%s` (%" Pd " and %" Pd ")\n",
|
| + ToCString(), id(), old_enum.id());
|
| +
|
| + // Grab '_enum_names' from |old_enum|.
|
| + const Field& old_enum_names_field = Field::Handle(
|
| + old_enum.LookupStaticFieldAllowPrivate(Symbols::_EnumNames()));
|
| + ASSERT(!old_enum_names_field.IsNull());
|
| + const Array& old_enum_names =
|
| + Array::Handle(Array::RawCast(old_enum_names_field.StaticValue()));
|
| + ASSERT(!old_enum_names.IsNull());
|
| +
|
| + // Grab 'values' from |old_enum|.
|
| + const Field& old_enum_values_field = Field::Handle(
|
| + old_enum.LookupStaticField(Symbols::Values()));
|
| + ASSERT(!old_enum_values_field.IsNull());
|
| + const Array& old_enum_values =
|
| + Array::Handle(Array::RawCast(old_enum_values_field.StaticValue()));
|
| + ASSERT(!old_enum_values.IsNull());
|
| +
|
| + // Grab _enum_names from |this|.
|
| + const Field& enum_names_field = Field::Handle(
|
| + LookupStaticFieldAllowPrivate(Symbols::_EnumNames()));
|
| + ASSERT(!enum_names_field.IsNull());
|
| + Array& enum_names =
|
| + Array::Handle(Array::RawCast(enum_names_field.StaticValue()));
|
| + ASSERT(!enum_names.IsNull());
|
| +
|
| + // Grab values from |this|.
|
| + const Field& enum_values_field = Field::Handle(
|
| + LookupStaticField(Symbols::Values()));
|
| + ASSERT(!enum_values_field.IsNull());
|
| + Array& enum_values =
|
| + Array::Handle(Array::RawCast(enum_values_field.StaticValue()));
|
| + ASSERT(!enum_values.IsNull());
|
| +
|
| + // Grab the |index| field.
|
| + const Field& index_field =
|
| + Field::Handle(old_enum.LookupInstanceField(Symbols::Index()));
|
| + ASSERT(!index_field.IsNull());
|
| +
|
| + // Build list of enum from |old_enum| that aren't present in |this|.
|
| + // This array holds pairs: (name, value).
|
| + const GrowableObjectArray& to_add =
|
| + GrowableObjectArray::Handle(GrowableObjectArray::New(Heap::kOld));
|
| + const String& enum_class_name = String::Handle(UserVisibleName());
|
| + String& enum_name = String::Handle();
|
| + String& enum_field_name = String::Handle();
|
| + Object& enum_value = Object::Handle();
|
| + Field& enum_field = Field::Handle();
|
| +
|
| + TIR_Print("New version of enum has %" Pd " elements\n",
|
| + enum_values.Length());
|
| + TIR_Print("Old version of enum had %" Pd " elements\n",
|
| + old_enum_values.Length());
|
| +
|
| + for (intptr_t i = 0; i < old_enum_names.Length(); i++) {
|
| + enum_name = String::RawCast(old_enum_names.At(i));
|
| + const intptr_t index_in_new_cls = IndexOfEnum(enum_names, enum_name);
|
| + if (index_in_new_cls < 0) {
|
| + // Doesn't exist in new enum, add.
|
| + TIR_Print("Adding enum value `%s` to %s\n",
|
| + enum_name.ToCString(),
|
| + this->ToCString());
|
| + enum_value = old_enum_values.At(i);
|
| + ASSERT(!enum_value.IsNull());
|
| + to_add.Add(enum_name);
|
| + to_add.Add(enum_value);
|
| + } else {
|
| + // Exists in both the new and the old.
|
| + TIR_Print("Moving enum value `%s` to %" Pd "\n",
|
| + enum_name.ToCString(),
|
| + index_in_new_cls);
|
| + // Grab old value.
|
| + enum_value = old_enum_values.At(i);
|
| + // Update index to the be new index.
|
| + UpdateEnumIndex(Instance::Cast(enum_value),
|
| + index_field,
|
| + index_in_new_cls);
|
| + // Chop off the 'EnumClass.'
|
| + enum_field_name = String::SubString(enum_name,
|
| + enum_class_name.Length() + 1);
|
| + ASSERT(!enum_field_name.IsNull());
|
| + // Grab the static field.
|
| + enum_field = LookupStaticField(enum_field_name);
|
| + ASSERT(!enum_field.IsNull());
|
| + // Use old value with updated index.
|
| + enum_field.SetStaticValue(Instance::Cast(enum_value), true);
|
| + enum_values.SetAt(index_in_new_cls, enum_value);
|
| + enum_names.SetAt(index_in_new_cls, enum_name);
|
| + }
|
| + }
|
| +
|
| + if (to_add.Length() == 0) {
|
| + // Nothing to do.
|
| + TIR_Print("Found no missing enums in %s\n", ToCString());
|
| + return;
|
| + }
|
| +
|
| + // Grow the values and enum_names arrays.
|
| + const intptr_t offset = enum_names.Length();
|
| + const intptr_t num_to_add = to_add.Length() / 2;
|
| + ASSERT(offset == enum_values.Length());
|
| + enum_names = Array::Grow(enum_names,
|
| + enum_names.Length() + num_to_add,
|
| + Heap::kOld);
|
| + enum_values = Array::Grow(enum_values,
|
| + enum_values.Length() + num_to_add,
|
| + Heap::kOld);
|
| +
|
| + // Install new names and values into the grown arrays. Also, update
|
| + // the index of the new enum values and add static fields for the new
|
| + // enum values.
|
| + Field& enum_value_field = Field::Handle();
|
| + for (intptr_t i = 0; i < num_to_add; i++) {
|
| + const intptr_t target_index = offset + i;
|
| + enum_name = String::RawCast(to_add.At(i * 2));
|
| + enum_value = to_add.At(i * 2 + 1);
|
| +
|
| + // Update the enum value's index into the new arrays.
|
| + TIR_Print("Updating index of %s in %s to %" Pd "\n",
|
| + enum_name.ToCString(),
|
| + ToCString(),
|
| + target_index);
|
| + UpdateEnumIndex(Instance::Cast(enum_value), index_field, target_index);
|
| +
|
| + enum_names.SetAt(target_index, enum_name);
|
| + enum_values.SetAt(target_index, enum_value);
|
| +
|
| + // Install new static field into class.
|
| + // Chop off the 'EnumClass.'
|
| + enum_field_name = String::SubString(enum_name,
|
| + enum_class_name.Length() + 1);
|
| + ASSERT(!enum_field_name.IsNull());
|
| + enum_field_name = Symbols::New(thread, enum_field_name);
|
| + enum_value_field = Field::New(enum_field_name,
|
| + /* is_static = */ true,
|
| + /* is_final = */ true,
|
| + /* is_const = */ true,
|
| + /* is_reflectable = */ true,
|
| + *this,
|
| + Object::dynamic_type(),
|
| + token_pos());
|
| + enum_value_field.set_has_initializer(false);
|
| + enum_value_field.SetStaticValue(Instance::Cast(enum_value), true);
|
| + enum_value_field.RecordStore(Instance::Cast(enum_value));
|
| + AddField(enum_value_field);
|
| + }
|
| +
|
| + // Replace the arrays stored in the static fields.
|
| + enum_names_field.SetStaticValue(enum_names, true);
|
| + enum_values_field.SetStaticValue(enum_values, true);
|
| +}
|
| +
|
| +
|
| +void Class::PatchFieldsAndFunctions() const {
|
| + // Move all old functions and fields to a patch class so that they
|
| + // still refer to their original script.
|
| + const PatchClass& patch =
|
| + PatchClass::Handle(PatchClass::New(*this, Script::Handle(script())));
|
| + ASSERT(!patch.IsNull());
|
| +
|
| + const Array& funcs = Array::Handle(functions());
|
| + Function& func = Function::Handle();
|
| + Object& owner = Object::Handle();
|
| + for (intptr_t i = 0; i < funcs.Length(); i++) {
|
| + func = Function::RawCast(funcs.At(i));
|
| + if ((func.token_pos() == TokenPosition::kMinSource) ||
|
| + func.IsClosureFunction()) {
|
| + // Eval functions do not need to have their script updated.
|
| + //
|
| + // Closure functions refer to the parent's script which we can
|
| + // rely on being updated for us, if necessary.
|
| + continue;
|
| + }
|
| +
|
| + // If the source for this function is already patched, leave it alone.
|
| + owner = func.RawOwner();
|
| + ASSERT(!owner.IsNull());
|
| + if (!owner.IsPatchClass()) {
|
| + ASSERT(owner.raw() == this->raw());
|
| + func.set_owner(patch);
|
| + }
|
| + }
|
| +
|
| + const Array& field_list = Array::Handle(fields());
|
| + Field& field = Field::Handle();
|
| + for (intptr_t i = 0; i < field_list.Length(); i++) {
|
| + field = Field::RawCast(field_list.At(i));
|
| + owner = field.RawOwner();
|
| + ASSERT(!owner.IsNull());
|
| + if (!owner.IsPatchClass()) {
|
| + ASSERT(owner.raw() == this->raw());
|
| + field.set_owner(patch);
|
| + }
|
| + field.ForceDynamicGuardedCidAndLength();
|
| + }
|
| +}
|
| +
|
| +
|
| +bool Class::CanReload(const Class& replacement) const {
|
| + ASSERT(IsolateReloadContext::IsSameClass(*this, replacement));
|
| +
|
| + if (is_enum_class() && !replacement.is_enum_class()) {
|
| + IRC->ReportError(String::Handle(String::NewFormatted(
|
| + "Enum class cannot be redefined to be a non-enum class: %s",
|
| + ToCString())));
|
| + return false;
|
| + }
|
| +
|
| + if (!is_enum_class() && replacement.is_enum_class()) {
|
| + IRC->ReportError(String::Handle(String::NewFormatted(
|
| + "Class cannot be redefined to be a enum class: %s",
|
| + ToCString())));
|
| + return false;
|
| + }
|
| +
|
| + if (is_finalized()) {
|
| + const Error& error =
|
| + Error::Handle(replacement.EnsureIsFinalized(Thread::Current()));
|
| + if (!error.IsNull()) {
|
| + IRC->ReportError(error);
|
| + return false;
|
| + }
|
| + TIR_Print("Finalized replacement class for %s\n", ToCString());
|
| + }
|
| +
|
| + if (is_finalized()) {
|
| + // Get the field maps for both classes. These field maps walk the class
|
| + // hierarchy.
|
| + const Array& fields =
|
| + Array::Handle(OffsetToFieldMap());
|
| + const Array& replacement_fields =
|
| + Array::Handle(replacement.OffsetToFieldMap());
|
| +
|
| + // Check that we have the same number of fields.
|
| + if (fields.Length() != replacement_fields.Length()) {
|
| + IRC->ReportError(String::Handle(String::NewFormatted(
|
| + "Number of instance fields changed in %s", ToCString())));
|
| + return false;
|
| + }
|
| +
|
| + if (NumTypeArguments() != replacement.NumTypeArguments()) {
|
| + IRC->ReportError(String::Handle(String::NewFormatted(
|
| + "Number of type arguments changed in %s", ToCString())));
|
| + return false;
|
| + }
|
| +
|
| + // Verify that field names / offsets match across the entire hierarchy.
|
| + Field& field = Field::Handle();
|
| + String& field_name = String::Handle();
|
| + Field& replacement_field = Field::Handle();
|
| + String& replacement_field_name = String::Handle();
|
| + for (intptr_t i = 0; i < fields.Length(); i++) {
|
| + if (fields.At(i) == Field::null()) {
|
| + ASSERT(replacement_fields.At(i) == Field::null());
|
| + continue;
|
| + }
|
| + field = Field::RawCast(fields.At(i));
|
| + replacement_field = Field::RawCast(replacement_fields.At(i));
|
| + field_name = field.name();
|
| + replacement_field_name = replacement_field.name();
|
| + if (!field_name.Equals(replacement_field_name)) {
|
| + IRC->ReportError(String::Handle(String::NewFormatted(
|
| + "Name of instance field changed ('%s' vs '%s') in '%s'",
|
| + field_name.ToCString(),
|
| + replacement_field_name.ToCString(),
|
| + ToCString())));
|
| + return false;
|
| + }
|
| + }
|
| + } else if (is_prefinalized()) {
|
| + if (!replacement.is_prefinalized()) {
|
| + IRC->ReportError(String::Handle(String::NewFormatted(
|
| + "Original class ('%s') is prefinalized and replacement class ('%s')",
|
| + ToCString(), replacement.ToCString())));
|
| + return false;
|
| + }
|
| + if (instance_size() != replacement.instance_size()) {
|
| + IRC->ReportError(String::Handle(String::NewFormatted(
|
| + "Instance size mismatch between '%s' (%" Pd ") and replacement "
|
| + "'%s' ( %" Pd ")",
|
| + ToCString(),
|
| + instance_size(),
|
| + replacement.ToCString(),
|
| + replacement.instance_size())));
|
| + return false;
|
| + }
|
| + }
|
| +
|
| + // native field count check.
|
| + if (num_native_fields() != replacement.num_native_fields()) {
|
| + IRC->ReportError(String::Handle(String::NewFormatted(
|
| + "Number of native fields changed in %s", ToCString())));
|
| + return false;
|
| + }
|
| +
|
| + // TODO(johnmccutchan) type parameter count check.
|
| +
|
| + TIR_Print("Class `%s` can be reloaded (%" Pd " and %" Pd ")\n",
|
| + ToCString(),
|
| + id(),
|
| + replacement.id());
|
| + return true;
|
| +}
|
| +
|
| +
|
| +bool Library::CanReload(const Library& replacement) const {
|
| + return true;
|
| +}
|
| +
|
| +
|
| +static const Function* static_call_target = NULL;
|
| +
|
| +void ICData::Reset(bool is_static_call) const {
|
| + // TODO(johnmccutchan): ICData should know whether or not it's for a
|
| + // static call.
|
| + if (is_static_call) {
|
| + const Function& old_target = Function::Handle(GetTargetAt(0));
|
| + if (old_target.IsNull()) {
|
| + FATAL("old_target is NULL.\n");
|
| + }
|
| + static_call_target = &old_target;
|
| + if (!old_target.is_static()) {
|
| + // TODO(johnmccutchan): Improve this.
|
| + TIR_Print("Cannot rebind super-call to %s from %s\n",
|
| + old_target.ToCString(),
|
| + Object::Handle(Owner()).ToCString());
|
| + return;
|
| + }
|
| + const String& selector = String::Handle(old_target.name());
|
| + const Class& cls = Class::Handle(old_target.Owner());
|
| + const Function& new_target =
|
| + Function::Handle(cls.LookupStaticFunction(selector));
|
| + if (new_target.IsNull()) {
|
| + // TODO(johnmccutchan): Improve this.
|
| + TIR_Print("Cannot rebind static call to %s from %s\n",
|
| + old_target.ToCString(),
|
| + Object::Handle(Owner()).ToCString());
|
| + return;
|
| + }
|
| + ClearAndSetStaticTarget(new_target);
|
| + } else {
|
| + ClearWithSentinel();
|
| + }
|
| +}
|
| +
|
| +#endif // !PRODUCT
|
| +
|
| +} // namespace dart.
|
|
|