7ff2dd4117
The call sequence is very similar to a classic IC call, except the guarded class and the target are loaded indirectly from the constant pool instead of as immediates. In the monomorphic case, we call directly to the expected target with a class check in the callee. In the unlinked, polymorphic and megamorphic cases, we call a stub; these case are now call-through instead of call-and-return. Every code, except stubs involved in switchable calls, includes the class check sequence at the beginning. So we now distinguish between a checked and an unchecked entry point. Generated code except the switchable call continues to use the unchecked entry point. PC offsets are calculated relative to the beginning of the instruction stream, rather than either entry point. BUG= R=fschneider@google.com Review URL: https://codereview.chromium.org/2226893002 .
763 lines
25 KiB
C++
763 lines
25 KiB
C++
// 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/hash_table.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, trace_reload_verbose);
|
|
DECLARE_FLAG(bool, two_args_smi_icd);
|
|
|
|
|
|
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(Zone* zone) 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(zone, instructions());
|
|
ASSERT(!instrs.IsNull());
|
|
uword base_address = instrs.PayloadStart();
|
|
Object& object = Object::Handle(zone);
|
|
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(zone);
|
|
}
|
|
}
|
|
#else
|
|
const ObjectPool& pool = ObjectPool::Handle(zone, object_pool());
|
|
Object& object = Object::Handle(zone);
|
|
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(zone);
|
|
}
|
|
}
|
|
#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()) {
|
|
// 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)
|
|
{
|
|
// 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);
|
|
}
|
|
|
|
|
|
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 uword Hash(const Object& obj) {
|
|
ASSERT(obj.IsString());
|
|
return String::Cast(obj).Hash();
|
|
}
|
|
};
|
|
|
|
|
|
// 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());
|
|
ASSERT(old_enum.is_enum_class());
|
|
ASSERT(is_finalized());
|
|
ASSERT(old_enum.is_finalized());
|
|
|
|
Thread* thread = Thread::Current();
|
|
Zone* zone = thread->zone();
|
|
IsolateReloadContext* reload_context = Isolate::Current()->reload_context();
|
|
ASSERT(reload_context != NULL);
|
|
|
|
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);
|
|
// The E.values array.
|
|
Instance& old_enum_values = Instance::Handle(zone);
|
|
// The E.values array.
|
|
Instance& enum_values = 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())) {
|
|
old_enum_values = field.StaticValue();
|
|
// 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();
|
|
}
|
|
|
|
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())) {
|
|
enum_values = field.StaticValue();
|
|
// 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();
|
|
}
|
|
|
|
// Map the old E.values array to the new E.values array.
|
|
ASSERT(!old_enum_values.IsNull());
|
|
ASSERT(!enum_values.IsNull());
|
|
reload_context->AddEnumBecomeMapping(old_enum_values, enum_values);
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
|
|
void Class::MigrateImplicitStaticClosures(IsolateReloadContext* irc,
|
|
const Class& new_cls) const {
|
|
const Array& funcs = Array::Handle(functions());
|
|
Function& old_func = Function::Handle();
|
|
String& selector = String::Handle();
|
|
Function& new_func = Function::Handle();
|
|
Instance& old_closure = Instance::Handle();
|
|
Instance& new_closure = Instance::Handle();
|
|
for (intptr_t i = 0; i < funcs.Length(); i++) {
|
|
old_func ^= funcs.At(i);
|
|
if (old_func.is_static() &&
|
|
old_func.HasImplicitClosureFunction()) {
|
|
selector = old_func.name();
|
|
new_func = new_cls.LookupFunction(selector);
|
|
if (!new_func.IsNull() && new_func.is_static()) {
|
|
old_func = old_func.ImplicitClosureFunction();
|
|
old_closure = old_func.ImplicitStaticClosure();
|
|
new_func = new_func.ImplicitClosureFunction();
|
|
new_closure = new_func.ImplicitStaticClosure();
|
|
irc->AddBecomeMapping(old_closure, new_closure);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
class EnumClassConflict : public ClassReasonForCancelling {
|
|
public:
|
|
EnumClassConflict(Zone* zone, const Class& from, const Class& to)
|
|
: ClassReasonForCancelling(zone, from, to) { }
|
|
|
|
RawString* ToString() {
|
|
return String::NewFormatted(
|
|
from_.is_enum_class()
|
|
? "Enum class cannot be redefined to be a non-enum class: %s"
|
|
: "Class cannot be redefined to be a enum class: %s",
|
|
from_.ToCString());
|
|
}
|
|
};
|
|
|
|
|
|
class EnsureFinalizedError : public ClassReasonForCancelling {
|
|
public:
|
|
EnsureFinalizedError(Zone* zone,
|
|
const Class& from,
|
|
const Class& to,
|
|
const Error& error)
|
|
: ClassReasonForCancelling(zone, from, to), error_(error) { }
|
|
|
|
private:
|
|
const Error& error_;
|
|
|
|
RawError* ToError() { return error_.raw(); }
|
|
|
|
RawString* ToString() {
|
|
return String::New(error_.ToErrorCString());
|
|
}
|
|
};
|
|
|
|
|
|
class NativeFieldsConflict : public ClassReasonForCancelling {
|
|
public:
|
|
NativeFieldsConflict(Zone* zone, const Class& from, const Class& to)
|
|
: ClassReasonForCancelling(zone, from, to) { }
|
|
|
|
private:
|
|
RawString* ToString() {
|
|
return String::NewFormatted(
|
|
"Number of native fields changed in %s", from_.ToCString());
|
|
}
|
|
};
|
|
|
|
|
|
class TypeParametersChanged : public ClassReasonForCancelling {
|
|
public:
|
|
TypeParametersChanged(Zone* zone, const Class& from, const Class& to)
|
|
: ClassReasonForCancelling(zone, from, to) {}
|
|
|
|
RawString* ToString() {
|
|
return String::NewFormatted(
|
|
"Limitation: type parameters have changed for %s", from_.ToCString());
|
|
}
|
|
|
|
void AppendTo(JSONArray* array) {
|
|
JSONObject jsobj(array);
|
|
jsobj.AddProperty("type", "ReasonForCancellingReload");
|
|
jsobj.AddProperty("kind", "TypeParametersChanged");
|
|
jsobj.AddProperty("class", to_);
|
|
jsobj.AddProperty("message",
|
|
"Limitation: changing type parameters "
|
|
"does not work with hot reload.");
|
|
}
|
|
};
|
|
|
|
|
|
class PreFinalizedConflict : public ClassReasonForCancelling {
|
|
public:
|
|
PreFinalizedConflict(Zone* zone, const Class& from, const Class& to)
|
|
: ClassReasonForCancelling(zone, from, to) {}
|
|
|
|
private:
|
|
RawString* ToString() {
|
|
return String::NewFormatted(
|
|
"Original class ('%s') is prefinalized and replacement class "
|
|
"('%s') is not ",
|
|
from_.ToCString(), to_.ToCString());
|
|
}
|
|
};
|
|
|
|
|
|
class InstanceSizeConflict : public ClassReasonForCancelling {
|
|
public:
|
|
InstanceSizeConflict(Zone* zone, const Class& from, const Class& to)
|
|
: ClassReasonForCancelling(zone, from, to) {}
|
|
|
|
private:
|
|
RawString* ToString() {
|
|
return String::NewFormatted(
|
|
"Instance size mismatch between '%s' (%" Pd ") and replacement "
|
|
"'%s' ( %" Pd ")",
|
|
from_.ToCString(),
|
|
from_.instance_size(),
|
|
to_.ToCString(),
|
|
to_.instance_size());
|
|
}
|
|
};
|
|
|
|
|
|
class UnimplementedDeferredLibrary : public ReasonForCancelling {
|
|
public:
|
|
UnimplementedDeferredLibrary(Zone* zone,
|
|
const Library& from,
|
|
const Library& to,
|
|
const String& name)
|
|
: ReasonForCancelling(zone), from_(from), to_(to), name_(name) {}
|
|
|
|
private:
|
|
const Library& from_;
|
|
const Library& to_;
|
|
const String& name_;
|
|
|
|
RawString* ToString() {
|
|
const String& lib_url = String::Handle(to_.url());
|
|
from_.ToCString();
|
|
return String::NewFormatted(
|
|
"Reloading support for deferred loading has not yet been implemented:"
|
|
" library '%s' has deferred import '%s'",
|
|
lib_url.ToCString(), name_.ToCString());
|
|
}
|
|
};
|
|
|
|
|
|
// This is executed before interating over the instances.
|
|
void Class::CheckReload(const Class& replacement,
|
|
IsolateReloadContext* context) const {
|
|
ASSERT(IsolateReloadContext::IsSameClass(*this, replacement));
|
|
|
|
// Class cannot change enum property.
|
|
if (is_enum_class() != replacement.is_enum_class()) {
|
|
context->AddReasonForCancelling(
|
|
new(context->zone())
|
|
EnumClassConflict(context->zone(), *this, replacement));
|
|
return;
|
|
}
|
|
|
|
if (is_finalized()) {
|
|
// Ensure the replacement class is also finalized.
|
|
const Error& error =
|
|
Error::Handle(replacement.EnsureIsFinalized(Thread::Current()));
|
|
if (!error.IsNull()) {
|
|
context->AddReasonForCancelling(
|
|
new(context->zone())
|
|
EnsureFinalizedError(context->zone(), *this, replacement, error));
|
|
return; // No reason to check other properties.
|
|
}
|
|
ASSERT(replacement.is_finalized());
|
|
TIR_Print("Finalized replacement class for %s\n", ToCString());
|
|
}
|
|
|
|
// Native field count cannot change.
|
|
if (num_native_fields() != replacement.num_native_fields()) {
|
|
context->AddReasonForCancelling(
|
|
new(context->zone())
|
|
NativeFieldsConflict(context->zone(), *this, replacement));
|
|
return;
|
|
}
|
|
|
|
// Just checking.
|
|
ASSERT(is_enum_class() == replacement.is_enum_class());
|
|
ASSERT(num_native_fields() == replacement.num_native_fields());
|
|
|
|
if (is_finalized()) {
|
|
if (!CanReloadFinalized(replacement, context)) return;
|
|
}
|
|
if (is_prefinalized()) {
|
|
if (!CanReloadPreFinalized(replacement, context)) return;
|
|
}
|
|
ASSERT(is_finalized() == replacement.is_finalized());
|
|
TIR_Print("Class `%s` can be reloaded (%" Pd " and %" Pd ")\n",
|
|
ToCString(), id(), replacement.id());
|
|
}
|
|
|
|
|
|
|
|
bool Class::RequiresInstanceMorphing(const Class& replacement) const {
|
|
// 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()) return true;
|
|
|
|
// 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()) return true;
|
|
|
|
// 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)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
bool Class::CanReloadFinalized(const Class& replacement,
|
|
IsolateReloadContext* context) const {
|
|
// Make sure the declaration types matches for the two classes.
|
|
// ex. class A<int,B> {} cannot be replace with class A<B> {}.
|
|
|
|
const AbstractType& dt = AbstractType::Handle(DeclarationType());
|
|
const AbstractType& replacement_dt =
|
|
AbstractType::Handle(replacement.DeclarationType());
|
|
if (!dt.Equals(replacement_dt)) {
|
|
context->AddReasonForCancelling(
|
|
new(context->zone())
|
|
TypeParametersChanged(context->zone(), *this, replacement));
|
|
return false;
|
|
}
|
|
if (RequiresInstanceMorphing(replacement)) {
|
|
context->AddInstanceMorpher(
|
|
new(context->zone())
|
|
InstanceMorpher(context->zone(), *this, replacement));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
bool Class::CanReloadPreFinalized(const Class& replacement,
|
|
IsolateReloadContext* context) const {
|
|
// The replacement class must also prefinalized.
|
|
if (!replacement.is_prefinalized()) {
|
|
context->AddReasonForCancelling(
|
|
new(context->zone())
|
|
PreFinalizedConflict(context->zone(), *this, replacement));
|
|
return false;
|
|
}
|
|
// Check the instance sizes are equal.
|
|
if (instance_size() != replacement.instance_size()) {
|
|
context->AddReasonForCancelling(
|
|
new(context->zone())
|
|
InstanceSizeConflict(context->zone(), *this, replacement));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
void Library::CheckReload(const Library& replacement,
|
|
IsolateReloadContext* context) const {
|
|
// TODO(26878): If the replacement library uses deferred loading,
|
|
// reject it. We do not yet support reloading deferred libraries.
|
|
LibraryPrefix& prefix = LibraryPrefix::Handle();
|
|
LibraryPrefixIterator it(replacement);
|
|
while (it.HasNext()) {
|
|
prefix = it.GetNext();
|
|
if (prefix.is_deferred_load()) {
|
|
const String& prefix_name = String::Handle(prefix.name());
|
|
context->AddReasonForCancelling(
|
|
new(context->zone())
|
|
UnimplementedDeferredLibrary(context->zone(),
|
|
*this, replacement, prefix_name));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static const Function* static_call_target = NULL;
|
|
|
|
|
|
void ICData::Reset(Zone* zone) const {
|
|
if (is_static_call()) {
|
|
const Function& old_target = Function::Handle(zone, GetTargetAt(0));
|
|
if (old_target.IsNull()) {
|
|
FATAL("old_target is NULL.\n");
|
|
}
|
|
static_call_target = &old_target;
|
|
|
|
const String& selector = String::Handle(zone, old_target.name());
|
|
Function& new_target = Function::Handle(zone);
|
|
if (!old_target.is_static()) {
|
|
if (old_target.kind() == RawFunction::kConstructor) {
|
|
return; // Super constructor call.
|
|
}
|
|
Function& caller = Function::Handle(zone);
|
|
caller ^= Owner();
|
|
ASSERT(!caller.is_static());
|
|
Class& cls = Class::Handle(zone, caller.Owner());
|
|
if (cls.raw() == old_target.Owner()) {
|
|
// Dispatcher.
|
|
if (caller.IsImplicitClosureFunction()) {
|
|
return; // Tear-off.
|
|
}
|
|
if (caller.kind() == RawFunction::kNoSuchMethodDispatcher) {
|
|
// TODO(rmacnak): noSuchMethod might have been redefined.
|
|
return;
|
|
}
|
|
const Function& caller_parent =
|
|
Function::Handle(zone, caller.parent_function());
|
|
if (!caller_parent.IsNull()) {
|
|
if (caller_parent.kind() == RawFunction::kInvokeFieldDispatcher) {
|
|
return; // Call-through-getter.
|
|
}
|
|
}
|
|
FATAL2("Unexpected dispatcher-like call site: %s from %s\n",
|
|
selector.ToCString(), caller.ToQualifiedCString());
|
|
}
|
|
// Super call.
|
|
cls = cls.SuperClass();
|
|
while (!cls.IsNull()) {
|
|
// TODO(rmacnak): Should use Resolver::ResolveDynamicAnyArgs to handle
|
|
// method-extractors and call-through-getters, but we're in a no
|
|
// safepoint scope here.
|
|
new_target = cls.LookupDynamicFunction(selector);
|
|
if (!new_target.IsNull()) {
|
|
break;
|
|
}
|
|
cls = cls.SuperClass();
|
|
}
|
|
} else {
|
|
// This can be incorrect if the call site was an unqualified invocation.
|
|
const Class& cls = Class::Handle(zone, old_target.Owner());
|
|
new_target = cls.LookupStaticFunction(selector);
|
|
}
|
|
|
|
const Array& args_desc_array = Array::Handle(zone, arguments_descriptor());
|
|
ArgumentsDescriptor args_desc(args_desc_array);
|
|
if (new_target.IsNull() ||
|
|
!new_target.AreValidArguments(args_desc, NULL)) {
|
|
// TODO(rmacnak): Patch to a NSME stub.
|
|
VTIR_Print("Cannot rebind static call to %s from %s\n",
|
|
old_target.ToCString(),
|
|
Object::Handle(zone, Owner()).ToCString());
|
|
return;
|
|
}
|
|
ClearAndSetStaticTarget(new_target);
|
|
} else {
|
|
intptr_t num_args = NumArgsTested();
|
|
if (num_args == 2) {
|
|
ClearWithSentinel();
|
|
} else {
|
|
const Array& data_array =
|
|
Array::Handle(zone, CachedEmptyICDataArray(num_args));
|
|
set_ic_data_array(data_array);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // !PRODUCT
|
|
|
|
} // namespace dart.
|