blob: f9c5ad9869e4fffd1c825f305413977bfb0a8bb2 [file] [log] [blame]
// 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);
}
}
void Code::ResetICDatas() const {
// Iterate over the Code's object pool and reset all ICDatas.
#ifdef TARGET_ARCH_IA32
// IA32 does not have an object pool, but, we can iterate over all
// embedded objects by using the variable length data section.
if (!is_alive()) {
return;
}
const Instructions& instrs = Instructions::Handle(instructions());
ASSERT(!instrs.IsNull());
uword base_address = instrs.EntryPoint();
Object& object = Object::Handle();
intptr_t offsets_length = pointer_offsets_length();
const int32_t* offsets = raw_ptr()->data();
for (intptr_t i = 0; i < offsets_length; i++) {
int32_t offset = offsets[i];
RawObject** object_ptr =
reinterpret_cast<RawObject**>(base_address + offset);
RawObject* raw_object = *object_ptr;
if (!raw_object->IsHeapObject()) {
continue;
}
object = raw_object;
if (object.IsICData()) {
ICData::Cast(object).Reset();
}
}
#else
const ObjectPool& pool = ObjectPool::Handle(object_pool());
Object& object = Object::Handle();
ASSERT(!pool.IsNull());
for (intptr_t i = 0; i < pool.Length(); i++) {
ObjectPool::EntryType entry_type = pool.InfoAt(i);
if (entry_type != ObjectPool::kTaggedObject) {
continue;
}
object = pool.ObjectAt(i);
if (object.IsICData()) {
ICData::Cast(object).Reset();
}
}
#endif
}
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::CopyCanonicalType(const Class& old_cls) const {
const Type& old_canonical_type = Type::Handle(old_cls.canonical_type());
if (old_canonical_type.IsNull()) {
return;
}
set_canonical_type(old_canonical_type);
}
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());
}
// At this point the original and replacement must be in the same state.
ASSERT(is_finalized() == replacement.is_finalized());
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 the size of the instance is the same.
if (fields.Length() != replacement_fields.Length()) {
IRC->ReportError(String::Handle(String::NewFormatted(
"Number of instance fields changed in %s", ToCString())));
return false;
}
// Check that we have the same next field offset. This check is not
// redundant with the one above because the instance OffsetToFieldMap
// array length is based on the instance size (which may be aligned up).
if (next_field_offset() != replacement.next_field_offset()) {
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') is not ",
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() const {
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.