| 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 |