| // 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 '../common_elements.dart'; |
| import '../constants/values.dart' show ConstantValue; |
| import '../deferred_load/output_unit.dart' show OutputUnit; |
| import '../elements/entities.dart'; |
| import '../elements/types.dart'; |
| import '../js/js.dart' as js show Expression, Name, Statement, TokenFinalizer; |
| import '../js/js_debug.dart' as js show nodeToString; |
| import '../js_backend/runtime_types_codegen.dart'; |
| import 'js_emitter.dart' show MetadataCollector; |
| |
| class Program { |
| final List<Fragment> fragments; |
| final bool outputContainsConstantList; |
| final bool needsNativeSupport; |
| |
| // 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.typeToInterceptorMap, this._metadataCollector, |
| this.finalizers, |
| {this.needsNativeSupport, this.outputContainsConstantList}) { |
| assert(needsNativeSupport != null); |
| assert(outputContainsConstantList != null); |
| } |
| |
| void mergeOutputUnitMetadata(OutputUnit target, OutputUnit source) { |
| _metadataCollector.mergeOutputUnitMetadata(target, source); |
| } |
| |
| /// Accessor for the list of type 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 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); |
| |
| @override |
| bool get isMainFragment => true; |
| |
| @override |
| String toString() { |
| return 'MainFragment()'; |
| } |
| } |
| |
| /// 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); |
| |
| @override |
| bool get isMainFragment => false; |
| |
| @override |
| String toString() { |
| return 'DeferredFragment(name=${name})'; |
| } |
| } |
| |
| class Constant { |
| final js.Name name; |
| final ConstantValue value; |
| |
| Constant(this.name, this.value); |
| |
| @override |
| String toString() { |
| return 'Constant(name=${name.key},value=${value.toStructuredText(null)})'; |
| } |
| } |
| |
| class Library { |
| /// The element should only be used during the transition to the new model. |
| /// Uses indicate missing information in the model. |
| final LibraryEntity element; |
| |
| final String uri; |
| final List<StaticMethod> statics; |
| final List<Class> classes; |
| final List<ClassTypeData> classTypeData; |
| |
| Library( |
| this.element, this.uri, this.statics, this.classes, this.classTypeData); |
| |
| @override |
| String toString() { |
| return 'Library(uri=${uri},element=${element})'; |
| } |
| } |
| |
| class StaticField { |
| /// The element should only be used during the transition to the new model. |
| /// Uses indicate missing information in the model. |
| final FieldEntity element; |
| |
| final js.Name name; |
| final js.Name getterName; |
| // TODO(floitsch): the holder for static fields is the isolate object. We |
| // could remove this field and use the isolate object directly. |
| final js.Expression code; |
| final bool isFinal; |
| final bool isLazy; |
| final bool isInitializedByConstant; |
| final bool usesNonNullableInitialization; |
| |
| StaticField(this.element, this.name, this.getterName, this.code, |
| {this.isFinal, |
| this.isLazy, |
| this.isInitializedByConstant = false, |
| this.usesNonNullableInitialization = false}); |
| |
| @override |
| String toString() { |
| return 'StaticField(name=${name.key},element=${element})'; |
| } |
| } |
| |
| class ClassTypeData { |
| /// The element should only be used during the transition to the new model. |
| /// Uses indicate missing information in the model. |
| final ClassEntity element; |
| |
| final ClassChecks classChecks; |
| final Set<TypeVariableType> namedTypeVariables = {}; |
| |
| ClassTypeData(this.element, this.classChecks); |
| |
| bool isTriviallyChecked(CommonElements commonElements) => |
| classChecks.checks.every((TypeCheck check) => |
| check.cls == commonElements.objectClass || check.cls == element); |
| } |
| |
| class Class { |
| /// The element should only be used during the transition to the new model. |
| /// Uses indicate missing information in the model. |
| final ClassEntity element; |
| |
| // TODO(joshualitt): Now that we collect all rti needed classes and handle |
| // them separately, we should investigate whether or not we still need to |
| // store the type data on the class. |
| final ClassTypeData typeData; |
| |
| final js.Name name; |
| Class _superclass; |
| Class _mixinClass; |
| final List<Method> methods; |
| final List<Field> fields; |
| final List<StubMethod> isChecks; |
| final List<StubMethod> checkedSetters; |
| final List<StubMethod> gettersSetters; |
| |
| /// Stub methods for this class that are call stubs for getters. |
| final List<StubMethod> callStubs; |
| |
| /// noSuchMethod stubs in the special case that the class is Object. |
| final List<StubMethod> noSuchMethodStubs; |
| |
| final bool hasRtiField; // Per-instance runtime type information pseudo-field. |
| final bool onlyForRti; |
| final bool onlyForConstructor; |
| final bool isDirectlyInstantiated; |
| final bool isNative; |
| |
| /// `true` if this is the one class that is the root of all 'Closure' classes. |
| final bool isClosureBaseClass; |
| |
| /// If non-null, this class is used as a base class for closures with a fixed |
| /// small number of arguments in order to inherit `Function.apply` |
| /// metadata. The value is the fixed number of arguments. |
| final int sharedClosureApplyMetadata; |
| |
| final bool isMixinApplicationWithMembers; |
| |
| // 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.typeData, |
| this.name, |
| this.methods, |
| this.fields, |
| this.callStubs, |
| this.noSuchMethodStubs, |
| this.checkedSetters, |
| this.gettersSetters, |
| this.isChecks, |
| this.functionTypeIndex, |
| {this.hasRtiField, |
| this.onlyForRti, |
| this.onlyForConstructor, |
| this.isDirectlyInstantiated, |
| this.isNative, |
| this.isClosureBaseClass, |
| this.sharedClosureApplyMetadata, |
| this.isMixinApplicationWithMembers}) { |
| assert(onlyForRti != null); |
| assert(onlyForConstructor != null); |
| assert(isDirectlyInstantiated != null); |
| assert(isNative != null); |
| assert(isClosureBaseClass != null); |
| } |
| |
| bool get isSimpleMixinApplication => false; |
| |
| Class get superclass => _superclass; |
| |
| void setSuperclass(Class superclass) { |
| _superclass = superclass; |
| } |
| |
| Class get mixinClass => _mixinClass; |
| |
| void setMixinClass(Class mixinClass) { |
| _mixinClass = mixinClass; |
| } |
| |
| js.Name get superclassName => superclass?.name; |
| |
| @override |
| String toString() => 'Class(name=${name.key},element=$element)'; |
| } |
| |
| class MixinApplication extends Class { |
| MixinApplication( |
| ClassEntity element, |
| ClassTypeData typeData, |
| js.Name name, |
| List<Field> instanceFields, |
| List<StubMethod> callStubs, |
| List<StubMethod> checkedSetters, |
| List<StubMethod> gettersSetters, |
| List<StubMethod> isChecks, |
| js.Expression functionTypeIndex, |
| {bool hasRtiField, |
| bool onlyForRti, |
| bool onlyForConstructor, |
| bool isDirectlyInstantiated}) |
| : super( |
| element, |
| typeData, |
| name, |
| const <Method>[], |
| instanceFields, |
| callStubs, |
| const <StubMethod>[], |
| checkedSetters, |
| gettersSetters, |
| isChecks, |
| functionTypeIndex, |
| hasRtiField: hasRtiField, |
| onlyForRti: onlyForRti, |
| onlyForConstructor: onlyForConstructor, |
| isDirectlyInstantiated: isDirectlyInstantiated, |
| isNative: false, |
| isClosureBaseClass: false, |
| isMixinApplicationWithMembers: false); |
| |
| @override |
| bool get isSimpleMixinApplication => true; |
| |
| @override |
| String toString() => 'Mixin(name=${name.key},element=$element)'; |
| } |
| |
| /// 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 FieldEntity 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; |
| |
| final ConstantValue initializerInAllocator; |
| |
| final ConstantValue constantValue; |
| |
| final bool isElided; |
| |
| // TODO(floitsch): support renamed fields. |
| Field( |
| this.element, |
| this.name, |
| this.accessorName, |
| this.getterFlags, |
| this.setterFlags, |
| this.needsCheckedSetter, |
| this.initializerInAllocator, |
| this.constantValue, |
| this.isElided); |
| |
| 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; |
| |
| @override |
| String toString() { |
| return 'Field(name=${name.key},element=${element})'; |
| } |
| } |
| |
| abstract class Method { |
| /// The element should only be used during the transition to the new model. |
| /// Uses indicate missing information in the model. |
| final MemberEntity 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 int applyIndex; |
| |
| // Is non-null if [needsTearOff]. |
| // |
| // 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. [optionalParameterDefaultValues] is |
| // only required and stored here if the method [canBeApplied]. The count is |
| // always stored to help select specialized tear-off paths. |
| 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(FunctionEntity element, js.Name name, js.Expression code, |
| this.parameterStubs, this.callName, |
| {this.needsTearOff, |
| this.tearOffName, |
| this.canBeApplied, |
| this.requiredParameterCount, |
| this.optionalParameterDefaultValues, |
| this.functionType, |
| this.applyIndex}) |
| : super(element, name, code) { |
| assert(needsTearOff != null); |
| assert(!needsTearOff || tearOffName != null); |
| assert(canBeApplied != null); |
| assert(!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 the tear-off needs to access methods directly rather than rely |
| /// on JavaScript prototype lookup. This happens when a tear-off getter is |
| /// called via `super.method` and there is a shadowing definition of `method` |
| /// in some sublcass. |
| // TODO(sra): Consider instead having an alias per stub, creating tear-off |
| // trampolines that target the stubs. |
| final bool tearOffNeedsDirectAccess; |
| |
| /// 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; |
| |
| final bool inheritsApplyMetadata; |
| |
| /// True if the interceptor calling convention is used for this method. |
| final bool isIntercepted; |
| |
| /// Name called via the general 'catch all' path of Function.apply. |
| ///final js.Name applyName; |
| |
| InstanceMethod( |
| FunctionEntity element, |
| js.Name name, |
| js.Expression code, |
| List<ParameterStubMethod> parameterStubs, |
| js.Name callName, { |
| bool needsTearOff, |
| js.Name tearOffName, |
| this.aliasName, |
| this.tearOffNeedsDirectAccess, |
| bool canBeApplied, |
| int requiredParameterCount, |
| /* List | Map */ optionalParameterDefaultValues, |
| this.isClosureCallMethod, |
| this.inheritsApplyMetadata, |
| this.isIntercepted, |
| js.Expression functionType, |
| int applyIndex, |
| }) : super(element, name, code, parameterStubs, callName, |
| needsTearOff: needsTearOff, |
| tearOffName: tearOffName, |
| canBeApplied: canBeApplied, |
| requiredParameterCount: requiredParameterCount, |
| optionalParameterDefaultValues: optionalParameterDefaultValues, |
| functionType: functionType, |
| applyIndex: applyIndex) { |
| assert(isClosureCallMethod != null); |
| } |
| |
| @override |
| bool get isStatic => false; |
| |
| @override |
| String toString() { |
| return 'InstanceMethod(name=${name.key},element=${element}' |
| ',code=${js.nodeToString(code)})'; |
| } |
| } |
| |
| /// 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, {MemberEntity element}) |
| : super(element, name, code); |
| |
| @override |
| String toString() { |
| return 'StubMethod(name=${name.key},element=${element}' |
| ',code=${js.nodeToString(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, |
| {MemberEntity element}) |
| : super(name, code, element: element); |
| |
| @override |
| String toString() { |
| return 'ParameterStubMethod(name=${name.key}, callName=${callName?.key}' |
| ', element=${element}' |
| ', code=${js.nodeToString(code)})'; |
| } |
| } |
| |
| abstract class StaticMethod implements Method {} |
| |
| class StaticDartMethod extends DartMethod implements StaticMethod { |
| StaticDartMethod(FunctionEntity element, js.Name name, js.Expression code, |
| List<ParameterStubMethod> parameterStubs, js.Name callName, |
| {bool needsTearOff, |
| js.Name tearOffName, |
| bool canBeApplied, |
| int requiredParameterCount, |
| /* List | Map */ optionalParameterDefaultValues, |
| js.Expression functionType, |
| int applyIndex}) |
| : super(element, name, code, parameterStubs, callName, |
| needsTearOff: needsTearOff, |
| tearOffName: tearOffName, |
| canBeApplied: canBeApplied, |
| requiredParameterCount: requiredParameterCount, |
| optionalParameterDefaultValues: optionalParameterDefaultValues, |
| functionType: functionType, |
| applyIndex: applyIndex); |
| |
| @override |
| bool get isStatic => true; |
| |
| @override |
| String toString() { |
| return 'StaticDartMethod(name=${name.key},element=${element}' |
| ',code=${js.nodeToString(code)})'; |
| } |
| } |
| |
| class StaticStubMethod extends StubMethod implements StaticMethod { |
| LibraryEntity library; |
| StaticStubMethod(this.library, js.Name name, js.Expression code) |
| : super(name, code); |
| |
| @override |
| String toString() { |
| return 'StaticStubMethod(name=${name.key},element=${element}}' |
| ',code=${js.nodeToString(code)})'; |
| } |
| } |