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

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

Issue 1320503003: dart2js: add initial support for lookup-maps (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
(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 reflectable type-literal if a mirror API is used to create
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 LookupMapAnalysis(this.backend);
96
97 /// Whether this analysis and optimization is enabled.
98 bool get _isEnabled {
99 // `lookupMap==off` kept here to make it easy to test disabling this feature
100 if (const String.fromEnvironment('lookupMap') == 'off') return false;
101 return typeLookupMapClass != null;
102 }
103
104 /// Initializes this analysis by providing the resolver information of
105 /// `LookupMap`.
106 void initRuntimeClass(ClassElement cls) {
107 cls.computeType(backend.compiler);
108 entriesField = cls.lookupMember('_entries');
109 keyField = cls.lookupMember('_key');
110 valueField = cls.lookupMember('_value');
111 // TODO(sigmund): Maybe inline nested maps make the output code smaller?
112 typeLookupMapClass = cls;
113 }
114
115 /// Whether [constant] is an instance of a `LookupMap`.
116 bool isLookupMap(ConstantValue constant) =>
117 _isEnabled &&
118 constant is ConstructedConstantValue &&
119 constant.type.asRaw().element.isSubclassOf(typeLookupMapClass);
120
121 /// Registers an instance of a lookup-map with the analysis.
122 void registerLookupMapReference(ConstantValue lookupMap) {
123 if (!_isEnabled || !_inCodegen) return;
124 assert(isLookupMap(lookupMap));
125 _lookupMaps.putIfAbsent(lookupMap,
126 () => new _LookupMapInfo(lookupMap, this).._updateUsed());
127 }
128
129 /// Records that [type] is used in the program, and updates every map that
130 /// has it as a key.
131 void _addUse(ClassElement type) {
132 if (_inUse.add(type)) {
133 _pending[type]?.forEach((info) => info._markUsed(type));
134 _pending.remove(type);
135 }
136 }
137
138 /// Callback from the enqueuer, invoked when [element] is instantiated.
139 void registerInstantiatedClass(ClassElement element) {
140 if (!_isEnabled || !_inCodegen) return;
141 // TODO(sigmund): only add if .runtimeType is ever used
142 _addUse(element);
143 }
144
145 /// Callback from the enqueuer, invoked when [type] is instantiated.
146 void registerInstantiatedType(InterfaceType type, Registry registry) {
147 if (!_isEnabled || !_inCodegen) return;
148 // TODO(sigmund): only add if .runtimeType is ever used
149 _addUse(type.element);
150 // TODO(sigmund): only do this when type-argument expressions are used?
151 _addGenerics(type, registry);
152 }
153
154 /// Records generic type arguments in [type], in case they are retrieved and
155 /// returned using a type-argument expression.
156 void _addGenerics(InterfaceType type, Registry registry) {
157 if (!type.isGeneric) return;
158 for (var arg in type.typeArguments) {
159 if (arg is InterfaceType) {
160 _addUse(arg.element);
161 // Note: this call was needed to generate correct code for
162 // type_lookup_map/generic_type_test
163 // TODO(sigmund): can we get rid of this?
164 backend.registerInstantiatedConstantType(
165 backend.typeImplementation.rawType, registry);
166 _addGenerics(arg, registry);
167 }
168 }
169 }
170
171 /// Callback from the codegen enqueuer, invoked when a type constant
172 /// corresponding to the [element] is used in the program.
173 void registerTypeConstant(Element element) {
174 if (!_isEnabled || !_inCodegen) return;
175 assert(element.isClass);
176 _addUse(element);
177 }
178
179 /// 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
181 /// all types used in the program and we can tree-shake anything that is
182 /// unused.
183 void onQueueClosed() {
184 if (!_isEnabled || !_inCodegen) return;
185
186 _lookupMaps.values.forEach((info) {
187 assert (!info.emitted);
188 info.emitted = true;
189 info._prepareForEmission();
190 });
191
192 // When --verbose is passed, we show the total number and set of keys that
193 // were tree-shaken from lookup maps.
194 Compiler compiler = backend.compiler;
195 if (compiler.verbose) {
196 var sb = new StringBuffer();
197 int count = 0;
198 for (var info in _lookupMaps.values) {
199 for (var key in info.unusedEntries.keys) {
200 if (count != 0) sb.write(',');
201 sb.write(key.unparse());
202 count++;
203 }
204 }
205 compiler.log(count == 0
206 ? 'lookup-map: nothing was tree-shaken'
207 : 'lookup-map: found $count unused keys ($sb)');
208 }
209 }
210 }
211
212 /// Internal information about the entries on a lookup-map.
213 class _LookupMapInfo {
214 /// The original reference to the constant value.
215 ///
216 /// 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
218 /// them) when we have finished discovering which entries are used. This has
219 /// the side-effect that `orignal.getDependencies()` will be empty during
220 /// 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
222 /// agnostic of this optimization.
223 final ConstructedConstantValue original;
224
225 /// Reference to the lookup map analysis to be able to refer to data shared
226 /// accross infos.
227 final LookupMapAnalysis analysis;
228
229 /// Whether we have already emitted this constant.
230 bool emitted = false;
231
232 /// Whether the `LookupMap` constant was built using the `LookupMap.pair`
233 /// constructor.
234 bool singlePair;
235
236 /// Entries in the lookup map whose keys have not been seen in the rest of the
237 /// program.
238 Map<ClassElement, ConstantValue> unusedEntries =
239 <ClassElement, ConstantValue>{};
240
241 /// Entries that have been used, and thus will be part of the generated code.
242 Map<ClassElement, ConstantValue> usedEntries =
243 <ClassElement, 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
250 /// Creates and initializes the information containing all keys of the
251 /// original map marked as unused.
252 _LookupMapInfo(this.original, this.analysis) {
253 ConstantValue key = original.fields[analysis.keyField];
254 singlePair = !key.isNull;
255
256 if (singlePair) {
257 TypeConstantValue typeKey = key;
258 ClassElement cls = typeKey.representedType.element;
259 _typeConstants[cls] = typeKey;
260 unusedEntries[cls] = original.fields[analysis.valueField];
261
262 // Note: we modify the constant in-place, see comment in [original].
263 original.fields[analysis.keyField] = new NullConstantValue();
264 original.fields[analysis.valueField] = new NullConstantValue();
265 } else {
266 ListConstantValue list = original.fields[analysis.entriesField];
267 List<ConstantValue> keyValuePairs = list.entries;
268 for (int i = 0; i < keyValuePairs.length; i += 2) {
269 TypeConstantValue type = keyValuePairs[i];
270 ClassElement cls = type.representedType.element;
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 }
278
279 // Note: we modify the constant in-place, see comment in [original].
280 original.fields[analysis.entriesField] =
281 new ListConstantValue(list.type, []);
282 }
283 }
284
285 /// 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
287 /// initialization after constructing an instance of this class. Afterwards,
288 /// we call [_markUsed] on each individual key as it gets discovered.
289 void _updateUsed() {
290 // Note: we call toList because `_markUsed` modifies the map.
291 for (ClassElement type in unusedEntries.keys.toList()) {
292 if (analysis._inUse.contains(type)) {
293 _markUsed(type);
294 } else {
295 analysis._pending.putIfAbsent(type, () => []).add(this);
296 }
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 void _markUsed(ClassElement type) {
303 assert(!emitted);
304 assert(unusedEntries.containsKey(type));
305 assert(!usedEntries.containsKey(type));
306 ConstantValue constant = unusedEntries.remove(type);
307 usedEntries[type] = constant;
308 analysis.backend.registerCompileTimeConstant(constant,
309 analysis.backend.compiler.globalDependencies,
310 addForEmission: false);
311 }
312
313 /// Restores [original] to contain all of the entries marked as possibly used.
314 void _prepareForEmission() {
315 ListConstantValue originalEntries = original.fields[analysis.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[analysis.keyField] = keyValuePairs[0];
328 original.fields[analysis.valueField] = keyValuePairs[1];
329 }
330 } else {
331 original.fields[analysis.entriesField] =
332 new ListConstantValue(listType, keyValuePairs);
333 }
334 }
335 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698