Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | |
| 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. | |
| 4 | |
| 5 /// Analysis to determine how to generate code for `LookupMap`s. | |
| 6 library compiler.src.js_backend.lookup_map_analysis; | |
| 7 | |
| 8 import '../common/registry.dart' show Registry; | |
| 9 import '../compiler.dart' show Compiler; | |
| 10 import '../constants/values.dart' show | |
| 11 ConstantValue, | |
| 12 ConstructedConstantValue, | |
| 13 ListConstantValue, | |
| 14 NullConstantValue, | |
| 15 TypeConstantValue; | |
| 16 import '../dart_types.dart' show DartType; | |
| 17 import '../elements/elements.dart' show Elements, Element, ClassElement, | |
| 18 FieldElement, FunctionElement, FunctionSignature; | |
| 19 import '../enqueue.dart' show Enqueuer; | |
| 20 import 'js_backend.dart' show JavaScriptBackend; | |
| 21 import '../dart_types.dart' show DynamicType, InterfaceType; | |
| 22 | |
| 23 /// An analysis and optimization to remove unused entries from a `LookupMap`. | |
| 24 /// | |
| 25 /// `LookupMaps` are defined in `package:lookup_map/lookup_map.dart`. They are | |
| 26 /// simple maps that contain constant expressions as keys, and that only support | |
| 27 /// the lookup operation. | |
| 28 /// | |
| 29 /// This analysis and optimization will tree-shake the contents of the maps by | |
| 30 /// looking at the program and finding which keys are clearly unused. Not all | |
| 31 /// constants can be approximated statically, so this optimization is limited to | |
| 32 /// the following keys: | |
| 33 /// | |
| 34 /// * Const expressions that can only be created via const constructors. This | |
| 35 /// excludes primitives, strings, and any const type that overrides the == | |
| 36 /// operator. | |
| 37 /// | |
| 38 /// * Type literals. | |
| 39 /// | |
| 40 /// Type literals are more complex than const expressions because they can be | |
| 41 /// created in multiple ways. We can approximate the possible set of keys if we | |
| 42 /// follow these rules: | |
| 43 /// | |
| 44 /// * Include all type-literals used explicitly in the code (excluding | |
| 45 /// obviously the uses that can be removed from LookupMaps) | |
| 46 /// | |
| 47 /// * Include every single type-literal if a mirror API is used to create | |
|
herhut
2015/09/02 12:59:57
These should be only those classes that are reflec
Siggi Cherem (dart-lang)
2015/09/03 00:44:29
Good point. done
| |
| 48 /// types (e.g. ClassMirror.reflectedType). | |
| 49 /// | |
| 50 /// * Include all allocated types if the program contains `e.runtimeType` | |
| 51 /// expressions. | |
| 52 /// | |
| 53 /// * Include all generic-type arguments, if the program uses type | |
| 54 /// variables in expressions such as `class A<T> { Type get extract => T }`. | |
| 55 /// | |
| 56 // TODO(sigmund): add support for const expressions, currently this | |
| 57 // implementation only supports Type literals. To support const expressions we | |
| 58 // need to change some of the invariants below (e.g. we can no longer use the | |
| 59 // ClassElement of a type to refer to keys we need to discover). | |
| 60 // TODO(sigmund): detect uses of mirrors | |
| 61 class LookupMapAnalysis { | |
| 62 /// Reference to [JavaScriptBackend] to be able to enqueue work when we | |
| 63 /// discover that a key in a map is potentially used. | |
| 64 final JavaScriptBackend backend; | |
| 65 | |
| 66 /// The resolved [ClassElement] associated with `LookupMap`. | |
| 67 ClassElement typeLookupMapClass; | |
| 68 | |
| 69 /// The resolved [FieldElement] for `LookupMap._entries`. | |
| 70 FieldElement entriesField; | |
| 71 | |
| 72 /// The resolved [FieldElement] for `LookupMap._key`. | |
| 73 FieldElement keyField; | |
| 74 | |
| 75 /// The resolved [FieldElement] for `LookupMap._value`. | |
| 76 FieldElement valueField; | |
| 77 | |
| 78 /// Constant instances of `LookupMap` and information about them tracked by | |
| 79 /// this analysis. | |
| 80 final Map<ConstantValue, _LookupMapInfo> _lookupMaps = {}; | |
| 81 | |
| 82 /// Types that we have discovered to be in use in the program. | |
| 83 final _inUse = new Set<ClassElement>(); | |
| 84 | |
| 85 /// Pending work to do if we discover that a new type is in use. For each type | |
| 86 /// that we haven't seen, we record the list of lookup-maps that use such type | |
| 87 /// as a key. | |
| 88 final _pending = <ClassElement, List<_LookupMapInfo>>{}; | |
| 89 | |
| 90 /// Whether the backend is currently processing the codegen queue. | |
| 91 // TODO(sigmund): is there a better way to do this. Do we need to plumb the | |
| 92 // enqueuer on each callback? | |
| 93 bool get _inCodegen => backend.compiler.phase == Compiler.PHASE_COMPILING; | |
| 94 | |
| 95 /// Whether to enable some print debugging for this feature. | |
| 96 static const _debug = const String.fromEnvironment('lookupMap') == 'debug'; | |
|
sra1
2015/09/02 00:55:24
maybe _DEBUG ?
Siggi Cherem (dart-lang)
2015/09/03 00:44:29
I went ahead and removed the debug code. The only
| |
| 97 | |
| 98 /// Whether this analysis and optimization is enabled. | |
| 99 bool get _isEnabled { | |
| 100 // `tlm==off` kept here to make it easy to test disabling this feature | |
|
sra1
2015/09/02 00:55:24
tlm?
herhut
2015/09/02 12:59:57
I guess this is a left over from type lookup map :
Siggi Cherem (dart-lang)
2015/09/03 00:44:28
exactly :) - old name. fixed the comment now "look
| |
| 101 if (const String.fromEnvironment('lookupMap') == 'off') return false; | |
| 102 return typeLookupMapClass != null; | |
| 103 } | |
| 104 | |
| 105 LookupMapAnalysis(this.backend); | |
|
sra1
2015/09/02 00:55:24
Move ctor above _isEnabled
Siggi Cherem (dart-lang)
2015/09/03 00:44:29
Done.
| |
| 106 | |
| 107 /// Initializes this analysis by providing the resolver information of | |
| 108 /// `LookupMap`. | |
| 109 void initRuntimeClass(ClassElement cls) { | |
| 110 cls.computeType(backend.compiler); | |
| 111 entriesField = cls.lookupMember('_entries'); | |
| 112 keyField = cls.lookupMember('_key'); | |
| 113 valueField = cls.lookupMember('_value'); | |
| 114 // TODO(sigmund): consider inlining nested maps to improve the quality of | |
| 115 // the output code. | |
|
sra1
2015/09/02 00:55:24
Say what aspect do you think will improve. Size?
Siggi Cherem (dart-lang)
2015/09/03 00:44:29
Done.
| |
| 116 typeLookupMapClass = cls; | |
| 117 } | |
| 118 | |
| 119 /// Whether [constant] is an instance of a `LookupMap`. | |
| 120 bool isLookupMap(ConstantValue constant) => | |
| 121 _isEnabled && | |
| 122 constant.isConstructedObject && | |
| 123 (constant as ConstructedConstantValue).type.asRaw().element | |
|
herhut
2015/09/02 12:59:57
Maybe use local variable instead of as?
Siggi Cherem (dart-lang)
2015/09/03 00:44:29
Done - in this case, I realize I can switch the ch
| |
| 124 .isSubclassOf(typeLookupMapClass); | |
| 125 | |
| 126 /// Registers an instance of a lookup-map with the analysis. | |
| 127 void registerLookupMapReference(ConstantValue lookupMap) { | |
| 128 if (!_isEnabled || !_inCodegen) return; | |
| 129 assert(isLookupMap(lookupMap)); | |
| 130 _lookupMaps.putIfAbsent(lookupMap, () { | |
| 131 if (_debug) print('\x1b[32m>>> ${lookupMap.unparse()} \x1b[0m'); | |
|
herhut
2015/09/02 12:59:57
https://github.com/google/ansicolor-dart :-D
Siggi Cherem (dart-lang)
2015/09/03 00:44:30
nice :) - I normally define constnats for it, but
| |
| 132 var info = new _LookupMapInfo( | |
| 133 lookupMap, entriesField, keyField, valueField); | |
| 134 for (ClassElement type in info.unusedEntries.keys.toList()) { | |
|
sra1
2015/09/02 00:55:24
Is the toList() copy part of the algorithm?
Yes: s
herhut
2015/09/02 12:59:57
I guess this is because _markUsed will update the
Siggi Cherem (dart-lang)
2015/09/03 00:44:30
Correct, it's to avoid the concurrent modification
| |
| 135 if (_inUse.contains(type)) { | |
| 136 info._markUsed(type, backend); | |
| 137 } else { | |
| 138 _pending.putIfAbsent(type, () => []).add(info); | |
| 139 } | |
| 140 } | |
| 141 return info; | |
| 142 }); | |
| 143 } | |
| 144 | |
| 145 /// Records that [type] is used in the program, and updates every map that | |
| 146 /// has it as a key. | |
| 147 void _addUse(ClassElement type, [String debugContext]) { | |
| 148 if (_inUse.add(type)) { | |
| 149 if (_debug) print('\x1b[33m FYI($debugContext): ${type} \x1b[0m'); | |
| 150 _pending[type]?.forEach((info) => info._markUsed(type, backend)); | |
| 151 _pending[type] = []; | |
|
herhut
2015/09/02 12:59:57
How about const []?
Siggi Cherem (dart-lang)
2015/09/03 00:44:30
Oh - good point, later on when we discover a new L
| |
| 152 } | |
| 153 } | |
| 154 | |
| 155 /// Callback from the enqueuer, invoked when [element] is instantiated. | |
| 156 void registerInstantiatedClass(ClassElement element) { | |
| 157 if (!_isEnabled || !_inCodegen) return; | |
| 158 // TODO(sigmund): only add if .runtimeType is ever used | |
| 159 _addUse(element, '+class'); | |
| 160 } | |
| 161 | |
| 162 /// Callback from the enqueuer, invoked when [type] is instantiated. | |
| 163 void registerInstantiatedType(InterfaceType type, Registry registry) { | |
| 164 if (!_isEnabled || !_inCodegen) return; | |
| 165 // TODO(sigmund): only add if .runtimeType is ever used | |
| 166 _addUse(type.element, '+type'); | |
| 167 // TODO(sigmund): only do this when type-argument expressions are used? | |
| 168 _addGenerics(type, registry); | |
| 169 } | |
| 170 | |
| 171 /// Records generic type arguments in [type], in case they are retrieved and | |
| 172 /// returned using a type-argument expression. | |
| 173 void _addGenerics(InterfaceType type, Registry registry) { | |
| 174 if (!type.isGeneric) return; | |
| 175 for (var arg in type.typeArguments) { | |
| 176 if (arg is InterfaceType) { | |
| 177 _addUse(arg.element, '+generic'); | |
| 178 // Note: this call was needed to generate correct code for | |
| 179 // type_lookup_map/generic_type_test | |
| 180 // TODO(sigmund): can we get rid of this? | |
| 181 backend.registerInstantiatedConstantType( | |
| 182 backend.typeImplementation.rawType, registry); | |
| 183 _addGenerics(arg, registry); | |
| 184 } | |
| 185 } | |
| 186 } | |
| 187 | |
| 188 /// Callback from the codegen enqueuer, invoked when a type constant | |
| 189 /// corresponding to the [element] is used in the program. | |
| 190 void registerTypeConstant(Element element) { | |
| 191 if (!_isEnabled || !_inCodegen) return; | |
| 192 assert(element.isClass); | |
| 193 _addUse(element, '+literal'); | |
| 194 } | |
| 195 | |
| 196 /// Callback from the backend, invoked when reaching the end of the enqueuing | |
| 197 /// process, but before emitting the code. At this moment we have discovered | |
| 198 /// all types used in the program and we can tree-shake anything that is | |
| 199 /// unused. | |
| 200 void onQueueEmpty(Enqueuer enqueuer) { | |
|
herhut
2015/09/02 12:59:57
This is a bad name. In the enqueuer this might be
Siggi Cherem (dart-lang)
2015/09/03 00:44:29
Done - I had copied the name from custom_elements_
| |
| 201 if (!_isEnabled || enqueuer.isResolutionQueue) return; | |
| 202 assert(_inCodegen); | |
| 203 | |
| 204 _lookupMaps.values.forEach((info) { | |
| 205 // Technically this should be reached only once, but we seem to get a | |
|
herhut
2015/09/02 12:59:57
This should be fixed in the calling context...
Siggi Cherem (dart-lang)
2015/09/03 00:44:29
Done. I think it's cleaner now. I added an API in
| |
| 206 // second call during `checkQueue`. We set emitted = true to ensure we | |
| 207 // only do this once. | |
| 208 if (info.emitted) { | |
| 209 // TODO(sigmund): check that nothing really changed? | |
| 210 return; | |
| 211 } | |
| 212 info.emitted = true; | |
| 213 info._prepareForEmission(); | |
| 214 }); | |
| 215 | |
| 216 if (_debug) { | |
| 217 print('done!'); | |
| 218 _lookupMaps.forEach((original, info) { | |
| 219 info.unusedEntries.forEach((k, _) => print('${k.rawType} unused')); | |
| 220 }); | |
| 221 } | |
| 222 } | |
| 223 } | |
| 224 | |
| 225 /// Internal information about the entries on a lookup-map. | |
| 226 class _LookupMapInfo { | |
| 227 /// The original reference to the constant value. | |
| 228 /// | |
| 229 /// This reference will be mutated in place to remove it's entries when the | |
| 230 /// map is first seen during codegen, and to restore them (or a subset of | |
| 231 /// them) when we have finished discovering which entries are used. This has | |
| 232 /// the side-effect that `orignal.getDependencies()` will be empty during | |
| 233 /// most of codegen until we are ready to emit the constants. However, | |
| 234 /// restoring the entries before emitting code lets us keep the emitter logic | |
| 235 /// agnostic of this optimization. | |
| 236 final ConstructedConstantValue original; | |
| 237 | |
| 238 /// Resolved field element to lookup the entries list. | |
| 239 final FieldElement entriesField; | |
|
sra1
2015/09/02 00:55:24
Are these different to the ones in LookupMapAnalys
Siggi Cherem (dart-lang)
2015/09/03 00:44:29
Done.
| |
| 240 | |
| 241 /// Resolved field element to lookup the key when the map is built as a | |
| 242 /// single-pair. | |
| 243 final FieldElement keyField; | |
| 244 | |
| 245 /// Resolved field element to lookup the value whent he map is built as a | |
| 246 /// single-pair. | |
| 247 final FieldElement valueField; | |
| 248 | |
| 249 /// Whether we have already emitted this constant. | |
| 250 bool emitted = false; | |
| 251 | |
| 252 /// Whether the `LookupMap` constant was built using the `LookupMap.pair` | |
| 253 /// constructor. | |
| 254 bool singlePair; | |
| 255 | |
| 256 /// Entries in the lookup map whose keys have not been seen in the rest of the | |
| 257 /// program. | |
| 258 Map<ClassElement, ConstantValue> unusedEntries = {}; | |
|
sra1
2015/09/02 00:55:24
We typically use <ClassElement, ConstantValue>{} t
Siggi Cherem (dart-lang)
2015/09/03 00:44:30
Done, but we should chat more about it, we have a
| |
| 259 | |
| 260 /// Entries that have been used, and thus will be part of the generated code. | |
| 261 Map<ClassElement, ConstantValue> usedEntries = {}; | |
| 262 | |
| 263 /// Internal helper to memoize the mapping between map class elements and | |
| 264 /// their corresponding type constants. | |
| 265 Map<ClassElement, TypeConstantValue> _typeConstants = {}; | |
| 266 | |
| 267 _LookupMapInfo(this.original, | |
| 268 this.entriesField, this.keyField, this.valueField) { | |
|
herhut
2015/09/02 12:59:57
Nit: I think it would be easier to read if these w
Siggi Cherem (dart-lang)
2015/09/03 00:44:30
Done.
| |
| 269 ConstantValue key = original.fields[keyField]; | |
| 270 singlePair = !key.isNull; | |
| 271 | |
| 272 if (singlePair) { | |
| 273 ClassElement cls = key.representedType.element; | |
| 274 _typeConstants[cls] = key; | |
| 275 unusedEntries[cls] = original.fields[valueField]; | |
| 276 | |
| 277 // Note: we modify the constant in-place, see comment in [original]. | |
| 278 original.fields[keyField] = new NullConstantValue(); | |
| 279 original.fields[valueField] = new NullConstantValue(); | |
| 280 } else { | |
| 281 ListConstantValue list = original.fields[entriesField]; | |
| 282 List<ConstantValue> keyValuePairs = list.entries; | |
| 283 for (int i = 0; i < keyValuePairs.length; i += 2) { | |
| 284 TypeConstantValue type = keyValuePairs[i]; | |
| 285 ClassElement cls = type.representedType.element; | |
| 286 if (cls == null || !cls.isClass) { | |
| 287 // TODO(sigmund): report an error | |
| 288 continue; | |
| 289 } | |
| 290 _typeConstants[cls] = type; | |
| 291 unusedEntries[cls] = keyValuePairs[i + 1]; | |
| 292 } | |
| 293 | |
| 294 // Note: we modify the constant in-place, see comment in [original]. | |
| 295 original.fields[entriesField] = | |
| 296 new ListConstantValue(list.type, []); | |
| 297 } | |
| 298 } | |
| 299 | |
| 300 /// Marks that [type] is a key that has been seen, and thus, the corresponding | |
| 301 /// entry in this map should be considered reachable. | |
| 302 _markUsed(ClassElement type, JavaScriptBackend backend) { | |
| 303 assert(!emitted); | |
| 304 assert(unusedEntries.containsKey(type)); | |
| 305 assert(!usedEntries.containsKey(type)); | |
| 306 ConstantValue constant = unusedEntries.remove(type); | |
| 307 usedEntries[type] = constant; | |
| 308 backend.registerCompileTimeConstant(constant, | |
| 309 backend.compiler.globalDependencies, | |
| 310 addForEmission: false); | |
| 311 } | |
| 312 | |
| 313 /// Restores [original] to contain all of the entries marked as possibly used. | |
| 314 _prepareForEmission() { | |
| 315 ListConstantValue originalEntries = original.fields[entriesField]; | |
| 316 DartType listType = originalEntries.type; | |
| 317 List<ConstantValue> keyValuePairs = <ConstantValue>[]; | |
| 318 usedEntries.forEach((key, value) { | |
| 319 keyValuePairs.add(_typeConstants[key]); | |
| 320 keyValuePairs.add(value); | |
| 321 }); | |
| 322 | |
| 323 // Note: we are restoring the entries here, see comment in [original]. | |
| 324 if (singlePair) { | |
| 325 assert (keyValuePairs.length == 0 || keyValuePairs.length == 2); | |
| 326 if (keyValuePairs.length == 2) { | |
| 327 original.fields[keyField] = keyValuePairs[0]; | |
| 328 original.fields[valueField] = keyValuePairs[1]; | |
| 329 } | |
| 330 } else { | |
| 331 original.fields[entriesField] = | |
| 332 new ListConstantValue(listType, keyValuePairs); | |
| 333 } | |
| 334 } | |
| 335 } | |
| OLD | NEW |