[vm] Remove context allocations for tear-offs
Previously, when taking a tear-off, a separate Context object was allocated in order to capture receiver. Now, receiver is stored directly in the Closure object in the 'context' field. This saves 1 object allocation per tear-off and makes tear-offs cheaper compared to explicit closures which can share context with other closures. Benchmarks in AOT mode: x64: TearOff.NotInlined +40% TearOff.NotInlined.InTry +43% TearOff.Inlined.InTry +47% arm64: TearOff.NotInlined +27-43% TearOff.NotInlined.InTry +29-43% TearOff.Inlined.InTry +58-94% arm64c: TearOff.NotInlined +71% TearOff.NotInlined.InTry +72% TearOff.Inlined.InTry +96% TEST=ci Issue: https://github.com/dart-lang/sdk/issues/54808 Change-Id: I3ad95e8a8a4fc23f856bbc0fe238da58a9d25b8d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/350945 Reviewed-by: Martin Kustermann <kustermann@google.com> Commit-Queue: Alexander Markov <alexmarkov@google.com>
This commit is contained in:
committed by
Commit Queue
parent
6d57fe5a1c
commit
a261196ea7
@@ -4726,6 +4726,13 @@ class InstanceRef extends ObjRef {
|
||||
@optional
|
||||
ContextRef? closureContext;
|
||||
|
||||
/// The receiver captured by tear-off Closure instance.
|
||||
///
|
||||
/// Provided for instance kinds:
|
||||
/// - Closure
|
||||
@optional
|
||||
InstanceRef? closureReceiver;
|
||||
|
||||
/// The port ID for a ReceivePort.
|
||||
///
|
||||
/// Provided for instance kinds:
|
||||
@@ -4771,6 +4778,7 @@ class InstanceRef extends ObjRef {
|
||||
this.pattern,
|
||||
this.closureFunction,
|
||||
this.closureContext,
|
||||
this.closureReceiver,
|
||||
this.portId,
|
||||
this.allocationLocation,
|
||||
this.debugName,
|
||||
@@ -4813,6 +4821,9 @@ class InstanceRef extends ObjRef {
|
||||
closureContext =
|
||||
createServiceObject(json['closureContext'], const ['ContextRef'])
|
||||
as ContextRef?;
|
||||
closureReceiver =
|
||||
createServiceObject(json['closureReceiver'], const ['InstanceRef'])
|
||||
as InstanceRef?;
|
||||
portId = json['portId'];
|
||||
allocationLocation =
|
||||
createServiceObject(json['allocationLocation'], const ['InstanceRef'])
|
||||
@@ -4847,6 +4858,7 @@ class InstanceRef extends ObjRef {
|
||||
_setIfNotNull(json, 'pattern', pattern?.toJson());
|
||||
_setIfNotNull(json, 'closureFunction', closureFunction?.toJson());
|
||||
_setIfNotNull(json, 'closureContext', closureContext?.toJson());
|
||||
_setIfNotNull(json, 'closureReceiver', closureReceiver?.toJson());
|
||||
_setIfNotNull(json, 'portId', portId);
|
||||
_setIfNotNull(json, 'allocationLocation', allocationLocation?.toJson());
|
||||
_setIfNotNull(json, 'debugName', debugName);
|
||||
@@ -5109,6 +5121,14 @@ class Instance extends Obj implements InstanceRef {
|
||||
@override
|
||||
ContextRef? closureContext;
|
||||
|
||||
/// The receiver captured by tear-off Closure instance.
|
||||
///
|
||||
/// Provided for instance kinds:
|
||||
/// - Closure
|
||||
@optional
|
||||
@override
|
||||
InstanceRef? closureReceiver;
|
||||
|
||||
/// Whether this regular expression is case sensitive.
|
||||
///
|
||||
/// Provided for instance kinds:
|
||||
@@ -5280,6 +5300,7 @@ class Instance extends Obj implements InstanceRef {
|
||||
this.pattern,
|
||||
this.closureFunction,
|
||||
this.closureContext,
|
||||
this.closureReceiver,
|
||||
this.isCaseSensitive,
|
||||
this.isMultiLine,
|
||||
this.propertyKey,
|
||||
@@ -5356,6 +5377,9 @@ class Instance extends Obj implements InstanceRef {
|
||||
closureContext =
|
||||
createServiceObject(json['closureContext'], const ['ContextRef'])
|
||||
as ContextRef?;
|
||||
closureReceiver =
|
||||
createServiceObject(json['closureReceiver'], const ['InstanceRef'])
|
||||
as InstanceRef?;
|
||||
isCaseSensitive = json['isCaseSensitive'];
|
||||
isMultiLine = json['isMultiLine'];
|
||||
propertyKey =
|
||||
@@ -5426,6 +5450,7 @@ class Instance extends Obj implements InstanceRef {
|
||||
_setIfNotNull(json, 'pattern', pattern?.toJson());
|
||||
_setIfNotNull(json, 'closureFunction', closureFunction?.toJson());
|
||||
_setIfNotNull(json, 'closureContext', closureContext?.toJson());
|
||||
_setIfNotNull(json, 'closureReceiver', closureReceiver?.toJson());
|
||||
_setIfNotNull(json, 'isCaseSensitive', isCaseSensitive);
|
||||
_setIfNotNull(json, 'isMultiLine', isMultiLine);
|
||||
_setIfNotNull(json, 'propertyKey', propertyKey?.toJson());
|
||||
|
||||
+31
-25
@@ -72,35 +72,41 @@ static bool ClosureEqualsHelper(Zone* zone,
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (func_a.IsImplicitClosureFunction() &&
|
||||
func_b.IsImplicitClosureFunction()) {
|
||||
if (func_a.IsImplicitClosureFunction()) {
|
||||
ASSERT(func_b.IsImplicitClosureFunction());
|
||||
if (!func_a.is_static()) {
|
||||
ASSERT(!func_b.is_static());
|
||||
// Check that the both receiver instances are the same.
|
||||
const Context& context_a = Context::Handle(zone, receiver.context());
|
||||
const Context& context_b = Context::Handle(zone, other_closure.context());
|
||||
return context_a.At(0) == context_b.At(0);
|
||||
}
|
||||
} else if (func_a.IsGeneric()) {
|
||||
// Additional constraints for closures of generic functions:
|
||||
// (1) Different instantiations of the same generic closure
|
||||
// with the same type arguments should be equal.
|
||||
// This means that instantiated generic closures are not unique
|
||||
// and equality of instantiated generic closures should not be
|
||||
// based on identity.
|
||||
// (2) Instantiations of non-equal generic closures should be non-equal.
|
||||
// This means that equality of non-instantiated generic closures
|
||||
// should not be based on identity too as it won't match equality
|
||||
// after instantiation.
|
||||
if ((receiver.context() != other_closure.context()) ||
|
||||
(receiver.instantiator_type_arguments() !=
|
||||
other_closure.instantiator_type_arguments()) ||
|
||||
(receiver.function_type_arguments() !=
|
||||
other_closure.function_type_arguments())) {
|
||||
return false;
|
||||
const Instance& receiver_a =
|
||||
Instance::Handle(zone, receiver.GetImplicitClosureReceiver());
|
||||
const Instance& receiver_b =
|
||||
Instance::Handle(zone, other_closure.GetImplicitClosureReceiver());
|
||||
return receiver_a.ptr() == receiver_b.ptr();
|
||||
}
|
||||
} else {
|
||||
// Closures of non-generic functions are unique.
|
||||
return false;
|
||||
ASSERT(!func_b.IsImplicitClosureFunction());
|
||||
if (func_a.IsGeneric()) {
|
||||
// Additional constraints for closures of generic functions:
|
||||
// (1) Different instantiations of the same generic closure
|
||||
// with the same type arguments should be equal.
|
||||
// This means that instantiated generic closures are not unique
|
||||
// and equality of instantiated generic closures should not be
|
||||
// based on identity.
|
||||
// (2) Instantiations of non-equal generic closures should be non-equal.
|
||||
// This means that equality of non-instantiated generic closures
|
||||
// should not be based on identity too as it won't match equality
|
||||
// after instantiation.
|
||||
if ((receiver.GetContext() != other_closure.GetContext()) ||
|
||||
(receiver.instantiator_type_arguments() !=
|
||||
other_closure.instantiator_type_arguments()) ||
|
||||
(receiver.function_type_arguments() !=
|
||||
other_closure.function_type_arguments())) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Closures of non-generic functions are unique.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -238,7 +238,7 @@ static ObjectPtr ValidateMessageObject(Zone* zone,
|
||||
case kClosureCid:
|
||||
closure ^= raw;
|
||||
// Only context has to be checked.
|
||||
working_set->Add(closure.context());
|
||||
working_set->Add(closure.RawContext());
|
||||
continue;
|
||||
|
||||
#define MESSAGE_SNAPSHOT_ILLEGAL(type) \
|
||||
|
||||
@@ -12,7 +12,7 @@ var tests = <VMTest>[
|
||||
final result = await vm.invokeRpcNoUpgrade('getVersion', {});
|
||||
expect(result['type'], 'Version');
|
||||
expect(result['major'], 4);
|
||||
expect(result['minor'], 14);
|
||||
expect(result['minor'], 15);
|
||||
expect(result['_privateMajor'], 0);
|
||||
expect(result['_privateMinor'], 0);
|
||||
},
|
||||
|
||||
@@ -1581,7 +1581,7 @@ void FlowGraphSerializer::WriteObjectImpl(const Object& x,
|
||||
break;
|
||||
case kClosureCid: {
|
||||
const auto& closure = Closure::Cast(x);
|
||||
if (closure.context() != Object::null()) {
|
||||
if (closure.RawContext() != Object::null()) {
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
ASSERT(closure.IsCanonical());
|
||||
@@ -1870,9 +1870,9 @@ const Object& FlowGraphDeserializer::ReadObjectImpl(intptr_t cid,
|
||||
const auto& delayed_type_arguments = Read<const TypeArguments&>();
|
||||
const auto& function = Read<const Function&>();
|
||||
auto& closure = Closure::ZoneHandle(
|
||||
Z,
|
||||
Closure::New(instantiator_type_arguments, function_type_arguments,
|
||||
delayed_type_arguments, function, Context::Handle(Z)));
|
||||
Z, Closure::New(instantiator_type_arguments, function_type_arguments,
|
||||
delayed_type_arguments, function,
|
||||
Object::null_object()));
|
||||
closure ^= closure.Canonicalize(thread());
|
||||
return closure;
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ class ParsedFunction;
|
||||
#define NONNULLABLE_BOXED_NATIVE_SLOTS_LIST(V) \
|
||||
V(Array, UntaggedArray, length, Smi, FINAL) \
|
||||
V(Closure, UntaggedClosure, function, Function, FINAL) \
|
||||
V(Closure, UntaggedClosure, context, Context, FINAL) \
|
||||
V(Closure, UntaggedClosure, context, Dynamic, FINAL) \
|
||||
V(Closure, UntaggedClosure, hash, Context, VAR) \
|
||||
V(Finalizer, UntaggedFinalizer, callback, Closure, FINAL) \
|
||||
V(NativeFinalizer, UntaggedFinalizer, callback, Pointer, FINAL) \
|
||||
|
||||
@@ -37,42 +37,6 @@ T* PutIfAbsent(Thread* thread,
|
||||
return array->At(index);
|
||||
}
|
||||
|
||||
LocalVariable* CompilerState::GetDummyCapturedVariable(intptr_t context_id,
|
||||
intptr_t index) {
|
||||
return PutIfAbsent<LocalVariable>(
|
||||
thread(), &dummy_captured_vars_, index, [&]() {
|
||||
Zone* const Z = thread()->zone();
|
||||
const AbstractType& dynamic_type =
|
||||
AbstractType::ZoneHandle(Z, Type::DynamicType());
|
||||
const String& name = String::ZoneHandle(
|
||||
Z, Symbols::NewFormatted(thread(), ":context_var%" Pd, index));
|
||||
LocalVariable* var =
|
||||
new (Z) LocalVariable(TokenPosition::kNoSource,
|
||||
TokenPosition::kNoSource, name, dynamic_type);
|
||||
var->set_is_captured();
|
||||
var->set_index(VariableIndex(index));
|
||||
return var;
|
||||
});
|
||||
}
|
||||
|
||||
const ZoneGrowableArray<const Slot*>& CompilerState::GetDummyContextSlots(
|
||||
intptr_t context_id,
|
||||
intptr_t num_context_variables) {
|
||||
return *PutIfAbsent<ZoneGrowableArray<const Slot*>>(
|
||||
thread(), &dummy_slots_, num_context_variables, [&]() {
|
||||
Zone* const Z = thread()->zone();
|
||||
|
||||
auto slots =
|
||||
new (Z) ZoneGrowableArray<const Slot*>(num_context_variables);
|
||||
for (intptr_t i = 0; i < num_context_variables; i++) {
|
||||
LocalVariable* var = GetDummyCapturedVariable(context_id, i);
|
||||
slots->Add(&Slot::GetContextVariableSlotFor(thread(), *var));
|
||||
}
|
||||
|
||||
return slots;
|
||||
});
|
||||
}
|
||||
|
||||
CompilerTracing CompilerState::ShouldTrace(const Function& func) {
|
||||
return FlowGraphPrinter::ShouldPrint(func) ? CompilerTracing::kOn
|
||||
: CompilerTracing::kOff;
|
||||
|
||||
@@ -72,19 +72,6 @@ class CompilerState : public ThreadStackResource {
|
||||
SlotCache* slot_cache() const { return slot_cache_; }
|
||||
void set_slot_cache(SlotCache* cache) { slot_cache_ = cache; }
|
||||
|
||||
// Create a dummy list of local variables representing a context object
|
||||
// with the given number of captured variables and given ID.
|
||||
const ZoneGrowableArray<const Slot*>& GetDummyContextSlots(
|
||||
intptr_t context_id,
|
||||
intptr_t num_context_slots);
|
||||
|
||||
// Create a dummy LocalVariable that represents a captured local variable
|
||||
// at the given index in the context with given ID.
|
||||
//
|
||||
// This function returns the same variable when it is called with the
|
||||
// same index.
|
||||
LocalVariable* GetDummyCapturedVariable(intptr_t context_id, intptr_t index);
|
||||
|
||||
bool is_aot() const { return is_aot_; }
|
||||
|
||||
bool is_optimizing() const { return is_optimizing_; }
|
||||
|
||||
@@ -566,7 +566,7 @@ InstancePtr ConstantReader::ReadConstantInternal(intptr_t constant_index) {
|
||||
// closures. Though inner closures cannot be constants. We should
|
||||
// therefore see `null here.
|
||||
ASSERT(closure.function_type_arguments() == TypeArguments::null());
|
||||
Context& context = Context::Handle(Z, closure.context());
|
||||
Object& context = Object::Handle(Z, closure.RawContext());
|
||||
instance = Closure::New(type_arguments2, Object::null_type_arguments(),
|
||||
type_arguments, function, context, Heap::kOld);
|
||||
break;
|
||||
|
||||
@@ -2564,7 +2564,7 @@ Fragment StreamingFlowGraphBuilder::BuildSuperPropertyGet(TokenPosition* p) {
|
||||
Function& target =
|
||||
Function::ZoneHandle(Z, function.ImplicitClosureFunction());
|
||||
ASSERT(!target.IsNull());
|
||||
// Generate inline code for allocation closure object with context
|
||||
// Generate inline code for allocation closure object
|
||||
// which captures `this`.
|
||||
return BuildImplicitClosureCreation(target);
|
||||
}
|
||||
|
||||
@@ -2153,49 +2153,15 @@ Fragment FlowGraphBuilder::BuildTypedDataFactoryConstructor(
|
||||
return instructions;
|
||||
}
|
||||
|
||||
static const LocalScope* MakeImplicitClosureScope(Zone* Z, const Class& klass) {
|
||||
ASSERT(!klass.IsNull());
|
||||
// Note that if klass is _Closure, DeclarationType will be _Closure,
|
||||
// and not the signature type.
|
||||
Type& klass_type = Type::ZoneHandle(Z, klass.DeclarationType());
|
||||
|
||||
LocalVariable* receiver_variable =
|
||||
new (Z) LocalVariable(TokenPosition::kNoSource, TokenPosition::kNoSource,
|
||||
Symbols::This(), klass_type);
|
||||
|
||||
receiver_variable->set_is_captured();
|
||||
// receiver_variable->set_is_final();
|
||||
LocalScope* scope = new (Z) LocalScope(nullptr, 0, 0);
|
||||
scope->set_context_level(0);
|
||||
scope->AddVariable(receiver_variable);
|
||||
scope->AddContextVariable(receiver_variable);
|
||||
return scope;
|
||||
}
|
||||
|
||||
Fragment FlowGraphBuilder::BuildImplicitClosureCreation(
|
||||
const Function& target) {
|
||||
// The function cannot be local and have parent generic functions.
|
||||
ASSERT(!target.HasGenericParent());
|
||||
ASSERT(target.IsImplicitInstanceClosureFunction());
|
||||
|
||||
Fragment fragment;
|
||||
fragment += Constant(target);
|
||||
|
||||
// Allocate a context that closes over `this`.
|
||||
// Note: this must be kept in sync with ScopeBuilder::BuildScopes.
|
||||
const LocalScope* implicit_closure_scope =
|
||||
MakeImplicitClosureScope(Z, Class::Handle(Z, target.Owner()));
|
||||
fragment += AllocateContext(implicit_closure_scope->context_slots());
|
||||
LocalVariable* context = MakeTemporary();
|
||||
|
||||
// Store `this`. The context doesn't need a parent pointer because it doesn't
|
||||
// close over anything else.
|
||||
fragment += LoadLocal(context);
|
||||
fragment += LoadLocal(parsed_function_->receiver_var());
|
||||
fragment += StoreNativeField(
|
||||
Slot::GetContextVariableSlotFor(
|
||||
thread_, *implicit_closure_scope->context_variables()[0]),
|
||||
StoreFieldInstr::Kind::kInitializing);
|
||||
|
||||
fragment += AllocateClosure();
|
||||
LocalVariable* closure = MakeTemporary();
|
||||
|
||||
@@ -3757,8 +3723,6 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfNoSuchMethodForwarder(
|
||||
body += IntConstant(function.NumParameters());
|
||||
}
|
||||
body += LoadLocal(parsed_function_->current_context_var());
|
||||
body += LoadNativeField(Slot::GetContextVariableSlotFor(
|
||||
thread_, *parsed_function_->receiver_var()));
|
||||
body += StoreFpRelativeSlot(
|
||||
kWordSize * compiler::target::frame_layout.param_end_from_fp);
|
||||
}
|
||||
@@ -3896,8 +3860,6 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfNoSuchMethodForwarder(
|
||||
body += Constant(type);
|
||||
} else {
|
||||
body += LoadLocal(parsed_function_->current_context_var());
|
||||
body += LoadNativeField(Slot::GetContextVariableSlotFor(
|
||||
thread_, *parsed_function_->receiver_var()));
|
||||
}
|
||||
} else {
|
||||
body += LoadLocal(parsed_function_->ParameterVariable(0));
|
||||
@@ -4222,12 +4184,9 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfImplicitClosureFunction(
|
||||
LocalVariable* receiver = MakeTemporary();
|
||||
closure += LoadLocal(receiver);
|
||||
} else if (!target.is_static()) {
|
||||
// The context has a fixed shape: a single variable which is the
|
||||
// closed-over receiver.
|
||||
// The closure context is the receiver.
|
||||
closure += LoadLocal(parsed_function_->ParameterVariable(0));
|
||||
closure += LoadNativeField(Slot::Closure_context());
|
||||
closure += LoadNativeField(Slot::GetContextVariableSlotFor(
|
||||
thread_, *parsed_function_->receiver_var()));
|
||||
}
|
||||
|
||||
closure += PushExplicitParameters(function);
|
||||
|
||||
@@ -397,9 +397,8 @@ ScopeBuildingResult* ScopeBuilder::BuildScopes() {
|
||||
break;
|
||||
}
|
||||
case UntaggedFunction::kMethodExtractor: {
|
||||
// Add a receiver parameter. Though it is captured, we emit code to
|
||||
// explicitly copy it to a fixed offset in a freshly-allocated context
|
||||
// instead of using the generic code for regular functions.
|
||||
// Add a receiver parameter. Though it is captured, we emit code to
|
||||
// explicitly copy it to a freshly-allocated closure.
|
||||
// Therefore, it isn't necessary to mark it as captured here.
|
||||
Class& klass = Class::Handle(Z, function.Owner());
|
||||
Type& klass_type = H.GetDeclarationType(klass);
|
||||
|
||||
@@ -1182,6 +1182,7 @@ VM_TYPE_TESTING_STUB_CODE_LIST(GENERATE_BREAKPOINT_STUB)
|
||||
// Called for inline allocation of closure.
|
||||
// Input (preserved):
|
||||
// AllocateClosureABI::kFunctionReg: closure function.
|
||||
// AllocateClosureABI::kContextReg: closure context.
|
||||
// Output:
|
||||
// AllocateClosureABI::kResultReg: new allocated Closure object.
|
||||
// Clobbered:
|
||||
@@ -1191,9 +1192,6 @@ void StubCodeCompiler::GenerateAllocateClosureStub() {
|
||||
target::RoundedAllocationSize(target::Closure::InstanceSize());
|
||||
__ EnsureHasClassIdInDEBUG(kFunctionCid, AllocateClosureABI::kFunctionReg,
|
||||
AllocateClosureABI::kScratchReg);
|
||||
__ EnsureHasClassIdInDEBUG(kContextCid, AllocateClosureABI::kContextReg,
|
||||
AllocateClosureABI::kScratchReg,
|
||||
/*can_be_null=*/true);
|
||||
if (!FLAG_use_slow_path && FLAG_inline_alloc) {
|
||||
Label slow_case;
|
||||
__ Comment("Inline allocation of uninitialized closure");
|
||||
|
||||
@@ -224,49 +224,14 @@ void StubCodeCompiler::GenerateBuildMethodExtractorStub(
|
||||
__ ldr(R0, Address(FP, kReceiverOffset * target::kWordSize), NE);
|
||||
__ ldr(R3, Address(R0, R4), NE);
|
||||
|
||||
// Push type arguments & extracted method.
|
||||
// Push type arguments.
|
||||
__ Push(R3);
|
||||
__ Push(R1);
|
||||
|
||||
// Allocate context.
|
||||
{
|
||||
Label done, slow_path;
|
||||
if (!FLAG_use_slow_path && FLAG_inline_alloc) {
|
||||
__ TryAllocateArray(kContextCid, target::Context::InstanceSize(1),
|
||||
&slow_path,
|
||||
R0, // instance
|
||||
R1, // end address
|
||||
R2, R3);
|
||||
__ ldr(R1, Address(THR, target::Thread::object_null_offset()));
|
||||
__ str(R1, FieldAddress(R0, target::Context::parent_offset()));
|
||||
__ LoadImmediate(R1, 1);
|
||||
__ str(R1, FieldAddress(R0, target::Context::num_variables_offset()));
|
||||
__ b(&done);
|
||||
}
|
||||
|
||||
__ Bind(&slow_path);
|
||||
|
||||
__ LoadImmediate(/*num_vars=*/R1, 1);
|
||||
__ LoadObject(CODE_REG, context_allocation_stub);
|
||||
__ ldr(R0, FieldAddress(CODE_REG, target::Code::entry_point_offset()));
|
||||
__ blx(R0);
|
||||
|
||||
__ Bind(&done);
|
||||
}
|
||||
|
||||
// Put context in right register for AllocateClosure call.
|
||||
__ MoveRegister(AllocateClosureABI::kContextReg, R0);
|
||||
|
||||
// Store receiver in context
|
||||
__ ldr(AllocateClosureABI::kScratchReg,
|
||||
// Put function and context (receiver) in right registers for
|
||||
// AllocateClosure stub.
|
||||
__ MoveRegister(AllocateClosureABI::kFunctionReg, R1);
|
||||
__ ldr(AllocateClosureABI::kContextReg,
|
||||
Address(FP, target::kWordSize * kReceiverOffset));
|
||||
__ StoreIntoObject(AllocateClosureABI::kContextReg,
|
||||
FieldAddress(AllocateClosureABI::kContextReg,
|
||||
target::Context::variable_offset(0)),
|
||||
AllocateClosureABI::kScratchReg);
|
||||
|
||||
// Pop function.
|
||||
__ Pop(AllocateClosureABI::kFunctionReg);
|
||||
|
||||
// Allocate closure. After this point, we only use the registers in
|
||||
// AllocateClosureABI.
|
||||
|
||||
@@ -676,51 +676,14 @@ void StubCodeCompiler::GenerateBuildMethodExtractorStub(
|
||||
__ LoadCompressed(R3, Address(R0, R4));
|
||||
__ Bind(&no_type_args);
|
||||
|
||||
// Push type arguments & extracted method.
|
||||
// Push type arguments.
|
||||
__ Push(R3);
|
||||
__ Push(R1);
|
||||
|
||||
// Allocate context.
|
||||
{
|
||||
Label done, slow_path;
|
||||
if (!FLAG_use_slow_path && FLAG_inline_alloc) {
|
||||
__ TryAllocateArray(kContextCid, target::Context::InstanceSize(1),
|
||||
&slow_path,
|
||||
R0, // instance
|
||||
R1, // end address
|
||||
R2, R3);
|
||||
__ StoreCompressedIntoObjectNoBarrier(
|
||||
R0, FieldAddress(R0, target::Context::parent_offset()), NULL_REG);
|
||||
__ LoadImmediate(R1, 1);
|
||||
__ str(R1, FieldAddress(R0, target::Context::num_variables_offset()),
|
||||
kFourBytes);
|
||||
__ b(&done);
|
||||
}
|
||||
|
||||
__ Bind(&slow_path);
|
||||
|
||||
__ LoadImmediate(/*num_vars=*/R1, 1);
|
||||
__ LoadObject(CODE_REG, context_allocation_stub);
|
||||
__ ldr(R0, FieldAddress(CODE_REG, target::Code::entry_point_offset()));
|
||||
__ blr(R0);
|
||||
|
||||
__ Bind(&done);
|
||||
}
|
||||
|
||||
// Put context in right register for AllocateClosure call.
|
||||
__ MoveRegister(AllocateClosureABI::kContextReg, R0);
|
||||
|
||||
// Store receiver in context
|
||||
__ ldr(AllocateClosureABI::kScratchReg,
|
||||
// Put function and context (receiver) in right registers for
|
||||
// AllocateClosure stub.
|
||||
__ MoveRegister(AllocateClosureABI::kFunctionReg, R1);
|
||||
__ ldr(AllocateClosureABI::kContextReg,
|
||||
Address(FP, target::kWordSize * kReceiverOffset));
|
||||
__ StoreCompressedIntoObject(
|
||||
AllocateClosureABI::kContextReg,
|
||||
FieldAddress(AllocateClosureABI::kContextReg,
|
||||
target::Context::variable_offset(0)),
|
||||
AllocateClosureABI::kScratchReg);
|
||||
|
||||
// Pop function before pushing context.
|
||||
__ Pop(AllocateClosureABI::kFunctionReg);
|
||||
|
||||
// Allocate closure. After this point, we only use the registers in
|
||||
// AllocateClosureABI.
|
||||
|
||||
@@ -548,49 +548,14 @@ void StubCodeCompiler::GenerateBuildMethodExtractorStub(
|
||||
__ LoadCompressed(T3, Address(TMP, 0));
|
||||
__ Bind(&no_type_args);
|
||||
|
||||
// Push type arguments & extracted method.
|
||||
__ PushRegistersInOrder({T3, T1});
|
||||
// Push type arguments.
|
||||
__ PushRegister(T3);
|
||||
|
||||
// Allocate context.
|
||||
{
|
||||
Label done, slow_path;
|
||||
if (!FLAG_use_slow_path && FLAG_inline_alloc) {
|
||||
__ TryAllocateArray(kContextCid, target::Context::InstanceSize(1),
|
||||
&slow_path,
|
||||
A0, // instance
|
||||
T1, // end address
|
||||
T2, T3);
|
||||
__ StoreCompressedIntoObjectNoBarrier(
|
||||
A0, FieldAddress(A0, target::Context::parent_offset()), NULL_REG);
|
||||
__ LoadImmediate(T1, 1);
|
||||
__ sw(T1, FieldAddress(A0, target::Context::num_variables_offset()));
|
||||
__ j(&done, compiler::Assembler::kNearJump);
|
||||
}
|
||||
|
||||
__ Bind(&slow_path);
|
||||
|
||||
__ LoadImmediate(/*num_vars=*/T1, 1);
|
||||
__ LoadObject(CODE_REG, context_allocation_stub);
|
||||
__ lx(RA, FieldAddress(CODE_REG, target::Code::entry_point_offset()));
|
||||
__ jalr(RA);
|
||||
|
||||
__ Bind(&done);
|
||||
}
|
||||
|
||||
// Put context in right register for AllocateClosure call.
|
||||
__ MoveRegister(AllocateClosureABI::kContextReg, A0);
|
||||
|
||||
// Store receiver in context
|
||||
__ lx(AllocateClosureABI::kScratchReg,
|
||||
// Put function and context (receiver) in right registers for
|
||||
// AllocateClosure stub.
|
||||
__ MoveRegister(AllocateClosureABI::kFunctionReg, T1);
|
||||
__ lx(AllocateClosureABI::kContextReg,
|
||||
Address(FP, target::kWordSize * kReceiverOffset));
|
||||
__ StoreCompressedIntoObject(
|
||||
AllocateClosureABI::kContextReg,
|
||||
FieldAddress(AllocateClosureABI::kContextReg,
|
||||
target::Context::variable_offset(0)),
|
||||
AllocateClosureABI::kScratchReg);
|
||||
|
||||
// Pop function before pushing context.
|
||||
__ PopRegister(AllocateClosureABI::kFunctionReg);
|
||||
|
||||
// Allocate closure. After this point, we only use the registers in
|
||||
// AllocateClosureABI.
|
||||
|
||||
@@ -636,49 +636,11 @@ void StubCodeCompiler::GenerateBuildMethodExtractorStub(
|
||||
__ Bind(&no_type_args);
|
||||
__ pushq(RCX);
|
||||
|
||||
// Push extracted method.
|
||||
__ pushq(RBX);
|
||||
|
||||
// Allocate context.
|
||||
{
|
||||
Label done, slow_path;
|
||||
if (!FLAG_use_slow_path && FLAG_inline_alloc) {
|
||||
__ TryAllocateArray(kContextCid, target::Context::InstanceSize(1),
|
||||
&slow_path, Assembler::kFarJump,
|
||||
RAX, // instance
|
||||
RSI, // end address
|
||||
RDI);
|
||||
__ movq(RSI, Address(THR, target::Thread::object_null_offset()));
|
||||
__ StoreCompressedIntoObjectNoBarrier(
|
||||
RAX, FieldAddress(RAX, target::Context::parent_offset()), RSI);
|
||||
__ movl(FieldAddress(RAX, target::Context::num_variables_offset()),
|
||||
Immediate(1));
|
||||
__ jmp(&done);
|
||||
}
|
||||
|
||||
__ Bind(&slow_path);
|
||||
|
||||
__ LoadImmediate(/*num_vars=*/R10, Immediate(1));
|
||||
__ LoadObject(CODE_REG, context_allocation_stub);
|
||||
__ call(FieldAddress(CODE_REG, target::Code::entry_point_offset()));
|
||||
|
||||
__ Bind(&done);
|
||||
}
|
||||
|
||||
// Put context in right register for AllocateClosure call.
|
||||
__ MoveRegister(AllocateClosureABI::kContextReg, RAX);
|
||||
|
||||
// Store receiver in context
|
||||
__ movq(AllocateClosureABI::kScratchReg,
|
||||
// Put function and context (receiver) in right registers for
|
||||
// AllocateClosure stub.
|
||||
__ MoveRegister(AllocateClosureABI::kFunctionReg, RBX);
|
||||
__ movq(AllocateClosureABI::kContextReg,
|
||||
Address(RBP, target::kWordSize * kReceiverOffsetInWords));
|
||||
__ StoreCompressedIntoObject(
|
||||
AllocateClosureABI::kContextReg,
|
||||
FieldAddress(AllocateClosureABI::kContextReg,
|
||||
target::Context::variable_offset(0)),
|
||||
AllocateClosureABI::kScratchReg);
|
||||
|
||||
// Pop function.
|
||||
__ popq(AllocateClosureABI::kFunctionReg);
|
||||
|
||||
// Allocate closure. After this point, we only use the registers in
|
||||
// AllocateClosureABI.
|
||||
|
||||
@@ -717,7 +717,7 @@ const Context& ActivationFrame::GetSavedCurrentContext() {
|
||||
ASSERT(function().name() == Symbols::call().ptr());
|
||||
ASSERT(function().IsInvokeFieldDispatcher());
|
||||
// Closure.call frames.
|
||||
ctx_ = Closure::Cast(obj).context();
|
||||
ctx_ = Closure::Cast(obj).GetContext();
|
||||
} else if (obj.IsContext()) {
|
||||
ctx_ = Context::Cast(obj).ptr();
|
||||
} else {
|
||||
|
||||
+18
-10
@@ -10338,6 +10338,14 @@ bool Function::IsImplicitStaticClosureFunction(FunctionPtr func) {
|
||||
StaticBit::decode(kind_tag);
|
||||
}
|
||||
|
||||
bool Function::IsImplicitInstanceClosureFunction(FunctionPtr func) {
|
||||
NoSafepointScope no_safepoint;
|
||||
uint32_t kind_tag = func->untag()->kind_tag_.load(std::memory_order_relaxed);
|
||||
return (KindBits::decode(kind_tag) ==
|
||||
UntaggedFunction::kImplicitClosureFunction) &&
|
||||
!StaticBit::decode(kind_tag);
|
||||
}
|
||||
|
||||
FunctionPtr Function::New(Heap::Space space) {
|
||||
ASSERT(Object::function_class() != Class::null());
|
||||
return Object::Allocate<Function>(space);
|
||||
@@ -10816,11 +10824,10 @@ ClosurePtr Function::ImplicitStaticClosure() const {
|
||||
}
|
||||
|
||||
Zone* zone = thread->zone();
|
||||
const auto& null_context = Context::Handle(zone);
|
||||
const auto& closure =
|
||||
Closure::Handle(zone, Closure::New(Object::null_type_arguments(),
|
||||
Object::null_type_arguments(), *this,
|
||||
null_context, Heap::kOld));
|
||||
Object::null_object(), Heap::kOld));
|
||||
set_implicit_static_closure(closure);
|
||||
return implicit_static_closure();
|
||||
}
|
||||
@@ -10828,15 +10835,13 @@ ClosurePtr Function::ImplicitStaticClosure() const {
|
||||
ClosurePtr Function::ImplicitInstanceClosure(const Instance& receiver) const {
|
||||
ASSERT(IsImplicitClosureFunction());
|
||||
Zone* zone = Thread::Current()->zone();
|
||||
const Context& context = Context::Handle(zone, Context::New(1));
|
||||
context.SetAt(0, receiver);
|
||||
TypeArguments& instantiator_type_arguments = TypeArguments::Handle(zone);
|
||||
if (!HasInstantiatedSignature(kCurrentClass)) {
|
||||
instantiator_type_arguments = receiver.GetTypeArguments();
|
||||
}
|
||||
ASSERT(!HasGenericParent()); // No generic parent function.
|
||||
return Closure::New(instantiator_type_arguments,
|
||||
Object::null_type_arguments(), *this, context);
|
||||
Object::null_type_arguments(), *this, receiver);
|
||||
}
|
||||
|
||||
FunctionPtr Function::ImplicitClosureTarget(Zone* zone) const {
|
||||
@@ -26235,7 +26240,7 @@ bool Closure::CanonicalizeEquals(const Instance& other) const {
|
||||
other_closure.function_type_arguments()) &&
|
||||
(delayed_type_arguments() == other_closure.delayed_type_arguments()) &&
|
||||
(function() == other_closure.function()) &&
|
||||
(context() == other_closure.context());
|
||||
(RawContext() == other_closure.RawContext());
|
||||
}
|
||||
|
||||
void Closure::CanonicalizeFieldsLocked(Thread* thread) const {
|
||||
@@ -26290,9 +26295,8 @@ uword Closure::ComputeHash() const {
|
||||
result = CombineHashes(result, delayed_type_args.Hash());
|
||||
}
|
||||
if (func.IsImplicitInstanceClosureFunction()) {
|
||||
const Context& context = Context::Handle(zone, this->context());
|
||||
const Instance& receiver =
|
||||
Instance::Handle(zone, Instance::RawCast(context.At(0)));
|
||||
Instance::Handle(zone, GetImplicitClosureReceiver());
|
||||
const Integer& receiverHash =
|
||||
Integer::Handle(zone, receiver.IdentityHashCode(thread));
|
||||
result = CombineHashes(result, receiverHash.AsTruncatedUint32Value());
|
||||
@@ -26310,7 +26314,7 @@ uword Closure::ComputeHash() const {
|
||||
ClosurePtr Closure::New(const TypeArguments& instantiator_type_arguments,
|
||||
const TypeArguments& function_type_arguments,
|
||||
const Function& function,
|
||||
const Context& context,
|
||||
const Object& context,
|
||||
Heap::Space space) {
|
||||
// We store null delayed type arguments, not empty ones, in closures with
|
||||
// non-generic functions a) to make method extraction slightly faster and
|
||||
@@ -26326,12 +26330,16 @@ ClosurePtr Closure::New(const TypeArguments& instantiator_type_arguments,
|
||||
const TypeArguments& function_type_arguments,
|
||||
const TypeArguments& delayed_type_arguments,
|
||||
const Function& function,
|
||||
const Context& context,
|
||||
const Object& context,
|
||||
Heap::Space space) {
|
||||
ASSERT(instantiator_type_arguments.IsCanonical());
|
||||
ASSERT(function_type_arguments.IsCanonical());
|
||||
ASSERT(delayed_type_arguments.IsCanonical());
|
||||
ASSERT(FunctionType::Handle(function.signature()).IsCanonical());
|
||||
ASSERT(
|
||||
(function.IsImplicitInstanceClosureFunction() && context.IsInstance()) ||
|
||||
(function.IsNonImplicitClosureFunction() && context.IsContext()) ||
|
||||
context.IsNull());
|
||||
const auto& result = Closure::Handle(Object::Allocate<Closure>(space));
|
||||
result.untag()->set_instantiator_type_arguments(
|
||||
instantiator_type_arguments.ptr());
|
||||
|
||||
+21
-8
@@ -3324,8 +3324,9 @@ class Function : public Object {
|
||||
FunctionPtr ForwardingTarget() const;
|
||||
void SetForwardingTarget(const Function& target) const;
|
||||
|
||||
UntaggedFunction::Kind kind() const {
|
||||
return untag()->kind_tag_.Read<KindBits>();
|
||||
UntaggedFunction::Kind kind() const { return KindOf(ptr()); }
|
||||
static UntaggedFunction::Kind KindOf(FunctionPtr func) {
|
||||
return func->untag()->kind_tag_.Read<KindBits>();
|
||||
}
|
||||
|
||||
UntaggedFunction::AsyncModifier modifier() const {
|
||||
@@ -3877,6 +3878,9 @@ class Function : public Object {
|
||||
bool IsImplicitClosureFunction() const {
|
||||
return kind() == UntaggedFunction::kImplicitClosureFunction;
|
||||
}
|
||||
static bool IsImplicitClosureFunction(FunctionPtr func) {
|
||||
return KindOf(func) == UntaggedFunction::kImplicitClosureFunction;
|
||||
}
|
||||
|
||||
// Returns true if this function represents a non implicit closure function.
|
||||
bool IsNonImplicitClosureFunction() const {
|
||||
@@ -3895,6 +3899,7 @@ class Function : public Object {
|
||||
bool IsImplicitInstanceClosureFunction() const {
|
||||
return IsImplicitClosureFunction() && !is_static();
|
||||
}
|
||||
static bool IsImplicitInstanceClosureFunction(FunctionPtr func);
|
||||
|
||||
// Returns true if this function has a parent function.
|
||||
bool HasParent() const { return parent_function() != Function::null(); }
|
||||
@@ -12477,13 +12482,21 @@ class Closure : public Instance {
|
||||
return closure.untag()->function();
|
||||
}
|
||||
|
||||
ContextPtr context() const { return untag()->context(); }
|
||||
ObjectPtr RawContext() const { return untag()->context(); }
|
||||
|
||||
ContextPtr GetContext() const {
|
||||
ASSERT(!Function::IsImplicitClosureFunction(function()));
|
||||
return Context::RawCast(RawContext());
|
||||
}
|
||||
|
||||
InstancePtr GetImplicitClosureReceiver() const {
|
||||
ASSERT(Function::IsImplicitInstanceClosureFunction(function()));
|
||||
return Instance::RawCast(RawContext());
|
||||
}
|
||||
|
||||
static intptr_t context_offset() {
|
||||
return OFFSET_OF(UntaggedClosure, context_);
|
||||
}
|
||||
static ContextPtr ContextOf(ClosurePtr closure) {
|
||||
return closure.untag()->context();
|
||||
}
|
||||
|
||||
// Returns whether the closure is generic, that is, it has a generic closure
|
||||
// function and no delayed type arguments.
|
||||
@@ -12508,14 +12521,14 @@ class Closure : public Instance {
|
||||
static ClosurePtr New(const TypeArguments& instantiator_type_arguments,
|
||||
const TypeArguments& function_type_arguments,
|
||||
const Function& function,
|
||||
const Context& context,
|
||||
const Object& context,
|
||||
Heap::Space space = Heap::kNew);
|
||||
|
||||
static ClosurePtr New(const TypeArguments& instantiator_type_arguments,
|
||||
const TypeArguments& function_type_arguments,
|
||||
const TypeArguments& delayed_type_arguments,
|
||||
const Function& function,
|
||||
const Context& context,
|
||||
const Object& context,
|
||||
Heap::Space space = Heap::kNew);
|
||||
|
||||
FunctionTypePtr GetInstantiatedSignature(Zone* zone) const;
|
||||
|
||||
@@ -1031,7 +1031,7 @@ class RetainingPath {
|
||||
if (cid == kClosureCid) {
|
||||
closure ^= raw;
|
||||
// Only context has to be checked.
|
||||
working_list->Add(closure.context());
|
||||
working_list->Add(closure.RawContext());
|
||||
break;
|
||||
}
|
||||
// These we are not expected to drill into as they can't be on
|
||||
|
||||
@@ -1881,10 +1881,19 @@ void Closure::PrintJSONImpl(JSONStream* stream, bool ref) const {
|
||||
JSONObject jsobj(stream);
|
||||
PrintSharedInstanceJSON(&jsobj, ref);
|
||||
jsobj.AddProperty("kind", "Closure");
|
||||
jsobj.AddProperty("closureFunction",
|
||||
Function::Handle(Closure::Cast(*this).function()));
|
||||
jsobj.AddProperty("closureContext",
|
||||
Context::Handle(Closure::Cast(*this).context()));
|
||||
const auto& func = Function::Handle(function());
|
||||
jsobj.AddProperty("closureFunction", func);
|
||||
if (!func.IsImplicitClosureFunction()) {
|
||||
jsobj.AddProperty("closureContext", Context::Handle(GetContext()));
|
||||
} else {
|
||||
jsobj.AddProperty("closureContext", Object::null_object());
|
||||
}
|
||||
if (func.IsImplicitInstanceClosureFunction()) {
|
||||
jsobj.AddProperty("closureReceiver",
|
||||
Object::Handle(GetImplicitClosureReceiver()));
|
||||
} else {
|
||||
jsobj.AddProperty("closureReceiver", Object::null_object());
|
||||
}
|
||||
if (ref) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2700,7 +2700,7 @@ ISOLATE_UNIT_TEST_CASE(Closure) {
|
||||
EXPECT_EQ(closure_class.id(), kClosureCid);
|
||||
const Function& closure_function = Function::Handle(closure.function());
|
||||
EXPECT_EQ(closure_function.ptr(), function.ptr());
|
||||
const Context& closure_context = Context::Handle(closure.context());
|
||||
const Context& closure_context = Context::Handle(closure.GetContext());
|
||||
EXPECT_EQ(closure_context.ptr(), context.ptr());
|
||||
}
|
||||
|
||||
|
||||
@@ -2901,7 +2901,9 @@ class UntaggedClosure : public UntaggedInstance {
|
||||
// determine whether a given closure value is generic.
|
||||
COMPRESSED_POINTER_FIELD(TypeArgumentsPtr, delayed_type_arguments)
|
||||
COMPRESSED_POINTER_FIELD(FunctionPtr, function)
|
||||
COMPRESSED_POINTER_FIELD(ContextPtr, context)
|
||||
// For tear-offs - captured receiver.
|
||||
// For ordinary closures - Context object with captured variables.
|
||||
COMPRESSED_POINTER_FIELD(ObjectPtr, context)
|
||||
COMPRESSED_POINTER_FIELD(SmiPtr, hash)
|
||||
VISIT_TO(hash)
|
||||
|
||||
|
||||
@@ -693,7 +693,7 @@ DEFINE_RUNTIME_ENTRY(SubtypeCheck, 5) {
|
||||
// Return value: newly allocated closure.
|
||||
DEFINE_RUNTIME_ENTRY(AllocateClosure, 2) {
|
||||
const auto& function = Function::CheckedHandle(zone, arguments.ArgAt(0));
|
||||
const auto& context = Context::CheckedHandle(zone, arguments.ArgAt(1));
|
||||
const auto& context = Object::Handle(zone, arguments.ArgAt(1));
|
||||
const Closure& closure = Closure::Handle(
|
||||
zone,
|
||||
Closure::New(Object::null_type_arguments(), Object::null_type_arguments(),
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
namespace dart {
|
||||
|
||||
#define SERVICE_PROTOCOL_MAJOR_VERSION 4
|
||||
#define SERVICE_PROTOCOL_MINOR_VERSION 14
|
||||
#define SERVICE_PROTOCOL_MINOR_VERSION 15
|
||||
|
||||
class Array;
|
||||
class EmbedderServiceHandler;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Dart VM Service Protocol 4.14
|
||||
# Dart VM Service Protocol 4.15
|
||||
|
||||
> Please post feedback to the [observatory-discuss group][discuss-list]
|
||||
|
||||
@@ -2967,6 +2967,12 @@ class @Instance extends @Object {
|
||||
// Closure
|
||||
@Context closureContext [optional];
|
||||
|
||||
// The receiver captured by tear-off Closure instance.
|
||||
//
|
||||
// Provided for instance kinds:
|
||||
// Closure
|
||||
@Instance closureReceiver [optional];
|
||||
|
||||
// The port ID for a ReceivePort.
|
||||
//
|
||||
// Provided for instance kinds:
|
||||
@@ -3200,6 +3206,12 @@ class Instance extends Object {
|
||||
// Closure
|
||||
@Context closureContext [optional];
|
||||
|
||||
// The receiver captured by tear-off Closure instance.
|
||||
//
|
||||
// Provided for instance kinds:
|
||||
// Closure
|
||||
@Instance closureReceiver [optional];
|
||||
|
||||
// Whether this regular expression is case sensitive.
|
||||
//
|
||||
// Provided for instance kinds:
|
||||
@@ -4787,5 +4799,6 @@ version | comments
|
||||
4.12 | Added `@TypeParameters` and changed `TypeParameters` to extend `Object`.
|
||||
4.13 | Added `librariesAlreadyCompiled` to `getSourceReport`.
|
||||
4.14 | Added `Finalizer`, `NativeFinalizer`, and `FinalizerEntry`.
|
||||
4.15 | Added `closureReceiver` property to `@Instance` and `Instance`.
|
||||
|
||||
[discuss-list]: https://groups.google.com/a/dartlang.org/forum/#!forum/observatory-discuss
|
||||
|
||||
@@ -353,10 +353,10 @@ void AsyncAwareStackUnwinder::UnwindAwaiterFrame() {
|
||||
|
||||
while (!awaiter_frame_.closure.IsNull()) {
|
||||
function_ = awaiter_frame_.closure.function();
|
||||
context_ = awaiter_frame_.closure.context();
|
||||
|
||||
const auto awaiter_link = function_.awaiter_link();
|
||||
if (awaiter_link.depth != ClosureData::kNoAwaiterLinkDepth) {
|
||||
context_ = awaiter_frame_.closure.GetContext();
|
||||
intptr_t depth = awaiter_link.depth;
|
||||
while (depth-- > 0) {
|
||||
context_ = context_.parent();
|
||||
@@ -448,11 +448,8 @@ void AsyncAwareStackUnwinder::UnwindFrameToStreamListener() {
|
||||
}
|
||||
|
||||
// All implicit closure functions (tear-offs) have the "this" receiver
|
||||
// captured.
|
||||
context_ = closure_.context();
|
||||
ASSERT(context_.num_variables() == 1);
|
||||
stream_iterator_ = context_.At(0);
|
||||
ASSERT(stream_iterator_.IsInstance());
|
||||
// captured in the context.
|
||||
stream_iterator_ = closure_.GetImplicitClosureReceiver();
|
||||
|
||||
if (stream_iterator_.GetClassId() != _StreamIterator().id()) {
|
||||
UNREACHABLE();
|
||||
@@ -519,7 +516,7 @@ bool StackTraceUtils::GetSuspendState(const Closure& closure,
|
||||
const Function& function = Function::Handle(closure.function());
|
||||
const auto awaiter_link = function.awaiter_link();
|
||||
if (awaiter_link.depth != ClosureData::kNoAwaiterLinkDepth) {
|
||||
Context& context = Context::Handle(closure.context());
|
||||
Context& context = Context::Handle(closure.GetContext());
|
||||
intptr_t depth = awaiter_link.depth;
|
||||
while (depth-- > 0) {
|
||||
context = context.parent();
|
||||
|
||||
Reference in New Issue
Block a user