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

Side by Side Diff: frog/leg/native_emitter.dart

Issue 9750003: Write our JS blobs for handling native classes in Dart. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 years, 9 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 class NativeEmitter { 5 class NativeEmitter {
6 6
7 Compiler compiler; 7 Compiler compiler;
8 bool addedDynamicFunction = false; 8 StringBuffer buffer;
9 bool addedTypeNameOfFunction = false;
10 bool addedDefPropFunction = false;
11 bool addedNativeProperty = false;
12 9
13 // Classes that participate in dynamic dispatch. These are the 10 // Classes that participate in dynamic dispatch. These are the
14 // classes that contain used members. 11 // classes that contain used members.
15 Set<ClassElement> classesWithDynamicDispatch; 12 Set<ClassElement> classesWithDynamicDispatch;
16 13
17 // Native classes found in the application. 14 // Native classes found in the application.
18 Set<ClassElement> nativeClasses; 15 Set<ClassElement> nativeClasses;
19 16
20 // Caches the direct native subclasses of a native class. 17 // Caches the direct native subclasses of a native class.
21 Map<ClassElement, List<ClassElement>> subclasses; 18 Map<ClassElement, List<ClassElement>> subclasses;
22 19
23 // Caches the native methods that are overridden by a native class. 20 // Caches the native methods that are overridden by a native class.
24 // Note that the method that overrides does not have to be native: 21 // Note that the method that overrides does not have to be native:
25 // it's the overridden method that must make sure it will dispatch 22 // it's the overridden method that must make sure it will dispatch
26 // to its subclass if it sees an instance whose class is a subclass. 23 // to its subclass if it sees an instance whose class is a subclass.
27 Set<FunctionElement> overriddenMethods; 24 Set<FunctionElement> overriddenMethods;
28 25
29 NativeEmitter(this.compiler) 26 NativeEmitter(this.compiler)
30 : classesWithDynamicDispatch = new Set<ClassElement>(), 27 : classesWithDynamicDispatch = new Set<ClassElement>(),
31 nativeClasses = new Set<ClassElement>(), 28 nativeClasses = new Set<ClassElement>(),
32 subclasses = new Map<ClassElement, List<ClassElement>>(), 29 subclasses = new Map<ClassElement, List<ClassElement>>(),
33 overriddenMethods = new Set<FunctionElement>(); 30 overriddenMethods = new Set<FunctionElement>(),
31 buffer = new StringBuffer();
34 32
35 /** 33 String get dynamicName() {
36 * Code for finding the type name of a JavaScript object. 34 Element element = compiler.findHelper(
37 */ 35 const SourceString('dynamicFunction'));
38 static final String TYPE_NAME_OF_FUNCTION = @""" 36 return compiler.namer.isolateAccess(element);
39 (function() {
40 function constructorNameWithFallback(obj) {
41 var constructor = obj.constructor;
42 if (typeof(constructor) == 'function') {
43 // The constructor isn't null or undefined at this point. Try
44 // to grab hold of its name.
45 var name = constructor.name;
46 // If the name is a non-empty string, we use that as the type
47 // name of this object. On Firefox, we often get 'Object' as
48 // the constructor name even for more specialized objects so
49 // we have to fall through to the toString() based implementation
50 // below in that case.
51 if (typeof(name) == 'string' && name && name != 'Object') return name;
52 }
53 var string = Object.prototype.toString.call(obj);
54 return string.substring(8, string.length - 1);
55 } 37 }
56 38
57 function chrome$typeNameOf(obj) { 39 String get dynamicSetMetadataName() {
58 var name = obj.constructor.name; 40 Element element = compiler.findHelper(
59 if (name == 'Window') return 'DOMWindow'; 41 const SourceString('dynamicSetMetadata'));
60 return name; 42 return compiler.namer.isolateAccess(element);
61 } 43 }
62 44
63 function firefox$typeNameOf(obj) { 45 String get typeNameOfName() {
64 var name = constructorNameWithFallback(obj); 46 Element element = compiler.findHelper(
65 if (name == 'Window') return 'DOMWindow'; 47 const SourceString('getTypeNameOf'));
66 if (name == 'Document') return 'HTMLDocument'; 48 return compiler.namer.isolateAccess(element);
67 if (name == 'XMLDocument') return 'Document';
68 return name;
69 } 49 }
70 50
71 function ie$typeNameOf(obj) { 51 String get dynamicIsCheckName() {
72 var name = constructorNameWithFallback(obj); 52 Element element = compiler.findHelper(
73 if (name == 'Window') return 'DOMWindow'; 53 const SourceString('dynamicIsCheck'));
74 // IE calls both HTML and XML documents 'Document', so we check for the 54 return compiler.namer.isolateAccess(element);
75 // xmlVersion property, which is the empty string on HTML documents.
76 if (name == 'Document' && obj.xmlVersion) return 'Document';
77 if (name == 'Document') return 'HTMLDocument';
78 return name;
79 } 55 }
80 56
81 // If we're not in the browser, we're almost certainly running on v8. 57 String get isChecksHelperName() {
82 if (typeof(navigator) != 'object') return chrome$typeNameOf; 58 Element element = compiler.findHelper(
83 59 const SourceString('isChecksHelper'));
84 var userAgent = navigator.userAgent; 60 return compiler.namer.isolateAccess(element);
85 if (/Chrome|DumpRenderTree/.test(userAgent)) return chrome$typeNameOf;
86 if (/Firefox/.test(userAgent)) return firefox$typeNameOf;
87 if (/MSIE/.test(userAgent)) return ie$typeNameOf;
88 return constructorNameWithFallback;
89 })()""";
90
91 /**
92 * Code for defining a property in a JavaScript object that will not
93 * be visible through for-in (aka enumerable is false).
94 */
95 static final String DEF_PROP_FUNCTION = '''
96 function(obj, prop, value) {
97 Object.defineProperty(obj, prop,
98 {value: value, enumerable: false, writable: true, configurable: true});
99 }''';
100
101 /**
102 * Code for doing the dynamic dispatch on JavaScript prototypes that are not
103 * available at compile-time. Each property of a native Dart class
104 * is registered through this function, which is called with the
105 * following pattern:
106 *
107 * $dynamic('propertyName').prototypeName = // JS code
108 *
109 * What this function does is:
110 * - Creates a map of { prototypeName: JS code }.
111 * - Attaches 'propertyName' to the JS Object prototype that will
112 * intercept at runtime all calls to propertyName.
113 * - Sets the value of 'propertyName' to a function that queries the
114 * map with the prototype of 'this', patches the prototype of
115 * 'this' with the found JS code, and invokes the JS code.
116 *
117 */
118 String buildDynamicFunctionCode() {
119 ClassElement noSuchMethodException =
120 compiler.coreLibrary.find(Compiler.NO_SUCH_METHOD_EXCEPTION);
121 Element helper = compiler.findHelper(new SourceString('captureStackTrace'));
122 String capture = compiler.namer.isolateAccess(helper);
123 String exception = compiler.namer.isolateAccess(noSuchMethodException);
124
125 return '''
126 function(name) {
127 var f = Object.prototype[name];
128 if (f && f.methods) return f.methods;
129
130 var methods = {};
131 // If there is a method attached to the Dart Object class, use it as
132 // the method to call in case no method is registered for that type.
133 var dartMethod = ${compiler.emitter.objectClassName}.prototype[name];
134 if (dartMethod) methods.Object = dartMethod;
135 function dynamicBind() {
136 // Find the target method
137 var obj = this;
138 var tag = $typeNameOfName(obj);
139 var method = methods[tag];
140 if (!method) {
141 var table = $dynamicMetadataName;
142 for (var i = 0; i < table.length; i++) {
143 var entry = table[i];
144 if (entry.map.hasOwnProperty(tag)) {
145 method = methods[entry.tag];
146 if (method) break;
147 }
148 }
149 }
150 method = method || methods.Object;
151
152 if (method == null) {
153 method = function() {
154 throw $capture(new $exception(obj, name, arguments));
155 };
156 }
157
158 var proto = Object.getPrototypeOf(obj);
159 var nullCheckMethod = function() {
160 var res = method.apply(this, Array.prototype.slice.call(arguments));
161 return res === null ? (void 0) : res;
162 }
163 if (!proto.hasOwnProperty(name)) {
164 $defPropName(proto, name, nullCheckMethod);
165 }
166
167 return nullCheckMethod.apply(this, Array.prototype.slice.call(arguments));
168 };
169 dynamicBind.methods = methods;
170 $defPropName(Object.prototype, name, dynamicBind);
171 return methods;
172 }''';
173 }
174
175 String buildDynamicMetadataCode() => '''
176 if (typeof $dynamicMetadataName == 'undefined') $dynamicMetadataName = [];''';
177
178 // This method will be called for 'is' checks on native types.
179 // It takes the object on which the 'is' check is being done, and the
180 // property name for the type check. The method patches the real
181 // prototype of the object with the value from the Dart object
182 // (see [generateNativeClass]).
183 String buildDynamicIsCheckCode() {
184 ClassElement objectClass =
185 compiler.coreLibrary.find(const SourceString('Object'));
186 return '''
187 function(obj, isCheck) {
188 if (obj.constructor === Array) return false;
189 var proto = Object.getPrototypeOf(obj);
190 // Check if the Dart object corresponding to this class has the property.
191 var res =
192 !!${compiler.namer.CURRENT_ISOLATE}.native[$typeNameOfName(obj)][isCheck];
193 res = res || false;
194 $defPropName(proto, isCheck, res);
195 return res;
196 }''';
197 } 61 }
198 62
199 String buildNativePropertyCode() => ''' 63 void generateNativeLiteral(ClassElement classElement) {
200 ${compiler.namer.ISOLATE}.prototype.native = {};''';
201
202 String buildDynamicSetMetadataCode() => """
203 function(inputTable) {
204 // TODO: Deal with light isolates.
205 var table = [];
206 for (var i = 0; i < inputTable.length; i++) {
207 var tag = inputTable[i][0];
208 var tags = inputTable[i][1];
209 var map = {};
210 var tagNames = tags.split('|');
211 for (var j = 0; j < tagNames.length; j++) {
212 map[tagNames[j]] = true;
213 }
214 table.push({tag: tag, tags: tags, map: map});
215 }
216 $dynamicMetadataName = table;
217 }""";
218
219
220 String get dynamicName() => '${compiler.namer.ISOLATE}.\$dynamic';
221 String get defPropName() => '${compiler.namer.ISOLATE}.\$defProp';
222 String get typeNameOfName() => '${compiler.namer.ISOLATE}.\$typeNameOf';
223 String get dynamicMetadataName() =>
224 '${compiler.namer.ISOLATE}.\$dynamicMetatada';
225 String get dynamicIsCheckName() =>
226 '${compiler.namer.ISOLATE}.\$dynamicIsCheck';
227 String get dynamicSetMetadataName() =>
228 '${compiler.namer.ISOLATE}.\$dynamicSetMetatada';
229
230 void addDynamicFunctionIfNecessary(StringBuffer buffer) {
231 if (addedDynamicFunction) return;
232 addedDynamicFunction = true;
233 addTypeNameOfFunctionIfNecessary(buffer);
234 buffer.add('$dynamicName = ');
235 buffer.add(buildDynamicFunctionCode());
236 buffer.add('\n');
237 buffer.add(buildDynamicMetadataCode());
238 buffer.add('\n');
239 }
240
241 void addTypeNameOfFunctionIfNecessary(StringBuffer buffer) {
242 if (addedTypeNameOfFunction) return;
243 addedTypeNameOfFunction = true;
244 addDefPropFunctionIfNecessary(buffer);
245 buffer.add('$typeNameOfName = ');
246 buffer.add(TYPE_NAME_OF_FUNCTION);
247 buffer.add('\n');
248 }
249
250 void addDefPropFunctionIfNecessary(StringBuffer buffer) {
251 if (addedDefPropFunction) return;
252 addedDefPropFunction = true;
253 buffer.add('$defPropName = ');
254 buffer.add(DEF_PROP_FUNCTION);
255 buffer.add('\n');
256 }
257
258 void addNativePropertyIfNecessary(StringBuffer buffer) {
259 if (addedNativeProperty) return;
260 addedNativeProperty = true;
261 buffer.add(buildNativePropertyCode());
262 buffer.add('\n');
263 }
264
265 void generateNativeLiteral(ClassElement classElement, StringBuffer buffer) {
266 String quotedNative = classElement.nativeName.slowToString(); 64 String quotedNative = classElement.nativeName.slowToString();
267 String nativeCode = quotedNative.substring(2, quotedNative.length - 1); 65 String nativeCode = quotedNative.substring(2, quotedNative.length - 1);
268 String className = compiler.namer.getName(classElement); 66 String className = compiler.namer.getName(classElement);
269 buffer.add(className); 67 buffer.add(className);
270 buffer.add(' = '); 68 buffer.add(' = ');
271 buffer.add(nativeCode); 69 buffer.add(nativeCode);
272 buffer.add(';\n'); 70 buffer.add(';\n');
273 71
274 String attachTo(name) => "$className.$name"; 72 String attachTo(name) => "$className.$name";
275 73
(...skipping 16 matching lines...) Expand all
292 String toNativeName(ClassElement cls) { 90 String toNativeName(ClassElement cls) {
293 String quotedName = cls.nativeName.slowToString(); 91 String quotedName = cls.nativeName.slowToString();
294 if (isNativeGlobal(quotedName)) { 92 if (isNativeGlobal(quotedName)) {
295 // Global object, just be like the other types for now. 93 // Global object, just be like the other types for now.
296 return quotedName.substring(3, quotedName.length - 1); 94 return quotedName.substring(3, quotedName.length - 1);
297 } else { 95 } else {
298 return quotedName.substring(2, quotedName.length - 1); 96 return quotedName.substring(2, quotedName.length - 1);
299 } 97 }
300 } 98 }
301 99
302 void generateNativeClass(ClassElement classElement, StringBuffer buffer) { 100 void generateNativeClass(ClassElement classElement) {
303 nativeClasses.add(classElement); 101 nativeClasses.add(classElement);
304 102
305 assert(classElement.backendMembers.isEmpty()); 103 assert(classElement.backendMembers.isEmpty());
306 String quotedName = classElement.nativeName.slowToString(); 104 String quotedName = classElement.nativeName.slowToString();
307 if (isNativeLiteral(quotedName)) { 105 if (isNativeLiteral(quotedName)) {
308 generateNativeLiteral(classElement, buffer); 106 generateNativeLiteral(classElement);
309 // The native literal kind needs to be dealt with specially when 107 // The native literal kind needs to be dealt with specially when
310 // generating code for it. 108 // generating code for it.
311 return; 109 return;
312 } 110 }
313 111
314 String nativeName = toNativeName(classElement); 112 String nativeName = toNativeName(classElement);
315 bool hasUsedSelectors = false; 113 bool hasUsedSelectors = false;
316 114
317 String attachTo(String name) { 115 String attachTo(String name) {
318 hasUsedSelectors = true; 116 hasUsedSelectors = true;
319 addDynamicFunctionIfNecessary(buffer);
320 return "$dynamicName('$name').$nativeName"; 117 return "$dynamicName('$name').$nativeName";
321 } 118 }
322 119
323 for (Element member in classElement.members) { 120 for (Element member in classElement.members) {
324 if (member.isInstanceMember()) { 121 if (member.isInstanceMember()) {
325 compiler.emitter.addInstanceMember( 122 compiler.emitter.addInstanceMember(
326 member, attachTo, buffer, isNative: true); 123 member, attachTo, buffer, isNative: true);
327 } 124 }
328 } 125 }
329 126
330 addNativePropertyIfNecessary(buffer); 127 // Create an object that contains the is checks properties.
331 // Create an object that contains the is checks properties. The 128 buffer.add('$isChecksHelperName.$nativeName = { ');
332 // object will be used when entering [buildDynamicIsCheckCode].
333 buffer.add('${compiler.namer.ISOLATE}.prototype.native.$nativeName = { ');
334 List<String> tests = <String>[]; 129 List<String> tests = <String>[];
335 130
336 ClassElement objectClass = 131 ClassElement objectClass =
337 compiler.coreLibrary.find(const SourceString('Object')); 132 compiler.coreLibrary.find(const SourceString('Object'));
338 ClassElement element = classElement; 133 ClassElement element = classElement;
339 // We need to put the super class is checks too, since a check on 134 // We need to put the super class is checks too, since a check on
340 // the subclass can happen before a check on the super class 135 // the subclass can happen before a check on the super class
341 // (which does the patching on the prototype). 136 // (which does the patching on the prototype).
342 do { 137 do {
343 compiler.emitter.generateTypeTests(element, (Element other) { 138 compiler.emitter.generateTypeTests(element, (Element other) {
(...skipping 11 matching lines...) Expand all
355 List<ClassElement> getDirectSubclasses(ClassElement cls) { 150 List<ClassElement> getDirectSubclasses(ClassElement cls) {
356 List<ClassElement> result = subclasses[cls]; 151 List<ClassElement> result = subclasses[cls];
357 if (result === null) result = const<ClassElement>[]; 152 if (result === null) result = const<ClassElement>[];
358 return result; 153 return result;
359 } 154 }
360 155
361 void emitParameterStub(Element member, 156 void emitParameterStub(Element member,
362 String invocationName, 157 String invocationName,
363 String stubParameters, 158 String stubParameters,
364 List<String> argumentsBuffer, 159 List<String> argumentsBuffer,
365 int indexOfLastOptionalArgumentInParameters, 160 int indexOfLastOptionalArgumentInParameters) {
366 StringBuffer buffer) {
367 // The target JS function may check arguments.length so we need to 161 // The target JS function may check arguments.length so we need to
368 // make sure not to pass any unspecified optional arguments to it. 162 // make sure not to pass any unspecified optional arguments to it.
369 // For example, for the following Dart method: 163 // For example, for the following Dart method:
370 // foo([x, y, z]); 164 // foo([x, y, z]);
371 // The call: 165 // The call:
372 // foo(y: 1) 166 // foo(y: 1)
373 // must be turned into a JS call to: 167 // must be turned into a JS call to:
374 // foo(null, y). 168 // foo(null, y).
375 169
376 List<String> nativeArgumentsBuffer = argumentsBuffer.getRange( 170 List<String> nativeArgumentsBuffer = argumentsBuffer.getRange(
(...skipping 20 matching lines...) Expand all
397 buffer.add(' if (Object.getPrototypeOf(this).hasOwnProperty('); 191 buffer.add(' if (Object.getPrototypeOf(this).hasOwnProperty(');
398 buffer.add("'$invocationName')) {\n"); 192 buffer.add("'$invocationName')) {\n");
399 buffer.add(' return this.${member.name.slowToString()}'); 193 buffer.add(' return this.${member.name.slowToString()}');
400 buffer.add('($nativeArguments)'); 194 buffer.add('($nativeArguments)');
401 buffer.add('\n }\n'); 195 buffer.add('\n }\n');
402 buffer.add(' return Object.prototype.$invocationName.call(this'); 196 buffer.add(' return Object.prototype.$invocationName.call(this');
403 buffer.add(stubParameters == '' ? '' : ', $stubParameters'); 197 buffer.add(stubParameters == '' ? '' : ', $stubParameters');
404 buffer.add(');'); 198 buffer.add(');');
405 } 199 }
406 200
407 void emitDynamicDispatchMetadata(StringBuffer buffer) { 201 void emitDynamicDispatchMetadata() {
408 // TODO(ngeoffray): emit this conditionally.
409 addTypeNameOfFunctionIfNecessary(buffer);
410 buffer.add('$dynamicIsCheckName = ');
411 buffer.add(buildDynamicIsCheckCode());
412 buffer.add('\n');
413
414 if (classesWithDynamicDispatch.isEmpty()) return; 202 if (classesWithDynamicDispatch.isEmpty()) return;
415 buffer.add('// ${classesWithDynamicDispatch.length} dynamic classes.\n'); 203 buffer.add('// ${classesWithDynamicDispatch.length} dynamic classes.\n');
416 204
417 // Build a pre-order traversal over all the classes and their subclasses. 205 // Build a pre-order traversal over all the classes and their subclasses.
418 Set<ClassElement> seen = new Set<ClassElement>(); 206 Set<ClassElement> seen = new Set<ClassElement>();
419 List<ClassElement> classes = <ClassElement>[]; 207 List<ClassElement> classes = <ClassElement>[];
420 void visit(ClassElement cls) { 208 void visit(ClassElement cls) {
421 if (seen.contains(cls)) return; 209 if (seen.contains(cls)) return;
422 seen.add(cls); 210 seen.add(cls);
423 for (final ClassElement subclass in getDirectSubclasses(cls)) { 211 for (final ClassElement subclass in getDirectSubclasses(cls)) {
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after
495 return expression; 283 return expression;
496 } 284 }
497 285
498 for (final ClassElement cls in dispatchClasses) { 286 for (final ClassElement cls in dispatchClasses) {
499 tagDefns[cls] = makeExpression(cls); 287 tagDefns[cls] = makeExpression(cls);
500 } 288 }
501 289
502 // Write out a thunk that builds the metadata. 290 // Write out a thunk that builds the metadata.
503 291
504 if (!tagDefns.isEmpty()) { 292 if (!tagDefns.isEmpty()) {
505 buffer.add('$dynamicSetMetadataName = ');
506 buffer.add(buildDynamicSetMetadataCode());
507 buffer.add(';\n\n');
508
509 buffer.add('(function(){\n'); 293 buffer.add('(function(){\n');
510 294
511 for (final String varName in varNames) { 295 for (final String varName in varNames) {
512 buffer.add(' var ${varName} = ${varDefns[varName]};\n'); 296 buffer.add(' var ${varName} = ${varDefns[varName]};\n');
513 } 297 }
514 298
515 buffer.add(' var table = [\n'); 299 buffer.add(' var table = [\n');
516 buffer.add( 300 buffer.add(
517 ' // [dynamic-dispatch-tag, ' 301 ' // [dynamic-dispatch-tag, '
518 'tags of classes implementing dynamic-dispatch-tag]'); 302 'tags of classes implementing dynamic-dispatch-tag]');
519 bool needsComma = false; 303 bool needsComma = false;
520 List<String> entries = <String>[]; 304 List<String> entries = <String>[];
521 for (final ClassElement cls in dispatchClasses) { 305 for (final ClassElement cls in dispatchClasses) {
522 String clsName = toNativeName(cls); 306 String clsName = toNativeName(cls);
523 entries.add("\n ['$clsName', ${tagDefns[cls]}]"); 307 entries.add("\n ['$clsName', ${tagDefns[cls]}]");
524 } 308 }
525 buffer.add(Strings.join(entries, ',')); 309 buffer.add(Strings.join(entries, ','));
526 buffer.add('];\n'); 310 buffer.add('];\n');
527 buffer.add('$dynamicSetMetadataName(table);\n'); 311 buffer.add('$dynamicSetMetadataName(table);\n');
528 312
529 buffer.add('})();\n'); 313 buffer.add('})();\n');
530 } 314 }
531 } 315 }
316
317 void assembleCode(StringBuffer other) {
318 other.add('(function() { $isChecksHelperName = {};\n$buffer\n })();\n');
319 }
532 } 320 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698