[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 <alexmarkov@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Reviewed-by: Slava Egorov <vegorov@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
This commit is contained in:
Alexander Markov
2026-03-19 06:12:41 -07:00
committed by Commit Queue
parent aa3665ed78
commit efce7c1cb0
5 changed files with 83 additions and 1 deletions
@@ -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']
@@ -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();
}
@@ -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',
};
}
@@ -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';
}
+1 -1
View File
@@ -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()) {