Skip to content

Commit bacbbf8

Browse files
MarkzipanCommit Queue
authored andcommitted
[ddc] Adding RTI subtype cache clearing.
This is required when a hot reload causes changes to the subtype hierarchy. This change also adds RTI operations for clearing subtype caches and deleting type rules. The DDC Embedder also now accesses the RTI library to clear subtype caches on hot reload. See: #57049 Change-Id: I50a43ce342f23060bc28a3654c2da37c362492b7 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/394040 Reviewed-by: Stephen Adams <[email protected]> Reviewed-by: Nicholas Shahan <[email protected]> Commit-Queue: Mark Zhou <[email protected]>
1 parent e2b7ec2 commit bacbbf8

File tree

6 files changed

+80
-15
lines changed

6 files changed

+80
-15
lines changed

pkg/dev_compiler/lib/js/ddc/ddc_module_loader.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1560,6 +1560,12 @@ if (!self.deferred_loader) {
15601560
* application.
15611561
*/
15621562
hotReloadEnd() {
1563+
// Clear RTI subtype caches before initializing libraries.
1564+
// These needs to be done before hot reload completes (and any new
1565+
// libraries initialize) in case subtype hierarchies updated.
1566+
let dartRtiLibrary = this.importLibrary('dart:_rti');
1567+
dartRtiLibrary.resetRtiSubtypeCaches();
1568+
15631569
// On a hot reload, we reuse the existing library objects to ensure all
15641570
// references remain valid and continue to be unique. We track in
15651571
// `previouslyLoaded` which libraries already exist in the system, so we

pkg/dev_compiler/lib/src/kernel/compiler_new.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,24 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
812812
]).toStatement();
813813
_typeRuleLinks.add(addRulesStatement);
814814
}
815+
// Reset type rules for all classes with empty type hierarchies. These
816+
// classes implicitly extend `Object` and may have been updated after a
817+
// hot reload. This is unnecessary for types in `liveInterfaceTypeRules`,
818+
// as `addRules` overrides the old rules.
819+
// TODO(57049): Only do this after a hot reload.
820+
var emptyInterfaceTypeRecipes =
821+
Set.from(_typeRecipeGenerator.visitedInterfaceTypeRecipes)
822+
..removeAll(typeRules.keys);
823+
if (emptyInterfaceTypeRecipes.isNotEmpty) {
824+
var template = '#._Universe.#(#, JSON.parse(#))';
825+
var deleteRulesStatement = js.call(template, [
826+
_emitLibraryName(_rtiLibrary),
827+
_emitMemberName('deleteRules', memberClass: universeClass),
828+
_runtimeCall('typeUniverse'),
829+
js.string(jsonEncode(emptyInterfaceTypeRecipes.toList()), "'")
830+
]).toStatement();
831+
_typeRuleLinks.add(deleteRulesStatement);
832+
}
815833
// Update type rules for `LegacyJavaScriptObject` to add all interop
816834
// types in this module as a supertype.
817835
var updateRules = _typeRecipeGenerator.updateLegacyJavaScriptObjectRules;

pkg/dev_compiler/lib/src/kernel/type_recipe_generator.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ class TypeRecipeGenerator {
7171
void addLiveTypeAncestries(InterfaceType type) =>
7272
_recipeVisitor.addLiveTypeAncestries(type);
7373

74+
/// Returns all recipes for [InterfaceType]s that have appeared in type
75+
/// recipes.
76+
List<String> get visitedInterfaceTypeRecipes => [
77+
for (var type in _recipeVisitor.visitedInterfaceTypes)
78+
interfaceTypeRecipe(type.classNode)
79+
];
80+
7481
/// Returns a mapping of type hierarchies for all [InterfaceType]s that have
7582
/// appeared in type recipes.
7683
///

sdk/lib/_internal/js_shared/lib/rti.dart

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@ class Rti {
202202

203203
Object? _isSubtypeCache;
204204

205+
static Object? _getRawIsSubtypeCache(Rti rti) => rti._isSubtypeCache;
206+
207+
/// Same as [_getRawIsSubtypeCache] but also initializes the cache.
205208
static Object? _getIsSubtypeCache(Rti rti) =>
206209
rti._isSubtypeCache ??= JS('', 'new Map()');
207210

@@ -567,6 +570,24 @@ Rti _rtiBind(Rti environment, Rti types) {
567570
return _Universe.bind(_theUniverse(), environment, types);
568571
}
569572

573+
/// Resets subtype caches on all evaluated RTIs.
574+
///
575+
/// This operation is used when subtyping relationships change in a running
576+
/// app (such as after a hot reload).
577+
void resetRtiSubtypeCaches() {
578+
var universe = _theUniverse();
579+
var cache = _Universe.evalCache(universe);
580+
var values = _Utils.mapValues(cache);
581+
var length = _Utils.arrayLength(values);
582+
for (int i = 0; i < length; i++) {
583+
Rti rti = _Utils.asRti(_Utils.arrayAt(values, i));
584+
var sCache = Rti._getRawIsSubtypeCache(rti);
585+
if (sCache != null) {
586+
_Utils.mapClear(sCache);
587+
}
588+
}
589+
}
590+
570591
/// Evaluate a ground-term type.
571592
/// Called from generated code.
572593
Rti findType(String recipe) {
@@ -2309,6 +2330,15 @@ class _Universe {
23092330
static void addRules(Object? universe, Object? rules) =>
23102331
_Utils.objectAssign(typeRules(universe), rules);
23112332

2333+
static void deleteRules(Object? universe, Object? types) {
2334+
var universeTypeRules = typeRules(universe);
2335+
var typeCount = _Utils.arrayLength(types);
2336+
for (int i = 0; i < typeCount; i++) {
2337+
var type = _Utils.asString(_Utils.arrayAt(types, i));
2338+
_Utils.objectDelete(universeTypeRules, type);
2339+
}
2340+
}
2341+
23122342
/// Adds or updates existing type rules in the type [universe].
23132343
///
23142344
/// This update is intended to add new rules to the set of rules that exist
@@ -4344,6 +4374,9 @@ class _Utils {
43444374
}
43454375
}
43464376

4377+
static void objectDelete(Object? o, Object? property) =>
4378+
JS('', 'delete #[#]', o, property);
4379+
43474380
static Object? newArrayOrEmpty(int length) =>
43484381
length > 0
43494382
? JS('', 'new Array(#)', length)
@@ -4380,6 +4413,11 @@ class _Utils {
43804413
static bool stringLessThan(String s1, String s2) =>
43814414
JS('bool', '# < #', s1, s2);
43824415

4416+
static JSArray mapValues(Object? map) =>
4417+
JS('JSArray', 'Array.from(#.values())', map);
4418+
4419+
static void mapClear(Object? map) => JS('', '#.clear()', map);
4420+
43834421
static Object? mapGet(Object? cache, Object? key) =>
43844422
JS('', '#.get(#)', cache, key);
43854423

tests/hot_reload/existing_field_changes_type_indirect_function/main.0.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ Future<void> main() async {
3333
await hotReload();
3434

3535
// B is no longer a subtype of A.
36-
Expect.equals(
37-
"type '(A) => bool' is not a subtype of type '(B) => bool'", helper());
36+
Expect.throws<TypeError>(
37+
() => helper(),
38+
(error) => '$error'.contains(
39+
"type '(A) => bool' is not a subtype of type '(B) => bool'"));
3840
Expect.equals(1, hotReloadGeneration);
3941
}

tests/hot_reload/existing_field_changes_type_indirect_function/main.1.dart

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,7 @@ class Foo {
2222
late Foo value;
2323

2424
helper() {
25-
try {
26-
return value.x.toString();
27-
} catch (e) {
28-
return e.toString();
29-
}
25+
return value.x.toString();
3026
}
3127

3228
Future<void> main() async {
@@ -36,8 +32,10 @@ Future<void> main() async {
3632
await hotReload();
3733

3834
// B is no longer a subtype of A.
39-
Expect.equals(
40-
"type '(A) => bool' is not a subtype of type '(B) => bool'", helper());
35+
Expect.throws<TypeError>(
36+
() => helper(),
37+
(error) => '$error'.contains(
38+
"type '(A) => bool' is not a subtype of type '(B) => bool'"));
4139
Expect.equals(1, hotReloadGeneration);
4240
}
4341
/** DIFF **/
@@ -51,17 +49,13 @@ Future<void> main() async {
5149
5250
typedef bool Predicate(B b);
5351
54-
@@ -22,8 +22,11 @@
52+
@@ -22,8 +22,7 @@
5553
late Foo value;
5654
5755
helper() {
5856
- value = Foo((A a) => true);
5957
- return 'okay';
60-
+ try {
61-
+ return value.x.toString();
62-
+ } catch (e) {
63-
+ return e.toString();
64-
+ }
58+
+ return value.x.toString();
6559
}
6660
6761
Future<void> main() async {

0 commit comments

Comments
 (0)