| // Copyright (c) 2014, 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.full_emitter; |
| |
| import 'dart:collection' show HashMap; |
| import 'dart:convert'; |
| |
| import 'package:js_runtime/shared/embedded_names.dart' as embeddedNames; |
| import 'package:js_runtime/shared/embedded_names.dart' |
| show JsBuiltin, JsGetName; |
| |
| import '../../common.dart'; |
| import '../../common/names.dart' show Names; |
| import '../../compiler.dart' show Compiler; |
| import '../../constants/values.dart'; |
| import '../../core_types.dart' show CommonElements; |
| import '../../dart_types.dart' show DartType; |
| import '../../deferred_load.dart' show OutputUnit; |
| import '../../elements/elements.dart' |
| show |
| ClassElement, |
| Element, |
| Elements, |
| FieldElement, |
| FunctionElement, |
| FunctionSignature, |
| LibraryElement, |
| MetadataAnnotation, |
| MethodElement, |
| Name, |
| TypedefElement, |
| VariableElement; |
| 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 jsAst; |
| import '../../js/js.dart' show js; |
| import '../../js_backend/backend_helpers.dart' show BackendHelpers; |
| import '../../js_backend/js_backend.dart' |
| show |
| CompoundName, |
| ConstantEmitter, |
| GetterName, |
| JavaScriptBackend, |
| JavaScriptConstantCompiler, |
| Namer, |
| SetterName, |
| TypeVariableHandler; |
| import '../../universe/call_structure.dart' show CallStructure; |
| import '../../universe/selector.dart' show Selector; |
| import '../../universe/world_builder.dart' show CodegenWorldBuilder; |
| import '../../util/characters.dart' show $$, $A, $HASH, $Z, $a, $z; |
| import '../../util/uri_extras.dart' show relativize; |
| import '../../util/util.dart' show equalElements; |
| import '../../world.dart' show ClosedWorld; |
| import '../constant_ordering.dart' show deepCompareConstants; |
| import '../headers.dart'; |
| import '../js_emitter.dart' hide Emitter, EmitterFactory; |
| import '../js_emitter.dart' as js_emitter show Emitter, EmitterFactory; |
| import '../model.dart'; |
| import '../program_builder/program_builder.dart'; |
| |
| part 'class_builder.dart'; |
| part 'class_emitter.dart'; |
| part 'code_emitter_helper.dart'; |
| part 'container_builder.dart'; |
| part 'declarations.dart'; |
| part 'deferred_output_unit_hash.dart'; |
| part 'interceptor_emitter.dart'; |
| part 'nsm_emitter.dart'; |
| part 'setup_program_builder.dart'; |
| |
| class EmitterFactory implements js_emitter.EmitterFactory { |
| final bool generateSourceMap; |
| |
| EmitterFactory({this.generateSourceMap}); |
| |
| @override |
| String get patchVersion => "full"; |
| |
| @override |
| bool get supportsReflection => true; |
| |
| @override |
| Emitter createEmitter( |
| CodeEmitterTask task, Namer namer, ClosedWorld closedWorld) { |
| return new Emitter( |
| task.compiler, namer, closedWorld, generateSourceMap, task); |
| } |
| } |
| |
| class Emitter implements js_emitter.Emitter { |
| final Compiler compiler; |
| final CodeEmitterTask task; |
| |
| // The following fields will be set to copies of the program-builder's |
| // collector. |
| Map<OutputUnit, List<VariableElement>> outputStaticNonFinalFieldLists; |
| Map<OutputUnit, Set<LibraryElement>> outputLibraryLists; |
| List<TypedefElement> typedefsNeededForReflection; |
| |
| final ContainerBuilder containerBuilder = new ContainerBuilder(); |
| final ClassEmitter classEmitter; |
| final NsmEmitter nsmEmitter; |
| final InterceptorEmitter interceptorEmitter; |
| |
| // TODO(johnniwinther): Wrap these fields in a caching strategy. |
| final Set<ConstantValue> cachedEmittedConstants; |
| final List<jsAst.Statement> cachedEmittedConstantsAst = <jsAst.Statement>[]; |
| final Map<Element, ClassBuilder> cachedClassBuilders; |
| final Set<Element> cachedElements; |
| |
| bool needsClassSupport = false; |
| bool needsMixinSupport = false; |
| bool needsLazyInitializer = false; |
| |
| /// True if [ContainerBuilder.addMemberMethodFromInfo] used "structured info", |
| /// that is, some function was needed for reflection, had stubs, or had a |
| /// super alias. |
| bool needsStructuredMemberInfo = false; |
| |
| final Namer namer; |
| ConstantEmitter constantEmitter; |
| NativeEmitter get nativeEmitter => task.nativeEmitter; |
| TypeTestRegistry get typeTestRegistry => task.typeTestRegistry; |
| CommonElements get commonElements => compiler.commonElements; |
| |
| // The full code that is written to each hunk part-file. |
| Map<OutputUnit, CodeOutput> outputBuffers = new Map<OutputUnit, CodeOutput>(); |
| |
| String classesCollector; |
| final Map<jsAst.Name, String> mangledFieldNames = |
| new HashMap<jsAst.Name, String>(); |
| final Map<jsAst.Name, String> mangledGlobalFieldNames = |
| new HashMap<jsAst.Name, String>(); |
| final Set<jsAst.Name> recordedMangledNames = new Set<jsAst.Name>(); |
| |
| JavaScriptBackend get backend => compiler.backend; |
| TypeVariableHandler get typeVariableHandler => backend.typeVariableHandler; |
| |
| String get _ => space; |
| String get space => compiler.options.enableMinification ? "" : " "; |
| String get n => compiler.options.enableMinification ? "" : "\n"; |
| String get N => compiler.options.enableMinification ? "\n" : ";\n"; |
| |
| /** |
| * List of expressions and statements that will be included in the |
| * precompiled function. |
| * |
| * To save space, dart2js normally generates constructors and accessors |
| * dynamically. This doesn't work in CSP mode, so dart2js emits them directly |
| * when in CSP mode. |
| */ |
| Map<OutputUnit, List<jsAst.Node>> _cspPrecompiledFunctions = |
| new Map<OutputUnit, List<jsAst.Node>>(); |
| |
| Map<OutputUnit, List<jsAst.Expression>> _cspPrecompiledConstructorNames = |
| new Map<OutputUnit, List<jsAst.Expression>>(); |
| |
| /** |
| * Accumulate properties for classes and libraries, describing their |
| * static/top-level members. |
| * Later, these members are emitted when the class or library is emitted. |
| * |
| * See [getElementDescriptor]. |
| */ |
| // TODO(ahe): Generate statics with their class, and store only libraries in |
| // this map. |
| final Map<Fragment, Map<Element, ClassBuilder>> elementDescriptors = |
| new Map<Fragment, Map<Element, ClassBuilder>>(); |
| |
| final bool generateSourceMap; |
| |
| Emitter(Compiler compiler, Namer namer, ClosedWorld closedWorld, |
| this.generateSourceMap, this.task) |
| : this.compiler = compiler, |
| this.namer = namer, |
| cachedEmittedConstants = compiler.cacheStrategy.newSet(), |
| cachedClassBuilders = compiler.cacheStrategy.newMap(), |
| cachedElements = compiler.cacheStrategy.newSet(), |
| classEmitter = new ClassEmitter(closedWorld), |
| interceptorEmitter = new InterceptorEmitter(closedWorld), |
| nsmEmitter = new NsmEmitter(closedWorld) { |
| constantEmitter = new ConstantEmitter( |
| compiler, namer, this.constantReference, constantListGenerator); |
| containerBuilder.emitter = this; |
| classEmitter.emitter = this; |
| nsmEmitter.emitter = this; |
| interceptorEmitter.emitter = this; |
| if (compiler.options.hasIncrementalSupport) { |
| // Much like a scout, an incremental compiler is always prepared. For |
| // mixins, classes, and lazy statics, at least. |
| needsClassSupport = true; |
| needsMixinSupport = true; |
| needsLazyInitializer = true; |
| needsStructuredMemberInfo = true; |
| } |
| } |
| |
| DiagnosticReporter get reporter => compiler.reporter; |
| |
| List<jsAst.Node> cspPrecompiledFunctionFor(OutputUnit outputUnit) { |
| return _cspPrecompiledFunctions.putIfAbsent( |
| outputUnit, () => new List<jsAst.Node>()); |
| } |
| |
| List<jsAst.Expression> cspPrecompiledConstructorNamesFor( |
| OutputUnit outputUnit) { |
| return _cspPrecompiledConstructorNames.putIfAbsent( |
| outputUnit, () => new List<jsAst.Expression>()); |
| } |
| |
| /// Erases the precompiled information for csp mode for all output units. |
| /// Used by the incremental compiler. |
| void clearCspPrecompiledNodes() { |
| _cspPrecompiledFunctions.clear(); |
| _cspPrecompiledConstructorNames.clear(); |
| } |
| |
| @override |
| bool isConstantInlinedOrAlreadyEmitted(ConstantValue constant) { |
| if (constant.isFunction) return true; // Already emitted. |
| if (constant.isPrimitive) return true; // Inlined. |
| if (constant.isDummy) return true; // Inlined. |
| // The name is null when the constant is already a JS constant. |
| // TODO(floitsch): every constant should be registered, so that we can |
| // share the ones that take up too much space (like some strings). |
| if (namer.constantName(constant) == null) return true; |
| return false; |
| } |
| |
| @override |
| 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); |
| } |
| |
| @override |
| jsAst.Expression constantReference(ConstantValue value) { |
| if (value.isFunction) { |
| FunctionConstantValue functionConstant = value; |
| return isolateStaticClosureAccess(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('#.#', |
| [namer.globalObjectForConstant(value), namer.constantName(value)]); |
| } |
| |
| jsAst.Expression constantInitializerExpression(ConstantValue value) { |
| return constantEmitter.generate(value); |
| } |
| |
| String get name => 'CodeEmitter'; |
| |
| String get finishIsolateConstructorName => |
| '${namer.isolateName}.\$finishIsolateConstructor'; |
| String get isolatePropertiesName => |
| '${namer.isolateName}.${namer.isolatePropertiesName}'; |
| String get lazyInitializerProperty => r'$lazy'; |
| String get lazyInitializerName => |
| '${namer.isolateName}.${lazyInitializerProperty}'; |
| String get initName => 'init'; |
| |
| jsAst.Name get makeConstListProperty => |
| namer.internalGlobal('makeConstantList'); |
| |
| /// The name of the property that contains all field names. |
| /// |
| /// This property is added to constructors when isolate support is enabled. |
| static const String FIELD_NAMES_PROPERTY_NAME = r"$__fields__"; |
| |
| /// For deferred loading we communicate the initializers via this global var. |
| final String deferredInitializers = r"$dart_deferred_initializers$"; |
| |
| /// Contains the global state that is needed to initialize and load a |
| /// deferred library. |
| String get globalsHolder => r"$globals$"; |
| |
| @override |
| jsAst.Expression generateEmbeddedGlobalAccess(String global) { |
| return js(generateEmbeddedGlobalAccessString(global)); |
| } |
| |
| String generateEmbeddedGlobalAccessString(String global) { |
| // TODO(floitsch): don't use 'init' as global embedder storage. |
| return '$initName.$global'; |
| } |
| |
| jsAst.PropertyAccess globalPropertyAccess(Element element) { |
| jsAst.Name name = namer.globalPropertyName(element); |
| jsAst.PropertyAccess pa = new jsAst.PropertyAccess( |
| new jsAst.VariableUse(namer.globalObjectFor(element)), name); |
| return pa; |
| } |
| |
| @override |
| jsAst.Expression isolateLazyInitializerAccess(FieldElement element) { |
| return jsAst.js('#.#', |
| [namer.globalObjectFor(element), namer.lazyInitializerName(element)]); |
| } |
| |
| @override |
| jsAst.Expression isolateStaticClosureAccess(FunctionElement element) { |
| return jsAst.js('#.#()', |
| [namer.globalObjectFor(element), namer.staticClosureName(element)]); |
| } |
| |
| @override |
| jsAst.PropertyAccess staticFieldAccess(FieldElement element) { |
| return globalPropertyAccess(element); |
| } |
| |
| @override |
| jsAst.PropertyAccess staticFunctionAccess(FunctionElement element) { |
| return globalPropertyAccess(element); |
| } |
| |
| @override |
| jsAst.PropertyAccess constructorAccess(ClassElement element) { |
| return globalPropertyAccess(element); |
| } |
| |
| @override |
| jsAst.PropertyAccess prototypeAccess( |
| ClassElement element, bool hasBeenInstantiated) { |
| return jsAst.js('#.prototype', constructorAccess(element)); |
| } |
| |
| @override |
| jsAst.PropertyAccess interceptorClassAccess(ClassElement element) { |
| return globalPropertyAccess(element); |
| } |
| |
| @override |
| jsAst.PropertyAccess typeAccess(Element element) { |
| return globalPropertyAccess(element); |
| } |
| |
| @override |
| jsAst.Template templateForBuiltin(JsBuiltin builtin) { |
| switch (builtin) { |
| case JsBuiltin.dartObjectConstructor: |
| return jsAst.js |
| .expressionTemplateYielding(typeAccess(commonElements.objectClass)); |
| |
| case JsBuiltin.isCheckPropertyToJsConstructorName: |
| int isPrefixLength = namer.operatorIsPrefix.length; |
| return jsAst.js.expressionTemplateFor('#.substring($isPrefixLength)'); |
| |
| case JsBuiltin.isFunctionType: |
| return backend.rtiEncoder.templateForIsFunctionType; |
| |
| case JsBuiltin.rawRtiToJsConstructorName: |
| return jsAst.js.expressionTemplateFor("#.$typeNameProperty"); |
| |
| case JsBuiltin.rawRuntimeType: |
| return jsAst.js.expressionTemplateFor("#.constructor"); |
| |
| case JsBuiltin.createFunctionTypeRti: |
| return backend.rtiEncoder.templateForCreateFunctionType; |
| |
| case JsBuiltin.isSubtype: |
| // TODO(floitsch): move this closer to where is-check properties are |
| // built. |
| String isPrefix = namer.operatorIsPrefix; |
| return jsAst.js |
| .expressionTemplateFor("('$isPrefix' + #) in #.prototype"); |
| |
| case JsBuiltin.isGivenTypeRti: |
| return jsAst.js.expressionTemplateFor('#.$typeNameProperty === #'); |
| |
| case JsBuiltin.getMetadata: |
| String metadataAccess = |
| generateEmbeddedGlobalAccessString(embeddedNames.METADATA); |
| return jsAst.js.expressionTemplateFor("$metadataAccess[#]"); |
| |
| case JsBuiltin.getType: |
| String typesAccess = |
| generateEmbeddedGlobalAccessString(embeddedNames.TYPES); |
| return jsAst.js.expressionTemplateFor("$typesAccess[#]"); |
| |
| case JsBuiltin.createDartClosureFromNameOfStaticFunction: |
| // The global-functions map contains a map from name to tear-off |
| // getters. |
| String functionGettersMap = |
| generateEmbeddedGlobalAccessString(embeddedNames.GLOBAL_FUNCTIONS); |
| return jsAst.js.expressionTemplateFor("$functionGettersMap[#]()"); |
| |
| default: |
| reporter.internalError( |
| NO_LOCATION_SPANNABLE, "Unhandled Builtin: $builtin"); |
| return null; |
| } |
| } |
| |
| List<jsAst.Statement> buildTrivialNsmHandlers() { |
| return nsmEmitter.buildTrivialNsmHandlers(); |
| } |
| |
| jsAst.Statement buildNativeInfoHandler( |
| jsAst.Expression infoAccess, |
| jsAst.Expression constructorAccess, |
| jsAst.Expression subclassReadGenerator(jsAst.Expression subclass), |
| jsAst.Expression interceptorsByTagAccess, |
| jsAst.Expression leafTagsAccess) { |
| return NativeGenerator.buildNativeInfoHandler(infoAccess, constructorAccess, |
| subclassReadGenerator, interceptorsByTagAccess, leafTagsAccess); |
| } |
| |
| jsAst.ObjectInitializer generateInterceptedNamesSet() { |
| return interceptorEmitter.generateInterceptedNamesSet(); |
| } |
| |
| /// In minified mode we want to keep the name for the most common core types. |
| bool _isNativeTypeNeedingReflectionName(Element element) { |
| if (!element.isClass) return false; |
| return (element == commonElements.intClass || |
| element == commonElements.doubleClass || |
| element == commonElements.numClass || |
| element == commonElements.stringClass || |
| element == commonElements.boolClass || |
| element == commonElements.nullClass || |
| element == commonElements.listClass); |
| } |
| |
| /// Returns the "reflection name" of an [Element] or [Selector]. |
| /// The reflection name of a getter 'foo' is 'foo'. |
| /// The reflection name of a setter 'foo' is 'foo='. |
| /// The reflection name of a method 'foo' is 'foo:N:M:O', where N is the |
| /// number of required arguments, M is the number of optional arguments, and |
| /// O is the named arguments. |
| /// The reflection name of a constructor is similar to a regular method but |
| /// starts with 'new '. |
| /// The reflection name of class 'C' is 'C'. |
| /// An anonymous mixin application has no reflection name. |
| /// This is used by js_mirrors.dart. |
| String getReflectionName(elementOrSelector, jsAst.Name mangledName) { |
| String name = elementOrSelector.name; |
| if (backend.shouldRetainName(name) || |
| elementOrSelector is Element && |
| // Make sure to retain names of unnamed constructors, and |
| // for common native types. |
| ((name == '' && |
| backend.isAccessibleByReflection(elementOrSelector)) || |
| _isNativeTypeNeedingReflectionName(elementOrSelector))) { |
| // TODO(ahe): Enable the next line when I can tell the difference between |
| // an instance method and a global. They may have the same mangled name. |
| // if (recordedMangledNames.contains(mangledName)) return null; |
| recordedMangledNames.add(mangledName); |
| return getReflectionNameInternal(elementOrSelector, mangledName); |
| } |
| return null; |
| } |
| |
| String getReflectionNameInternal(elementOrSelector, jsAst.Name mangledName) { |
| String name = namer.privateName(elementOrSelector.memberName); |
| if (elementOrSelector.isGetter) return name; |
| if (elementOrSelector.isSetter) { |
| if (mangledName is! SetterName) return '$name='; |
| SetterName setterName = mangledName; |
| jsAst.Name base = setterName.base; |
| jsAst.Name getter = namer.deriveGetterName(base); |
| mangledFieldNames.putIfAbsent(getter, () => name); |
| assert(mangledFieldNames[getter] == name); |
| recordedMangledNames.add(getter); |
| // TODO(karlklose,ahe): we do not actually need to store information |
| // about the name of this setter in the output, but it is needed for |
| // marking the function as invokable by reflection. |
| return '$name='; |
| } |
| if (elementOrSelector is Element && elementOrSelector.isClosure) { |
| // Closures are synthesized and their name might conflict with existing |
| // globals. Assign an illegal name, and make sure they don't clash |
| // with each other. |
| return " $name"; |
| } |
| if (elementOrSelector is Selector || |
| elementOrSelector.isFunction || |
| elementOrSelector.isConstructor) { |
| int positionalParameterCount; |
| String namedArguments = ''; |
| bool isConstructor = false; |
| if (elementOrSelector is Selector) { |
| CallStructure callStructure = elementOrSelector.callStructure; |
| positionalParameterCount = callStructure.positionalArgumentCount; |
| namedArguments = namedParametersAsReflectionNames(callStructure); |
| } else { |
| FunctionElement function = elementOrSelector; |
| if (function.isConstructor) { |
| isConstructor = true; |
| name = Elements.reconstructConstructorName(function); |
| } |
| FunctionSignature signature = function.functionSignature; |
| positionalParameterCount = signature.requiredParameterCount; |
| if (signature.optionalParametersAreNamed) { |
| var names = []; |
| for (Element e in signature.optionalParameters) { |
| names.add(e.name); |
| } |
| CallStructure callStructure = |
| new CallStructure(positionalParameterCount, names); |
| namedArguments = namedParametersAsReflectionNames(callStructure); |
| } else { |
| // Named parameters are handled differently by mirrors. For unnamed |
| // parameters, they are actually required if invoked |
| // reflectively. Also, if you have a method c(x) and c([x]) they both |
| // get the same mangled name, so they must have the same reflection |
| // name. |
| positionalParameterCount += signature.optionalParameterCount; |
| } |
| } |
| String suffix = '$name:$positionalParameterCount$namedArguments'; |
| return (isConstructor) ? 'new $suffix' : suffix; |
| } |
| Element element = elementOrSelector; |
| if (element.isGenerativeConstructorBody) { |
| return null; |
| } else if (element.isClass) { |
| ClassElement cls = element; |
| if (cls.isUnnamedMixinApplication) return null; |
| return cls.name; |
| } else if (element.isTypedef) { |
| return element.name; |
| } |
| throw reporter.internalError( |
| element, 'Do not know how to reflect on this $element.'); |
| } |
| |
| String namedParametersAsReflectionNames(CallStructure structure) { |
| if (structure.isUnnamed) return ''; |
| String names = structure.getOrderedNamedArguments().join(':'); |
| return ':$names'; |
| } |
| |
| jsAst.Statement buildCspPrecompiledFunctionFor(OutputUnit outputUnit) { |
| if (compiler.options.useContentSecurityPolicy) { |
| // TODO(ahe): Compute a hash code. |
| // TODO(sigurdm): Avoid this precompiled function. Generated |
| // constructor-functions and getter/setter functions can be stored in the |
| // library-description table. Setting properties on these can be moved to |
| // finishClasses. |
| return js.statement( |
| r""" |
| #precompiled = function ($collectedClasses$) { |
| #norename; |
| var $desc; |
| #functions; |
| return #result; |
| };""", |
| { |
| 'norename': new jsAst.Comment("// ::norenaming:: "), |
| 'precompiled': |
| generateEmbeddedGlobalAccess(embeddedNames.PRECOMPILED), |
| 'functions': cspPrecompiledFunctionFor(outputUnit), |
| 'result': new jsAst.ArrayInitializer( |
| cspPrecompiledConstructorNamesFor(outputUnit)) |
| }); |
| } else { |
| return js.comment("Constructors are generated at runtime."); |
| } |
| } |
| |
| void assembleClass( |
| Class cls, ClassBuilder enclosingBuilder, Fragment fragment) { |
| ClassElement classElement = cls.element; |
| reporter.withCurrentElement(classElement, () { |
| if (compiler.options.hasIncrementalSupport) { |
| ClassBuilder cachedBuilder = |
| cachedClassBuilders.putIfAbsent(classElement, () { |
| ClassBuilder builder = new ClassBuilder.forClass(classElement, namer); |
| classEmitter.emitClass(cls, builder, fragment); |
| return builder; |
| }); |
| invariant(classElement, cachedBuilder.fields.isEmpty); |
| invariant(classElement, cachedBuilder.superName == null); |
| invariant(classElement, cachedBuilder.functionType == null); |
| invariant(classElement, cachedBuilder.fieldMetadata == null); |
| enclosingBuilder.properties.addAll(cachedBuilder.properties); |
| } else { |
| classEmitter.emitClass(cls, enclosingBuilder, fragment); |
| } |
| }); |
| } |
| |
| void assembleStaticFunctions( |
| Iterable<Method> staticFunctions, Fragment fragment) { |
| if (staticFunctions == null) return; |
| |
| for (Method method in staticFunctions) { |
| Element element = method.element; |
| // We need to filter out null-elements for the interceptors. |
| // TODO(floitsch): use the precomputed interceptors here. |
| if (element == null) continue; |
| ClassBuilder builder = new ClassBuilder.forStatics(element, namer); |
| containerBuilder.addMemberMethod(method, builder); |
| getElementDescriptor(element, fragment) |
| .properties |
| .addAll(builder.properties); |
| } |
| } |
| |
| jsAst.Statement buildStaticNonFinalFieldInitializations( |
| OutputUnit outputUnit) { |
| jsAst.Statement buildInitialization( |
| Element element, jsAst.Expression initialValue) { |
| return js.statement('${namer.staticStateHolder}.# = #', |
| [namer.globalPropertyName(element), initialValue]); |
| } |
| |
| bool inMainUnit = (outputUnit == compiler.deferredLoadTask.mainOutputUnit); |
| JavaScriptConstantCompiler handler = backend.constants; |
| List<jsAst.Statement> parts = <jsAst.Statement>[]; |
| |
| Iterable<Element> fields = outputStaticNonFinalFieldLists[outputUnit]; |
| // If the outputUnit does not contain any static non-final fields, then |
| // [fields] is `null`. |
| if (fields != null) { |
| for (FieldElement element in fields) { |
| reporter.withCurrentElement(element, () { |
| ConstantValue constant = handler.getConstantValue(element.constant); |
| parts.add(buildInitialization(element, constantReference(constant))); |
| }); |
| } |
| } |
| |
| if (inMainUnit && outputStaticNonFinalFieldLists.length > 1) { |
| // In the main output-unit we output a stub initializer for deferred |
| // variables, so that `isolateProperties` stays a fast object. |
| outputStaticNonFinalFieldLists.forEach( |
| (OutputUnit fieldsOutputUnit, Iterable<VariableElement> fields) { |
| if (fieldsOutputUnit == outputUnit) return; // Skip the main unit. |
| for (Element element in fields) { |
| reporter.withCurrentElement(element, () { |
| parts.add(buildInitialization(element, jsAst.number(0))); |
| }); |
| } |
| }); |
| } |
| |
| return new jsAst.Block(parts); |
| } |
| |
| jsAst.Statement buildLazilyInitializedStaticFields( |
| Iterable<StaticField> lazyFields, |
| {bool isMainFragment: true}) { |
| if (lazyFields.isNotEmpty) { |
| needsLazyInitializer = true; |
| List<jsAst.Expression> laziesInfo = |
| buildLaziesInfo(lazyFields, isMainFragment); |
| return js.statement( |
| ''' |
| (function(lazies) { |
| for (var i = 0; i < lazies.length; ) { |
| var fieldName = lazies[i++]; |
| var getterName = lazies[i++]; |
| var lazyValue = lazies[i++]; |
| if (#notMinified) { |
| var staticName = lazies[i++]; |
| } |
| if (#isDeferredFragment) { |
| var fieldHolder = lazies[i++]; |
| } |
| // We build the lazy-check here: |
| // lazyInitializer(fieldName, getterName, lazyValue, staticName); |
| // 'staticName' is used for error reporting in non-minified mode. |
| // 'lazyValue' must be a closure that constructs the initial value. |
| if (#isMainFragment) { |
| if (#notMinified) { |
| #lazy(fieldName, getterName, lazyValue, staticName); |
| } else { |
| #lazy(fieldName, getterName, lazyValue); |
| } |
| } else { |
| if (#notMinified) { |
| #lazy(fieldName, getterName, lazyValue, staticName, fieldHolder); |
| } else { |
| #lazy(fieldName, getterName, lazyValue, null, fieldHolder); |
| } |
| } |
| } |
| })(#laziesInfo) |
| ''', |
| { |
| 'notMinified': !compiler.options.enableMinification, |
| 'laziesInfo': new jsAst.ArrayInitializer(laziesInfo), |
| 'lazy': js(lazyInitializerName), |
| 'isMainFragment': isMainFragment, |
| 'isDeferredFragment': !isMainFragment |
| }); |
| } else { |
| return js.comment("No lazy statics."); |
| } |
| } |
| |
| List<jsAst.Expression> buildLaziesInfo( |
| Iterable<StaticField> lazies, bool isMainFragment) { |
| List<jsAst.Expression> laziesInfo = <jsAst.Expression>[]; |
| for (StaticField field in lazies) { |
| laziesInfo.add(js.quoteName(field.name)); |
| laziesInfo.add(js.quoteName(namer.deriveLazyInitializerName(field.name))); |
| laziesInfo.add(field.code); |
| if (!compiler.options.enableMinification) { |
| laziesInfo.add(js.quoteName(field.name)); |
| } |
| if (!isMainFragment) { |
| laziesInfo.add(js('#', field.holder.name)); |
| } |
| } |
| return laziesInfo; |
| } |
| |
| // TODO(sra): Remove this unused function. |
| jsAst.Expression buildLazilyInitializedStaticField(VariableElement element, |
| {String isolateProperties}) { |
| jsAst.Expression code = backend.generatedCode[element]; |
| // The code is null if we ended up not needing the lazily |
| // initialized field after all because of constant folding |
| // before code generation. |
| if (code == null) return null; |
| // The code only computes the initial value. We build the lazy-check |
| // here: |
| // lazyInitializer(fieldName, getterName, initial, name, prototype); |
| // The name is used for error reporting. The 'initial' must be a |
| // closure that constructs the initial value. |
| if (isolateProperties != null) { |
| // This is currently only used in incremental compilation to patch |
| // in new lazy values. |
| return js('#(#,#,#,#,#)', [ |
| js(lazyInitializerName), |
| js.quoteName(namer.globalPropertyName(element)), |
| js.quoteName(namer.lazyInitializerName(element)), |
| code, |
| js.string(element.name), |
| isolateProperties |
| ]); |
| } |
| |
| if (compiler.options.enableMinification) { |
| return js('#(#,#,#)', [ |
| js(lazyInitializerName), |
| js.quoteName(namer.globalPropertyName(element)), |
| js.quoteName(namer.lazyInitializerName(element)), |
| code |
| ]); |
| } else { |
| return js('#(#,#,#,#)', [ |
| js(lazyInitializerName), |
| js.quoteName(namer.globalPropertyName(element)), |
| js.quoteName(namer.lazyInitializerName(element)), |
| code, |
| js.string(element.name) |
| ]); |
| } |
| } |
| |
| jsAst.Statement buildMetadata(Program program, OutputUnit outputUnit) { |
| List<jsAst.Statement> parts = <jsAst.Statement>[]; |
| |
| jsAst.Expression types = program.metadataTypesForOutputUnit(outputUnit); |
| |
| if (outputUnit == compiler.deferredLoadTask.mainOutputUnit) { |
| jsAst.Expression metadataAccess = |
| generateEmbeddedGlobalAccess(embeddedNames.METADATA); |
| jsAst.Expression typesAccess = |
| generateEmbeddedGlobalAccess(embeddedNames.TYPES); |
| |
| parts |
| ..add(js.statement('# = #;', [metadataAccess, program.metadata])) |
| ..add(js.statement('# = #;', [typesAccess, types])); |
| } else if (types != null) { |
| parts.add(js.statement('var ${namer.deferredTypesName} = #;', types)); |
| } |
| return new jsAst.Block(parts); |
| } |
| |
| jsAst.Statement buildCompileTimeConstants(List<Constant> constants, |
| {bool isMainFragment}) { |
| assert(isMainFragment != null); |
| |
| if (constants.isEmpty) return js.comment("No constants in program."); |
| List<jsAst.Statement> parts = <jsAst.Statement>[]; |
| if (compiler.options.hasIncrementalSupport && isMainFragment) { |
| parts = cachedEmittedConstantsAst; |
| } |
| for (Constant constant in constants) { |
| ConstantValue constantValue = constant.value; |
| if (compiler.options.hasIncrementalSupport && isMainFragment) { |
| if (cachedEmittedConstants.contains(constantValue)) continue; |
| cachedEmittedConstants.add(constantValue); |
| } |
| parts.add(buildConstantInitializer(constantValue)); |
| } |
| |
| return new jsAst.Block(parts); |
| } |
| |
| jsAst.Statement buildConstantInitializer(ConstantValue constant) { |
| jsAst.Name name = namer.constantName(constant); |
| jsAst.Statement initializer = js.statement('#.# = #', [ |
| namer.globalObjectForConstant(constant), |
| name, |
| constantInitializerExpression(constant) |
| ]); |
| compiler.dumpInfoTask.registerConstantAst(constant, initializer); |
| return initializer; |
| } |
| |
| jsAst.Expression constantListGenerator(jsAst.Expression array) { |
| // TODO(floitsch): there is no harm in caching the template. |
| return js('${namer.isolateName}.#(#)', [makeConstListProperty, array]); |
| } |
| |
| jsAst.Statement buildMakeConstantList(bool outputContainsConstantList) { |
| if (outputContainsConstantList) { |
| return js.statement( |
| r''' |
| // Functions are stored in the hidden class and not as properties in |
| // the object. We never actually look at the value, but only want |
| // to know if the property exists. |
| #.# = function (list) { |
| list.immutable$list = Array; |
| list.fixed$length = Array; |
| return list; |
| }''', |
| [namer.isolateName, makeConstListProperty]); |
| } else { |
| return js.comment("Output contains no constant list."); |
| } |
| } |
| |
| jsAst.Statement buildFunctionThatReturnsNull() { |
| return js.statement('#.# = function() {}', |
| [namer.isolateName, backend.rtiEncoder.getFunctionThatReturnsNullName]); |
| } |
| |
| jsAst.Expression generateFunctionThatReturnsNull() { |
| return js("#.#", |
| [namer.isolateName, backend.rtiEncoder.getFunctionThatReturnsNullName]); |
| } |
| |
| buildMain(jsAst.Statement invokeMain) { |
| if (compiler.isMockCompilation) return js.comment("Mock compilation"); |
| |
| List<jsAst.Statement> parts = <jsAst.Statement>[]; |
| |
| if (NativeGenerator.needsIsolateAffinityTagInitialization(backend)) { |
| parts.add(NativeGenerator.generateIsolateAffinityTagInitialization( |
| backend, |
| generateEmbeddedGlobalAccess, |
| js( |
| """ |
| // On V8, the 'intern' function converts a string to a symbol, which |
| // makes property access much faster. |
| function (s) { |
| var o = {}; |
| o[s] = 1; |
| return Object.keys(convertToFastObject(o))[0]; |
| }""", |
| []))); |
| } |
| |
| parts |
| ..add(js.comment('BEGIN invoke [main].')) |
| ..add(invokeMain) |
| ..add(js.comment('END invoke [main].')); |
| |
| return new jsAst.Block(parts); |
| } |
| |
| jsAst.Statement buildInitFunction(bool outputContainsConstantList) { |
| jsAst.Expression allClassesAccess = |
| generateEmbeddedGlobalAccess(embeddedNames.ALL_CLASSES); |
| jsAst.Expression getTypeFromNameAccess = |
| generateEmbeddedGlobalAccess(embeddedNames.GET_TYPE_FROM_NAME); |
| jsAst.Expression interceptorsByTagAccess = |
| generateEmbeddedGlobalAccess(embeddedNames.INTERCEPTORS_BY_TAG); |
| jsAst.Expression leafTagsAccess = |
| generateEmbeddedGlobalAccess(embeddedNames.LEAF_TAGS); |
| jsAst.Expression finishedClassesAccess = |
| generateEmbeddedGlobalAccess(embeddedNames.FINISHED_CLASSES); |
| jsAst.Expression cyclicThrow = |
| staticFunctionAccess(backend.helpers.cyclicThrowHelper); |
| jsAst.Expression laziesAccess = |
| generateEmbeddedGlobalAccess(embeddedNames.LAZIES); |
| |
| return js.statement( |
| ''' |
| function init() { |
| $isolatePropertiesName = Object.create(null); |
| #allClasses = map(); |
| #getTypeFromName = function(name) {return #allClasses[name];}; |
| #interceptorsByTag = map(); |
| #leafTags = map(); |
| #finishedClasses = map(); |
| |
| if (#needsLazyInitializer) { |
| // [staticName] is only provided in non-minified mode. If missing, we |
| // fall back to [fieldName]. Likewise, [prototype] is optional and |
| // defaults to the isolateProperties object. |
| $lazyInitializerName = function (fieldName, getterName, lazyValue, |
| staticName, prototype) { |
| if (!#lazies) #lazies = Object.create(null); |
| #lazies[fieldName] = getterName; |
| |
| // 'prototype' will be undefined except if we are doing an update |
| // during incremental compilation. In this case we put the lazy |
| // field directly on the isolate instead of the isolateProperties. |
| prototype = prototype || $isolatePropertiesName; |
| var sentinelUndefined = {}; |
| var sentinelInProgress = {}; |
| prototype[fieldName] = sentinelUndefined; |
| |
| prototype[getterName] = function () { |
| var result = this[fieldName]; |
| try { |
| if (result === sentinelUndefined) { |
| this[fieldName] = sentinelInProgress; |
| |
| try { |
| result = this[fieldName] = lazyValue(); |
| } finally { |
| // Use try-finally, not try-catch/throw as it destroys the |
| // stack trace. |
| if (result === sentinelUndefined) |
| this[fieldName] = null; |
| } |
| } else { |
| if (result === sentinelInProgress) |
| // In minified mode, static name is not provided, so fall |
| // back to the minified fieldName. |
| #cyclicThrow(staticName || fieldName); |
| } |
| |
| return result; |
| } finally { |
| this[getterName] = function() { return this[fieldName]; }; |
| } |
| } |
| } |
| } |
| |
| // We replace the old Isolate function with a new one that initializes |
| // all its fields with the initial (and often final) value of all |
| // globals. |
| // |
| // We also copy over old values like the prototype, and the |
| // isolateProperties themselves. |
| $finishIsolateConstructorName = function (oldIsolate) { |
| var isolateProperties = oldIsolate.#isolatePropertiesName; |
| function Isolate() { |
| |
| var staticNames = Object.keys(isolateProperties); |
| for (var i = 0; i < staticNames.length; i++) { |
| var staticName = staticNames[i]; |
| this[staticName] = isolateProperties[staticName]; |
| } |
| |
| // Reset lazy initializers to null. |
| // When forcing the object to fast mode (below) v8 will consider |
| // functions as part the object's map. Since we will change them |
| // (after the first call to the getter), we would have a map |
| // transition. |
| var lazies = init.lazies; |
| var lazyInitializers = lazies ? Object.keys(lazies) : []; |
| for (var i = 0; i < lazyInitializers.length; i++) { |
| this[lazies[lazyInitializers[i]]] = null; |
| } |
| |
| // Use the newly created object as prototype. In Chrome, |
| // this creates a hidden class for the object and makes |
| // sure it is fast to access. |
| function ForceEfficientMap() {} |
| ForceEfficientMap.prototype = this; |
| new ForceEfficientMap(); |
| |
| // Now, after being a fast map we can set the lazies again. |
| for (var i = 0; i < lazyInitializers.length; i++) { |
| var lazyInitName = lazies[lazyInitializers[i]]; |
| this[lazyInitName] = isolateProperties[lazyInitName]; |
| } |
| } |
| Isolate.prototype = oldIsolate.prototype; |
| Isolate.prototype.constructor = Isolate; |
| Isolate.#isolatePropertiesName = isolateProperties; |
| if (#outputContainsConstantList) { |
| Isolate.#makeConstListProperty = oldIsolate.#makeConstListProperty; |
| } |
| Isolate.#functionThatReturnsNullProperty = |
| oldIsolate.#functionThatReturnsNullProperty; |
| if (#hasIncrementalSupport) { |
| Isolate.#lazyInitializerProperty = |
| oldIsolate.#lazyInitializerProperty; |
| } |
| return Isolate; |
| } |
| |
| }''', |
| { |
| 'allClasses': allClassesAccess, |
| 'getTypeFromName': getTypeFromNameAccess, |
| 'interceptorsByTag': interceptorsByTagAccess, |
| 'leafTags': leafTagsAccess, |
| 'finishedClasses': finishedClassesAccess, |
| 'needsLazyInitializer': needsLazyInitializer, |
| 'lazies': laziesAccess, |
| 'cyclicThrow': cyclicThrow, |
| 'isolatePropertiesName': namer.isolatePropertiesName, |
| 'outputContainsConstantList': outputContainsConstantList, |
| 'makeConstListProperty': makeConstListProperty, |
| 'functionThatReturnsNullProperty': |
| backend.rtiEncoder.getFunctionThatReturnsNullName, |
| 'hasIncrementalSupport': compiler.options.hasIncrementalSupport, |
| 'lazyInitializerProperty': lazyInitializerProperty, |
| }); |
| } |
| |
| jsAst.Statement buildConvertToFastObjectFunction() { |
| List<jsAst.Statement> debugCode = <jsAst.Statement>[]; |
| if (DEBUG_FAST_OBJECTS) { |
| debugCode.add(js.statement(r''' |
| // The following only works on V8 when run with option |
| // "--allow-natives-syntax". We use'new Function' because the |
| // miniparser does not understand V8 native syntax. |
| if (typeof print === "function") { |
| var HasFastProperties = |
| new Function("a", "return %HasFastProperties(a)"); |
| print("Size of global object: " |
| + String(Object.getOwnPropertyNames(properties).length) |
| + ", fast properties " + HasFastProperties(properties)); |
| }''')); |
| } |
| |
| return js.statement( |
| r''' |
| function convertToFastObject(properties) { |
| // Create an instance that uses 'properties' as prototype. This should |
| // make 'properties' a fast object. |
| function MyClass() {}; |
| MyClass.prototype = properties; |
| new MyClass(); |
| #; |
| return properties; |
| }''', |
| [debugCode]); |
| } |
| |
| jsAst.Statement buildConvertToSlowObjectFunction() { |
| return js.statement(r''' |
| function convertToSlowObject(properties) { |
| // Add and remove a property to make the object transition into hashmap |
| // mode. |
| properties.__MAGIC_SLOW_PROPERTY = 1; |
| delete properties.__MAGIC_SLOW_PROPERTY; |
| return properties; |
| }'''); |
| } |
| |
| jsAst.Statement buildSupportsDirectProtoAccess() { |
| jsAst.Statement supportsDirectProtoAccess; |
| |
| if (compiler.options.hasIncrementalSupport) { |
| supportsDirectProtoAccess = js.statement(r''' |
| var supportsDirectProtoAccess = false; |
| '''); |
| } else { |
| supportsDirectProtoAccess = js.statement(r''' |
| var supportsDirectProtoAccess = (function () { |
| var cls = function () {}; |
| cls.prototype = {'p': {}}; |
| var object = new cls(); |
| if (!(object.__proto__ && object.__proto__.p === cls.prototype.p)) |
| return false; |
| |
| try { |
| // Are we running on a platform where the performance is good? |
| // (i.e. Chrome or d8). |
| |
| // Chrome userAgent? |
| if (typeof navigator != "undefined" && |
| typeof navigator.userAgent == "string" && |
| navigator.userAgent.indexOf("Chrome/") >= 0) return true; |
| |
| // d8 version() looks like "N.N.N.N", jsshell version() like "N". |
| if (typeof version == "function" && |
| version.length == 0) { |
| var v = version(); |
| if (/^\d+\.\d+\.\d+\.\d+$/.test(v)) return true; |
| } |
| } catch(_) {} |
| |
| return false; |
| })(); |
| '''); |
| } |
| |
| return supportsDirectProtoAccess; |
| } |
| |
| jsAst.Expression generateLibraryDescriptor( |
| LibraryElement library, Fragment fragment) { |
| var uri = ""; |
| if (!compiler.options.enableMinification || backend.mustPreserveUris) { |
| uri = library.canonicalUri; |
| if (uri.scheme == 'file' && compiler.options.outputUri != null) { |
| uri = |
| relativize(compiler.options.outputUri, library.canonicalUri, false); |
| } |
| } |
| |
| String libraryName = |
| (!compiler.options.enableMinification || backend.mustRetainLibraryNames) |
| ? library.libraryName |
| : ""; |
| |
| jsAst.Fun metadata = task.metadataCollector.buildMetadataFunction(library); |
| |
| ClassBuilder descriptor = elementDescriptors[fragment][library]; |
| |
| jsAst.ObjectInitializer initializer; |
| if (descriptor == null) { |
| // Nothing of the library was emitted. |
| // TODO(floitsch): this should not happen. We currently have an example |
| // with language/prefix6_negative_test.dart where we have an instance |
| // method without its corresponding class. |
| initializer = new jsAst.ObjectInitializer([]); |
| } else { |
| initializer = descriptor.toObjectInitializer(); |
| } |
| |
| compiler.dumpInfoTask.registerElementAst(library, metadata); |
| compiler.dumpInfoTask.registerElementAst(library, initializer); |
| |
| List<jsAst.Expression> parts = <jsAst.Expression>[]; |
| parts |
| ..add(js.string(libraryName)) |
| ..add(js.string(uri.toString())) |
| ..add(metadata == null ? new jsAst.ArrayHole() : metadata) |
| ..add(js('#', namer.globalObjectFor(library))) |
| ..add(initializer); |
| if (library == compiler.mainApp) { |
| parts.add(js.number(1)); |
| } |
| |
| return new jsAst.ArrayInitializer(parts); |
| } |
| |
| void assemblePrecompiledConstructor( |
| OutputUnit outputUnit, |
| jsAst.Name constructorName, |
| jsAst.Expression constructorAst, |
| List<jsAst.Name> fields) { |
| cspPrecompiledFunctionFor(outputUnit) |
| .add(new jsAst.FunctionDeclaration(constructorName, constructorAst)); |
| |
| String fieldNamesProperty = FIELD_NAMES_PROPERTY_NAME; |
| bool hasIsolateSupport = backend.hasIsolateSupport; |
| jsAst.Node fieldNamesArray; |
| if (hasIsolateSupport) { |
| fieldNamesArray = |
| new jsAst.ArrayInitializer(fields.map(js.quoteName).toList()); |
| } else { |
| fieldNamesArray = new jsAst.LiteralNull(); |
| } |
| |
| cspPrecompiledFunctionFor(outputUnit).add(js.statement( |
| r''' |
| { |
| #constructorName.#typeNameProperty = #constructorNameString; |
| // IE does not have a name property. |
| if (!("name" in #constructorName)) |
| #constructorName.name = #constructorNameString; |
| $desc = $collectedClasses$.#constructorName[1]; |
| #constructorName.prototype = $desc; |
| ''' /* next string is not a raw string */ ''' |
| if (#hasIsolateSupport) { |
| #constructorName.$fieldNamesProperty = #fieldNamesArray; |
| } |
| }''', |
| { |
| "constructorName": constructorName, |
| "typeNameProperty": typeNameProperty, |
| "constructorNameString": js.quoteName(constructorName), |
| "hasIsolateSupport": hasIsolateSupport, |
| "fieldNamesArray": fieldNamesArray |
| })); |
| |
| cspPrecompiledConstructorNamesFor(outputUnit).add(js('#', constructorName)); |
| } |
| |
| void assembleTypedefs(Program program) { |
| Fragment mainFragment = program.mainFragment; |
| OutputUnit mainOutputUnit = mainFragment.outputUnit; |
| |
| // Emit all required typedef declarations into the main output unit. |
| // TODO(karlklose): unify required classes and typedefs to declarations |
| // and have builders for each kind. |
| for (TypedefElement typedef in typedefsNeededForReflection) { |
| LibraryElement library = typedef.library; |
| // TODO(karlklose): add a TypedefBuilder and move this code there. |
| DartType type = typedef.alias; |
| // TODO(zarah): reify type variables once reflection on type arguments of |
| // typedefs is supported. |
| jsAst.Expression typeIndex = |
| task.metadataCollector.reifyType(type, ignoreTypeVariables: true); |
| ClassBuilder builder = new ClassBuilder.forStatics(typedef, namer); |
| builder.addPropertyByName( |
| embeddedNames.TYPEDEF_TYPE_PROPERTY_NAME, typeIndex); |
| builder.addPropertyByName( |
| embeddedNames.TYPEDEF_PREDICATE_PROPERTY_NAME, js.boolean(true)); |
| |
| // We can be pretty sure that the objectClass is initialized, since |
| // typedefs are only emitted with reflection, which requires lots of |
| // classes. |
| assert(commonElements.objectClass != null); |
| builder.superName = namer.className(commonElements.objectClass); |
| jsAst.Node declaration = builder.toObjectInitializer(); |
| jsAst.Name mangledName = namer.globalPropertyName(typedef); |
| String reflectionName = getReflectionName(typedef, mangledName); |
| getElementDescriptor(library, mainFragment) |
| ..addProperty(mangledName, declaration) |
| ..addPropertyByName("+$reflectionName", js.string('')); |
| // Also emit a trivial constructor for CSP mode. |
| jsAst.Name constructorName = mangledName; |
| jsAst.Expression constructorAst = js('function() {}'); |
| List<jsAst.Name> fieldNames = []; |
| assemblePrecompiledConstructor( |
| mainOutputUnit, constructorName, constructorAst, fieldNames); |
| } |
| } |
| |
| jsAst.Statement buildGlobalObjectSetup(bool isProgramSplit) { |
| List<jsAst.Statement> parts = <jsAst.Statement>[]; |
| |
| parts.add(js.comment(""" |
| // The global objects start as so-called "slow objects". For V8, this |
| // means that it won't try to make map transitions as we add properties |
| // to these objects. Later on, we attempt to turn these objects into |
| // fast objects by calling "convertToFastObject" (see |
| // [emitConvertToFastObjectFunction]). |
| """)); |
| |
| for (String globalObject in Namer.reservedGlobalObjectNames) { |
| if (isProgramSplit) { |
| String template = |
| "var #globalObject = #globalsHolder.#globalObject = map();"; |
| parts.add(js.statement(template, |
| {"globalObject": globalObject, "globalsHolder": globalsHolder})); |
| } else { |
| parts.add(js.statement( |
| "var #globalObject = map();", {"globalObject": globalObject})); |
| } |
| } |
| |
| return new jsAst.Block(parts); |
| } |
| |
| jsAst.Statement buildConvertGlobalObjectToFastObjects() { |
| List<jsAst.Statement> parts = <jsAst.Statement>[]; |
| |
| for (String globalObject in Namer.reservedGlobalObjectNames) { |
| parts.add(js.statement( |
| '#globalObject = convertToFastObject(#globalObject);', |
| {"globalObject": globalObject})); |
| } |
| |
| return new jsAst.Block(parts); |
| } |
| |
| jsAst.Statement buildDebugFastObjectCode() { |
| List<jsAst.Statement> parts = <jsAst.Statement>[]; |
| |
| if (DEBUG_FAST_OBJECTS) { |
| parts.add(js.statement(r''' |
| // The following only works on V8 when run with option |
| // "--allow-natives-syntax". We use'new Function' because the |
| // miniparser does not understand V8 native syntax. |
| if (typeof print === "function") { |
| var HasFastProperties = |
| new Function("a", "return %HasFastProperties(a)"); |
| print("Size of global helper object: " |
| + String(Object.getOwnPropertyNames(H).length) |
| + ", fast properties " + HasFastProperties(H)); |
| print("Size of global platform object: " |
| + String(Object.getOwnPropertyNames(P).length) |
| + ", fast properties " + HasFastProperties(P)); |
| print("Size of global dart:html object: " |
| + String(Object.getOwnPropertyNames(W).length) |
| + ", fast properties " + HasFastProperties(W)); |
| print("Size of isolate properties object: " |
| + String(Object.getOwnPropertyNames($).length) |
| + ", fast properties " + HasFastProperties($)); |
| print("Size of constant object: " |
| + String(Object.getOwnPropertyNames(C).length) |
| + ", fast properties " + HasFastProperties(C)); |
| var names = Object.getOwnPropertyNames($); |
| for (var i = 0; i < names.length; i++) { |
| print("$." + names[i]); |
| } |
| } |
| ''')); |
| |
| for (String object in Namer.userGlobalObjects) { |
| parts.add(js.statement( |
| ''' |
| if (typeof print === "function") { |
| print("Size of " + #objectString + ": " |
| + String(Object.getOwnPropertyNames(#object).length) |
| + ", fast properties " + HasFastProperties(#object)); |
| } |
| ''', |
| {"object": object, "objectString": js.string(object)})); |
| } |
| } |
| |
| return new jsAst.Block(parts); |
| } |
| |
| jsAst.Statement buildMangledNames() { |
| List<jsAst.Statement> parts = <jsAst.Statement>[]; |
| |
| if (!mangledFieldNames.isEmpty) { |
| List<jsAst.Name> keys = mangledFieldNames.keys.toList()..sort(); |
| var properties = []; |
| for (jsAst.Name key in keys) { |
| var value = js.string(mangledFieldNames[key]); |
| properties.add(new jsAst.Property(key, value)); |
| } |
| |
| jsAst.Expression mangledNamesAccess = |
| generateEmbeddedGlobalAccess(embeddedNames.MANGLED_NAMES); |
| var map = new jsAst.ObjectInitializer(properties); |
| parts.add(js.statement('# = #', [mangledNamesAccess, map])); |
| } |
| |
| if (!mangledGlobalFieldNames.isEmpty) { |
| List<jsAst.Name> keys = mangledGlobalFieldNames.keys.toList()..sort(); |
| List<jsAst.Property> properties = <jsAst.Property>[]; |
| for (jsAst.Name key in keys) { |
| jsAst.Literal value = js.string(mangledGlobalFieldNames[key]); |
| properties.add(new jsAst.Property(js.quoteName(key), value)); |
| } |
| jsAst.Expression mangledGlobalNamesAccess = |
| generateEmbeddedGlobalAccess(embeddedNames.MANGLED_GLOBAL_NAMES); |
| jsAst.ObjectInitializer map = new jsAst.ObjectInitializer(properties); |
| parts.add(js.statement('# = #', [mangledGlobalNamesAccess, map])); |
| } |
| |
| return new jsAst.Block(parts); |
| } |
| |
| void checkEverythingEmitted(Iterable<Element> elements) { |
| List<Element> pendingStatics; |
| if (!compiler.options.hasIncrementalSupport) { |
| pendingStatics = |
| Elements.sortedByPosition(elements.where((e) => !e.isLibrary)); |
| |
| pendingStatics.forEach((element) => reporter.reportInfo( |
| element, MessageKind.GENERIC, {'text': 'Pending statics.'})); |
| } |
| |
| if (pendingStatics != null && !pendingStatics.isEmpty) { |
| reporter.internalError( |
| pendingStatics.first, 'Pending statics (see above).'); |
| } |
| } |
| |
| void assembleLibrary(Library library, Fragment fragment) { |
| LibraryElement libraryElement = library.element; |
| |
| assembleStaticFunctions(library.statics, fragment); |
| |
| ClassBuilder libraryBuilder = |
| getElementDescriptor(libraryElement, fragment); |
| for (Class cls in library.classes) { |
| assembleClass(cls, libraryBuilder, fragment); |
| } |
| |
| classEmitter.emitFields(library, libraryBuilder, emitStatics: true); |
| } |
| |
| void assembleProgram(Program program) { |
| for (Fragment fragment in program.fragments) { |
| for (Library library in fragment.libraries) { |
| assembleLibrary(library, fragment); |
| } |
| } |
| assembleTypedefs(program); |
| } |
| |
| jsAst.Statement buildDeferredHeader() { |
| /// For deferred loading we communicate the initializers via this global |
| /// variable. The deferred hunks will add their initialization to this. |
| /// The semicolon is important in minified mode, without it the |
| /// following parenthesis looks like a call to the object literal. |
| return js.statement( |
| 'self.#deferredInitializers = ' |
| 'self.#deferredInitializers || Object.create(null);', |
| {'deferredInitializers': deferredInitializers}); |
| } |
| |
| jsAst.Program buildOutputAstForMain(Program program, |
| Map<OutputUnit, _DeferredOutputUnitHash> deferredLoadHashes) { |
| MainFragment mainFragment = program.mainFragment; |
| OutputUnit mainOutputUnit = mainFragment.outputUnit; |
| bool isProgramSplit = program.isSplit; |
| |
| List<jsAst.Statement> statements = <jsAst.Statement>[]; |
| |
| statements..add(buildGeneratedBy())..add(js.comment(HOOKS_API_USAGE)); |
| |
| if (isProgramSplit) { |
| statements.add(buildDeferredHeader()); |
| } |
| |
| // Collect the AST for the decriptors |
| Map<Element, ClassBuilder> descriptors = elementDescriptors[mainFragment]; |
| if (descriptors == null) descriptors = const {}; |
| |
| checkEverythingEmitted(descriptors.keys); |
| |
| Iterable<LibraryElement> libraries = outputLibraryLists[mainOutputUnit]; |
| if (libraries == null) libraries = <LibraryElement>[]; |
| |
| List<jsAst.Expression> parts = <jsAst.Expression>[]; |
| for (LibraryElement library in Elements.sortedByPosition(libraries)) { |
| parts.add(generateLibraryDescriptor(library, mainFragment)); |
| descriptors.remove(library); |
| } |
| |
| if (descriptors.isNotEmpty) { |
| List<Element> remainingLibraries = |
| descriptors.keys.where((Element e) => e is LibraryElement).toList(); |
| |
| // The remaining descriptors are only accessible through reflection. |
| // The program builder does not collect libraries that only |
| // contain typedefs that are used for reflection. |
| for (LibraryElement element in remainingLibraries) { |
| assert(element is LibraryElement || |
| compiler.options.hasIncrementalSupport); |
| if (element is LibraryElement) { |
| parts.add(generateLibraryDescriptor(element, mainFragment)); |
| descriptors.remove(element); |
| } |
| } |
| } |
| jsAst.ArrayInitializer descriptorsAst = new jsAst.ArrayInitializer(parts); |
| |
| // Using a named function here produces easier to read stack traces in |
| // Chrome/V8. |
| statements.add(js.statement( |
| """ |
| (function() { |
| // No renaming in the top-level function to save the locals for the |
| // nested context where they will be used more. We have to put the |
| // comment into a hole as the parser strips out comments right away. |
| #disableVariableRenaming; |
| #supportsDirectProtoAccess; |
| |
| if (#hasIncrementalSupport) { |
| #helper = #helper || Object.create(null); |
| #helper.patch = function(a) { eval(a)}; |
| #helper.schemaChange = #schemaChange; |
| #helper.addMethod = #addMethod; |
| #helper.extractStubs = |
| function(array, name, isStatic, originalDescriptor) { |
| var descriptor = Object.create(null); |
| this.addStubs(descriptor, array, name, isStatic, []); |
| return descriptor; |
| }; |
| } |
| |
| if (#isProgramSplit) { |
| /// We collect all the global state, so it can be passed to the |
| /// initializer of deferred files. |
| var #globalsHolder = Object.create(null) |
| } |
| |
| // [map] returns an object that V8 shouldn't try to optimize with a |
| // hidden class. This prevents a potential performance problem where V8 |
| // tries to build a hidden class for an object used as a hashMap. |
| // It requires fewer characters to declare a variable as a parameter than |
| // with `var`. |
| function map(x) { |
| x = Object.create(null); |
| x.x = 0; |
| delete x.x; |
| return x; |
| } |
| |
| #globalObjectSetup; |
| |
| function #isolateName() {} |
| |
| if (#isProgramSplit) { |
| #globalsHolder.#isolateName = #isolateName; |
| #globalsHolder.#initName = #initName; |
| #globalsHolder.#setupProgramName = #setupProgramName; |
| } |
| |
| init(); |
| |
| #mangledNames; |
| |
| #cspPrecompiledFunctions; |
| |
| #setupProgram; |
| |
| #functionThatReturnsNull; |
| |
| // The argument to reflectionDataParser is assigned to a temporary 'dart' |
| // so that 'dart.' will appear as the prefix to dart methods in stack |
| // traces and profile entries. |
| var dart = #descriptors; |
| |
| #setupProgramName(dart, 0); |
| |
| #getInterceptorMethods; |
| #oneShotInterceptors; |
| |
| #makeConstantList; |
| |
| // We abuse the short name used for the isolate here to store |
| // the isolate properties. This is safe as long as the real isolate |
| // object does not exist yet. |
| var ${namer.staticStateHolder} = #isolatePropertiesName; |
| |
| // Constants in checked mode call into RTI code to set type information |
| // which may need getInterceptor (and one-shot interceptor) methods, so |
| // we have to make sure that [emitGetInterceptorMethods] and |
| // [emitOneShotInterceptors] have been called. |
| #compileTimeConstants; |
| |
| // Static field initializations require the classes and compile-time |
| // constants to be set up. |
| #staticNonFinalInitializers; |
| |
| ${namer.staticStateHolder} = null; |
| |
| #deferredBoilerPlate; |
| |
| #typeToInterceptorMap; |
| |
| #lazyStaticFields; |
| |
| #isolateName = $finishIsolateConstructorName(#isolateName); |
| |
| ${namer.staticStateHolder} = new #isolateName(); |
| |
| #metadata; |
| |
| #convertToFastObject; |
| #convertToSlowObject; |
| |
| #convertGlobalObjectsToFastObjects; |
| #debugFastObjects; |
| |
| #init; |
| |
| #main; |
| })(); |
| """, |
| { |
| "disableVariableRenaming": js.comment("/* ::norenaming:: */"), |
| "hasIncrementalSupport": compiler.options.hasIncrementalSupport, |
| "helper": js('this.#', [namer.incrementalHelperName]), |
| "schemaChange": buildSchemaChangeFunction(), |
| "addMethod": buildIncrementalAddMethod(), |
| "isProgramSplit": isProgramSplit, |
| "supportsDirectProtoAccess": buildSupportsDirectProtoAccess(), |
| "globalsHolder": globalsHolder, |
| "globalObjectSetup": buildGlobalObjectSetup(isProgramSplit), |
| "isolateName": namer.isolateName, |
| "isolatePropertiesName": js(isolatePropertiesName), |
| "initName": initName, |
| "functionThatReturnsNull": buildFunctionThatReturnsNull(), |
| "mangledNames": buildMangledNames(), |
| "setupProgram": |
| buildSetupProgram(program, compiler, backend, namer, this), |
| "setupProgramName": setupProgramName, |
| "descriptors": descriptorsAst, |
| "cspPrecompiledFunctions": |
| buildCspPrecompiledFunctionFor(mainOutputUnit), |
| "getInterceptorMethods": |
| interceptorEmitter.buildGetInterceptorMethods(), |
| "oneShotInterceptors": interceptorEmitter.buildOneShotInterceptors(), |
| "makeConstantList": |
| buildMakeConstantList(program.outputContainsConstantList), |
| "compileTimeConstants": buildCompileTimeConstants( |
| mainFragment.constants, |
| isMainFragment: true), |
| "deferredBoilerPlate": buildDeferredBoilerPlate(deferredLoadHashes), |
| "staticNonFinalInitializers": |
| buildStaticNonFinalFieldInitializations(mainOutputUnit), |
| "typeToInterceptorMap": |
| interceptorEmitter.buildTypeToInterceptorMap(program), |
| "lazyStaticFields": buildLazilyInitializedStaticFields( |
| mainFragment.staticLazilyInitializedFields), |
| "metadata": buildMetadata(program, mainOutputUnit), |
| "convertToFastObject": buildConvertToFastObjectFunction(), |
| "convertToSlowObject": buildConvertToSlowObjectFunction(), |
| "convertGlobalObjectsToFastObjects": |
| buildConvertGlobalObjectToFastObjects(), |
| "debugFastObjects": buildDebugFastObjectCode(), |
| "init": buildInitFunction(program.outputContainsConstantList), |
| "main": buildMain(mainFragment.invokeMain) |
| })); |
| |
| return new jsAst.Program(statements); |
| } |
| |
| void emitMainOutputUnit(OutputUnit mainOutputUnit, jsAst.Program program) { |
| LineColumnCollector lineColumnCollector; |
| List<CodeOutputListener> codeOutputListeners; |
| if (generateSourceMap) { |
| lineColumnCollector = new LineColumnCollector(); |
| codeOutputListeners = <CodeOutputListener>[lineColumnCollector]; |
| } |
| |
| CodeOutput mainOutput = new StreamCodeOutput( |
| compiler.outputProvider('', 'js'), codeOutputListeners); |
| outputBuffers[mainOutputUnit] = mainOutput; |
| |
| mainOutput.addBuffer(jsAst.createCodeBuffer(program, compiler, |
| monitor: compiler.dumpInfoTask)); |
| |
| if (compiler.options.deferredMapUri != null) { |
| outputDeferredMap(); |
| } |
| |
| if (generateSourceMap) { |
| mainOutput.add(generateSourceMapTag( |
| compiler.options.sourceMapUri, compiler.options.outputUri)); |
| } |
| |
| mainOutput.close(); |
| |
| if (generateSourceMap) { |
| outputSourceMap(mainOutput, lineColumnCollector, '', |
| compiler.options.sourceMapUri, compiler.options.outputUri); |
| } |
| } |
| |
| /// Used by incremental compilation to patch up the prototype of |
| /// [oldConstructor] for use as prototype of [newConstructor]. |
| jsAst.Fun buildSchemaChangeFunction() { |
| if (!compiler.options.hasIncrementalSupport) return null; |
| return js(''' |
| function(newConstructor, oldConstructor, superclass) { |
| // Invariant: newConstructor.prototype has no interesting properties besides |
| // generated accessors. These are copied to oldPrototype which will be |
| // updated by other incremental changes. |
| if (superclass != null) { |
| this.inheritFrom(newConstructor, superclass); |
| } |
| var oldPrototype = oldConstructor.prototype; |
| var newPrototype = newConstructor.prototype; |
| var hasOwnProperty = Object.prototype.hasOwnProperty; |
| for (var property in newPrototype) { |
| if (hasOwnProperty.call(newPrototype, property)) { |
| // Copy generated accessors. |
| oldPrototype[property] = newPrototype[property]; |
| } |
| } |
| oldPrototype.__proto__ = newConstructor.prototype.__proto__; |
| oldPrototype.constructor = newConstructor; |
| newConstructor.prototype = oldPrototype; |
| return newConstructor; |
| }'''); |
| } |
| |
| /// Used by incremental compilation to patch up an object ([holder]) with a |
| /// new (or updated) method. [arrayOrFunction] is either the new method, or |
| /// an array containing the method (see |
| /// [ContainerBuilder.addMemberMethodFromInfo]). [name] is the name of the |
| /// new method. [isStatic] tells if method is static (or |
| /// top-level). [globalFunctionsAccess] is a reference to |
| /// [embeddedNames.GLOBAL_FUNCTIONS]. |
| jsAst.Fun buildIncrementalAddMethod() { |
| if (!compiler.options.hasIncrementalSupport) return null; |
| return js(r""" |
| function(originalDescriptor, name, holder, isStatic, globalFunctionsAccess) { |
| var arrayOrFunction = originalDescriptor[name]; |
| var method; |
| if (arrayOrFunction.constructor === Array) { |
| var existing = holder[name]; |
| var array = arrayOrFunction; |
| |
| // Each method may have a number of stubs associated. For example, if an |
| // instance method supports multiple arguments, a stub for each matching |
| // selector. There is also a getter stub for tear-off getters. For example, |
| // an instance method foo([a]) may have the following stubs: foo$0, foo$1, |
| // and get$foo (here exemplified using unminified names). |
| // [extractStubs] returns a JavaScript object whose own properties |
| // corresponds to the stubs. |
| var descriptor = |
| this.extractStubs(array, name, isStatic, originalDescriptor); |
| method = descriptor[name]; |
| |
| // Iterate through the properties of descriptor and copy the stubs to the |
| // existing holder (for instance methods, a prototype). |
| for (var property in descriptor) { |
| if (!Object.prototype.hasOwnProperty.call(descriptor, property)) continue; |
| var stub = descriptor[property]; |
| var existingStub = holder[property]; |
| if (stub === method || !existingStub || !stub.$getterStub) { |
| // Not replacing an existing getter stub. |
| holder[property] = stub; |
| continue; |
| } |
| if (!stub.$getterStub) { |
| var error = new Error('Unexpected stub.'); |
| error.stub = stub; |
| throw error; |
| } |
| |
| // Existing getter stubs need special treatment as they may already have |
| // been called and produced a closure. |
| this.pendingStubs = this.pendingStubs || []; |
| // It isn't safe to invoke the stub yet. |
| this.pendingStubs.push((function(holder, stub, existingStub, existing, |
| method) { |
| return function() { |
| var receiver = isStatic ? holder : new holder.constructor(); |
| // Invoke the existing stub to obtain the tear-off closure. |
| existingStub = existingStub.call(receiver); |
| // Invoke the new stub to create a tear-off closure we can use as a |
| // prototype. |
| stub = stub.call(receiver); |
| |
| // Copy the properties from the new tear-off's prototype to the |
| // prototype of the existing tear-off. |
| var newProto = stub.constructor.prototype; |
| var existingProto = existingStub.constructor.prototype; |
| for (var stubProperty in newProto) { |
| if (!Object.prototype.hasOwnProperty.call(newProto, stubProperty)) |
| continue; |
| existingProto[stubProperty] = newProto[stubProperty]; |
| } |
| |
| // Update all the existing stub's references to [existing] to |
| // [method]. Instance tear-offs are call-by-name, so this isn't |
| // necessary for those. |
| if (!isStatic) return; |
| for (var reference in existingStub) { |
| if (existingStub[reference] === existing) { |
| existingStub[reference] = method; |
| } |
| } |
| } |
| })(holder, stub, existingStub, existing, method)); |
| } |
| } else { |
| method = arrayOrFunction; |
| holder[name] = method; |
| } |
| if (isStatic) globalFunctionsAccess[name] = method; |
| }"""); |
| } |
| |
| Map<OutputUnit, jsAst.Expression> buildDescriptorsForOutputUnits( |
| Program program) { |
| Map<OutputUnit, jsAst.Expression> outputs = |
| new Map<OutputUnit, jsAst.Expression>(); |
| |
| for (Fragment fragment in program.deferredFragments) { |
| OutputUnit outputUnit = fragment.outputUnit; |
| |
| Map<Element, ClassBuilder> descriptors = elementDescriptors[fragment]; |
| |
| if (descriptors != null && descriptors.isNotEmpty) { |
| Iterable<LibraryElement> libraries = outputLibraryLists[outputUnit]; |
| if (libraries == null) libraries = []; |
| |
| // TODO(johnniwinther): Avoid creating [CodeBuffer]s. |
| List<jsAst.Expression> parts = <jsAst.Expression>[]; |
| for (LibraryElement library in Elements.sortedByPosition(libraries)) { |
| parts.add(generateLibraryDescriptor(library, fragment)); |
| descriptors.remove(library); |
| } |
| |
| outputs[outputUnit] = new jsAst.ArrayInitializer(parts); |
| } |
| } |
| |
| return outputs; |
| } |
| |
| void finalizeTokensInAst( |
| jsAst.Program main, Iterable<jsAst.Program> deferredParts) { |
| jsAst.TokenCounter counter = new jsAst.TokenCounter(); |
| counter.countTokens(main); |
| deferredParts.forEach(counter.countTokens); |
| task.metadataCollector.finalizeTokens(); |
| if (backend.namer is jsAst.TokenFinalizer) { |
| var finalizer = backend.namer; |
| finalizer.finalizeTokens(); |
| } |
| } |
| |
| int emitProgram(ProgramBuilder programBuilder) { |
| Program program = |
| programBuilder.buildProgram(storeFunctionTypesInMetadata: true); |
| |
| outputStaticNonFinalFieldLists = |
| programBuilder.collector.outputStaticNonFinalFieldLists; |
| outputLibraryLists = programBuilder.collector.outputLibraryLists; |
| typedefsNeededForReflection = |
| programBuilder.collector.typedefsNeededForReflection; |
| |
| assembleProgram(program); |
| |
| // Construct the ASTs for all deferred output units. |
| Map<OutputUnit, jsAst.Program> deferredParts = |
| buildOutputAstForDeferredCode(program); |
| |
| Map<OutputUnit, _DeferredOutputUnitHash> deferredHashTokens = |
| new Map<OutputUnit, _DeferredOutputUnitHash>.fromIterables( |
| deferredParts.keys, deferredParts.keys.map((OutputUnit unit) { |
| return new _DeferredOutputUnitHash(unit); |
| })); |
| |
| jsAst.Program mainOutput = |
| buildOutputAstForMain(program, deferredHashTokens); |
| |
| finalizeTokensInAst(mainOutput, deferredParts.values); |
| |
| // Emit deferred units first, so we have their hashes. |
| // Map from OutputUnit to a hash of its content. The hash uniquely |
| // identifies the code of the output-unit. It does not include |
| // boilerplate JS code, like the sourcemap directives or the hash |
| // itself. |
| Map<OutputUnit, String> deferredLoadHashes = |
| emitDeferredOutputUnits(deferredParts); |
| |
| deferredHashTokens.forEach((OutputUnit key, _DeferredOutputUnitHash token) { |
| token.setHash(deferredLoadHashes[key]); |
| }); |
| emitMainOutputUnit(program.mainFragment.outputUnit, mainOutput); |
| |
| if (backend.requiresPreamble && !backend.htmlLibraryIsLoaded) { |
| reporter.reportHintMessage(NO_LOCATION_SPANNABLE, MessageKind.PREAMBLE); |
| } |
| // Return the total program size. |
| return outputBuffers.values.fold(0, (a, b) => a + b.length); |
| } |
| |
| String generateSourceMapTag(Uri sourceMapUri, Uri fileUri) { |
| if (sourceMapUri != null && fileUri != null) { |
| String sourceMapFileName = relativize(fileUri, sourceMapUri, false); |
| return ''' |
| |
| //# sourceMappingURL=$sourceMapFileName |
| '''; |
| } |
| return ''; |
| } |
| |
| ClassBuilder getElementDescriptor(Element element, Fragment fragment) { |
| Element owner = element.library; |
| if (!element.isLibrary && |
| !element.isTopLevel && |
| !backend.isNative(element)) { |
| // For static (not top level) elements, record their code in a buffer |
| // specific to the class. For now, not supported for native classes and |
| // native elements. |
| ClassElement cls = element.enclosingClassOrCompilationUnit.declaration; |
| if (compiler.codegenWorld.directlyInstantiatedClasses.contains(cls) && |
| !backend.isNative(cls) && |
| compiler.deferredLoadTask.outputUnitForElement(element) == |
| compiler.deferredLoadTask.outputUnitForElement(cls)) { |
| owner = cls; |
| } |
| } |
| if (owner == null) { |
| reporter.internalError(element, 'Owner is null.'); |
| } |
| return elementDescriptors |
| .putIfAbsent(fragment, () => new Map<Element, ClassBuilder>()) |
| .putIfAbsent(owner, () { |
| return new ClassBuilder(owner, namer, owner.isClass); |
| }); |
| } |
| |
| /// Emits support-code for deferred loading into [output]. |
| jsAst.Statement buildDeferredBoilerPlate( |
| Map<OutputUnit, _DeferredOutputUnitHash> deferredLoadHashes) { |
| List<jsAst.Statement> parts = <jsAst.Statement>[]; |
| |
| parts.add(js.statement( |
| ''' |
| { |
| // Function for checking if a hunk is loaded given its hash. |
| #isHunkLoaded = function(hunkHash) { |
| return !!$deferredInitializers[hunkHash]; |
| }; |
| #deferredInitialized = new Object(null); |
| // Function for checking if a hunk is initialized given its hash. |
| #isHunkInitialized = function(hunkHash) { |
| return #deferredInitialized[hunkHash]; |
| }; |
| // Function for initializing a loaded hunk, given its hash. |
| #initializeLoadedHunk = function(hunkHash) { |
| $deferredInitializers[hunkHash]( |
| #globalsHolder, ${namer.staticStateHolder}); |
| #deferredInitialized[hunkHash] = true; |
| }; |
| } |
| ''', |
| { |
| "globalsHolder": globalsHolder, |
| "isHunkLoaded": |
| generateEmbeddedGlobalAccess(embeddedNames.IS_HUNK_LOADED), |
| "isHunkInitialized": |
| generateEmbeddedGlobalAccess(embeddedNames.IS_HUNK_INITIALIZED), |
| "initializeLoadedHunk": generateEmbeddedGlobalAccess( |
| embeddedNames.INITIALIZE_LOADED_HUNK), |
| "deferredInitialized": |
| generateEmbeddedGlobalAccess(embeddedNames.DEFERRED_INITIALIZED) |
| })); |
| |
| // Write a javascript mapping from Deferred import load ids (derrived |
| // from the import prefix.) to a list of lists of uris of hunks to load, |
| // and a corresponding mapping to a list of hashes used by |
| // INITIALIZE_LOADED_HUNK and IS_HUNK_LOADED. |
| Map<String, List<jsAst.LiteralString>> deferredLibraryUris = |
| new Map<String, List<jsAst.LiteralString>>(); |
| Map<String, List<_DeferredOutputUnitHash>> deferredLibraryHashes = |
| new Map<String, List<_DeferredOutputUnitHash>>(); |
| compiler.deferredLoadTask.hunksToLoad |
| .forEach((String loadId, List<OutputUnit> outputUnits) { |
| List<jsAst.LiteralString> uris = new List<jsAst.LiteralString>(); |
| List<_DeferredOutputUnitHash> hashes = |
| new List<_DeferredOutputUnitHash>(); |
| deferredLibraryHashes[loadId] = new List<_DeferredOutputUnitHash>(); |
| for (OutputUnit outputUnit in outputUnits) { |
| uris.add( |
| js.escapedString(backend.deferredPartFileName(outputUnit.name))); |
| hashes.add(deferredLoadHashes[outputUnit]); |
| } |
| |
| deferredLibraryUris[loadId] = uris; |
| deferredLibraryHashes[loadId] = hashes; |
| }); |
| |
| void emitMapping(String name, Map<String, List<jsAst.Expression>> mapping) { |
| List<jsAst.Property> properties = new List<jsAst.Property>(); |
| mapping.forEach((String key, List<jsAst.Expression> values) { |
| properties.add(new jsAst.Property( |
| js.escapedString(key), new jsAst.ArrayInitializer(values))); |
| }); |
| jsAst.Node initializer = |
| new jsAst.ObjectInitializer(properties, isOneLiner: true); |
| |
| jsAst.Node globalName = generateEmbeddedGlobalAccess(name); |
| parts.add(js.statement("# = #", [globalName, initializer])); |
| } |
| |
| emitMapping(embeddedNames.DEFERRED_LIBRARY_URIS, deferredLibraryUris); |
| emitMapping(embeddedNames.DEFERRED_LIBRARY_HASHES, deferredLibraryHashes); |
| |
| return new jsAst.Block(parts); |
| } |
| |
| Map<OutputUnit, jsAst.Program> buildOutputAstForDeferredCode( |
| Program program) { |
| if (!program.isSplit) return const <OutputUnit, jsAst.Program>{}; |
| |
| Map<OutputUnit, jsAst.Program> result = |
| new Map<OutputUnit, jsAst.Program>(); |
| |
| Map<OutputUnit, jsAst.Expression> deferredAsts = |
| buildDescriptorsForOutputUnits(program); |
| |
| for (Fragment fragment in program.deferredFragments) { |
| OutputUnit outputUnit = fragment.outputUnit; |
| jsAst.Expression libraryDescriptor = deferredAsts[outputUnit]; |
| List<jsAst.Statement> body = <jsAst.Statement>[]; |
| |
| // No renaming in the top-level function to save the locals for the |
| // nested context where they will be used more. |
| body.add(js.comment("/* ::norenaming:: ")); |
| |
| for (String globalObject in Namer.reservedGlobalObjectNames) { |
| body.add(js.statement('var #object = #globalsHolder.#object;', |
| {'globalsHolder': globalsHolder, 'object': globalObject})); |
| } |
| body |
| ..add(js.statement('var init = #globalsHolder.init;', |
| {'globalsHolder': globalsHolder})) |
| ..add(js.statement( |
| 'var $setupProgramName = ' |
| '#globalsHolder.$setupProgramName;', |
| {'globalsHolder': globalsHolder})) |
| ..add(js.statement( |
| 'var ${namer.isolateName} = ' |
| '#globalsHolder.${namer.isolateName};', |
| {'globalsHolder': globalsHolder})); |
| String typesAccess = |
| generateEmbeddedGlobalAccessString(embeddedNames.TYPES); |
| if (libraryDescriptor != null) { |
| // The argument to reflectionDataParser is assigned to a temporary |
| // 'dart' so that 'dart.' will appear as the prefix to dart methods |
| // in stack traces and profile entries. |
| body.add(js.statement('var dart = #', libraryDescriptor)); |
| |
| if (compiler.options.useContentSecurityPolicy) { |
| body.add(buildCspPrecompiledFunctionFor(outputUnit)); |
| } |
| body.add( |
| js.statement('$setupProgramName(dart, ${typesAccess}.length);')); |
| } |
| |
| body |
| ..add(buildMetadata(program, outputUnit)) |
| ..add(js.statement('${typesAccess}.push.apply(${typesAccess}, ' |
| '${namer.deferredTypesName});')); |
| |
| body.add( |
| buildCompileTimeConstants(fragment.constants, isMainFragment: false)); |
| body.add(buildStaticNonFinalFieldInitializations(outputUnit)); |
| body.add(buildLazilyInitializedStaticFields( |
| fragment.staticLazilyInitializedFields, |
| isMainFragment: false)); |
| |
| List<jsAst.Statement> statements = <jsAst.Statement>[]; |
| |
| statements |
| ..add(buildGeneratedBy()) |
| ..add(buildDeferredHeader()) |
| ..add(js.statement( |
| '${deferredInitializers}.current = ' |
| """function (#, ${namer.staticStateHolder}) { |
| # |
| } |
| """, |
| [globalsHolder, body])); |
| |
| result[outputUnit] = new jsAst.Program(statements); |
| } |
| |
| return result; |
| } |
| |
| /// Returns a map from OutputUnit to a hash of its content. The hash uniquely |
| /// identifies the code of the output-unit. It does not include |
| /// boilerplate JS code, like the sourcemap directives or the hash |
| /// itself. |
| Map<OutputUnit, String> emitDeferredOutputUnits( |
| Map<OutputUnit, jsAst.Program> outputAsts) { |
| Map<OutputUnit, String> hunkHashes = new Map<OutputUnit, String>(); |
| |
| for (OutputUnit outputUnit in outputAsts.keys) { |
| List<CodeOutputListener> outputListeners = <CodeOutputListener>[]; |
| Hasher hasher = new Hasher(); |
| outputListeners.add(hasher); |
| |
| LineColumnCollector lineColumnCollector; |
| if (generateSourceMap) { |
| lineColumnCollector = new LineColumnCollector(); |
| outputListeners.add(lineColumnCollector); |
| } |
| |
| String partPrefix = |
| backend.deferredPartFileName(outputUnit.name, addExtension: false); |
| CodeOutput output = new StreamCodeOutput( |
| compiler.outputProvider(partPrefix, 'part.js'), outputListeners); |
| |
| outputBuffers[outputUnit] = output; |
| |
| output.addBuffer(jsAst.createCodeBuffer(outputAsts[outputUnit], 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(); |
| |
| output.add('$N${deferredInitializers}["$hash"]$_=$_' |
| '${deferredInitializers}.current$N'); |
| |
| if (generateSourceMap) { |
| Uri mapUri, partUri; |
| Uri sourceMapUri = compiler.options.sourceMapUri; |
| Uri outputUri = compiler.options.outputUri; |
| |
| String partName = "$partPrefix.part"; |
| |
| if (sourceMapUri != null) { |
| String mapFileName = partName + ".js.map"; |
| List<String> mapSegments = sourceMapUri.pathSegments.toList(); |
| mapSegments[mapSegments.length - 1] = mapFileName; |
| mapUri = |
| compiler.options.sourceMapUri.replace(pathSegments: mapSegments); |
| } |
| |
| if (outputUri != null) { |
| String partFileName = partName + ".js"; |
| List<String> partSegments = outputUri.pathSegments.toList(); |
| partSegments[partSegments.length - 1] = partFileName; |
| partUri = |
| compiler.options.outputUri.replace(pathSegments: partSegments); |
| } |
| |
| output.add(generateSourceMapTag(mapUri, partUri)); |
| output.close(); |
| outputSourceMap(output, lineColumnCollector, partName, mapUri, partUri); |
| } else { |
| output.close(); |
| } |
| |
| hunkHashes[outputUnit] = hash; |
| } |
| return hunkHashes; |
| } |
| |
| jsAst.Comment buildGeneratedBy() { |
| List<String> options = []; |
| if (compiler.commonElements.mirrorsLibrary != null) options.add('mirrors'); |
| if (compiler.options.useContentSecurityPolicy) options.add("CSP"); |
| return new jsAst.Comment(generatedBy(compiler, flavor: options.join(", "))); |
| } |
| |
| void outputSourceMap( |
| CodeOutput output, LineColumnProvider lineColumnProvider, String name, |
| [Uri sourceMapUri, Uri fileUri]) { |
| if (!generateSourceMap) 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(); |
| } |
| |
| void outputDeferredMap() { |
| 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(); |
| } |
| |
| void invalidateCaches() { |
| if (!compiler.options.hasIncrementalSupport) return; |
| if (cachedElements.isEmpty) return; |
| for (Element element in backend.codegenEnqueuer.newlyEnqueuedElements) { |
| if (element.isInstanceMember) { |
| cachedClassBuilders.remove(element.enclosingClass); |
| |
| nativeEmitter.cachedBuilders.remove(element.enclosingClass); |
| } |
| } |
| } |
| } |