From fd18bbb4a3008da1924a847cf2168adea0f3f866 Mon Sep 17 00:00:00 2001 From: Alexander Markov Date: Thu, 5 Mar 2026 15:51:29 -0800 Subject: [PATCH] [vm,modular_aot] Dynamic calls TEST=ci Issue: https://github.com/dart-lang/sdk/issues/61635 Change-Id: I13eaccbefae4917500ee3aeb6a09d71f480a4c94 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/485560 Reviewed-by: Slava Egorov --- .../lib/back_end/arm64/code_generator.dart | 32 ++++++++-- .../lib/back_end/object_pool.dart | 52 ++++++++++------ pkg/native_compiler/lib/runtime/names.dart | 61 +++++++++++++++++++ .../lib/snapshot/snapshot.dart | 36 +++-------- runtime/vm/module_snapshot.cc | 4 +- runtime/vm/object.cc | 30 +++++++-- 6 files changed, 156 insertions(+), 59 deletions(-) create mode 100644 pkg/native_compiler/lib/runtime/names.dart diff --git a/pkg/native_compiler/lib/back_end/arm64/code_generator.dart b/pkg/native_compiler/lib/back_end/arm64/code_generator.dart index a4e56978335..695b835ed2c 100644 --- a/pkg/native_compiler/lib/back_end/arm64/code_generator.dart +++ b/pkg/native_compiler/lib/back_end/arm64/code_generator.dart @@ -529,13 +529,37 @@ final class Arm64CodeGenerator extends CodeGenerator { } @override - void visitClosureCall(ClosureCall instr) { - _asm.unimplemented('Unimplemented: code generation for ClosureCall'); + void visitDynamicCall(DynamicCall instr) { + _passArguments(instr); + _asm.loadFromPool(argumentsDescriptorReg, instr.argumentsShape); + _asm.loadFromPool(R6, graph.function); + _asm.ldr( + R0, + _asm.address( + stackPointerReg, + (instr.inputCount - 1 - (instr.hasTypeArguments ? 1 : 0)) * wordSize, + ), + ); + _asm.loadPairFromPool( + inlineCacheDataReg, + codeReg, + DynamicCallEntry( + graph.function, + instr.argumentsShape, + instr.kind, + instr.selector, + ), + ); + _asm.ldr( + tempReg, + _asm.fieldAddress(codeReg, vmOffsets.Code_entry_point_offset.first), + ); + _asm.blr(tempReg); } @override - void visitDynamicCall(DynamicCall instr) { - _asm.unimplemented('Unimplemented: code generation for DynamicCall'); + void visitClosureCall(ClosureCall instr) { + _asm.unimplemented('Unimplemented: code generation for ClosureCall'); } @override diff --git a/pkg/native_compiler/lib/back_end/object_pool.dart b/pkg/native_compiler/lib/back_end/object_pool.dart index 6f32073f98c..faad2bba268 100644 --- a/pkg/native_compiler/lib/back_end/object_pool.dart +++ b/pkg/native_compiler/lib/back_end/object_pool.dart @@ -4,7 +4,10 @@ import 'package:cfg/ir/field.dart'; import 'package:cfg/ir/functions.dart'; +import 'package:cfg/ir/instructions.dart'; +import 'package:cfg/utils/misc.dart'; import 'package:kernel/ast.dart' as ast; +import 'package:native_compiler/runtime/names.dart'; /// Helper class for building object pool accessible from generated code. class ObjectPool { @@ -57,35 +60,44 @@ final class NewObjectTags extends SpecializedEntry { other is NewObjectTags && this.cls == other.cls; } -/// InterfaceCall object pool entry occupies 2 slots: dispatcher data, dispatcher code. -final class InterfaceCallEntry extends PairSpecializedEntry { - final CFunction owner; // TODO: remove, only needed for ICData. +/// ICData call object pool entries occupies 2 slots: ICData, dispatcher code. +sealed class ICDataCallEntry extends PairSpecializedEntry { + final CFunction owner; final ArgumentsShape argumentsShape; - final CFunction interfaceTarget; + final Name selector; - InterfaceCallEntry(this.owner, this.argumentsShape, this.interfaceTarget); - - /// Returns selector name corresponding to interface call - /// in the VM convention (with get: and set: prefixes), - /// but without a library key (`@nnnn`). - String get selectorName { - final simpleName = interfaceTarget.member.name.text; - return switch (interfaceTarget) { - GetterFunction() => 'get:$simpleName', - SetterFunction() => 'set:$simpleName', - _ => simpleName, - }; - } + ICDataCallEntry(this.owner, this.argumentsShape, {required this.selector}); @override - int get hashCode => interfaceTarget.hashCode + 23; + int get hashCode => + finalizeHash(combineHash(selector.hashCode, argumentsShape.hashCode)); @override bool operator ==(Object other) => - other is InterfaceCallEntry && + other is ICDataCallEntry && this.owner == other.owner && this.argumentsShape == other.argumentsShape && - this.interfaceTarget == other.interfaceTarget; + this.selector == other.selector; +} + +/// InterfaceCall object pool entry occupies 2 slots: dispatcher data, dispatcher code. +/// TODO: switch from ICData calls to dispatch table calls. +final class InterfaceCallEntry extends ICDataCallEntry { + InterfaceCallEntry( + super.owner, + super.argumentsShape, + CFunction interfaceTarget, + ) : super(selector: Name.interfaceCallSelector(interfaceTarget)); +} + +/// DynamicCall object pool entry occupies 2 slots: ICData, dispatcher code. +final class DynamicCallEntry extends ICDataCallEntry { + DynamicCallEntry( + super.owner, + super.argumentsShape, + DynamicCallKind kind, + ast.Name selector, + ) : super(selector: Name.dynamicCallSelector(kind, selector)); } /// Reserved entry, filled from a preceeding [SpecializedEntry] diff --git a/pkg/native_compiler/lib/runtime/names.dart b/pkg/native_compiler/lib/runtime/names.dart new file mode 100644 index 00000000000..6de0bbefa86 --- /dev/null +++ b/pkg/native_compiler/lib/runtime/names.dart @@ -0,0 +1,61 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cfg/ir/functions.dart'; +import 'package:cfg/ir/instructions.dart'; +import 'package:cfg/utils/misc.dart'; +import 'package:kernel/ast.dart' as ast; + +/// Prefix for the getter names. +const String getterPrefix = 'get:'; + +/// Prefix for the setter names. +const String setterPrefix = 'set:'; + +/// Prefix for the selectors in the dynamic calls. +const String dynamicPrefix = 'dyn:'; + +/// Identifier in the Dart program, in the VM conventions. +/// +/// Public names are represented with [String] objects, while +/// private names are represented with [PrivateName] objects. +extension type Name._(Object /*String|PrivateName*/ raw) implements Object { + /// If [library] is not null, create a private name. + /// Otherwise, create a public name. + factory Name(String text, ast.Library? library) => + Name._((library != null) ? PrivateName(text, library) : text); + + factory Name.interfaceCallSelector(CFunction interfaceTarget) { + final simpleName = interfaceTarget.member.name.text; + return Name(switch (interfaceTarget) { + GetterFunction() => '$getterPrefix$simpleName', + SetterFunction() => '$setterPrefix$simpleName', + _ => simpleName, + }, interfaceTarget.member.name.library); + } + + factory Name.dynamicCallSelector(DynamicCallKind kind, ast.Name selector) { + return Name(switch (kind) { + .method => '$dynamicPrefix${selector.text}', + .getter => '$dynamicPrefix$getterPrefix${selector.text}', + .setter => '$dynamicPrefix$setterPrefix${selector.text}', + }, selector.library); + } +} + +/// Private name in a [library]. +/// VM mangles such names with a library key (`@nnnn`). +final class PrivateName { + final String text; + final ast.Library library; + PrivateName(this.text, this.library); + + @override + bool operator ==(Object other) => + other is PrivateName && text == other.text && library == other.library; + + @override + int get hashCode => + finalizeHash(combineHash(text.hashCode, library.hashCode)); +} diff --git a/pkg/native_compiler/lib/snapshot/snapshot.dart b/pkg/native_compiler/lib/snapshot/snapshot.dart index 065638cd880..b2a0e254e61 100644 --- a/pkg/native_compiler/lib/snapshot/snapshot.dart +++ b/pkg/native_compiler/lib/snapshot/snapshot.dart @@ -18,6 +18,7 @@ import 'package:kernel/type_environment.dart' import 'package:native_compiler/back_end/code.dart'; import 'package:native_compiler/back_end/object_pool.dart'; import 'package:native_compiler/configuration.dart'; +import 'package:native_compiler/runtime/names.dart'; import 'package:native_compiler/runtime/object_layout.dart'; import 'package:native_compiler/runtime/type_utils.dart'; @@ -104,6 +105,7 @@ enum ObjectPoolEntryKind { newObjectTags, staticFieldOffset, interfaceCall, + dynamicCall, } abstract base class SerializationCluster { @@ -710,27 +712,6 @@ final class TwoByteStringSerializationCluster extends SerializationCluster { } } -extension type Name._(Object raw) implements Object { - factory Name(String text, ast.Library? library) => - Name._((library != null) ? PrivateName(text, library) : text); -} - -/// Private name in a [library]. -/// VM mangles such names with a library key (`@nnnn`). -final class PrivateName { - final String text; - final ast.Library library; - PrivateName(this.text, this.library); - - @override - bool operator ==(Object other) => - other is PrivateName && text == other.text && library == other.library; - - @override - int get hashCode => - finalizeHash(combineHash(text.hashCode, library.hashCode)); -} - final class PrivateNameSerializationCluster extends SerializationCluster { final List _objects = []; @@ -1288,7 +1269,7 @@ final class ICDataSerializationCluster extends SerializationCluster { final class ObjectPoolSerializationCluster extends SerializationCluster { final List _objects = []; - final Map icDatas = {}; + final Map icDatas = {}; @override void trace(SnapshotSerializer serializer, Object object) { @@ -1301,15 +1282,11 @@ final class ObjectPoolSerializationCluster extends SerializationCluster { serializer.push(entry.cls); case StaticFieldOffset(): serializer.push(entry.field); - case InterfaceCallEntry(): - // TODO: call through monomorphic/table dispatcher. + case ICDataCallEntry(): final icData = icDatas[entry] = ICData( entry.owner, entry.argumentsShape, - Name( - entry.selectorName, - entry.interfaceTarget.member.name.library, - ), + entry.selector, ); serializer.push(icData); case ReservedEntry(): @@ -1351,6 +1328,9 @@ final class ObjectPoolSerializationCluster extends SerializationCluster { case InterfaceCallEntry(): serializer.writeUint(ObjectPoolEntryKind.interfaceCall.index); serializer.writeRefId(icDatas[entry]); + case DynamicCallEntry(): + serializer.writeUint(ObjectPoolEntryKind.dynamicCall.index); + serializer.writeRefId(icDatas[entry]); case ReservedEntry(): } } else { diff --git a/runtime/vm/module_snapshot.cc b/runtime/vm/module_snapshot.cc index a2b9cfffd2b..427d3c8cfa9 100644 --- a/runtime/vm/module_snapshot.cc +++ b/runtime/vm/module_snapshot.cc @@ -99,6 +99,7 @@ class ModuleSnapshot : public AllStatic { kNewObjectTags, kStaticFieldOffset, kInterfaceCall, + kDynamicCall, }; }; @@ -1183,7 +1184,8 @@ class ObjectPoolDeserializationCluster : public DeserializationCluster { Smi::Value(field->untag()->host_offset_or_field_id())); break; } - case ModuleSnapshot::kInterfaceCall: { + case ModuleSnapshot::kInterfaceCall: + case ModuleSnapshot::kDynamicCall: { pool->untag()->entry_bits()[j] = tagged_entry_bits; UntaggedObjectPool::Entry& entry = pool->untag()->data()[j]; entry.raw_obj_ = d.ReadRef(); diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc index 20d0660ff52..9ab8c98e954 100644 --- a/runtime/vm/object.cc +++ b/runtime/vm/object.cc @@ -14611,12 +14611,30 @@ void Library::CopyPragmas(const Library& old_lib) { #endif // !defined(DART_PRECOMPILED_RUNTIME) static bool ShouldBePrivate(const String& name) { - return (name.Length() >= 1 && name.CharAt(0) == '_') || - (name.Length() >= 5 && - (name.CharAt(4) == '_' && - (name.CharAt(0) == 'g' || name.CharAt(0) == 's') && - name.CharAt(1) == 'e' && name.CharAt(2) == 't' && - name.CharAt(3) == ':')); + // _foo + if (name.Length() >= 1 && name.CharAt(0) == '_') { + return true; + } + // get:_foo, set:_foo or dyn:_foo + if (name.Length() >= 5 && name.CharAt(3) == ':' && name.CharAt(4) == '_') { + if ((name.CharAt(0) == 'g' || name.CharAt(0) == 's') && + name.CharAt(1) == 'e' && name.CharAt(2) == 't') { + return true; + } + if (name.CharAt(0) == 'd' && name.CharAt(1) == 'y' && + name.CharAt(2) == 'n') { + return true; + } + } + // dyn:get:_foo, dyn:set:_foo + if (name.Length() >= 9 && name.CharAt(0) == 'd' && name.CharAt(1) == 'y' && + name.CharAt(2) == 'n' && name.CharAt(3) == ':' && + (name.CharAt(4) == 'g' || name.CharAt(4) == 's') && + name.CharAt(5) == 'e' && name.CharAt(6) == 't' && name.CharAt(7) == ':' && + name.CharAt(8) == '_') { + return true; + } + return false; } void Library::RehashDictionary(const Array& old_dict,