| // 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; | |
| 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 'metadata_collector.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, { | |
| required this.needsNativeSupport, | |
| required this.outputContainsConstantList, | |
| }); | |
| 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; | |
| // TODO(50081): We should collect a list of stub objects in the model to | |
| // support different stubs in different units, and merging units and their | |
| // stubs. | |
| final js.Expression? recordTypeStubs; | |
| MainFragment( | |
| OutputUnit outputUnit, | |
| String outputFileName, | |
| this.invokeMain, | |
| this.recordTypeStubs, | |
| 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; | |
| // Null for static non-final fields. | |
| 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; | |
| StaticField( | |
| this.element, | |
| this.name, | |
| this.getterName, | |
| this.code, { | |
| required this.isFinal, | |
| required this.isLazy, | |
| this.isInitializedByConstant = 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, | |
| ); | |
| } | |
| // TODO(sra): There are a lot of fields here that apply in limited cases | |
| // (e.g. isClosureBaseClass is true for one class). Can we refactor the special | |
| // case information, for example, into a subclass, or an extension object? | |
| 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 | |
| // metadata table, then this field contains the index into that field. | |
| final js.Expression? functionTypeIndex; | |
| final int? recordShapeTag; | |
| final js.Expression? recordShapeRecipe; | |
| /// 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, { | |
| required this.hasRtiField, | |
| required this.onlyForRti, | |
| required this.onlyForConstructor, | |
| required this.isDirectlyInstantiated, | |
| required this.isNative, | |
| required this.isClosureBaseClass, | |
| this.sharedClosureApplyMetadata, | |
| required this.isMixinApplicationWithMembers, | |
| this.recordShapeRecipe, | |
| this.recordShapeTag, | |
| }); | |
| bool get isSimpleMixinApplication => false; | |
| 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, { | |
| required super.hasRtiField, | |
| required super.onlyForRti, | |
| required super.onlyForConstructor, | |
| required super.isDirectlyInstantiated, | |
| }) : super( | |
| element, | |
| typeData, | |
| name, | |
| const <Method>[], | |
| instanceFields, | |
| callStubs, | |
| const <StubMethod>[], | |
| checkedSetters, | |
| gettersSetters, | |
| isChecks, | |
| functionTypeIndex, | |
| 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 Object? /* 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 super.element, | |
| super.name, | |
| super.code, | |
| this.parameterStubs, | |
| this.callName, { | |
| required this.needsTearOff, | |
| this.tearOffName, | |
| required this.canBeApplied, | |
| required this.requiredParameterCount, | |
| this.optionalParameterDefaultValues, | |
| this.functionType, | |
| required this.applyIndex, | |
| }) { | |
| assert(!needsTearOff || tearOffName != null); | |
| assert(!canBeApplied || 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 subclass. | |
| // 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( | |
| super.element, | |
| super.name, | |
| super.code, | |
| super.parameterStubs, | |
| super.callName, { | |
| required super.needsTearOff, | |
| super.tearOffName, | |
| this.aliasName, | |
| required this.tearOffNeedsDirectAccess, | |
| required super.canBeApplied, | |
| required super.requiredParameterCount, | |
| /* List | Map */ super.optionalParameterDefaultValues, | |
| required this.isClosureCallMethod, | |
| required this.inheritsApplyMetadata, | |
| required this.isIntercepted, | |
| super.functionType, | |
| required super.applyIndex, | |
| }); | |
| @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( | |
| super.name, | |
| this.callName, | |
| super.code, { | |
| required super.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( | |
| super.element, | |
| super.name, | |
| super.code, | |
| super.parameterStubs, | |
| super.callName, { | |
| required super.needsTearOff, | |
| super.tearOffName, | |
| required super.canBeApplied, | |
| required super.requiredParameterCount, | |
| /* List | Map */ super.optionalParameterDefaultValues, | |
| super.functionType, | |
| required super.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)})'; | |
| } | |
| } |