[ddc] Add hot restart to new ddc module format

Add a simple implementation that throws out all libraries and runs the
main method again which triggers all libraries to be initialized with
fresh values.

Move the hot reload tests to the ddc canary test configuration
since that is where the support works at this time.

Update frontend server to use the use the new version of the DDC
LibraryCompiler when the emit library bundle option is true.

Change-Id: I6eba613106672536ef8bfcb0ff0a55749e2fb63c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381902
Reviewed-by: Kevin Moore <kevmoo@google.com>
Reviewed-by: Nate Biggs <natebiggs@google.com>
Reviewed-by: Mark Zhou <markzipan@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
Nicholas Shahan
2024-09-09 17:58:56 +00:00
parent 5d28adfcc3
commit ec0445ae79
13 changed files with 175 additions and 117 deletions
+2 -1
View File
@@ -8,6 +8,7 @@ export 'src/compiler/module_builder.dart'
show ModuleFormat, parseModuleFormat, libraryUriToJsIdentifier;
export 'src/compiler/shared_command.dart' show SharedCompilerOptions;
export 'src/kernel/command.dart' show jsProgramToCode;
export 'src/kernel/compiler.dart' show ProgramCompiler;
export 'src/kernel/compiler.dart' show Compiler, ProgramCompiler;
export 'src/kernel/compiler_new.dart' show LibraryBundleCompiler;
export 'src/kernel/expression_compiler.dart' show ExpressionCompiler;
export 'src/kernel/target.dart' show DevCompilerTarget;
@@ -1058,12 +1058,6 @@ if (!self.dart_library) {
hotReload() {
this.hotReloadGeneration += 1;
}
// Initiates a hot restart.
hotRestart() {
this.intendedHotRestartGeneration += 1;
self.dart_library.reload();
}
};
let policy = {
@@ -1286,9 +1280,23 @@ if (!self.deferred_loader) {
// These are the result of calling a library's initialization function.
libraries = Object.create(null);
// The current hot restart generation.
//
// 0-indexed and increases by 1 on every successful hot restart.
// This value is read to determine the 'current' hot restart generation
// in our hot restart tests. This closely tracks but is not the same as
// `hotRestartIteration` in DDC's runtime.
// TODO(nshahan): This value should become shared across the embedder and
// the runtime.
hotRestartGeneration = 0;
// TODO(nshahan): Set to true at the start of the hot reload process.
hotReloadInProgress = false;
// The name of the entrypoint module. Set when the application starts for
// the first time and used during a hot restart.
savedEntryPointLibraryName = null;
createEmptyLibrary() {
return Object.create(null);
}
@@ -1367,20 +1375,32 @@ if (!self.deferred_loader) {
// See docs on `DartDevEmbedder.runMain`.
runMain(entryPointLibraryName, dartSdkRuntimeOptions) {
console.log('Setting Dart SDK runtime options.');
let dartRuntimeLibrary = this.initializeAndLinkLibrary('dart:_runtime');
// TODO(nshahan) Use a single method in the Dart SDK to set all options.
dartRuntimeLibrary.weakNullSafetyErrors(dartSdkRuntimeOptions.weakNullSafetyErrors);
dartRuntimeLibrary.nonNullAsserts(dartSdkRuntimeOptions.nonNullAsserts);
dartRuntimeLibrary.nativeNonNullAsserts(dartSdkRuntimeOptions.nativeNonNullAsserts);
dartRuntimeLibrary.jsInteropNonNullAsserts(dartSdkRuntimeOptions.jsInteropNonNullAsserts);
this.setDartSDKRuntimeOptions(dartSdkRuntimeOptions);
console.log('Starting application from main method in: ' + entryPointLibraryName + '.');
let entryPointLibrary = this.initializeAndLinkLibrary(entryPointLibraryName);
this.savedEntryPointLibraryName = entryPointLibraryName;
entryPointLibrary.main();
}
setDartSDKRuntimeOptions(options) {
let dartRuntimeLibrary = this.importLibrary('dart:_runtime');
// TODO(nshahan) Use a single method in the Dart SDK to set all options?
// Or assign the single JS object and read it from the SDK?
if (options.weakNullSafetyErrors != null) {
dartRuntimeLibrary.weakNullSafetyErrors(options.weakNullSafetyErrors);
}
if (options.nonNullAsserts != null) {
dartRuntimeLibrary.nonNullAsserts(options.nonNullAsserts);
}
if (options.nativeNonNullAsserts != null) {
dartRuntimeLibrary.nativeNonNullAsserts(options.nativeNonNullAsserts);
}
if (options.jsInteropNonNullAsserts != null) {
dartRuntimeLibrary.jsInteropNonNullAsserts(options.jsInteropNonNullAsserts);
}
}
/**
* Begins a hot reload operation.
*/
@@ -1395,6 +1415,25 @@ if (!self.deferred_loader) {
hotReloadEnd(librariesToReload) {
// TODO(nshahan): Initialize and link all the newly compiled libraries.
}
/**
* Completes a hot restart operation.
*/
hotRestart() {
if (!this.savedEntryPointLibraryName) {
throw "Error: Hot restart requested before application started.";
}
console.log('Hot restarting...');
// TODO(nshahan): Stop calling hotRestart in the SDK when the libraries
// have real initialization functions.
let dart = this.importLibrary('dart:_runtime');
dart.hotRestart();
// Clear all libraries.
this.libraries = Object.create(null);
let entryPointLibrary = this.initializeAndLinkLibrary(this.savedEntryPointLibraryName);
this.hotRestartGeneration += 1;
entryPointLibrary.main();
}
}
const libraryManager = new LibraryManager();
@@ -1456,6 +1495,24 @@ if (!self.deferred_loader) {
importLibrary(libraryName, installFn) {
return libraryManager.importLibrary(libraryName, installFn);
}
/**
* Immediately triggers a hot restart of the application losing all state
* and running the main method again.
*/
hotRestart() {
self.$dartReloadModifiedModules(
libraryManager.savedEntryPointLibraryName,
() => { libraryManager.hotRestart(); });
}
/**
* @return {Number} The current hot restart generation of the running
* application.
*/
get hotRestartGeneration() {
return libraryManager.hotRestartGeneration;
}
}
self.dartDevEmbedder = new DartDevEmbedder();
@@ -53,6 +53,8 @@ abstract class Compiler {
Map<Member, String> get memberNames;
Map<Procedure, js_ast.Identifier> get procedureIdentifiers;
Map<VariableDeclaration, js_ast.Identifier> get variableIdentifiers;
js_ast.Fun emitFunctionIncremental(List<ModuleItem> items, Library library,
Class? cls, FunctionNode functionNode, String name);
}
class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
@@ -3531,6 +3533,7 @@ class ProgramCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
/// by the debugger.
/// Triggers incremental mode, which only emits symbols, types, constants,
/// libraries, and uris referenced in the expression compilation result.
@override
js_ast.Fun emitFunctionIncremental(List<ModuleItem> items, Library library,
Class? cls, FunctionNode functionNode, String name) {
// Setup context.
@@ -90,6 +90,7 @@ class LibraryBundleCompiler implements old.Compiler {
final CoreTypes _coreTypes;
final Ticker? _ticker;
final _symbolData = SymbolData();
final _libraryCompilers = <Library, LibraryCompiler>{};
LibraryBundleCompiler(
Component component,
@@ -118,10 +119,8 @@ class LibraryBundleCompiler implements old.Compiler {
js_ast.Program emitModule(Component component) {
_ticker?.logMs('Emitting library bundle');
var compiledLibraries = <js_ast.Program>[];
for (var library in component.libraries) {
// TODO(nshahan) Capture compiler state for each library here?
compiledLibraries.add(LibraryCompiler(
var compiler = LibraryCompiler(
component,
_hierarchy,
_options,
@@ -130,12 +129,21 @@ class LibraryBundleCompiler implements old.Compiler {
coreTypes: _coreTypes,
ticker: _ticker,
symbolData: _symbolData,
).emitLibrary(library));
);
_libraryCompilers[library] = compiler;
compiledLibraries.add(compiler.emitLibrary(library));
}
return js_ast.LibraryBundle(compiledLibraries,
header: _generateCompilationHeader());
}
@override
js_ast.Fun emitFunctionIncremental(List<js_ast.ModuleItem> items,
Library library, Class? cls, FunctionNode functionNode, String name) {
return _libraryCompilers[library]!
._emitFunctionIncremental(items, library, cls, functionNode, name);
}
/// Creates header comments with helpful compilation information.
List<js_ast.Comment> _generateCompilationHeader() {
var headerOptions = [
@@ -3607,7 +3615,7 @@ class LibraryCompiler extends ComputeOnceConstantVisitor<js_ast.Expression>
/// by the debugger.
/// Triggers incremental mode, which only emits symbols, types, constants,
/// libraries, and uris referenced in the expression compilation result.
js_ast.Fun emitFunctionIncremental(List<ModuleItem> items, Library library,
js_ast.Fun _emitFunctionIncremental(List<ModuleItem> items, Library library,
Class? cls, FunctionNode functionNode, String name) {
// Setup context.
_currentLibrary = library;
@@ -15,7 +15,7 @@ import 'package:kernel/dart_scope_calculator.dart';
import '../compiler/js_names.dart' as js_ast;
import '../compiler/module_builder.dart';
import '../js_ast/js_ast.dart' as js_ast;
import 'compiler.dart' show ProgramCompiler;
import 'compiler.dart' show Compiler;
DiagnosticMessage _createInternalError(Uri uri, int line, int col, String msg) {
return Message(Code<String>('Expression Compiler Internal error'),
@@ -32,7 +32,7 @@ class ExpressionCompiler {
final CompilerOptions _options;
final List<String> errors;
final IncrementalCompiler _compiler;
final ProgramCompiler _kernel2jsCompiler;
final Compiler _kernel2jsCompiler;
final Component _component;
final ModuleFormat _moduleFormat;
+4 -3
View File
@@ -88,7 +88,7 @@ Future<void> main(List<String> args) async {
final packageConfigUri = sdkRoot.resolve('.dart_tool/package_config.json');
final allTestsUri = sdkRoot.resolve('tests/hot_reload/');
final soundStableDartSdkJsUri =
buildRootUri.resolve('gen/utils/ddc/stable/sdk/ddc/dart_sdk.js');
buildRootUri.resolve('gen/utils/ddc/canary/sdk/ddc/dart_sdk.js');
final ddcModuleLoaderJsUri =
sdkRoot.resolve('pkg/dev_compiler/lib/js/ddc/ddc_module_loader.js');
@@ -139,6 +139,7 @@ Future<void> main(List<String> args) async {
final fesArgs = [
...commonArgs,
'--dartdevc-module-format=ddc',
'--dartdevc-canary',
'--platform=$ddcPlatformDillFromSdkRoot',
'--target=dartdevc',
];
@@ -857,7 +858,7 @@ class D8SuiteRunner implements HotReloadSuiteRunner {
factory D8SuiteRunner({
required ddc_helpers.D8Configuration config,
required Uri bootstrapJsUri,
String entrypointModuleName = 'main.dart',
String entrypointModuleName = 'hot-reload-test:///main.dart',
String entrypointLibraryExportName = 'main',
required Uri dartSdkJsUri,
required Uri ddcModuleLoaderJsUri,
@@ -954,7 +955,7 @@ class ChromeSuiteRunner implements HotReloadSuiteRunner {
required Uri mainEntrypointJsUri,
required Uri bootstrapJsUri,
required Uri bootstrapHtmlUri,
String entrypointModuleName = 'main.dart',
String entrypointModuleName = 'hot-reload-test:///main.dart',
String entrypointLibraryExportName = 'main',
required Uri dartSdkJsUri,
required Uri ddcModuleLoaderJsUri,
+12 -18
View File
@@ -18,11 +18,7 @@ import 'dart:typed_data' show BytesBuilder;
import 'package:args/args.dart';
import 'package:dev_compiler/dev_compiler.dart'
show
DevCompilerTarget,
ExpressionCompiler,
parseModuleFormat,
ProgramCompiler;
show Compiler, DevCompilerTarget, ExpressionCompiler, parseModuleFormat;
import 'package:front_end/src/api_prototype/macros.dart' as macros
show isMacroLibraryUri;
import 'package:front_end/src/api_unstable/ddc.dart' as ddc
@@ -875,16 +871,15 @@ class FrontendCompiler implements CompilerInterface {
emitDebugMetadata ? metadataFile.openWrite() : null;
final IOSink? symbolsFileSink =
emitDebugSymbols ? symbolsFile.openWrite() : null;
final Map<String, ProgramCompiler> kernel2JsCompilers =
await bundler.compile(
results.classHierarchy!,
results.coreTypes!,
packageConfig,
sourceFileSink,
manifestFileSink,
sourceMapsFileSink,
metadataFileSink,
symbolsFileSink);
final Map<String, Compiler> kernel2JsCompilers = await bundler.compile(
results.classHierarchy!,
results.coreTypes!,
packageConfig,
sourceFileSink,
manifestFileSink,
sourceMapsFileSink,
metadataFileSink,
symbolsFileSink);
cachedProgramCompilers.addAll(kernel2JsCompilers);
await Future.wait([
sourceFileSink.close(),
@@ -1106,7 +1101,7 @@ class FrontendCompiler implements CompilerInterface {
///
/// Produced during initial compilation of the module to JavaScript,
/// cached to be used for expression compilation in [compileExpressionToJs].
final Map<String, ProgramCompiler> cachedProgramCompilers = {};
final Map<String, Compiler> cachedProgramCompilers = {};
@override
Future<void> compileExpressionToJs(
@@ -1136,8 +1131,7 @@ class FrontendCompiler implements CompilerInterface {
_processedOptions.ticker
.logMs('Compiling expression to JavaScript in $moduleName');
final ProgramCompiler kernel2jsCompiler =
cachedProgramCompilers[moduleName]!;
final Compiler kernel2jsCompiler = cachedProgramCompilers[moduleName]!;
IncrementalCompilerResult compilerResult = _generator.lastKnownGoodResult!;
Component component = compilerResult.component;
component.computeCanonicalNames();
@@ -164,7 +164,7 @@ class IncrementalJavaScriptBundler {
}
/// Compile each component into a single JavaScript module.
Future<Map<String, ProgramCompiler>> compile(
Future<Map<String, Compiler>> compile(
ClassHierarchy classHierarchy,
CoreTypes coreTypes,
PackageConfig packageConfig,
@@ -180,7 +180,7 @@ class IncrementalJavaScriptBundler {
int symbolsOffset = 0;
final Map<String, Map<String, List<int>>> manifest = {};
final Set<Uri> visited = {};
final Map<String, ProgramCompiler> kernel2JsCompilers = {};
final Map<String, Compiler> kernel2JsCompilers = {};
for (Library library in _currentComponent.libraries) {
if (_loadedLibraries.contains(library) ||
@@ -200,23 +200,33 @@ class IncrementalJavaScriptBundler {
// use full path for tracking if module uri is not a package uri.
final String moduleUrl = urlForComponentUri(moduleUri, packageConfig);
final String moduleName = makeModuleName(moduleUrl);
ProgramCompiler compiler = new ProgramCompiler(
_currentComponent,
classHierarchy,
new SharedCompilerOptions(
sourceMap: true,
summarizeApi: false,
emitDebugMetadata: emitDebugMetadata,
emitDebugSymbols: emitDebugSymbols,
moduleName: moduleName,
soundNullSafety: true,
canaryFeatures: canaryFeatures,
),
_importToSummary,
_summaryToModule,
coreTypes: coreTypes,
final SharedCompilerOptions ddcOptions = new SharedCompilerOptions(
sourceMap: true,
summarizeApi: false,
emitDebugMetadata: emitDebugMetadata,
emitDebugSymbols: emitDebugSymbols,
moduleName: moduleName,
soundNullSafety: true,
canaryFeatures: canaryFeatures,
moduleFormats: [_moduleFormat],
);
Compiler compiler = ddcOptions.emitLibraryBundle
? new LibraryBundleCompiler(
_currentComponent,
classHierarchy,
ddcOptions,
_importToSummary,
_summaryToModule,
coreTypes: coreTypes,
)
: new ProgramCompiler(
_currentComponent,
classHierarchy,
ddcOptions,
_importToSummary,
_summaryToModule,
coreTypes: coreTypes,
);
final Program jsModule = compiler.emitModule(summaryComponent);
@@ -233,7 +243,9 @@ class IncrementalJavaScriptBundler {
final JSCode code = jsProgramToCode(
jsModule,
_moduleFormat,
ddcOptions.emitLibraryBundle
? ModuleFormat.ddcLibraryBundle
: _moduleFormat,
inlineSourceMap: true,
buildSourceMap: true,
emitDebugMetadata: emitDebugMetadata,
@@ -5,7 +5,7 @@
import 'dart:convert';
import 'dart:io';
import 'package:dev_compiler/src/kernel/compiler.dart' show ProgramCompiler;
import 'package:dev_compiler/dev_compiler.dart' show Compiler;
import 'package:frontend_server/src/javascript_bundle.dart';
import 'package:kernel/ast.dart';
import 'package:kernel/class_hierarchy.dart';
@@ -188,8 +188,7 @@ void main() {
final _MemorySink symbolsSink = new _MemorySink();
final CoreTypes coreTypes = new CoreTypes(testComponent);
final Map<String, ProgramCompiler> compilers =
await javaScriptBundler.compile(
final Map<String, Compiler> compilers = await javaScriptBundler.compile(
new ClassHierarchy(testComponent, coreTypes),
coreTypes,
packageConfig,
+3 -28
View File
@@ -95,7 +95,6 @@ var prerequisiteScripts = [
}
];
let sdk = dart_library.import('dart_sdk');
let scripts = ${_encoder.convert(scriptDescriptors)};
let loadConfig = new self.\$dartLoader.LoadConfiguration();
@@ -139,11 +138,6 @@ self.\$dartReloadModifiedModules = function(subAppName, callback) {
}
previousGenerations.add(nextGeneration);
// Increment the hot restart generation before loading files or running main
// This lets us treat the value in `hotRestartGeneration` as the 'current'
// generation until local state is updated.
self.\$dartLoader.loader.hotRestartGeneration += 1;
let modifiedFilePaths = modifiedFilesPerGeneration[nextGeneration];
// Stop if the next generation does not exist.
if (modifiedFilePaths == void 0) {
@@ -163,7 +157,7 @@ self.\$dartReloadModifiedModules = function(subAppName, callback) {
// D8 does not support the core Timer API methods beside `setTimeout` so our
// D8 preambles provide a custom implementation.
//
// Timers in this implementatiom are simulated, so they all complete before
// Timers in this implementation are simulated, so they all complete before
// native JS `await` boundaries. If this boundary occurs before our runtime's
// `hotRestartIteration` counter increments, we can observe Futures not being
// cancelled in D8 when they might otherwise have been in Chrome.
@@ -195,12 +189,7 @@ loader.nextAttempt();
// Invoke main through the d8 preamble to ensure the code is running
// within the fake event loop.
self.dartMainRunner(function () {
dart_library.start("$entrypointModuleName",
"$uuid",
"$entrypointModuleName",
"$entrypointLibraryExportName",
false
);
dartDevEmbedder.runMain("$entrypointModuleName", {});
});
''';
return d8BootstrapJS;
@@ -258,16 +247,7 @@ String generateChromeMainEntrypoint({
let child = {};
child.main = function() {
let dart = self.dart_library.import('dart_sdk', appName).dart;
dart.nonNullAsserts($nullAssertions);
dart.nativeNonNullAsserts($nativeNullAssertions);
dart_library.start(
appName,
"$uuid",
moduleName,
"$entrypointLibraryExportName",
false
);
dartDevEmbedder.runMain("$entrypointModuleName", {});
}
child.main();
@@ -453,11 +433,6 @@ let _scriptUrls = {
}
previousGenerations.add(nextGeneration);
// Increment the hot restart generation before loading files or running main
// This lets us treat the value in `hotRestartGeneration` as the 'current'
// generation until local state is updated.
self.\$dartLoader.loader.hotRestartGeneration += 1;
let modifiedFilePaths = modifiedFilesPerGeneration[nextGeneration];
// Stop if the next generation does not exist.
if (modifiedFilePaths == void 0) {
+14 -2
View File
@@ -18,16 +18,28 @@ extension type _DDCLoader(JSObject _) implements JSObject {
external void hotRestart();
external int get hotReloadGeneration;
external int get hotRestartGeneration;
external int intendedHotRestartGeneration;
}
extension type _DartDevEmbedder(JSObject _) implements JSObject {
external void hotRestart();
external JSNumber get hotRestartGeneration;
}
@JS('dartDevEmbedder')
external _DartDevEmbedder get _dartDevEmbedder;
@JS('\$dartLoader')
external _DartLoader get _dartLoader;
final _ddcLoader = _dartLoader.loader;
int get hotRestartGeneration => _ddcLoader.hotRestartGeneration;
int get hotRestartGeneration => _dartDevEmbedder.hotRestartGeneration.toDartInt;
void hotRestart() => _ddcLoader.hotRestart();
void hotRestart() {
_ddcLoader.intendedHotRestartGeneration++;
_dartDevEmbedder.hotRestart();
}
int get hotReloadGeneration => _ddcLoader.hotReloadGeneration;
@@ -24,16 +24,11 @@ void main() {
}).then((_) {
Expect.equals(2, hotRestartGeneration);
});
Future.delayed(Duration(seconds: 5), () {
throw Exception('Future from main.2.dart before hot restart. '
'This should never run.');
});
hotRestart();
}
/** DIFF **/
/*
@@ -7,25 +7,25 @@
@@ -7,27 +7,22 @@
import 'package:expect/expect.dart';
import 'package:reload_test/reload_test_utils.dart';
@@ -59,12 +54,13 @@ void main() {
+ Expect.equals(2, hotRestartGeneration);
}).then((_) {
- Expect.equals(1, hotRestartGeneration);
- });
- Future.delayed(Duration(seconds: 5), () {
- throw Exception('Future from main.1.dart before hot restart. '
- 'This should never run.');
+ Expect.equals(2, hotRestartGeneration);
});
Future.delayed(Duration(seconds: 5), () {
- throw Exception('Future from main.1.dart before hot restart. '
+ throw Exception('Future from main.2.dart before hot restart. '
'This should never run.');
});
-
hotRestart();
}
*/
+10 -10
View File
@@ -1878,16 +1878,6 @@
"--use-sdk"
]
},
{
"name": "ddc hot reload tests",
"script": "out/ReleaseX64/dart-sdk/bin/dart",
"testRunner": true,
"arguments": [
"pkg/dev_compiler/test/hot_reload_suite.dart",
"-nddc-${system}-chrome",
"--verbose"
]
},
{
"name": "ddc sourcemap tests",
"script": "out/ReleaseX64/dart",
@@ -2116,6 +2106,16 @@
"ddc_canary_test"
]
},
{
"name": "ddc hot reload tests in d8",
"script": "out/ReleaseX64/dart-sdk/bin/dart",
"testRunner": true,
"arguments": [
"pkg/dev_compiler/test/hot_reload_suite.dart",
"-nddc-canary-linux-chrome",
"--verbose"
]
},
{
"name": "ddc sdk tests",
"arguments": [