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 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert' show JSON;
import 'package:crypto/crypto.dart' show md5;
......@@ -42,3 +43,21 @@ class Checksum {
@override
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 @@
import 'dart:async';
import '../artifacts.dart';
import '../base/build.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
......@@ -149,13 +150,15 @@ Future<String> _buildAotSnapshot(
final String uiPath = fs.path.join(skyEnginePkg, 'lib', 'ui', 'ui.dart');
final String vmServicePath = fs.path.join(skyEnginePkg, 'sdk_ext', 'vmservice_io.dart');
final List<String> filePaths = <String>[
final List<String> inputPaths = <String>[
vmEntryPoints,
ioEntryPoints,
uiPath,
vmServicePath,
];
final Set<String> outputPaths = new Set<String>();
// These paths are used only on iOS.
String snapshotDartIOS;
String assembly;
......@@ -164,13 +167,15 @@ Future<String> _buildAotSnapshot(
case TargetPlatform.android_arm:
case TargetPlatform.android_x64:
case TargetPlatform.android_x86:
outputPaths.addAll(<String>[
vmSnapshotData,
isolateSnapshotData,
]);
break;
case TargetPlatform.ios:
snapshotDartIOS = artifacts.getArtifactPath(Artifact.snapshotDart, platform, buildMode);
assembly = fs.path.join(outputDir.path, 'snapshot_assembly.S');
filePaths.addAll(<String>[
snapshotDartIOS,
]);
inputPaths.add(snapshotDartIOS);
break;
case TargetPlatform.darwin_x64:
case TargetPlatform.linux_x64:
......@@ -179,9 +184,9 @@ Future<String> _buildAotSnapshot(
assert(false);
}
final List<String> missingFiles = filePaths.where((String p) => !fs.isFileSync(p)).toList();
if (missingFiles.isNotEmpty) {
printError('Missing files: $missingFiles');
final Iterable<String> missingInputs = inputPaths.where((String p) => !fs.isFileSync(p));
if (missingInputs.isNotEmpty) {
printError('Missing input files: $missingInputs');
return null;
}
if (!processManager.canRun(genSnapshot)) {
......@@ -207,6 +212,17 @@ Future<String> _buildAotSnapshot(
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) {
case TargetPlatform.android_arm:
case TargetPlatform.android_x64:
......@@ -223,9 +239,14 @@ Future<String> _buildAotSnapshot(
if (interpreter) {
genSnapshotCmd.add('--snapshot_kind=core');
genSnapshotCmd.add(snapshotDartIOS);
outputPaths.addAll(<String>[
kVmSnapshotDataO,
kIsolateSnapshotDataO,
]);
} else {
genSnapshotCmd.add('--snapshot_kind=app-aot-assembly');
genSnapshotCmd.add('--assembly=$assembly');
outputPaths.add(assemblyO);
}
break;
case TargetPlatform.darwin_x64:
......@@ -244,6 +265,27 @@ Future<String> _buildAotSnapshot(
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);
if (results.exitCode != 0) {
printError('Dart snapshot generator failed with exit code ${results.exitCode}');
......@@ -260,16 +302,6 @@ Future<String> _buildAotSnapshot(
if (platform == TargetPlatform.ios) {
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'];
if (interpreter) {
......@@ -316,5 +348,16 @@ Future<String> _buildAotSnapshot(
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;
}
......@@ -65,7 +65,7 @@ Future<int> _createSnapshot({
try {
final String json = await checksumFile.readAsString();
final Checksum oldChecksum = new Checksum.fromJson(json);
final Set<String> inputPaths = await _readDepfile(depfilePath);
final Set<String> inputPaths = await readDepfile(depfilePath);
inputPaths.add(snapshotPath);
final Checksum newChecksum = new Checksum.fromFiles(inputPaths);
if (oldChecksum == newChecksum) {
......@@ -85,7 +85,7 @@ Future<int> _createSnapshot({
// Compute and record input file checksums.
try {
final Set<String> inputPaths = await _readDepfile(depfilePath);
final Set<String> inputPaths = await readDepfile(depfilePath);
inputPaths.add(snapshotPath);
final Checksum checksum = new Checksum.fromFiles(inputPaths);
await checksumFile.writeAsString(checksum.toJson());
......@@ -96,24 +96,6 @@ Future<int> _createSnapshot({
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({
String mainPath: defaultMainPath,
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