dart-sdk/runtime/vm/object_reload.cc
Ryan Macnak 5cf55de409 [vm, reload] Delay enum forwarding until after instance morphing.
Enum forwarding requires evaluating and canonicalizing the new enum instances. If these in turn refer to other instances of classes with shape changes, canonicalization may try to compare instances of the old and new shapes and dereference past the end of an object. By waiting for instance morphing to complete, canonicalization can only see instances with the new shape.

Also, don't attempt to forward Enum.values. When an enum member is added or removed, this has the same merging problem as 40442.

Cf. bad074cc49.

TEST=ci
Bug: https://github.com/flutter/flutter/issues/129177
Bug: https://github.com/dart-lang/sdk/issues/53039
Change-Id: Ib7a54db30c4d16a6ae6e1acd595dc7482134165b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/316527
Commit-Queue: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Ben Konyi <bkonyi@google.com>
2023-07-26 21:14:09 +00:00

825 lines
29 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 "platform/unaligned.h"
#include "vm/code_patcher.h"
#include "vm/dart_entry.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(Function::ICDataArrayIndices::kEdgeCounters);
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.
// SubtypeTestCaches are reset during the same heap traversal as type
// testing stub deoptimization.
#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.untag()->data();
for (intptr_t i = 0; i < offsets_length; i++) {
int32_t offset = offsets[i];
ObjectPtr* object_ptr = reinterpret_cast<ObjectPtr*>(base_address + offset);
ObjectPtr raw_object = LoadUnaligned(object_ptr);
if (!raw_object->IsHeapObject()) {
continue;
}
object_ = raw_object;
if (object_.IsICData()) {
Reset(ICData::Cast(object_));
}
}
#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 = Function::ICDataArrayIndices::kFirstICData;
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;
}
}
FATAL("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() == UntaggedFunction::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_, UntaggedPcDescriptors::kIcCall);
while (iter.MoveNext()) {
FATAL("%s has IC calls but no ic_data_array\n",
function.ToFullyQualifiedCString());
}
#endif
return;
}
descriptors_ = code.pc_descriptors();
PcDescriptors::Iterator iter(descriptors_, UntaggedPcDescriptors::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::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_));
}
}
}
void Class::CopyStaticFieldValues(ProgramReloadContext* reload_context,
const Class& old_cls) const {
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();
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 (!field.is_const()) {
// Make new field point to the old field value so that both
// old and new code see and update same value.
reload_context->isolate_group()->FreeStaticField(field);
field.set_field_id_unsafe(old_field.field_id());
}
reload_context->AddStaticFieldMapping(old_field, field);
}
}
}
}
}
void Class::CopyCanonicalConstants(const Class& old_cls) const {
#if defined(DEBUG)
{
// Class has no canonical constants allocated.
const Array& my_constants = Array::Handle(constants());
ASSERT(my_constants.IsNull() || 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.ptr() == b.ptr();
}
static uword Hash(const Object& obj) {
ASSERT(obj.IsString());
return String::Cast(obj).Hash();
}
};
void Class::PatchFieldsAndFunctions() const {
// Move all old functions and fields to a patch class so that they
// still refer to their original script.
const auto& kernel_info = KernelProgramInfo::Handle(KernelProgramInfo());
const PatchClass& patch = PatchClass::Handle(
PatchClass::New(*this, kernel_info, Script::Handle(script())));
ASSERT(!patch.IsNull());
const Library& lib = Library::Handle(library());
patch.set_kernel_library_index(lib.kernel_library_index());
const Array& funcs = Array::Handle(current_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.ptr() == this->ptr());
func.set_owner(patch);
}
}
Thread* thread = Thread::Current();
SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
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.ptr() == this->ptr());
field.set_owner(patch);
}
field.ForceDynamicGuardedCidAndLength();
}
}
void Class::MigrateImplicitStaticClosures(ProgramReloadContext* irc,
const Class& new_cls) const {
const Array& funcs = Array::Handle(current_functions());
Thread* thread = Thread::Current();
Function& old_func = Function::Handle();
String& selector = String::Handle();
Function& new_func = Function::Handle();
Closure& old_closure = Closure::Handle();
Closure& new_closure = Closure::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 = Resolver::ResolveFunction(thread->zone(), new_cls, 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) {}
StringPtr 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_;
ErrorPtr ToError() { return error_.ptr(); }
StringPtr ToString() { return String::New(error_.ToErrorCString()); }
};
class ConstToNonConstClass : public ClassReasonForCancelling {
public:
ConstToNonConstClass(Zone* zone, const Class& from, const Class& to)
: ClassReasonForCancelling(zone, from, to) {}
private:
StringPtr ToString() {
return String::NewFormatted("Const class cannot become non-const: %s",
from_.ToCString());
}
};
class ConstClassFieldRemoved : public ClassReasonForCancelling {
public:
ConstClassFieldRemoved(Zone* zone, const Class& from, const Class& to)
: ClassReasonForCancelling(zone, from, to) {}
private:
StringPtr ToString() {
return String::NewFormatted("Const class cannot remove fields: %s",
from_.ToCString());
}
};
class NativeFieldsConflict : public ClassReasonForCancelling {
public:
NativeFieldsConflict(Zone* zone, const Class& from, const Class& to)
: ClassReasonForCancelling(zone, from, to) {}
private:
StringPtr 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) {}
StringPtr 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:
StringPtr 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:
StringPtr ToString() {
return String::NewFormatted("Instance size mismatch between '%s' (%" Pd
") and replacement "
"'%s' ( %" Pd ")",
from_.ToCString(), from_.host_instance_size(),
to_.ToCString(), to_.host_instance_size());
}
};
// This is executed before iterating over the instances.
void Class::CheckReload(const Class& replacement,
ProgramReloadContext* context) const {
ASSERT(ProgramReloadContext::IsSameClass(*this, replacement));
if (!is_declaration_loaded()) {
// The old class hasn't been used in any meaningful 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;
}
if (is_finalized()) {
// Make sure the declaration types parameter 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 (NumTypeParameters() != replacement.NumTypeParameters()) {
group_context->AddReasonForCancelling(
new (context->zone())
TypeParametersChanged(context->zone(), *this, replacement));
return;
}
}
if (is_finalized() || is_allocate_finalized()) {
auto thread = Thread::Current();
// Ensure the replacement class is also finalized.
const Error& error = Error::Handle(
is_allocate_finalized() ? replacement.EnsureIsAllocateFinalized(thread)
: replacement.EnsureIsFinalized(thread));
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());
}
if (is_finalized() && is_const() && (constants() != Array::null()) &&
(Array::LengthOf(constants()) > 0)) {
// Consts can't become non-consts.
if (!replacement.is_const()) {
context->group_reload_context()->AddReasonForCancelling(
new (context->zone())
ConstToNonConstClass(context->zone(), *this, replacement));
return;
}
// Consts can't lose fields.
bool field_removed = false;
const Array& old_fields = Array::Handle(
OffsetToFieldMap(IsolateGroup::Current()->heap_walk_class_table()));
const Array& new_fields = Array::Handle(replacement.OffsetToFieldMap());
if (new_fields.Length() < old_fields.Length()) {
field_removed = true;
} else {
Field& old_field = Field::Handle();
Field& new_field = Field::Handle();
String& old_name = String::Handle();
String& new_name = String::Handle();
for (intptr_t i = 0, n = old_fields.Length(); i < n; i++) {
old_field ^= old_fields.At(i);
new_field ^= new_fields.At(i);
if (old_field.IsNull()) {
continue;
}
if (new_field.IsNull()) {
field_removed = true;
break;
}
old_name = old_field.name();
new_name = new_field.name();
if (!old_name.Equals(new_name)) {
field_removed = true;
break;
}
}
}
if (field_removed) {
context->group_reload_context()->AddReasonForCancelling(
new (context->zone())
ConstClassFieldRemoved(context->zone(), *this, replacement));
return;
}
}
// 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());
}
void Class::MarkFieldBoxedDuringReload(ClassTable* class_table,
const Field& field) const {
if (!field.is_unboxed()) {
return;
}
field.set_is_unboxed_unsafe(false);
// Make sure to update the bitmap used for scanning.
auto unboxed_fields_map = class_table->GetUnboxedFieldsMapAt(id());
const auto start_index = field.HostOffset() >> kCompressedWordSizeLog2;
const auto end_index =
start_index + (Class::UnboxedFieldSizeInBytesByCid(field.guarded_cid()) >>
kCompressedWordSizeLog2);
ASSERT(unboxed_fields_map.Get(start_index));
for (intptr_t i = start_index; i < end_index; i++) {
unboxed_fields_map.Clear(i);
}
class_table->SetUnboxedFieldsMapAt(id(), unboxed_fields_map);
}
bool Class::RequiresInstanceMorphing(ClassTable* class_table,
const Class& replacement) const {
if (!is_allocate_finalized()) {
// No instances of this class exists on the heap - nothing to morph.
return false;
}
// Get the field maps for both classes. These field maps walk the class
// hierarchy.
auto isolate_group = IsolateGroup::Current();
// heap_walk_class_table is the original class table before it was
// updated by reloading sources.
const Array& fields =
Array::Handle(OffsetToFieldMap(isolate_group->heap_walk_class_table()));
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 (host_next_field_offset() != replacement.host_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;
if (field.is_unboxed() && !replacement_field.is_unboxed()) {
return true;
}
if (field.is_unboxed() && (field.type() != replacement_field.type())) {
return true;
}
if (!field.is_unboxed() && replacement_field.is_unboxed()) {
// No actual morphing is required in this case but we need to mark
// the field boxed.
replacement.MarkFieldBoxedDuringReload(class_table, replacement_field);
}
if (field.needs_load_guard()) {
ASSERT(!field.is_unboxed());
ASSERT(!replacement_field.is_unboxed());
replacement_field.set_needs_load_guard(true);
}
}
return false;
}
bool Class::CanReloadFinalized(const Class& replacement,
ProgramReloadContext* 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();
auto class_table = group_context->isolate_group()->class_table();
if (NumTypeArguments() != replacement.NumTypeArguments()) {
group_context->AddReasonForCancelling(
new (context->zone())
TypeParametersChanged(context->zone(), *this, replacement));
return false;
}
if (RequiresInstanceMorphing(class_table, 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 guarded on load.
auto instance_morpher = InstanceMorpher::CreateFromClassDescriptors(
context->zone(), class_table, *this, replacement);
group_context->EnsureHasInstanceMorpherFor(cid, instance_morpher);
}
return true;
}
bool Class::CanReloadPreFinalized(const Class& replacement,
ProgramReloadContext* 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 (host_instance_size() != replacement.host_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,
ProgramReloadContext* context) const {
// Carry over the loaded bit of any deferred prefixes.
Object& object = Object::Handle();
LibraryPrefix& prefix = LibraryPrefix::Handle();
LibraryPrefix& original_prefix = LibraryPrefix::Handle();
String& name = String::Handle();
String& original_name = String::Handle();
DictionaryIterator it(replacement);
while (it.HasNext()) {
object = it.GetNext();
if (!object.IsLibraryPrefix()) continue;
prefix ^= object.ptr();
if (!prefix.is_deferred_load()) continue;
name = prefix.name();
DictionaryIterator original_it(*this);
while (original_it.HasNext()) {
object = original_it.GetNext();
if (!object.IsLibraryPrefix()) continue;
original_prefix ^= object.ptr();
if (!original_prefix.is_deferred_load()) continue;
original_name = original_prefix.name();
if (!name.Equals(original_name)) continue;
// The replacement of the old prefix with the new prefix
// in Isolate::loaded_prefixes_set_ implicitly carried
// the loaded state over to the new prefix.
context->AddBecomeMapping(original_prefix, prefix);
}
}
}
void CallSiteResetter::Reset(const ICData& ic) {
ICData::RebindRule rule = ic.rebind_rule();
if (rule == ICData::kInstance) {
const intptr_t num_args = ic.NumArgsTested();
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.ptr() == smi_op_target.ptr()) && (class_ids[0] == kSmiCid) &&
(class_ids[1] == kSmiCid)) {
// The smi fast path case, preserve the initial entry but reset the
// count.
ic.ClearCountAt(0, *this);
ic.TruncateTo(/*num_checks=*/1, *this);
return;
}
// Fall back to the normal behavior with cached empty ICData arrays.
}
ic.Clear(*this);
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 nullptr.\n");
}
name_ = old_target_.name();
if (rule == ICData::kStatic) {
ASSERT(old_target_.is_static() ||
old_target_.kind() == UntaggedFunction::kConstructor);
// This can be incorrect if the call site was an unqualified invocation.
new_cls_ = old_target_.Owner();
new_target_ = Resolver::ResolveFunction(zone_, new_cls_, 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_ = Resolver::ResolveDynamicAnyArgs(zone_, new_cls_, name_,
/*allow_add=*/true);
}
args_desc_array_ = ic.arguments_descriptor();
ArgumentsDescriptor args_desc(args_desc_array_);
if (new_target_.IsNull() ||
!new_target_.AreValidArguments(args_desc, nullptr)) {
// 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_, *this);
} else {
FATAL("Unexpected rebind rule.");
}
}
#endif // !defined(PRODUCT) && !defined(DART_PRECOMPILED_RUNTIME)
} // namespace dart