Skip to content

Commit 237313b

Browse files
committed
Add support for profile-based startup optimizations.
An experimental (and undocumented) flag '--experimental-track-allocations' makes the '--fast-startup' compiler add instructions that track which classes are allocated. By going to the JavaScript console and dumping this information as JSON into `allocated_classes_profile` the next dart2js compilation can optimize the startup by deferring the initialization of the classes that weren't necessary for startup. The workflow would look something like: ``` dart2js --fast-startup --experimental-track-allocations -o foo.js -o foo.dart // Run the program in Chrome and run the following command in the console: JSON.stringify($__dart_deferred_initializers__.allocations) // Take the result and copy it into a file, say "allocations.json". // Then rerun the compilation with the allocation-information. dart2js --experimental-allocations-path=allocations.json --fast-startup -o foo.js -o foo.dart ``` [email protected] Review-Url: https://codereview.chromium.org/2847343002 .
1 parent 0592404 commit 237313b

File tree

8 files changed

+244
-16
lines changed

8 files changed

+244
-16
lines changed

pkg/compiler/lib/src/commandline_options.dart

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ class Flags {
2323
static const String enableDiagnosticColors = '--enable-diagnostic-colors';
2424
static const String enableExperimentalMirrors =
2525
'--enable-experimental-mirrors';
26+
static const String experimentalTrackAllocations =
27+
'--experimental-track-allocations';
28+
static const String experimentalAllocationsPath =
29+
'--experimental-allocations-path';
2630
static const String fastStartup = '--fast-startup';
2731
static const String fatalWarnings = '--fatal-warnings';
2832
static const String generateCodeWithCompileTimeErrors =

pkg/compiler/lib/src/dart2js.dart

+8
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,14 @@ Future<api.CompilationResult> compile(List<String> argv) {
404404
new OptionHandler(Flags.useNewSourceInfo, passThrough),
405405
new OptionHandler(Flags.testMode, passThrough),
406406

407+
// Experimental features.
408+
// We don't provide documentation for these yet.
409+
// TODO(29574): provide documentation when this feature is supported.
410+
// TODO(29574): provide a warning/hint/error, when profile-based data is
411+
// used without `--fast-startup`.
412+
new OptionHandler(Flags.experimentalTrackAllocations, passThrough),
413+
new OptionHandler("${Flags.experimentalAllocationsPath}=.+", passThrough),
414+
407415
// The following three options must come last.
408416
new OptionHandler('-D.+=.*', addInEnvironment),
409417
new OptionHandler('-.*', (String argument) {

pkg/compiler/lib/src/js_emitter/code_emitter_task.dart

+1
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ class CodeEmitterTask extends CompilerTask {
189189
_finalizeRti();
190190
ProgramBuilder programBuilder = new ProgramBuilder(
191191
compiler.options,
192+
compiler.reporter,
192193
compiler.elementEnvironment,
193194
compiler.commonElements,
194195
compiler.types,

pkg/compiler/lib/src/js_emitter/model.dart

+10-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class Program {
1616
final bool outputContainsConstantList;
1717
final bool needsNativeSupport;
1818
final bool hasIsolateSupport;
19+
final bool hasSoftDeferredClasses;
1920

2021
/// A map from load id to the list of fragments that need to be loaded.
2122
final Map<String, List<Fragment>> loadMap;
@@ -38,7 +39,8 @@ class Program {
3839
this.typeToInterceptorMap, this._metadataCollector, this.finalizers,
3940
{this.needsNativeSupport,
4041
this.outputContainsConstantList,
41-
this.hasIsolateSupport}) {
42+
this.hasIsolateSupport,
43+
this.hasSoftDeferredClasses}) {
4244
assert(needsNativeSupport != null);
4345
assert(outputContainsConstantList != null);
4446
assert(hasIsolateSupport != null);
@@ -232,6 +234,11 @@ class Class implements FieldContainer {
232234
final bool isNative;
233235
final bool isClosureBaseClass; // Common base class for closures.
234236

237+
/// Whether this class should be soft deferred.
238+
///
239+
/// A soft-deferred class is only fully initialized at first instantiation.
240+
final bool isSoftDeferred;
241+
235242
// If the class implements a function type, and the type is encoded in the
236243
// metatada table, then this field contains the index into that field.
237244
final js.Expression functionTypeIndex;
@@ -264,7 +271,8 @@ class Class implements FieldContainer {
264271
this.onlyForRti,
265272
this.isDirectlyInstantiated,
266273
this.isNative,
267-
this.isClosureBaseClass}) {
274+
this.isClosureBaseClass,
275+
this.isSoftDeferred = false}) {
268276
assert(onlyForRti != null);
269277
assert(isDirectlyInstantiated != null);
270278
assert(isNative != null);

pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart

+85-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
library dart2js.js_emitter.program_builder;
66

7+
import 'dart:io';
8+
import 'dart:convert' show JSON;
9+
710
import '../../closure.dart' show ClosureTask, ClosureFieldElement;
811
import '../../common.dart';
912
import '../../common/names.dart' show Names, Selectors;
@@ -75,6 +78,7 @@ part 'registry.dart';
7578
/// emitted more easily by the individual emitters.
7679
class ProgramBuilder {
7780
final CompilerOptions _options;
81+
final DiagnosticReporter _reporter;
7882
final ElementEnvironment _elementEnvironment;
7983
final CommonElements _commonElements;
8084
final DartTypes _types;
@@ -117,6 +121,7 @@ class ProgramBuilder {
117121

118122
ProgramBuilder(
119123
this._options,
124+
this._reporter,
120125
this._elementEnvironment,
121126
this._commonElements,
122127
this._types,
@@ -184,8 +189,17 @@ class ProgramBuilder {
184189

185190
Set<Class> _unneededNativeClasses;
186191

192+
/// Classes that have been allocated during a profile run.
193+
///
194+
/// These classes should not be soft-deferred.
195+
///
196+
/// Also contains classes that are not tracked by the profile run (like
197+
/// interceptors, ...).
198+
Set<ClassElement> _notSoftDeferred;
199+
187200
Program buildProgram({bool storeFunctionTypesInMetadata: false}) {
188201
collector.collect();
202+
_initializeSoftDeferredMap();
189203

190204
this._storeFunctionTypesInMetadata = storeFunctionTypesInMetadata;
191205
// Note: In rare cases (mostly tests) output units can be empty. This
@@ -266,13 +280,77 @@ class ProgramBuilder {
266280
_buildTypeToInterceptorMap(), _task.metadataCollector, finalizers,
267281
needsNativeSupport: needsNativeSupport,
268282
outputContainsConstantList: collector.outputContainsConstantList,
269-
hasIsolateSupport: _backendUsage.isIsolateInUse);
283+
hasIsolateSupport: _backendUsage.isIsolateInUse,
284+
hasSoftDeferredClasses: _notSoftDeferred != null);
270285
}
271286

272287
void _markEagerClasses() {
273288
_markEagerInterceptorClasses();
274289
}
275290

291+
void _initializeSoftDeferredMap() {
292+
var allocatedClassesPath = _options.experimentalAllocationsPath;
293+
if (allocatedClassesPath != null) {
294+
// TODO(29574): the following blacklist is ad-hoc and potentially
295+
// incomplete. We need to mark all classes as black listed, that are
296+
// used without code going through the class' constructor.
297+
var blackList = [
298+
'dart:_interceptors',
299+
'dart:html',
300+
'dart:typed_data_implementation',
301+
'dart:_native_typed_data'
302+
].toSet();
303+
304+
// TODO(29574): the compiler should not just use dart:io to get the
305+
// contents of a file.
306+
File file = new File(allocatedClassesPath);
307+
308+
// TODO(29574): are the following checks necessary?
309+
// To make compilation in build-systems easier, we ignore non-existing
310+
// or empty profiles.
311+
if (!file.existsSync()) {
312+
_reporter.log("Profile file does not exist: $allocatedClassesPath");
313+
return;
314+
}
315+
if (file.lengthSync() == 0) {
316+
_reporter.log("Profile information (allocated classes) is empty.");
317+
return;
318+
}
319+
320+
String data = new File(allocatedClassesPath).readAsStringSync();
321+
Set<String> allocatedClassesKeys = JSON.decode(data).keys.toSet();
322+
Set<ClassElement> allocatedClasses = new Set<ClassElement>();
323+
324+
// Collects all super and mixin classes of a class.
325+
void collect(ClassElement element) {
326+
allocatedClasses.add(element);
327+
if (element.isMixinApplication) {
328+
collect(computeMixinClass(element));
329+
}
330+
if (element.superclass != null) {
331+
collect(element.superclass);
332+
}
333+
}
334+
335+
// For every known class, see if it was allocated in the profile. If yes,
336+
// collect its dependencies (supers and mixins) and mark them as
337+
// not-soft-deferrable.
338+
collector.outputClassLists.forEach((_, List<ClassElement> elements) {
339+
for (ClassElement element in elements) {
340+
// TODO(29574): share the encoding of the element with the code
341+
// that emits the profile-run.
342+
var key = "${element.library.canonicalUri}:${element.name}";
343+
if (allocatedClassesKeys.contains(key) ||
344+
_nativeData.isJsInteropClass(element) ||
345+
blackList.contains(element.library.canonicalUri.toString())) {
346+
collect(element);
347+
}
348+
}
349+
});
350+
_notSoftDeferred = allocatedClasses;
351+
}
352+
}
353+
276354
/// Builds a map from loadId to outputs-to-load.
277355
Map<String, List<Fragment>> _buildLoadMap() {
278356
Map<String, List<Fragment>> loadMap = <String, List<Fragment>>{};
@@ -582,6 +660,10 @@ class ProgramBuilder {
582660
library, uri, statics, classes, staticFieldsForReflection);
583661
}
584662

663+
bool _isSoftDeferred(ClassElement element) {
664+
return _notSoftDeferred != null && !_notSoftDeferred.contains(element);
665+
}
666+
585667
Class _buildClass(ClassElement element) {
586668
bool onlyForRti = collector.classesOnlyNeededForRti.contains(element);
587669
bool hasRtiField = _rtiNeed.classNeedsRtiField(element);
@@ -745,7 +827,8 @@ class ProgramBuilder {
745827
hasRtiField: hasRtiField,
746828
onlyForRti: onlyForRti,
747829
isNative: _nativeData.isNativeClass(element),
748-
isClosureBaseClass: isClosureBaseClass);
830+
isClosureBaseClass: isClosureBaseClass,
831+
isSoftDeferred: _isSoftDeferred(element));
749832
}
750833
_classes[element] = result;
751834
return result;

0 commit comments

Comments
 (0)