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 1115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1287 | 1311 |
1288 void generateIsTest(Element other) { | 1312 void generateIsTest(Element other) { |
1289 if (other == compiler.objectClass && other != classElement) { | 1313 if (other == compiler.objectClass && other != classElement) { |
1290 // Avoid emitting [:$isObject:] on all classes but [Object]. | 1314 // Avoid emitting [:$isObject:] on all classes but [Object]. |
1291 return; | 1315 return; |
1292 } | 1316 } |
1293 other = backend.getImplementationClass(other); | 1317 other = backend.getImplementationClass(other); |
1294 builder.addProperty(namer.operatorIs(other), js('true')); | 1318 builder.addProperty(namer.operatorIs(other), js('true')); |
1295 } | 1319 } |
1296 | 1320 |
| 1321 void generateIsFunctionTypeTest(FunctionType type) { |
| 1322 String operator = namer.operatorIsType(type); |
| 1323 builder.addProperty(operator, new jsAst.LiteralBool(true)); |
| 1324 } |
| 1325 |
| 1326 void generateFunctionTypeSignature(Element method, FunctionType type) { |
| 1327 assert(method.isImplementation); |
| 1328 String thisAccess = 'this'; |
| 1329 Node node = method.parseNode(compiler); |
| 1330 ClosureClassMap closureData = |
| 1331 compiler.closureToClassMapper.closureMappingCache[node]; |
| 1332 if (closureData != null) { |
| 1333 Element thisElement = |
| 1334 closureData.freeVariableMapping[closureData.thisElement]; |
| 1335 if (thisElement != null) { |
| 1336 String thisName = backend.namer.getName(thisElement); |
| 1337 thisAccess = 'this.$thisName'; |
| 1338 } |
| 1339 } |
| 1340 RuntimeTypes rti = backend.rti; |
| 1341 String encoding = rti.getSignatureEncoding(type, () => '$thisAccess'); |
| 1342 String operatorSignature = namer.operatorSignature(); |
| 1343 builder.addProperty(operatorSignature, |
| 1344 new jsAst.LiteralExpression(encoding)); |
| 1345 } |
| 1346 |
1297 void generateSubstitution(Element other, {bool emitNull: false}) { | 1347 void generateSubstitution(Element other, {bool emitNull: false}) { |
1298 RuntimeTypes rti = backend.rti; | 1348 RuntimeTypes rti = backend.rti; |
1299 // TODO(karlklose): support typedefs with variables. | |
1300 jsAst.Expression expression; | 1349 jsAst.Expression expression; |
1301 bool needsNativeCheck = nativeEmitter.requiresNativeIsCheck(other); | 1350 bool needsNativeCheck = nativeEmitter.requiresNativeIsCheck(other); |
1302 if (other.kind == ElementKind.CLASS) { | 1351 if (other.kind == ElementKind.CLASS) { |
1303 String substitution = rti.getSupertypeSubstitution(classElement, other, | 1352 String substitution = rti.getSupertypeSubstitution(classElement, other, |
1304 alwaysGenerateFunction: true); | 1353 alwaysGenerateFunction: true); |
1305 if (substitution != null) { | 1354 if (substitution != null) { |
1306 expression = new jsAst.LiteralExpression(substitution); | 1355 expression = new jsAst.LiteralExpression(substitution); |
1307 } else if (emitNull || needsNativeCheck) { | 1356 } else if (emitNull || needsNativeCheck) { |
1308 expression = new jsAst.LiteralNull(); | 1357 expression = new jsAst.LiteralNull(); |
1309 } | 1358 } |
1310 } | 1359 } |
1311 if (expression != null) { | 1360 if (expression != null) { |
1312 builder.addProperty(namer.substitutionName(other), expression); | 1361 builder.addProperty(namer.substitutionName(other), expression); |
1313 } | 1362 } |
1314 } | 1363 } |
1315 | 1364 |
1316 generateIsTestsOn(classElement, generateIsTest, generateSubstitution); | 1365 generateIsTestsOn(classElement, generateIsTest, |
| 1366 generateIsFunctionTypeTest, generateFunctionTypeSignature, |
| 1367 generateSubstitution); |
1317 } | 1368 } |
1318 | 1369 |
1319 void emitRuntimeTypeSupport(CodeBuffer buffer) { | 1370 void emitRuntimeTypeSupport(CodeBuffer buffer) { |
1320 RuntimeTypes rti = backend.rti; | 1371 RuntimeTypes rti = backend.rti; |
1321 TypeChecks typeChecks = rti.requiredChecks; | 1372 TypeChecks typeChecks = rti.requiredChecks; |
1322 | 1373 |
1323 // Add checks to the constructors of instantiated classes. | 1374 // Add checks to the constructors of instantiated classes. |
1324 for (ClassElement cls in typeChecks) { | 1375 for (ClassElement cls in typeChecks) { |
1325 String holder = namer.isolateAccess(backend.getImplementationClass(cls)); | 1376 String holder = namer.isolateAccess(backend.getImplementationClass(cls)); |
1326 for (TypeCheck check in typeChecks[cls]) { | 1377 for (TypeCheck check in typeChecks[cls]) { |
1327 ClassElement cls = check.cls; | 1378 ClassElement cls = check.cls; |
1328 buffer.write('$holder.${namer.operatorIs(cls)}$_=${_}true$N'); | 1379 buffer.write('$holder.${namer.operatorIs(cls)}$_=${_}true$N'); |
1329 Substitution substitution = check.substitution; | 1380 Substitution substitution = check.substitution; |
1330 if (substitution != null) { | 1381 if (substitution != null) { |
1331 String body = substitution.getCode(rti, false); | 1382 String body = substitution.getCode(rti, false); |
1332 buffer.write('$holder.${namer.substitutionName(cls)}$_=${_}$body$N'); | 1383 buffer.write('$holder.${namer.substitutionName(cls)}$_=${_}$body$N'); |
1333 } | 1384 } |
1334 }; | 1385 }; |
1335 } | 1386 } |
| 1387 |
| 1388 void addSignature(FunctionType type) { |
| 1389 String encoding = rti.getTypeEncoding(type); |
| 1390 buffer.add('${namer.signatureName(type)}$_=${_}$encoding$N'); |
| 1391 } |
| 1392 |
| 1393 checkedNonGenericFunctionTypes.forEach(addSignature); |
| 1394 |
| 1395 checkedGenericFunctionTypes.forEach((_, Set<FunctionType> functionTypes) { |
| 1396 functionTypes.forEach(addSignature); |
| 1397 }); |
1336 } | 1398 } |
1337 | 1399 |
1338 /** | 1400 /** |
1339 * Documentation wanted -- johnniwinther | 1401 * Documentation wanted -- johnniwinther |
1340 * | 1402 * |
1341 * Invariant: [classElement] must be a declaration element. | 1403 * Invariant: [classElement] must be a declaration element. |
1342 */ | 1404 */ |
1343 void visitClassFields(ClassElement classElement, | 1405 void visitClassFields(ClassElement classElement, |
1344 void addField(Element member, | 1406 void addField(Element member, |
1345 String name, | 1407 String name, |
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1457 } | 1519 } |
1458 | 1520 |
1459 void generateCheckedSetter(Element member, | 1521 void generateCheckedSetter(Element member, |
1460 String fieldName, | 1522 String fieldName, |
1461 String accessorName, | 1523 String accessorName, |
1462 ClassBuilder builder) { | 1524 ClassBuilder builder) { |
1463 assert(canGenerateCheckedSetter(member)); | 1525 assert(canGenerateCheckedSetter(member)); |
1464 DartType type = member.computeType(compiler); | 1526 DartType type = member.computeType(compiler); |
1465 // TODO(ahe): Generate a dynamic type error here. | 1527 // TODO(ahe): Generate a dynamic type error here. |
1466 if (type.element.isErroneous()) return; | 1528 if (type.element.isErroneous()) return; |
1467 FunctionElement helperElement | 1529 type = type.unalias(compiler); |
1468 = backend.getCheckedModeHelper(type, typeCast: false); | 1530 CheckedModeHelper helper = |
| 1531 backend.getCheckedModeHelper(type, typeCast: false); |
| 1532 FunctionElement helperElement = helper.getElement(compiler); |
1469 String helperName = namer.isolateAccess(helperElement); | 1533 String helperName = namer.isolateAccess(helperElement); |
1470 List<jsAst.Expression> arguments = <jsAst.Expression>[js('v')]; | 1534 List<jsAst.Expression> arguments = <jsAst.Expression>[js('v')]; |
1471 if (helperElement.computeSignature(compiler).parameterCount != 1) { | 1535 if (helperElement.computeSignature(compiler).parameterCount != 1) { |
1472 arguments.add(js.string(namer.operatorIs(type.element))); | 1536 arguments.add(js.string(namer.operatorIsType(type))); |
1473 } | 1537 } |
1474 | 1538 |
1475 String setterName = namer.setterNameFromAccessorName(accessorName); | 1539 String setterName = namer.setterNameFromAccessorName(accessorName); |
1476 String receiver = backend.isInterceptorClass(member.getEnclosingClass()) | 1540 String receiver = backend.isInterceptorClass(member.getEnclosingClass()) |
1477 ? 'receiver' : 'this'; | 1541 ? 'receiver' : 'this'; |
1478 List<String> args = backend.isInterceptedMethod(member) | 1542 List<String> args = backend.isInterceptedMethod(member) |
1479 ? ['receiver', 'v'] | 1543 ? ['receiver', 'v'] |
1480 : ['v']; | 1544 : ['v']; |
1481 builder.addProperty(setterName, | 1545 builder.addProperty(setterName, |
1482 js.fun(args, | 1546 js.fun(args, |
(...skipping 220 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1703 return arity; | 1767 return arity; |
1704 } | 1768 } |
1705 | 1769 |
1706 int _compareSelectorNames(Selector selector1, Selector selector2) { | 1770 int _compareSelectorNames(Selector selector1, Selector selector2) { |
1707 String name1 = selector1.name.toString(); | 1771 String name1 = selector1.name.toString(); |
1708 String name2 = selector2.name.toString(); | 1772 String name2 = selector2.name.toString(); |
1709 if (name1 != name2) return Comparable.compare(name1, name2); | 1773 if (name1 != name2) return Comparable.compare(name1, name2); |
1710 return _selectorRank(selector1) - _selectorRank(selector2); | 1774 return _selectorRank(selector1) - _selectorRank(selector2); |
1711 } | 1775 } |
1712 | 1776 |
1713 Iterable<Element> getTypedefChecksOn(DartType type) { | 1777 /** |
1714 bool isSubtype(TypedefElement typedef) { | 1778 * Returns a mapping containing all checked function types for which [type] |
1715 FunctionType typedefType = | 1779 * can be a subtype. A function type is mapped to [:true:] if [type] is |
1716 typedef.computeType(compiler).unalias(compiler); | 1780 * statically known to be a subtype of it and to [:false:] if [type] might |
1717 return compiler.types.isSubtype(type, typedefType); | 1781 * be a subtype, provided with the right type arguments. |
| 1782 */ |
| 1783 // TODO(johnniwinther): Change to return a mapping from function types to |
| 1784 // a set of variable points and use this to detect statically/dynamically |
| 1785 // known subtype relations. |
| 1786 Map<FunctionType, bool> getFunctionTypeChecksOn(DartType type) { |
| 1787 Map<FunctionType, bool> functionTypeMap = |
| 1788 new LinkedHashMap<FunctionType, bool>(); |
| 1789 for (FunctionType functionType in checkedFunctionTypes) { |
| 1790 if (compiler.types.isSubtype(type, functionType)) { |
| 1791 functionTypeMap[functionType] = true; |
| 1792 } else if (compiler.types.isPotentialSubtype(type, functionType)) { |
| 1793 functionTypeMap[functionType] = false; |
| 1794 } |
1718 } | 1795 } |
1719 return checkedTypedefs.where(isSubtype).toList() | 1796 // TODO(johnniwinther): Ensure stable ordering of the keys. |
1720 ..sort(Elements.compareByPosition); | 1797 return functionTypeMap; |
1721 } | 1798 } |
1722 | 1799 |
1723 /** | 1800 /** |
1724 * Generate "is tests" for [cls]: itself, and the "is tests" for the | 1801 * Generate "is tests" for [cls]: itself, and the "is tests" for the |
1725 * classes it implements and type argument substitution functions for these | 1802 * classes it implements and type argument substitution functions for these |
1726 * tests. We don't need to add the "is tests" of the super class because | 1803 * tests. We don't need to add the "is tests" of the super class because |
1727 * they will be inherited at runtime, but we may need to generate the | 1804 * they will be inherited at runtime, but we may need to generate the |
1728 * substitutions, because they may have changed. | 1805 * substitutions, because they may have changed. |
1729 */ | 1806 */ |
1730 void generateIsTestsOn(ClassElement cls, | 1807 void generateIsTestsOn(ClassElement cls, |
1731 void emitIsTest(Element element), | 1808 void emitIsTest(Element element), |
1732 void emitSubstitution(Element element, {emitNull})) { | 1809 FunctionTypeTestEmitter emitIsFunctionTypeTest, |
| 1810 FunctionTypeSignatureEmitter emitFunctionTypeSignature, |
| 1811 SubstitutionEmitter emitSubstitution) { |
1733 if (checkedClasses.contains(cls)) { | 1812 if (checkedClasses.contains(cls)) { |
1734 emitIsTest(cls); | 1813 emitIsTest(cls); |
1735 emitSubstitution(cls); | 1814 emitSubstitution(cls); |
1736 } | 1815 } |
1737 | 1816 |
1738 RuntimeTypes rti = backend.rti; | 1817 RuntimeTypes rti = backend.rti; |
1739 ClassElement superclass = cls.superclass; | 1818 ClassElement superclass = cls.superclass; |
1740 | 1819 |
1741 bool haveSameTypeVariables(ClassElement a, ClassElement b) { | 1820 bool haveSameTypeVariables(ClassElement a, ClassElement b) { |
1742 if (a.isClosure()) return true; | 1821 if (a.isClosure()) return true; |
1743 return a.typeVariables == b.typeVariables; | 1822 return a.typeVariables == b.typeVariables; |
1744 } | 1823 } |
1745 | 1824 |
1746 if (superclass != null && superclass != compiler.objectClass && | 1825 if (superclass != null && superclass != compiler.objectClass && |
1747 !haveSameTypeVariables(cls, superclass)) { | 1826 !haveSameTypeVariables(cls, superclass)) { |
1748 // We cannot inherit the generated substitutions, because the type | 1827 // We cannot inherit the generated substitutions, because the type |
1749 // variable layout for this class is different. Instead we generate | 1828 // variable layout for this class is different. Instead we generate |
1750 // substitutions for all checks and make emitSubstitution a NOP for the | 1829 // substitutions for all checks and make emitSubstitution a NOP for the |
1751 // rest of this function. | 1830 // rest of this function. |
1752 Set<ClassElement> emitted = new Set<ClassElement>(); | 1831 Set<ClassElement> emitted = new Set<ClassElement>(); |
1753 // TODO(karlklose): move the computation of these checks to | 1832 // TODO(karlklose): move the computation of these checks to |
1754 // RuntimeTypeInformation. | 1833 // RuntimeTypeInformation. |
1755 if (backend.needsRti(cls)) { | 1834 if (backend.classNeedsRti(cls)) { |
1756 emitSubstitution(superclass, emitNull: true); | 1835 emitSubstitution(superclass, emitNull: true); |
1757 emitted.add(superclass); | 1836 emitted.add(superclass); |
1758 } | 1837 } |
1759 for (DartType supertype in cls.allSupertypes) { | 1838 for (DartType supertype in cls.allSupertypes) { |
1760 ClassElement superclass = supertype.element; | 1839 ClassElement superclass = supertype.element; |
1761 if (classesUsingTypeVariableTests.contains(superclass)) { | 1840 if (classesUsingTypeVariableTests.contains(superclass)) { |
1762 emitSubstitution(superclass, emitNull: true); | 1841 emitSubstitution(superclass, emitNull: true); |
1763 emitted.add(superclass); | 1842 emitted.add(superclass); |
1764 } | 1843 } |
1765 for (ClassElement check in checkedClasses) { | 1844 for (ClassElement check in checkedClasses) { |
1766 if (supertype.element == check && !emitted.contains(check)) { | 1845 if (supertype.element == check && !emitted.contains(check)) { |
1767 // Generate substitution. If no substitution is necessary, emit | 1846 // Generate substitution. If no substitution is necessary, emit |
1768 // [:null:] to overwrite a (possibly) existing substitution from the | 1847 // [:null:] to overwrite a (possibly) existing substitution from the |
1769 // super classes. | 1848 // super classes. |
1770 emitSubstitution(check, emitNull: true); | 1849 emitSubstitution(check, emitNull: true); |
1771 emitted.add(check); | 1850 emitted.add(check); |
1772 } | 1851 } |
1773 } | 1852 } |
1774 } | 1853 } |
1775 void emitNothing(_, {emitNull}) {}; | 1854 void emitNothing(_, {emitNull}) {}; |
1776 emitSubstitution = emitNothing; | 1855 emitSubstitution = emitNothing; |
1777 } | 1856 } |
1778 | 1857 |
1779 Set<Element> generated = new Set<Element>(); | 1858 Set<Element> generated = new Set<Element>(); |
1780 // A class that defines a [:call:] method implicitly implements | 1859 // A class that defines a [:call:] method implicitly implements |
1781 // [Function] and needs checks for all typedefs that are used in is-checks. | 1860 // [Function] and needs checks for all typedefs that are used in is-checks. |
1782 if (checkedClasses.contains(compiler.functionClass) || | 1861 if (checkedClasses.contains(compiler.functionClass) || |
1783 !checkedTypedefs.isEmpty) { | 1862 !checkedFunctionTypes.isEmpty) { |
1784 Element call = cls.lookupLocalMember(Compiler.CALL_OPERATOR_NAME); | 1863 Element call = cls.lookupLocalMember(Compiler.CALL_OPERATOR_NAME); |
1785 if (call == null) { | 1864 if (call == null) { |
1786 // If [cls] is a closure, it has a synthetic call operator method. | 1865 // If [cls] is a closure, it has a synthetic call operator method. |
1787 call = cls.lookupBackendMember(Compiler.CALL_OPERATOR_NAME); | 1866 call = cls.lookupBackendMember(Compiler.CALL_OPERATOR_NAME); |
1788 } | 1867 } |
1789 if (call != null && call.isFunction()) { | 1868 if (call != null && call.isFunction()) { |
1790 generateInterfacesIsTests(compiler.functionClass, | 1869 generateInterfacesIsTests(compiler.functionClass, |
1791 emitIsTest, | 1870 emitIsTest, |
1792 emitSubstitution, | 1871 emitSubstitution, |
1793 generated); | 1872 generated); |
1794 getTypedefChecksOn(call.computeType(compiler)).forEach(emitIsTest); | 1873 FunctionType callType = call.computeType(compiler); |
1795 } | 1874 Map<FunctionType, bool> functionTypeChecks = |
| 1875 getFunctionTypeChecksOn(callType); |
| 1876 generateFunctionTypeTests(call, callType, functionTypeChecks, |
| 1877 emitFunctionTypeSignature, emitIsFunctionTypeTest); |
| 1878 } |
1796 } | 1879 } |
1797 | 1880 |
1798 for (DartType interfaceType in cls.interfaces) { | 1881 for (DartType interfaceType in cls.interfaces) { |
1799 generateInterfacesIsTests(interfaceType.element, emitIsTest, | 1882 generateInterfacesIsTests(interfaceType.element, emitIsTest, |
1800 emitSubstitution, generated); | 1883 emitSubstitution, generated); |
1801 } | 1884 } |
1802 } | 1885 } |
1803 | 1886 |
1804 /** | 1887 /** |
1805 * Generate "is tests" where [cls] is being implemented. | 1888 * Generate "is tests" where [cls] is being implemented. |
1806 */ | 1889 */ |
1807 void generateInterfacesIsTests(ClassElement cls, | 1890 void generateInterfacesIsTests(ClassElement cls, |
1808 void emitIsTest(ClassElement element), | 1891 void emitIsTest(ClassElement element), |
1809 void emitSubstitution(ClassElement element), | 1892 SubstitutionEmitter emitSubstitution, |
1810 Set<Element> alreadyGenerated) { | 1893 Set<Element> alreadyGenerated) { |
1811 void tryEmitTest(ClassElement check) { | 1894 void tryEmitTest(ClassElement check) { |
1812 if (!alreadyGenerated.contains(check) && checkedClasses.contains(check)) { | 1895 if (!alreadyGenerated.contains(check) && checkedClasses.contains(check)) { |
1813 alreadyGenerated.add(check); | 1896 alreadyGenerated.add(check); |
1814 emitIsTest(check); | 1897 emitIsTest(check); |
1815 emitSubstitution(check); | 1898 emitSubstitution(check); |
1816 } | 1899 } |
1817 }; | 1900 }; |
1818 | 1901 |
1819 tryEmitTest(cls); | 1902 tryEmitTest(cls); |
1820 | 1903 |
1821 for (DartType interfaceType in cls.interfaces) { | 1904 for (DartType interfaceType in cls.interfaces) { |
1822 Element element = interfaceType.element; | 1905 Element element = interfaceType.element; |
1823 tryEmitTest(element); | 1906 tryEmitTest(element); |
1824 generateInterfacesIsTests(element, emitIsTest, emitSubstitution, | 1907 generateInterfacesIsTests(element, emitIsTest, emitSubstitution, |
1825 alreadyGenerated); | 1908 alreadyGenerated); |
1826 } | 1909 } |
1827 | 1910 |
1828 // We need to also emit "is checks" for the superclass and its supertypes. | 1911 // We need to also emit "is checks" for the superclass and its supertypes. |
1829 ClassElement superclass = cls.superclass; | 1912 ClassElement superclass = cls.superclass; |
1830 if (superclass != null) { | 1913 if (superclass != null) { |
1831 tryEmitTest(superclass); | 1914 tryEmitTest(superclass); |
1832 generateInterfacesIsTests(superclass, emitIsTest, emitSubstitution, | 1915 generateInterfacesIsTests(superclass, emitIsTest, emitSubstitution, |
1833 alreadyGenerated); | 1916 alreadyGenerated); |
1834 } | 1917 } |
1835 } | 1918 } |
1836 | 1919 |
| 1920 const int MAX_FUNCTION_TYPE_PREDICATES = 10; |
| 1921 |
| 1922 /** |
| 1923 * Generates function type checks on [method] with type [methodType] against |
| 1924 * the function type checks in [functionTypeChecks]. |
| 1925 */ |
| 1926 void generateFunctionTypeTests( |
| 1927 Element method, |
| 1928 FunctionType methodType, |
| 1929 Map<FunctionType, bool> functionTypeChecks, |
| 1930 FunctionTypeSignatureEmitter emitFunctionTypeSignature, |
| 1931 FunctionTypeTestEmitter emitIsFunctionTypeTest) { |
| 1932 bool hasDynamicFunctionTypeCheck = false; |
| 1933 int neededPredicates = 0; |
| 1934 functionTypeChecks.forEach((FunctionType functionType, bool knownSubtype) { |
| 1935 if (!knownSubtype) { |
| 1936 registerDynamicFunctionTypeCheck(functionType); |
| 1937 hasDynamicFunctionTypeCheck = true; |
| 1938 } else { |
| 1939 neededPredicates++; |
| 1940 } |
| 1941 }); |
| 1942 bool alwaysUseSignature = false; |
| 1943 if (hasDynamicFunctionTypeCheck || |
| 1944 neededPredicates > MAX_FUNCTION_TYPE_PREDICATES) { |
| 1945 emitFunctionTypeSignature(method, methodType); |
| 1946 alwaysUseSignature = true; |
| 1947 } |
| 1948 functionTypeChecks.forEach((FunctionType functionType, bool knownSubtype) { |
| 1949 if (knownSubtype) { |
| 1950 if (alwaysUseSignature) { |
| 1951 registerDynamicFunctionTypeCheck(functionType); |
| 1952 } else { |
| 1953 emitIsFunctionTypeTest(functionType); |
| 1954 } |
| 1955 } |
| 1956 }); |
| 1957 } |
| 1958 |
1837 /** | 1959 /** |
1838 * Return a function that returns true if its argument is a class | 1960 * Return a function that returns true if its argument is a class |
1839 * that needs to be emitted. | 1961 * that needs to be emitted. |
1840 */ | 1962 */ |
1841 Function computeClassFilter() { | 1963 Function computeClassFilter() { |
1842 Set<ClassElement> unneededClasses = new Set<ClassElement>(); | 1964 Set<ClassElement> unneededClasses = new Set<ClassElement>(); |
1843 // The [Bool] class is not marked as abstract, but has a factory | 1965 // The [Bool] class is not marked as abstract, but has a factory |
1844 // constructor that always throws. We never need to emit it. | 1966 // constructor that always throws. We never need to emit it. |
1845 unneededClasses.add(compiler.boolClass); | 1967 unneededClasses.add(compiler.boolClass); |
1846 | 1968 |
(...skipping 142 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1989 ClassBuilder closureBuilder = new ClassBuilder(); | 2111 ClassBuilder closureBuilder = new ClassBuilder(); |
1990 // If a static function is used as a closure we need to add its name | 2112 // If a static function is used as a closure we need to add its name |
1991 // in case it is used in spawnFunction. | 2113 // in case it is used in spawnFunction. |
1992 String methodName = namer.STATIC_CLOSURE_NAME_NAME; | 2114 String methodName = namer.STATIC_CLOSURE_NAME_NAME; |
1993 emitClosureClassHeader( | 2115 emitClosureClassHeader( |
1994 mangledName, superName, <String>[invocationName, methodName], | 2116 mangledName, superName, <String>[invocationName, methodName], |
1995 closureBuilder); | 2117 closureBuilder); |
1996 | 2118 |
1997 addParameterStubs(callElement, closureBuilder.addProperty); | 2119 addParameterStubs(callElement, closureBuilder.addProperty); |
1998 | 2120 |
1999 DartType type = element.computeType(compiler); | |
2000 getTypedefChecksOn(type).forEach((Element typedef) { | |
2001 String operator = namer.operatorIs(typedef); | |
2002 closureBuilder.addProperty(operator, js('true')); | |
2003 }); | |
2004 | |
2005 // TODO(ngeoffray): Cache common base classes for closures, bound | 2121 // TODO(ngeoffray): Cache common base classes for closures, bound |
2006 // closures, and static closures that have common type checks. | 2122 // closures, and static closures that have common type checks. |
2007 boundClosures.add( | 2123 boundClosures.add( |
2008 js('$classesCollector.$mangledName = #', | 2124 js('$classesCollector.$mangledName = #', |
2009 closureBuilder.toObjectInitializer())); | 2125 closureBuilder.toObjectInitializer())); |
2010 | 2126 |
2011 staticGetters[element] = closureClassElement; | 2127 staticGetters[element] = closureClassElement; |
| 2128 |
| 2129 void emitFunctionTypeSignature(Element method, FunctionType methodType) { |
| 2130 RuntimeTypes rti = backend.rti; |
| 2131 // [:() => null:] is dummy encoding of [this] which is never needed for |
| 2132 // the encoding of the type of the static [method]. |
| 2133 String encoding = rti.getSignatureEncoding(methodType, () => 'null'); |
| 2134 String operatorSignature = namer.operatorSignature(); |
| 2135 // TODO(johnniwinther): Make MiniJsParser support function expressions. |
| 2136 closureBuilder.addProperty(operatorSignature, |
| 2137 new jsAst.LiteralExpression(encoding)); |
| 2138 } |
| 2139 |
| 2140 void emitIsFunctionTypeTest(FunctionType functionType) { |
| 2141 String operator = namer.operatorIsType(functionType); |
| 2142 closureBuilder.addProperty(operator, js('true')); |
| 2143 } |
| 2144 |
| 2145 FunctionType methodType = element.computeType(compiler); |
| 2146 Map<FunctionType, bool> functionTypeChecks = |
| 2147 getFunctionTypeChecksOn(methodType); |
| 2148 generateFunctionTypeTests(element, methodType, functionTypeChecks, |
| 2149 emitFunctionTypeSignature, emitIsFunctionTypeTest); |
2012 } | 2150 } |
2013 } | 2151 } |
2014 | 2152 |
2015 void emitClosureClassHeader(String mangledName, | 2153 void emitClosureClassHeader(String mangledName, |
2016 String superName, | 2154 String superName, |
2017 List<String> fieldNames, | 2155 List<String> fieldNames, |
2018 ClassBuilder builder) { | 2156 ClassBuilder builder) { |
2019 builder.addProperty('', | 2157 builder.addProperty('', |
2020 js.string("$superName;${fieldNames.join(',')}")); | 2158 js.string("$superName;${fieldNames.join(',')}")); |
2021 } | 2159 } |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2059 if (inInterceptor) { | 2197 if (inInterceptor) { |
2060 cache = interceptorClosureCache; | 2198 cache = interceptorClosureCache; |
2061 } else { | 2199 } else { |
2062 cache = boundClosureCache; | 2200 cache = boundClosureCache; |
2063 } | 2201 } |
2064 List<String> fieldNames = <String>[]; | 2202 List<String> fieldNames = <String>[]; |
2065 compiler.boundClosureClass.forEachInstanceField((_, Element field) { | 2203 compiler.boundClosureClass.forEachInstanceField((_, Element field) { |
2066 fieldNames.add(namer.getName(field)); | 2204 fieldNames.add(namer.getName(field)); |
2067 }); | 2205 }); |
2068 | 2206 |
2069 Iterable<Element> typedefChecks = | 2207 DartType memberType = member.computeType(compiler); |
2070 getTypedefChecksOn(member.computeType(compiler)); | 2208 Map<FunctionType, bool> functionTypeChecks = |
2071 bool hasTypedefChecks = !typedefChecks.isEmpty; | 2209 getFunctionTypeChecksOn(memberType); |
| 2210 bool hasFunctionTypeChecks = !functionTypeChecks.isEmpty; |
2072 | 2211 |
2073 bool canBeShared = !hasOptionalParameters && !hasTypedefChecks; | 2212 bool canBeShared = !hasOptionalParameters && !hasFunctionTypeChecks; |
2074 | 2213 |
| 2214 ClassElement classElement = member.getEnclosingClass(); |
2075 String closureClass = canBeShared ? cache[parameterCount] : null; | 2215 String closureClass = canBeShared ? cache[parameterCount] : null; |
2076 if (closureClass == null) { | 2216 if (closureClass == null) { |
2077 // Either the class was not cached yet, or there are optional parameters. | 2217 // Either the class was not cached yet, or there are optional parameters. |
2078 // Create a new closure class. | 2218 // Create a new closure class. |
2079 String name; | 2219 String name; |
2080 if (canBeShared) { | 2220 if (canBeShared) { |
2081 if (inInterceptor) { | 2221 if (inInterceptor) { |
2082 name = 'BoundClosure\$i${parameterCount}'; | 2222 name = 'BoundClosure\$i${parameterCount}'; |
2083 } else { | 2223 } else { |
2084 name = 'BoundClosure\$${parameterCount}'; | 2224 name = 'BoundClosure\$${parameterCount}'; |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2120 arguments.add(js(name)); | 2260 arguments.add(js(name)); |
2121 } | 2261 } |
2122 | 2262 |
2123 jsAst.Expression fun = js.fun( | 2263 jsAst.Expression fun = js.fun( |
2124 parameters, | 2264 parameters, |
2125 js.return_( | 2265 js.return_( |
2126 js('this')[fieldNames[0]][js('this')[fieldNames[1]]](arguments))); | 2266 js('this')[fieldNames[0]][js('this')[fieldNames[1]]](arguments))); |
2127 boundClosureBuilder.addProperty(invocationName, fun); | 2267 boundClosureBuilder.addProperty(invocationName, fun); |
2128 | 2268 |
2129 addParameterStubs(callElement, boundClosureBuilder.addProperty); | 2269 addParameterStubs(callElement, boundClosureBuilder.addProperty); |
2130 typedefChecks.forEach((Element typedef) { | 2270 |
2131 String operator = namer.operatorIs(typedef); | 2271 void emitFunctionTypeSignature(Element method, FunctionType methodType) { |
2132 boundClosureBuilder.addProperty(operator, js('true')); | 2272 String encoding = backend.rti.getSignatureEncoding( |
2133 }); | 2273 methodType, () => 'this.${fieldNames[0]}'); |
| 2274 String operatorSignature = namer.operatorSignature(); |
| 2275 boundClosureBuilder.addProperty(operatorSignature, |
| 2276 new jsAst.LiteralExpression(encoding)); |
| 2277 } |
| 2278 |
| 2279 void emitIsFunctionTypeTest(FunctionType functionType) { |
| 2280 String operator = namer.operatorIsType(functionType); |
| 2281 boundClosureBuilder.addProperty(operator, |
| 2282 new jsAst.LiteralBool(true)); |
| 2283 } |
| 2284 |
| 2285 generateFunctionTypeTests(member, memberType, functionTypeChecks, |
| 2286 emitFunctionTypeSignature, emitIsFunctionTypeTest); |
2134 | 2287 |
2135 boundClosures.add( | 2288 boundClosures.add( |
2136 js('$classesCollector.$mangledName = #', | 2289 js('$classesCollector.$mangledName = #', |
2137 boundClosureBuilder.toObjectInitializer())); | 2290 boundClosureBuilder.toObjectInitializer())); |
2138 | 2291 |
2139 closureClass = namer.isolateAccess(closureClassElement); | 2292 closureClass = namer.isolateAccess(closureClassElement); |
2140 | 2293 |
2141 // Cache it. | 2294 // Cache it. |
2142 if (canBeShared) { | 2295 if (canBeShared) { |
2143 cache[parameterCount] = closureClass; | 2296 cache[parameterCount] = closureClass; |
(...skipping 11 matching lines...) Expand all Loading... |
2155 if (inInterceptor) { | 2308 if (inInterceptor) { |
2156 String receiverArg = fieldNames[2]; | 2309 String receiverArg = fieldNames[2]; |
2157 parameters.add(receiverArg); | 2310 parameters.add(receiverArg); |
2158 arguments.add(js(receiverArg)); | 2311 arguments.add(js(receiverArg)); |
2159 } else { | 2312 } else { |
2160 // Put null in the intercepted receiver field. | 2313 // Put null in the intercepted receiver field. |
2161 arguments.add(new jsAst.LiteralNull()); | 2314 arguments.add(new jsAst.LiteralNull()); |
2162 } | 2315 } |
2163 | 2316 |
2164 jsAst.Expression getterFunction = js.fun( | 2317 jsAst.Expression getterFunction = js.fun( |
2165 parameters, | 2318 parameters, js.return_(js(closureClass).newWith(arguments))); |
2166 js.return_(js(closureClass).newWith(arguments))); | |
2167 | 2319 |
2168 defineStub(getterName, getterFunction); | 2320 defineStub(getterName, getterFunction); |
2169 } | 2321 } |
2170 | 2322 |
2171 /** | 2323 /** |
2172 * Documentation wanted -- johnniwinther | 2324 * Documentation wanted -- johnniwinther |
2173 * | 2325 * |
2174 * Invariant: [member] must be a declaration element. | 2326 * Invariant: [member] must be a declaration element. |
2175 */ | 2327 */ |
2176 void emitCallStubForGetter(Element member, | 2328 void emitCallStubForGetter(Element member, |
(...skipping 19 matching lines...) Expand all Loading... |
2196 ? member.fixedBackendName() | 2348 ? member.fixedBackendName() |
2197 : namer.instanceFieldName(member); | 2349 : namer.instanceFieldName(member); |
2198 return js('this')[fieldName]; | 2350 return js('this')[fieldName]; |
2199 } | 2351 } |
2200 } | 2352 } |
2201 | 2353 |
2202 // Two selectors may match but differ only in type. To avoid generating | 2354 // Two selectors may match but differ only in type. To avoid generating |
2203 // identical stubs for each we track untyped selectors which already have | 2355 // identical stubs for each we track untyped selectors which already have |
2204 // stubs. | 2356 // stubs. |
2205 Set<Selector> generatedSelectors = new Set<Selector>(); | 2357 Set<Selector> generatedSelectors = new Set<Selector>(); |
2206 | |
2207 for (Selector selector in selectors) { | 2358 for (Selector selector in selectors) { |
2208 if (selector.applies(member, compiler)) { | 2359 if (selector.applies(member, compiler)) { |
2209 selector = selector.asUntyped; | 2360 selector = selector.asUntyped; |
2210 if (generatedSelectors.contains(selector)) continue; | 2361 if (generatedSelectors.contains(selector)) continue; |
2211 generatedSelectors.add(selector); | 2362 generatedSelectors.add(selector); |
2212 | 2363 |
2213 String invocationName = namer.invocationName(selector); | 2364 String invocationName = namer.invocationName(selector); |
2214 Selector callSelector = new Selector.callClosureFrom(selector); | 2365 Selector callSelector = new Selector.callClosureFrom(selector); |
2215 String closureCallName = namer.invocationName(callSelector); | 2366 String closureCallName = namer.invocationName(callSelector); |
2216 | 2367 |
(...skipping 584 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2801 }); | 2952 }); |
2802 | 2953 |
2803 // 3b. Add classes that are referenced by substitutions in object checks and | 2954 // 3b. Add classes that are referenced by substitutions in object checks and |
2804 // their superclasses. | 2955 // their superclasses. |
2805 TypeChecks requiredChecks = | 2956 TypeChecks requiredChecks = |
2806 backend.rti.computeChecks(neededClasses, checkedClasses); | 2957 backend.rti.computeChecks(neededClasses, checkedClasses); |
2807 Set<ClassElement> classesUsedInSubstitutions = | 2958 Set<ClassElement> classesUsedInSubstitutions = |
2808 rti.getClassesUsedInSubstitutions(backend, requiredChecks); | 2959 rti.getClassesUsedInSubstitutions(backend, requiredChecks); |
2809 addClassesWithSuperclasses(classesUsedInSubstitutions); | 2960 addClassesWithSuperclasses(classesUsedInSubstitutions); |
2810 | 2961 |
| 2962 // 3c. Add classes that contain checked generic function types. These are |
| 2963 // needed to store the signature encoding. |
| 2964 for (FunctionType type in checkedFunctionTypes) { |
| 2965 ClassElement contextClass = Types.getClassContext(type); |
| 2966 if (contextClass != null) { |
| 2967 neededClasses.add(contextClass); |
| 2968 } |
| 2969 } |
| 2970 |
2811 // 4. Finally, sort the classes. | 2971 // 4. Finally, sort the classes. |
2812 List<ClassElement> sortedClasses = Elements.sortedByPosition(neededClasses); | 2972 List<ClassElement> sortedClasses = Elements.sortedByPosition(neededClasses); |
2813 | 2973 |
2814 // If we need noSuchMethod support, we run through all needed | 2974 // If we need noSuchMethod support, we run through all needed |
2815 // classes to figure out if we need the support on any native | 2975 // classes to figure out if we need the support on any native |
2816 // class. If so, we let the native emitter deal with it. | 2976 // class. If so, we let the native emitter deal with it. |
2817 if (compiler.enabledNoSuchMethod) { | 2977 if (compiler.enabledNoSuchMethod) { |
2818 SourceString noSuchMethodName = Compiler.NO_SUCH_METHOD; | 2978 SourceString noSuchMethodName = Compiler.NO_SUCH_METHOD; |
2819 Selector noSuchMethodSelector = compiler.noSuchMethodSelector; | 2979 Selector noSuchMethodSelector = compiler.noSuchMethodSelector; |
2820 for (ClassElement element in sortedClasses) { | 2980 for (ClassElement element in sortedClasses) { |
(...skipping 631 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
3452 | 3612 |
3453 const String HOOKS_API_USAGE = """ | 3613 const String HOOKS_API_USAGE = """ |
3454 // The code supports the following hooks: | 3614 // The code supports the following hooks: |
3455 // dartPrint(message) - if this function is defined it is called | 3615 // dartPrint(message) - if this function is defined it is called |
3456 // instead of the Dart [print] method. | 3616 // instead of the Dart [print] method. |
3457 // dartMainRunner(main) - if this function is defined, the Dart [main] | 3617 // dartMainRunner(main) - if this function is defined, the Dart [main] |
3458 // method will not be invoked directly. | 3618 // method will not be invoked directly. |
3459 // Instead, a closure that will invoke [main] is | 3619 // Instead, a closure that will invoke [main] is |
3460 // passed to [dartMainRunner]. | 3620 // passed to [dartMainRunner]. |
3461 """; | 3621 """; |
OLD | NEW |