[vm,dyn_modules] Support error cases during hot reload when rebinding direct calls in bytecode

TEST=vm/cc/IsolateReload

Change-Id: Ib989d2d951a342e4725b99b1651e8ea298ba2f64
Cq-Include-Trybots: luci.dart.try:vm-aot-dyn-linux-debug-x64-try,vm-aot-dyn-linux-product-x64-try,vm-dyn-linux-debug-x64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/441826
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
Alexander Markov
2025-07-28 13:32:36 -07:00
committed by Commit Queue
parent 21ce423b26
commit 6082256159
6 changed files with 1297 additions and 1157 deletions
File diff suppressed because it is too large Load Diff
+30 -2
View File
@@ -2030,7 +2030,15 @@ SwitchDispatch:
const uint32_t kidx = rD;
InterpreterHelpers::IncrementUsageCounter(FrameFunction(FP));
*++SP = LOAD_CONSTANT(kidx);
ObjectPtr target = LOAD_CONSTANT(kidx);
*++SP = target;
#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(PRODUCT)
if (target->IsArray()) {
// Hot reload failed to find a suitable target for this call.
goto ThrowNoSuchMethodError;
}
#endif
ASSERT(target->IsFunction());
ObjectPtr* call_base = SP - argc;
ObjectPtr* call_top = SP;
argdesc_ = static_cast<ArrayPtr>(LOAD_CONSTANT(kidx + 1));
@@ -2051,7 +2059,15 @@ SwitchDispatch:
const uint32_t kidx = rD;
InterpreterHelpers::IncrementUsageCounter(FrameFunction(FP));
*++SP = LOAD_CONSTANT(kidx);
ObjectPtr target = LOAD_CONSTANT(kidx);
*++SP = target;
#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(PRODUCT)
if (target->IsArray()) {
// Hot reload failed to find a suitable target for this call.
goto ThrowNoSuchMethodError;
}
#endif
ASSERT(target->IsFunction());
ObjectPtr* call_base = SP - argc;
ObjectPtr* call_top = SP;
argdesc_ = static_cast<ArrayPtr>(LOAD_CONSTANT(kidx + 1));
@@ -4015,6 +4031,18 @@ SwitchDispatch:
UNREACHABLE();
}
#if !defined(DART_PRECOMPILED_RUNTIME) && !defined(PRODUCT)
{
ThrowNoSuchMethodError:
// SP[0] contains arguments.
SP[1] = 0; // Unused space for result.
Exit(thread, FP, SP + 2, pc);
INVOKE_RUNTIME(DRT_NoSuchMethodError,
NativeArguments(thread, 1, SP, SP + 1));
UNREACHABLE();
}
#endif // !defined(DART_PRECOMPILED_RUNTIME) && !defined(PRODUCT)
// Exception handling helper. Gets handler FP and PC from the Interpreter
// where they were stored by Interpreter::Longjmp and proceeds to execute the
// handler. Corner case: handler PC can be a fake marker that marks entry
+3
View File
@@ -420,12 +420,15 @@ class CallSiteResetter : public ValueObject {
void RebindBytecode(const Bytecode& bytecode);
private:
Thread* thread_;
Zone* zone_;
Instructions& instrs_;
ObjectPool& pool_;
Object& object_;
String& name_;
Class& old_cls_;
Class& new_cls_;
Library& old_lib_;
Library& new_lib_;
Function& new_function_;
Field& new_field_;
+100 -11
View File
@@ -4,6 +4,7 @@
#include "vm/object.h"
#include "lib/invocation_mirror.h"
#include "platform/unaligned.h"
#include "vm/code_patcher.h"
#include "vm/dart_entry.h"
@@ -41,12 +42,15 @@ void CallSiteResetter::ZeroEdgeCounters(const Function& function) {
}
CallSiteResetter::CallSiteResetter(Zone* zone)
: zone_(zone),
: thread_(Thread::Current()),
zone_(zone),
instrs_(Instructions::Handle(zone)),
pool_(ObjectPool::Handle(zone)),
object_(Object::Handle(zone)),
name_(String::Handle(zone)),
old_cls_(Class::Handle(zone)),
new_cls_(Class::Handle(zone)),
old_lib_(Library::Handle(zone)),
new_lib_(Library::Handle(zone)),
new_function_(Function::Handle(zone)),
new_field_(Field::Handle(zone)),
@@ -840,6 +844,55 @@ void CallSiteResetter::Reset(const ICData& ic) {
}
}
#if defined(DART_DYNAMIC_MODULES)
static ArrayPtr PrepareNoSuchMethodErrorArguments(const Function& target,
bool incompatible_arguments) {
InvocationMirror::Kind kind = InvocationMirror::Kind::kMethod;
if (target.IsImplicitGetterFunction() || target.IsGetterFunction()) {
kind = InvocationMirror::kGetter;
} else if (target.IsImplicitSetterFunction() || target.IsSetterFunction()) {
kind = InvocationMirror::kSetter;
}
const Class& owner = Class::Handle(target.Owner());
auto& receiver = Instance::Handle();
InvocationMirror::Level level;
if (owner.IsTopLevel()) {
if (incompatible_arguments) {
receiver = target.UserVisibleSignature();
}
level = InvocationMirror::Level::kTopLevel;
} else {
receiver = owner.RareType();
if (target.IsConstructor()) {
level = InvocationMirror::Level::kConstructor;
} else {
level = InvocationMirror::Level::kStatic;
}
}
const auto& member_name = String::Handle(target.name());
const auto& invocation_type =
Smi::Handle(Smi::New(InvocationMirror::EncodeType(level, kind)));
// NoSuchMethodError._throwNew takes the following arguments:
// Object receiver,
// String memberName,
// int invocationType,
// int typeArgumentsLength,
// Object? typeArguments,
// List? arguments,
// List? argumentNames
const Array& args = Array::Handle(Array::New(7));
args.SetAt(0, receiver);
args.SetAt(1, member_name);
args.SetAt(2, invocation_type);
args.SetAt(3, Object::smi_zero());
args.SetAt(4, Object::null_type_arguments());
args.SetAt(5, Object::null_object());
args.SetAt(6, Object::null_object());
return args.ptr();
}
#endif // defined(DART_DYNAMIC_MODULES)
void CallSiteResetter::RebindBytecode(const Bytecode& bytecode) {
#if defined(DART_DYNAMIC_MODULES)
pool_ = bytecode.object_pool();
@@ -858,22 +911,58 @@ void CallSiteResetter::RebindBytecode(const Bytecode& bytecode) {
case KernelBytecode::kUncheckedDirectCall:
case KernelBytecode::kUncheckedDirectCall_Wide: {
const intptr_t idx = KernelBytecode::DecodeD(instr);
old_target_ ^= pool_.ObjectAt(idx);
object_ = pool_.ObjectAt(idx);
if (object_.IsArray()) {
break;
}
old_target_ ^= object_.ptr();
args_desc_array_ ^= pool_.ObjectAt(idx + 1);
ArgumentsDescriptor args_desc(args_desc_array_);
name_ = old_target_.name();
new_cls_ = old_target_.Owner();
new_target_ = Resolver::ResolveFunction(zone_, new_cls_, name_);
if (new_target_.ptr() != old_target_.ptr()) {
if (!new_target_.IsNull() &&
(new_target_.is_static() == old_target_.is_static()) &&
(new_target_.kind() == old_target_.kind()) &&
new_target_.AreValidArguments(args_desc, nullptr)) {
pool_.SetObjectAt(idx, new_target_);
// Re-resolve class in case it was deleted.
old_cls_ = old_target_.Owner();
old_lib_ = old_cls_.library();
name_ = old_lib_.url();
new_lib_ = Library::LookupLibrary(thread_, name_);
if (!new_lib_.IsNull()) {
if (old_cls_.IsTopLevel()) {
new_cls_ = new_lib_.toplevel_class();
} else {
name_ = old_cls_.Name();
new_cls_ = new_lib_.LookupClassAllowPrivate(name_);
}
} else {
new_cls_ = Class::null();
}
if (!new_cls_.IsNull()) {
name_ = old_target_.name();
new_target_ = Resolver::ResolveFunction(zone_, new_cls_, name_);
if (new_target_.IsNull() && Field::IsGetterName(name_)) {
name_ = Field::NameFromGetter(name_);
new_target_ = Resolver::ResolveFunction(zone_, new_cls_, name_);
if (!new_target_.IsNull()) {
name_ = old_target_.name();
new_target_ = new_target_.GetMethodExtractor(name_);
}
}
} else {
new_target_ = Function::null();
}
if (new_target_.ptr() != old_target_.ptr()) {
if (new_target_.IsNull() ||
(new_target_.is_static() != old_target_.is_static())) {
VTIR_Print("Cannot rebind function %s\n",
old_target_.ToFullyQualifiedCString());
object_ = PrepareNoSuchMethodErrorArguments(
old_target_, /*incompatible_arguments=*/false);
} else if (!new_target_.AreValidArguments(args_desc, nullptr)) {
VTIR_Print("Cannot rebind function %s - arguments mismatch\n",
old_target_.ToFullyQualifiedCString());
object_ = PrepareNoSuchMethodErrorArguments(
old_target_, /*incompatible_arguments=*/true);
} else {
object_ = new_target_.ptr();
}
pool_.SetObjectAt(idx, object_);
}
break;
}
+19
View File
@@ -3345,6 +3345,25 @@ DEFINE_RUNTIME_ENTRY(NoSuchMethodFromPrologue, 4) {
arguments.SetReturn(result);
}
// Throw NoSuchMethodError with given arguments.
// Arg0: arguments of NoSuchMethodError._throwNew.
DEFINE_RUNTIME_ENTRY(NoSuchMethodError, 1) {
const Array& args = Array::CheckedHandle(zone, arguments.ArgAt(0));
const Library& libcore = Library::Handle(Library::CoreLibrary());
const Class& cls =
Class::Handle(libcore.LookupClass(Symbols::NoSuchMethodError()));
ASSERT(!cls.IsNull());
const auto& error = cls.EnsureIsFinalized(Thread::Current());
ASSERT(error == Error::null());
const Function& throwNew =
Function::Handle(cls.LookupFunctionAllowPrivate(Symbols::ThrowNew()));
ASSERT(args.Length() == throwNew.NumParameters());
const Object& result =
Object::Handle(zone, DartEntry::InvokeFunction(throwNew, args));
ThrowIfError(result);
arguments.SetReturn(result);
}
// Invoke appropriate noSuchMethod function (or in the case of no lazy
// dispatchers, walk the receiver to find the correct method to call).
// Arg0: receiver
+1
View File
@@ -43,6 +43,7 @@ namespace dart {
V(InstantiateTypeArguments) \
V(NoSuchMethodFromCallStub) \
V(NoSuchMethodFromPrologue) \
V(NoSuchMethodError) \
V(OptimizeInvokedFunction) \
V(PatchStaticCall) \
V(RangeError) \