Skip to content

Commit 39c6099

Browse files
nshahanCommit Queue
authored andcommitted
[ddc] Add initial hot reload metadata
* Demonstrates the ability to generate metadata when inspecting the last accepted and delta components. * Attach the metadata to the component right before compiling with DDC. * Use the metadata to avoid deoptimizations in the initial compile when they are only required to support a hot reload. * Deletion of all type rules for classes that extend Object in the initial compile. In the future this should be reduced further to only the classes that had a hierarchy change in a hot reload. * Type checks on the return value of getters used to represent fields. Change-Id: I2812b564bc3f4d72f005d4bd11fa55ec0eb394ad Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/404940 Commit-Queue: Nicholas Shahan <[email protected]> Reviewed-by: Jens Johansen <[email protected]> Reviewed-by: Nate Biggs <[email protected]>
1 parent e072cdb commit 39c6099

File tree

4 files changed

+142
-29
lines changed

4 files changed

+142
-29
lines changed

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

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import '../js_ast/source_map_printer.dart'
3636
import 'compiler.dart' as old;
3737
import 'constants.dart';
3838
import 'future_or_normalizer.dart';
39+
import 'hot_reload_delta_inspector.dart';
3940
import 'js_interop.dart';
4041
import 'js_typerep.dart';
4142
import 'kernel_helpers.dart';
@@ -128,6 +129,12 @@ class LibraryBundleCompiler implements old.Compiler {
128129
js_ast.Program emitModule(Component component) {
129130
assert(_options.emitLibraryBundle);
130131
_ticker?.logMs('Emitting library bundle');
132+
// When there is hot reload metadata, update the mappings to the nodes in
133+
// the current LibraryIndex before passing it on the library compilers.
134+
var repo = component.metadata[hotReloadLibraryMetadataTag]
135+
as HotReloadLibraryMetadataRepository?;
136+
repo?.mapToIndexedNodes(LibraryIndex.all(component));
137+
var metadata = repo?.mapping;
131138
var compiledLibraries = <js_ast.Program>[];
132139
for (var library in component.libraries) {
133140
var libraryCompiler = LibraryCompiler(
@@ -139,6 +146,7 @@ class LibraryBundleCompiler implements old.Compiler {
139146
coreTypes: _coreTypes,
140147
ticker: _ticker,
141148
symbolData: _symbolData,
149+
compileForHotReload: metadata?[library],
142150
);
143151
_libraryCompilers[library] = libraryCompiler;
144152
compiledLibraries.add(libraryCompiler.emitLibrary(library));
@@ -599,6 +607,9 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
599607
/// Supports verbose logging with a timer.
600608
Ticker? _ticker;
601609

610+
/// Whether the library is being compiled for a hot reload.
611+
bool _compileForHotReload;
612+
602613
factory LibraryCompiler(
603614
Component component,
604615
ClassHierarchy hierarchy,
@@ -608,6 +619,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
608619
CoreTypes? coreTypes,
609620
Ticker? ticker,
610621
required SymbolData symbolData,
622+
bool? compileForHotReload,
611623
}) {
612624
coreTypes ??= CoreTypes(component);
613625
var types = TypeEnvironment(coreTypes, hierarchy);
@@ -630,6 +642,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
630642
importToSummary,
631643
summaryToModule,
632644
symbolData,
645+
compileForHotReload ?? false,
633646
);
634647
}
635648

@@ -647,7 +660,8 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
647660
this._options,
648661
this._importToSummary,
649662
this._summaryToModule,
650-
this._symbolData)
663+
this._symbolData,
664+
this._compileForHotReload)
651665
: _jsArrayClass = sdk.getClass('dart:_interceptors', 'JSArray'),
652666
_privateSymbolClass = sdk.getClass('dart:_js_helper', 'PrivateSymbol'),
653667
_linkedHashMapImplClass = sdk.getClass('dart:_js_helper', 'LinkedMap'),
@@ -871,7 +885,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
871885
typeRulesExceptLegacyJavaScriptObject,
872886
rulesFunction: 'addRules'));
873887
}
874-
if (typesThatOnlyExtendObject.isNotEmpty) {
888+
if (_compileForHotReload && typesThatOnlyExtendObject.isNotEmpty) {
875889
_typeRuleLinks.add(emitRulesStatement(typesThatOnlyExtendObject.toList(),
876890
rulesFunction: 'deleteRules'));
877891
}
@@ -2857,10 +2871,10 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
28572871
// We avoid emitting casts for top level fields in the legacy SDK since
28582872
// some are used for legacy type checks and must be initialized to avoid
28592873
// infinite loops.
2860-
var initialFieldValueExpression =
2861-
!_options.soundNullSafety && _isSdkInternalRuntime(_currentLibrary!)
2862-
? valueCache
2863-
: _emitCast(valueCache, field.type);
2874+
var initialFieldValueExpression = !_compileForHotReload ||
2875+
!_options.soundNullSafety && _isSdkInternalRuntime(_currentLibrary!)
2876+
? valueCache
2877+
: _emitCast(valueCache, field.type);
28642878

28652879
// Lazy static fields require an additional type check around their value
28662880
// cache if their type is updated after hot reload. To avoid a type check

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

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,21 @@ class HotReloadDeltaInspector {
2020

2121
/// Returns all hot reload rejection errors discovered while comparing [delta]
2222
/// against the [lastAccepted] version.
23-
// TODO(nshahan): Annotate delta component with information for DDC.
23+
///
24+
/// Attaches metadata to the [delta] component to be consumed by DDC when
25+
/// compiling.
2426
List<String> compareGenerations(Component lastAccepted, Component delta) {
27+
final hotReloadLibraryMetadata =
28+
lastAccepted.metadata[hotReloadLibraryMetadataTag]
29+
as HotReloadLibraryMetadataRepository? ??
30+
HotReloadLibraryMetadataRepository();
2531
_partialLastAcceptedLibraryIndex = LibraryIndex(lastAccepted,
2632
[for (var library in delta.libraries) '${library.importUri}']);
2733
_rejectionMessages.clear();
28-
29-
for (var library in delta.libraries) {
30-
for (var deltaClass in library.classes) {
34+
for (var deltaLibrary in delta.libraries) {
35+
for (var deltaClass in deltaLibrary.classes) {
3136
final acceptedClass = _partialLastAcceptedLibraryIndex.tryGetClass(
32-
'${library.importUri}', deltaClass.name);
37+
'${deltaLibrary.importUri}', deltaClass.name);
3338
if (acceptedClass == null) {
3439
// No previous version of the class to compare with.
3540
continue;
@@ -39,7 +44,14 @@ class HotReloadDeltaInspector {
3944
_checkConstClassDeletedFields(acceptedClass, deltaClass);
4045
}
4146
}
47+
hotReloadLibraryMetadata.mapping[deltaLibrary] = true;
4248
}
49+
// Finalize the metadata written in this comparison.
50+
hotReloadLibraryMetadata.encodeMapping();
51+
// Attaching the metadata to the delta node simplifies the stateless server
52+
// approach used by dartpad. Ideally in the future the frontend server can
53+
// rely on this being attached to the delta component as well.
54+
delta.addMetadataRepository(hotReloadLibraryMetadata);
4355
return _rejectionMessages;
4456
}
4557

@@ -82,3 +94,79 @@ class HotReloadDeltaInspector {
8294
}
8395
}
8496
}
97+
98+
const hotReloadLibraryMetadataTag = 'ddc.hot-reload-library.metadata';
99+
100+
/// Metadata repository implementation that tracks hot reload information
101+
/// associated to [Library] nodes.
102+
///
103+
/// Currently only tracks if the library should be compiled with the intention
104+
/// to be hot reloaded.
105+
// TODO(nshahan): Expand to track more useful information.
106+
class HotReloadLibraryMetadataRepository extends MetadataRepository<bool> {
107+
@override
108+
String get tag => hotReloadLibraryMetadataTag;
109+
110+
/// [mapping] in this repository should not be considered consistently live.
111+
///
112+
/// Each hot reload compile request should move through these steps:
113+
///
114+
/// * Write: Add entries using `mapping[lib] = data` but they should be
115+
/// considered pending.
116+
/// * Finalize Write: After collecting all the entries, [encodeMapping] will
117+
/// persist the pending entries to storage that is consistent across future
118+
/// compiles.
119+
/// * Prepare Read: Before reading any values from [mapping] the metadata must
120+
/// be linked to nodes by calling [mapToIndexedNodes].
121+
/// * Read: After mapping to nodes in a [LibraryIndex] access metadata using
122+
/// `var data = mapping[lib]` but know that this only contains data that was
123+
/// available to be linked in the previous step.
124+
@override
125+
Map<Library, bool> mapping = <Library, bool>{};
126+
127+
@override
128+
void writeToBinary(bool metadata, Node node, BinarySink sink) {
129+
// TODO(nshahan): How to write all metadata even when there are no
130+
// associated nodes.
131+
}
132+
133+
@override
134+
bool readFromBinary(Node node, BinarySource source) {
135+
// TODO(nshahan): Read metadata when it is available.
136+
return false;
137+
}
138+
139+
final _reloadedLibraries = <String, bool>{};
140+
141+
/// Modifies [mapping] to contain metadata associated with the [Node]s present
142+
/// in [index].
143+
///
144+
/// This method should always be called before reading values from [mapping].
145+
///
146+
/// Clears [mapping] before adding the [Node] to metadata mappings.
147+
void mapToIndexedNodes(LibraryIndex index) {
148+
mapping.clear();
149+
for (var identifier in _reloadedLibraries.keys) {
150+
final library = index.tryGetLibrary(identifier);
151+
if (library == null) continue;
152+
final metadata = _reloadedLibraries[identifier];
153+
if (metadata == null) continue;
154+
155+
mapping[library] = metadata;
156+
}
157+
}
158+
159+
/// Encodes the current [mapping] to enable lookup in a future compile
160+
/// generation when the `Node` keys are no longer valid.
161+
///
162+
/// This method should always be called after writing values to [mapping].
163+
///
164+
/// Clears [mapping] after encoding.
165+
void encodeMapping() {
166+
_reloadedLibraries.addAll({
167+
for (var library in mapping.keys)
168+
'${library.importUri}': mapping[library]!
169+
});
170+
mapping.clear();
171+
}
172+
}

pkg/front_end/test/spell_checking_list_common.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1402,6 +1402,7 @@ grouping
14021402
groups
14031403
grow
14041404
growable
1405+
growing
14051406
guaranteed
14061407
guaranteeing
14071408
guarantees

pkg/frontend_server/lib/src/javascript_bundle.dart

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ class IncrementalJavaScriptBundler {
5757
final Map<Uri, String> _summaryToLibraryBundleJSPath = <Uri, String>{};
5858
final String _fileSystemScheme;
5959
final HotReloadDeltaInspector _deltaInspector = new HotReloadDeltaInspector();
60+
final HotReloadLibraryMetadataRepository _libraryMetadataRepository =
61+
new HotReloadLibraryMetadataRepository();
6062

6163
late Component _lastFullComponent;
6264
late Component _currentComponent;
@@ -86,6 +88,9 @@ class IncrementalJavaScriptBundler {
8688
if (canaryFeatures &&
8789
_moduleFormat == ModuleFormat.ddc &&
8890
!recompileRestart) {
91+
// Attach the global metadata to the last full component. The delta
92+
// inspector will add to it while comparing the two components.
93+
lastFullComponent.addMetadataRepository(_libraryMetadataRepository);
8994
// Find any potential hot reload rejections before updating the strongly
9095
// connected component graph.
9196
final List<String> errors = _deltaInspector.compareGenerations(
@@ -228,24 +233,29 @@ class IncrementalJavaScriptBundler {
228233
canaryFeatures: canaryFeatures,
229234
moduleFormats: [_moduleFormat],
230235
);
231-
Compiler compiler = ddcOptions.emitLibraryBundle
232-
? new LibraryBundleCompiler(
233-
_currentComponent,
234-
classHierarchy,
235-
ddcOptions,
236-
_libraryToSummary,
237-
_summaryToLibraryBundleName,
238-
coreTypes: coreTypes,
239-
)
240-
: new ProgramCompiler(
241-
_currentComponent,
242-
classHierarchy,
243-
ddcOptions,
244-
_libraryToSummary,
245-
_summaryToLibraryBundleName,
246-
coreTypes: coreTypes,
247-
);
248-
236+
Compiler compiler;
237+
if (ddcOptions.emitLibraryBundle) {
238+
compiler = new LibraryBundleCompiler(
239+
_currentComponent,
240+
classHierarchy,
241+
ddcOptions,
242+
_libraryToSummary,
243+
_summaryToLibraryBundleName,
244+
coreTypes: coreTypes,
245+
);
246+
// Attach all the hot reload metadata collected so far to the component
247+
// that is about to be compiled.
248+
summaryComponent.addMetadataRepository(_libraryMetadataRepository);
249+
} else {
250+
compiler = new ProgramCompiler(
251+
_currentComponent,
252+
classHierarchy,
253+
ddcOptions,
254+
_libraryToSummary,
255+
_summaryToLibraryBundleName,
256+
coreTypes: coreTypes,
257+
);
258+
}
249259
final Program jsBundle = compiler.emitModule(summaryComponent);
250260

251261
// Save program compiler to reuse for expression evaluation.

0 commit comments

Comments
 (0)