OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, 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 part of js_backend; | 5 part of js_backend; |
6 | 6 |
7 /** | 7 /** |
8 * A function element that represents a closure call. The signature is copied | 8 * A function element that represents a closure call. The signature is copied |
9 * from the given element. | 9 * from the given element. |
10 */ | 10 */ |
(...skipping 30 matching lines...) Expand all Loading... |
41 // Has the same signature as [DefineStubFunction]. | 41 // Has the same signature as [DefineStubFunction]. |
42 void addProperty(String name, jsAst.Expression value) { | 42 void addProperty(String name, jsAst.Expression value) { |
43 properties.add(new jsAst.Property(js.string(name), value)); | 43 properties.add(new jsAst.Property(js.string(name), value)); |
44 } | 44 } |
45 | 45 |
46 jsAst.Expression toObjectInitializer() { | 46 jsAst.Expression toObjectInitializer() { |
47 return new jsAst.ObjectInitializer(properties); | 47 return new jsAst.ObjectInitializer(properties); |
48 } | 48 } |
49 } | 49 } |
50 | 50 |
| 51 // Function signatures used in the generation of runtime type information. |
| 52 typedef void FunctionTypeSignatureEmitter(Element method, |
| 53 FunctionType methodType); |
| 54 // TODO(johnniwinther): Clean up terminology for rti in the emitter. |
| 55 typedef void FunctionTypeTestEmitter(FunctionType functionType); |
| 56 typedef void SubstitutionEmitter(Element element, {bool emitNull}); |
| 57 |
51 /** | 58 /** |
52 * Generates the code for all used classes in the program. Static fields (even | 59 * Generates the code for all used classes in the program. Static fields (even |
53 * in classes) are ignored, since they can be treated as non-class elements. | 60 * in classes) are ignored, since they can be treated as non-class elements. |
54 * | 61 * |
55 * The code for the containing (used) methods must exist in the [:universe:]. | 62 * The code for the containing (used) methods must exist in the [:universe:]. |
56 */ | 63 */ |
57 class CodeEmitterTask extends CompilerTask { | 64 class CodeEmitterTask extends CompilerTask { |
58 bool needsInheritFunction = false; | 65 bool needsInheritFunction = false; |
59 bool needsDefineClass = false; | 66 bool needsDefineClass = false; |
60 bool needsMixinSupport = false; | 67 bool needsMixinSupport = false; |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
105 | 112 |
106 /** | 113 /** |
107 * Raw ClassElement symbols occuring in is-checks and type assertions. If the | 114 * Raw ClassElement symbols occuring in is-checks and type assertions. If the |
108 * program contains parameterized checks `x is Set<int>` and | 115 * program contains parameterized checks `x is Set<int>` and |
109 * `x is Set<String>` then the ClassElement `Set` will occur once in | 116 * `x is Set<String>` then the ClassElement `Set` will occur once in |
110 * [checkedClasses]. | 117 * [checkedClasses]. |
111 */ | 118 */ |
112 Set<ClassElement> checkedClasses; | 119 Set<ClassElement> checkedClasses; |
113 | 120 |
114 /** | 121 /** |
115 * Raw Typedef symbols occuring in is-checks and type assertions. If the | 122 * The set of function types that checked, both explicity through tests of |
116 * program contains `x is F<int>` and `x is F<bool>` then the TypedefElement | 123 * typedefs and implicitly through type annotations in checked mode. |
117 * `F` will occur once in [checkedTypedefs]. | |
118 */ | 124 */ |
119 Set<TypedefElement> checkedTypedefs; | 125 Set<FunctionType> checkedFunctionTypes; |
| 126 |
| 127 Map<ClassElement, Set<FunctionType>> checkedGenericFunctionTypes = |
| 128 new Map<ClassElement, Set<FunctionType>>(); |
| 129 |
| 130 Set<FunctionType> checkedNonGenericFunctionTypes = |
| 131 new Set<FunctionType>(); |
| 132 |
| 133 void registerDynamicFunctionTypeCheck(FunctionType functionType) { |
| 134 ClassElement classElement = Types.getClassContext(functionType); |
| 135 if (classElement != null) { |
| 136 checkedGenericFunctionTypes.putIfAbsent(classElement, |
| 137 () => new Set<FunctionType>()).add(functionType); |
| 138 } else { |
| 139 checkedNonGenericFunctionTypes.add(functionType); |
| 140 } |
| 141 } |
120 | 142 |
121 final bool generateSourceMap; | 143 final bool generateSourceMap; |
122 | 144 |
123 Iterable<ClassElement> cachedClassesUsingTypeVariableTests; | 145 Iterable<ClassElement> cachedClassesUsingTypeVariableTests; |
124 | 146 |
125 Iterable<ClassElement> get classesUsingTypeVariableTests { | 147 Iterable<ClassElement> get classesUsingTypeVariableTests { |
126 if (cachedClassesUsingTypeVariableTests == null) { | 148 if (cachedClassesUsingTypeVariableTests == null) { |
127 cachedClassesUsingTypeVariableTests = compiler.codegenWorld.isChecks | 149 cachedClassesUsingTypeVariableTests = compiler.codegenWorld.isChecks |
128 .where((DartType t) => t is TypeVariableType) | 150 .where((DartType t) => t is TypeVariableType) |
129 .map((TypeVariableType v) => v.element.getEnclosingClass()) | 151 .map((TypeVariableType v) => v.element.getEnclosingClass()) |
(...skipping 10 matching lines...) Expand all Loading... |
140 constantEmitter = new ConstantEmitter(compiler, namer), | 162 constantEmitter = new ConstantEmitter(compiler, namer), |
141 super(compiler) { | 163 super(compiler) { |
142 nativeEmitter = new NativeEmitter(this); | 164 nativeEmitter = new NativeEmitter(this); |
143 } | 165 } |
144 | 166 |
145 void addComment(String comment, CodeBuffer buffer) { | 167 void addComment(String comment, CodeBuffer buffer) { |
146 buffer.write(jsAst.prettyPrint(js.comment(comment), compiler)); | 168 buffer.write(jsAst.prettyPrint(js.comment(comment), compiler)); |
147 } | 169 } |
148 | 170 |
149 void computeRequiredTypeChecks() { | 171 void computeRequiredTypeChecks() { |
150 assert(checkedClasses == null && checkedTypedefs == null); | 172 assert(checkedClasses == null && checkedFunctionTypes == null); |
151 | 173 |
152 backend.rti.addImplicitChecks(compiler.codegenWorld, | 174 backend.rti.addImplicitChecks(compiler.codegenWorld, |
153 classesUsingTypeVariableTests); | 175 classesUsingTypeVariableTests); |
154 | 176 |
155 checkedClasses = new Set<ClassElement>(); | 177 checkedClasses = new Set<ClassElement>(); |
156 checkedTypedefs = new Set<TypedefElement>(); | 178 checkedFunctionTypes = new Set<FunctionType>(); |
157 compiler.codegenWorld.isChecks.forEach((DartType t) { | 179 compiler.codegenWorld.isChecks.forEach((DartType t) { |
158 if (t is InterfaceType) { | 180 if (!t.isMalformed) { |
159 checkedClasses.add(t.element); | 181 if (t is InterfaceType) { |
160 } else if (t is TypedefType) { | 182 checkedClasses.add(t.element); |
161 checkedTypedefs.add(t.element); | 183 } else if (t is FunctionType) { |
| 184 checkedFunctionTypes.add(t); |
| 185 } |
162 } | 186 } |
163 }); | 187 }); |
164 } | 188 } |
165 | 189 |
166 ClassElement computeMixinClass(MixinApplicationElement mixinApplication) { | 190 ClassElement computeMixinClass(MixinApplicationElement mixinApplication) { |
167 ClassElement mixin = mixinApplication.mixin; | 191 ClassElement mixin = mixinApplication.mixin; |
168 while (mixin.isMixinApplication) { | 192 while (mixin.isMixinApplication) { |
169 mixinApplication = mixin; | 193 mixinApplication = mixin; |
170 mixin = mixinApplication.mixin; | 194 mixin = mixinApplication.mixin; |
171 } | 195 } |
(...skipping 1140 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1312 | 1336 |
1313 void generateIsTest(Element other) { | 1337 void generateIsTest(Element other) { |
1314 if (other == compiler.objectClass && other != classElement) { | 1338 if (other == compiler.objectClass && other != classElement) { |
1315 // Avoid emitting [:$isObject:] on all classes but [Object]. | 1339 // Avoid emitting [:$isObject:] on all classes but [Object]. |
1316 return; | 1340 return; |
1317 } | 1341 } |
1318 other = backend.getImplementationClass(other); | 1342 other = backend.getImplementationClass(other); |
1319 builder.addProperty(namer.operatorIs(other), js('true')); | 1343 builder.addProperty(namer.operatorIs(other), js('true')); |
1320 } | 1344 } |
1321 | 1345 |
| 1346 void generateIsFunctionTypeTest(FunctionType type) { |
| 1347 String operator = namer.operatorIsType(type); |
| 1348 builder.addProperty(operator, new jsAst.LiteralBool(true)); |
| 1349 } |
| 1350 |
| 1351 void generateFunctionTypeSignature(Element method, FunctionType type) { |
| 1352 assert(method.isImplementation); |
| 1353 String thisAccess = 'this'; |
| 1354 Node node = method.parseNode(compiler); |
| 1355 ClosureClassMap closureData = |
| 1356 compiler.closureToClassMapper.closureMappingCache[node]; |
| 1357 if (closureData != null) { |
| 1358 Element thisElement = |
| 1359 closureData.freeVariableMapping[closureData.thisElement]; |
| 1360 if (thisElement != null) { |
| 1361 String thisName = backend.namer.getName(thisElement); |
| 1362 thisAccess = 'this.$thisName'; |
| 1363 } |
| 1364 } |
| 1365 RuntimeTypes rti = backend.rti; |
| 1366 String encoding = rti.getSignatureEncoding(type, () => '$thisAccess'); |
| 1367 String operatorSignature = namer.operatorSignature(); |
| 1368 builder.addProperty(operatorSignature, |
| 1369 new jsAst.LiteralExpression(encoding)); |
| 1370 } |
| 1371 |
1322 void generateSubstitution(Element other, {bool emitNull: false}) { | 1372 void generateSubstitution(Element other, {bool emitNull: false}) { |
1323 RuntimeTypes rti = backend.rti; | 1373 RuntimeTypes rti = backend.rti; |
1324 // TODO(karlklose): support typedefs with variables. | |
1325 jsAst.Expression expression; | 1374 jsAst.Expression expression; |
1326 bool needsNativeCheck = nativeEmitter.requiresNativeIsCheck(other); | 1375 bool needsNativeCheck = nativeEmitter.requiresNativeIsCheck(other); |
1327 if (other.kind == ElementKind.CLASS) { | 1376 if (other.kind == ElementKind.CLASS) { |
1328 String substitution = rti.getSupertypeSubstitution(classElement, other, | 1377 String substitution = rti.getSupertypeSubstitution(classElement, other, |
1329 alwaysGenerateFunction: true); | 1378 alwaysGenerateFunction: true); |
1330 if (substitution != null) { | 1379 if (substitution != null) { |
1331 expression = new jsAst.LiteralExpression(substitution); | 1380 expression = new jsAst.LiteralExpression(substitution); |
1332 } else if (emitNull || needsNativeCheck) { | 1381 } else if (emitNull || needsNativeCheck) { |
1333 expression = new jsAst.LiteralNull(); | 1382 expression = new jsAst.LiteralNull(); |
1334 } | 1383 } |
1335 } | 1384 } |
1336 if (expression != null) { | 1385 if (expression != null) { |
1337 builder.addProperty(namer.substitutionName(other), expression); | 1386 builder.addProperty(namer.substitutionName(other), expression); |
1338 } | 1387 } |
1339 } | 1388 } |
1340 | 1389 |
1341 generateIsTestsOn(classElement, generateIsTest, generateSubstitution); | 1390 generateIsTestsOn(classElement, generateIsTest, |
| 1391 generateIsFunctionTypeTest, generateFunctionTypeSignature, |
| 1392 generateSubstitution); |
1342 } | 1393 } |
1343 | 1394 |
1344 void emitRuntimeTypeSupport(CodeBuffer buffer) { | 1395 void emitRuntimeTypeSupport(CodeBuffer buffer) { |
1345 RuntimeTypes rti = backend.rti; | 1396 RuntimeTypes rti = backend.rti; |
1346 TypeChecks typeChecks = rti.requiredChecks; | 1397 TypeChecks typeChecks = rti.requiredChecks; |
1347 | 1398 |
1348 // Add checks to the constructors of instantiated classes. | 1399 // Add checks to the constructors of instantiated classes. |
1349 for (ClassElement cls in typeChecks) { | 1400 for (ClassElement cls in typeChecks) { |
1350 String holder = namer.isolateAccess(backend.getImplementationClass(cls)); | 1401 String holder = namer.isolateAccess(backend.getImplementationClass(cls)); |
1351 for (TypeCheck check in typeChecks[cls]) { | 1402 for (TypeCheck check in typeChecks[cls]) { |
1352 ClassElement cls = check.cls; | 1403 ClassElement cls = check.cls; |
1353 buffer.write('$holder.${namer.operatorIs(cls)}$_=${_}true$N'); | 1404 buffer.write('$holder.${namer.operatorIs(cls)}$_=${_}true$N'); |
1354 Substitution substitution = check.substitution; | 1405 Substitution substitution = check.substitution; |
1355 if (substitution != null) { | 1406 if (substitution != null) { |
1356 String body = substitution.getCode(rti, false); | 1407 String body = substitution.getCode(rti, false); |
1357 buffer.write('$holder.${namer.substitutionName(cls)}$_=${_}$body$N'); | 1408 buffer.write('$holder.${namer.substitutionName(cls)}$_=${_}$body$N'); |
1358 } | 1409 } |
1359 }; | 1410 }; |
1360 } | 1411 } |
| 1412 |
| 1413 void addSignature(FunctionType type) { |
| 1414 String encoding = rti.getTypeEncoding(type); |
| 1415 buffer.add('${namer.signatureName(type)}$_=${_}$encoding$N'); |
| 1416 } |
| 1417 |
| 1418 checkedNonGenericFunctionTypes.forEach(addSignature); |
| 1419 |
| 1420 checkedGenericFunctionTypes.forEach((_, Set<FunctionType> functionTypes) { |
| 1421 functionTypes.forEach(addSignature); |
| 1422 }); |
1361 } | 1423 } |
1362 | 1424 |
1363 /** | 1425 /** |
1364 * Documentation wanted -- johnniwinther | 1426 * Documentation wanted -- johnniwinther |
1365 * | 1427 * |
1366 * Invariant: [classElement] must be a declaration element. | 1428 * Invariant: [classElement] must be a declaration element. |
1367 */ | 1429 */ |
1368 void visitClassFields(ClassElement classElement, | 1430 void visitClassFields(ClassElement classElement, |
1369 void addField(Element member, | 1431 void addField(Element member, |
1370 String name, | 1432 String name, |
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1482 } | 1544 } |
1483 | 1545 |
1484 void generateCheckedSetter(Element member, | 1546 void generateCheckedSetter(Element member, |
1485 String fieldName, | 1547 String fieldName, |
1486 String accessorName, | 1548 String accessorName, |
1487 ClassBuilder builder) { | 1549 ClassBuilder builder) { |
1488 assert(canGenerateCheckedSetter(member)); | 1550 assert(canGenerateCheckedSetter(member)); |
1489 DartType type = member.computeType(compiler); | 1551 DartType type = member.computeType(compiler); |
1490 // TODO(ahe): Generate a dynamic type error here. | 1552 // TODO(ahe): Generate a dynamic type error here. |
1491 if (type.element.isErroneous()) return; | 1553 if (type.element.isErroneous()) return; |
1492 FunctionElement helperElement | 1554 type = type.unalias(compiler); |
1493 = backend.getCheckedModeHelper(type, typeCast: false); | 1555 CheckedModeHelper helper = |
| 1556 backend.getCheckedModeHelper(type, typeCast: false); |
| 1557 FunctionElement helperElement = helper.getElement(compiler); |
1494 String helperName = namer.isolateAccess(helperElement); | 1558 String helperName = namer.isolateAccess(helperElement); |
1495 List<jsAst.Expression> arguments = <jsAst.Expression>[js('v')]; | 1559 List<jsAst.Expression> arguments = <jsAst.Expression>[js('v')]; |
1496 if (helperElement.computeSignature(compiler).parameterCount != 1) { | 1560 if (helperElement.computeSignature(compiler).parameterCount != 1) { |
1497 arguments.add(js.string(namer.operatorIs(type.element))); | 1561 arguments.add(js.string(namer.operatorIsType(type))); |
1498 } | 1562 } |
1499 | 1563 |
1500 String setterName = namer.setterNameFromAccessorName(accessorName); | 1564 String setterName = namer.setterNameFromAccessorName(accessorName); |
1501 String receiver = backend.isInterceptorClass(member.getEnclosingClass()) | 1565 String receiver = backend.isInterceptorClass(member.getEnclosingClass()) |
1502 ? 'receiver' : 'this'; | 1566 ? 'receiver' : 'this'; |
1503 List<String> args = backend.isInterceptedMethod(member) | 1567 List<String> args = backend.isInterceptedMethod(member) |
1504 ? ['receiver', 'v'] | 1568 ? ['receiver', 'v'] |
1505 : ['v']; | 1569 : ['v']; |
1506 builder.addProperty(setterName, | 1570 builder.addProperty(setterName, |
1507 js.fun(args, | 1571 js.fun(args, |
(...skipping 220 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1728 return arity; | 1792 return arity; |
1729 } | 1793 } |
1730 | 1794 |
1731 int _compareSelectorNames(Selector selector1, Selector selector2) { | 1795 int _compareSelectorNames(Selector selector1, Selector selector2) { |
1732 String name1 = selector1.name.toString(); | 1796 String name1 = selector1.name.toString(); |
1733 String name2 = selector2.name.toString(); | 1797 String name2 = selector2.name.toString(); |
1734 if (name1 != name2) return Comparable.compare(name1, name2); | 1798 if (name1 != name2) return Comparable.compare(name1, name2); |
1735 return _selectorRank(selector1) - _selectorRank(selector2); | 1799 return _selectorRank(selector1) - _selectorRank(selector2); |
1736 } | 1800 } |
1737 | 1801 |
1738 Iterable<Element> getTypedefChecksOn(DartType type) { | 1802 /** |
1739 bool isSubtype(TypedefElement typedef) { | 1803 * Returns a mapping containing all checked function types for which [type] |
1740 FunctionType typedefType = | 1804 * can be a subtype. A function type is mapped to [:true:] if [type] is |
1741 typedef.computeType(compiler).unalias(compiler); | 1805 * statically known to be a subtype of it and to [:false:] if [type] might |
1742 return compiler.types.isSubtype(type, typedefType); | 1806 * be a subtype, provided with the right type arguments. |
| 1807 */ |
| 1808 // TODO(johnniwinther): Change to return a mapping from function types to |
| 1809 // a set of variable points and use this to detect statically/dynamically |
| 1810 // known subtype relations. |
| 1811 Map<FunctionType, bool> getFunctionTypeChecksOn(DartType type) { |
| 1812 Map<FunctionType, bool> functionTypeMap = |
| 1813 new LinkedHashMap<FunctionType, bool>(); |
| 1814 for (FunctionType functionType in checkedFunctionTypes) { |
| 1815 if (compiler.types.isSubtype(type, functionType)) { |
| 1816 functionTypeMap[functionType] = true; |
| 1817 } else if (compiler.types.isPotentialSubtype(type, functionType)) { |
| 1818 functionTypeMap[functionType] = false; |
| 1819 } |
1743 } | 1820 } |
1744 return checkedTypedefs.where(isSubtype).toList() | 1821 // TODO(johnniwinther): Ensure stable ordering of the keys. |
1745 ..sort(Elements.compareByPosition); | 1822 return functionTypeMap; |
1746 } | 1823 } |
1747 | 1824 |
1748 /** | 1825 /** |
1749 * Generate "is tests" for [cls]: itself, and the "is tests" for the | 1826 * Generate "is tests" for [cls]: itself, and the "is tests" for the |
1750 * classes it implements and type argument substitution functions for these | 1827 * classes it implements and type argument substitution functions for these |
1751 * tests. We don't need to add the "is tests" of the super class because | 1828 * tests. We don't need to add the "is tests" of the super class because |
1752 * they will be inherited at runtime, but we may need to generate the | 1829 * they will be inherited at runtime, but we may need to generate the |
1753 * substitutions, because they may have changed. | 1830 * substitutions, because they may have changed. |
1754 */ | 1831 */ |
1755 void generateIsTestsOn(ClassElement cls, | 1832 void generateIsTestsOn(ClassElement cls, |
1756 void emitIsTest(Element element), | 1833 void emitIsTest(Element element), |
1757 void emitSubstitution(Element element, {emitNull})) { | 1834 FunctionTypeTestEmitter emitIsFunctionTypeTest, |
| 1835 FunctionTypeSignatureEmitter emitFunctionTypeSignature, |
| 1836 SubstitutionEmitter emitSubstitution) { |
1758 if (checkedClasses.contains(cls)) { | 1837 if (checkedClasses.contains(cls)) { |
1759 emitIsTest(cls); | 1838 emitIsTest(cls); |
1760 emitSubstitution(cls); | 1839 emitSubstitution(cls); |
1761 } | 1840 } |
1762 | 1841 |
1763 RuntimeTypes rti = backend.rti; | 1842 RuntimeTypes rti = backend.rti; |
1764 ClassElement superclass = cls.superclass; | 1843 ClassElement superclass = cls.superclass; |
1765 | 1844 |
1766 bool haveSameTypeVariables(ClassElement a, ClassElement b) { | 1845 bool haveSameTypeVariables(ClassElement a, ClassElement b) { |
1767 if (a.isClosure()) return true; | 1846 if (a.isClosure()) return true; |
1768 return a.typeVariables == b.typeVariables; | 1847 return a.typeVariables == b.typeVariables; |
1769 } | 1848 } |
1770 | 1849 |
1771 if (superclass != null && superclass != compiler.objectClass && | 1850 if (superclass != null && superclass != compiler.objectClass && |
1772 !haveSameTypeVariables(cls, superclass)) { | 1851 !haveSameTypeVariables(cls, superclass)) { |
1773 // We cannot inherit the generated substitutions, because the type | 1852 // We cannot inherit the generated substitutions, because the type |
1774 // variable layout for this class is different. Instead we generate | 1853 // variable layout for this class is different. Instead we generate |
1775 // substitutions for all checks and make emitSubstitution a NOP for the | 1854 // substitutions for all checks and make emitSubstitution a NOP for the |
1776 // rest of this function. | 1855 // rest of this function. |
1777 Set<ClassElement> emitted = new Set<ClassElement>(); | 1856 Set<ClassElement> emitted = new Set<ClassElement>(); |
1778 // TODO(karlklose): move the computation of these checks to | 1857 // TODO(karlklose): move the computation of these checks to |
1779 // RuntimeTypeInformation. | 1858 // RuntimeTypeInformation. |
1780 if (backend.needsRti(cls)) { | 1859 if (backend.classNeedsRti(cls)) { |
1781 emitSubstitution(superclass, emitNull: true); | 1860 emitSubstitution(superclass, emitNull: true); |
1782 emitted.add(superclass); | 1861 emitted.add(superclass); |
1783 } | 1862 } |
1784 for (DartType supertype in cls.allSupertypes) { | 1863 for (DartType supertype in cls.allSupertypes) { |
1785 ClassElement superclass = supertype.element; | 1864 ClassElement superclass = supertype.element; |
1786 if (classesUsingTypeVariableTests.contains(superclass)) { | 1865 if (classesUsingTypeVariableTests.contains(superclass)) { |
1787 emitSubstitution(superclass, emitNull: true); | 1866 emitSubstitution(superclass, emitNull: true); |
1788 emitted.add(superclass); | 1867 emitted.add(superclass); |
1789 } | 1868 } |
1790 for (ClassElement check in checkedClasses) { | 1869 for (ClassElement check in checkedClasses) { |
1791 if (supertype.element == check && !emitted.contains(check)) { | 1870 if (supertype.element == check && !emitted.contains(check)) { |
1792 // Generate substitution. If no substitution is necessary, emit | 1871 // Generate substitution. If no substitution is necessary, emit |
1793 // [:null:] to overwrite a (possibly) existing substitution from the | 1872 // [:null:] to overwrite a (possibly) existing substitution from the |
1794 // super classes. | 1873 // super classes. |
1795 emitSubstitution(check, emitNull: true); | 1874 emitSubstitution(check, emitNull: true); |
1796 emitted.add(check); | 1875 emitted.add(check); |
1797 } | 1876 } |
1798 } | 1877 } |
1799 } | 1878 } |
1800 void emitNothing(_, {emitNull}) {}; | 1879 void emitNothing(_, {emitNull}) {}; |
1801 emitSubstitution = emitNothing; | 1880 emitSubstitution = emitNothing; |
1802 } | 1881 } |
1803 | 1882 |
1804 Set<Element> generated = new Set<Element>(); | 1883 Set<Element> generated = new Set<Element>(); |
1805 // A class that defines a [:call:] method implicitly implements | 1884 // A class that defines a [:call:] method implicitly implements |
1806 // [Function] and needs checks for all typedefs that are used in is-checks. | 1885 // [Function] and needs checks for all typedefs that are used in is-checks. |
1807 if (checkedClasses.contains(compiler.functionClass) || | 1886 if (checkedClasses.contains(compiler.functionClass) || |
1808 !checkedTypedefs.isEmpty) { | 1887 !checkedFunctionTypes.isEmpty) { |
1809 Element call = cls.lookupLocalMember(Compiler.CALL_OPERATOR_NAME); | 1888 Element call = cls.lookupLocalMember(Compiler.CALL_OPERATOR_NAME); |
1810 if (call == null) { | 1889 if (call == null) { |
1811 // If [cls] is a closure, it has a synthetic call operator method. | 1890 // If [cls] is a closure, it has a synthetic call operator method. |
1812 call = cls.lookupBackendMember(Compiler.CALL_OPERATOR_NAME); | 1891 call = cls.lookupBackendMember(Compiler.CALL_OPERATOR_NAME); |
1813 } | 1892 } |
1814 if (call != null && call.isFunction()) { | 1893 if (call != null && call.isFunction()) { |
1815 generateInterfacesIsTests(compiler.functionClass, | 1894 generateInterfacesIsTests(compiler.functionClass, |
1816 emitIsTest, | 1895 emitIsTest, |
1817 emitSubstitution, | 1896 emitSubstitution, |
1818 generated); | 1897 generated); |
1819 getTypedefChecksOn(call.computeType(compiler)).forEach(emitIsTest); | 1898 FunctionType callType = call.computeType(compiler); |
1820 } | 1899 Map<FunctionType, bool> functionTypeChecks = |
| 1900 getFunctionTypeChecksOn(callType); |
| 1901 generateFunctionTypeTests(call, callType, functionTypeChecks, |
| 1902 emitFunctionTypeSignature, emitIsFunctionTypeTest); |
| 1903 } |
1821 } | 1904 } |
1822 | 1905 |
1823 for (DartType interfaceType in cls.interfaces) { | 1906 for (DartType interfaceType in cls.interfaces) { |
1824 generateInterfacesIsTests(interfaceType.element, emitIsTest, | 1907 generateInterfacesIsTests(interfaceType.element, emitIsTest, |
1825 emitSubstitution, generated); | 1908 emitSubstitution, generated); |
1826 } | 1909 } |
1827 } | 1910 } |
1828 | 1911 |
1829 /** | 1912 /** |
1830 * Generate "is tests" where [cls] is being implemented. | 1913 * Generate "is tests" where [cls] is being implemented. |
1831 */ | 1914 */ |
1832 void generateInterfacesIsTests(ClassElement cls, | 1915 void generateInterfacesIsTests(ClassElement cls, |
1833 void emitIsTest(ClassElement element), | 1916 void emitIsTest(ClassElement element), |
1834 void emitSubstitution(ClassElement element), | 1917 SubstitutionEmitter emitSubstitution, |
1835 Set<Element> alreadyGenerated) { | 1918 Set<Element> alreadyGenerated) { |
1836 void tryEmitTest(ClassElement check) { | 1919 void tryEmitTest(ClassElement check) { |
1837 if (!alreadyGenerated.contains(check) && checkedClasses.contains(check)) { | 1920 if (!alreadyGenerated.contains(check) && checkedClasses.contains(check)) { |
1838 alreadyGenerated.add(check); | 1921 alreadyGenerated.add(check); |
1839 emitIsTest(check); | 1922 emitIsTest(check); |
1840 emitSubstitution(check); | 1923 emitSubstitution(check); |
1841 } | 1924 } |
1842 }; | 1925 }; |
1843 | 1926 |
1844 tryEmitTest(cls); | 1927 tryEmitTest(cls); |
1845 | 1928 |
1846 for (DartType interfaceType in cls.interfaces) { | 1929 for (DartType interfaceType in cls.interfaces) { |
1847 Element element = interfaceType.element; | 1930 Element element = interfaceType.element; |
1848 tryEmitTest(element); | 1931 tryEmitTest(element); |
1849 generateInterfacesIsTests(element, emitIsTest, emitSubstitution, | 1932 generateInterfacesIsTests(element, emitIsTest, emitSubstitution, |
1850 alreadyGenerated); | 1933 alreadyGenerated); |
1851 } | 1934 } |
1852 | 1935 |
1853 // We need to also emit "is checks" for the superclass and its supertypes. | 1936 // We need to also emit "is checks" for the superclass and its supertypes. |
1854 ClassElement superclass = cls.superclass; | 1937 ClassElement superclass = cls.superclass; |
1855 if (superclass != null) { | 1938 if (superclass != null) { |
1856 tryEmitTest(superclass); | 1939 tryEmitTest(superclass); |
1857 generateInterfacesIsTests(superclass, emitIsTest, emitSubstitution, | 1940 generateInterfacesIsTests(superclass, emitIsTest, emitSubstitution, |
1858 alreadyGenerated); | 1941 alreadyGenerated); |
1859 } | 1942 } |
1860 } | 1943 } |
1861 | 1944 |
| 1945 static const int MAX_FUNCTION_TYPE_PREDICATES = 10; |
| 1946 |
| 1947 /** |
| 1948 * Generates function type checks on [method] with type [methodType] against |
| 1949 * the function type checks in [functionTypeChecks]. |
| 1950 */ |
| 1951 void generateFunctionTypeTests( |
| 1952 Element method, |
| 1953 FunctionType methodType, |
| 1954 Map<FunctionType, bool> functionTypeChecks, |
| 1955 FunctionTypeSignatureEmitter emitFunctionTypeSignature, |
| 1956 FunctionTypeTestEmitter emitIsFunctionTypeTest) { |
| 1957 bool hasDynamicFunctionTypeCheck = false; |
| 1958 int neededPredicates = 0; |
| 1959 functionTypeChecks.forEach((FunctionType functionType, bool knownSubtype) { |
| 1960 if (!knownSubtype) { |
| 1961 registerDynamicFunctionTypeCheck(functionType); |
| 1962 hasDynamicFunctionTypeCheck = true; |
| 1963 } else { |
| 1964 neededPredicates++; |
| 1965 } |
| 1966 }); |
| 1967 bool alwaysUseSignature = false; |
| 1968 if (hasDynamicFunctionTypeCheck || |
| 1969 neededPredicates > MAX_FUNCTION_TYPE_PREDICATES) { |
| 1970 emitFunctionTypeSignature(method, methodType); |
| 1971 alwaysUseSignature = true; |
| 1972 } |
| 1973 functionTypeChecks.forEach((FunctionType functionType, bool knownSubtype) { |
| 1974 if (knownSubtype) { |
| 1975 if (alwaysUseSignature) { |
| 1976 registerDynamicFunctionTypeCheck(functionType); |
| 1977 } else { |
| 1978 emitIsFunctionTypeTest(functionType); |
| 1979 } |
| 1980 } |
| 1981 }); |
| 1982 } |
| 1983 |
1862 /** | 1984 /** |
1863 * Return a function that returns true if its argument is a class | 1985 * Return a function that returns true if its argument is a class |
1864 * that needs to be emitted. | 1986 * that needs to be emitted. |
1865 */ | 1987 */ |
1866 Function computeClassFilter() { | 1988 Function computeClassFilter() { |
1867 Set<ClassElement> unneededClasses = new Set<ClassElement>(); | 1989 Set<ClassElement> unneededClasses = new Set<ClassElement>(); |
1868 // The [Bool] class is not marked as abstract, but has a factory | 1990 // The [Bool] class is not marked as abstract, but has a factory |
1869 // constructor that always throws. We never need to emit it. | 1991 // constructor that always throws. We never need to emit it. |
1870 unneededClasses.add(compiler.boolClass); | 1992 unneededClasses.add(compiler.boolClass); |
1871 | 1993 |
(...skipping 142 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2014 ClassBuilder closureBuilder = new ClassBuilder(); | 2136 ClassBuilder closureBuilder = new ClassBuilder(); |
2015 // If a static function is used as a closure we need to add its name | 2137 // If a static function is used as a closure we need to add its name |
2016 // in case it is used in spawnFunction. | 2138 // in case it is used in spawnFunction. |
2017 String methodName = namer.STATIC_CLOSURE_NAME_NAME; | 2139 String methodName = namer.STATIC_CLOSURE_NAME_NAME; |
2018 emitClosureClassHeader( | 2140 emitClosureClassHeader( |
2019 mangledName, superName, <String>[invocationName, methodName], | 2141 mangledName, superName, <String>[invocationName, methodName], |
2020 closureBuilder); | 2142 closureBuilder); |
2021 | 2143 |
2022 addParameterStubs(callElement, closureBuilder.addProperty); | 2144 addParameterStubs(callElement, closureBuilder.addProperty); |
2023 | 2145 |
2024 DartType type = element.computeType(compiler); | |
2025 getTypedefChecksOn(type).forEach((Element typedef) { | |
2026 String operator = namer.operatorIs(typedef); | |
2027 closureBuilder.addProperty(operator, js('true')); | |
2028 }); | |
2029 | |
2030 // TODO(ngeoffray): Cache common base classes for closures, bound | 2146 // TODO(ngeoffray): Cache common base classes for closures, bound |
2031 // closures, and static closures that have common type checks. | 2147 // closures, and static closures that have common type checks. |
2032 boundClosures.add( | 2148 boundClosures.add( |
2033 js('$classesCollector.$mangledName = #', | 2149 js('$classesCollector.$mangledName = #', |
2034 closureBuilder.toObjectInitializer())); | 2150 closureBuilder.toObjectInitializer())); |
2035 | 2151 |
2036 staticGetters[element] = closureClassElement; | 2152 staticGetters[element] = closureClassElement; |
| 2153 |
| 2154 void emitFunctionTypeSignature(Element method, FunctionType methodType) { |
| 2155 RuntimeTypes rti = backend.rti; |
| 2156 // [:() => null:] is dummy encoding of [this] which is never needed for |
| 2157 // the encoding of the type of the static [method]. |
| 2158 String encoding = rti.getSignatureEncoding(methodType, () => 'null'); |
| 2159 String operatorSignature = namer.operatorSignature(); |
| 2160 // TODO(johnniwinther): Make MiniJsParser support function expressions. |
| 2161 closureBuilder.addProperty(operatorSignature, |
| 2162 new jsAst.LiteralExpression(encoding)); |
| 2163 } |
| 2164 |
| 2165 void emitIsFunctionTypeTest(FunctionType functionType) { |
| 2166 String operator = namer.operatorIsType(functionType); |
| 2167 closureBuilder.addProperty(operator, js('true')); |
| 2168 } |
| 2169 |
| 2170 FunctionType methodType = element.computeType(compiler); |
| 2171 Map<FunctionType, bool> functionTypeChecks = |
| 2172 getFunctionTypeChecksOn(methodType); |
| 2173 generateFunctionTypeTests(element, methodType, functionTypeChecks, |
| 2174 emitFunctionTypeSignature, emitIsFunctionTypeTest); |
2037 } | 2175 } |
2038 } | 2176 } |
2039 | 2177 |
2040 void emitClosureClassHeader(String mangledName, | 2178 void emitClosureClassHeader(String mangledName, |
2041 String superName, | 2179 String superName, |
2042 List<String> fieldNames, | 2180 List<String> fieldNames, |
2043 ClassBuilder builder) { | 2181 ClassBuilder builder) { |
2044 builder.addProperty('', | 2182 builder.addProperty('', |
2045 js.string("$superName;${fieldNames.join(',')}")); | 2183 js.string("$superName;${fieldNames.join(',')}")); |
2046 } | 2184 } |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2084 if (inInterceptor) { | 2222 if (inInterceptor) { |
2085 cache = interceptorClosureCache; | 2223 cache = interceptorClosureCache; |
2086 } else { | 2224 } else { |
2087 cache = boundClosureCache; | 2225 cache = boundClosureCache; |
2088 } | 2226 } |
2089 List<String> fieldNames = <String>[]; | 2227 List<String> fieldNames = <String>[]; |
2090 compiler.boundClosureClass.forEachInstanceField((_, Element field) { | 2228 compiler.boundClosureClass.forEachInstanceField((_, Element field) { |
2091 fieldNames.add(namer.getName(field)); | 2229 fieldNames.add(namer.getName(field)); |
2092 }); | 2230 }); |
2093 | 2231 |
2094 Iterable<Element> typedefChecks = | 2232 DartType memberType = member.computeType(compiler); |
2095 getTypedefChecksOn(member.computeType(compiler)); | 2233 Map<FunctionType, bool> functionTypeChecks = |
2096 bool hasTypedefChecks = !typedefChecks.isEmpty; | 2234 getFunctionTypeChecksOn(memberType); |
| 2235 bool hasFunctionTypeChecks = !functionTypeChecks.isEmpty; |
2097 | 2236 |
2098 bool canBeShared = !hasOptionalParameters && !hasTypedefChecks; | 2237 bool canBeShared = !hasOptionalParameters && !hasFunctionTypeChecks; |
2099 | 2238 |
| 2239 ClassElement classElement = member.getEnclosingClass(); |
2100 String closureClass = canBeShared ? cache[parameterCount] : null; | 2240 String closureClass = canBeShared ? cache[parameterCount] : null; |
2101 if (closureClass == null) { | 2241 if (closureClass == null) { |
2102 // Either the class was not cached yet, or there are optional parameters. | 2242 // Either the class was not cached yet, or there are optional parameters. |
2103 // Create a new closure class. | 2243 // Create a new closure class. |
2104 String name; | 2244 String name; |
2105 if (canBeShared) { | 2245 if (canBeShared) { |
2106 if (inInterceptor) { | 2246 if (inInterceptor) { |
2107 name = 'BoundClosure\$i${parameterCount}'; | 2247 name = 'BoundClosure\$i${parameterCount}'; |
2108 } else { | 2248 } else { |
2109 name = 'BoundClosure\$${parameterCount}'; | 2249 name = 'BoundClosure\$${parameterCount}'; |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2145 arguments.add(js(name)); | 2285 arguments.add(js(name)); |
2146 } | 2286 } |
2147 | 2287 |
2148 jsAst.Expression fun = js.fun( | 2288 jsAst.Expression fun = js.fun( |
2149 parameters, | 2289 parameters, |
2150 js.return_( | 2290 js.return_( |
2151 js('this')[fieldNames[0]][js('this')[fieldNames[1]]](arguments))); | 2291 js('this')[fieldNames[0]][js('this')[fieldNames[1]]](arguments))); |
2152 boundClosureBuilder.addProperty(invocationName, fun); | 2292 boundClosureBuilder.addProperty(invocationName, fun); |
2153 | 2293 |
2154 addParameterStubs(callElement, boundClosureBuilder.addProperty); | 2294 addParameterStubs(callElement, boundClosureBuilder.addProperty); |
2155 typedefChecks.forEach((Element typedef) { | 2295 |
2156 String operator = namer.operatorIs(typedef); | 2296 void emitFunctionTypeSignature(Element method, FunctionType methodType) { |
2157 boundClosureBuilder.addProperty(operator, js('true')); | 2297 String encoding = backend.rti.getSignatureEncoding( |
2158 }); | 2298 methodType, () => 'this.${fieldNames[0]}'); |
| 2299 String operatorSignature = namer.operatorSignature(); |
| 2300 boundClosureBuilder.addProperty(operatorSignature, |
| 2301 new jsAst.LiteralExpression(encoding)); |
| 2302 } |
| 2303 |
| 2304 void emitIsFunctionTypeTest(FunctionType functionType) { |
| 2305 String operator = namer.operatorIsType(functionType); |
| 2306 boundClosureBuilder.addProperty(operator, |
| 2307 new jsAst.LiteralBool(true)); |
| 2308 } |
| 2309 |
| 2310 generateFunctionTypeTests(member, memberType, functionTypeChecks, |
| 2311 emitFunctionTypeSignature, emitIsFunctionTypeTest); |
2159 | 2312 |
2160 boundClosures.add( | 2313 boundClosures.add( |
2161 js('$classesCollector.$mangledName = #', | 2314 js('$classesCollector.$mangledName = #', |
2162 boundClosureBuilder.toObjectInitializer())); | 2315 boundClosureBuilder.toObjectInitializer())); |
2163 | 2316 |
2164 closureClass = namer.isolateAccess(closureClassElement); | 2317 closureClass = namer.isolateAccess(closureClassElement); |
2165 | 2318 |
2166 // Cache it. | 2319 // Cache it. |
2167 if (canBeShared) { | 2320 if (canBeShared) { |
2168 cache[parameterCount] = closureClass; | 2321 cache[parameterCount] = closureClass; |
(...skipping 11 matching lines...) Expand all Loading... |
2180 if (inInterceptor) { | 2333 if (inInterceptor) { |
2181 String receiverArg = fieldNames[2]; | 2334 String receiverArg = fieldNames[2]; |
2182 parameters.add(receiverArg); | 2335 parameters.add(receiverArg); |
2183 arguments.add(js(receiverArg)); | 2336 arguments.add(js(receiverArg)); |
2184 } else { | 2337 } else { |
2185 // Put null in the intercepted receiver field. | 2338 // Put null in the intercepted receiver field. |
2186 arguments.add(new jsAst.LiteralNull()); | 2339 arguments.add(new jsAst.LiteralNull()); |
2187 } | 2340 } |
2188 | 2341 |
2189 jsAst.Expression getterFunction = js.fun( | 2342 jsAst.Expression getterFunction = js.fun( |
2190 parameters, | 2343 parameters, js.return_(js(closureClass).newWith(arguments))); |
2191 js.return_(js(closureClass).newWith(arguments))); | |
2192 | 2344 |
2193 defineStub(getterName, getterFunction); | 2345 defineStub(getterName, getterFunction); |
2194 } | 2346 } |
2195 | 2347 |
2196 /** | 2348 /** |
2197 * Documentation wanted -- johnniwinther | 2349 * Documentation wanted -- johnniwinther |
2198 * | 2350 * |
2199 * Invariant: [member] must be a declaration element. | 2351 * Invariant: [member] must be a declaration element. |
2200 */ | 2352 */ |
2201 void emitCallStubForGetter(Element member, | 2353 void emitCallStubForGetter(Element member, |
(...skipping 19 matching lines...) Expand all Loading... |
2221 ? member.fixedBackendName() | 2373 ? member.fixedBackendName() |
2222 : namer.instanceFieldName(member); | 2374 : namer.instanceFieldName(member); |
2223 return js('this')[fieldName]; | 2375 return js('this')[fieldName]; |
2224 } | 2376 } |
2225 } | 2377 } |
2226 | 2378 |
2227 // Two selectors may match but differ only in type. To avoid generating | 2379 // Two selectors may match but differ only in type. To avoid generating |
2228 // identical stubs for each we track untyped selectors which already have | 2380 // identical stubs for each we track untyped selectors which already have |
2229 // stubs. | 2381 // stubs. |
2230 Set<Selector> generatedSelectors = new Set<Selector>(); | 2382 Set<Selector> generatedSelectors = new Set<Selector>(); |
2231 | |
2232 for (Selector selector in selectors) { | 2383 for (Selector selector in selectors) { |
2233 if (selector.applies(member, compiler)) { | 2384 if (selector.applies(member, compiler)) { |
2234 selector = selector.asUntyped; | 2385 selector = selector.asUntyped; |
2235 if (generatedSelectors.contains(selector)) continue; | 2386 if (generatedSelectors.contains(selector)) continue; |
2236 generatedSelectors.add(selector); | 2387 generatedSelectors.add(selector); |
2237 | 2388 |
2238 String invocationName = namer.invocationName(selector); | 2389 String invocationName = namer.invocationName(selector); |
2239 Selector callSelector = new Selector.callClosureFrom(selector); | 2390 Selector callSelector = new Selector.callClosureFrom(selector); |
2240 String closureCallName = namer.invocationName(callSelector); | 2391 String closureCallName = namer.invocationName(callSelector); |
2241 | 2392 |
(...skipping 584 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2826 }); | 2977 }); |
2827 | 2978 |
2828 // 3b. Add classes that are referenced by substitutions in object checks and | 2979 // 3b. Add classes that are referenced by substitutions in object checks and |
2829 // their superclasses. | 2980 // their superclasses. |
2830 TypeChecks requiredChecks = | 2981 TypeChecks requiredChecks = |
2831 backend.rti.computeChecks(neededClasses, checkedClasses); | 2982 backend.rti.computeChecks(neededClasses, checkedClasses); |
2832 Set<ClassElement> classesUsedInSubstitutions = | 2983 Set<ClassElement> classesUsedInSubstitutions = |
2833 rti.getClassesUsedInSubstitutions(backend, requiredChecks); | 2984 rti.getClassesUsedInSubstitutions(backend, requiredChecks); |
2834 addClassesWithSuperclasses(classesUsedInSubstitutions); | 2985 addClassesWithSuperclasses(classesUsedInSubstitutions); |
2835 | 2986 |
| 2987 // 3c. Add classes that contain checked generic function types. These are |
| 2988 // needed to store the signature encoding. |
| 2989 for (FunctionType type in checkedFunctionTypes) { |
| 2990 ClassElement contextClass = Types.getClassContext(type); |
| 2991 if (contextClass != null) { |
| 2992 neededClasses.add(contextClass); |
| 2993 } |
| 2994 } |
| 2995 |
2836 // 4. Finally, sort the classes. | 2996 // 4. Finally, sort the classes. |
2837 List<ClassElement> sortedClasses = Elements.sortedByPosition(neededClasses); | 2997 List<ClassElement> sortedClasses = Elements.sortedByPosition(neededClasses); |
2838 | 2998 |
2839 // If we need noSuchMethod support, we run through all needed | 2999 // If we need noSuchMethod support, we run through all needed |
2840 // classes to figure out if we need the support on any native | 3000 // classes to figure out if we need the support on any native |
2841 // class. If so, we let the native emitter deal with it. | 3001 // class. If so, we let the native emitter deal with it. |
2842 if (compiler.enabledNoSuchMethod) { | 3002 if (compiler.enabledNoSuchMethod) { |
2843 SourceString noSuchMethodName = Compiler.NO_SUCH_METHOD; | 3003 SourceString noSuchMethodName = Compiler.NO_SUCH_METHOD; |
2844 Selector noSuchMethodSelector = compiler.noSuchMethodSelector; | 3004 Selector noSuchMethodSelector = compiler.noSuchMethodSelector; |
2845 for (ClassElement element in sortedClasses) { | 3005 for (ClassElement element in sortedClasses) { |
(...skipping 634 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3480 | 3640 |
3481 const String HOOKS_API_USAGE = """ | 3641 const String HOOKS_API_USAGE = """ |
3482 // The code supports the following hooks: | 3642 // The code supports the following hooks: |
3483 // dartPrint(message) - if this function is defined it is called | 3643 // dartPrint(message) - if this function is defined it is called |
3484 // instead of the Dart [print] method. | 3644 // instead of the Dart [print] method. |
3485 // dartMainRunner(main) - if this function is defined, the Dart [main] | 3645 // dartMainRunner(main) - if this function is defined, the Dart [main] |
3486 // method will not be invoked directly. | 3646 // method will not be invoked directly. |
3487 // Instead, a closure that will invoke [main] is | 3647 // Instead, a closure that will invoke [main] is |
3488 // passed to [dartMainRunner]. | 3648 // passed to [dartMainRunner]. |
3489 """; | 3649 """; |
OLD | NEW |