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