Skip to content

Commit 8c1f86e

Browse files
alexmarkovCommit Queue
authored and
Commit Queue
committed
[vm/aot] Devirtualization of function calls
TFA now produces direct call metadata for function calls based on the results of the analysis. VM reads direct call metadata for function calls, but only uses it if target is a tear-off, as it cannot handle inlining of an arbitrary closure function yet (due to scope building dependencies between compilation of a closure and its parent function). TEST=pkg/vm/testcases/transformations/type_flow/transformer/closures.dart Issue: #39692 Change-Id: Idf0b6988534bfca1a64c6661a82df8d453272abf Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/357180 Reviewed-by: Martin Kustermann <[email protected]> Reviewed-by: Slava Egorov <[email protected]> Commit-Queue: Alexander Markov <[email protected]>
1 parent 84c05f7 commit 8c1f86e

18 files changed

+289
-124
lines changed

pkg/dart2wasm/lib/translator.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -945,7 +945,7 @@ class Translator with KernelNodes {
945945
DirectCallMetadataRepository metadata =
946946
component.metadata[DirectCallMetadataRepository.repositoryTag]
947947
as DirectCallMetadataRepository;
948-
return metadata.mapping[node]?.target;
948+
return metadata.mapping[node]?.targetMember;
949949
}
950950

951951
bool shouldInline(Reference target) {

pkg/vm/lib/metadata/direct_call.dart

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,42 @@ library vm.metadata.direct_call;
77
import 'package:kernel/ast.dart';
88
import 'package:kernel/src/printer.dart';
99

10-
/// Metadata for annotating method invocations converted to direct calls.
10+
/// Metadata for annotating invocations converted to direct calls.
1111
class DirectCallMetadata {
12-
final Reference _targetReference;
13-
final bool checkReceiverForNull;
12+
// Target of the direct call or enclosing member of a closure.
13+
final Reference _memberReference;
14+
final int _flags;
15+
final int _closureId;
1416

15-
DirectCallMetadata(Member target, bool checkReceiverForNull)
16-
: this.byReference(
17-
getNonNullableMemberReferenceGetter(target), checkReceiverForNull);
17+
static const int flagCheckReceiverForNull = 1 << 0;
18+
static const int flagClosure = 1 << 1;
1819

19-
DirectCallMetadata.byReference(
20-
this._targetReference, this.checkReceiverForNull);
20+
DirectCallMetadata.targetMember(Member target, bool checkReceiverForNull)
21+
: this._(getNonNullableMemberReferenceGetter(target),
22+
checkReceiverForNull ? flagCheckReceiverForNull : 0, 0);
2123

22-
Member get target => _targetReference.asMember;
24+
DirectCallMetadata.targetClosure(
25+
Member closureMember, int closureId, bool checkReceiverForNull)
26+
: this._(
27+
getNonNullableMemberReferenceGetter(closureMember),
28+
(checkReceiverForNull ? flagCheckReceiverForNull : 0) | flagClosure,
29+
closureId);
30+
31+
DirectCallMetadata._(this._memberReference, this._flags, this._closureId)
32+
: assert(_closureId >= 0);
33+
34+
// Target member or enclosing member of a closure.
35+
Member get _member => _memberReference.asMember;
36+
37+
Member? get targetMember => isClosure ? null : _member;
38+
39+
bool get checkReceiverForNull => (_flags & flagCheckReceiverForNull) != 0;
40+
bool get isClosure => (_flags & flagClosure) != 0;
2341

2442
@override
25-
String toString() => "${target.toText(astTextStrategyForTesting)}"
26-
"${checkReceiverForNull ? '??' : ''}";
43+
String toString() => isClosure
44+
? 'closure ${_closureId} in ${_member.toText(astTextStrategyForTesting)}'
45+
: '${_member.toText(astTextStrategyForTesting)}${checkReceiverForNull ? '??' : ''}';
2746
}
2847

2948
/// Repository for [DirectCallMetadata].
@@ -41,19 +60,23 @@ class DirectCallMetadataRepository
4160
@override
4261
void writeToBinary(DirectCallMetadata metadata, Node node, BinarySink sink) {
4362
sink.writeNullAllowedCanonicalNameReference(
44-
getMemberReferenceGetter(metadata.target));
45-
sink.writeByte(metadata.checkReceiverForNull ? 1 : 0);
63+
getMemberReferenceGetter(metadata._member));
64+
sink.writeByte(metadata._flags);
65+
if (metadata.isClosure) {
66+
sink.writeUInt30(metadata._closureId);
67+
}
4668
}
4769

4870
@override
4971
DirectCallMetadata readFromBinary(Node node, BinarySource source) {
50-
final targetReference =
72+
final memberReference =
5173
source.readNullableCanonicalNameReference()?.reference;
52-
if (targetReference == null) {
53-
throw 'DirectCallMetadata should have a non-null target';
74+
if (memberReference == null) {
75+
throw 'DirectCallMetadata should have a non-null member';
5476
}
55-
final checkReceiverForNull = (source.readByte() != 0);
56-
return new DirectCallMetadata.byReference(
57-
targetReference, checkReceiverForNull);
77+
final flags = source.readByte();
78+
final closureId =
79+
(flags & DirectCallMetadata.flagClosure) != 0 ? source.readUInt30() : 0;
80+
return DirectCallMetadata._(memberReference, flags, closureId);
5881
}
5982
}

pkg/vm/lib/transformations/devirtualization.dart

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,14 @@ abstract class Devirtualization extends RecursiveVisitor {
8181

8282
bool hasExtraTargetForNull(DirectCallMetadata directCall) =>
8383
directCall.checkReceiverForNull &&
84-
_objectMemberNames.contains(directCall.target.name);
84+
_objectMemberNames.contains(directCall.targetMember!.name);
8585

8686
DirectCallMetadata? getDirectCall(TreeNode node, Member? interfaceTarget,
8787
{bool setter = false});
8888

8989
makeDirectCall(TreeNode node, Member? target, DirectCallMetadata directCall) {
9090
if (_trace) {
91-
print("[devirt] Resolving ${target} to ${directCall.target}"
91+
print("[devirt] Resolving ${target} to ${directCall.targetMember}"
9292
" at ${node.location}");
9393
}
9494
_metadata.mapping[node] = directCall;
@@ -113,8 +113,8 @@ abstract class Devirtualization extends RecursiveVisitor {
113113
// TODO(alexmarkov): Convert _isLegalTargetForMethodInvocation()
114114
// check into an assertion once front-end implements all override checks.
115115
if ((directCall != null) &&
116-
isMethod(directCall.target) &&
117-
isLegalTargetForMethodInvocation(directCall.target, arguments) &&
116+
isMethod(directCall.targetMember!) &&
117+
isLegalTargetForMethodInvocation(directCall.targetMember!, arguments) &&
118118
!hasExtraTargetForNull(directCall)) {
119119
makeDirectCall(node, target, directCall);
120120
}
@@ -151,7 +151,7 @@ abstract class Devirtualization extends RecursiveVisitor {
151151
final DirectCallMetadata? directCall = getDirectCall(node, target);
152152

153153
if ((directCall != null) &&
154-
isFieldOrGetter(directCall.target) &&
154+
isFieldOrGetter(directCall.targetMember!) &&
155155
!hasExtraTargetForNull(directCall)) {
156156
makeDirectCall(node, target, directCall);
157157
}
@@ -188,6 +188,15 @@ abstract class Devirtualization extends RecursiveVisitor {
188188
super.visitDynamicSet(node);
189189
_handlePropertySet(node, null);
190190
}
191+
192+
@override
193+
visitFunctionInvocation(FunctionInvocation node) {
194+
super.visitFunctionInvocation(node);
195+
final DirectCallMetadata? directCall = getDirectCall(node, null);
196+
if (directCall != null) {
197+
makeDirectCall(node, null, directCall);
198+
}
199+
}
191200
}
192201

193202
/// Devirtualization based on the closed-world class hierarchy analysis.
@@ -209,6 +218,6 @@ class CHADevirtualization extends Devirtualization {
209218
if (singleTarget == null) {
210219
return null;
211220
}
212-
return new DirectCallMetadata(singleTarget, true);
221+
return DirectCallMetadata.targetMember(singleTarget, true);
213222
}
214223
}

pkg/vm/lib/transformations/type_flow/analysis.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ final class _DirectInvocation extends _Invocation {
350350
assert(areArgumentsValidFor(member));
351351
Args<Type> args = this.args;
352352
if (selector.memberAgreesToCallKind(member)) {
353-
final closure = typeFlowAnalysis._closureByCallMethod[member];
353+
final closure = typeFlowAnalysis.getClosureByCallMethod(member);
354354
if (closure != null && closure.function == null) {
355355
// Calling tear-off.
356356
//
@@ -489,6 +489,7 @@ final class _DispatchableInvocation extends _Invocation {
489489
if (!_collectTargetsForFunctionCall(
490490
args.receiver, targets, typeFlowAnalysis)) {
491491
// No known closure target, approximate function call with static type.
492+
_setPolymorphic();
492493
return selector.staticResultType;
493494
}
494495
} else {
@@ -1904,6 +1905,9 @@ class TypeFlowAnalysis
19041905
_summaries[member]?.adjustFunctionParameters(member);
19051906
}
19061907

1908+
Closure? getClosureByCallMethod(Member member) =>
1909+
_closureByCallMethod[member];
1910+
19071911
/// ---- Implementation of [CallHandler] interface. ----
19081912
19091913
@override

pkg/vm/lib/transformations/type_flow/summary.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,7 @@ class Call extends Statement {
388388

389389
Member? _monomorphicTarget;
390390

391-
Member? get monomorphicTarget => filterArtificialNode(_monomorphicTarget);
391+
Member? get monomorphicTarget => _monomorphicTarget;
392392

393393
bool get isMonomorphic => (_flags & kMonomorphic) != 0;
394394

pkg/vm/lib/transformations/type_flow/transformer.dart

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,10 @@ Component transformComponent(
133133
treeShakeWriteOnlyFields: treeShakeWriteOnlyFields);
134134
treeShaker.transformComponent(component);
135135

136-
new TFADevirtualization(
137-
component, typeFlowAnalysis, hierarchy, treeShaker.fieldMorpher)
136+
final closureIdMetadata = ClosureIdMetadataRepository();
137+
138+
new TFADevirtualization(component, typeFlowAnalysis, hierarchy,
139+
treeShaker.fieldMorpher, closureIdMetadata)
138140
.visitComponent(component);
139141

140142
final tableSelectorAssigner = new TableSelectorAssigner(component);
@@ -148,8 +150,14 @@ Component transformComponent(
148150
final unboxingInfo = new UnboxingInfoManager(typeFlowAnalysis)
149151
..analyzeComponent(component, typeFlowAnalysis, tableSelectorAssigner);
150152

151-
new AnnotateKernel(component, typeFlowAnalysis, hierarchy,
152-
treeShaker.fieldMorpher, tableSelectorAssigner, unboxingInfo)
153+
new AnnotateKernel(
154+
component,
155+
typeFlowAnalysis,
156+
hierarchy,
157+
treeShaker.fieldMorpher,
158+
tableSelectorAssigner,
159+
unboxingInfo,
160+
closureIdMetadata)
153161
.visitComponent(component);
154162

155163
transformsStopWatch.stop();
@@ -293,9 +301,10 @@ class CleanupAnnotations extends RecursiveVisitor {
293301
class TFADevirtualization extends Devirtualization {
294302
final TypeFlowAnalysis _typeFlowAnalysis;
295303
final FieldMorpher fieldMorpher;
304+
final ClosureIdMetadataRepository _closureIdMetadata;
296305

297306
TFADevirtualization(Component component, this._typeFlowAnalysis,
298-
ClassHierarchy hierarchy, this.fieldMorpher)
307+
ClassHierarchy hierarchy, this.fieldMorpher, this._closureIdMetadata)
299308
: super(_typeFlowAnalysis.environment.coreTypes, component, hierarchy);
300309

301310
@override
@@ -306,8 +315,28 @@ class TFADevirtualization extends Devirtualization {
306315
final Member? singleTarget = fieldMorpher
307316
.getMorphedMember(callSite.monomorphicTarget, isSetter: setter);
308317
if (singleTarget != null) {
309-
return new DirectCallMetadata(
310-
singleTarget, callSite.isNullableReceiver);
318+
if (node is FunctionInvocation) {
319+
final closure =
320+
_typeFlowAnalysis.getClosureByCallMethod(singleTarget)!;
321+
final function = closure.function;
322+
int closureId;
323+
if (function != null) {
324+
_closureIdMetadata.indexClosures(closure.member);
325+
closureId = _closureIdMetadata.getClosureId(function);
326+
if (closureId < 0) {
327+
return null;
328+
} else {
329+
assert(closureId > 0);
330+
}
331+
} else {
332+
closureId = 0;
333+
}
334+
return DirectCallMetadata.targetClosure(
335+
closure.member, closureId, callSite.isNullableReceiver);
336+
} else if (!isArtificialNode(singleTarget)) {
337+
return DirectCallMetadata.targetMember(
338+
singleTarget, callSite.isNullableReceiver);
339+
}
311340
}
312341
}
313342
return null;
@@ -333,8 +362,14 @@ class AnnotateKernel extends RecursiveVisitor {
333362
final TFClass _intTFClass;
334363
late final Constant _nullConstant = NullConstant();
335364

336-
AnnotateKernel(Component component, this._typeFlowAnalysis, this.hierarchy,
337-
this.fieldMorpher, this._tableSelectorAssigner, this._unboxingInfo)
365+
AnnotateKernel(
366+
Component component,
367+
this._typeFlowAnalysis,
368+
this.hierarchy,
369+
this.fieldMorpher,
370+
this._tableSelectorAssigner,
371+
this._unboxingInfo,
372+
this._closureIdMetadata)
338373
: _directCallMetadataRepository =
339374
component.metadata[DirectCallMetadataRepository.repositoryTag]
340375
as DirectCallMetadataRepository,
@@ -343,7 +378,6 @@ class AnnotateKernel extends RecursiveVisitor {
343378
_unreachableNodeMetadata = UnreachableNodeMetadataRepository(),
344379
_procedureAttributesMetadata = ProcedureAttributesMetadataRepository(),
345380
_tableSelectorMetadata = TableSelectorMetadataRepository(),
346-
_closureIdMetadata = ClosureIdMetadataRepository(),
347381
_unboxingInfoMetadata = UnboxingInfoMetadataRepository(),
348382
_intClass = _typeFlowAnalysis.environment.coreTypes.intClass,
349383
_intTFClass = _typeFlowAnalysis.hierarchyCache

pkg/vm/testcases/transformations/ffi/as_function.dart.aot.expect

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,37 +6,47 @@ import "dart:_internal" as _in;
66

77
import "dart:ffi";
88

9+
10+
[@vm.closure-id=1]
911
static method testVoidNoArg() → dynamic {
1012
final ffi::Pointer<ffi::NativeFunction<() → ffi::Void>> pointer = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Pointer::fromAddress<ffi::NativeFunction<() → ffi::Void>>(3735928559);
1113
final () → void function = block {
1214
[@vm.inferred-type.metadata=dart.ffi::Pointer] synthesized ffi::Pointer<ffi::NativeFunction<() → ffi::Void>> #ffiTarget0 = pointer;
15+
16+
[@vm.closure-id=1]
1317
@#C4
1418
function #ffiClosure0() → void {
1519
return ffi::_ffiCall<void>(#ffiTarget0);
1620
}
1721
} =>#ffiClosure0;
18-
[@vm.inferred-type.metadata=!? (receiver not int)] function(){() → void};
22+
[@vm.direct-call.metadata=closure 1 in #lib::testVoidNoArg] [@vm.inferred-type.metadata=!? (receiver not int)] function(){() → void};
1923
}
2024

25+
[@vm.closure-id=1]
2126
[@vm.unboxing-info.metadata=()->i]
2227
static method testIntInt() → dynamic {
2328
final ffi::Pointer<ffi::NativeFunction<(ffi::Int64) → ffi::Int32>> pointer = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Pointer::fromAddress<ffi::NativeFunction<(ffi::Int64) → ffi::Int32>>(3735928559);
2429
final (core::int) → core::int function = block {
2530
[@vm.inferred-type.metadata=dart.ffi::Pointer] synthesized ffi::Pointer<ffi::NativeFunction<(ffi::Int64) → ffi::Int32>> #ffiTarget1 = pointer;
31+
32+
[@vm.closure-id=1]
2633
@#C6
2734
function #ffiClosure1(core::int arg1) → core::int {
2835
_in::_nativeEffect(arg1);
2936
return [@vm.inferred-type.metadata=int] ffi::_ffiCall<core::int>(#ffiTarget1);
3037
}
3138
} =>#ffiClosure1;
32-
return [@vm.inferred-type.metadata=int (receiver not int)] function(42){(core::int) → core::int};
39+
return [@vm.direct-call.metadata=closure 1 in #lib::testIntInt] [@vm.inferred-type.metadata=int (receiver not int)] function(42){(core::int) → core::int};
3340
}
3441

42+
[@vm.closure-id=1]
3543
[@vm.unboxing-info.metadata=()->i]
3644
static method testLeaf5Args() → dynamic {
3745
final ffi::Pointer<ffi::NativeFunction<(ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32) → ffi::Int32>> pointer = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::Pointer::fromAddress<ffi::NativeFunction<(ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32) → ffi::Int32>>(3735928559);
3846
final (core::int, core::int, core::int, core::int, core::int) → core::int function = block {
3947
[@vm.inferred-type.metadata=dart.ffi::Pointer] synthesized ffi::Pointer<ffi::NativeFunction<(ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32, ffi::Int32) → ffi::Int32>> #ffiTarget2 = pointer;
48+
49+
[@vm.closure-id=1]
4050
@#C9
4151
function #ffiClosure2(core::int arg1, core::int arg2, core::int arg3, core::int arg4, core::int arg5) → core::int {
4252
_in::_nativeEffect(arg1);
@@ -47,7 +57,7 @@ static method testLeaf5Args() → dynamic {
4757
return [@vm.inferred-type.metadata=int] ffi::_ffiCall<core::int>(#ffiTarget2);
4858
}
4959
} =>#ffiClosure2;
50-
return [@vm.inferred-type.metadata=int (receiver not int)] function(1, 2, 3, 4, 5){(core::int, core::int, core::int, core::int, core::int) → core::int};
60+
return [@vm.direct-call.metadata=closure 1 in #lib::testLeaf5Args] [@vm.inferred-type.metadata=int (receiver not int)] function(1, 2, 3, 4, 5){(core::int, core::int, core::int, core::int, core::int) → core::int};
5161
}
5262
static method main() → void {
5363
self::testVoidNoArg();

pkg/vm/testcases/transformations/ffi/finalizable_late_initializer.dart.aot.expect

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,22 @@ class Foo extends core::Object implements ffi::Finalizable {
1111
: super core::Object::•()
1212
;
1313
}
14+
15+
[@vm.closure-id=3]
1416
static method main() → void {
1517
[@vm.inferred-type.metadata=#lib::Foo?] self::Foo? :foo:finalizableValue;
18+
19+
[@vm.closure-id=1]
1620
function #foo#initializer() → self::Foo
1721
return :foo:finalizableValue = new self::Foo::•();
1822
[@vm.inferred-type.metadata=#lib::Foo] late final self::Foo foo = [@vm.inferred-type.metadata=#lib::Foo (receiver not int)] #foo#initializer(){() → self::Foo};
19-
[@vm.inferred-type.metadata=!? (receiver not int)](() → Null {
23+
[@vm.direct-call.metadata=closure 2 in #lib::main] [@vm.inferred-type.metadata=!? (receiver not int)]([@vm.closure-id=2]() → Null {
2024
core::print(foo);
2125
_in::reachabilityFence(:foo:finalizableValue);
2226
})(){() → Null};
2327
[@vm.inferred-type.metadata=#lib::Foo?] self::Foo? :foo2:finalizableValue;
28+
29+
[@vm.closure-id=3]
2430
function #foo2#initializer() → self::Foo
2531
return :foo2:finalizableValue = new self::Foo::•();
2632
late final self::Foo foo2 = [@vm.inferred-type.metadata=#lib::Foo (receiver not int)] #foo2#initializer(){() → self::Foo};

pkg/vm/testcases/transformations/ffi/native_callable.dart.aot.expect

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ static method printInt(core::int i) → void
2121
static method testNativeCallableListener() → void {
2222
final ffi::NativeCallable<(ffi::Int32) → ffi::Void> callback = block {
2323
final ffi::NativeCallable<(ffi::Int32) → ffi::Void> #t1 = new ffi::_NativeCallableListener::•<(ffi::Int32) → ffi::Void>((final core::List<dynamic> args) → void
24-
[@vm.inferred-type.metadata=!? (receiver not int)] #C1(args.{core::List::[]}(0){(core::int) → dynamic}){(ffi::Int32) → ffi::Void};
24+
[@vm.direct-call.metadata=closure 0 in #lib::printInt] [@vm.inferred-type.metadata=!? (receiver not int)] #C1(args.{core::List::[]}(0){(core::int) → dynamic}){(ffi::Int32) → ffi::Void};
2525
, "NativeCallable(ConstantExpression(printInt))");
2626
[@vm.call-site-attributes.metadata=receiverType:dart.ffi::NativeCallable<dart.ffi::Void Function(dart.ffi::Int32)>] [@vm.direct-call.metadata=dart.ffi::_NativeCallableBase._pointer] #t1.{ffi::_NativeCallableBase::_pointer} = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::_createNativeCallableListener<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>(ffi::_nativeAsyncCallbackFunction<(ffi::Int32) → ffi::Void>(), [@vm.direct-call.metadata=dart.ffi::_NativeCallableListener._port] [@vm.inferred-type.metadata=dart.isolate::_RawReceivePort] #t1.{ffi::_NativeCallableListener::_port}{iso::RawReceivePort});
2727
} =>#t1;
@@ -38,7 +38,7 @@ static method testNativeCallableListenerClosure() → void {
3838
return [@vm.inferred-type.metadata=dart.core::Null? (value: null)] core::print([@vm.direct-call.metadata=dart.core::_IntegerImplementation.+] [@vm.inferred-type.metadata=int (skip check)] i.{core::num::+}(j){(core::num) → core::int});
3939
final ffi::NativeCallable<(ffi::Int32) → ffi::Void> callback = block {
4040
final ffi::NativeCallable<(ffi::Int32) → ffi::Void> #t2 = new ffi::_NativeCallableListener::•<(ffi::Int32) → ffi::Void>([@vm.closure-id=2](final core::List<dynamic> args) → void
41-
[@vm.inferred-type.metadata=!? (receiver not int)] closure(args.{core::List::[]}(0){(core::int) → dynamic}){(ffi::Int32) → ffi::Void};
41+
[@vm.direct-call.metadata=closure 1 in #lib::testNativeCallableListenerClosure] [@vm.inferred-type.metadata=!? (receiver not int)] closure(args.{core::List::[]}(0){(core::int) → dynamic}){(ffi::Int32) → ffi::Void};
4242
, "NativeCallable(VariableGetImpl(closure))");
4343
[@vm.call-site-attributes.metadata=receiverType:dart.ffi::NativeCallable<dart.ffi::Void Function(dart.ffi::Int32)>] [@vm.direct-call.metadata=dart.ffi::_NativeCallableBase._pointer] #t2.{ffi::_NativeCallableBase::_pointer} = [@vm.inferred-type.metadata=dart.ffi::Pointer] ffi::_createNativeCallableListener<ffi::NativeFunction<(ffi::Int32) → ffi::Void>>(ffi::_nativeAsyncCallbackFunction<(ffi::Int32) → ffi::Void>(), [@vm.direct-call.metadata=dart.ffi::_NativeCallableListener._port] [@vm.inferred-type.metadata=dart.isolate::_RawReceivePort] #t2.{ffi::_NativeCallableListener::_port}{iso::RawReceivePort});
4444
} =>#t2;

0 commit comments

Comments
 (0)