Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library dump_info; | 5 library dump_info; |
| 6 | 6 |
| 7 import 'dart:convert' show | 7 import 'dart:convert' |
| 8 HtmlEscape, | 8 show HtmlEscape, JsonEncoder, StringConversionSink, ChunkedConversionSink; |
|
Siggi Cherem (dart-lang)
2015/07/24 01:07:58
FYI - I ran all files in the CL through dartfmt 0.
| |
| 9 JsonEncoder, | |
| 10 StringConversionSink, | |
| 11 ChunkedConversionSink; | |
| 12 | 9 |
| 13 import 'elements/elements.dart'; | 10 import 'elements/elements.dart'; |
| 14 import 'elements/visitor.dart'; | 11 import 'elements/visitor.dart'; |
| 15 import 'dart2jslib.dart' show | 12 import 'dart2jslib.dart' |
| 16 Backend, | 13 show Backend, CodeBuffer, Compiler, CompilerTask, MessageKind; |
| 17 CodeBuffer, | |
| 18 Compiler, | |
| 19 CompilerTask, | |
| 20 MessageKind; | |
| 21 import 'types/types.dart' show TypeMask; | 14 import 'types/types.dart' show TypeMask; |
| 22 import 'deferred_load.dart' show OutputUnit; | 15 import 'deferred_load.dart' show OutputUnit; |
| 16 import 'info/info.dart'; | |
| 23 import 'js_backend/js_backend.dart' show JavaScriptBackend; | 17 import 'js_backend/js_backend.dart' show JavaScriptBackend; |
| 24 import 'js_emitter/full_emitter/emitter.dart' as full show Emitter; | 18 import 'js_emitter/full_emitter/emitter.dart' as full show Emitter; |
| 25 import 'js/js.dart' as jsAst; | 19 import 'js/js.dart' as jsAst; |
| 26 import 'universe/universe.dart' show Selector, UniverseSelector; | 20 import 'universe/universe.dart' show Selector, UniverseSelector; |
| 27 import 'util/util.dart' show NO_LOCATION_SPANNABLE; | 21 import 'util/util.dart' show NO_LOCATION_SPANNABLE; |
| 28 | 22 |
| 29 /// Maps objects to an id. Supports lookups in | 23 class ElementInfoCollector extends BaseElementVisitor<Info, dynamic> { |
| 30 /// both directions. | |
| 31 class IdMapper<T>{ | |
| 32 Map<int, T> _idToElement = {}; | |
| 33 Map<T, int> _elementToId = {}; | |
| 34 int _idCounter = 0; | |
| 35 final String name; | |
| 36 | |
| 37 IdMapper(this.name); | |
| 38 | |
| 39 Iterable<T> get elements => _elementToId.keys; | |
| 40 | |
| 41 String add(T e) { | |
| 42 if (_elementToId.containsKey(e)) { | |
| 43 return name + "/${_elementToId[e]}"; | |
| 44 } | |
| 45 | |
| 46 _idToElement[_idCounter] = e; | |
| 47 _elementToId[e] = _idCounter; | |
| 48 _idCounter += 1; | |
| 49 return name + "/${_idCounter - 1}"; | |
| 50 } | |
| 51 } | |
| 52 | |
| 53 class GroupedIdMapper { | |
| 54 // Mappers for specific kinds of elements. | |
| 55 IdMapper<LibraryElement> _library = new IdMapper('library'); | |
| 56 IdMapper<TypedefElement> _typedef = new IdMapper('typedef'); | |
| 57 IdMapper<FieldElement> _field = new IdMapper('field'); | |
| 58 IdMapper<ClassElement> _class = new IdMapper('class'); | |
| 59 IdMapper<FunctionElement> _function = new IdMapper('function'); | |
| 60 IdMapper<OutputUnit> _outputUnit = new IdMapper('outputUnit'); | |
| 61 | |
| 62 Iterable<Element> get functions => _function.elements; | |
| 63 | |
| 64 // Convert this database of elements into JSON for rendering | |
| 65 Map<String, dynamic> _toJson(ElementToJsonVisitor elementToJson) { | |
| 66 Map<String, dynamic> json = {}; | |
| 67 var m = [_library, _typedef, _field, _class, _function]; | |
| 68 for (IdMapper mapper in m) { | |
| 69 Map<String, dynamic> innerMapper = {}; | |
| 70 mapper._idToElement.forEach((k, v) { | |
| 71 // All these elements are already cached in the | |
| 72 // jsonCache, so this is just an access. | |
| 73 var elementJson = elementToJson.process(v); | |
| 74 if (elementJson != null) { | |
| 75 innerMapper["$k"] = elementJson; | |
| 76 } | |
| 77 }); | |
| 78 json[mapper.name] = innerMapper; | |
| 79 } | |
| 80 return json; | |
| 81 } | |
| 82 } | |
| 83 | |
| 84 class ElementToJsonVisitor | |
| 85 extends BaseElementVisitor<Map<String, dynamic>, dynamic> { | |
| 86 final GroupedIdMapper mapper = new GroupedIdMapper(); | |
| 87 final Compiler compiler; | 24 final Compiler compiler; |
| 88 | 25 |
| 89 final Map<Element, Map<String, dynamic>> jsonCache = {}; | 26 final AllInfo result = new AllInfo(); |
| 27 final Map<Element, Info> _elementToInfo = {}; | |
| 28 final Map<OutputUnit, OutputUnitInfo> _outputToInfo = {}; | |
| 90 | 29 |
| 91 String dart2jsVersion; | 30 ElementInfoCollector(this.compiler); |
| 92 | 31 |
| 93 ElementToJsonVisitor(this.compiler); | 32 void run() => compiler.libraryLoader.libraries.forEach(visit); |
| 94 | 33 |
| 95 void run() { | 34 Info visit(Element e, [_]) => e.accept(this, null); |
| 96 dart2jsVersion = compiler.hasBuildId ? compiler.buildId : null; | |
| 97 | 35 |
| 98 for (LibraryElement library in compiler.libraryLoader.libraries.toList()) { | 36 /// Whether to emit information about [element]. |
| 99 visit(library); | 37 /// |
| 100 } | 38 /// By default we emit information for any element that contributes to the |
| 39 /// output size. Either becuase the it is a function being emitted or inlined, | |
| 40 /// or because it is an element that holds dependencies to other elements. | |
| 41 bool shouldKeep(Element element) { | |
| 42 return compiler.dumpInfoTask.selectorsFromElement.containsKey(element) || | |
| 43 compiler.dumpInfoTask.inlineCount.containsKey(element); | |
| 101 } | 44 } |
| 102 | 45 |
| 103 Map<String, dynamic> visit(Element e, [_]) => e.accept(this, null); | 46 /// Visits [element] and produces it's corresponding info. |
| 104 | 47 Info process(Element element) { |
| 105 // If keeping the element is in question (like if a function has a size | 48 // TODO(sigmund): change the visit order to eliminate the need to check |
| 106 // of zero), only keep it if it holds dependencies to elsewhere. | 49 // whether or not an element has been processed. |
| 107 bool shouldKeep(Element element) { | 50 return _elementToInfo.putIfAbsent(element, () => visit(element)); |
| 108 return compiler.dumpInfoTask.selectorsFromElement.containsKey(element) | |
| 109 || compiler.dumpInfoTask.inlineCount.containsKey(element); | |
| 110 } | 51 } |
| 111 | 52 |
| 112 Map<String, dynamic> toJson() { | 53 void _record(Element element, Info info, List list) { |
| 113 return mapper._toJson(this); | 54 _elementToInfo[element] = info; |
| 114 } | 55 list.add(info); |
| 115 | |
| 116 // Memoization of the JSON creating process. | |
| 117 Map<String, dynamic> process(Element element) { | |
| 118 return jsonCache.putIfAbsent(element, () => visit(element)); | |
| 119 } | 56 } |
| 120 | 57 |
| 121 // Returns the id of an [element] if it has already been processed. | 58 // Returns the id of an [element] if it has already been processed. |
| 122 // If the element has not been processed, this function does not | 59 // If the element has not been processed, this function does not |
| 123 // process it, and simply returns null instead. | 60 // process it, and simply returns null instead. |
| 124 String idOf(Element element) { | 61 String idOf(Element element) => _elementToInfo[element]?.id; |
| 125 if (jsonCache.containsKey(element) && jsonCache[element] != null) { | |
| 126 return jsonCache[element]['id']; | |
| 127 } else { | |
| 128 return null; | |
| 129 } | |
| 130 } | |
| 131 | 62 |
| 132 Map<String, dynamic> visitElement(Element element, _) { | 63 Info visitElement(Element element, _) => null; |
| 133 return null; | |
| 134 } | |
| 135 | 64 |
| 136 Map<String, dynamic> visitConstructorBodyElement( | 65 FunctionInfo visitConstructorBodyElement(ConstructorBodyElement e, _) { |
| 137 ConstructorBodyElement e, _) { | |
| 138 return visitFunctionElement(e.constructor, _); | 66 return visitFunctionElement(e.constructor, _); |
| 139 } | 67 } |
| 140 | 68 |
| 141 Map<String, dynamic> visitLibraryElement(LibraryElement element, _) { | 69 LibraryInfo visitLibraryElement(LibraryElement element, _) { |
| 142 var id = mapper._library.add(element); | |
| 143 List<String> children = <String>[]; | |
| 144 | |
| 145 String libname = element.getLibraryName(); | 70 String libname = element.getLibraryName(); |
| 146 libname = libname == "" ? "<unnamed>" : libname; | 71 libname = libname == "" ? "<unnamed>" : libname; |
| 72 int size = compiler.dumpInfoTask.sizeOf(element); | |
| 73 var info = new LibraryInfo(libname, element.canonicalUri, null, size); | |
| 147 | 74 |
| 148 int size = compiler.dumpInfoTask.sizeOf(element); | 75 LibraryElement realElement = element.isPatched ? element.patch : element; |
| 149 | 76 realElement.forEachLocalMember((Element member) { |
| 150 LibraryElement contentsOfLibrary = element.isPatched | 77 Info child = this.process(member); |
| 151 ? element.patch : element; | 78 if (child is ClassInfo) { |
| 152 contentsOfLibrary.forEachLocalMember((Element member) { | 79 info.classes.add(child); |
| 153 Map<String, dynamic> childJson = this.process(member); | 80 } else if (child is FunctionInfo) { |
| 154 if (childJson == null) return; | 81 info.topLevelFunctions.add(child); |
| 155 children.add(childJson['id']); | 82 } else if (child is FieldInfo) { |
| 83 info.topLevelVariables.add(child); | |
| 84 } else if (child != null) { | |
| 85 print('unexpected child of $info: $child ==> ${child.runtimeType}'); | |
| 86 assert(false); | |
| 87 } | |
| 156 }); | 88 }); |
| 157 | 89 |
| 158 if (children.length == 0 && !shouldKeep(element)) { | 90 if (info.isEmpty && !shouldKeep(element)) return null; |
| 159 return null; | 91 _record(element, info, result.libraries); |
| 160 } | 92 return info; |
| 161 | |
| 162 return { | |
| 163 'kind': 'library', | |
| 164 'name': libname, | |
| 165 'size': size, | |
| 166 'id': id, | |
| 167 'children': children, | |
| 168 'canonicalUri': element.canonicalUri.toString() | |
| 169 }; | |
| 170 } | 93 } |
| 171 | 94 |
| 172 Map<String, dynamic> visitTypedefElement(TypedefElement element, _) { | 95 TypedefInfo visitTypedefElement(TypedefElement element, _) { |
| 173 String id = mapper._typedef.add(element); | 96 if (element.alias == null) return null; |
| 174 return element.alias == null | 97 var info = new TypedefInfo(element.name, '${element.alias}'); |
| 175 ? null | 98 _record(element, info, result.typedefs); |
| 176 : { | 99 return info; |
| 177 'id': id, | |
| 178 'type': element.alias.toString(), | |
| 179 'kind': 'typedef', | |
| 180 'name': element.name | |
| 181 }; | |
| 182 } | 100 } |
| 183 | 101 |
| 184 Map<String, dynamic> visitFieldElement(FieldElement element, _) { | 102 FieldInfo visitFieldElement(FieldElement element, _) { |
| 185 String id = mapper._field.add(element); | |
| 186 List<String> children = []; | |
| 187 StringBuffer emittedCode = compiler.dumpInfoTask.codeOf(element); | |
| 188 | |
| 189 TypeMask inferredType = | 103 TypeMask inferredType = |
| 190 compiler.typesTask.getGuaranteedTypeOfElement(element); | 104 compiler.typesTask.getGuaranteedTypeOfElement(element); |
| 191 // If a field has an empty inferred type it is never used. | 105 // If a field has an empty inferred type it is never used. |
| 192 if (inferredType == null || inferredType.isEmpty || element.isConst) { | 106 if (inferredType == null || inferredType.isEmpty || element.isConst) { |
| 193 return null; | 107 return null; |
| 194 } | 108 } |
| 195 | 109 |
| 196 int size = compiler.dumpInfoTask.sizeOf(element); | 110 int size = compiler.dumpInfoTask.sizeOf(element); |
| 197 String code; | 111 String code; |
| 198 | 112 StringBuffer emittedCode = compiler.dumpInfoTask.codeOf(element); |
| 199 if (emittedCode != null) { | 113 if (emittedCode != null) { |
| 200 size += emittedCode.length; | 114 size += emittedCode.length; |
| 201 code = emittedCode.toString(); | 115 code = emittedCode.toString(); |
| 202 } | 116 } |
| 203 | 117 |
| 118 var nestedClosures = <FunctionInfo>[]; | |
| 204 for (Element closure in element.nestedClosures) { | 119 for (Element closure in element.nestedClosures) { |
| 205 var childJson = this.process(closure); | 120 var child = this.process(closure); |
| 206 if (childJson != null) { | 121 if (child != null) { |
| 207 children.add(childJson['id']); | 122 nestedClosures.add(child); |
| 208 if (childJson.containsKey('size')) { | 123 size += child.size; |
| 209 size += childJson['size']; | |
| 210 } | |
| 211 } | 124 } |
| 212 } | 125 } |
| 213 | 126 |
| 214 OutputUnit outputUnit = | 127 FieldInfo field = new FieldInfo( |
| 215 compiler.deferredLoadTask.outputUnitForElement(element); | 128 name: element.name, |
| 216 | 129 type: '${element.type}', |
| 217 return { | 130 inferredType: '$inferredType', |
| 218 'id': id, | 131 closures: nestedClosures, |
| 219 'kind': 'field', | 132 size: size, |
| 220 'type': element.type.toString(), | 133 code: code, |
| 221 'inferredType': inferredType.toString(), | 134 outputUnit: _unitInfoForElement(element)); |
| 222 'name': element.name, | 135 _record(element, field, result.fields); |
| 223 'children': children, | 136 return field; |
| 224 'size': size, | |
| 225 'code': code, | |
| 226 'outputUnit': mapper._outputUnit.add(outputUnit) | |
| 227 }; | |
| 228 } | 137 } |
| 229 | 138 |
| 230 Map<String, dynamic> visitClassElement(ClassElement element, _) { | 139 ClassInfo visitClassElement(ClassElement element, _) { |
| 231 String id = mapper._class.add(element); | 140 ClassInfo classInfo = new ClassInfo( |
| 232 List<String> children = []; | 141 name: element.name, |
| 142 isAbstract: element.isAbstract, | |
| 143 outputUnit: _unitInfoForElement(element)); | |
| 144 _elementToInfo[element] = classInfo; | |
| 233 | 145 |
| 234 int size = compiler.dumpInfoTask.sizeOf(element); | 146 int size = compiler.dumpInfoTask.sizeOf(element); |
| 235 JavaScriptBackend backend = compiler.backend; | 147 element.forEachLocalMember((Element member) { |
| 148 Info info = this.process(member); | |
| 149 if (info == null) return; | |
| 150 if (info is FieldInfo) { | |
| 151 classInfo.fields.add(info); | |
| 152 } else { | |
| 153 assert(info is FunctionInfo); | |
| 154 classInfo.functions.add(info); | |
| 155 } | |
| 236 | 156 |
| 237 Map<String, dynamic> modifiers = { 'abstract': element.isAbstract }; | 157 // Closures are placed in the library namespace, but we want to attribute |
| 158 // them to a function, and by extension, this class. Process and add the | |
| 159 // sizes here. | |
| 160 if (member is MemberElement) { | |
| 161 for (Element closure in member.nestedClosures) { | |
| 162 FunctionInfo closureInfo = this.process(closure); | |
| 163 if (closureInfo == null) continue; | |
| 238 | 164 |
| 239 element.forEachLocalMember((Element member) { | 165 // TODO(sigmund): remove this legacy update on the name, represent the |
| 240 Map<String, dynamic> childJson = this.process(member); | 166 // information explicitly in the info format. |
| 241 if (childJson != null) { | 167 // Look for the parent element of this closure might be the enclosing |
| 242 children.add(childJson['id']); | 168 // class or an enclosing function. |
| 243 | 169 Element parent = closure.enclosingElement; |
| 244 // Closures are placed in the library namespace, but | 170 ClassInfo parentInfo = this.process(parent); |
| 245 // we want to attribute them to a function, and by | 171 if (parentInfo != null) { |
| 246 // extension, this class. Process and add the sizes | 172 closureInfo.name = "${parentInfo.name}.${closureInfo.name}"; |
| 247 // here. | |
| 248 if (member is MemberElement) { | |
| 249 for (Element closure in member.nestedClosures) { | |
| 250 Map<String, dynamic> child = this.process(closure); | |
| 251 | |
| 252 // Look for the parent element of this closure which should | |
| 253 // be a class. If it exists, set the display name to | |
| 254 // the name of the class + the name of the closure function. | |
| 255 Element parent = closure.enclosingElement; | |
| 256 Map<String, dynamic> processedParent = this.process(parent); | |
| 257 if (processedParent != null) { | |
| 258 child['name'] = "${processedParent['name']}.${child['name']}"; | |
| 259 } | |
| 260 | |
| 261 if (child != null) { | |
| 262 size += child['size']; | |
| 263 } | |
| 264 } | 173 } |
| 174 size += closureInfo.size; | |
| 265 } | 175 } |
| 266 } | 176 } |
| 267 }); | 177 }); |
| 268 | 178 |
| 179 classInfo.size = size; | |
| 180 | |
| 269 // Omit element if it is not needed. | 181 // Omit element if it is not needed. |
| 270 if (!backend.emitter.neededClasses.contains(element) && | 182 if (!compiler.backend.emitter.neededClasses.contains(element) && |
| 271 children.length == 0) { | 183 classInfo.fields.isEmpty && |
| 184 classInfo.functions.isEmpty) { | |
| 272 return null; | 185 return null; |
| 273 } | 186 } |
| 274 | 187 _record(element, classInfo, result.classes); |
| 275 OutputUnit outputUnit = | 188 return classInfo; |
| 276 compiler.deferredLoadTask.outputUnitForElement(element); | |
| 277 | |
| 278 return { | |
| 279 'name': element.name, | |
| 280 'size': size, | |
| 281 'kind': 'class', | |
| 282 'modifiers': modifiers, | |
| 283 'children': children, | |
| 284 'id': id, | |
| 285 'outputUnit': mapper._outputUnit.add(outputUnit) | |
| 286 }; | |
| 287 } | 189 } |
| 288 | 190 |
| 289 Map<String, dynamic> visitFunctionElement(FunctionElement element, _) { | 191 FunctionInfo visitFunctionElement(FunctionElement element, _) { |
| 290 String id = mapper._function.add(element); | 192 int size = compiler.dumpInfoTask.sizeOf(element); |
| 193 if (size == 0 && !shouldKeep(element)) return null; | |
| 194 | |
| 291 String name = element.name; | 195 String name = element.name; |
| 292 String kind = "function"; | 196 int kind = FunctionInfo.TOP_LEVEL_FUNCTION_KIND; |
| 293 List<String> children = []; | |
| 294 List<Map<String, dynamic>> parameters = []; | |
| 295 String inferredReturnType = null; | |
| 296 String returnType = null; | |
| 297 String sideEffects = null; | |
| 298 | |
| 299 StringBuffer emittedCode = compiler.dumpInfoTask.codeOf(element); | |
| 300 int size = compiler.dumpInfoTask.sizeOf(element); | |
| 301 | |
| 302 Map<String, dynamic> modifiers = { | |
| 303 'static': element.isStatic, | |
| 304 'const': element.isConst, | |
| 305 'factory': element.isFactoryConstructor, | |
| 306 'external': element.isPatched | |
| 307 }; | |
| 308 | |
| 309 var enclosingElement = element.enclosingElement; | 197 var enclosingElement = element.enclosingElement; |
| 310 if (enclosingElement.isField || | 198 if (enclosingElement.isField || |
| 311 enclosingElement.isFunction || | 199 enclosingElement.isFunction || |
| 312 element.isClosure || | 200 element.isClosure || |
| 313 enclosingElement.isConstructor) { | 201 enclosingElement.isConstructor) { |
| 314 kind = "closure"; | 202 kind = FunctionInfo.CLOSURE_FUNCTION_KIND; |
| 315 name = "<unnamed>"; | 203 name = "<unnamed>"; |
| 316 } else if (modifiers['static']) { | 204 } else if (element.isStatic) { |
| 317 kind = 'function'; | 205 kind = FunctionInfo.TOP_LEVEL_FUNCTION_KIND; |
| 318 } else if (enclosingElement.isClass) { | 206 } else if (enclosingElement.isClass) { |
| 319 kind = 'method'; | 207 kind = FunctionInfo.METHOD_FUNCTION_KIND; |
| 320 } | 208 } |
| 321 | 209 |
| 322 if (element.isConstructor) { | 210 if (element.isConstructor) { |
| 323 name == "" | 211 name = name == "" |
| 324 ? "${element.enclosingElement.name}" | 212 ? "${element.enclosingElement.name}" |
| 325 : "${element.enclosingElement.name}.${element.name}"; | 213 : "${element.enclosingElement.name}.${element.name}"; |
| 326 kind = "constructor"; | 214 kind = FunctionInfo.CONSTRUCTOR_FUNCTION_KIND; |
| 327 } | 215 } |
| 328 | 216 |
| 217 var modifiers = new FunctionModifiers( | |
|
Johnni Winther
2015/07/27 10:14:42
Type the locals (dart2js style!)
Siggi Cherem (dart-lang)
2015/07/27 20:10:58
:) -- changed for now, but we should chat about it
| |
| 218 isStatic: element.isStatic, | |
| 219 isConst: element.isConst, | |
| 220 isFactory: element.isFactoryConstructor, | |
| 221 isExternal: element.isPatched); | |
| 222 var emittedCode = compiler.dumpInfoTask.codeOf(element); | |
| 223 String code = emittedCode == null ? null : '$emittedCode'; | |
| 224 | |
| 225 var parameters = <ParameterInfo>[]; | |
| 329 if (element.hasFunctionSignature) { | 226 if (element.hasFunctionSignature) { |
| 330 FunctionSignature signature = element.functionSignature; | 227 FunctionSignature signature = element.functionSignature; |
| 331 signature.forEachParameter((parameter) { | 228 signature.forEachParameter((parameter) { |
| 332 parameters.add({ | 229 parameters.add(new ParameterInfo( |
| 333 'name': parameter.name, | 230 parameter.name, |
| 334 'type': '${compiler.typesTask.getGuaranteedTypeOfElement(parameter)}', | 231 '${compiler.typesTask.getGuaranteedTypeOfElement(parameter)}', |
| 335 'declaredType': '${parameter.node.type}' | 232 '${parameter.node.type}')); |
| 336 }); | |
| 337 }); | 233 }); |
| 338 } | 234 } |
| 339 | 235 |
| 340 if (element.isInstanceMember && !element.isAbstract && | 236 String returnType = null; |
| 237 // TODO(sigmund): why all these checks? | |
| 238 if (element.isInstanceMember && | |
| 239 !element.isAbstract && | |
| 341 compiler.world.allFunctions.contains(element)) { | 240 compiler.world.allFunctions.contains(element)) { |
| 342 returnType = '${element.type.returnType}'; | 241 returnType = '${element.type.returnType}'; |
| 343 } | 242 } |
| 344 inferredReturnType = | 243 String inferredReturnType = |
| 345 '${compiler.typesTask.getGuaranteedReturnTypeOfElement(element)}'; | 244 '${compiler.typesTask.getGuaranteedReturnTypeOfElement(element)}'; |
| 346 sideEffects = compiler.world.getSideEffectsOfElement(element).toString(); | 245 String sideEffects = '${compiler.world.getSideEffectsOfElement(element)}'; |
| 347 | 246 |
| 247 var nestedClosures = <FunctionInfo>[]; | |
| 348 if (element is MemberElement) { | 248 if (element is MemberElement) { |
| 349 MemberElement member = element as MemberElement; | 249 MemberElement member = element as MemberElement; |
| 350 for (Element closure in member.nestedClosures) { | 250 for (Element closure in member.nestedClosures) { |
| 351 Map<String, dynamic> child = this.process(closure); | 251 var child = this.process(closure); |
| 352 if (child != null) { | 252 if (child != null) { |
| 353 child['kind'] = 'closure'; | 253 nestedClosures.add(child); |
| 354 children.add(child['id']); | 254 size += child.size; |
| 355 size += child['size']; | |
| 356 } | 255 } |
| 357 } | 256 } |
| 358 } | 257 } |
| 359 | 258 |
| 360 if (size == 0 && !shouldKeep(element)) { | 259 int inlinedCount = compiler.dumpInfoTask.inlineCount[element]; |
| 361 return null; | 260 if (inlinedCount == null) inlinedCount = 0; |
| 362 } | |
| 363 | 261 |
| 364 int inlinedCount = compiler.dumpInfoTask.inlineCount[element]; | 262 var info = new FunctionInfo( |
| 365 if (inlinedCount == null) { | 263 name: name, |
| 366 inlinedCount = 0; | 264 modifiers: modifiers, |
| 367 } | 265 closures: nestedClosures, |
| 266 size: size, | |
| 267 returnType: returnType, | |
| 268 inferredReturnType: inferredReturnType, | |
| 269 parameters: parameters, | |
| 270 sideEffects: sideEffects, | |
| 271 inlinedCount: inlinedCount, | |
| 272 code: code, | |
| 273 type: element.type.toString(), | |
| 274 outputUnit: _unitInfoForElement(element)); | |
| 275 _record(element, info, result.functions); | |
| 276 return info; | |
| 277 } | |
| 368 | 278 |
| 369 OutputUnit outputUnit = | 279 OutputUnitInfo _unitInfoForElement(Element element) { |
| 370 compiler.deferredLoadTask.outputUnitForElement(element); | 280 var outputUnit = compiler.deferredLoadTask.outputUnitForElement(element); |
| 371 | 281 return _outputToInfo.putIfAbsent(outputUnit, () { |
| 372 return { | 282 // Dump-info currently only works with the full emitter. If another |
| 373 'kind': kind, | 283 // emitter is used it will fail here. |
| 374 'name': name, | 284 full.Emitter emitter = compiler.backend.emitter.emitter; |
| 375 'id': id, | 285 var info = new OutputUnitInfo( |
| 376 'modifiers': modifiers, | 286 outputUnit.name, emitter.outputBuffers[outputUnit].length); |
| 377 'children': children, | 287 result.outputUnits.add(info); |
| 378 'size': size, | 288 return info; |
| 379 'returnType': returnType, | 289 }); |
| 380 'inferredReturnType': inferredReturnType, | |
| 381 'parameters': parameters, | |
| 382 'sideEffects': sideEffects, | |
| 383 'inlinedCount': inlinedCount, | |
| 384 'code': emittedCode == null ? null : '$emittedCode', | |
| 385 'type': element.type.toString(), | |
| 386 'outputUnit': mapper._outputUnit.add(outputUnit) | |
| 387 }; | |
| 388 } | 290 } |
| 389 } | 291 } |
| 390 | 292 |
| 391 class Selection { | 293 class Selection { |
| 392 final Element selectedElement; | 294 final Element selectedElement; |
| 393 final TypeMask mask; | 295 final TypeMask mask; |
| 394 Selection(this.selectedElement, this.mask); | 296 Selection(this.selectedElement, this.mask); |
| 395 } | 297 } |
| 396 | 298 |
| 397 class DumpInfoTask extends CompilerTask { | 299 class DumpInfoTask extends CompilerTask { |
| 398 DumpInfoTask(Compiler compiler) | 300 DumpInfoTask(Compiler compiler) : super(compiler); |
| 399 : super(compiler); | |
| 400 | 301 |
| 401 String get name => "Dump Info"; | 302 String get name => "Dump Info"; |
| 402 | 303 |
| 403 ElementToJsonVisitor infoCollector; | 304 ElementInfoCollector infoCollector; |
| 404 | 305 |
| 405 /// The size of the generated output. | 306 /// The size of the generated output. |
| 406 int _programSize; | 307 int _programSize; |
| 407 | 308 |
| 408 // A set of javascript AST nodes that we care about the size of. | 309 // A set of javascript AST nodes that we care about the size of. |
| 409 // This set is automatically populated when registerElementAst() | 310 // This set is automatically populated when registerElementAst() |
| 410 // is called. | 311 // is called. |
| 411 final Set<jsAst.Node> _tracking = new Set<jsAst.Node>(); | 312 final Set<jsAst.Node> _tracking = new Set<jsAst.Node>(); |
| 412 // A mapping from Dart Elements to Javascript AST Nodes. | 313 // A mapping from Dart Elements to Javascript AST Nodes. |
| 413 final Map<Element, List<jsAst.Node>> _elementToNodes = | 314 final Map<Element, List<jsAst.Node>> _elementToNodes = |
| 414 <Element, List<jsAst.Node>>{}; | 315 <Element, List<jsAst.Node>>{}; |
| 415 // A mapping from Javascript AST Nodes to the size of their | 316 // A mapping from Javascript AST Nodes to the size of their |
| 416 // pretty-printed contents. | 317 // pretty-printed contents. |
| 417 final Map<jsAst.Node, int> _nodeToSize = <jsAst.Node, int>{}; | 318 final Map<jsAst.Node, int> _nodeToSize = <jsAst.Node, int>{}; |
| 418 | 319 |
| 419 final Map<Element, Set<UniverseSelector>> selectorsFromElement = {}; | 320 final Map<Element, Set<UniverseSelector>> selectorsFromElement = {}; |
| 420 final Map<Element, int> inlineCount = <Element, int>{}; | 321 final Map<Element, int> inlineCount = <Element, int>{}; |
| 421 // A mapping from an element to a list of elements that are | 322 // A mapping from an element to a list of elements that are |
| 422 // inlined inside of it. | 323 // inlined inside of it. |
| 423 final Map<Element, List<Element>> inlineMap = <Element, List<Element>>{}; | 324 final Map<Element, List<Element>> inlineMap = <Element, List<Element>>{}; |
| 424 | 325 |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 448 | 349 |
| 449 /** | 350 /** |
| 450 * Returns an iterable of [Selection]s that are used by | 351 * Returns an iterable of [Selection]s that are used by |
| 451 * [element]. Each [Selection] contains an element that is | 352 * [element]. Each [Selection] contains an element that is |
| 452 * used and the selector that selected the element. | 353 * used and the selector that selected the element. |
| 453 */ | 354 */ |
| 454 Iterable<Selection> getRetaining(Element element) { | 355 Iterable<Selection> getRetaining(Element element) { |
| 455 if (!selectorsFromElement.containsKey(element)) { | 356 if (!selectorsFromElement.containsKey(element)) { |
| 456 return const <Selection>[]; | 357 return const <Selection>[]; |
| 457 } else { | 358 } else { |
| 458 return selectorsFromElement[element].expand( | 359 return selectorsFromElement[element].expand((UniverseSelector selector) { |
| 459 (UniverseSelector selector) { | 360 return compiler.world.allFunctions |
| 460 return compiler.world.allFunctions.filter( | 361 .filter(selector.selector, selector.mask) |
| 461 selector.selector, selector.mask) | 362 .map((element) { |
| 462 .map((element) { | 363 return new Selection(element, selector.mask); |
| 463 return new Selection(element, selector.mask); | |
| 464 }); | |
| 465 }); | 364 }); |
| 365 }); | |
| 466 } | 366 } |
| 467 } | 367 } |
| 468 | 368 |
| 469 // Returns true if we care about tracking the size of | 369 // Returns true if we care about tracking the size of |
| 470 // this node. | 370 // this node. |
| 471 bool isTracking(jsAst.Node code) { | 371 bool isTracking(jsAst.Node code) { |
| 472 if (compiler.dumpInfo) { | 372 if (compiler.dumpInfo) { |
| 473 return _tracking.contains(code); | 373 return _tracking.contains(code); |
| 474 } else { | 374 } else { |
| 475 return false; | 375 return false; |
| 476 } | 376 } |
| 477 } | 377 } |
| 478 | 378 |
| 479 // Registers that a javascript AST node `code` was produced by the | 379 // Registers that a javascript AST node `code` was produced by the |
| 480 // dart Element `element`. | 380 // dart Element `element`. |
| 481 void registerElementAst(Element element, jsAst.Node code) { | 381 void registerElementAst(Element element, jsAst.Node code) { |
| 482 if (compiler.dumpInfo) { | 382 if (compiler.dumpInfo) { |
| 483 _elementToNodes | 383 _elementToNodes |
| 484 .putIfAbsent(element, () => new List<jsAst.Node>()) | 384 .putIfAbsent(element, () => new List<jsAst.Node>()) |
| 485 .add(code); | 385 .add(code); |
| 486 _tracking.add(code); | 386 _tracking.add(code); |
| 487 } | 387 } |
| 488 } | 388 } |
| 489 | 389 |
| 490 // Records the size of a dart AST node after it has been | 390 // Records the size of a dart AST node after it has been |
| 491 // pretty-printed into the output buffer. | 391 // pretty-printed into the output buffer. |
| 492 void recordAstSize(jsAst.Node node, int size) { | 392 void recordAstSize(jsAst.Node node, int size) { |
| 493 if (isTracking(node)) { | 393 if (isTracking(node)) { |
| 494 //TODO: should I be incrementing here instead? | 394 //TODO: should I be incrementing here instead? |
| 495 _nodeToSize[node] = size; | 395 _nodeToSize[node] = size; |
| 496 } | 396 } |
| 497 } | 397 } |
| 498 | 398 |
| 499 // Returns the size of the source code that | 399 // Returns the size of the source code that |
| 500 // was generated for an element. If no source | 400 // was generated for an element. If no source |
| 501 // code was produced, return 0. | 401 // code was produced, return 0. |
| 502 int sizeOf(Element element) { | 402 int sizeOf(Element element) { |
| 503 if (_elementToNodes.containsKey(element)) { | 403 if (_elementToNodes.containsKey(element)) { |
| 504 return _elementToNodes[element] | 404 return _elementToNodes[element].map(sizeOfNode).fold(0, (a, b) => a + b); |
| 505 .map(sizeOfNode) | |
| 506 .fold(0, (a, b) => a + b); | |
| 507 } else { | 405 } else { |
| 508 return 0; | 406 return 0; |
| 509 } | 407 } |
| 510 } | 408 } |
| 511 | 409 |
| 512 int sizeOfNode(jsAst.Node node) { | 410 int sizeOfNode(jsAst.Node node) { |
| 513 if (_nodeToSize.containsKey(node)) { | 411 if (_nodeToSize.containsKey(node)) { |
| 514 return _nodeToSize[node]; | 412 return _nodeToSize[node]; |
| 515 } else { | 413 } else { |
| 516 return 0; | 414 return 0; |
| 517 } | 415 } |
| 518 } | 416 } |
| 519 | 417 |
| 520 StringBuffer codeOf(Element element) { | 418 StringBuffer codeOf(Element element) { |
| 521 List<jsAst.Node> code = _elementToNodes[element]; | 419 List<jsAst.Node> code = _elementToNodes[element]; |
| 522 if (code == null) return null; | 420 if (code == null) return null; |
| 523 // Concatenate rendered ASTs. | 421 // Concatenate rendered ASTs. |
| 524 StringBuffer sb = new StringBuffer(); | 422 StringBuffer sb = new StringBuffer(); |
| 525 for (jsAst.Node ast in code) { | 423 for (jsAst.Node ast in code) { |
| 526 sb.writeln(jsAst.prettyPrint(ast, compiler).getText()); | 424 sb.writeln(jsAst.prettyPrint(ast, compiler).getText()); |
| 527 } | 425 } |
| 528 return sb; | 426 return sb; |
| 529 } | 427 } |
| 530 | 428 |
| 531 void collectInfo() { | 429 void collectInfo() { |
| 532 infoCollector = new ElementToJsonVisitor(compiler)..run(); | 430 infoCollector = new ElementInfoCollector(compiler)..run(); |
| 533 } | 431 } |
| 534 | 432 |
| 535 void dumpInfo() { | 433 void dumpInfo() { |
| 536 measure(() { | 434 measure(() { |
| 537 if (infoCollector == null) { | 435 if (infoCollector == null) { |
| 538 collectInfo(); | 436 collectInfo(); |
| 539 } | 437 } |
| 540 | 438 |
| 541 StringBuffer jsonBuffer = new StringBuffer(); | 439 StringBuffer jsonBuffer = new StringBuffer(); |
| 542 dumpInfoJson(jsonBuffer); | 440 dumpInfoJson(jsonBuffer); |
| 543 compiler.outputProvider('', 'info.json') | 441 compiler.outputProvider('', 'info.json') |
| 544 ..add(jsonBuffer.toString()) | 442 ..add(jsonBuffer.toString()) |
| 545 ..close(); | 443 ..close(); |
| 546 }); | 444 }); |
| 547 } | 445 } |
| 548 | 446 |
| 549 | |
| 550 void dumpInfoJson(StringSink buffer) { | 447 void dumpInfoJson(StringSink buffer) { |
| 551 JsonEncoder encoder = const JsonEncoder.withIndent(' '); | 448 JsonEncoder encoder = const JsonEncoder.withIndent(' '); |
| 552 Stopwatch stopwatch = new Stopwatch(); | 449 Stopwatch stopwatch = new Stopwatch(); |
| 553 stopwatch.start(); | 450 stopwatch.start(); |
| 554 | 451 |
| 555 Map<String, List<Map<String, String>>> holding = | 452 // Recursively build links to function uses |
| 556 <String, List<Map<String, String>>>{}; | 453 var functionElements = |
|
Johnni Winther
2015/07/27 10:14:42
Type the locals...
Siggi Cherem (dart-lang)
2015/07/27 20:10:58
Done.
| |
| 557 for (Element fn in infoCollector.mapper.functions) { | 454 infoCollector._elementToInfo.keys.where((k) => k is FunctionElement); |
| 558 Iterable<Selection> pulling = getRetaining(fn); | 455 for (var element in functionElements) { |
| 456 var info = infoCollector._elementToInfo[element]; | |
| 457 Iterable<Selection> uses = getRetaining(element); | |
| 559 // Don't bother recording an empty list of dependencies. | 458 // Don't bother recording an empty list of dependencies. |
| 560 if (pulling.length > 0) { | 459 for (var selection in uses) { |
| 561 String fnId = infoCollector.idOf(fn); | 460 // Don't register dart2js builtin functions that are not recorded. |
| 562 // Some dart2js builtin functions are not | 461 var useInfo = infoCollector._elementToInfo[selection.selectedElement]; |
| 563 // recorded. Don't register these. | 462 if (useInfo == null) continue; |
| 564 if (fnId != null) { | 463 info.uses.add(new DependencyInfo(useInfo, '${selection.mask}')); |
| 565 holding[fnId] = pulling | |
| 566 .map((selection) { | |
| 567 return <String, String>{ | |
| 568 "id": infoCollector.idOf(selection.selectedElement), | |
| 569 "mask": selection.mask.toString() | |
| 570 }; | |
| 571 }) | |
| 572 // Filter non-null ids for the same reason as above. | |
| 573 .where((a) => a['id'] != null) | |
| 574 .toList(); | |
| 575 } | |
| 576 } | 464 } |
| 577 } | 465 } |
| 578 | 466 |
| 579 // Track dependencies that come from inlining. | 467 // Track dependencies that come from inlining. |
| 580 for (Element element in inlineMap.keys) { | 468 for (Element element in inlineMap.keys) { |
| 581 String keyId = infoCollector.idOf(element); | 469 var functionInfo = infoCollector._elementToInfo[element]; |
| 582 if (keyId != null) { | 470 if (functionInfo == null) continue; |
| 583 for (Element held in inlineMap[element]) { | 471 for (Element held in inlineMap[element]) { |
| 584 String valueId = infoCollector.idOf(held); | 472 var heldInfo = infoCollector._elementToInfo[held]; |
| 585 if (valueId != null) { | 473 if (heldInfo == null) continue; |
| 586 holding.putIfAbsent(keyId, () => new List<Map<String, String>>()) | 474 functionInfo.uses.add(new DependencyInfo(heldInfo, 'inlined')); |
| 587 .add(<String, String>{ | |
| 588 "id": valueId, | |
| 589 "mask": "inlined" | |
| 590 }); | |
| 591 } | |
| 592 } | |
| 593 } | 475 } |
| 594 } | 476 } |
| 595 | 477 |
| 596 List<Map<String, dynamic>> outputUnits = | 478 var result = infoCollector.result; |
| 597 new List<Map<String, dynamic>>(); | 479 result.deferredFiles = compiler.deferredLoadTask.computeDeferredMap(); |
| 480 stopwatch.stop(); | |
| 481 result.program = new ProgramInfo( | |
| 482 size: _programSize, | |
| 483 dart2jsVersion: compiler.hasBuildId ? compiler.buildId : null, | |
| 484 compilationMoment: new DateTime.now(), | |
| 485 compilationDuration: compiler.totalCompileTime.elapsed, | |
| 486 toJsonDuration: stopwatch.elapsedMilliseconds, | |
| 487 dumpInfoDuration: this.timing, | |
| 488 noSuchMethodEnabled: compiler.backend.enabledNoSuchMethod, | |
| 489 minified: compiler.enableMinification); | |
| 598 | 490 |
| 599 JavaScriptBackend backend = compiler.backend; | 491 ChunkedConversionSink<Object> sink = encoder.startChunkedConversion( |
| 600 // Dump-info currently only works with the full emitter. If another | 492 new StringConversionSink.fromStringSink(buffer)); |
| 601 // emitter is used it will fail here. | 493 sink.add(result.toJson()); |
| 602 full.Emitter fullEmitter = backend.emitter.emitter; | 494 compiler.reportInfo(NO_LOCATION_SPANNABLE, MessageKind.GENERIC, { |
| 603 | 495 'text': "View the dumped .info.json file at " |
| 604 for (OutputUnit outputUnit in | 496 "https://dart-lang.github.io/dump-info-visualizer" |
| 605 infoCollector.mapper._outputUnit._elementToId.keys) { | 497 }); |
| 606 String id = infoCollector.mapper._outputUnit.add(outputUnit); | |
| 607 outputUnits.add(<String, dynamic> { | |
| 608 'id': id, | |
| 609 'name': outputUnit.name, | |
| 610 'size': fullEmitter.outputBuffers[outputUnit].length, | |
| 611 }); | |
| 612 } | |
| 613 | |
| 614 Map<String, dynamic> outJson = { | |
| 615 'elements': infoCollector.toJson(), | |
| 616 'holding': holding, | |
| 617 'outputUnits': outputUnits, | |
| 618 'dump_version': 3, | |
| 619 'deferredFiles': compiler.deferredLoadTask.computeDeferredMap(), | |
| 620 // This increases when new information is added to the map, but the viewer | |
| 621 // still is compatible. | |
| 622 'dump_minor_version': '2' | |
| 623 }; | |
| 624 | |
| 625 Map<String, dynamic> generalProgramInfo = <String, dynamic> { | |
| 626 'size': _programSize, | |
| 627 'dart2jsVersion': infoCollector.dart2jsVersion, | |
| 628 'compilationMoment': new DateTime.now().toString(), | |
| 629 'compilationDuration': compiler.totalCompileTime.elapsed.toString(), | |
| 630 'toJsonDuration': stopwatch.elapsedMilliseconds, | |
| 631 'dumpInfoDuration': this.timing.toString(), | |
| 632 'noSuchMethodEnabled': backend.enabledNoSuchMethod, | |
| 633 'minified': compiler.enableMinification | |
| 634 }; | |
| 635 | |
| 636 outJson['program'] = generalProgramInfo; | |
| 637 | |
| 638 ChunkedConversionSink<Object> sink = | |
| 639 encoder.startChunkedConversion( | |
| 640 new StringConversionSink.fromStringSink(buffer)); | |
| 641 sink.add(outJson); | |
| 642 compiler.reportInfo( | |
| 643 NO_LOCATION_SPANNABLE, | |
| 644 MessageKind.GENERIC, | |
| 645 {'text': "View the dumped .info.json file at " | |
| 646 "https://dart-lang.github.io/dump-info-visualizer"}); | |
| 647 } | 498 } |
| 648 } | 499 } |
| OLD | NEW |