[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 <sra@google.com>
Reviewed-by: Nicholas Shahan <nshahan@google.com>
Commit-Queue: Mark Zhou <markzipan@google.com>
This commit is contained in:
MarkZ
2024-11-26 19:33:37 +00:00
committed by Commit Queue
parent e2b7ec229d
commit bacbbf83c1
6 changed files with 80 additions and 15 deletions
@@ -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
@@ -812,6 +812,24 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
]).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;
@@ -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<String> 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.
///
+38
View File
@@ -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);
@@ -33,7 +33,9 @@ Future<void> 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<TypeError>(
() => helper(),
(error) => '$error'.contains(
"type '(A) => bool' is not a subtype of type '(B) => bool'"));
Expect.equals(1, hotReloadGeneration);
}
@@ -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<void> main() async {
@@ -36,8 +32,10 @@ Future<void> 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<TypeError>(
() => 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<void> 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<void> main() async {