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

Side by Side Diff: dart/sdk/lib/_internal/compiler/implementation/js_backend/emitter.dart

Issue 19693011: Fix some bugs regarding native fields. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge
Patch Set: Remove debug code and add a comment Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
70 ConstantEmitter constantEmitter; 70 ConstantEmitter constantEmitter;
71 NativeEmitter nativeEmitter; 71 NativeEmitter nativeEmitter;
72 CodeBuffer mainBuffer; 72 CodeBuffer mainBuffer;
73 final CodeBuffer deferredLibraries = new CodeBuffer(); 73 final CodeBuffer deferredLibraries = new CodeBuffer();
74 final CodeBuffer deferredConstants = new CodeBuffer(); 74 final CodeBuffer deferredConstants = new CodeBuffer();
75 /** Shorter access to [isolatePropertiesName]. Both here in the code, as 75 /** Shorter access to [isolatePropertiesName]. Both here in the code, as
76 well as in the generated code. */ 76 well as in the generated code. */
77 String isolateProperties; 77 String isolateProperties;
78 String classesCollector; 78 String classesCollector;
79 final Set<ClassElement> neededClasses = new Set<ClassElement>(); 79 final Set<ClassElement> neededClasses = new Set<ClassElement>();
80 final Set<ClassElement> rtiNeededClasses = new Set<ClassElement>();
80 final List<ClassElement> regularClasses = <ClassElement>[]; 81 final List<ClassElement> regularClasses = <ClassElement>[];
81 final List<ClassElement> deferredClasses = <ClassElement>[]; 82 final List<ClassElement> deferredClasses = <ClassElement>[];
82 final List<ClassElement> nativeClasses = <ClassElement>[]; 83 final List<ClassElement> nativeClasses = <ClassElement>[];
83 final List<Selector> trivialNsmHandlers = <Selector>[]; 84 final List<Selector> trivialNsmHandlers = <Selector>[];
84 final Map<String, String> mangledFieldNames = <String, String>{}; 85 final Map<String, String> mangledFieldNames = <String, String>{};
85 final Map<String, String> mangledGlobalFieldNames = <String, String>{}; 86 final Map<String, String> mangledGlobalFieldNames = <String, String>{};
86 final Set<String> recordedMangledNames = new Set<String>(); 87 final Set<String> recordedMangledNames = new Set<String>();
87 final Set<String> interceptorInvocationNames = new Set<String>(); 88 final Set<String> interceptorInvocationNames = new Set<String>();
88 89
89 /// A list of JS expressions that represent metadata, parameter names and 90 /// A list of JS expressions that represent metadata, parameter names and
(...skipping 1581 matching lines...) Expand 10 before | Expand all | Expand 10 after
1671 } 1672 }
1672 assert(invariant(member, previousName == memberName, 1673 assert(invariant(member, previousName == memberName,
1673 message: '$previousName != ${memberName}')); 1674 message: '$previousName != ${memberName}'));
1674 } 1675 }
1675 1676
1676 /// Returns `true` if fields added. 1677 /// Returns `true` if fields added.
1677 bool emitClassFields(ClassElement classElement, 1678 bool emitClassFields(ClassElement classElement,
1678 ClassBuilder builder, 1679 ClassBuilder builder,
1679 String superName, 1680 String superName,
1680 { bool classIsNative: false, 1681 { bool classIsNative: false,
1681 bool emitStatics: false }) { 1682 bool emitStatics: false,
1683 bool onlyForRti: false }) {
1684 assert(!emitStatics || !onlyForRti);
1682 StringBuffer buffer = new StringBuffer(); 1685 StringBuffer buffer = new StringBuffer();
1683 if (emitStatics) { 1686 if (emitStatics) {
1684 assert(invariant(classElement, superName == null, message: superName)); 1687 assert(invariant(classElement, superName == null, message: superName));
1685 } else { 1688 } else {
1686 assert(invariant(classElement, superName != null)); 1689 assert(invariant(classElement, superName != null));
1687 String nativeName = 1690 String nativeName =
1688 namer.getPrimitiveInterceptorRuntimeName(classElement); 1691 namer.getPrimitiveInterceptorRuntimeName(classElement);
1689 if (nativeName != null) { 1692 if (nativeName != null) {
1690 buffer.write('$nativeName/'); 1693 buffer.write('$nativeName/');
1691 } 1694 }
1692 buffer.write('$superName;'); 1695 buffer.write('$superName;');
1693 } 1696 }
1694 int bufferClassLength = buffer.length; 1697 int bufferClassLength = buffer.length;
1695 1698
1696 String separator = ''; 1699 String separator = '';
1697 1700
1698 var fieldMetadata = []; 1701 var fieldMetadata = [];
1699 bool hasMetadata = false; 1702 bool hasMetadata = false;
1700 1703
1701 visitClassFields(classElement, emitStatics, 1704 if (!onlyForRti) {
1702 (Element member, 1705 visitClassFields(classElement, emitStatics,
1703 String name, 1706 (Element member,
1704 String accessorName, 1707 String name,
1705 bool needsGetter, 1708 String accessorName,
1706 bool needsSetter, 1709 bool needsGetter,
1707 bool needsCheckedSetter) { 1710 bool needsSetter,
1708 // Ignore needsCheckedSetter - that is handled below. 1711 bool needsCheckedSetter) {
1709 bool needsAccessor = (needsGetter || needsSetter); 1712 // Ignore needsCheckedSetter - that is handled below.
1710 // We need to output the fields for non-native classes so we can auto- 1713 bool needsAccessor = (needsGetter || needsSetter);
1711 // generate the constructor. For native classes there are no 1714 // We need to output the fields for non-native classes so we can auto-
1712 // constructors, so we don't need the fields unless we are generating 1715 // generate the constructor. For native classes there are no
1713 // accessors at runtime. 1716 // constructors, so we don't need the fields unless we are generating
1714 if (!classIsNative || needsAccessor) { 1717 // accessors at runtime.
1715 buffer.write(separator); 1718 if (!classIsNative || needsAccessor) {
1716 separator = ','; 1719 buffer.write(separator);
1717 var metadata = buildMetadataFunction(member); 1720 separator = ',';
1718 if (metadata != null) { 1721 var metadata = buildMetadataFunction(member);
1719 hasMetadata = true; 1722 if (metadata != null) {
1720 } else { 1723 hasMetadata = true;
1721 metadata = new jsAst.LiteralNull(); 1724 } else {
1722 } 1725 metadata = new jsAst.LiteralNull();
1723 fieldMetadata.add(metadata);
1724 recordMangledField(member, accessorName, member.name.slowToString());
1725 if (!needsAccessor) {
1726 // Emit field for constructor generation.
1727 assert(!classIsNative);
1728 buffer.write(name);
1729 } else {
1730 // Emit (possibly renaming) field name so we can add accessors at
1731 // runtime.
1732 buffer.write(accessorName);
1733 if (name != accessorName) {
1734 buffer.write(':$name');
1735 // Only the native classes can have renaming accessors.
1736 assert(classIsNative);
1737 } 1726 }
1727 fieldMetadata.add(metadata);
1728 recordMangledField(member, accessorName, member.name.slowToString());
1729 if (!needsAccessor) {
1730 // Emit field for constructor generation.
1731 assert(!classIsNative);
1732 buffer.write(name);
1733 } else {
1734 // Emit (possibly renaming) field name so we can add accessors at
1735 // runtime.
1736 buffer.write(accessorName);
1737 if (name != accessorName) {
1738 buffer.write(':$name');
1739 // Only the native classes can have renaming accessors.
1740 assert(classIsNative);
1741 }
1738 1742
1739 int getterCode = 0; 1743 int getterCode = 0;
1740 if (needsGetter) { 1744 if (needsGetter) {
1741 // 01: function() { return this.field; } 1745 // 01: function() { return this.field; }
1742 // 10: function(receiver) { return receiver.field; } 1746 // 10: function(receiver) { return receiver.field; }
1743 // 11: function(receiver) { return this.field; } 1747 // 11: function(receiver) { return this.field; }
1744 getterCode += backend.fieldHasInterceptedGetter(member) ? 2 : 0; 1748 if (member.isInstanceMember()) {
1745 getterCode += backend.isInterceptorClass(classElement) ? 0 : 1; 1749 getterCode += backend.fieldHasInterceptedGetter(member) ? 2 : 0;
1746 // TODO(sra): 'isInterceptorClass' might not be the correct test for 1750 getterCode += backend.isInterceptorClass(classElement) ? 0 : 1;
1747 // methods forced to use the interceptor convention because the 1751 // TODO(sra): 'isInterceptorClass' might not be the correct
1748 // method's class was elsewhere mixed-in to an interceptor. 1752 // test for methods forced to use the interceptor convention
1749 assert(!member.isInstanceMember() || getterCode != 0); 1753 // because the method's class was elsewhere mixed-in to an
1750 } 1754 // interceptor.
1751 int setterCode = 0; 1755 assert(getterCode != 0);
1752 if (needsSetter) { 1756 } else {
1753 // 01: function(value) { this.field = value; } 1757 getterCode = 1;
1754 // 10: function(receiver, value) { receiver.field = value; } 1758 }
1755 // 11: function(receiver, value) { this.field = value; } 1759 }
1756 setterCode += backend.fieldHasInterceptedSetter(member) ? 2 : 0; 1760 int setterCode = 0;
1757 setterCode += backend.isInterceptorClass(classElement) ? 0 : 1; 1761 if (needsSetter) {
1758 assert(!member.isInstanceMember() || setterCode != 0); 1762 // 01: function(value) { this.field = value; }
1759 } 1763 // 10: function(receiver, value) { receiver.field = value; }
1760 int code = getterCode + (setterCode << 2); 1764 // 11: function(receiver, value) { this.field = value; }
1761 if (code == 0) { 1765 if (member.isInstanceMember()) {
1762 compiler.reportInternalError( 1766 setterCode += backend.fieldHasInterceptedSetter(member) ? 2 : 0;
1763 member, 'Internal error: code is 0 ($classElement/$member)'); 1767 setterCode += backend.isInterceptorClass(classElement) ? 0 : 1;
1764 } else { 1768 assert(setterCode != 0);
1765 buffer.write(FIELD_CODE_CHARACTERS[code - FIRST_FIELD_CODE]); 1769 } else {
1770 setterCode = 1;
1771 }
1772 }
1773 int code = getterCode + (setterCode << 2);
1774 if (code == 0) {
1775 compiler.reportInternalError(
1776 member, 'Internal error: code is 0 ($classElement/$member)');
1777 } else {
1778 buffer.write(FIELD_CODE_CHARACTERS[code - FIRST_FIELD_CODE]);
1779 }
1766 } 1780 }
1767 } 1781 }
1768 } 1782 });
1769 }); 1783 }
1770 1784
1771 bool fieldsAdded = buffer.length > bufferClassLength; 1785 bool fieldsAdded = buffer.length > bufferClassLength;
1772 String compactClassData = buffer.toString(); 1786 String compactClassData = buffer.toString();
1773 jsAst.Expression classDataNode = js.string(compactClassData); 1787 jsAst.Expression classDataNode = js.string(compactClassData);
1774 if (hasMetadata) { 1788 if (hasMetadata) {
1775 fieldMetadata.insert(0, classDataNode); 1789 fieldMetadata.insert(0, classDataNode);
1776 classDataNode = new jsAst.ArrayInitializer.from(fieldMetadata); 1790 classDataNode = new jsAst.ArrayInitializer.from(fieldMetadata);
1777 } 1791 }
1778 builder.addProperty('', classDataNode); 1792 builder.addProperty('', classDataNode);
1779 return fieldsAdded; 1793 return fieldsAdded;
(...skipping 25 matching lines...) Expand all
1805 }); 1819 });
1806 }); 1820 });
1807 } 1821 }
1808 1822
1809 /** 1823 /**
1810 * Documentation wanted -- johnniwinther 1824 * Documentation wanted -- johnniwinther
1811 * 1825 *
1812 * Invariant: [classElement] must be a declaration element. 1826 * Invariant: [classElement] must be a declaration element.
1813 */ 1827 */
1814 void generateClass(ClassElement classElement, CodeBuffer buffer) { 1828 void generateClass(ClassElement classElement, CodeBuffer buffer) {
1829 final onlyForRti = rtiNeededClasses.contains(classElement);
1830
1815 assert(invariant(classElement, classElement.isDeclaration)); 1831 assert(invariant(classElement, classElement.isDeclaration));
1816 assert(invariant(classElement, !classElement.isNative())); 1832 assert(invariant(classElement, !classElement.isNative() || onlyForRti));
1817 1833
1818 needsDefineClass = true; 1834 needsDefineClass = true;
1819 String className = namer.getName(classElement); 1835 String className = namer.getName(classElement);
1820 1836
1821 ClassElement superclass = classElement.superclass; 1837 ClassElement superclass = classElement.superclass;
1822 String superName = ""; 1838 String superName = "";
1823 if (superclass != null) { 1839 if (superclass != null) {
1824 superName = namer.getName(superclass); 1840 superName = namer.getName(superclass);
1825 } 1841 }
1826 String runtimeName = 1842 String runtimeName =
1827 namer.getPrimitiveInterceptorRuntimeName(classElement); 1843 namer.getPrimitiveInterceptorRuntimeName(classElement);
1828 1844
1829 if (classElement.isMixinApplication) { 1845 if (classElement.isMixinApplication) {
1830 String mixinName = namer.getName(computeMixinClass(classElement)); 1846 String mixinName = namer.getName(computeMixinClass(classElement));
1831 superName = '$superName+$mixinName'; 1847 superName = '$superName+$mixinName';
1832 needsMixinSupport = true; 1848 needsMixinSupport = true;
1833 } 1849 }
1834 1850
1835 ClassBuilder builder = new ClassBuilder(); 1851 ClassBuilder builder = new ClassBuilder();
1836 emitClassConstructor(classElement, builder); 1852 emitClassConstructor(classElement, builder);
1837 emitSuper(superName, builder); 1853 emitSuper(superName, builder);
1838 emitRuntimeName(runtimeName, builder); 1854 emitRuntimeName(runtimeName, builder);
1839 emitClassFields(classElement, builder, superName); 1855 emitClassFields(classElement, builder, superName, onlyForRti: onlyForRti);
1840 emitClassGettersSetters(classElement, builder); 1856 emitClassGettersSetters(classElement, builder);
1841 if (!classElement.isMixinApplication) { 1857 if (!classElement.isMixinApplication) {
1842 emitInstanceMembers(classElement, builder); 1858 emitInstanceMembers(classElement, builder);
1843 } 1859 }
1844 emitIsTests(classElement, builder); 1860 emitIsTests(classElement, builder);
1845 1861
1846 emitClassBuilderWithReflectionData( 1862 emitClassBuilderWithReflectionData(
1847 className, classElement, builder, buffer); 1863 className, classElement, builder, buffer);
1848 } 1864 }
1849 1865
(...skipping 1236 matching lines...) Expand 10 before | Expand all | Expand 10 after
3086 // 1. We need to generate all classes that are instantiated. 3102 // 1. We need to generate all classes that are instantiated.
3087 addClassesWithSuperclasses(instantiatedClasses); 3103 addClassesWithSuperclasses(instantiatedClasses);
3088 3104
3089 // 2. Add all classes used as mixins. 3105 // 2. Add all classes used as mixins.
3090 Set<ClassElement> mixinClasses = neededClasses 3106 Set<ClassElement> mixinClasses = neededClasses
3091 .where((ClassElement element) => element.isMixinApplication) 3107 .where((ClassElement element) => element.isMixinApplication)
3092 .map(computeMixinClass) 3108 .map(computeMixinClass)
3093 .toSet(); 3109 .toSet();
3094 neededClasses.addAll(mixinClasses); 3110 neededClasses.addAll(mixinClasses);
3095 3111
3096 // 3a. Add classes that are referenced by type arguments or substitutions in 3112 // 3. If we need noSuchMethod support, we run through all needed
3097 // argument checks.
3098 // TODO(karlklose): merge this case with 3b when unifying argument and
3099 // object checks.
3100 RuntimeTypes rti = backend.rti;
3101 backend.rti.getRequiredArgumentClasses(backend).forEach((ClassElement c) {
3102 // Types that we represent with JS native types (like int and String) do
3103 // not need a class definition as we use the interceptor classes instead.
3104 if (!rti.isJsNative(c)) {
3105 addClassWithSuperclasses(c);
3106 }
3107 });
3108
3109 // 3b. Add classes that are referenced by substitutions in object checks and
3110 // their superclasses.
3111 TypeChecks requiredChecks =
3112 backend.rti.computeChecks(neededClasses, checkedClasses);
3113 Set<ClassElement> classesUsedInSubstitutions =
3114 rti.getClassesUsedInSubstitutions(backend, requiredChecks);
3115 addClassesWithSuperclasses(classesUsedInSubstitutions);
3116
3117 // 3c. Add classes that contain checked generic function types. These are
3118 // needed to store the signature encoding.
3119 for (FunctionType type in checkedFunctionTypes) {
3120 ClassElement contextClass = Types.getClassContext(type);
3121 if (contextClass != null) {
3122 neededClasses.add(contextClass);
3123 }
3124 }
3125
3126 // 4. Finally, sort the classes.
3127 List<ClassElement> sortedClasses = Elements.sortedByPosition(neededClasses);
3128
3129 // If we need noSuchMethod support, we run through all needed
3130 // classes to figure out if we need the support on any native 3113 // classes to figure out if we need the support on any native
3131 // class. If so, we let the native emitter deal with it. 3114 // class. If so, we let the native emitter deal with it.
3132 if (compiler.enabledNoSuchMethod) { 3115 if (compiler.enabledNoSuchMethod) {
3133 SourceString noSuchMethodName = Compiler.NO_SUCH_METHOD; 3116 SourceString noSuchMethodName = Compiler.NO_SUCH_METHOD;
3134 Selector noSuchMethodSelector = compiler.noSuchMethodSelector; 3117 Selector noSuchMethodSelector = compiler.noSuchMethodSelector;
3135 for (ClassElement element in sortedClasses) { 3118 for (ClassElement element in neededClasses) {
3136 if (!element.isNative()) continue; 3119 if (!element.isNative()) continue;
3137 Element member = element.lookupLocalMember(noSuchMethodName); 3120 Element member = element.lookupLocalMember(noSuchMethodName);
3138 if (member == null) continue; 3121 if (member == null) continue;
3139 if (noSuchMethodSelector.applies(member, compiler)) { 3122 if (noSuchMethodSelector.applies(member, compiler)) {
3140 nativeEmitter.handleNoSuchMethod = true; 3123 nativeEmitter.handleNoSuchMethod = true;
3141 break; 3124 break;
3142 } 3125 }
3143 } 3126 }
3144 } 3127 }
3145 3128
3129 // 4. Find all classes needed for rti.
3130 // It is important that this is the penultimate step, at this point,
3131 // neededClasses must only contain classes that have been resolved and
3132 // codegen'd. The rtiNeededClasses may contain additional classes, but
3133 // these are thought to not have been instantiated, so we neeed to be able
3134 // to identify them later and make sure we only emit "empty shells" without
3135 // fields, etc.
3136 computeRtiNeededClasses();
3137 rtiNeededClasses.removeAll(neededClasses);
3138 // rtiNeededClasses now contains only the "empty shells".
3139 neededClasses.addAll(rtiNeededClasses);
3140
3141 // 5. Finally, sort the classes.
3142 List<ClassElement> sortedClasses = Elements.sortedByPosition(neededClasses);
3143
3146 for (ClassElement element in sortedClasses) { 3144 for (ClassElement element in sortedClasses) {
3147 if (element.isNative()) { 3145 if (rtiNeededClasses.contains(element)) {
3146 regularClasses.add(element);
3147 } else if (element.isNative()) {
3148 // For now, native classes cannot be deferred. 3148 // For now, native classes cannot be deferred.
3149 nativeClasses.add(element); 3149 nativeClasses.add(element);
3150 } else if (isDeferred(element)) { 3150 } else if (isDeferred(element)) {
3151 deferredClasses.add(element); 3151 deferredClasses.add(element);
3152 } else { 3152 } else {
3153 regularClasses.add(element); 3153 regularClasses.add(element);
3154 } 3154 }
3155 } 3155 }
3156 } 3156 }
3157 3157
3158 Set<ClassElement> computeRtiNeededClasses() {
3159 void addClassWithSuperclasses(ClassElement cls) {
3160 if (cls.name == const SourceString('CssStyleDeclaration')) {
3161 throw 'hest';
3162 }
3163 rtiNeededClasses.add(cls);
3164 for (ClassElement superclass = cls.superclass;
3165 superclass != null;
3166 superclass = superclass.superclass) {
3167 rtiNeededClasses.add(superclass);
3168 }
3169 }
3170
3171 void addClassesWithSuperclasses(Iterable<ClassElement> classes) {
3172 for (ClassElement cls in classes) {
3173 addClassWithSuperclasses(cls);
3174 }
3175 }
3176
3177 // 1. Add classes that are referenced by type arguments or substitutions in
3178 // argument checks.
3179 // TODO(karlklose): merge this case with 2 when unifying argument and
3180 // object checks.
3181 RuntimeTypes rti = backend.rti;
3182 rti.getRequiredArgumentClasses(backend).forEach((ClassElement c) {
3183 // Types that we represent with JS native types (like int and String) do
3184 // not need a class definition as we use the interceptor classes instead.
3185 if (!rti.isJsNative(c)) {
3186 addClassWithSuperclasses(c);
3187 }
3188 });
3189
3190 // 2. Add classes that are referenced by substitutions in object checks and
3191 // their superclasses.
3192 TypeChecks requiredChecks =
3193 rti.computeChecks(rtiNeededClasses, checkedClasses);
3194 Set<ClassElement> classesUsedInSubstitutions =
3195 rti.getClassesUsedInSubstitutions(backend, requiredChecks);
3196 addClassesWithSuperclasses(classesUsedInSubstitutions);
3197
3198 // 3. Add classes that contain checked generic function types. These are
3199 // needed to store the signature encoding.
3200 for (FunctionType type in checkedFunctionTypes) {
3201 ClassElement contextClass = Types.getClassContext(type);
3202 if (contextClass != null) {
3203 rtiNeededClasses.add(contextClass);
3204 }
3205 }
3206
3207 return rtiNeededClasses;
3208 }
3209
3158 // Optimize performance critical one shot interceptors. 3210 // Optimize performance critical one shot interceptors.
3159 jsAst.Statement tryOptimizeOneShotInterceptor(Selector selector, 3211 jsAst.Statement tryOptimizeOneShotInterceptor(Selector selector,
3160 Set<ClassElement> classes) { 3212 Set<ClassElement> classes) {
3161 jsAst.Expression isNumber(String variable) { 3213 jsAst.Expression isNumber(String variable) {
3162 return js('typeof $variable == "number"'); 3214 return js('typeof $variable == "number"');
3163 } 3215 }
3164 3216
3165 jsAst.Expression isNotObject(String variable) { 3217 jsAst.Expression isNotObject(String variable) {
3166 return js('typeof $variable != "object"'); 3218 return js('typeof $variable != "object"');
3167 } 3219 }
(...skipping 739 matching lines...) Expand 10 before | Expand all | Expand 10 after
3907 3959
3908 const String HOOKS_API_USAGE = """ 3960 const String HOOKS_API_USAGE = """
3909 // The code supports the following hooks: 3961 // The code supports the following hooks:
3910 // dartPrint(message) - if this function is defined it is called 3962 // dartPrint(message) - if this function is defined it is called
3911 // instead of the Dart [print] method. 3963 // instead of the Dart [print] method.
3912 // dartMainRunner(main) - if this function is defined, the Dart [main] 3964 // dartMainRunner(main) - if this function is defined, the Dart [main]
3913 // method will not be invoked directly. 3965 // method will not be invoked directly.
3914 // Instead, a closure that will invoke [main] is 3966 // Instead, a closure that will invoke [main] is
3915 // passed to [dartMainRunner]. 3967 // passed to [dartMainRunner].
3916 """; 3968 """;
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698