Index: pkg/compiler/lib/src/js_emitter/metadata_collector.dart |
diff --git a/pkg/compiler/lib/src/js_emitter/metadata_collector.dart b/pkg/compiler/lib/src/js_emitter/metadata_collector.dart |
index b2f1351fd9a55ba3574fc4e2725f3fcf0ce7bc75..a2eeb986d358677f708289955645f8508b4a7ba2 100644 |
--- a/pkg/compiler/lib/src/js_emitter/metadata_collector.dart |
+++ b/pkg/compiler/lib/src/js_emitter/metadata_collector.dart |
@@ -4,27 +4,144 @@ |
part of dart2js.js_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.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.BaseVisitor 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(_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) { |
+ // TODO(herhut): Enable the below assertion once incremental mode is gone. |
+ // 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 { |
final Compiler _compiler; |
final Emitter _emitter; |
- /// A list of JS expressions that represent metadata, parameter names and |
- /// type variable types. |
- final List<jsAst.Expression> globalMetadata = <jsAst.Expression>[]; |
+ /// A token for a list of expressions that represent metadata, parameter names |
+ /// and type variable types. |
+ final _MetadataList _globalMetadata = new _MetadataList(); |
+ jsAst.Expression get globalMetadata => _globalMetadata; |
/// A map used to canonicalize the entries of globalMetadata. |
- final Map<String, int> _globalMetadataMap = <String, int>{}; |
+ Map<String, _BoundMetadataEntry> _globalMetadataMap; |
- /// A map with lists of JS expressions, one list for each output unit. The |
- /// entries represent types including function types and typedefs. |
- final Map<OutputUnit, List<jsAst.Expression>> types = |
- <OutputUnit, List<jsAst.Expression>>{}; |
+ /// 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. |
- final Map<OutputUnit, Map<String, int>> _typesMap = |
- <OutputUnit, Map<String, int>>{}; |
+ Map<OutputUnit, Map<DartType, _BoundMetadataEntry>> _typesMap = |
+ <OutputUnit, Map<DartType, _BoundMetadataEntry>>{}; |
- MetadataCollector(this._compiler, this._emitter); |
+ // To support incremental compilation, we have to be able to eagerly emit |
+ // metadata and add metadata later on. We use the below two counters for |
+ // this. |
+ int _globalMetadataCounter = 0; |
+ int _globalTypesCounter = 0; |
+ |
+ MetadataCollector(this._compiler, this._emitter) { |
+ _globalMetadataMap = new Map<String, _BoundMetadataEntry>(); |
+ } |
JavaScriptBackend get _backend => _compiler.backend; |
TypeVariableHandler get _typeVariableHandler => _backend.typeVariableHandler; |
@@ -64,51 +181,78 @@ class MetadataCollector { |
}); |
} |
- List<int> reifyDefaultArguments(FunctionElement function) { |
+ List<jsAst.DeferredNumber> reifyDefaultArguments(FunctionElement function) { |
FunctionSignature signature = function.functionSignature; |
if (signature.optionalParameterCount == 0) return const []; |
- List<int> defaultValues = <int>[]; |
+ List<jsAst.DeferredNumber> defaultValues = <jsAst.DeferredNumber>[]; |
for (ParameterElement element in signature.optionalParameters) { |
ConstantValue constant = |
_backend.constants.getConstantValueForVariable(element); |
jsAst.Expression expression = (constant == null) |
- ? null |
+ ? new jsAst.LiteralNull() |
: _emitter.constantReference(constant); |
- defaultValues.add(addGlobalMetadata(expression)); |
+ defaultValues.add(_addGlobalMetadata(expression)); |
} |
return defaultValues; |
} |
- int reifyMetadata(MetadataAnnotation annotation) { |
+ jsAst.Expression reifyMetadata(MetadataAnnotation annotation) { |
ConstantValue constant = |
_backend.constants.getConstantValueForMetadata(annotation); |
if (constant == null) { |
_compiler.internalError(annotation, 'Annotation value is null.'); |
- return -1; |
+ return null; |
} |
- return addGlobalMetadata(_emitter.constantReference(constant)); |
+ return _addGlobalMetadata(_emitter.constantReference(constant)); |
} |
- int reifyType(DartType type, {bool ignoreTypeVariables: false}) { |
+ jsAst.Expression reifyType(DartType type, {ignoreTypeVariables: false}) { |
return reifyTypeForOutputUnit(type, |
_compiler.deferredLoadTask.mainOutputUnit, |
ignoreTypeVariables: ignoreTypeVariables); |
} |
- int reifyTypeForOutputUnit(DartType type, OutputUnit outputUnit, |
- {bool ignoreTypeVariables: false}) { |
- jsAst.Expression representation = |
- _backend.rti.getTypeRepresentation( |
- type, |
- (variable) { |
- if (ignoreTypeVariables) return new jsAst.LiteralNull(); |
- return js.number( |
- _typeVariableHandler.reifyTypeVariable( |
- variable.element)); |
- }, |
- (TypedefType typedef) { |
- return _backend.isAccessibleByReflection(typedef.element); |
- }); |
+ jsAst.Expression reifyTypeForOutputUnit(DartType type, |
+ OutputUnit outputUnit, |
+ {ignoreTypeVariables: false}) { |
+ return addTypeInOutputUnit(type, outputUnit, |
+ ignoreTypeVariables: ignoreTypeVariables); |
+ } |
+ |
+ jsAst.Expression reifyName(String name) { |
+ return _addGlobalMetadata(js.string(name)); |
+ } |
+ |
+ jsAst.Expression reifyExpression(jsAst.Expression expression) { |
+ return _addGlobalMetadata(expression); |
+ } |
+ |
+ Placeholder getMetadataPlaceholder([debug]) { |
+ return new _ForwardingMetadataEntry(debug); |
+ } |
+ |
+ _MetadataEntry _addGlobalMetadata(jsAst.Node node) { |
+ String printed = jsAst.prettyPrint(node, _compiler).getText(); |
+ return _globalMetadataMap.putIfAbsent(printed, () { |
+ _BoundMetadataEntry result = new _BoundMetadataEntry(node); |
+ if (_compiler.hasIncrementalSupport) { |
+ result.finalize(_globalMetadataCounter++); |
+ } |
+ return result; |
+ }); |
+ } |
+ |
+ jsAst.Expression _computeTypeRepresentation(DartType type, |
+ {ignoreTypeVariables: false}) { |
+ jsAst.Expression representation = _backend.rti.getTypeRepresentation( |
+ type, |
+ (variable) { |
+ if (ignoreTypeVariables) return new jsAst.LiteralNull(); |
+ return _typeVariableHandler.reifyTypeVariable(variable.element); |
+ }, |
+ (TypedefType typedef) { |
+ return _backend.isAccessibleByReflection(typedef.element); |
+ }); |
if (representation is jsAst.LiteralString) { |
// We don't want the representation to be a string, since we use |
@@ -117,42 +261,31 @@ class MetadataCollector { |
NO_LOCATION_SPANNABLE, 'reified types should not be strings.'); |
} |
- return addTypeInOutputUnit(representation, outputUnit); |
- } |
- |
- int reifyName(String name) { |
- return addGlobalMetadata(js('"$name"')); |
- } |
+ return representation; |
- int addGlobalMetadata(jsAst.Expression expression) { |
- // TODO(sigmund): consider adding an effient way to compare expressions |
- String string = jsAst.prettyPrint(expression, _compiler).getText(); |
- return _globalMetadataMap.putIfAbsent(string, () { |
- globalMetadata.add(expression); |
- return globalMetadata.length - 1; |
- }); |
} |
- int addTypeInOutputUnit(jsAst.Expression type, OutputUnit outputUnit) { |
- String string = jsAst.prettyPrint(type, _compiler).getText(); |
+ jsAst.Expression addTypeInOutputUnit(DartType type, |
+ OutputUnit outputUnit, |
+ {ignoreTypeVariables: false}) { |
if (_typesMap[outputUnit] == null) { |
- _typesMap[outputUnit] = <String, int>{}; |
+ _typesMap[outputUnit] = new Map<DartType, _BoundMetadataEntry>(); |
} |
- return _typesMap[outputUnit].putIfAbsent(string, () { |
- |
- if (types[outputUnit] == null) { |
- types[outputUnit] = <jsAst.Expression>[]; |
+ return _typesMap[outputUnit].putIfAbsent(type, () { |
+ _BoundMetadataEntry result = new _BoundMetadataEntry( |
+ _computeTypeRepresentation(type, |
+ ignoreTypeVariables: ignoreTypeVariables)); |
+ if (_compiler.hasIncrementalSupport) { |
+ result.finalize(_globalTypesCounter++); |
} |
- |
- types[outputUnit].add(type); |
- return types[outputUnit].length - 1; |
+ return result; |
}); |
} |
- List<int> computeMetadata(FunctionElement element) { |
+ List<jsAst.DeferredNumber> computeMetadata(FunctionElement element) { |
return _compiler.withCurrentElement(element, () { |
- if (!_mustEmitMetadataFor(element)) return const <int>[]; |
- List<int> metadata = <int>[]; |
+ if (!_mustEmitMetadataFor(element)) return const <jsAst.DeferredNumber>[]; |
+ List<jsAst.DeferredNumber> metadata = <jsAst.DeferredNumber>[]; |
Link link = element.metadata; |
// TODO(ahe): Why is metadata sometimes null? |
if (link != null) { |
@@ -163,4 +296,94 @@ class MetadataCollector { |
return metadata; |
}); |
} |
+ |
+ void countTokensInProgram(jsAst.Program program) { |
+ TokenCounter visitor = new TokenCounter(); |
+ visitor.countTokens(program); |
+ } |
+ |
+ 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) { |
+ TokenCounter counter = new TokenCounter(); |
+ entries.where((_BoundMetadataEntry e) => e._rc > 0) |
+ .map((_BoundMetadataEntry e) => e.entry) |
+ .forEach(counter.countTokens); |
+ } |
+ |
+ jsAst.ArrayInitializer finalizeMap(Map<dynamic, _BoundMetadataEntry> map) { |
+ // When in incremental mode, we allocate entries eagerly. |
+ if (_compiler.hasIncrementalSupport) { |
+ return new jsAst.ArrayInitializer(map.values.toList()); |
+ } |
+ |
+ 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); |
+ } |
+ |
+ _globalMetadata.setExpression(finalizeMap(_globalMetadataMap)); |
+ |
+ _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 TokenCounter extends jsAst.BaseVisitor { |
+ @override |
+ visitDeferredNumber(jsAst.DeferredNumber token) { |
+ if (token is _MetadataEntry) { |
+ token.markSeen(this); |
+ } |
+ } |
+ |
+ void countTokens(jsAst.Node node) => node.accept(this); |
+} |
+ |
+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; |
+ } |
+} |