OLD | NEW |
| (Empty) |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 part of dart2js.js_emitter; | |
6 | |
7 class TypeTestEmitter extends CodeEmitterHelper { | |
8 static const int MAX_FUNCTION_TYPE_PREDICATES = 10; | |
9 | |
10 /** | |
11 * Raw ClassElement symbols occuring in is-checks and type assertions. If the | |
12 * program contains parameterized checks `x is Set<int>` and | |
13 * `x is Set<String>` then the ClassElement `Set` will occur once in | |
14 * [checkedClasses]. | |
15 */ | |
16 Set<ClassElement> checkedClasses; | |
17 | |
18 /** | |
19 * The set of function types that checked, both explicity through tests of | |
20 * typedefs and implicitly through type annotations in checked mode. | |
21 */ | |
22 Set<FunctionType> checkedFunctionTypes; | |
23 | |
24 Map<ClassElement, Set<FunctionType>> checkedGenericFunctionTypes = | |
25 new Map<ClassElement, Set<FunctionType>>(); | |
26 | |
27 Set<FunctionType> checkedNonGenericFunctionTypes = | |
28 new Set<FunctionType>(); | |
29 | |
30 final Set<ClassElement> rtiNeededClasses = new Set<ClassElement>(); | |
31 | |
32 Iterable<ClassElement> cachedClassesUsingTypeVariableTests; | |
33 | |
34 Iterable<ClassElement> get classesUsingTypeVariableTests { | |
35 if (cachedClassesUsingTypeVariableTests == null) { | |
36 cachedClassesUsingTypeVariableTests = compiler.codegenWorld.isChecks | |
37 .where((DartType t) => t is TypeVariableType) | |
38 .map((TypeVariableType v) => v.element.enclosingClass) | |
39 .toList(); | |
40 } | |
41 return cachedClassesUsingTypeVariableTests; | |
42 } | |
43 | |
44 void emitIsTests(ClassElement classElement, ClassBuilder builder) { | |
45 assert(invariant(classElement, classElement.isDeclaration)); | |
46 | |
47 void generateIsTest(Element other) { | |
48 if (other == compiler.objectClass && other != classElement) { | |
49 // Avoid emitting [:$isObject:] on all classes but [Object]. | |
50 return; | |
51 } | |
52 builder.addProperty(namer.operatorIs(other), js('true')); | |
53 } | |
54 | |
55 void generateFunctionTypeSignature(FunctionElement method, | |
56 FunctionType type) { | |
57 assert(method.isImplementation); | |
58 jsAst.Expression thisAccess = new jsAst.This(); | |
59 Node node = method.node; | |
60 ClosureClassMap closureData = | |
61 compiler.closureToClassMapper.closureMappingCache[node]; | |
62 if (closureData != null) { | |
63 ClosureFieldElement thisLocal = | |
64 closureData.getFreeVariableElement(closureData.thisLocal); | |
65 if (thisLocal != null) { | |
66 String thisName = namer.instanceFieldPropertyName(thisLocal); | |
67 thisAccess = js('this.#', thisName); | |
68 } | |
69 } | |
70 RuntimeTypes rti = backend.rti; | |
71 jsAst.Expression encoding = rti.getSignatureEncoding(type, thisAccess); | |
72 String operatorSignature = namer.operatorSignature(); | |
73 if (!type.containsTypeVariables) { | |
74 builder.functionType = '${emitter.metadataEmitter.reifyType(type)}'; | |
75 } else { | |
76 builder.addProperty(operatorSignature, encoding); | |
77 } | |
78 } | |
79 | |
80 void generateSubstitution(ClassElement cls, {bool emitNull: false}) { | |
81 if (cls.typeVariables.isEmpty) return; | |
82 RuntimeTypes rti = backend.rti; | |
83 jsAst.Expression expression; | |
84 bool needsNativeCheck = emitter.nativeEmitter.requiresNativeIsCheck(cls); | |
85 expression = rti.getSupertypeSubstitution( | |
86 classElement, cls, alwaysGenerateFunction: true); | |
87 if (expression == null && (emitNull || needsNativeCheck)) { | |
88 expression = new jsAst.LiteralNull(); | |
89 } | |
90 if (expression != null) { | |
91 builder.addProperty(namer.substitutionName(cls), expression); | |
92 } | |
93 } | |
94 | |
95 generateIsTestsOn(classElement, generateIsTest, | |
96 generateFunctionTypeSignature, | |
97 generateSubstitution); | |
98 } | |
99 | |
100 /** | |
101 * Generate "is tests" for [cls]: itself, and the "is tests" for the | |
102 * classes it implements and type argument substitution functions for these | |
103 * tests. We don't need to add the "is tests" of the super class because | |
104 * they will be inherited at runtime, but we may need to generate the | |
105 * substitutions, because they may have changed. | |
106 */ | |
107 void generateIsTestsOn(ClassElement cls, | |
108 void emitIsTest(Element element), | |
109 FunctionTypeSignatureEmitter emitFunctionTypeSignature, | |
110 SubstitutionEmitter emitSubstitution) { | |
111 if (checkedClasses.contains(cls)) { | |
112 emitIsTest(cls); | |
113 emitSubstitution(cls); | |
114 } | |
115 | |
116 RuntimeTypes rti = backend.rti; | |
117 ClassElement superclass = cls.superclass; | |
118 | |
119 bool haveSameTypeVariables(ClassElement a, ClassElement b) { | |
120 if (a.isClosure) return true; | |
121 return backend.rti.isTrivialSubstitution(a, b); | |
122 } | |
123 | |
124 if (superclass != null && superclass != compiler.objectClass && | |
125 !haveSameTypeVariables(cls, superclass)) { | |
126 // We cannot inherit the generated substitutions, because the type | |
127 // variable layout for this class is different. Instead we generate | |
128 // substitutions for all checks and make emitSubstitution a NOP for the | |
129 // rest of this function. | |
130 Set<ClassElement> emitted = new Set<ClassElement>(); | |
131 // TODO(karlklose): move the computation of these checks to | |
132 // RuntimeTypeInformation. | |
133 while (superclass != null) { | |
134 if (backend.classNeedsRti(superclass)) { | |
135 emitSubstitution(superclass, emitNull: true); | |
136 emitted.add(superclass); | |
137 } | |
138 superclass = superclass.superclass; | |
139 } | |
140 for (DartType supertype in cls.allSupertypes) { | |
141 ClassElement superclass = supertype.element; | |
142 if (classesUsingTypeVariableTests.contains(superclass)) { | |
143 emitSubstitution(superclass, emitNull: true); | |
144 emitted.add(superclass); | |
145 } | |
146 for (ClassElement check in checkedClasses) { | |
147 if (supertype.element == check && !emitted.contains(check)) { | |
148 // Generate substitution. If no substitution is necessary, emit | |
149 // [:null:] to overwrite a (possibly) existing substitution from the | |
150 // super classes. | |
151 emitSubstitution(check, emitNull: true); | |
152 emitted.add(check); | |
153 } | |
154 } | |
155 } | |
156 void emitNothing(_, {emitNull}) {}; | |
157 emitSubstitution = emitNothing; | |
158 } | |
159 | |
160 Set<Element> generated = new Set<Element>(); | |
161 // A class that defines a [:call:] method implicitly implements | |
162 // [Function] and needs checks for all typedefs that are used in is-checks. | |
163 if (checkedClasses.contains(compiler.functionClass) || | |
164 !checkedFunctionTypes.isEmpty) { | |
165 Element call = cls.lookupLocalMember(Compiler.CALL_OPERATOR_NAME); | |
166 if (call == null) { | |
167 // If [cls] is a closure, it has a synthetic call operator method. | |
168 call = cls.lookupBackendMember(Compiler.CALL_OPERATOR_NAME); | |
169 } | |
170 if (call != null && call.isFunction) { | |
171 generateInterfacesIsTests(compiler.functionClass, | |
172 emitIsTest, | |
173 emitSubstitution, | |
174 generated); | |
175 FunctionType callType = call.computeType(compiler); | |
176 Map<FunctionType, bool> functionTypeChecks = | |
177 getFunctionTypeChecksOn(callType); | |
178 generateFunctionTypeTests( | |
179 call, callType, functionTypeChecks, | |
180 emitFunctionTypeSignature); | |
181 } | |
182 } | |
183 | |
184 for (DartType interfaceType in cls.interfaces) { | |
185 generateInterfacesIsTests(interfaceType.element, emitIsTest, | |
186 emitSubstitution, generated); | |
187 } | |
188 } | |
189 | |
190 /** | |
191 * Generate "is tests" where [cls] is being implemented. | |
192 */ | |
193 void generateInterfacesIsTests(ClassElement cls, | |
194 void emitIsTest(ClassElement element), | |
195 SubstitutionEmitter emitSubstitution, | |
196 Set<Element> alreadyGenerated) { | |
197 void tryEmitTest(ClassElement check) { | |
198 if (!alreadyGenerated.contains(check) && checkedClasses.contains(check)) { | |
199 alreadyGenerated.add(check); | |
200 emitIsTest(check); | |
201 emitSubstitution(check); | |
202 } | |
203 }; | |
204 | |
205 tryEmitTest(cls); | |
206 | |
207 for (DartType interfaceType in cls.interfaces) { | |
208 Element element = interfaceType.element; | |
209 tryEmitTest(element); | |
210 generateInterfacesIsTests(element, emitIsTest, emitSubstitution, | |
211 alreadyGenerated); | |
212 } | |
213 | |
214 // We need to also emit "is checks" for the superclass and its supertypes. | |
215 ClassElement superclass = cls.superclass; | |
216 if (superclass != null) { | |
217 tryEmitTest(superclass); | |
218 generateInterfacesIsTests(superclass, emitIsTest, emitSubstitution, | |
219 alreadyGenerated); | |
220 } | |
221 } | |
222 | |
223 /** | |
224 * Returns a mapping containing all checked function types for which [type] | |
225 * can be a subtype. A function type is mapped to [:true:] if [type] is | |
226 * statically known to be a subtype of it and to [:false:] if [type] might | |
227 * be a subtype, provided with the right type arguments. | |
228 */ | |
229 // TODO(johnniwinther): Change to return a mapping from function types to | |
230 // a set of variable points and use this to detect statically/dynamically | |
231 // known subtype relations. | |
232 Map<FunctionType, bool> getFunctionTypeChecksOn(DartType type) { | |
233 Map<FunctionType, bool> functionTypeMap = new Map<FunctionType, bool>(); | |
234 for (FunctionType functionType in checkedFunctionTypes) { | |
235 int maybeSubtype = | |
236 compiler.types.computeSubtypeRelation(type, functionType); | |
237 if (maybeSubtype == Types.IS_SUBTYPE) { | |
238 functionTypeMap[functionType] = true; | |
239 } else if (maybeSubtype == Types.MAYBE_SUBTYPE) { | |
240 functionTypeMap[functionType] = false; | |
241 } | |
242 } | |
243 // TODO(johnniwinther): Ensure stable ordering of the keys. | |
244 return functionTypeMap; | |
245 } | |
246 | |
247 /** | |
248 * Generates function type checks on [method] with type [methodType] against | |
249 * the function type checks in [functionTypeChecks]. | |
250 */ | |
251 void generateFunctionTypeTests( | |
252 Element method, | |
253 FunctionType methodType, | |
254 Map<FunctionType, bool> functionTypeChecks, | |
255 FunctionTypeSignatureEmitter emitFunctionTypeSignature) { | |
256 | |
257 // TODO(ahe): We should be able to remove this forEach loop. | |
258 functionTypeChecks.forEach((FunctionType functionType, bool knownSubtype) { | |
259 registerDynamicFunctionTypeCheck(functionType); | |
260 }); | |
261 | |
262 emitFunctionTypeSignature(method, methodType); | |
263 } | |
264 | |
265 void registerDynamicFunctionTypeCheck(FunctionType functionType) { | |
266 ClassElement classElement = Types.getClassContext(functionType); | |
267 if (classElement != null) { | |
268 checkedGenericFunctionTypes.putIfAbsent(classElement, | |
269 () => new Set<FunctionType>()).add(functionType); | |
270 } else { | |
271 checkedNonGenericFunctionTypes.add(functionType); | |
272 } | |
273 } | |
274 | |
275 void emitRuntimeTypeSupport(CodeBuffer buffer, OutputUnit outputUnit) { | |
276 emitter.addComment('Runtime type support', buffer); | |
277 RuntimeTypes rti = backend.rti; | |
278 TypeChecks typeChecks = rti.requiredChecks; | |
279 | |
280 // Add checks to the constructors of instantiated classes. | |
281 // TODO(sigurdm): We should avoid running through this list for each | |
282 // output unit. | |
283 | |
284 jsAst.Statement variables = js.statement('var TRUE = !0, _;'); | |
285 List<jsAst.Statement> statements = <jsAst.Statement>[]; | |
286 | |
287 for (ClassElement cls in typeChecks) { | |
288 OutputUnit destination = | |
289 compiler.deferredLoadTask.outputUnitForElement(cls); | |
290 if (destination != outputUnit) continue; | |
291 // TODO(9556). The properties added to 'holder' should be generated | |
292 // directly as properties of the class object, not added later. | |
293 | |
294 // Each element is a pair: [propertyName, valueExpression] | |
295 List<List> properties = <List>[]; | |
296 | |
297 for (TypeCheck check in typeChecks[cls]) { | |
298 ClassElement checkedClass = check.cls; | |
299 properties.add([namer.operatorIs(checkedClass), js('TRUE')]); | |
300 Substitution substitution = check.substitution; | |
301 if (substitution != null) { | |
302 jsAst.Expression body = substitution.getCode(rti, false); | |
303 properties.add([namer.substitutionName(checkedClass), body]); | |
304 } | |
305 } | |
306 | |
307 jsAst.Expression holder = namer.elementAccess(cls); | |
308 if (properties.length > 1) { | |
309 // Use temporary shortened reference. | |
310 statements.add(js.statement('_ = #;', holder)); | |
311 holder = js('#', '_'); | |
312 } | |
313 for (List nameAndValue in properties) { | |
314 statements.add( | |
315 js.statement('#.# = #', | |
316 [holder, nameAndValue[0], nameAndValue[1]])); | |
317 } | |
318 } | |
319 | |
320 if (statements.isNotEmpty) { | |
321 buffer.write(';'); | |
322 buffer.write( | |
323 jsAst.prettyPrint( | |
324 js.statement('(function() { #; #; })()', [variables, statements]), | |
325 compiler)); | |
326 buffer.write('$N'); | |
327 } | |
328 } | |
329 | |
330 /** | |
331 * Returns the classes with constructors used as a 'holder' in | |
332 * [emitRuntimeTypeSupport]. | |
333 * TODO(9556): Some cases will go away when the class objects are created as | |
334 * complete. Not all classes will go away while constructors are referenced | |
335 * from type substitutions. | |
336 */ | |
337 Set<ClassElement> classesModifiedByEmitRuntimeTypeSupport() { | |
338 TypeChecks typeChecks = backend.rti.requiredChecks; | |
339 Set<ClassElement> result = new Set<ClassElement>(); | |
340 for (ClassElement cls in typeChecks) { | |
341 for (TypeCheck check in typeChecks[cls]) { | |
342 result.add(cls); | |
343 break; | |
344 } | |
345 } | |
346 return result; | |
347 } | |
348 | |
349 Set<ClassElement> computeRtiNeededClasses() { | |
350 void addClassWithSuperclasses(ClassElement cls) { | |
351 rtiNeededClasses.add(cls); | |
352 for (ClassElement superclass = cls.superclass; | |
353 superclass != null; | |
354 superclass = superclass.superclass) { | |
355 rtiNeededClasses.add(superclass); | |
356 } | |
357 } | |
358 | |
359 void addClassesWithSuperclasses(Iterable<ClassElement> classes) { | |
360 for (ClassElement cls in classes) { | |
361 addClassWithSuperclasses(cls); | |
362 } | |
363 } | |
364 | |
365 // 1. Add classes that are referenced by type arguments or substitutions in | |
366 // argument checks. | |
367 // TODO(karlklose): merge this case with 2 when unifying argument and | |
368 // object checks. | |
369 RuntimeTypes rti = backend.rti; | |
370 rti.getRequiredArgumentClasses(backend) | |
371 .forEach(addClassWithSuperclasses); | |
372 | |
373 // 2. Add classes that are referenced by substitutions in object checks and | |
374 // their superclasses. | |
375 TypeChecks requiredChecks = | |
376 rti.computeChecks(rtiNeededClasses, checkedClasses); | |
377 Set<ClassElement> classesUsedInSubstitutions = | |
378 rti.getClassesUsedInSubstitutions(backend, requiredChecks); | |
379 addClassesWithSuperclasses(classesUsedInSubstitutions); | |
380 | |
381 // 3. Add classes that contain checked generic function types. These are | |
382 // needed to store the signature encoding. | |
383 for (FunctionType type in checkedFunctionTypes) { | |
384 ClassElement contextClass = Types.getClassContext(type); | |
385 if (contextClass != null) { | |
386 rtiNeededClasses.add(contextClass); | |
387 } | |
388 } | |
389 | |
390 bool canTearOff(Element function) { | |
391 if (!function.isFunction || | |
392 function.isConstructor || | |
393 function.isAccessor) { | |
394 return false; | |
395 } else if (function.isInstanceMember) { | |
396 if (!function.enclosingClass.isClosure) { | |
397 return compiler.codegenWorld.hasInvokedGetter( | |
398 function, compiler.world); | |
399 } | |
400 } | |
401 return false; | |
402 } | |
403 | |
404 bool canBeReflectedAsFunction(Element element) { | |
405 return element.kind == ElementKind.FUNCTION || | |
406 element.kind == ElementKind.GETTER || | |
407 element.kind == ElementKind.SETTER || | |
408 element.kind == ElementKind.GENERATIVE_CONSTRUCTOR; | |
409 } | |
410 | |
411 bool canBeReified(Element element) { | |
412 return (canTearOff(element) || backend.isAccessibleByReflection(element)); | |
413 } | |
414 | |
415 // Find all types referenced from the types of elements that can be | |
416 // reflected on 'as functions'. | |
417 backend.generatedCode.keys.where((element) { | |
418 return canBeReflectedAsFunction(element) && canBeReified(element); | |
419 }).forEach((FunctionElement function) { | |
420 DartType type = function.computeType(compiler); | |
421 for (ClassElement cls in backend.rti.getReferencedClasses(type)) { | |
422 while (cls != null) { | |
423 rtiNeededClasses.add(cls); | |
424 cls = cls.superclass; | |
425 } | |
426 } | |
427 }); | |
428 | |
429 return rtiNeededClasses; | |
430 } | |
431 | |
432 void computeRequiredTypeChecks() { | |
433 assert(checkedClasses == null && checkedFunctionTypes == null); | |
434 | |
435 backend.rti.addImplicitChecks(compiler.codegenWorld, | |
436 classesUsingTypeVariableTests); | |
437 | |
438 checkedClasses = new Set<ClassElement>(); | |
439 checkedFunctionTypes = new Set<FunctionType>(); | |
440 compiler.codegenWorld.isChecks.forEach((DartType t) { | |
441 if (t is InterfaceType) { | |
442 checkedClasses.add(t.element); | |
443 } else if (t is FunctionType) { | |
444 checkedFunctionTypes.add(t); | |
445 } | |
446 }); | |
447 } | |
448 } | |
OLD | NEW |