blob: 046fa6fb1accc566e51687a53afe2222856fdd7e [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 'package:js_runtime/shared/embedded_names.dart'
show
CLASS_FIELDS_EXTRACTOR,
CLASS_ID_EXTRACTOR,
CREATE_NEW_ISOLATE,
DEFERRED_INITIALIZED,
DEFERRED_LIBRARY_URIS,
DEFERRED_LIBRARY_HASHES,
GET_TYPE_FROM_NAME,
INITIALIZE_EMPTY_INSTANCE,
INITIALIZE_LOADED_HUNK,
INSTANCE_FROM_CLASS_ID,
INTERCEPTORS_BY_TAG,
IS_HUNK_INITIALIZED,
IS_HUNK_LOADED,
LEAF_TAGS,
MANGLED_GLOBAL_NAMES,
MANGLED_NAMES,
METADATA,
NATIVE_SUPERCLASS_TAG_NAME,
STATIC_FUNCTION_NAME_TO_CLOSURE,
TYPE_TO_INTERCEPTOR_MAP,
TYPES;
import '../../common.dart';
import '../../compiler.dart' show Compiler;
import '../../constants/values.dart' show ConstantValue, FunctionConstantValue;
import '../../core_types.dart' show CoreClasses;
import '../../elements/elements.dart' show ClassElement, FunctionElement;
import '../../hash/sha1.dart' show Hasher;
import '../../io/code_output.dart';
import '../../io/line_column_provider.dart'
show LineColumnCollector, LineColumnProvider;
import '../../io/source_map_builder.dart' show SourceMapBuilder;
import '../../js/js.dart' as js;
import '../../js_backend/js_backend.dart'
show JavaScriptBackend, Namer, ConstantEmitter;
import '../../util/uri_extras.dart' show relativize;
import '../constant_ordering.dart' show deepCompareConstants;
import '../headers.dart';
import '../js_emitter.dart' show NativeEmitter;
import '../js_emitter.dart' show buildTearOffCode, NativeGenerator;
import '../model.dart';
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;
// 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(Compiler compiler, Namer namer, this.nativeEmitter,
this.shouldGenerateSourceMap)
: this.compiler = compiler,
this.namer = namer {
this.constantEmitter = new ConstantEmitter(
compiler, namer, 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 deepCompareConstants(a, b);
}
js.Expression generateStaticClosureAccess(FunctionElement element) {
return js.js('#.#()',
[namer.globalObjectFor(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);
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);
if (backend.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() {
String flavor = compiler.options.useContentSecurityPolicy
? 'fast startup, CSP'
: 'fast startup';
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}) {
LineColumnCollector lineColumnCollector;
List<CodeOutputListener> codeOutputListeners;
if (shouldGenerateSourceMap) {
lineColumnCollector = new LineColumnCollector();
codeOutputListeners = <CodeOutputListener>[lineColumnCollector];
}
CodeOutput mainOutput = new StreamCodeOutput(
compiler.outputProvider('', '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, monitor: compiler.dumpInfoTask));
if (shouldGenerateSourceMap) {
mainOutput.add(generateSourceMapTag(
compiler.options.sourceMapUri, compiler.options.outputUri));
}
mainOutput.close();
if (shouldGenerateSourceMap) {
outputSourceMap(mainOutput, lineColumnCollector, '',
compiler.options.sourceMapUri, compiler.options.outputUri);
}
}
// 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);
LineColumnCollector lineColumnCollector;
if (shouldGenerateSourceMap) {
lineColumnCollector = new LineColumnCollector();
outputListeners.add(lineColumnCollector);
}
String hunkPrefix = fragment.outputFileName;
CodeOutput output = new StreamCodeOutput(
compiler.outputProvider(hunkPrefix, deferredExtension),
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, 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(generateSourceMapTag(mapUri, partUri));
output.close();
outputSourceMap(output, lineColumnCollector, partName, mapUri, partUri);
} else {
output.close();
}
return hash;
}
String generateSourceMapTag(Uri sourceMapUri, Uri fileUri) {
if (sourceMapUri != null && fileUri != null) {
String sourceMapFileName = relativize(fileUri, sourceMapUri, false);
return '''
//# sourceMappingURL=$sourceMapFileName
''';
}
return '';
}
void outputSourceMap(
CodeOutput output, LineColumnProvider lineColumnProvider, String name,
[Uri sourceMapUri, Uri fileUri]) {
if (!shouldGenerateSourceMap) return;
// Create a source file for the compilation output. This allows using
// [:getLine:] to transform offsets to line numbers in [SourceMapBuilder].
SourceMapBuilder sourceMapBuilder =
new SourceMapBuilder(sourceMapUri, fileUri, lineColumnProvider);
output.forEachSourceLocation(sourceMapBuilder.addMapping);
String sourceMap = sourceMapBuilder.build();
compiler.outputProvider(name, 'js.map')
..add(sourceMap)
..close();
}
/// 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(
compiler.options.deferredMapUri.path, 'deferred_map')
..add(const JsonEncoder.withIndent(" ").convert(mapping))
..close();
}
}