Commit b5e522e2 authored by Chris Bracken's avatar Chris Bracken Committed by GitHub

Skip AOT snapshot build if inputs are unchanged (#11084)

Previously, the snapshot file was recomputed on every build. We now
record checksums for all snapshot inputs (which are catalogued in the
snapshot dependencies file output alongside the snapshot) and only
rebuild if the checksum for any input file (or the previous output file) has
changed.
parent 678c31f1
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'dart:convert' show JSON; import 'dart:convert' show JSON;
import 'package:crypto/crypto.dart' show md5; import 'package:crypto/crypto.dart' show md5;
...@@ -42,3 +43,21 @@ class Checksum { ...@@ -42,3 +43,21 @@ class Checksum {
@override @override
int get hashCode => _checksums.hashCode; int get hashCode => _checksums.hashCode;
} }
/// Parses a VM snapshot dependency file.
///
/// Snapshot dependency files are a single line mapping the output snapshot to a
/// space-separated list of input files used to generate that output. e.g,
///
/// outfile : file1.dart file2.dart file3.dart
Future<Set<String>> readDepfile(String depfilePath) async {
// Depfile format:
// outfile1 outfile2 : file1.dart file2.dart file3.dart
final String contents = await fs.file(depfilePath).readAsString();
final String dependencies = contents.split(': ')[1];
return dependencies
.split(' ')
.map((String path) => path.trim())
.where((String path) => path.isNotEmpty)
.toSet();
}
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'dart:async'; import 'dart:async';
import '../artifacts.dart'; import '../artifacts.dart';
import '../base/build.dart';
import '../base/common.dart'; import '../base/common.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/logger.dart'; import '../base/logger.dart';
...@@ -149,13 +150,15 @@ Future<String> _buildAotSnapshot( ...@@ -149,13 +150,15 @@ Future<String> _buildAotSnapshot(
final String uiPath = fs.path.join(skyEnginePkg, 'lib', 'ui', 'ui.dart'); final String uiPath = fs.path.join(skyEnginePkg, 'lib', 'ui', 'ui.dart');
final String vmServicePath = fs.path.join(skyEnginePkg, 'sdk_ext', 'vmservice_io.dart'); final String vmServicePath = fs.path.join(skyEnginePkg, 'sdk_ext', 'vmservice_io.dart');
final List<String> filePaths = <String>[ final List<String> inputPaths = <String>[
vmEntryPoints, vmEntryPoints,
ioEntryPoints, ioEntryPoints,
uiPath, uiPath,
vmServicePath, vmServicePath,
]; ];
final Set<String> outputPaths = new Set<String>();
// These paths are used only on iOS. // These paths are used only on iOS.
String snapshotDartIOS; String snapshotDartIOS;
String assembly; String assembly;
...@@ -164,13 +167,15 @@ Future<String> _buildAotSnapshot( ...@@ -164,13 +167,15 @@ Future<String> _buildAotSnapshot(
case TargetPlatform.android_arm: case TargetPlatform.android_arm:
case TargetPlatform.android_x64: case TargetPlatform.android_x64:
case TargetPlatform.android_x86: case TargetPlatform.android_x86:
outputPaths.addAll(<String>[
vmSnapshotData,
isolateSnapshotData,
]);
break; break;
case TargetPlatform.ios: case TargetPlatform.ios:
snapshotDartIOS = artifacts.getArtifactPath(Artifact.snapshotDart, platform, buildMode); snapshotDartIOS = artifacts.getArtifactPath(Artifact.snapshotDart, platform, buildMode);
assembly = fs.path.join(outputDir.path, 'snapshot_assembly.S'); assembly = fs.path.join(outputDir.path, 'snapshot_assembly.S');
filePaths.addAll(<String>[ inputPaths.add(snapshotDartIOS);
snapshotDartIOS,
]);
break; break;
case TargetPlatform.darwin_x64: case TargetPlatform.darwin_x64:
case TargetPlatform.linux_x64: case TargetPlatform.linux_x64:
...@@ -179,9 +184,9 @@ Future<String> _buildAotSnapshot( ...@@ -179,9 +184,9 @@ Future<String> _buildAotSnapshot(
assert(false); assert(false);
} }
final List<String> missingFiles = filePaths.where((String p) => !fs.isFileSync(p)).toList(); final Iterable<String> missingInputs = inputPaths.where((String p) => !fs.isFileSync(p));
if (missingFiles.isNotEmpty) { if (missingInputs.isNotEmpty) {
printError('Missing files: $missingFiles'); printError('Missing input files: $missingInputs');
return null; return null;
} }
if (!processManager.canRun(genSnapshot)) { if (!processManager.canRun(genSnapshot)) {
...@@ -207,6 +212,17 @@ Future<String> _buildAotSnapshot( ...@@ -207,6 +212,17 @@ Future<String> _buildAotSnapshot(
genSnapshotCmd.add('--embedder_entry_points_manifest=$ioEntryPoints'); genSnapshotCmd.add('--embedder_entry_points_manifest=$ioEntryPoints');
} }
// iOS symbols used to load snapshot data in the engine.
const String kVmSnapshotData = 'kDartVmSnapshotData';
const String kIsolateSnapshotData = 'kDartIsolateSnapshotData';
// iOS snapshot generated files, compiled object files.
final String kVmSnapshotDataC = fs.path.join(outputDir.path, '$kVmSnapshotData.c');
final String kIsolateSnapshotDataC = fs.path.join(outputDir.path, '$kIsolateSnapshotData.c');
final String kVmSnapshotDataO = fs.path.join(outputDir.path, '$kVmSnapshotData.o');
final String kIsolateSnapshotDataO = fs.path.join(outputDir.path, '$kIsolateSnapshotData.o');
final String assemblyO = fs.path.join(outputDir.path, 'snapshot_assembly.o');
switch (platform) { switch (platform) {
case TargetPlatform.android_arm: case TargetPlatform.android_arm:
case TargetPlatform.android_x64: case TargetPlatform.android_x64:
...@@ -223,9 +239,14 @@ Future<String> _buildAotSnapshot( ...@@ -223,9 +239,14 @@ Future<String> _buildAotSnapshot(
if (interpreter) { if (interpreter) {
genSnapshotCmd.add('--snapshot_kind=core'); genSnapshotCmd.add('--snapshot_kind=core');
genSnapshotCmd.add(snapshotDartIOS); genSnapshotCmd.add(snapshotDartIOS);
outputPaths.addAll(<String>[
kVmSnapshotDataO,
kIsolateSnapshotDataO,
]);
} else { } else {
genSnapshotCmd.add('--snapshot_kind=app-aot-assembly'); genSnapshotCmd.add('--snapshot_kind=app-aot-assembly');
genSnapshotCmd.add('--assembly=$assembly'); genSnapshotCmd.add('--assembly=$assembly');
outputPaths.add(assemblyO);
} }
break; break;
case TargetPlatform.darwin_x64: case TargetPlatform.darwin_x64:
...@@ -244,6 +265,27 @@ Future<String> _buildAotSnapshot( ...@@ -244,6 +265,27 @@ Future<String> _buildAotSnapshot(
genSnapshotCmd.add(mainPath); genSnapshotCmd.add(mainPath);
final File checksumFile = fs.file('$dependencies.checksum');
final List<File> checksumFiles = <File>[checksumFile, fs.file(dependencies)]
..addAll(inputPaths.map(fs.file))
..addAll(outputPaths.map(fs.file));
if (checksumFiles.every((File file) => file.existsSync())) {
try {
final String json = await checksumFile.readAsString();
final Checksum oldChecksum = new Checksum.fromJson(json);
final Set<String> snapshotInputPaths = await readDepfile(dependencies);
snapshotInputPaths.addAll(outputPaths);
final Checksum newChecksum = new Checksum.fromFiles(snapshotInputPaths);
if (oldChecksum == newChecksum) {
printTrace('Skipping AOT snapshot build. Checksums match.');
return outputPath;
}
} catch (e, s) {
// Log exception and continue, this step is a performance improvement only.
printTrace('Error during AOT snapshot checksum check: $e\n$s');
}
}
final RunResult results = await runAsync(genSnapshotCmd); final RunResult results = await runAsync(genSnapshotCmd);
if (results.exitCode != 0) { if (results.exitCode != 0) {
printError('Dart snapshot generator failed with exit code ${results.exitCode}'); printError('Dart snapshot generator failed with exit code ${results.exitCode}');
...@@ -260,16 +302,6 @@ Future<String> _buildAotSnapshot( ...@@ -260,16 +302,6 @@ Future<String> _buildAotSnapshot(
if (platform == TargetPlatform.ios) { if (platform == TargetPlatform.ios) {
printStatus('Building App.framework...'); printStatus('Building App.framework...');
// These names are known to from the engine.
const String kVmSnapshotData = 'kDartVmSnapshotData';
const String kIsolateSnapshotData = 'kDartIsolateSnapshotData';
final String kVmSnapshotDataC = fs.path.join(outputDir.path, '$kVmSnapshotData.c');
final String kIsolateSnapshotDataC = fs.path.join(outputDir.path, '$kIsolateSnapshotData.c');
final String kVmSnapshotDataO = fs.path.join(outputDir.path, '$kVmSnapshotData.o');
final String kIsolateSnapshotDataO = fs.path.join(outputDir.path, '$kIsolateSnapshotData.o');
final String assemblyO = fs.path.join(outputDir.path, 'snapshot_assembly.o');
final List<String> commonBuildOptions = <String>['-arch', 'arm64', '-miphoneos-version-min=8.0']; final List<String> commonBuildOptions = <String>['-arch', 'arm64', '-miphoneos-version-min=8.0'];
if (interpreter) { if (interpreter) {
...@@ -316,5 +348,16 @@ Future<String> _buildAotSnapshot( ...@@ -316,5 +348,16 @@ Future<String> _buildAotSnapshot(
await runCheckedAsync(linkCommand); await runCheckedAsync(linkCommand);
} }
// Compute and record checksums.
try {
final Set<String> snapshotInputPaths = await readDepfile(dependencies);
snapshotInputPaths..addAll(outputPaths);
final Checksum checksum = new Checksum.fromFiles(snapshotInputPaths);
await checksumFile.writeAsString(checksum.toJson());
} catch (e, s) {
// Log exception and continue, this step is a performance improvement only.
printTrace('Error during AOT snapshot checksum output: $e\n$s');
}
return outputPath; return outputPath;
} }
...@@ -65,7 +65,7 @@ Future<int> _createSnapshot({ ...@@ -65,7 +65,7 @@ Future<int> _createSnapshot({
try { try {
final String json = await checksumFile.readAsString(); final String json = await checksumFile.readAsString();
final Checksum oldChecksum = new Checksum.fromJson(json); final Checksum oldChecksum = new Checksum.fromJson(json);
final Set<String> inputPaths = await _readDepfile(depfilePath); final Set<String> inputPaths = await readDepfile(depfilePath);
inputPaths.add(snapshotPath); inputPaths.add(snapshotPath);
final Checksum newChecksum = new Checksum.fromFiles(inputPaths); final Checksum newChecksum = new Checksum.fromFiles(inputPaths);
if (oldChecksum == newChecksum) { if (oldChecksum == newChecksum) {
...@@ -85,7 +85,7 @@ Future<int> _createSnapshot({ ...@@ -85,7 +85,7 @@ Future<int> _createSnapshot({
// Compute and record input file checksums. // Compute and record input file checksums.
try { try {
final Set<String> inputPaths = await _readDepfile(depfilePath); final Set<String> inputPaths = await readDepfile(depfilePath);
inputPaths.add(snapshotPath); inputPaths.add(snapshotPath);
final Checksum checksum = new Checksum.fromFiles(inputPaths); final Checksum checksum = new Checksum.fromFiles(inputPaths);
await checksumFile.writeAsString(checksum.toJson()); await checksumFile.writeAsString(checksum.toJson());
...@@ -96,24 +96,6 @@ Future<int> _createSnapshot({ ...@@ -96,24 +96,6 @@ Future<int> _createSnapshot({
return 0; return 0;
} }
/// Parses a VM snapshot dependency file.
///
/// Snapshot dependency files are a single line mapping the output snapshot to a
/// space-separated list of input files used to generate that output. e.g,
///
/// outfile : file1.dart file2.dart file3.dart
Future<Set<String>> _readDepfile(String depfilePath) async {
// Depfile format:
// outfile : file1.dart file2.dart file3.dart
final String contents = await fs.file(depfilePath).readAsString();
final String dependencies = contents.split(': ')[1];
return dependencies
.split(' ')
.map((String path) => path.trim())
.where((String path) => path.isNotEmpty)
.toSet();
}
Future<Null> build({ Future<Null> build({
String mainPath: defaultMainPath, String mainPath: defaultMainPath,
String manifestPath: defaultManifestPath, String manifestPath: defaultManifestPath,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment