[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:
Alexander Markov
2024-02-13 15:33:38 +00:00
committed by Commit Queue
parent 6d57fe5a1c
commit a261196ea7
28 changed files with 168 additions and 333 deletions
+25
View File
@@ -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
View File
@@ -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;
}
+1 -1
View File
@@ -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);
},
+4 -4
View File
@@ -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;
}
+1 -1
View File
@@ -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) \
-36
View File
@@ -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;
-13
View File
@@ -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);
}
+2 -43
View File
@@ -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);
+1 -3
View File
@@ -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");
+5 -40
View File
@@ -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.
+4 -42
View File
@@ -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.
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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;
+1 -1
View File
@@ -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
+13 -4
View File
@@ -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;
}
+1 -1
View File
@@ -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());
}
+3 -1
View File
@@ -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)
+1 -1
View File
@@ -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(),
+1 -1
View File
@@ -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;
+14 -1
View File
@@ -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
+4 -7
View File
@@ -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();