| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 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 | 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 /// Analysis to determine how to generate code for `LookupMap`s. | 5 /// Analysis to determine how to generate code for `LookupMap`s. |
| 6 library compiler.src.js_backend.lookup_map_analysis; | 6 library compiler.src.js_backend.lookup_map_analysis; |
| 7 | 7 |
| 8 import '../common/registry.dart' show Registry; | 8 import '../common/registry.dart' show Registry; |
| 9 import '../compiler.dart' show Compiler; | 9 import '../compiler.dart' show Compiler; |
| 10 import '../constants/values.dart' show | 10 import '../constants/values.dart' show |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 72 /// The resolved [FieldElement] for `LookupMap._key`. | 72 /// The resolved [FieldElement] for `LookupMap._key`. |
| 73 FieldElement keyField; | 73 FieldElement keyField; |
| 74 | 74 |
| 75 /// The resolved [FieldElement] for `LookupMap._value`. | 75 /// The resolved [FieldElement] for `LookupMap._value`. |
| 76 FieldElement valueField; | 76 FieldElement valueField; |
| 77 | 77 |
| 78 /// Constant instances of `LookupMap` and information about them tracked by | 78 /// Constant instances of `LookupMap` and information about them tracked by |
| 79 /// this analysis. | 79 /// this analysis. |
| 80 final Map<ConstantValue, _LookupMapInfo> _lookupMaps = {}; | 80 final Map<ConstantValue, _LookupMapInfo> _lookupMaps = {}; |
| 81 | 81 |
| 82 /// Types that we have discovered to be in use in the program. | 82 /// Keys that we have discovered to be in use in the program. |
| 83 final _inUse = new Set<ClassElement>(); | 83 final _inUse = new Set<ConstantValue>(); |
| 84 | 84 |
| 85 /// Pending work to do if we discover that a new type is in use. For each type | 85 /// Internal helper to memoize the mapping between class elements and their |
| 86 /// that we haven't seen, we record the list of lookup-maps that use such type | 86 /// corresponding type constants. |
| 87 /// as a key. | 87 final _typeConstants = <ClassElement, TypeConstantValue>{}; |
| 88 final _pending = <ClassElement, List<_LookupMapInfo>>{}; | 88 |
| 89 /// Internal helper to memoize which classes (ignoring Type) override equals. |
| 90 /// |
| 91 /// Const keys of these types will not be tree-shaken because we can't |
| 92 /// statically guarantee that the program doesn't produce an equivalent key at |
| 93 /// runtime. Technically if we limit lookup-maps to check for identical keys, |
| 94 /// we could allow const instances of these types. However, we internally use |
| 95 /// a hash map within lookup-maps today, so we need this restriction. |
| 96 final _typesWithEquals = <ClassElement, bool>{}; |
| 97 |
| 98 /// Pending work to do if we discover that a new key is in use. For each key |
| 99 /// that we haven't seen, we record the list of lookup-maps that contain an |
| 100 /// entry with that key. |
| 101 final _pending = <ConstantValue, List<_LookupMapInfo>>{}; |
| 89 | 102 |
| 90 /// Whether the backend is currently processing the codegen queue. | 103 /// 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 | 104 // TODO(sigmund): is there a better way to do this. Do we need to plumb the |
| 92 // enqueuer on each callback? | 105 // enqueuer on each callback? |
| 93 bool get _inCodegen => backend.compiler.phase == Compiler.PHASE_COMPILING; | 106 bool get _inCodegen => backend.compiler.phase == Compiler.PHASE_COMPILING; |
| 94 | 107 |
| 95 LookupMapAnalysis(this.backend); | 108 LookupMapAnalysis(this.backend); |
| 96 | 109 |
| 97 /// Whether this analysis and optimization is enabled. | 110 /// Whether this analysis and optimization is enabled. |
| 98 bool get _isEnabled { | 111 bool get _isEnabled { |
| (...skipping 20 matching lines...) Expand all Loading... |
| 119 constant.type.asRaw().element.isSubclassOf(typeLookupMapClass); | 132 constant.type.asRaw().element.isSubclassOf(typeLookupMapClass); |
| 120 | 133 |
| 121 /// Registers an instance of a lookup-map with the analysis. | 134 /// Registers an instance of a lookup-map with the analysis. |
| 122 void registerLookupMapReference(ConstantValue lookupMap) { | 135 void registerLookupMapReference(ConstantValue lookupMap) { |
| 123 if (!_isEnabled || !_inCodegen) return; | 136 if (!_isEnabled || !_inCodegen) return; |
| 124 assert(isLookupMap(lookupMap)); | 137 assert(isLookupMap(lookupMap)); |
| 125 _lookupMaps.putIfAbsent(lookupMap, | 138 _lookupMaps.putIfAbsent(lookupMap, |
| 126 () => new _LookupMapInfo(lookupMap, this).._updateUsed()); | 139 () => new _LookupMapInfo(lookupMap, this).._updateUsed()); |
| 127 } | 140 } |
| 128 | 141 |
| 129 /// Records that [type] is used in the program, and updates every map that | 142 /// Whether [key] is a constant value whose type overrides equals. |
| 130 /// has it as a key. | 143 bool _overridesEquals(ConstantValue key) { |
| 131 void _addUse(ClassElement type) { | 144 if (key is ConstructedConstantValue) { |
| 132 if (_inUse.add(type)) { | 145 ClassElement element = key.type.element; |
| 133 _pending[type]?.forEach((info) => info._markUsed(type)); | 146 return _typesWithEquals.putIfAbsent(element, () => |
| 134 _pending.remove(type); | 147 element.lookupMember('==').enclosingClass != |
| 148 backend.compiler.objectClass); |
| 149 } |
| 150 return false; |
| 151 } |
| 152 |
| 153 /// Whether we need to preserve [key]. This is true for keys that are not |
| 154 /// candidates for tree-shaking in the first place (primitives and non-type |
| 155 /// const values overriding equals) and keys that we have seen in the program. |
| 156 bool _shouldKeep(ConstantValue key) => |
| 157 key.isPrimitive || _inUse.contains(key) || _overridesEquals(key); |
| 158 |
| 159 void _addClassUse(ClassElement cls) { |
| 160 ConstantValue key = _typeConstants.putIfAbsent(cls, |
| 161 () => backend.constantSystem.createType(backend.compiler, cls.rawType)); |
| 162 _addUse(key); |
| 163 } |
| 164 |
| 165 /// Record that [key] is used and update every lookup map that contains it. |
| 166 void _addUse(ConstantValue key) { |
| 167 if (_inUse.add(key)) { |
| 168 _pending[key]?.forEach((info) => info._markUsed(key)); |
| 169 _pending.remove(key); |
| 135 } | 170 } |
| 136 } | 171 } |
| 137 | 172 |
| 173 /// If [key] is a type, cache it in [_typeConstants]. |
| 174 _registerTypeKey(ConstantValue key) { |
| 175 if (key is TypeConstantValue) { |
| 176 ClassElement cls = key.representedType.element; |
| 177 if (cls == null || !cls.isClass) { |
| 178 // TODO(sigmund): report error? |
| 179 return; |
| 180 } |
| 181 _typeConstants[cls] = key; |
| 182 } |
| 183 } |
| 184 |
| 138 /// Callback from the enqueuer, invoked when [element] is instantiated. | 185 /// Callback from the enqueuer, invoked when [element] is instantiated. |
| 139 void registerInstantiatedClass(ClassElement element) { | 186 void registerInstantiatedClass(ClassElement element) { |
| 140 if (!_isEnabled || !_inCodegen) return; | 187 if (!_isEnabled || !_inCodegen) return; |
| 141 // TODO(sigmund): only add if .runtimeType is ever used | 188 // TODO(sigmund): only add if .runtimeType is ever used |
| 142 _addUse(element); | 189 _addClassUse(element); |
| 143 } | 190 } |
| 144 | 191 |
| 145 /// Callback from the enqueuer, invoked when [type] is instantiated. | 192 /// Callback from the enqueuer, invoked when [type] is instantiated. |
| 146 void registerInstantiatedType(InterfaceType type, Registry registry) { | 193 void registerInstantiatedType(InterfaceType type, Registry registry) { |
| 147 if (!_isEnabled || !_inCodegen) return; | 194 if (!_isEnabled || !_inCodegen) return; |
| 148 // TODO(sigmund): only add if .runtimeType is ever used | 195 // TODO(sigmund): only add if .runtimeType is ever used |
| 149 _addUse(type.element); | 196 _addClassUse(type.element); |
| 150 // TODO(sigmund): only do this when type-argument expressions are used? | 197 // TODO(sigmund): only do this when type-argument expressions are used? |
| 151 _addGenerics(type, registry); | 198 _addGenerics(type, registry); |
| 152 } | 199 } |
| 153 | 200 |
| 154 /// Records generic type arguments in [type], in case they are retrieved and | 201 /// Records generic type arguments in [type], in case they are retrieved and |
| 155 /// returned using a type-argument expression. | 202 /// returned using a type-argument expression. |
| 156 void _addGenerics(InterfaceType type, Registry registry) { | 203 void _addGenerics(InterfaceType type, Registry registry) { |
| 157 if (!type.isGeneric) return; | 204 if (!type.isGeneric) return; |
| 158 for (var arg in type.typeArguments) { | 205 for (var arg in type.typeArguments) { |
| 159 if (arg is InterfaceType) { | 206 if (arg is InterfaceType) { |
| 160 _addUse(arg.element); | 207 _addClassUse(arg.element); |
| 161 // Note: this call was needed to generate correct code for | 208 // Note: this call was needed to generate correct code for |
| 162 // type_lookup_map/generic_type_test | 209 // type_lookup_map/generic_type_test |
| 163 // TODO(sigmund): can we get rid of this? | 210 // TODO(sigmund): can we get rid of this? |
| 164 backend.registerInstantiatedConstantType( | 211 backend.registerInstantiatedConstantType( |
| 165 backend.typeImplementation.rawType, registry); | 212 backend.typeImplementation.rawType, registry); |
| 166 _addGenerics(arg, registry); | 213 _addGenerics(arg, registry); |
| 167 } | 214 } |
| 168 } | 215 } |
| 169 } | 216 } |
| 170 | 217 |
| 171 /// Callback from the codegen enqueuer, invoked when a type constant | 218 /// Callback from the codegen enqueuer, invoked when a constant (which is |
| 172 /// corresponding to the [element] is used in the program. | 219 /// possibly a const key or a type literal) is used in the program. |
| 173 void registerTypeConstant(Element element) { | 220 void registerTypeConstant(ClassElement element) { |
| 174 if (!_isEnabled || !_inCodegen) return; | 221 if (!_isEnabled || !_inCodegen) return; |
| 175 assert(element.isClass); | 222 _addClassUse(element); |
| 176 _addUse(element); | 223 } |
| 224 |
| 225 void registerConstantKey(ConstantValue constant) { |
| 226 if (!_isEnabled || !_inCodegen) return; |
| 227 if (constant.isPrimitive || _overridesEquals(constant)) return; |
| 228 _addUse(constant); |
| 177 } | 229 } |
| 178 | 230 |
| 179 /// Callback from the backend, invoked when reaching the end of the enqueuing | 231 /// Callback from the backend, invoked when reaching the end of the enqueuing |
| 180 /// process, but before emitting the code. At this moment we have discovered | 232 /// process, but before emitting the code. At this moment we have discovered |
| 181 /// all types used in the program and we can tree-shake anything that is | 233 /// all types used in the program and we can tree-shake anything that is |
| 182 /// unused. | 234 /// unused. |
| 183 void onQueueClosed() { | 235 void onQueueClosed() { |
| 184 if (!_isEnabled || !_inCodegen) return; | 236 if (!_isEnabled || !_inCodegen) return; |
| 185 | 237 |
| 186 _lookupMaps.values.forEach((info) { | 238 _lookupMaps.values.forEach((info) { |
| (...skipping 12 matching lines...) Expand all Loading... |
| 199 for (var key in info.unusedEntries.keys) { | 251 for (var key in info.unusedEntries.keys) { |
| 200 if (count != 0) sb.write(','); | 252 if (count != 0) sb.write(','); |
| 201 sb.write(key.unparse()); | 253 sb.write(key.unparse()); |
| 202 count++; | 254 count++; |
| 203 } | 255 } |
| 204 } | 256 } |
| 205 compiler.log(count == 0 | 257 compiler.log(count == 0 |
| 206 ? 'lookup-map: nothing was tree-shaken' | 258 ? 'lookup-map: nothing was tree-shaken' |
| 207 : 'lookup-map: found $count unused keys ($sb)'); | 259 : 'lookup-map: found $count unused keys ($sb)'); |
| 208 } | 260 } |
| 261 |
| 262 // Release resources. |
| 263 _lookupMaps.clear(); |
| 264 _pending.clear(); |
| 265 _inUse.clear(); |
| 209 } | 266 } |
| 210 } | 267 } |
| 211 | 268 |
| 212 /// Internal information about the entries on a lookup-map. | 269 /// Internal information about the entries on a lookup-map. |
| 213 class _LookupMapInfo { | 270 class _LookupMapInfo { |
| 214 /// The original reference to the constant value. | 271 /// The original reference to the constant value. |
| 215 /// | 272 /// |
| 216 /// This reference will be mutated in place to remove it's entries when the | 273 /// This reference will be mutated in place to remove it's entries when the |
| 217 /// map is first seen during codegen, and to restore them (or a subset of | 274 /// map is first seen during codegen, and to restore them (or a subset of |
| 218 /// them) when we have finished discovering which entries are used. This has | 275 /// them) when we have finished discovering which entries are used. This has |
| 219 /// the side-effect that `orignal.getDependencies()` will be empty during | 276 /// the side-effect that `orignal.getDependencies()` will be empty during |
| 220 /// most of codegen until we are ready to emit the constants. However, | 277 /// most of codegen until we are ready to emit the constants. However, |
| 221 /// restoring the entries before emitting code lets us keep the emitter logic | 278 /// restoring the entries before emitting code lets us keep the emitter logic |
| 222 /// agnostic of this optimization. | 279 /// agnostic of this optimization. |
| 223 final ConstructedConstantValue original; | 280 final ConstructedConstantValue original; |
| 224 | 281 |
| 225 /// Reference to the lookup map analysis to be able to refer to data shared | 282 /// Reference to the lookup map analysis to be able to refer to data shared |
| 226 /// accross infos. | 283 /// accross infos. |
| 227 final LookupMapAnalysis analysis; | 284 final LookupMapAnalysis analysis; |
| 228 | 285 |
| 229 /// Whether we have already emitted this constant. | 286 /// Whether we have already emitted this constant. |
| 230 bool emitted = false; | 287 bool emitted = false; |
| 231 | 288 |
| 232 /// Whether the `LookupMap` constant was built using the `LookupMap.pair` | 289 /// Whether the `LookupMap` constant was built using the `LookupMap.pair` |
| 233 /// constructor. | 290 /// constructor. |
| 234 bool singlePair; | 291 bool singlePair; |
| 235 | 292 |
| 236 /// Entries in the lookup map whose keys have not been seen in the rest of the | 293 /// Entries in the lookup map whose keys have not been seen in the rest of the |
| 237 /// program. | 294 /// program. |
| 238 Map<ClassElement, ConstantValue> unusedEntries = | 295 Map<ConstantValue, ConstantValue> unusedEntries = |
| 239 <ClassElement, ConstantValue>{}; | 296 <ConstantValue, ConstantValue> {}; |
| 240 | 297 |
| 241 /// Entries that have been used, and thus will be part of the generated code. | 298 /// Entries that have been used, and thus will be part of the generated code. |
| 242 Map<ClassElement, ConstantValue> usedEntries = | 299 Map<ConstantValue, ConstantValue> usedEntries = |
| 243 <ClassElement, ConstantValue>{}; | 300 <ConstantValue, ConstantValue> {}; |
| 244 | |
| 245 /// Internal helper to memoize the mapping between map class elements and | |
| 246 /// their corresponding type constants. | |
| 247 Map<ClassElement, TypeConstantValue> _typeConstants = | |
| 248 <ClassElement, TypeConstantValue>{}; | |
| 249 | 301 |
| 250 /// Creates and initializes the information containing all keys of the | 302 /// Creates and initializes the information containing all keys of the |
| 251 /// original map marked as unused. | 303 /// original map marked as unused. |
| 252 _LookupMapInfo(this.original, this.analysis) { | 304 _LookupMapInfo(this.original, this.analysis) { |
| 253 ConstantValue key = original.fields[analysis.keyField]; | 305 ConstantValue key = original.fields[analysis.keyField]; |
| 254 singlePair = !key.isNull; | 306 singlePair = !key.isNull; |
| 255 | 307 |
| 256 if (singlePair) { | 308 if (singlePair) { |
| 257 TypeConstantValue typeKey = key; | 309 unusedEntries[key] = original.fields[analysis.valueField]; |
| 258 ClassElement cls = typeKey.representedType.element; | |
| 259 _typeConstants[cls] = typeKey; | |
| 260 unusedEntries[cls] = original.fields[analysis.valueField]; | |
| 261 | 310 |
| 262 // Note: we modify the constant in-place, see comment in [original]. | 311 // Note: we modify the constant in-place, see comment in [original]. |
| 263 original.fields[analysis.keyField] = new NullConstantValue(); | 312 original.fields[analysis.keyField] = new NullConstantValue(); |
| 264 original.fields[analysis.valueField] = new NullConstantValue(); | 313 original.fields[analysis.valueField] = new NullConstantValue(); |
| 265 } else { | 314 } else { |
| 266 ListConstantValue list = original.fields[analysis.entriesField]; | 315 ListConstantValue list = original.fields[analysis.entriesField]; |
| 267 List<ConstantValue> keyValuePairs = list.entries; | 316 List<ConstantValue> keyValuePairs = list.entries; |
| 268 for (int i = 0; i < keyValuePairs.length; i += 2) { | 317 for (int i = 0; i < keyValuePairs.length; i += 2) { |
| 269 TypeConstantValue type = keyValuePairs[i]; | 318 ConstantValue key = keyValuePairs[i]; |
| 270 ClassElement cls = type.representedType.element; | 319 unusedEntries[key] = keyValuePairs[i + 1]; |
| 271 if (cls == null || !cls.isClass) { | |
| 272 // TODO(sigmund): report an error | |
| 273 continue; | |
| 274 } | |
| 275 _typeConstants[cls] = type; | |
| 276 unusedEntries[cls] = keyValuePairs[i + 1]; | |
| 277 } | 320 } |
| 278 | 321 |
| 279 // Note: we modify the constant in-place, see comment in [original]. | 322 // Note: we modify the constant in-place, see comment in [original]. |
| 280 original.fields[analysis.entriesField] = | 323 original.fields[analysis.entriesField] = |
| 281 new ListConstantValue(list.type, []); | 324 new ListConstantValue(list.type, []); |
| 282 } | 325 } |
| 283 } | 326 } |
| 284 | 327 |
| 285 /// Check every key in unusedEntries and mark it as used if the analysis has | 328 /// Check every key in unusedEntries and mark it as used if the analysis has |
| 286 /// already discovered them. This is meant to be called once to finalize | 329 /// already discovered them. This is meant to be called once to finalize |
| 287 /// initialization after constructing an instance of this class. Afterwards, | 330 /// initialization after constructing an instance of this class. Afterwards, |
| 288 /// we call [_markUsed] on each individual key as it gets discovered. | 331 /// we call [_markUsed] on each individual key as it gets discovered. |
| 289 void _updateUsed() { | 332 void _updateUsed() { |
| 290 // Note: we call toList because `_markUsed` modifies the map. | 333 // Note: we call toList because `_markUsed` modifies the map. |
| 291 for (ClassElement type in unusedEntries.keys.toList()) { | 334 for (ConstantValue key in unusedEntries.keys.toList()) { |
| 292 if (analysis._inUse.contains(type)) { | 335 analysis._registerTypeKey(key); |
| 293 _markUsed(type); | 336 if (analysis._shouldKeep(key)) { |
| 337 _markUsed(key); |
| 294 } else { | 338 } else { |
| 295 analysis._pending.putIfAbsent(type, () => []).add(this); | 339 analysis._pending.putIfAbsent(key, () => []).add(this); |
| 296 } | 340 } |
| 297 } | 341 } |
| 298 } | 342 } |
| 299 | 343 |
| 300 /// Marks that [type] is a key that has been seen, and thus, the corresponding | 344 /// Marks that [key] has been seen, and thus, the corresponding entry in this |
| 301 /// entry in this map should be considered reachable. | 345 /// map should be considered reachable. |
| 302 void _markUsed(ClassElement type) { | 346 _markUsed(ConstantValue key) { |
| 303 assert(!emitted); | 347 assert(!emitted); |
| 304 assert(unusedEntries.containsKey(type)); | 348 assert(unusedEntries.containsKey(key)); |
| 305 assert(!usedEntries.containsKey(type)); | 349 assert(!usedEntries.containsKey(key)); |
| 306 ConstantValue constant = unusedEntries.remove(type); | 350 ConstantValue constant = unusedEntries.remove(key); |
| 307 usedEntries[type] = constant; | 351 usedEntries[key] = constant; |
| 308 analysis.backend.registerCompileTimeConstant(constant, | 352 analysis.backend.registerCompileTimeConstant(constant, |
| 309 analysis.backend.compiler.globalDependencies, | 353 analysis.backend.compiler.globalDependencies, |
| 310 addForEmission: false); | 354 addForEmission: false); |
| 311 } | 355 } |
| 312 | 356 |
| 313 /// Restores [original] to contain all of the entries marked as possibly used. | 357 /// Restores [original] to contain all of the entries marked as possibly used. |
| 314 void _prepareForEmission() { | 358 void _prepareForEmission() { |
| 315 ListConstantValue originalEntries = original.fields[analysis.entriesField]; | 359 ListConstantValue originalEntries = original.fields[analysis.entriesField]; |
| 316 DartType listType = originalEntries.type; | 360 DartType listType = originalEntries.type; |
| 317 List<ConstantValue> keyValuePairs = <ConstantValue>[]; | 361 List<ConstantValue> keyValuePairs = <ConstantValue>[]; |
| 318 usedEntries.forEach((key, value) { | 362 usedEntries.forEach((key, value) { |
| 319 keyValuePairs.add(_typeConstants[key]); | 363 keyValuePairs.add(key); |
| 320 keyValuePairs.add(value); | 364 keyValuePairs.add(value); |
| 321 }); | 365 }); |
| 322 | 366 |
| 323 // Note: we are restoring the entries here, see comment in [original]. | 367 // Note: we are restoring the entries here, see comment in [original]. |
| 324 if (singlePair) { | 368 if (singlePair) { |
| 325 assert (keyValuePairs.length == 0 || keyValuePairs.length == 2); | 369 assert (keyValuePairs.length == 0 || keyValuePairs.length == 2); |
| 326 if (keyValuePairs.length == 2) { | 370 if (keyValuePairs.length == 2) { |
| 327 original.fields[analysis.keyField] = keyValuePairs[0]; | 371 original.fields[analysis.keyField] = keyValuePairs[0]; |
| 328 original.fields[analysis.valueField] = keyValuePairs[1]; | 372 original.fields[analysis.valueField] = keyValuePairs[1]; |
| 329 } | 373 } |
| 330 } else { | 374 } else { |
| 331 original.fields[analysis.entriesField] = | 375 original.fields[analysis.entriesField] = |
| 332 new ListConstantValue(listType, keyValuePairs); | 376 new ListConstantValue(listType, keyValuePairs); |
| 333 } | 377 } |
| 334 } | 378 } |
| 335 } | 379 } |
| OLD | NEW |