From bacbbf83c14ebba7d1ebc972aa5fdfbc3fe21a2c Mon Sep 17 00:00:00 2001 From: MarkZ Date: Tue, 26 Nov 2024 19:33:37 +0000 Subject: [PATCH] [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 Reviewed-by: Nicholas Shahan Commit-Queue: Mark Zhou --- .../lib/js/ddc/ddc_module_loader.js | 6 +++ .../lib/src/kernel/compiler_new.dart | 18 +++++++++ .../lib/src/kernel/type_recipe_generator.dart | 7 ++++ sdk/lib/_internal/js_shared/lib/rti.dart | 38 +++++++++++++++++++ .../main.0.dart | 6 ++- .../main.1.dart | 20 ++++------ 6 files changed, 80 insertions(+), 15 deletions(-) diff --git a/pkg/dev_compiler/lib/js/ddc/ddc_module_loader.js b/pkg/dev_compiler/lib/js/ddc/ddc_module_loader.js index c219f4ef8f1..09b5c8f7d34 100644 --- a/pkg/dev_compiler/lib/js/ddc/ddc_module_loader.js +++ b/pkg/dev_compiler/lib/js/ddc/ddc_module_loader.js @@ -1560,6 +1560,12 @@ if (!self.deferred_loader) { * application. */ hotReloadEnd() { + // Clear RTI subtype caches before initializing libraries. + // These needs to be done before hot reload completes (and any new + // libraries initialize) in case subtype hierarchies updated. + let dartRtiLibrary = this.importLibrary('dart:_rti'); + dartRtiLibrary.resetRtiSubtypeCaches(); + // On a hot reload, we reuse the existing library objects to ensure all // references remain valid and continue to be unique. We track in // `previouslyLoaded` which libraries already exist in the system, so we diff --git a/pkg/dev_compiler/lib/src/kernel/compiler_new.dart b/pkg/dev_compiler/lib/src/kernel/compiler_new.dart index f782234cacf..449bebefa2d 100644 --- a/pkg/dev_compiler/lib/src/kernel/compiler_new.dart +++ b/pkg/dev_compiler/lib/src/kernel/compiler_new.dart @@ -812,6 +812,24 @@ class LibraryCompiler extends ComputeOnceConstantVisitor ]).toStatement(); _typeRuleLinks.add(addRulesStatement); } + // Reset type rules for all classes with empty type hierarchies. These + // classes implicitly extend `Object` and may have been updated after a + // hot reload. This is unnecessary for types in `liveInterfaceTypeRules`, + // as `addRules` overrides the old rules. + // TODO(57049): Only do this after a hot reload. + var emptyInterfaceTypeRecipes = + Set.from(_typeRecipeGenerator.visitedInterfaceTypeRecipes) + ..removeAll(typeRules.keys); + if (emptyInterfaceTypeRecipes.isNotEmpty) { + var template = '#._Universe.#(#, JSON.parse(#))'; + var deleteRulesStatement = js.call(template, [ + _emitLibraryName(_rtiLibrary), + _emitMemberName('deleteRules', memberClass: universeClass), + _runtimeCall('typeUniverse'), + js.string(jsonEncode(emptyInterfaceTypeRecipes.toList()), "'") + ]).toStatement(); + _typeRuleLinks.add(deleteRulesStatement); + } // Update type rules for `LegacyJavaScriptObject` to add all interop // types in this module as a supertype. var updateRules = _typeRecipeGenerator.updateLegacyJavaScriptObjectRules; diff --git a/pkg/dev_compiler/lib/src/kernel/type_recipe_generator.dart b/pkg/dev_compiler/lib/src/kernel/type_recipe_generator.dart index 3f8cd6b1139..741ad0d28d2 100644 --- a/pkg/dev_compiler/lib/src/kernel/type_recipe_generator.dart +++ b/pkg/dev_compiler/lib/src/kernel/type_recipe_generator.dart @@ -71,6 +71,13 @@ class TypeRecipeGenerator { void addLiveTypeAncestries(InterfaceType type) => _recipeVisitor.addLiveTypeAncestries(type); + /// Returns all recipes for [InterfaceType]s that have appeared in type + /// recipes. + List get visitedInterfaceTypeRecipes => [ + for (var type in _recipeVisitor.visitedInterfaceTypes) + interfaceTypeRecipe(type.classNode) + ]; + /// Returns a mapping of type hierarchies for all [InterfaceType]s that have /// appeared in type recipes. /// diff --git a/sdk/lib/_internal/js_shared/lib/rti.dart b/sdk/lib/_internal/js_shared/lib/rti.dart index df28aad75ad..65152c760a5 100644 --- a/sdk/lib/_internal/js_shared/lib/rti.dart +++ b/sdk/lib/_internal/js_shared/lib/rti.dart @@ -202,6 +202,9 @@ class Rti { Object? _isSubtypeCache; + static Object? _getRawIsSubtypeCache(Rti rti) => rti._isSubtypeCache; + + /// Same as [_getRawIsSubtypeCache] but also initializes the cache. static Object? _getIsSubtypeCache(Rti rti) => rti._isSubtypeCache ??= JS('', 'new Map()'); @@ -567,6 +570,24 @@ Rti _rtiBind(Rti environment, Rti types) { return _Universe.bind(_theUniverse(), environment, types); } +/// Resets subtype caches on all evaluated RTIs. +/// +/// This operation is used when subtyping relationships change in a running +/// app (such as after a hot reload). +void resetRtiSubtypeCaches() { + var universe = _theUniverse(); + var cache = _Universe.evalCache(universe); + var values = _Utils.mapValues(cache); + var length = _Utils.arrayLength(values); + for (int i = 0; i < length; i++) { + Rti rti = _Utils.asRti(_Utils.arrayAt(values, i)); + var sCache = Rti._getRawIsSubtypeCache(rti); + if (sCache != null) { + _Utils.mapClear(sCache); + } + } +} + /// Evaluate a ground-term type. /// Called from generated code. Rti findType(String recipe) { @@ -2309,6 +2330,15 @@ class _Universe { static void addRules(Object? universe, Object? rules) => _Utils.objectAssign(typeRules(universe), rules); + static void deleteRules(Object? universe, Object? types) { + var universeTypeRules = typeRules(universe); + var typeCount = _Utils.arrayLength(types); + for (int i = 0; i < typeCount; i++) { + var type = _Utils.asString(_Utils.arrayAt(types, i)); + _Utils.objectDelete(universeTypeRules, type); + } + } + /// Adds or updates existing type rules in the type [universe]. /// /// This update is intended to add new rules to the set of rules that exist @@ -4344,6 +4374,9 @@ class _Utils { } } + static void objectDelete(Object? o, Object? property) => + JS('', 'delete #[#]', o, property); + static Object? newArrayOrEmpty(int length) => length > 0 ? JS('', 'new Array(#)', length) @@ -4380,6 +4413,11 @@ class _Utils { static bool stringLessThan(String s1, String s2) => JS('bool', '# < #', s1, s2); + static JSArray mapValues(Object? map) => + JS('JSArray', 'Array.from(#.values())', map); + + static void mapClear(Object? map) => JS('', '#.clear()', map); + static Object? mapGet(Object? cache, Object? key) => JS('', '#.get(#)', cache, key); diff --git a/tests/hot_reload/existing_field_changes_type_indirect_function/main.0.dart b/tests/hot_reload/existing_field_changes_type_indirect_function/main.0.dart index ca9c35cc64d..a72c787e5fb 100644 --- a/tests/hot_reload/existing_field_changes_type_indirect_function/main.0.dart +++ b/tests/hot_reload/existing_field_changes_type_indirect_function/main.0.dart @@ -33,7 +33,9 @@ Future main() async { await hotReload(); // B is no longer a subtype of A. - Expect.equals( - "type '(A) => bool' is not a subtype of type '(B) => bool'", helper()); + Expect.throws( + () => helper(), + (error) => '$error'.contains( + "type '(A) => bool' is not a subtype of type '(B) => bool'")); Expect.equals(1, hotReloadGeneration); } diff --git a/tests/hot_reload/existing_field_changes_type_indirect_function/main.1.dart b/tests/hot_reload/existing_field_changes_type_indirect_function/main.1.dart index d3250f854f3..1f91ea56db7 100644 --- a/tests/hot_reload/existing_field_changes_type_indirect_function/main.1.dart +++ b/tests/hot_reload/existing_field_changes_type_indirect_function/main.1.dart @@ -22,11 +22,7 @@ class Foo { late Foo value; helper() { - try { - return value.x.toString(); - } catch (e) { - return e.toString(); - } + return value.x.toString(); } Future main() async { @@ -36,8 +32,10 @@ Future main() async { await hotReload(); // B is no longer a subtype of A. - Expect.equals( - "type '(A) => bool' is not a subtype of type '(B) => bool'", helper()); + Expect.throws( + () => helper(), + (error) => '$error'.contains( + "type '(A) => bool' is not a subtype of type '(B) => bool'")); Expect.equals(1, hotReloadGeneration); } /** DIFF **/ @@ -51,17 +49,13 @@ Future main() async { typedef bool Predicate(B b); -@@ -22,8 +22,11 @@ +@@ -22,8 +22,7 @@ late Foo value; helper() { - value = Foo((A a) => true); - return 'okay'; -+ try { -+ return value.x.toString(); -+ } catch (e) { -+ return e.toString(); -+ } ++ return value.x.toString(); } Future main() async {