From efce7c1cb04d292589ff563c0531d055285e5927 Mon Sep 17 00:00:00 2001 From: Alexander Markov Date: Thu, 19 Mar 2026 06:12:41 -0700 Subject: [PATCH] [vm,dyn_modules] Fix single-target cid range dispatch for dynamically loaded classes In AOT mode, when call site is transitioned from monomorphic state (with receiver cid1) to polymorphic (with receiver cid2), there is an optimization which checks if all _allocated_ classes in the class id range cid1..cid2 have the same dispatch target. If so, a specialized SingleTargetCall stub is used. The problem is that 'allocated' bit is only set during precompilation, and dynamically loaded classes were not considered as valid receiver classes by this optimization. As a result, the following situation could happen: cid1 < cid3 < cid2, cid1 dispatches to target1 cid2 dispatches to target1 cid3 should dispatch to target2, but it is still in range cid1..cid2 and SingleTargetCall stub would incorrectly dispatch it to target1. The fix is to treat all dynamically loaded classes as allocated when checking for single target optimization. TEST=pkg/dynamic_modules/test/data/single_target_cid_range_dispatch Fixes b/493677699 Change-Id: I42e407385a5d9b0a4a1017f713b47587c3c6a818 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/488920 Commit-Queue: Alexander Markov Reviewed-by: Ryan Macnak Reviewed-by: Slava Egorov Reviewed-by: Sigmund Cherem --- .../dynamic_interface.yaml | 11 +++++++ .../main.dart | 33 +++++++++++++++++++ .../modules/entry1.dart | 21 ++++++++++++ .../shared/shared.dart | 17 ++++++++++ runtime/vm/runtime_entry.cc | 2 +- 5 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 pkg/dynamic_modules/test/data/single_target_cid_range_dispatch/dynamic_interface.yaml create mode 100644 pkg/dynamic_modules/test/data/single_target_cid_range_dispatch/main.dart create mode 100644 pkg/dynamic_modules/test/data/single_target_cid_range_dispatch/modules/entry1.dart create mode 100644 pkg/dynamic_modules/test/data/single_target_cid_range_dispatch/shared/shared.dart diff --git a/pkg/dynamic_modules/test/data/single_target_cid_range_dispatch/dynamic_interface.yaml b/pkg/dynamic_modules/test/data/single_target_cid_range_dispatch/dynamic_interface.yaml new file mode 100644 index 00000000000..3a5b11462b8 --- /dev/null +++ b/pkg/dynamic_modules/test/data/single_target_cid_range_dispatch/dynamic_interface.yaml @@ -0,0 +1,11 @@ +# 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. + +callable: + - library: 'dart:core' + - library: 'shared/shared.dart' + +extendable: + - library: 'shared/shared.dart' + class: ['Base1', 'Base2'] diff --git a/pkg/dynamic_modules/test/data/single_target_cid_range_dispatch/main.dart b/pkg/dynamic_modules/test/data/single_target_cid_range_dispatch/main.dart new file mode 100644 index 00000000000..568136f1bdb --- /dev/null +++ b/pkg/dynamic_modules/test/data/single_target_cid_range_dispatch/main.dart @@ -0,0 +1,33 @@ +// 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 '../../common/testing.dart' as helper; +import 'package:expect/expect.dart'; + +import 'shared/shared.dart'; + +@pragma('vm:never-inline') +@pragma('wasm:never-inline') +String callCreateElement(Base obj) { + // Call site which dispatches to different dynamic objects. + return obj.createElement(); +} + +/// Regression test for b/493677699. +/// Verifies that single target cid range dispatching works properly +/// for dynamically loaded classes. +void main() async { + final createObj = (await helper.load('entry1.dart')) as Base Function(int); + + // Transition call site from unlinked to monomorphic. + Expect.equals('Element1', callCreateElement(createObj(1))); + // Transition call site from monomorphic to a single target cid range + // (if a dynamically loaded class C2 is not properly accounted). + Expect.equals('Element1', callCreateElement(createObj(3))); + // Class id of C2 is within [C1..C3] range, so single target dispatch + // would result in the incorrect dispatch target. + Expect.equals('Element2', callCreateElement(createObj(2))); + + helper.done(); +} diff --git a/pkg/dynamic_modules/test/data/single_target_cid_range_dispatch/modules/entry1.dart b/pkg/dynamic_modules/test/data/single_target_cid_range_dispatch/modules/entry1.dart new file mode 100644 index 00000000000..f4623a4308a --- /dev/null +++ b/pkg/dynamic_modules/test/data/single_target_cid_range_dispatch/modules/entry1.dart @@ -0,0 +1,21 @@ +// 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 '../shared/shared.dart'; + +class C1 extends Base1 {} + +class C2 extends Base2 {} + +class C3 extends Base1 {} + +@pragma('dyn-module:entry-point') +Object? dynamicModuleEntrypoint() { + return (int arg) => switch (arg) { + 1 => C1(), + 2 => C2(), + 3 => C3(), + _ => throw 'Unexpected $arg', + }; +} diff --git a/pkg/dynamic_modules/test/data/single_target_cid_range_dispatch/shared/shared.dart b/pkg/dynamic_modules/test/data/single_target_cid_range_dispatch/shared/shared.dart new file mode 100644 index 00000000000..aa82a3359e7 --- /dev/null +++ b/pkg/dynamic_modules/test/data/single_target_cid_range_dispatch/shared/shared.dart @@ -0,0 +1,17 @@ +// 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. + +abstract class Base { + String createElement(); +} + +class Base1 extends Base { + @override + String createElement() => 'Element1'; +} + +class Base2 extends Base { + @override + String createElement() => 'Element2'; +} diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc index b10d001dbdf..3b28cc75593 100644 --- a/runtime/vm/runtime_entry.cc +++ b/runtime/vm/runtime_entry.cc @@ -2559,7 +2559,7 @@ static bool IsSingleTarget(IsolateGroup* isolate_group, if (!table->HasValidClassAt(cid)) continue; cls = table->At(cid); if (cls.is_abstract()) continue; - if (!cls.is_allocated()) continue; + if (!cls.is_allocated() && !cls.is_declared_in_bytecode()) continue; other_target = Resolver::ResolveDynamicAnyArgs(zone, cls, name, /*allow_add=*/false); if (other_target.ptr() != target.ptr()) {