| // Copyright (c) 2013, 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.metadata_collector; |
| |
| import 'package:js_ast/src/precedence.dart' as js_precedence; |
| |
| import '../common.dart'; |
| import '../constants/values.dart'; |
| import '../deferred_load.dart' show OutputUnit; |
| import '../elements/elements.dart' |
| show |
| ClassElement, |
| ConstructorElement, |
| Element, |
| FieldElement, |
| FunctionSignature, |
| LibraryElement, |
| MemberElement, |
| MethodElement, |
| MetadataAnnotation, |
| ParameterElement; |
| import '../elements/entities.dart' show FunctionEntity; |
| |
| import '../elements/entities.dart'; |
| import '../elements/resolution_types.dart' show ResolutionTypedefType; |
| import '../elements/types.dart'; |
| import '../js/js.dart' as jsAst; |
| import '../js/js.dart' show js; |
| import '../js_backend/constant_handler_javascript.dart'; |
| import '../js_backend/mirrors_data.dart'; |
| import '../js_backend/runtime_types.dart' show RuntimeTypesEncoder; |
| import '../js_backend/type_variable_handler.dart' |
| show TypeVariableCodegenAnalysis; |
| import '../options.dart'; |
| import '../universe/world_builder.dart' show CodegenWorldBuilder; |
| |
| import 'code_emitter_task.dart' show Emitter; |
| |
| /// Represents an entry's position in one of the global metadata arrays. |
| /// |
| /// [_rc] is used to count the number of references of the token in the |
| /// ast for a program. |
| /// [value] is the actual position, once they have been finalized. |
| abstract class _MetadataEntry extends jsAst.DeferredNumber |
| implements Comparable, jsAst.ReferenceCountedAstNode { |
| jsAst.Expression get entry; |
| int get value; |
| int get _rc; |
| |
| // Mark this entry as seen. On the first time this is seen, the visitor |
| // will be applied to the [entry] to also mark potential [_MetadataEntry] |
| // instances in the [entry] as seen. |
| markSeen(jsAst.TokenCounter visitor); |
| } |
| |
| class _BoundMetadataEntry extends _MetadataEntry { |
| int _value = -1; |
| int _rc = 0; |
| final jsAst.Expression entry; |
| |
| _BoundMetadataEntry(this.entry); |
| |
| bool get isFinalized => _value != -1; |
| |
| finalize(int value) { |
| assert(!isFinalized); |
| _value = value; |
| } |
| |
| int get value { |
| assert(isFinalized); |
| return _value; |
| } |
| |
| bool get isUsed => _rc > 0; |
| |
| markSeen(jsAst.BaseVisitor visitor) { |
| _rc++; |
| if (_rc == 1) entry.accept(visitor); |
| } |
| |
| int compareTo(covariant _MetadataEntry other) => other._rc - this._rc; |
| } |
| |
| abstract class Placeholder implements jsAst.DeferredNumber { |
| bind(_MetadataEntry entry); |
| } |
| |
| class _ForwardingMetadataEntry extends _MetadataEntry implements Placeholder { |
| _MetadataEntry _forwardTo; |
| var debug; |
| |
| bool get isBound => _forwardTo != null; |
| |
| _ForwardingMetadataEntry([this.debug]); |
| |
| _MetadataEntry get forwardTo { |
| assert(isBound); |
| return _forwardTo; |
| } |
| |
| jsAst.Expression get entry { |
| assert(isBound); |
| return forwardTo.entry; |
| } |
| |
| int get value { |
| assert(isBound); |
| return forwardTo.value; |
| } |
| |
| int get _rc => forwardTo._rc; |
| |
| markSeen(jsAst.BaseVisitor visitor) => forwardTo.markSeen(visitor); |
| |
| int compareTo(other) => forwardTo.compareTo(other); |
| |
| bind(_MetadataEntry entry) { |
| assert(!isBound); |
| _forwardTo = entry; |
| } |
| } |
| |
| class _MetadataList extends jsAst.DeferredExpression { |
| jsAst.Expression _value; |
| |
| void setExpression(jsAst.Expression value) { |
| assert(_value == null); |
| assert(value.precedenceLevel == this.precedenceLevel); |
| _value = value; |
| } |
| |
| jsAst.Expression get value { |
| assert(_value != null); |
| return _value; |
| } |
| |
| int get precedenceLevel => js_precedence.PRIMARY; |
| } |
| |
| class MetadataCollector implements jsAst.TokenFinalizer { |
| final CompilerOptions _options; |
| final DiagnosticReporter reporter; |
| final Emitter _emitter; |
| final JavaScriptConstantCompiler _constants; |
| final TypeVariableCodegenAnalysis _typeVariableCodegenAnalysis; |
| final MirrorsData _mirrorsData; |
| final RuntimeTypesEncoder _rtiEncoder; |
| final CodegenWorldBuilder _codegenWorldBuilder; |
| |
| /// A map with a token per output unit for a list of expressions that |
| /// represent metadata, parameter names and type variable types. |
| Map<OutputUnit, _MetadataList> _metadataTokens = |
| new Map<OutputUnit, _MetadataList>(); |
| |
| jsAst.Expression getMetadataForOutputUnit(OutputUnit outputUnit) { |
| return _metadataTokens.putIfAbsent(outputUnit, () => new _MetadataList()); |
| } |
| |
| /// A map used to canonicalize the entries of metadata. |
| Map<OutputUnit, Map<String, _BoundMetadataEntry>> _metadataMap = |
| <OutputUnit, Map<String, _BoundMetadataEntry>>{}; |
| |
| /// A map with a token for a lists of JS expressions, one token for each |
| /// output unit. Once finalized, the entries represent types including |
| /// function types and typedefs. |
| Map<OutputUnit, _MetadataList> _typesTokens = |
| new Map<OutputUnit, _MetadataList>(); |
| |
| jsAst.Expression getTypesForOutputUnit(OutputUnit outputUnit) { |
| return _typesTokens.putIfAbsent(outputUnit, () => new _MetadataList()); |
| } |
| |
| /// A map used to canonicalize the entries of types. |
| Map<OutputUnit, Map<DartType, _BoundMetadataEntry>> _typesMap = |
| <OutputUnit, Map<DartType, _BoundMetadataEntry>>{}; |
| |
| MetadataCollector( |
| this._options, |
| this.reporter, |
| this._emitter, |
| this._constants, |
| this._typeVariableCodegenAnalysis, |
| this._mirrorsData, |
| this._rtiEncoder, |
| this._codegenWorldBuilder); |
| |
| jsAst.Fun buildLibraryMetadataFunction(LibraryEntity element) { |
| if (!_mirrorsData.mustRetainMetadata || |
| !_mirrorsData.isLibraryReferencedFromMirrorSystem(element)) { |
| return null; |
| } |
| return _buildMetadataFunction(element as LibraryElement); |
| } |
| |
| jsAst.Fun buildClassMetadataFunction(ClassEntity cls) { |
| if (!_mirrorsData.mustRetainMetadata || |
| !_mirrorsData.isClassReferencedFromMirrorSystem(cls)) { |
| return null; |
| } |
| // TODO(redemption): Handle class entities. |
| ClassElement element = cls; |
| return _buildMetadataFunction(element); |
| } |
| |
| bool _mustEmitMetadataForMember(MemberEntity member) { |
| if (!_mirrorsData.mustRetainMetadata) { |
| return false; |
| } |
| // TODO(redemption): Handle member entities. |
| MemberElement element = member; |
| return _mirrorsData.isMemberReferencedFromMirrorSystem(element); |
| } |
| |
| jsAst.Fun buildFieldMetadataFunction(FieldEntity field) { |
| if (!_mustEmitMetadataForMember(field)) return null; |
| // TODO(redemption): Handle field entities. |
| FieldElement element = field; |
| return _buildMetadataFunction(element); |
| } |
| |
| /// The metadata function returns the metadata associated with |
| /// [element] in generated code. The metadata needs to be wrapped |
| /// in a function as it refers to constants that may not have been |
| /// constructed yet. For example, a class is allowed to be |
| /// annotated with itself. The metadata function is used by |
| /// mirrors_patch to implement DeclarationMirror.metadata. |
| jsAst.Fun _buildMetadataFunction(Element element) { |
| return reporter.withCurrentElement(element, () { |
| List<jsAst.Expression> metadata = <jsAst.Expression>[]; |
| for (MetadataAnnotation annotation in element.metadata) { |
| ConstantValue constant = |
| _constants.getConstantValueForMetadata(annotation); |
| if (constant == null) { |
| reporter.internalError(annotation, 'Annotation value is null.'); |
| } else { |
| metadata.add(_emitter.constantReference(constant)); |
| } |
| } |
| if (metadata.isEmpty) return null; |
| return js( |
| 'function() { return # }', new jsAst.ArrayInitializer(metadata)); |
| }); |
| } |
| |
| List<jsAst.DeferredNumber> reifyDefaultArguments( |
| FunctionEntity function, OutputUnit outputUnit) { |
| // TODO(sra): These are stored on the InstanceMethod or StaticDartMethod. |
| if (function is MethodElement) |
| return reifyDefaultArgumentsAst(function, outputUnit); |
| |
| List<jsAst.DeferredNumber> defaultValues = <jsAst.DeferredNumber>[]; |
| _codegenWorldBuilder.forEachParameter(function, |
| (_, String name, ConstantValue constant) { |
| if (constant == null) return; |
| jsAst.Expression expression = _emitter.constantReference(constant); |
| defaultValues.add(_addGlobalMetadata(expression, outputUnit)); |
| }); |
| return defaultValues; |
| } |
| |
| List<jsAst.DeferredNumber> reifyDefaultArgumentsAst( |
| MethodElement function, OutputUnit outputUnit) { |
| function = function.implementation; |
| FunctionSignature signature = function.functionSignature; |
| if (signature.optionalParameterCount == 0) return const []; |
| |
| // Optional parameters of redirecting factory constructors take their |
| // defaults from the corresponding parameters of the redirection target. |
| Map<ParameterElement, ParameterElement> targetParameterMap; |
| if (function is ConstructorElement) { |
| // TODO(sra): dart2js generates a redirecting factory constructor body |
| // that has the signature of the redirecting constructor that calls the |
| // redirection target. This is wrong - it should have the signature of the |
| // target. This would make the reified default arguments trivial. |
| |
| ConstructorElement constructor = function; |
| while (constructor.isRedirectingFactory && |
| !constructor.isCyclicRedirection) { |
| // TODO(sra): Remove the loop once effectiveTarget forwards to patches. |
| constructor = constructor.effectiveTarget.implementation; |
| } |
| |
| if (constructor != function) { |
| if (signature.hasOptionalParameters) { |
| targetParameterMap = |
| mapRedirectingFactoryConstructorOptionalParameters( |
| signature, constructor.functionSignature); |
| } |
| } |
| } |
| |
| List<jsAst.DeferredNumber> defaultValues = <jsAst.DeferredNumber>[]; |
| for (ParameterElement element in signature.optionalParameters) { |
| ParameterElement parameter = |
| (targetParameterMap == null) ? element : targetParameterMap[element]; |
| ConstantValue constant = (parameter == null) |
| ? null |
| : _constants.getConstantValue(parameter.constant); |
| jsAst.Expression expression = (constant == null) |
| ? new jsAst.LiteralNull() |
| : _emitter.constantReference(constant); |
| defaultValues.add(_addGlobalMetadata(expression, outputUnit)); |
| } |
| return defaultValues; |
| } |
| |
| Map<ParameterElement, ParameterElement> |
| mapRedirectingFactoryConstructorOptionalParameters( |
| FunctionSignature source, FunctionSignature target) { |
| var map = <ParameterElement, ParameterElement>{}; |
| |
| if (source.optionalParametersAreNamed != |
| target.optionalParametersAreNamed) { |
| // No legal optional arguments due to mismatch between named vs positional |
| // optional arguments. |
| return map; |
| } |
| |
| if (source.optionalParametersAreNamed) { |
| for (ParameterElement element in source.optionalParameters) { |
| for (ParameterElement redirectedElement in target.optionalParameters) { |
| if (element.name == redirectedElement.name) { |
| map[element] = redirectedElement; |
| break; |
| } |
| } |
| } |
| } else { |
| int i = source.requiredParameterCount; |
| for (ParameterElement element in source.orderedOptionalParameters) { |
| if (i >= target.requiredParameterCount && i < target.parameterCount) { |
| map[element] = target |
| .orderedOptionalParameters[i - target.requiredParameterCount]; |
| } |
| ++i; |
| } |
| } |
| return map; |
| } |
| |
| jsAst.Expression reifyMetadata( |
| MetadataAnnotation annotation, OutputUnit outputUnit) { |
| ConstantValue constant = _constants.getConstantValueForMetadata(annotation); |
| if (constant == null) { |
| reporter.internalError(annotation, 'Annotation value is null.'); |
| return null; |
| } |
| return _addGlobalMetadata(_emitter.constantReference(constant), outputUnit); |
| } |
| |
| jsAst.Expression reifyType(DartType type, OutputUnit outputUnit, |
| {ignoreTypeVariables: false}) { |
| return addTypeInOutputUnit(type, outputUnit, |
| ignoreTypeVariables: ignoreTypeVariables); |
| } |
| |
| jsAst.Expression reifyName(String name, OutputUnit outputUnit) { |
| return _addGlobalMetadata(js.string(name), outputUnit); |
| } |
| |
| jsAst.Expression reifyExpression( |
| jsAst.Expression expression, OutputUnit outputUnit) { |
| return _addGlobalMetadata(expression, outputUnit); |
| } |
| |
| Placeholder getMetadataPlaceholder([debug]) { |
| return new _ForwardingMetadataEntry(debug); |
| } |
| |
| _MetadataEntry _addGlobalMetadata(jsAst.Node node, OutputUnit outputUnit) { |
| String nameToKey(jsAst.Name name) => "${name.key}"; |
| String printed = jsAst.prettyPrint(node, |
| enableMinification: _options.enableMinification, |
| renamerForNames: nameToKey); |
| _metadataMap[outputUnit] ??= new Map<String, _BoundMetadataEntry>(); |
| return _metadataMap[outputUnit].putIfAbsent(printed, () { |
| return new _BoundMetadataEntry(node); |
| }); |
| } |
| |
| jsAst.Expression _computeTypeRepresentation(DartType type, |
| {ignoreTypeVariables: false}) { |
| jsAst.Expression representation = |
| _rtiEncoder.getTypeRepresentation(_emitter, type, (variable) { |
| if (ignoreTypeVariables) return new jsAst.LiteralNull(); |
| return _typeVariableCodegenAnalysis.reifyTypeVariable(variable.element); |
| }, (ResolutionTypedefType typedef) { |
| return _mirrorsData.isTypedefAccessibleByReflection(typedef.element); |
| }); |
| |
| if (representation is jsAst.LiteralString) { |
| // We don't want the representation to be a string, since we use |
| // strings as indicator for non-initialized types in the lazy emitter. |
| reporter.internalError( |
| NO_LOCATION_SPANNABLE, 'reified types should not be strings.'); |
| } |
| |
| return representation; |
| } |
| |
| jsAst.Expression addTypeInOutputUnit(DartType type, OutputUnit outputUnit, |
| {ignoreTypeVariables: false}) { |
| _typesMap[outputUnit] ??= new Map<DartType, _BoundMetadataEntry>(); |
| return _typesMap[outputUnit].putIfAbsent(type, () { |
| return new _BoundMetadataEntry(_computeTypeRepresentation(type, |
| ignoreTypeVariables: ignoreTypeVariables)); |
| }); |
| } |
| |
| List<jsAst.DeferredNumber> computeMetadata( |
| MethodElement element, OutputUnit outputUnit) { |
| return reporter.withCurrentElement(element, () { |
| if (!_mustEmitMetadataForMember(element)) |
| return const <jsAst.DeferredNumber>[]; |
| List<jsAst.DeferredNumber> metadata = <jsAst.DeferredNumber>[]; |
| for (MetadataAnnotation annotation in element.metadata) { |
| metadata.add(reifyMetadata(annotation, outputUnit)); |
| } |
| return metadata; |
| }); |
| } |
| |
| @override |
| void finalizeTokens() { |
| bool checkTokensInTypes(OutputUnit outputUnit, entries) { |
| UnBoundDebugger debugger = new UnBoundDebugger(outputUnit); |
| for (_BoundMetadataEntry entry in entries) { |
| if (!entry.isUsed) continue; |
| if (debugger.findUnboundPlaceholders(entry.entry)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| void countTokensInTypes(Iterable<_BoundMetadataEntry> entries) { |
| jsAst.TokenCounter counter = new jsAst.TokenCounter(); |
| entries |
| .where((_BoundMetadataEntry e) => e._rc > 0) |
| .map((_BoundMetadataEntry e) => e.entry) |
| .forEach(counter.countTokens); |
| } |
| |
| jsAst.ArrayInitializer finalizeMap(Map<dynamic, _BoundMetadataEntry> map) { |
| bool isUsed(_BoundMetadataEntry entry) => entry.isUsed; |
| List<_BoundMetadataEntry> entries = map.values.where(isUsed).toList(); |
| entries.sort(); |
| |
| // TODO(herhut): Bucket entries by index length and use a stable |
| // distribution within buckets. |
| int count = 0; |
| for (_BoundMetadataEntry entry in entries) { |
| entry.finalize(count++); |
| } |
| |
| List<jsAst.Node> values = |
| entries.map((_BoundMetadataEntry e) => e.entry).toList(); |
| |
| return new jsAst.ArrayInitializer(values); |
| } |
| |
| _metadataTokens.forEach((OutputUnit outputUnit, _MetadataList token) { |
| Map metadataMap = _metadataMap[outputUnit]; |
| if (metadataMap != null) { |
| token.setExpression(finalizeMap(metadataMap)); |
| } else { |
| token.setExpression(new jsAst.ArrayInitializer([])); |
| } |
| }); |
| |
| _typesTokens.forEach((OutputUnit outputUnit, _MetadataList token) { |
| Map typesMap = _typesMap[outputUnit]; |
| if (typesMap != null) { |
| assert(checkTokensInTypes(outputUnit, typesMap.values)); |
| countTokensInTypes(typesMap.values); |
| token.setExpression(finalizeMap(typesMap)); |
| } else { |
| token.setExpression(new jsAst.ArrayInitializer([])); |
| } |
| }); |
| } |
| } |
| |
| class UnBoundDebugger extends jsAst.BaseVisitor { |
| OutputUnit outputUnit; |
| bool _foundUnboundToken = false; |
| |
| UnBoundDebugger(this.outputUnit); |
| |
| @override |
| visitDeferredNumber(jsAst.DeferredNumber token) { |
| if (token is _ForwardingMetadataEntry && !token.isBound) { |
| _foundUnboundToken = true; |
| } |
| } |
| |
| bool findUnboundPlaceholders(jsAst.Node node) { |
| node.accept(this); |
| return _foundUnboundToken; |
| } |
| } |