Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(25)

Side by Side Diff: pkg/compiler/lib/src/js_backend/lookup_map_analysis.dart

Issue 1310183014: Generalize lookup-maps to support other const keys (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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
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
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 }
OLDNEW
« no previous file with comments | « pkg/compiler/lib/src/js_backend/backend.dart ('k') | tests/compiler/dart2js/lookup_map_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698