Index: runtime/vm/object_reload.cc |
diff --git a/runtime/vm/object_reload.cc b/runtime/vm/object_reload.cc |
index 9b2e3e83e3328e2da5a1729c6e97b4ffb7bdf01f..838e9b8375b3d4d6850ced7752f6c8bfc5bfc4c9 100644 |
--- a/runtime/vm/object_reload.cc |
+++ b/runtime/vm/object_reload.cc |
@@ -4,6 +4,7 @@ |
#include "vm/object.h" |
+#include "vm/hash_table.h" |
#include "vm/isolate_reload.h" |
#include "vm/log.h" |
#include "vm/resolver.h" |
@@ -14,6 +15,7 @@ namespace dart { |
#ifndef PRODUCT |
DECLARE_FLAG(bool, trace_reload); |
+DECLARE_FLAG(bool, trace_reload_verbose); |
DECLARE_FLAG(bool, two_args_smi_icd); |
#define IRC (Isolate::Current()->reload_context()) |
@@ -141,6 +143,8 @@ void Class::CopyStaticFieldValues(const Class& old_cls) const { |
void Class::CopyCanonicalConstants(const Class& old_cls) const { |
if (is_enum_class()) { |
+ // We do not copy enum classes's canonical constants because we explicitly |
+ // become the old enum values to the new enum values. |
return; |
} |
#if defined(DEBUG) |
@@ -171,31 +175,47 @@ void Class::CopyCanonicalType(const Class& old_cls) const { |
} |
-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; |
-} |
+class EnumMapTraits { |
+ public: |
+ static bool ReportStats() { return false; } |
+ static const char* Name() { return "EnumMapTraits"; } |
+ static bool IsMatch(const Object& a, const Object& b) { |
+ return a.raw() == b.raw(); |
+ } |
-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))); |
-} |
+ static uword Hash(const Object& obj) { |
+ ASSERT(obj.IsString()); |
+ return String::Cast(obj).Hash(); |
+ } |
+}; |
-// TODO(johnmccutchan): The code in the class finalizer canonicalizes all |
-// instances and the values array. We probably should do the same thing. |
+// Given an old enum class, add become mappings from old values to new values. |
+// Some notes about how we reload enums below: |
+// |
+// When an enum is reloaded the following three things can happen, possibly |
+// simultaneously. |
+// |
+// 1) A new enum value is added. |
+// This case is handled automatically. |
+// 2) Enum values are reordered. |
+// We pair old and new enums and the old enums 'become' the new ones so |
+// the ordering is always correct (i.e. enum indicies match slots in values |
+// array) |
+// 3) An existing enum value is removed. |
+// We leave old enum values that have no mapping to the reloaded class |
+// in the heap. This means that if a programmer does the following: |
+// enum Foo { A, B }; var x = Foo.A; |
+// *reload* |
+// enum Foo { B }; |
+// *reload* |
+// enum Foo { A, B }; expect(identical(x, Foo.A)); |
+// The program will fail because we were not able to pair Foo.A on the second |
+// reload. |
+// |
+// Deleted enum values still in the heap continue to function but their |
+// index field will not be valid. |
void Class::ReplaceEnum(const Class& old_enum) const { |
// We only do this for finalized enum classes. |
ASSERT(is_enum_class()); |
@@ -204,160 +224,98 @@ void Class::ReplaceEnum(const Class& old_enum) const { |
ASSERT(old_enum.is_finalized()); |
Thread* thread = Thread::Current(); |
+ Zone* zone = thread->zone(); |
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.LookupStaticFieldAllowPrivate(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( |
- LookupStaticFieldAllowPrivate(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 = LookupStaticFieldAllowPrivate(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); |
+ Array& enum_fields = Array::Handle(zone); |
+ Field& field = Field::Handle(zone); |
+ String& enum_ident = String::Handle(); |
+ Instance& old_enum_value = Instance::Handle(zone); |
+ Instance& enum_value = Instance::Handle(zone); |
+ |
+ Array& enum_map_storage = Array::Handle(zone, |
+ HashTables::New<UnorderedHashMap<EnumMapTraits> >(4)); |
+ ASSERT(!enum_map_storage.IsNull()); |
+ |
+ TIR_Print("Replacing enum `%s`\n", String::Handle(Name()).ToCString()); |
+ |
+ { |
+ UnorderedHashMap<EnumMapTraits> enum_map(enum_map_storage.raw()); |
+ // Build a map of all enum name -> old enum instance. |
+ enum_fields = old_enum.fields(); |
+ for (intptr_t i = 0; i < enum_fields.Length(); i++) { |
+ field = Field::RawCast(enum_fields.At(i)); |
+ enum_ident = field.name(); |
+ if (!field.is_static()) { |
+ // Enum instances are only held in static fields. |
+ continue; |
+ } |
+ if (enum_ident.Equals(Symbols::Values())) { |
+ // Non-enum instance. |
+ continue; |
+ } |
+ old_enum_value = field.StaticValue(); |
+ ASSERT(!old_enum_value.IsNull()); |
+ VTIR_Print("Element %s being added to mapping\n", enum_ident.ToCString()); |
+ bool update = enum_map.UpdateOrInsert(enum_ident, old_enum_value); |
+ VTIR_Print("Element %s added to mapping\n", enum_ident.ToCString()); |
+ ASSERT(!update); |
} |
+ // The storage given to the map may have been reallocated, remember the new |
+ // address. |
+ enum_map_storage = enum_map.Release().raw(); |
} |
- if (to_add.Length() == 0) { |
- // Nothing to do. |
- TIR_Print("Found no missing enums in %s\n", ToCString()); |
- return; |
+ bool enums_deleted = false; |
+ { |
+ UnorderedHashMap<EnumMapTraits> enum_map(enum_map_storage.raw()); |
+ // Add a become mapping from the old instances to the new instances. |
+ enum_fields = fields(); |
+ for (intptr_t i = 0; i < enum_fields.Length(); i++) { |
+ field = Field::RawCast(enum_fields.At(i)); |
+ enum_ident = field.name(); |
+ if (!field.is_static()) { |
+ // Enum instances are only held in static fields. |
+ continue; |
+ } |
+ if (enum_ident.Equals(Symbols::Values())) { |
+ // Non-enum instance. |
+ continue; |
+ } |
+ enum_value = field.StaticValue(); |
+ ASSERT(!enum_value.IsNull()); |
+ old_enum_value ^= enum_map.GetOrNull(enum_ident); |
+ if (old_enum_value.IsNull()) { |
+ VTIR_Print("New element %s was not found in mapping\n", |
+ enum_ident.ToCString()); |
+ } else { |
+ VTIR_Print("Adding element `%s` to become mapping\n", |
+ enum_ident.ToCString()); |
+ bool removed = enum_map.Remove(enum_ident); |
+ ASSERT(removed); |
+ reload_context->AddEnumBecomeMapping(old_enum_value, enum_value); |
+ } |
+ } |
+ enums_deleted = enum_map.NumOccupied() > 0; |
+ // The storage given to the map may have been reallocated, remember the new |
+ // address. |
+ enum_map_storage = enum_map.Release().raw(); |
} |
- // 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); |
+ if (enums_deleted && FLAG_trace_reload_verbose) { |
+ // TODO(johnmccutchan): Add this to the reload 'notices' list. |
+ VTIR_Print("The following enum values were deleted and are forever lost in " |
+ "the heap:\n"); |
+ UnorderedHashMap<EnumMapTraits> enum_map(enum_map_storage.raw()); |
+ UnorderedHashMap<EnumMapTraits>::Iterator it(&enum_map); |
+ while (it.MoveNext()) { |
+ const intptr_t entry = it.Current(); |
+ enum_ident = String::RawCast(enum_map.GetKey(entry)); |
+ ASSERT(!enum_ident.IsNull()); |
+ } |
+ enum_map.Release(); |
} |
- |
- // Replace the arrays stored in the static fields. |
- enum_names_field.SetStaticValue(enum_names, true); |
- enum_values_field.SetStaticValue(enum_values, true); |
} |
@@ -609,9 +567,9 @@ void ICData::Reset() const { |
if (new_target.IsNull() || |
!new_target.AreValidArguments(args_desc, NULL)) { |
// TODO(rmacnak): Patch to a NSME stub. |
- TIR_Print("Cannot rebind static call to %s from %s\n", |
- old_target.ToCString(), |
- Object::Handle(Owner()).ToCString()); |
+ VTIR_Print("Cannot rebind static call to %s from %s\n", |
+ old_target.ToCString(), |
+ Object::Handle(Owner()).ToCString()); |
return; |
} |
ClearAndSetStaticTarget(new_target); |