| // 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.new_js_emitter.model; |
| |
| import '../constants/values.dart' show ConstantValue; |
| import '../deferred_load.dart' show OutputUnit; |
| import '../elements/elements.dart' show Element; |
| import '../js/js.dart' as js show Expression, Name, Statement, TokenFinalizer; |
| import 'js_emitter.dart' show MetadataCollector; |
| |
| class Program { |
| final List<Fragment> fragments; |
| final List<Holder> holders; |
| final bool outputContainsConstantList; |
| final bool needsNativeSupport; |
| final bool hasIsolateSupport; |
| |
| /// A map from load id to the list of fragments that need to be loaded. |
| final Map<String, List<Fragment>> loadMap; |
| |
| /// A map from names to strings. |
| /// |
| /// This map is needed to support `const Symbol` expressions; |
| final Map<js.Name, String> symbolsMap; |
| |
| // If this field is not `null` then its value must be emitted in the embedded |
| // global `TYPE_TO_INTERCEPTOR_MAP`. The map references constants and classes. |
| final js.Expression typeToInterceptorMap; |
| |
| // TODO(floitsch): we should store the metadata directly instead of storing |
| // the collector. However, the old emitter still updates the data. |
| final MetadataCollector _metadataCollector; |
| final Iterable<js.TokenFinalizer> finalizers; |
| |
| Program(this.fragments, this.holders, this.loadMap, this.symbolsMap, |
| this.typeToInterceptorMap, this._metadataCollector, this.finalizers, |
| {this.needsNativeSupport, |
| this.outputContainsConstantList, |
| this.hasIsolateSupport}) { |
| assert(needsNativeSupport != null); |
| assert(outputContainsConstantList != null); |
| assert(hasIsolateSupport != null); |
| } |
| |
| /// A list of metadata expressions. |
| /// |
| /// This list must be emitted in the `METADATA` embedded global. |
| /// The list references constants and must hence be emitted after constants |
| /// have been initialized. |
| /// |
| /// Note: the metadata is derived from the task's `metadataCollector`. The |
| /// list must not be emitted before all operations on it are done. For |
| /// example, the old emitter generates metadata when emitting reflection |
| /// data. |
| js.Expression get metadata => _metadataCollector.globalMetadata; |
| |
| /// Accessor for the list of metadata entries for a given [OutputUnit]. |
| /// |
| /// There is one list for each output unit. The list belonging to the main |
| /// unit must be emitted in the `TYPES` embedded global. The list references |
| /// constants and must hence be emitted after constants have been initialized. |
| /// |
| /// Note: the metadata is derived from the task's `metadataCollector`. The |
| /// list is only a placeholder and will be filled in once metadata collection |
| /// is finalized. |
| js.Expression metadataTypesForOutputUnit(OutputUnit unit) { |
| return _metadataCollector.getTypesForOutputUnit(unit); |
| } |
| |
| bool get isSplit => fragments.length > 1; |
| Iterable<Fragment> get deferredFragments => fragments.skip(1); |
| Fragment get mainFragment => fragments.first; |
| } |
| |
| /** |
| * This class represents a JavaScript object that contains static state, like |
| * classes or functions. |
| */ |
| class Holder { |
| final String name; |
| final int index; |
| final bool isStaticStateHolder; |
| final bool isConstantsHolder; |
| |
| Holder(this.name, this.index, |
| {this.isStaticStateHolder: false, this.isConstantsHolder: false}); |
| } |
| |
| /** |
| * This class represents one output file. |
| * |
| * If no library is deferred, there is only one [Fragment] of type |
| * [MainFragment]. |
| */ |
| abstract class Fragment { |
| /// The outputUnit should only be used during the transition to the new model. |
| /// Uses indicate missing information in the model. |
| final OutputUnit outputUnit; |
| |
| final List<Library> libraries; |
| final List<Constant> constants; |
| // TODO(floitsch): should we move static fields into libraries or classes? |
| final List<StaticField> staticNonFinalFields; |
| // TODO(floitsch): lazy fields should be in their library or even class. |
| final List<StaticField> staticLazilyInitializedFields; |
| |
| /// Output file name without extension. |
| final String outputFileName; |
| |
| Fragment( |
| this.outputUnit, |
| this.outputFileName, |
| this.libraries, |
| this.staticNonFinalFields, |
| this.staticLazilyInitializedFields, |
| this.constants); |
| |
| bool get isMainFragment; |
| } |
| |
| /** |
| * The main output file. |
| * |
| * This code emitted from this [Fragment] must be loaded first. It can then load |
| * other [DeferredFragment]s. |
| */ |
| class MainFragment extends Fragment { |
| final js.Statement invokeMain; |
| |
| MainFragment( |
| OutputUnit outputUnit, |
| String outputFileName, |
| this.invokeMain, |
| List<Library> libraries, |
| List<StaticField> staticNonFinalFields, |
| List<StaticField> staticLazilyInitializedFields, |
| List<Constant> constants) |
| : super(outputUnit, outputFileName, libraries, staticNonFinalFields, |
| staticLazilyInitializedFields, constants); |
| |
| bool get isMainFragment => true; |
| } |
| |
| /** |
| * An output (file) for deferred code. |
| */ |
| class DeferredFragment extends Fragment { |
| final String name; |
| |
| DeferredFragment( |
| OutputUnit outputUnit, |
| String outputFileName, |
| this.name, |
| List<Library> libraries, |
| List<StaticField> staticNonFinalFields, |
| List<StaticField> staticLazilyInitializedFields, |
| List<Constant> constants) |
| : super(outputUnit, outputFileName, libraries, staticNonFinalFields, |
| staticLazilyInitializedFields, constants); |
| |
| bool get isMainFragment => false; |
| } |
| |
| class Constant { |
| final js.Name name; |
| final Holder holder; |
| final ConstantValue value; |
| |
| Constant(this.name, this.holder, this.value); |
| } |
| |
| abstract class FieldContainer { |
| List<Field> get staticFieldsForReflection; |
| } |
| |
| class Library implements FieldContainer { |
| /// The element should only be used during the transition to the new model. |
| /// Uses indicate missing information in the model. |
| final Element element; |
| |
| final String uri; |
| final List<StaticMethod> statics; |
| final List<Class> classes; |
| |
| final List<Field> staticFieldsForReflection; |
| |
| Library(this.element, this.uri, this.statics, this.classes, |
| this.staticFieldsForReflection); |
| } |
| |
| class StaticField { |
| /// The element should only be used during the transition to the new model. |
| /// Uses indicate missing information in the model. |
| final Element element; |
| |
| js.Name name; |
| // TODO(floitsch): the holder for static fields is the isolate object. We |
| // could remove this field and use the isolate object directly. |
| final Holder holder; |
| final js.Expression code; |
| final bool isFinal; |
| final bool isLazy; |
| |
| StaticField(this.element, this.name, this.holder, this.code, this.isFinal, |
| this.isLazy); |
| } |
| |
| class Class implements FieldContainer { |
| /// The element should only be used during the transition to the new model. |
| /// Uses indicate missing information in the model. |
| final Element element; |
| |
| final js.Name name; |
| final Holder holder; |
| Class _superclass; |
| final List<Method> methods; |
| final List<Field> fields; |
| final List<StubMethod> isChecks; |
| final List<StubMethod> checkedSetters; |
| |
| /// Stub methods for this class that are call stubs for getters. |
| final List<StubMethod> callStubs; |
| |
| /// Stub methods for this class handling reads to type variables. |
| final List<StubMethod> typeVariableReaderStubs; |
| |
| /// noSuchMethod stubs in the special case that the class is Object. |
| final List<StubMethod> noSuchMethodStubs; |
| final List<Field> staticFieldsForReflection; |
| final bool onlyForRti; |
| final bool isDirectlyInstantiated; |
| final bool isNative; |
| |
| // If the class implements a function type, and the type is encoded in the |
| // metatada table, then this field contains the index into that field. |
| final js.Expression functionTypeIndex; |
| |
| /// Whether the class must be evaluated eagerly. |
| bool isEager = false; |
| |
| /// Leaf tags. See [NativeEmitter.prepareNativeClasses]. |
| List<String> nativeLeafTags; |
| |
| /// Non-leaf tags. See [NativeEmitter.prepareNativeClasses]. |
| List<String> nativeNonLeafTags; |
| |
| /// Native extensions. See [NativeEmitter.prepareNativeClasses]. |
| List<Class> nativeExtensions; |
| |
| Class( |
| this.element, |
| this.name, |
| this.holder, |
| this.methods, |
| this.fields, |
| this.staticFieldsForReflection, |
| this.callStubs, |
| this.typeVariableReaderStubs, |
| this.noSuchMethodStubs, |
| this.checkedSetters, |
| this.isChecks, |
| this.functionTypeIndex, |
| {this.onlyForRti, |
| this.isDirectlyInstantiated, |
| this.isNative}) { |
| assert(onlyForRti != null); |
| assert(isDirectlyInstantiated != null); |
| assert(isNative != null); |
| } |
| |
| bool get isMixinApplication => false; |
| Class get superclass => _superclass; |
| |
| void setSuperclass(Class superclass) { |
| _superclass = superclass; |
| } |
| |
| js.Name get superclassName => superclass == null ? null : superclass.name; |
| |
| int get superclassHolderIndex => |
| (superclass == null) ? 0 : superclass.holder.index; |
| } |
| |
| class MixinApplication extends Class { |
| Class _mixinClass; |
| |
| MixinApplication( |
| Element element, |
| js.Name name, |
| Holder holder, |
| List<Field> instanceFields, |
| List<Field> staticFieldsForReflection, |
| List<StubMethod> callStubs, |
| List<StubMethod> typeVariableReaderStubs, |
| List<StubMethod> checkedSetters, |
| List<StubMethod> isChecks, |
| js.Expression functionTypeIndex, |
| {bool onlyForRti, |
| bool isDirectlyInstantiated}) |
| : super( |
| element, |
| name, |
| holder, |
| const <Method>[], |
| instanceFields, |
| staticFieldsForReflection, |
| callStubs, |
| typeVariableReaderStubs, |
| const <StubMethod>[], |
| checkedSetters, |
| isChecks, |
| functionTypeIndex, |
| onlyForRti: onlyForRti, |
| isDirectlyInstantiated: isDirectlyInstantiated, |
| isNative: false); |
| |
| bool get isMixinApplication => true; |
| Class get mixinClass => _mixinClass; |
| |
| void setMixinClass(Class mixinClass) { |
| _mixinClass = mixinClass; |
| } |
| } |
| |
| /// A field. |
| /// |
| /// In general represents an instance field, but for reflection may also |
| /// represent static fields. |
| class Field { |
| /// The element should only be used during the transition to the new model. |
| /// Uses indicate missing information in the model. |
| final Element element; |
| |
| final js.Name name; |
| final js.Name accessorName; |
| |
| /// 00: Does not need any getter. |
| /// 01: function() { return this.field; } |
| /// 10: function(receiver) { return receiver.field; } |
| /// 11: function(receiver) { return this.field; } |
| final int getterFlags; |
| |
| /// 00: Does not need any setter. |
| /// 01: function(value) { this.field = value; } |
| /// 10: function(receiver, value) { receiver.field = value; } |
| /// 11: function(receiver, value) { this.field = value; } |
| final int setterFlags; |
| |
| final bool needsCheckedSetter; |
| |
| // TODO(floitsch): support renamed fields. |
| Field(this.element, this.name, this.accessorName, this.getterFlags, |
| this.setterFlags, this.needsCheckedSetter); |
| |
| bool get needsGetter => getterFlags != 0; |
| bool get needsUncheckedSetter => setterFlags != 0; |
| |
| bool get needsInterceptedGetter => getterFlags > 1; |
| bool get needsInterceptedSetter => setterFlags > 1; |
| |
| bool get needsInterceptedGetterOnReceiver => getterFlags == 2; |
| bool get needsInterceptedSetterOnReceiver => setterFlags == 2; |
| |
| bool get needsInterceptedGetterOnThis => getterFlags == 3; |
| bool get needsInterceptedSetterOnThis => setterFlags == 3; |
| } |
| |
| abstract class Method { |
| /// The element should only be used during the transition to the new model. |
| /// Uses indicate missing information in the model. |
| final Element element; |
| |
| /// The name of the method. If the method is a [ParameterStubMethod] for a |
| /// static function, then the name can be `null`. In that case, only the |
| /// [ParameterStubMethod.callName] should be used. |
| final js.Name name; |
| final js.Expression code; |
| |
| Method(this.element, this.name, this.code); |
| } |
| |
| /// A method that corresponds to a method in the original Dart program. |
| abstract class DartMethod extends Method { |
| final bool needsTearOff; |
| final js.Name tearOffName; |
| final List<ParameterStubMethod> parameterStubs; |
| final bool canBeApplied; |
| final bool canBeReflected; |
| |
| // Is non-null if [needsTearOff] or [canBeReflected]. |
| // |
| // If the type is encoded in the metadata table this field contains an index |
| // into the table. Otherwise the type contains type variables in which case |
| // this field holds a function computing the function signature. |
| final js.Expression functionType; |
| |
| // Signature information for this method. This is only required and stored |
| // here if the method [canBeApplied] or [canBeReflected] |
| final int requiredParameterCount; |
| final /* Map | List */ optionalParameterDefaultValues; |
| |
| // If this method can be torn off, contains the name of the corresponding |
| // call method. For example, for the member `foo$1$name` it would be |
| // `call$1$name` (in unminified mode). |
| final js.Name callName; |
| |
| DartMethod(Element element, js.Name name, js.Expression code, |
| this.parameterStubs, this.callName, |
| {this.needsTearOff, |
| this.tearOffName, |
| this.canBeApplied, |
| this.canBeReflected, |
| this.requiredParameterCount, |
| this.optionalParameterDefaultValues, |
| this.functionType}) |
| : super(element, name, code) { |
| assert(needsTearOff != null); |
| assert(!needsTearOff || tearOffName != null); |
| assert(canBeApplied != null); |
| assert(canBeReflected != null); |
| assert((!canBeReflected && !canBeApplied) || |
| (requiredParameterCount != null && |
| optionalParameterDefaultValues != null)); |
| } |
| |
| bool get isStatic; |
| } |
| |
| class InstanceMethod extends DartMethod { |
| /// An alternative name for this method. This is used to model calls to |
| /// a method via `super`. If [aliasName] is non-null, the emitter has to |
| /// ensure that this method is registered on the prototype under both [name] |
| /// and [aliasName]. |
| final js.Name aliasName; |
| |
| /// True if this is the implicit `call` instance method of an anonymous |
| /// closure. This predicate is false for explicit `call` methods and for |
| /// functions that can be torn off. |
| final bool isClosureCallMethod; |
| |
| InstanceMethod(Element element, js.Name name, js.Expression code, |
| List<ParameterStubMethod> parameterStubs, js.Name callName, |
| {bool needsTearOff, |
| js.Name tearOffName, |
| this.aliasName, |
| bool canBeApplied, |
| bool canBeReflected, |
| int requiredParameterCount, |
| /* List | Map */ optionalParameterDefaultValues, |
| this.isClosureCallMethod, |
| js.Expression functionType}) |
| : super(element, name, code, parameterStubs, callName, |
| needsTearOff: needsTearOff, |
| tearOffName: tearOffName, |
| canBeApplied: canBeApplied, |
| canBeReflected: canBeReflected, |
| requiredParameterCount: requiredParameterCount, |
| optionalParameterDefaultValues: optionalParameterDefaultValues, |
| functionType: functionType) { |
| assert(isClosureCallMethod != null); |
| } |
| |
| bool get isStatic => false; |
| } |
| |
| /// A method that is generated by the backend and has not direct correspondence |
| /// to a method in the original Dart program. Examples are getter and setter |
| /// stubs and stubs to dispatch calls to methods with optional parameters. |
| class StubMethod extends Method { |
| StubMethod(js.Name name, js.Expression code, {Element element}) |
| : super(element, name, code); |
| } |
| |
| /// A stub that adapts and redirects to the main method (the one containing) |
| /// the actual code. |
| /// |
| /// For example, given a method `foo$2(x, [y: 499])` a possible parameter |
| /// stub-method could be `foo$1(x) => foo$2(x, 499)`. |
| /// |
| /// ParameterStubMethods are always attached to (static or instance) methods. |
| class ParameterStubMethod extends StubMethod { |
| /// The `call` name of this stub. |
| /// |
| /// When an instance method is torn off, it is invoked as a `call` member and |
| /// not it's original name anymore. The [callName] provides the stub's |
| /// name when it is used this way. |
| /// |
| /// If a stub's member can not be torn off, the [callName] is `null`. |
| js.Name callName; |
| |
| ParameterStubMethod(js.Name name, this.callName, js.Expression code) |
| : super(name, code); |
| } |
| |
| abstract class StaticMethod implements Method { |
| Holder get holder; |
| } |
| |
| class StaticDartMethod extends DartMethod implements StaticMethod { |
| final Holder holder; |
| |
| StaticDartMethod( |
| Element element, |
| js.Name name, |
| this.holder, |
| js.Expression code, |
| List<ParameterStubMethod> parameterStubs, |
| js.Name callName, |
| {bool needsTearOff, |
| js.Name tearOffName, |
| bool canBeApplied, |
| bool canBeReflected, |
| int requiredParameterCount, |
| /* List | Map */ optionalParameterDefaultValues, |
| js.Expression functionType}) |
| : super(element, name, code, parameterStubs, callName, |
| needsTearOff: needsTearOff, |
| tearOffName: tearOffName, |
| canBeApplied: canBeApplied, |
| canBeReflected: canBeReflected, |
| requiredParameterCount: requiredParameterCount, |
| optionalParameterDefaultValues: optionalParameterDefaultValues, |
| functionType: functionType); |
| |
| bool get isStatic => true; |
| } |
| |
| class StaticStubMethod extends StubMethod implements StaticMethod { |
| Holder holder; |
| StaticStubMethod(js.Name name, this.holder, js.Expression code) |
| : super(name, code); |
| } |