mirror of
https://github.com/dart-lang/sdk
synced 2024-09-19 23:41:50 +00:00
8617da2bf0
Runtime functions do not yet implement the NNBD semantics. In a next change, generated code will pass NNBD mode to the runtime. Declare one new VM flags: --strong-non-nullable-type-checks Change-Id: I050a468e4a6b665ce46c345bafba04a947cf7cb0 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/124105 Commit-Queue: Régis Crelier <regis@google.com> Reviewed-by: Siva Annamalai <asiva@google.com>
913 lines
32 KiB
C++
913 lines
32 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/code_patcher.h"
|
|
#include "vm/hash_table.h"
|
|
#include "vm/isolate_reload.h"
|
|
#include "vm/log.h"
|
|
#include "vm/object_store.h"
|
|
#include "vm/resolver.h"
|
|
#include "vm/stub_code.h"
|
|
#include "vm/symbols.h"
|
|
|
|
namespace dart {
|
|
|
|
#if !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
DECLARE_FLAG(bool, trace_reload);
|
|
DECLARE_FLAG(bool, trace_reload_verbose);
|
|
DECLARE_FLAG(bool, two_args_smi_icd);
|
|
|
|
void CallSiteResetter::ZeroEdgeCounters(const Function& function) {
|
|
ic_data_array_ = function.ic_data_array();
|
|
if (ic_data_array_.IsNull()) {
|
|
return;
|
|
}
|
|
ASSERT(ic_data_array_.Length() > 0);
|
|
edge_counters_ ^= ic_data_array_.At(0);
|
|
if (edge_counters_.IsNull()) {
|
|
return;
|
|
}
|
|
// Fill edge counters array with zeros.
|
|
for (intptr_t i = 0; i < edge_counters_.Length(); i++) {
|
|
edge_counters_.SetAt(i, Object::smi_zero());
|
|
}
|
|
}
|
|
|
|
CallSiteResetter::CallSiteResetter(Zone* zone)
|
|
: zone_(zone),
|
|
instrs_(Instructions::Handle(zone)),
|
|
pool_(ObjectPool::Handle(zone)),
|
|
object_(Object::Handle(zone)),
|
|
name_(String::Handle(zone)),
|
|
new_cls_(Class::Handle(zone)),
|
|
new_lib_(Library::Handle(zone)),
|
|
new_function_(Function::Handle(zone)),
|
|
new_field_(Field::Handle(zone)),
|
|
entries_(Array::Handle(zone)),
|
|
old_target_(Function::Handle(zone)),
|
|
new_target_(Function::Handle(zone)),
|
|
caller_(Function::Handle(zone)),
|
|
args_desc_array_(Array::Handle(zone)),
|
|
ic_data_array_(Array::Handle(zone)),
|
|
edge_counters_(Array::Handle(zone)),
|
|
descriptors_(PcDescriptors::Handle(zone)),
|
|
ic_data_(ICData::Handle(zone)) {}
|
|
|
|
void CallSiteResetter::ResetCaches(const Code& code) {
|
|
// Iterate over the Code's object pool and reset all ICDatas and
|
|
// SubtypeTestCaches.
|
|
#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 (!code.is_alive()) {
|
|
return;
|
|
}
|
|
instrs_ = code.instructions();
|
|
ASSERT(!instrs_.IsNull());
|
|
uword base_address = instrs_.PayloadStart();
|
|
intptr_t offsets_length = code.pointer_offsets_length();
|
|
const int32_t* offsets = code.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()) {
|
|
Reset(ICData::Cast(object_));
|
|
} else if (object_.IsSubtypeTestCache()) {
|
|
SubtypeTestCache::Cast(object_).Reset();
|
|
}
|
|
}
|
|
#else
|
|
pool_ = code.object_pool();
|
|
ASSERT(!pool_.IsNull());
|
|
ResetCaches(pool_);
|
|
#endif
|
|
}
|
|
|
|
static void FindICData(const Array& ic_data_array,
|
|
intptr_t deopt_id,
|
|
ICData* ic_data) {
|
|
// ic_data_array is sorted because of how it is constructed in
|
|
// Function::SaveICDataMap.
|
|
intptr_t lo = 1;
|
|
intptr_t hi = ic_data_array.Length() - 1;
|
|
while (lo <= hi) {
|
|
intptr_t mid = (hi - lo + 1) / 2 + lo;
|
|
ASSERT(mid >= lo);
|
|
ASSERT(mid <= hi);
|
|
*ic_data ^= ic_data_array.At(mid);
|
|
if (ic_data->deopt_id() == deopt_id) {
|
|
return;
|
|
} else if (ic_data->deopt_id() > deopt_id) {
|
|
hi = mid - 1;
|
|
} else {
|
|
lo = mid + 1;
|
|
}
|
|
}
|
|
FATAL1("Missing deopt id %" Pd "\n", deopt_id);
|
|
}
|
|
|
|
void CallSiteResetter::ResetSwitchableCalls(const Code& code) {
|
|
if (code.is_optimized()) {
|
|
return; // No switchable calls in optimized code.
|
|
}
|
|
|
|
object_ = code.owner();
|
|
if (!object_.IsFunction()) {
|
|
return; // No switchable calls in stub code.
|
|
}
|
|
const Function& function = Function::Cast(object_);
|
|
|
|
if (function.kind() == RawFunction::kIrregexpFunction) {
|
|
// Regex matchers do not support breakpoints or stepping, and they only call
|
|
// core library functions that cannot change due to reload. As a performance
|
|
// optimization, avoid this matching of ICData to PCs for these functions'
|
|
// large number of instance calls.
|
|
ASSERT(!function.is_debuggable());
|
|
return;
|
|
}
|
|
|
|
ic_data_array_ = function.ic_data_array();
|
|
if (ic_data_array_.IsNull()) {
|
|
// The megamorphic miss stub and some recognized function doesn't populate
|
|
// their ic_data_array. Check this only happens for functions without IC
|
|
// calls.
|
|
#if defined(DEBUG)
|
|
descriptors_ = code.pc_descriptors();
|
|
PcDescriptors::Iterator iter(descriptors_, RawPcDescriptors::kIcCall);
|
|
while (iter.MoveNext()) {
|
|
FATAL1("%s has IC calls but no ic_data_array\n", object_.ToCString());
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
descriptors_ = code.pc_descriptors();
|
|
PcDescriptors::Iterator iter(descriptors_, RawPcDescriptors::kIcCall);
|
|
while (iter.MoveNext()) {
|
|
uword pc = code.PayloadStart() + iter.PcOffset();
|
|
CodePatcher::GetInstanceCallAt(pc, code, &object_);
|
|
// This check both avoids unnecessary patching to reduce log spam and
|
|
// prevents patching over breakpoint stubs.
|
|
if (!object_.IsICData()) {
|
|
FindICData(ic_data_array_, iter.DeoptId(), &ic_data_);
|
|
ASSERT(ic_data_.rebind_rule() == ICData::kInstance);
|
|
ASSERT(ic_data_.NumArgsTested() == 1);
|
|
const Code& stub =
|
|
ic_data_.is_tracking_exactness()
|
|
? StubCode::OneArgCheckInlineCacheWithExactnessCheck()
|
|
: StubCode::OneArgCheckInlineCache();
|
|
CodePatcher::PatchInstanceCallAt(pc, code, ic_data_, stub);
|
|
if (FLAG_trace_ic) {
|
|
OS::PrintErr("Instance call at %" Px
|
|
" resetting to polymorphic dispatch, %s\n",
|
|
pc, ic_data_.ToCString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CallSiteResetter::RebindStaticTargets(const Bytecode& bytecode) {
|
|
// Iterate over the Bytecode's object pool and reset all ICDatas.
|
|
pool_ = bytecode.object_pool();
|
|
ASSERT(!pool_.IsNull());
|
|
|
|
for (intptr_t i = 0; i < pool_.Length(); i++) {
|
|
ObjectPool::EntryType entry_type = pool_.TypeAt(i);
|
|
if (entry_type != ObjectPool::EntryType::kTaggedObject) {
|
|
continue;
|
|
}
|
|
object_ = pool_.ObjectAt(i);
|
|
if (object_.IsFunction()) {
|
|
const Function& old_function = Function::Cast(object_);
|
|
if (old_function.IsClosureFunction()) {
|
|
continue;
|
|
}
|
|
name_ = old_function.name();
|
|
new_cls_ = old_function.Owner();
|
|
if (new_cls_.IsTopLevel()) {
|
|
new_lib_ = new_cls_.library();
|
|
new_function_ = new_lib_.LookupLocalFunction(name_);
|
|
} else {
|
|
new_function_ = new_cls_.LookupFunction(name_);
|
|
}
|
|
if (!new_function_.IsNull() &&
|
|
(new_function_.is_static() == old_function.is_static()) &&
|
|
(new_function_.kind() == old_function.kind())) {
|
|
pool_.SetObjectAt(i, new_function_);
|
|
} else {
|
|
VTIR_Print("Cannot rebind function %s\n", old_function.ToCString());
|
|
}
|
|
} else if (object_.IsField()) {
|
|
const Field& old_field = Field::Cast(object_);
|
|
name_ = old_field.name();
|
|
new_cls_ = old_field.Owner();
|
|
if (new_cls_.IsTopLevel()) {
|
|
new_lib_ = new_cls_.library();
|
|
new_field_ = new_lib_.LookupLocalField(name_);
|
|
} else {
|
|
new_field_ = new_cls_.LookupField(name_);
|
|
}
|
|
if (!new_field_.IsNull() &&
|
|
(new_field_.is_static() == old_field.is_static())) {
|
|
pool_.SetObjectAt(i, new_field_);
|
|
} else {
|
|
VTIR_Print("Cannot rebind field %s\n", old_field.ToCString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CallSiteResetter::ResetCaches(const ObjectPool& pool) {
|
|
for (intptr_t i = 0; i < pool.Length(); i++) {
|
|
ObjectPool::EntryType entry_type = pool.TypeAt(i);
|
|
if (entry_type != ObjectPool::EntryType::kTaggedObject) {
|
|
continue;
|
|
}
|
|
object_ = pool.ObjectAt(i);
|
|
if (object_.IsICData()) {
|
|
Reset(ICData::Cast(object_));
|
|
} else if (object_.IsSubtypeTestCache()) {
|
|
SubtypeTestCache::Cast(object_).Reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Class::CopyStaticFieldValues(IsolateReloadContext* reload_context,
|
|
const Class& old_cls) const {
|
|
// We only update values for non-enum classes.
|
|
const bool update_values = !is_enum_class();
|
|
|
|
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();
|
|
// 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 (field.is_static()) {
|
|
// We only copy values if requested and if the field is not a const
|
|
// field. We let const fields be updated with a reload.
|
|
if (update_values && !field.is_const()) {
|
|
value = old_field.StaticValue();
|
|
field.SetStaticValue(value);
|
|
}
|
|
reload_context->AddStaticFieldMapping(old_field, field);
|
|
} else {
|
|
if (old_field.needs_load_guard()) {
|
|
ASSERT(!old_field.is_unboxing_candidate());
|
|
field.set_needs_load_guard(true);
|
|
field.set_is_unboxing_candidate(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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::CopyDeclarationType(const Class& old_cls) const {
|
|
const Type& old_declaration_type = Type::Handle(old_cls.declaration_type());
|
|
if (old_declaration_type.IsNull()) {
|
|
return;
|
|
}
|
|
set_declaration_type(old_declaration_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 indices match slots in values
|
|
// array)
|
|
// 3) An existing enum value is removed.
|
|
// Each enum class has a canonical 'deleted' enum sentinel instance.
|
|
// When an enum value is deleted, we 'become' all references to the 'deleted'
|
|
// sentinel value. The index value is -1.
|
|
//
|
|
void Class::ReplaceEnum(IsolateReloadContext* reload_context,
|
|
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());
|
|
|
|
Zone* zone = Thread::Current()->zone();
|
|
|
|
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);
|
|
// The E._deleted_enum_sentinel instance.
|
|
Instance& old_deleted_enum_sentinel = Instance::Handle(zone);
|
|
// The E._deleted_enum_sentinel instance.
|
|
Instance& deleted_enum_sentinel = 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;
|
|
}
|
|
if (enum_ident.Equals(Symbols::_DeletedEnumSentinel())) {
|
|
old_deleted_enum_sentinel = 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;
|
|
}
|
|
if (enum_ident.Equals(Symbols::_DeletedEnumSentinel())) {
|
|
deleted_enum_sentinel = 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);
|
|
|
|
// Map the old E._deleted_enum_sentinel to the new E._deleted_enum_sentinel.
|
|
ASSERT(!old_deleted_enum_sentinel.IsNull());
|
|
ASSERT(!deleted_enum_sentinel.IsNull());
|
|
reload_context->AddEnumBecomeMapping(old_deleted_enum_sentinel,
|
|
deleted_enum_sentinel);
|
|
|
|
if (enums_deleted) {
|
|
// Map all deleted enums to the deleted enum sentinel value.
|
|
// TODO(johnmccutchan): Add this to the reload 'notices' list.
|
|
VTIR_Print(
|
|
"The following enum values were deleted from %s and will become the "
|
|
"deleted enum sentinel:\n",
|
|
old_enum.ToCString());
|
|
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());
|
|
old_enum_value ^= enum_map.GetOrNull(enum_ident);
|
|
VTIR_Print("Element `%s` was deleted\n", enum_ident.ToCString());
|
|
reload_context->AddEnumBecomeMapping(old_enum_value,
|
|
deleted_enum_sentinel);
|
|
}
|
|
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 Library& lib = Library::Handle(library());
|
|
if (!lib.is_declared_in_bytecode()) {
|
|
patch.set_library_kernel_data(ExternalTypedData::Handle(lib.kernel_data()));
|
|
patch.set_library_kernel_offset(lib.kernel_offset());
|
|
}
|
|
|
|
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();
|
|
if (old_closure.IsCanonical()) {
|
|
new_closure.SetCanonical();
|
|
}
|
|
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 TypedefClassConflict : public ClassReasonForCancelling {
|
|
public:
|
|
TypedefClassConflict(Zone* zone, const Class& from, const Class& to)
|
|
: ClassReasonForCancelling(zone, from, to) {}
|
|
|
|
RawString* ToString() {
|
|
return String::NewFormatted(
|
|
from_.IsTypedefClass()
|
|
? "Typedef class cannot be redefined to be a non-typedef class: %s"
|
|
: "Class cannot be redefined to be a typedef 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());
|
|
}
|
|
};
|
|
|
|
// This is executed before iterating over the instances.
|
|
void Class::CheckReload(const Class& replacement,
|
|
IsolateReloadContext* context) const {
|
|
ASSERT(IsolateReloadContext::IsSameClass(*this, replacement));
|
|
|
|
if (!is_declaration_loaded()) {
|
|
// The old class hasn't been used in any meanfully way, so the VM is okay
|
|
// with any change.
|
|
return;
|
|
}
|
|
|
|
// Ensure is_enum_class etc have been set.
|
|
replacement.EnsureDeclarationLoaded();
|
|
|
|
// Class cannot change enum property.
|
|
if (is_enum_class() != replacement.is_enum_class()) {
|
|
context->group_reload_context()->AddReasonForCancelling(
|
|
new (context->zone())
|
|
EnumClassConflict(context->zone(), *this, replacement));
|
|
return;
|
|
}
|
|
|
|
// Class cannot change typedef property.
|
|
if (IsTypedefClass() != replacement.IsTypedefClass()) {
|
|
context->group_reload_context()->AddReasonForCancelling(
|
|
new (context->zone())
|
|
TypedefClassConflict(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->group_reload_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->group_reload_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;
|
|
}
|
|
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(true /* original classes */));
|
|
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 argument count matches for the two classes.
|
|
// ex. class A<int,B> {} cannot be replace with class A<B> {}.
|
|
auto group_context = context->group_reload_context();
|
|
if (NumTypeArguments() != replacement.NumTypeArguments()) {
|
|
group_context->AddReasonForCancelling(
|
|
new (context->zone())
|
|
TypeParametersChanged(context->zone(), *this, replacement));
|
|
return false;
|
|
}
|
|
if (RequiresInstanceMorphing(replacement)) {
|
|
ASSERT(id() == replacement.id());
|
|
const classid_t cid = id();
|
|
|
|
// We unconditionally create an instance morpher. As a side effect of
|
|
// building the morpher, we will mark all new fields as late.
|
|
auto instance_morpher = InstanceMorpher::CreateFromClassDescriptors(
|
|
context->zone(), context->isolate()->shared_class_table(), *this,
|
|
replacement);
|
|
group_context->EnsureHasInstanceMorpherFor(cid, instance_morpher);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Class::CanReloadPreFinalized(const Class& replacement,
|
|
IsolateReloadContext* context) const {
|
|
// The replacement class must also prefinalized.
|
|
if (!replacement.is_prefinalized()) {
|
|
context->group_reload_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->group_reload_context()->AddReasonForCancelling(
|
|
new (context->zone())
|
|
InstanceSizeConflict(context->zone(), *this, replacement));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Library::CheckReload(const Library& replacement,
|
|
IsolateReloadContext* context) const {
|
|
// Currently no library properties will prevent a reload.
|
|
}
|
|
|
|
void CallSiteResetter::Reset(const ICData& ic) {
|
|
ICData::RebindRule rule = ic.rebind_rule();
|
|
if (rule == ICData::kInstance) {
|
|
const intptr_t num_args = ic.NumArgsTested();
|
|
const bool tracking_exactness = ic.is_tracking_exactness();
|
|
const intptr_t len = ic.Length();
|
|
// We need at least one non-sentinel entry to require a check
|
|
// for the smi fast path case.
|
|
if (num_args == 2 && len >= 2) {
|
|
if (ic.IsImmutable()) {
|
|
return;
|
|
}
|
|
name_ = ic.target_name();
|
|
const Class& smi_class = Class::Handle(zone_, Smi::Class());
|
|
const Function& smi_op_target = Function::Handle(
|
|
zone_, Resolver::ResolveDynamicAnyArgs(zone_, smi_class, name_));
|
|
GrowableArray<intptr_t> class_ids(2);
|
|
Function& target = Function::Handle(zone_);
|
|
ic.GetCheckAt(0, &class_ids, &target);
|
|
if ((target.raw() == smi_op_target.raw()) && (class_ids[0] == kSmiCid) &&
|
|
(class_ids[1] == kSmiCid)) {
|
|
// The smi fast path case, preserve the initial entry but reset the
|
|
// count.
|
|
ic.ClearCountAt(0);
|
|
ic.WriteSentinelAt(1);
|
|
entries_ = ic.entries();
|
|
entries_.Truncate(2 * ic.TestEntryLength());
|
|
return;
|
|
}
|
|
// Fall back to the normal behavior with cached empty ICData arrays.
|
|
}
|
|
entries_ = ICData::CachedEmptyICDataArray(num_args, tracking_exactness);
|
|
ic.set_entries(entries_);
|
|
ic.set_is_megamorphic(false);
|
|
return;
|
|
} else if (rule == ICData::kNoRebind || rule == ICData::kNSMDispatch) {
|
|
// TODO(30877) we should account for addition/removal of NSM.
|
|
// Don't rebind dispatchers.
|
|
return;
|
|
} else if (rule == ICData::kStatic || rule == ICData::kSuper) {
|
|
old_target_ = ic.GetTargetAt(0);
|
|
if (old_target_.IsNull()) {
|
|
FATAL("old_target is NULL.\n");
|
|
}
|
|
name_ = old_target_.name();
|
|
|
|
if (rule == ICData::kStatic) {
|
|
ASSERT(old_target_.is_static() ||
|
|
old_target_.kind() == RawFunction::kConstructor);
|
|
// This can be incorrect if the call site was an unqualified invocation.
|
|
new_cls_ = old_target_.Owner();
|
|
new_target_ = new_cls_.LookupFunction(name_);
|
|
if (new_target_.kind() != old_target_.kind()) {
|
|
new_target_ = Function::null();
|
|
}
|
|
} else {
|
|
// Super call.
|
|
caller_ = ic.Owner();
|
|
ASSERT(!caller_.is_static());
|
|
new_cls_ = caller_.Owner();
|
|
new_cls_ = new_cls_.SuperClass();
|
|
new_target_ = Function::null();
|
|
while (!new_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_ = new_cls_.LookupDynamicFunction(name_);
|
|
if (!new_target_.IsNull()) {
|
|
break;
|
|
}
|
|
new_cls_ = new_cls_.SuperClass();
|
|
}
|
|
}
|
|
args_desc_array_ = ic.arguments_descriptor();
|
|
ArgumentsDescriptor args_desc(args_desc_array_);
|
|
if (new_target_.IsNull() ||
|
|
!new_target_.AreValidArguments(NNBDMode::kLegacy, 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_, ic.Owner()).ToCString());
|
|
return;
|
|
}
|
|
ic.ClearAndSetStaticTarget(new_target_);
|
|
} else {
|
|
FATAL("Unexpected rebind rule.");
|
|
}
|
|
}
|
|
|
|
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
} // namespace dart
|