blob: f2ba3545174e1f273436b39996a15a656be2725f [file] [log] [blame]
// Copyright (c) 2015, 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.
library dart2js.js_emitter.startup_emitter.model_emitter;
import 'dart:convert' show JsonEncoder;
import 'dart:math' show Random;
import 'package:js_runtime/shared/embedded_names.dart'
show
DEFERRED_INITIALIZED,
DEFERRED_LIBRARY_PARTS,
DEFERRED_PART_URIS,
DEFERRED_PART_HASHES,
GET_TYPE_FROM_NAME,
INITIALIZE_LOADED_HUNK,
INTERCEPTORS_BY_TAG,
IS_HUNK_INITIALIZED,
IS_HUNK_LOADED,
LEAF_TAGS,
MANGLED_GLOBAL_NAMES,
MANGLED_NAMES,
METADATA,
NATIVE_SUPERCLASS_TAG_NAME,
TYPE_TO_INTERCEPTOR_MAP,
TYPES;
import '../../../compiler_new.dart';
import '../../common.dart';
import '../../compiler.dart' show Compiler;
import '../../constants/values.dart' show ConstantValue, FunctionConstantValue;
import '../../common_elements.dart' show CommonElements;
import '../../elements/elements.dart' show ClassElement;
import '../../elements/entities.dart';
import '../../hash/sha1.dart' show Hasher;
import '../../io/code_output.dart';
import '../../io/location_provider.dart' show LocationCollector;
import '../../io/source_map_builder.dart' show SourceMapBuilder;
import '../../js/js.dart' as js;
import '../../js_backend/js_backend.dart'
show JavaScriptBackend, Namer, ConstantEmitter, StringBackedName;
import '../../world.dart';
import '../code_emitter_task.dart';
import '../constant_ordering.dart' show ConstantOrdering;
import '../headers.dart';
import '../js_emitter.dart' show NativeEmitter;
import '../js_emitter.dart' show buildTearOffCode, NativeGenerator;
import '../model.dart';
import '../sorter.dart' show Sorter;
part 'deferred_fragment_hash.dart';
part 'fragment_emitter.dart';
class ModelEmitter {
final Compiler compiler;
final Namer namer;
ConstantEmitter constantEmitter;
final NativeEmitter nativeEmitter;
final bool shouldGenerateSourceMap;
final ClosedWorld _closedWorld;
final ConstantOrdering _constantOrdering;
// The full code that is written to each hunk part-file.
final Map<Fragment, CodeOutput> outputBuffers = <Fragment, CodeOutput>{};
JavaScriptBackend get backend => compiler.backend;
/// For deferred loading we communicate the initializers via this global var.
static const String deferredInitializersGlobal =
r"$__dart_deferred_initializers__";
static const String partExtension = "part";
static const String deferredExtension = "part.js";
static const String typeNameProperty = r"builtin$cls";
ModelEmitter(this.compiler, this.namer, this.nativeEmitter, this._closedWorld,
Sorter sorter, CodeEmitterTask task, this.shouldGenerateSourceMap)
: _constantOrdering = new ConstantOrdering(sorter) {
this.constantEmitter = new ConstantEmitter(
compiler.options,
_closedWorld.commonElements,
compiler.codegenWorldBuilder,
_closedWorld.rtiNeed,
compiler.backend.rtiEncoder,
namer,
task,
this.generateConstantReference,
constantListGenerator);
}
DiagnosticReporter get reporter => compiler.reporter;
js.Expression constantListGenerator(js.Expression array) {
// TODO(floitsch): remove hard-coded name.
return js.js('makeConstList(#)', [array]);
}
js.Expression generateEmbeddedGlobalAccess(String global) {
return js.js(generateEmbeddedGlobalAccessString(global));
}
String generateEmbeddedGlobalAccessString(String global) {
// TODO(floitsch): don't use 'init' as global embedder storage.
return 'init.$global';
}
bool isConstantInlinedOrAlreadyEmitted(ConstantValue constant) {
if (constant.isFunction) return true; // Already emitted.
if (constant.isPrimitive) return true; // Inlined.
if (constant.isDummy) return true; // Inlined.
return false;
}
// TODO(floitsch): copied from OldEmitter. Adjust or share.
int compareConstants(ConstantValue a, ConstantValue b) {
// Inlined constants don't affect the order and sometimes don't even have
// names.
int cmp1 = isConstantInlinedOrAlreadyEmitted(a) ? 0 : 1;
int cmp2 = isConstantInlinedOrAlreadyEmitted(b) ? 0 : 1;
if (cmp1 + cmp2 < 2) return cmp1 - cmp2;
// Emit constant interceptors first. Constant interceptors for primitives
// might be used by code that builds other constants. See Issue 18173.
if (a.isInterceptor != b.isInterceptor) {
return a.isInterceptor ? -1 : 1;
}
// Sorting by the long name clusters constants with the same constructor
// which compresses a tiny bit better.
int r = namer.constantLongName(a).compareTo(namer.constantLongName(b));
if (r != 0) return r;
// Resolve collisions in the long name by using a structural order.
return _constantOrdering.compare(a, b);
}
js.Expression generateStaticClosureAccess(FunctionEntity element) {
return js.js('#.#()', [
namer.globalObjectForMember(element),
namer.staticClosureName(element)
]);
}
js.Expression generateConstantReference(ConstantValue value) {
if (value.isFunction) {
FunctionConstantValue functionConstant = value;
return generateStaticClosureAccess(functionConstant.element);
}
// We are only interested in the "isInlined" part, but it does not hurt to
// test for the other predicates.
if (isConstantInlinedOrAlreadyEmitted(value)) {
return constantEmitter.generate(value);
}
return js.js('#.#',
[namer.globalObjectForConstant(value), namer.constantName(value)]);
}
int emitProgram(Program program) {
MainFragment mainFragment = program.fragments.first;
List<DeferredFragment> deferredFragments =
new List<DeferredFragment>.from(program.deferredFragments);
FragmentEmitter fragmentEmitter = new FragmentEmitter(
compiler, namer, backend, constantEmitter, this, _closedWorld);
Map<DeferredFragment, _DeferredFragmentHash> deferredHashTokens =
new Map<DeferredFragment, _DeferredFragmentHash>();
for (DeferredFragment fragment in deferredFragments) {
deferredHashTokens[fragment] = new _DeferredFragmentHash(fragment);
}
js.Statement mainCode =
fragmentEmitter.emitMainFragment(program, deferredHashTokens);
Map<DeferredFragment, js.Expression> deferredFragmentsCode =
<DeferredFragment, js.Expression>{};
for (DeferredFragment fragment in deferredFragments) {
js.Expression types =
program.metadataTypesForOutputUnit(fragment.outputUnit);
deferredFragmentsCode[fragment] = fragmentEmitter.emitDeferredFragment(
fragment, types, program.holders);
}
js.TokenCounter counter = new js.TokenCounter();
deferredFragmentsCode.values.forEach(counter.countTokens);
counter.countTokens(mainCode);
program.finalizers.forEach((js.TokenFinalizer f) => f.finalizeTokens());
Map<DeferredFragment, String> hunkHashes =
writeDeferredFragments(deferredFragmentsCode);
// Now that we have written the deferred hunks, we can update the hash
// tokens in the main-fragment.
deferredHashTokens
.forEach((DeferredFragment key, _DeferredFragmentHash token) {
token.setHash(hunkHashes[key]);
});
writeMainFragment(mainFragment, mainCode,
isSplit: program.deferredFragments.isNotEmpty ||
program.hasSoftDeferredClasses ||
compiler.options.experimentalTrackAllocations);
if (_closedWorld.backendUsage.requiresPreamble &&
!backend.htmlLibraryIsLoaded) {
reporter.reportHintMessage(NO_LOCATION_SPANNABLE, MessageKind.PREAMBLE);
}
if (compiler.options.deferredMapUri != null) {
writeDeferredMap();
}
// Return the total program size.
return outputBuffers.values.fold(0, (a, b) => a + b.length);
}
/// Generates a simple header that provides the compiler's build id.
js.Comment buildGeneratedBy() {
StringBuffer flavor = new StringBuffer();
flavor.write('fast startup emitter');
if (compiler.options.strongMode) flavor.write(', strong');
if (compiler.options.trustPrimitives) flavor.write(', trust primitives');
if (compiler.options.trustTypeAnnotations) flavor.write(', trust types');
if (compiler.options.omitImplicitChecks) flavor.write(', omit checks');
if (compiler.options.useContentSecurityPolicy) flavor.write(', CSP');
return new js.Comment(generatedBy(compiler, flavor: '$flavor'));
}
/// Writes all deferred fragment's code into files.
///
/// Returns a map from fragment to its hashcode (as used for the deferred
/// library code).
///
/// Updates the shared [outputBuffers] field with the output.
Map<DeferredFragment, String> writeDeferredFragments(
Map<DeferredFragment, js.Expression> fragmentsCode) {
Map<DeferredFragment, String> hunkHashes = <DeferredFragment, String>{};
fragmentsCode.forEach((DeferredFragment fragment, js.Expression code) {
hunkHashes[fragment] = writeDeferredFragment(fragment, code);
});
return hunkHashes;
}
js.Statement buildDeferredInitializerGlobal() {
return js.js.statement(
'self.#deferredInitializers = '
'self.#deferredInitializers || Object.create(null);',
{'deferredInitializers': deferredInitializersGlobal});
}
// Writes the given [fragment]'s [code] into a file.
//
// Updates the shared [outputBuffers] field with the output.
void writeMainFragment(MainFragment fragment, js.Statement code,
{bool isSplit}) {
LocationCollector locationCollector;
List<CodeOutputListener> codeOutputListeners;
if (shouldGenerateSourceMap) {
locationCollector = new LocationCollector();
codeOutputListeners = <CodeOutputListener>[locationCollector];
}
CodeOutput mainOutput = new StreamCodeOutput(
compiler.outputProvider.createOutputSink('', 'js', OutputType.js),
codeOutputListeners);
outputBuffers[fragment] = mainOutput;
js.Program program = new js.Program([
buildGeneratedBy(),
new js.Comment(HOOKS_API_USAGE),
isSplit ? buildDeferredInitializerGlobal() : new js.Block.empty(),
code
]);
mainOutput.addBuffer(js.createCodeBuffer(
program, compiler.options, backend.sourceInformationStrategy,
monitor: compiler.dumpInfoTask));
if (shouldGenerateSourceMap) {
mainOutput.add(SourceMapBuilder.generateSourceMapTag(
compiler.options.sourceMapUri, compiler.options.outputUri));
}
mainOutput.close();
if (shouldGenerateSourceMap) {
SourceMapBuilder.outputSourceMap(
mainOutput,
locationCollector,
'',
compiler.options.sourceMapUri,
compiler.options.outputUri,
compiler.outputProvider);
}
}
// Writes the given [fragment]'s [code] into a file.
//
// Returns the deferred fragment's hash.
//
// Updates the shared [outputBuffers] field with the output.
String writeDeferredFragment(DeferredFragment fragment, js.Expression code) {
List<CodeOutputListener> outputListeners = <CodeOutputListener>[];
Hasher hasher = new Hasher();
outputListeners.add(hasher);
LocationCollector locationCollector;
if (shouldGenerateSourceMap) {
locationCollector = new LocationCollector();
outputListeners.add(locationCollector);
}
String hunkPrefix = fragment.outputFileName;
CodeOutput output = new StreamCodeOutput(
compiler.outputProvider
.createOutputSink(hunkPrefix, deferredExtension, OutputType.jsPart),
outputListeners);
outputBuffers[fragment] = output;
// The [code] contains the function that must be invoked when the deferred
// hunk is loaded.
// That function must be in a map from its hashcode to the function. Since
// we don't know the hash before we actually emit the code we store the
// function in a temporary field first:
//
// deferredInitializer.current = <pretty-printed code>;
// deferredInitializer[<hash>] = deferredInitializer.current;
js.Program program = new js.Program([
buildGeneratedBy(),
buildDeferredInitializerGlobal(),
js.js.statement('$deferredInitializersGlobal.current = #', code)
]);
output.addBuffer(js.createCodeBuffer(
program, compiler.options, backend.sourceInformationStrategy,
monitor: compiler.dumpInfoTask));
// Make a unique hash of the code (before the sourcemaps are added)
// This will be used to retrieve the initializing function from the global
// variable.
String hash = hasher.getHash();
// Now we copy the deferredInitializer.current into its correct hash.
output.add('\n${deferredInitializersGlobal}["$hash"] = '
'${deferredInitializersGlobal}.current');
if (shouldGenerateSourceMap) {
Uri mapUri, partUri;
Uri sourceMapUri = compiler.options.sourceMapUri;
Uri outputUri = compiler.options.outputUri;
String partName = "$hunkPrefix.$partExtension";
String hunkFileName = "$hunkPrefix.$deferredExtension";
if (sourceMapUri != null) {
String mapFileName = hunkFileName + ".map";
List<String> mapSegments = sourceMapUri.pathSegments.toList();
mapSegments[mapSegments.length - 1] = mapFileName;
mapUri =
compiler.options.sourceMapUri.replace(pathSegments: mapSegments);
}
if (outputUri != null) {
List<String> partSegments = outputUri.pathSegments.toList();
partSegments[partSegments.length - 1] = hunkFileName;
partUri =
compiler.options.outputUri.replace(pathSegments: partSegments);
}
output.add(SourceMapBuilder.generateSourceMapTag(mapUri, partUri));
output.close();
SourceMapBuilder.outputSourceMap(output, locationCollector, partName,
mapUri, partUri, compiler.outputProvider);
} else {
output.close();
}
return hash;
}
/// Writes a mapping from library-name to hunk files.
///
/// The output is written into a separate file that can be used by outside
/// tools.
void writeDeferredMap() {
Map<String, dynamic> mapping = new Map<String, dynamic>();
// Json does not support comments, so we embed the explanation in the
// data.
mapping["_comment"] = "This mapping shows which compiled `.js` files are "
"needed for a given deferred library import.";
mapping.addAll(compiler.deferredLoadTask.computeDeferredMap());
compiler.outputProvider.createOutputSink(
compiler.options.deferredMapUri.path, '', OutputType.info)
..add(const JsonEncoder.withIndent(" ").convert(mapping))
..close();
}
}