f4bb16232a
New, faster version of protobuf-aware tree shaker was introduced in https://dart-review.googlesource.com/c/sdk/+/152100. It has been used in Flutter use cases for a while. This change replaces old version of transformation with the new one in the pkg/vm/bin/protobuf_aware_treeshaker.dart tool and removes the old version. TEST=existing tests in pkg/vm/test/transformations Change-Id: I01546ae9fdc9fea3228595270c2aff04a0894e6b Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/178281 Commit-Queue: Alexander Markov <alexmarkov@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Reviewed-by: Sigurd Meldgaard <sigurdm@google.com> Reviewed-by: Aske Simon Christensen <askesc@google.com>
191 lines
6.4 KiB
Dart
191 lines
6.4 KiB
Dart
#!/usr/bin/env dart
|
|
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
/// This program will take a .dill file and do a protobuf aware tree-shaking.
|
|
///
|
|
/// All fields of GeneratedMessage subclasses that are not accessed with their
|
|
/// getter or setter will have their metadata removed from the class definition.
|
|
///
|
|
/// Then a general treeshaking will be run, and
|
|
/// all GeneratedMessage subclasses that are never used directly will be
|
|
/// removed.
|
|
///
|
|
/// The processed program will have observable differences: The tree-shaken
|
|
/// fields will be parsed as unknown fields.
|
|
/// The toString method will treat the unknown fields as missing.
|
|
///
|
|
/// Using the `GeneratedMessage.info_` field to reflect on fields will have
|
|
/// unpredictable behavior.
|
|
///
|
|
/// Constants are evaluated, this is mainly to enable detecting
|
|
/// `@pragma('vm:entry-point')`.
|
|
library vm.protobuf_aware_treeshaker;
|
|
|
|
import 'dart:async';
|
|
import 'dart:io';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:args/args.dart';
|
|
import 'package:kernel/kernel.dart';
|
|
import 'package:kernel/binary/ast_to_binary.dart';
|
|
import 'package:kernel/core_types.dart' show CoreTypes;
|
|
import 'package:vm/kernel_front_end.dart'
|
|
show runGlobalTransformations, ErrorDetector;
|
|
import 'package:kernel/target/targets.dart' show TargetFlags, getTarget;
|
|
import 'package:meta/meta.dart';
|
|
import 'package:vm/target/install.dart' show installAdditionalTargets;
|
|
import 'package:vm/transformations/type_flow/transformer.dart' as globalTypeFlow
|
|
show transformComponent;
|
|
|
|
ArgResults parseArgs(List<String> args) {
|
|
ArgParser argParser = ArgParser()
|
|
..addOption('platform',
|
|
valueHelp: "path/to/vm_platform.dill",
|
|
help: 'A platform.dill file to append to the input. If not given, no '
|
|
'platform.dill will be appended.')
|
|
..addOption('target',
|
|
allowed: ['dart_runner', 'flutter', 'flutter-runner', 'vm'],
|
|
defaultsTo: 'vm',
|
|
help: 'Target platform.')
|
|
..addFlag('aot',
|
|
help: 'If set, produces kernel file for AOT compilation (enables '
|
|
'global transformations). Otherwise, writes regular dill.',
|
|
defaultsTo: false)
|
|
..addFlag('write-txt',
|
|
help: 'Also write the result in kernel-text format as <out.dill>.txt',
|
|
defaultsTo: false)
|
|
..addFlag('remove-core-libs',
|
|
help: 'If set, the output dill file will not include `dart:` libraries',
|
|
defaultsTo: false)
|
|
..addMultiOption('define',
|
|
abbr: 'D',
|
|
help: 'Perform constant evaluation with this environment define set.',
|
|
valueHelp: 'variable=value')
|
|
..addFlag('remove-source',
|
|
help: 'Removes source code from the emitted dill', defaultsTo: false)
|
|
..addFlag('verbose',
|
|
help: 'Write to stdout about what classes and fields where remeoved')
|
|
..addFlag('help', help: 'Prints this help', negatable: false);
|
|
|
|
ArgResults argResults;
|
|
try {
|
|
argResults = argParser.parse(args);
|
|
} on FormatException catch (e) {
|
|
print(e.message);
|
|
}
|
|
|
|
if (argResults == null || argResults['help'] || argResults.rest.length != 2) {
|
|
String script = 'protobuf_aware_treeshaker.dart';
|
|
print(
|
|
'A tool for removing protobuf messages types that are never referred by a program');
|
|
print('Usage: $script [args] <input.dill> <output.dill>');
|
|
|
|
print(argParser.usage);
|
|
exit(-1);
|
|
}
|
|
|
|
if (argResults['aot'] && argResults['remove-core-libs']) {
|
|
print('The `--aot` option is incompatible with `--remove-core-libs`');
|
|
exit(-1);
|
|
}
|
|
|
|
return argResults;
|
|
}
|
|
|
|
Future main(List<String> args) async {
|
|
ArgResults argResults = parseArgs(args);
|
|
|
|
final input = argResults.rest[0];
|
|
final output = argResults.rest[1];
|
|
|
|
var bytes = File(input).readAsBytesSync();
|
|
final platformFile = argResults['platform'];
|
|
if (platformFile != null) {
|
|
bytes = concatenate(File(platformFile).readAsBytesSync(), bytes);
|
|
}
|
|
final component = loadComponentFromBytes(bytes);
|
|
|
|
installAdditionalTargets();
|
|
|
|
final target = getTarget(argResults['target'], TargetFlags());
|
|
|
|
// The [component] is treeshaken and has TFA annotations. Write output.
|
|
if (argResults['aot']) {
|
|
const bool useGlobalTypeFlowAnalysis = true;
|
|
const bool enableAsserts = false;
|
|
const bool useProtobufAwareTreeShakerV2 = true;
|
|
final nopErrorDetector = ErrorDetector();
|
|
runGlobalTransformations(
|
|
target,
|
|
component,
|
|
useGlobalTypeFlowAnalysis,
|
|
enableAsserts,
|
|
useProtobufAwareTreeShakerV2,
|
|
nopErrorDetector,
|
|
);
|
|
} else {
|
|
globalTypeFlow.transformComponent(target, CoreTypes(component), component,
|
|
treeShakeProtobufs: true, treeShakeSignatures: false);
|
|
}
|
|
|
|
if (argResults['aot']) {
|
|
// Write kernel file for AOT compilation.
|
|
final sink = File(output).openWrite();
|
|
final printer = BinaryPrinter(sink);
|
|
printer.writeComponentFile(component);
|
|
await sink.close();
|
|
} else {
|
|
// Clean out the AOT-only TFA annotations and write regular dill.
|
|
component.metadata.clear();
|
|
await writeComponent(component, output,
|
|
removeCoreLibs: argResults['remove-core-libs'],
|
|
removeSource: argResults['remove-source']);
|
|
}
|
|
if (argResults['write-txt']) {
|
|
writeComponentToText(component, path: output + '.txt');
|
|
}
|
|
}
|
|
|
|
Uint8List concatenate(Uint8List a, Uint8List b) {
|
|
final bytes = Uint8List(a.length + b.length);
|
|
bytes.setRange(0, a.length, a);
|
|
bytes.setRange(a.length, a.length + b.length, b);
|
|
return bytes;
|
|
}
|
|
|
|
Future writeComponent(Component component, String filename,
|
|
{@required bool removeCoreLibs, @required bool removeSource}) async {
|
|
if (removeSource) {
|
|
component.uriToSource.clear();
|
|
}
|
|
|
|
for (final lib in component.libraries) {
|
|
lib.dependencies.clear();
|
|
lib.additionalExports.clear();
|
|
lib.parts.clear();
|
|
}
|
|
|
|
final sink = File(filename).openWrite();
|
|
final printer = BinaryPrinter(sink, libraryFilter: (lib) {
|
|
if (removeCoreLibs && isCoreLibrary(lib)) return false;
|
|
if (isLibEmpty(lib)) return false;
|
|
return true;
|
|
}, includeSources: !removeSource);
|
|
|
|
printer.writeComponentFile(component);
|
|
await sink.close();
|
|
}
|
|
|
|
bool isLibEmpty(Library lib) {
|
|
return lib.classes.isEmpty &&
|
|
lib.procedures.isEmpty &&
|
|
lib.fields.isEmpty &&
|
|
lib.typedefs.isEmpty;
|
|
}
|
|
|
|
bool isCoreLibrary(Library library) {
|
|
return library.importUri.scheme == 'dart';
|
|
}
|