OLD | NEW |
| (Empty) |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 #library('native'); | |
6 #import('../../lib/uri/uri.dart'); | |
7 #import('leg.dart'); | |
8 #import('elements/elements.dart'); | |
9 #import('scanner/scannerlib.dart'); | |
10 #import('ssa/ssa.dart'); | |
11 #import('tree/tree.dart'); | |
12 #import('util/util.dart'); | |
13 | |
14 void processNativeClasses(Compiler compiler, | |
15 Collection<LibraryElement> libraries) { | |
16 for (LibraryElement library in libraries) { | |
17 processNativeClassesInLibrary(compiler, library); | |
18 } | |
19 } | |
20 | |
21 void addSubtypes(ClassElement cls, | |
22 NativeEmitter emitter) { | |
23 for (Type type in cls.allSupertypes) { | |
24 List<Element> subtypes = emitter.subtypes.putIfAbsent( | |
25 type.element, | |
26 () => <ClassElement>[]); | |
27 subtypes.add(cls); | |
28 } | |
29 } | |
30 | |
31 void processNativeClassesInLibrary(Compiler compiler, | |
32 LibraryElement library) { | |
33 bool hasNativeClass = false; | |
34 for (Link<Element> link = library.topLevelElements; | |
35 !link.isEmpty(); link = link.tail) { | |
36 Element element = link.head; | |
37 if (element.kind == ElementKind.CLASS) { | |
38 ClassElement classElement = element; | |
39 if (classElement.isNative()) { | |
40 hasNativeClass = true; | |
41 compiler.registerInstantiatedClass(classElement); | |
42 // Also parse the node to know all its methods because | |
43 // otherwise it will only be parsed if there is a call to | |
44 // one of its constructor. | |
45 classElement.parseNode(compiler); | |
46 // Resolve to setup the inheritance. | |
47 classElement.ensureResolved(compiler); | |
48 // Add the information that this class is a subtype of | |
49 // its supertypes. The code emitter and the ssa builder use that | |
50 // information. | |
51 NativeEmitter emitter = compiler.emitter.nativeEmitter; | |
52 addSubtypes(classElement, emitter); | |
53 } | |
54 } | |
55 } | |
56 if (hasNativeClass) { | |
57 compiler.registerStaticUse(compiler.findHelper( | |
58 const SourceString('dynamicFunction'))); | |
59 compiler.registerStaticUse(compiler.findHelper( | |
60 const SourceString('dynamicSetMetadata'))); | |
61 compiler.registerStaticUse(compiler.findHelper( | |
62 const SourceString('defineProperty'))); | |
63 compiler.registerStaticUse(compiler.findHelper( | |
64 const SourceString('toStringForNativeObject'))); | |
65 } | |
66 } | |
67 | |
68 void maybeEnableNative(Compiler compiler, | |
69 LibraryElement library, | |
70 Uri uri) { | |
71 String libraryName = uri.toString(); | |
72 if (library.script.name.contains('dart/frog/tests/native/src') | |
73 || libraryName == 'dart:dom' | |
74 || libraryName == 'dart:isolate' | |
75 || libraryName == 'dart:html') { | |
76 library.define(new ForeignElement( | |
77 const SourceString('native'), library), compiler); | |
78 library.canUseNative = true; | |
79 } | |
80 | |
81 // Additionaly, if this is a test, we allow access to foreign functions. | |
82 if (library.script.name.contains('dart/frog/tests/native/src')) { | |
83 library.define(compiler.findHelper(const SourceString('JS')), compiler); | |
84 } | |
85 } | |
86 | |
87 void checkAllowedLibrary(ElementListener listener, Token token) { | |
88 LibraryElement currentLibrary = listener.compilationUnitElement.getLibrary(); | |
89 if (!currentLibrary.canUseNative) { | |
90 listener.recoverableError("Unexpected token", token: token); | |
91 } | |
92 } | |
93 | |
94 Token handleNativeBlockToSkip(Listener listener, Token token) { | |
95 checkAllowedLibrary(listener, token); | |
96 token = token.next; | |
97 if (token.kind === STRING_TOKEN) { | |
98 token = token.next; | |
99 } | |
100 if (token.stringValue === '{') { | |
101 BeginGroupToken beginGroupToken = token; | |
102 token = beginGroupToken.endGroup; | |
103 } | |
104 return token; | |
105 } | |
106 | |
107 Token handleNativeClassBodyToSkip(Listener listener, Token token) { | |
108 checkAllowedLibrary(listener, token); | |
109 listener.handleIdentifier(token); | |
110 token = token.next; | |
111 if (token.kind !== STRING_TOKEN) { | |
112 return listener.unexpected(token); | |
113 } | |
114 token = token.next; | |
115 if (token.stringValue !== '{') { | |
116 return listener.unexpected(token); | |
117 } | |
118 BeginGroupToken beginGroupToken = token; | |
119 token = beginGroupToken.endGroup; | |
120 return token; | |
121 } | |
122 | |
123 Token handleNativeClassBody(Listener listener, Token token) { | |
124 checkAllowedLibrary(listener, token); | |
125 token = token.next; | |
126 if (token.kind !== STRING_TOKEN) { | |
127 listener.unexpected(token); | |
128 } else { | |
129 token = token.next; | |
130 } | |
131 return token; | |
132 } | |
133 | |
134 Token handleNativeFunctionBody(ElementListener listener, Token token) { | |
135 checkAllowedLibrary(listener, token); | |
136 Token begin = token; | |
137 listener.beginExpressionStatement(token); | |
138 listener.handleIdentifier(token); | |
139 token = token.next; | |
140 if (token.kind === STRING_TOKEN) { | |
141 listener.beginLiteralString(token); | |
142 listener.endLiteralString(0); | |
143 listener.pushNode(new NodeList.singleton(listener.popNode())); | |
144 listener.endSend(token); | |
145 token = token.next; | |
146 listener.endExpressionStatement(token); | |
147 } else { | |
148 listener.pushNode(new NodeList.empty()); | |
149 listener.endSend(token); | |
150 listener.endReturnStatement(true, begin, token); | |
151 } | |
152 listener.endFunctionBody(1, begin, token); | |
153 // TODO(ngeoffray): expect a ';'. | |
154 return token.next; | |
155 } | |
156 | |
157 SourceString checkForNativeClass(ElementListener listener) { | |
158 SourceString nativeName; | |
159 Node node = listener.nodes.head; | |
160 if (node != null | |
161 && node.asIdentifier() != null | |
162 && node.asIdentifier().source.stringValue == 'native') { | |
163 nativeName = node.asIdentifier().token.next.value; | |
164 listener.popNode(); | |
165 } | |
166 return nativeName; | |
167 } | |
168 | |
169 bool isOverriddenMethod(FunctionElement element, | |
170 ClassElement cls, | |
171 NativeEmitter nativeEmitter) { | |
172 List<ClassElement> subtypes = nativeEmitter.subtypes[cls]; | |
173 if (subtypes == null) return false; | |
174 for (ClassElement subtype in subtypes) { | |
175 if (subtype.lookupLocalMember(element.name) != null) return true; | |
176 } | |
177 return false; | |
178 } | |
179 | |
180 void handleSsaNative(SsaBuilder builder, Send node) { | |
181 // Register NoSuchMethodException and captureStackTrace in the compiler | |
182 // because the dynamic dispatch for native classes may use them. | |
183 Compiler compiler = builder.compiler; | |
184 ClassElement cls = compiler.coreLibrary.find( | |
185 Compiler.NO_SUCH_METHOD_EXCEPTION); | |
186 cls.ensureResolved(compiler); | |
187 compiler.addToWorkList(cls.lookupConstructor(cls.name)); | |
188 compiler.registerStaticUse( | |
189 compiler.findHelper(new SourceString('captureStackTrace'))); | |
190 | |
191 FunctionElement element = builder.work.element; | |
192 element.setNative(); | |
193 NativeEmitter nativeEmitter = compiler.emitter.nativeEmitter; | |
194 // If what we're compiling is a getter named 'typeName' and the native | |
195 // class is named 'DOMType', we generate a call to the typeNameOf | |
196 // function attached on the isolate. | |
197 // The DOM classes assume that their 'typeName' property, which is | |
198 // not a JS property on the DOM types, returns the type name. | |
199 if (element.name == const SourceString('typeName') | |
200 && element.isGetter() | |
201 && nativeEmitter.toNativeName(element.enclosingElement) == 'DOMType') { | |
202 DartString jsCode = new DartString.literal( | |
203 '${nativeEmitter.typeNameOfName}(#)'); | |
204 List<HInstruction> inputs = | |
205 <HInstruction>[builder.localsHandler.readThis()]; | |
206 builder.push(new HForeign( | |
207 jsCode, const LiteralDartString('String'), inputs)); | |
208 return; | |
209 } | |
210 | |
211 HInstruction convertDartClosure(Element parameter) { | |
212 HInstruction local = builder.localsHandler.readLocal(parameter); | |
213 // TODO(ngeoffray): by better analyzing the function type and | |
214 // its formal parameters, we could just pass, eg closure.$call$0. | |
215 builder.push(new HStatic(builder.interceptors.getClosureConverter())); | |
216 List<HInstruction> callInputs = <HInstruction>[builder.pop(), local]; | |
217 HInstruction closure = new HInvokeStatic(Selector.INVOCATION_1, callInputs); | |
218 builder.add(closure); | |
219 return closure; | |
220 } | |
221 | |
222 FunctionParameters parameters = element.computeParameters(builder.compiler); | |
223 if (node.arguments.isEmpty()) { | |
224 List<String> arguments = <String>[]; | |
225 List<HInstruction> inputs = <HInstruction>[]; | |
226 String receiver = ''; | |
227 if (element.isInstanceMember()) { | |
228 receiver = '#.'; | |
229 inputs.add(builder.localsHandler.readThis()); | |
230 } | |
231 parameters.forEachParameter((Element parameter) { | |
232 Type type = parameter.computeType(compiler); | |
233 HInstruction input = builder.localsHandler.readLocal(parameter); | |
234 if (type is FunctionType) input = convertDartClosure(parameter); | |
235 inputs.add(input); | |
236 arguments.add('#'); | |
237 }); | |
238 String foreignParameters = Strings.join(arguments, ','); | |
239 | |
240 String dartMethodName; | |
241 String nativeMethodName = element.name.slowToString(); | |
242 String nativeMethodCall; | |
243 | |
244 if (element.kind == ElementKind.FUNCTION) { | |
245 dartMethodName = builder.compiler.namer.instanceMethodName( | |
246 element.getLibrary(), element.name, parameters.parameterCount); | |
247 nativeMethodCall = '$receiver$nativeMethodName($foreignParameters)'; | |
248 } else if (element.kind == ElementKind.GETTER) { | |
249 dartMethodName = builder.compiler.namer.getterName( | |
250 element.getLibrary(), element.name); | |
251 nativeMethodCall = '$receiver$nativeMethodName'; | |
252 } else if (element.kind == ElementKind.SETTER) { | |
253 dartMethodName = builder.compiler.namer.setterName( | |
254 element.getLibrary(), element.name); | |
255 nativeMethodCall = '$receiver$nativeMethodName = $foreignParameters'; | |
256 } else { | |
257 builder.compiler.internalError('unexpected kind: "${element.kind}"', | |
258 element: element); | |
259 } | |
260 | |
261 HInstruction thenInstruction; | |
262 void visitThen() { | |
263 DartString jsCode = new DartString.literal(nativeMethodCall); | |
264 thenInstruction = | |
265 new HForeign(jsCode, const LiteralDartString('Object'), inputs); | |
266 builder.add(thenInstruction); | |
267 } | |
268 | |
269 bool isNativeLiteral = false; | |
270 bool isOverridden = false; | |
271 NativeEmitter nativeEmitter = builder.compiler.emitter.nativeEmitter; | |
272 if (element.enclosingElement.kind == ElementKind.CLASS) { | |
273 ClassElement classElement = element.enclosingElement; | |
274 String nativeName = classElement.nativeName.slowToString(); | |
275 isNativeLiteral = nativeEmitter.isNativeLiteral(nativeName); | |
276 isOverridden = isOverriddenMethod(element, classElement, nativeEmitter); | |
277 } | |
278 if (!element.isInstanceMember() || isNativeLiteral || !isOverridden) { | |
279 // We generate a direct call to the native method. | |
280 visitThen(); | |
281 builder.stack.add(thenInstruction); | |
282 } else { | |
283 // Record that this method is overridden. In case of optional | |
284 // arguments, the emitter will generate stubs to handle them, | |
285 // and needs to know if the method is overridden. | |
286 nativeEmitter.overriddenMethods.add(element); | |
287 | |
288 // If the method is an instance method that is overridden, we | |
289 // generate the following code: | |
290 // function(params) { | |
291 // return Object.getPrototypeOf(this).hasOwnProperty(dartMethodName)) | |
292 // ? this.methodName(params) | |
293 // : Object.prototype.methodName.call(this, params); | |
294 // } | |
295 // | |
296 // The property check at the beginning is to make sure we won't | |
297 // call the method from the super class, in case the prototype of | |
298 // 'this' does not have the method yet. | |
299 HInstruction elseInstruction; | |
300 void visitElse() { | |
301 String params = arguments.isEmpty() ? '' : ', $foreignParameters'; | |
302 DartString jsCode = new DartString.literal( | |
303 'Object.prototype.$dartMethodName.call(#$params)'); | |
304 elseInstruction = | |
305 new HForeign(jsCode, const LiteralDartString('Object'), inputs); | |
306 builder.add(elseInstruction); | |
307 } | |
308 | |
309 HConstant constant = builder.graph.addConstantString( | |
310 new DartString.literal('$dartMethodName')); | |
311 DartString jsCode = new DartString.literal( | |
312 'Object.getPrototypeOf(#).hasOwnProperty(#)'); | |
313 builder.push(new HForeign( | |
314 jsCode, const LiteralDartString('Object'), | |
315 <HInstruction>[builder.localsHandler.readThis(), constant])); | |
316 | |
317 builder.handleIf(visitThen, visitElse); | |
318 | |
319 HPhi phi = new HPhi.manyInputs( | |
320 null, <HInstruction>[thenInstruction, elseInstruction]); | |
321 builder.current.addPhi(phi); | |
322 builder.stack.add(phi); | |
323 } | |
324 | |
325 } else if (!node.arguments.tail.isEmpty()) { | |
326 builder.compiler.cancel('More than one argument to native'); | |
327 } else { | |
328 // This is JS code written in a Dart file with the construct | |
329 // native """ ... """;. It does not work well with mangling, | |
330 // but there should currently be no clash between leg mangling | |
331 // and the library where this construct is being used. This | |
332 // mangling problem will go away once we switch these libraries | |
333 // to use Leg's 'JS' function. | |
334 parameters.forEachParameter((Element parameter) { | |
335 Type type = parameter.computeType(compiler); | |
336 if (type is FunctionType) { | |
337 HInstruction jsClosure = convertDartClosure(parameter); | |
338 // Because the JS code references the argument name directly, | |
339 // we must keep the name and assign the JS closure to it. | |
340 builder.add(new HForeign( | |
341 new DartString.literal('${parameter.name.slowToString()} = #'), | |
342 const LiteralDartString('void'), | |
343 <HInstruction>[jsClosure])); | |
344 } | |
345 }); | |
346 LiteralString jsCode = node.arguments.head; | |
347 builder.push(new HForeign(jsCode.dartString, | |
348 const LiteralDartString('Object'), | |
349 <HInstruction>[])); | |
350 } | |
351 } | |
OLD | NEW |