| // 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 |
| ARRAY_RTI_PROPERTY, |
| 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, |
| JsGetName, |
| LEAF_TAGS, |
| MANGLED_GLOBAL_NAMES, |
| MANGLED_NAMES, |
| METADATA, |
| NATIVE_SUPERCLASS_TAG_NAME, |
| RTI_UNIVERSE, |
| RtiUniverseFieldNames, |
| TYPE_TO_INTERCEPTOR_MAP, |
| TYPES; |
| |
| import 'package:js_ast/src/precedence.dart' as js_precedence; |
| |
| import '../../../compiler_new.dart'; |
| import '../../common.dart'; |
| import '../../common/tasks.dart'; |
| import '../../constants/values.dart' |
| show ConstantValue, FunctionConstantValue, NullConstantValue; |
| import '../../common_elements.dart' show CommonElements, JElementEnvironment; |
| import '../../dump_info.dart'; |
| import '../../elements/entities.dart'; |
| import '../../elements/types.dart'; |
| import '../../hash/sha1.dart' show Hasher; |
| import '../../io/code_output.dart'; |
| import '../../io/location_provider.dart' show LocationCollector; |
| import '../../io/source_information.dart'; |
| import '../../io/source_map_builder.dart' show SourceMapBuilder; |
| import '../../js/js.dart' as js; |
| import '../../js_backend/js_backend.dart' |
| show Namer, ConstantEmitter, StringBackedName; |
| import '../../js_backend/js_interop_analysis.dart' as jsInteropAnalysis; |
| import '../../js_backend/runtime_types.dart'; |
| import '../../js_backend/runtime_types_codegen.dart'; |
| import '../../js_backend/runtime_types_new.dart' |
| show RecipeEncoder, RecipeEncoderImpl, Ruleset, RulesetEncoder; |
| import '../../js_backend/runtime_types_resolution.dart' show RuntimeTypesNeed; |
| import '../../js_backend/type_reference.dart' |
| show |
| TypeReferenceFinalizer, |
| TypeReferenceFinalizerImpl, |
| TypeReferenceResource; |
| import '../../js_backend/string_reference.dart' |
| show |
| StringReferenceFinalizer, |
| StringReferenceFinalizerImpl, |
| StringReferenceResource; |
| import '../../options.dart'; |
| import '../../universe/class_hierarchy.dart' show ClassHierarchy; |
| import '../../universe/codegen_world_builder.dart' show CodegenWorld; |
| import '../../world.dart'; |
| import '../code_emitter_task.dart'; |
| import '../constant_ordering.dart' show ConstantOrdering; |
| import '../headers.dart'; |
| import '../js_emitter.dart' show buildTearOffCode, NativeGenerator; |
| import '../model.dart'; |
| import '../native_emitter.dart'; |
| |
| part 'fragment_emitter.dart'; |
| |
| class ModelEmitter { |
| final CompilerOptions _options; |
| final DiagnosticReporter _reporter; |
| final CompilerOutput _outputProvider; |
| final DumpInfoTask _dumpInfoTask; |
| final Namer _namer; |
| final CompilerTask _task; |
| final Emitter _emitter; |
| ConstantEmitter _constantEmitter; |
| final NativeEmitter _nativeEmitter; |
| final bool _shouldGenerateSourceMap; |
| final JClosedWorld _closedWorld; |
| final ConstantOrdering _constantOrdering; |
| final SourceInformationStrategy _sourceInformationStrategy; |
| |
| // The full code that is written to each hunk part-file. |
| final Map<Fragment, CodeOutput> outputBuffers = {}; |
| |
| Set<Fragment> omittedFragments = Set(); |
| |
| /// 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._options, |
| this._reporter, |
| this._outputProvider, |
| this._dumpInfoTask, |
| this._namer, |
| this._closedWorld, |
| this._task, |
| this._emitter, |
| this._nativeEmitter, |
| this._sourceInformationStrategy, |
| RecipeEncoder rtiRecipeEncoder, |
| this._shouldGenerateSourceMap) |
| : _constantOrdering = new ConstantOrdering(_closedWorld.sorter) { |
| this._constantEmitter = new ConstantEmitter( |
| _options, |
| _closedWorld.commonElements, |
| _closedWorld.elementEnvironment, |
| _closedWorld.rtiNeed, |
| rtiRecipeEncoder, |
| _closedWorld.fieldAnalysis, |
| _emitter, |
| this.generateConstantReference, |
| constantListGenerator); |
| } |
| |
| js.Expression constantListGenerator(js.Expression array) { |
| // TODO(floitsch): remove hard-coded name. |
| return js.js('makeConstList(#)', [array]); |
| } |
| |
| 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 generateConstantReference(ConstantValue value) { |
| if (value.isFunction) { |
| FunctionConstantValue functionConstant = value; |
| return _emitter.staticClosureAccess(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, CodegenWorld codegenWorld) { |
| MainFragment mainFragment = program.fragments.first; |
| List<DeferredFragment> deferredFragments = |
| new List<DeferredFragment>.from(program.deferredFragments); |
| |
| FragmentEmitter fragmentEmitter = new FragmentEmitter( |
| _options, |
| _dumpInfoTask, |
| _namer, |
| _emitter, |
| _constantEmitter, |
| this, |
| _nativeEmitter, |
| _closedWorld, |
| codegenWorld); |
| |
| var deferredLoadingState = new DeferredLoadingState(); |
| js.Statement mainCode = |
| fragmentEmitter.emitMainFragment(program, deferredLoadingState); |
| |
| Map<DeferredFragment, js.Expression> deferredFragmentsCode = {}; |
| |
| for (DeferredFragment fragment in deferredFragments) { |
| js.Expression types = |
| program.metadataTypesForOutputUnit(fragment.outputUnit); |
| js.Expression fragmentCode = fragmentEmitter.emitDeferredFragment( |
| fragment, types, program.holders); |
| if (fragmentCode != null) { |
| deferredFragmentsCode[fragment] = fragmentCode; |
| } else { |
| omittedFragments.add(fragment); |
| } |
| } |
| |
| js.TokenCounter counter = new js.TokenCounter(); |
| deferredFragmentsCode.values.forEach(counter.countTokens); |
| counter.countTokens(mainCode); |
| |
| program.finalizers.forEach((js.TokenFinalizer f) => f.finalizeTokens()); |
| |
| // TODO(sra): This is where we know if the types (and potentially other |
| // deferred ASTs inside the parts) have any contents. We should wait until |
| // this point to decide if a part is empty. |
| |
| Map<DeferredFragment, String> hunkHashes = |
| _task.measureSubtask('write fragments', () { |
| return writeDeferredFragments(deferredFragmentsCode); |
| }); |
| |
| // Now that we have written the deferred hunks, we can create the deferred |
| // loading data. |
| fragmentEmitter.finalizeDeferredLoadingData( |
| program.loadMap, hunkHashes, deferredLoadingState); |
| |
| _task.measureSubtask('write fragments', () { |
| writeMainFragment(mainFragment, mainCode, |
| isSplit: program.deferredFragments.isNotEmpty || |
| program.hasSoftDeferredClasses || |
| _options.experimentalTrackAllocations); |
| }); |
| |
| if (_closedWorld.backendUsage.requiresPreamble && |
| !_closedWorld.backendUsage.isHtmlLoaded) { |
| _reporter.reportHintMessage(NO_LOCATION_SPANNABLE, MessageKind.PREAMBLE); |
| } |
| |
| if (_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'); |
| // TODO(johnniwinther): Remove this flavor. |
| flavor.write(', strong'); |
| if (_options.trustPrimitives) flavor.write(', trust primitives'); |
| if (_options.omitImplicitChecks) flavor.write(', omit checks'); |
| if (_options.laxRuntimeTypeToString) { |
| flavor.write(', lax runtime type'); |
| } |
| if (_options.useContentSecurityPolicy) flavor.write(', CSP'); |
| return new js.Comment(generatedBy(_options, 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 = {}; |
| |
| 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) { |
| _task.measureSubtask('source-maps', () { |
| locationCollector = LocationCollector(); |
| codeOutputListeners = <CodeOutputListener>[locationCollector]; |
| }); |
| } |
| |
| CodeOutput mainOutput = StreamCodeOutput( |
| _outputProvider.createOutputSink('', 'js', OutputType.js), |
| codeOutputListeners); |
| outputBuffers[fragment] = mainOutput; |
| |
| js.Program program = js.Program([ |
| buildGeneratedBy(), |
| js.Comment(HOOKS_API_USAGE), |
| if (isSplit) buildDeferredInitializerGlobal(), |
| code |
| ]); |
| |
| CodeBuffer buffer = js.createCodeBuffer( |
| program, _options, _sourceInformationStrategy, |
| monitor: _dumpInfoTask); |
| _task.measureSubtask('emit buffers', () { |
| mainOutput.addBuffer(buffer); |
| }); |
| |
| if (_shouldGenerateSourceMap) { |
| _task.measureSubtask('source-maps', () { |
| mainOutput.add(SourceMapBuilder.generateSourceMapTag( |
| _options.sourceMapUri, _options.outputUri)); |
| }); |
| } |
| |
| mainOutput.close(); |
| |
| if (_shouldGenerateSourceMap) { |
| _task.measureSubtask('source-maps', () { |
| SourceMapBuilder.outputSourceMap( |
| mainOutput, |
| locationCollector, |
| _namer.createMinifiedGlobalNameMap(), |
| _namer.createMinifiedInstanceNameMap(), |
| '', |
| _options.sourceMapUri, |
| _options.outputUri, |
| _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 = []; |
| Hasher hasher = new Hasher(); |
| outputListeners.add(hasher); |
| |
| LocationCollector locationCollector; |
| if (_shouldGenerateSourceMap) { |
| _task.measureSubtask('source-maps', () { |
| locationCollector = new LocationCollector(); |
| outputListeners.add(locationCollector); |
| }); |
| } |
| |
| String hunkPrefix = fragment.outputFileName; |
| |
| CodeOutput output = new StreamCodeOutput( |
| _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) |
| ]); |
| |
| CodeBuffer buffer = js.createCodeBuffer( |
| program, _options, _sourceInformationStrategy, |
| monitor: _dumpInfoTask); |
| _task.measureSubtask('emit buffers', () { |
| output.addBuffer(buffer); |
| }); |
| |
| // 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) { |
| _task.measureSubtask('source-maps', () { |
| Uri mapUri, partUri; |
| Uri sourceMapUri = _options.sourceMapUri; |
| Uri outputUri = _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 = _options.sourceMapUri.replace(pathSegments: mapSegments); |
| } |
| |
| if (outputUri != null) { |
| List<String> partSegments = outputUri.pathSegments.toList(); |
| partSegments[partSegments.length - 1] = hunkFileName; |
| partUri = _options.outputUri.replace(pathSegments: partSegments); |
| } |
| |
| output.add(SourceMapBuilder.generateSourceMapTag(mapUri, partUri)); |
| output.close(); |
| SourceMapBuilder.outputSourceMap(output, locationCollector, {}, {}, |
| partName, mapUri, partUri, _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 = {}; |
| // 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(_closedWorld.outputUnitData.computeDeferredMap( |
| _options, _closedWorld.elementEnvironment, |
| omittedUnits: |
| omittedFragments.map((fragment) => fragment.outputUnit).toSet())); |
| _outputProvider.createOutputSink( |
| _options.deferredMapUri.path, '', OutputType.deferredMap) |
| ..add(const JsonEncoder.withIndent(" ").convert(mapping)) |
| ..close(); |
| } |
| } |