OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013, 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 part of dart2js.js_emitter; | |
6 | |
7 // TODO(ahe): Share these with js_helper.dart. | |
8 const FUNCTION_INDEX = 0; | |
9 const NAME_INDEX = 1; | |
10 const CALL_NAME_INDEX = 2; | |
11 const REQUIRED_PARAMETER_INDEX = 3; | |
12 const OPTIONAL_PARAMETER_INDEX = 4; | |
13 const DEFAULT_ARGUMENTS_INDEX = 5; | |
14 | |
15 const bool VALIDATE_DATA = false; | |
16 | |
17 // TODO(ahe): This code should be integrated in CodeEmitterTask.finishClasses. | |
18 jsAst.Expression getReflectionDataParser(String classesCollector, | |
19 JavaScriptBackend backend) { | |
20 Namer namer = backend.namer; | |
21 Compiler compiler = backend.compiler; | |
22 CodeEmitterTask emitter = backend.emitter; | |
23 | |
24 String metadataField = '"${namer.metadataField}"'; | |
25 String reflectableField = namer.reflectableField; | |
26 | |
27 // TODO(ahe): Move this string constants to namer. | |
28 String reflectionInfoField = r'$reflectionInfo'; | |
29 String reflectionNameField = r'$reflectionName'; | |
30 String metadataIndexField = r'$metadataIndex'; | |
31 | |
32 String defaultValuesField = namer.defaultValuesField; | |
33 String methodsWithOptionalArgumentsField = | |
34 namer.methodsWithOptionalArgumentsField; | |
35 | |
36 String unmangledNameIndex = backend.mustRetainMetadata | |
37 ? ' 3 * optionalParameterCount + 2 * requiredParameterCount + 3' | |
38 : ' 2 * optionalParameterCount + requiredParameterCount + 3'; | |
39 | |
40 jsAst.Expression typeInformationAccess = | |
41 emitter.generateEmbeddedGlobalAccess(embeddedNames.TYPE_INFORMATION); | |
42 jsAst.Expression globalFunctionsAccess = | |
43 emitter.generateEmbeddedGlobalAccess(embeddedNames.GLOBAL_FUNCTIONS); | |
44 jsAst.Expression staticsAccess = | |
45 emitter.generateEmbeddedGlobalAccess(embeddedNames.STATICS); | |
46 jsAst.Expression interceptedNamesAccess = | |
47 emitter.generateEmbeddedGlobalAccess(embeddedNames.INTERCEPTED_NAMES); | |
48 jsAst.Expression mangledGlobalNamesAccess = | |
49 emitter.generateEmbeddedGlobalAccess(embeddedNames.MANGLED_GLOBAL_NAMES); | |
50 jsAst.Expression mangledNamesAccess = | |
51 emitter.generateEmbeddedGlobalAccess(embeddedNames.MANGLED_NAMES); | |
52 jsAst.Expression librariesAccess = | |
53 emitter.generateEmbeddedGlobalAccess(embeddedNames.LIBRARIES); | |
54 | |
55 jsAst.Statement header = js.statement(''' | |
56 // [map] returns an object literal that V8 shouldn not try to optimize with a | |
57 // hidden class. This prevents a potential performance problem where V8 tries | |
58 // to build a hidden class for an object used as a hashMap. | |
59 // It requires fewer characters to declare a variable as a parameter than | |
60 // with `var`. | |
61 function map(x){x=Object.create(null);x.x=0;delete x.x;return x} | |
62 '''); | |
63 | |
64 jsAst.Statement processStatics = js.statement(''' | |
65 function processStatics(descriptor) { | |
66 for (var property in descriptor) { | |
67 if (!hasOwnProperty.call(descriptor, property)) continue; | |
68 if (property === "${namer.classDescriptorProperty}") continue; | |
69 var element = descriptor[property]; | |
70 var firstChar = property.substring(0, 1); | |
71 var previousProperty; | |
72 if (firstChar === "+") { | |
73 mangledGlobalNames[previousProperty] = property.substring(1); | |
74 var flag = descriptor[property]; | |
75 if (flag > 0) | |
76 descriptor[previousProperty].$reflectableField = flag; | |
77 if (element && element.length) | |
78 #[previousProperty] = element; // embedded typeInformation. | |
79 } else if (firstChar === "@") { | |
80 property = property.substring(1); | |
81 ${namer.currentIsolate}[property][$metadataField] = element; | |
82 } else if (firstChar === "*") { | |
83 globalObject[previousProperty].$defaultValuesField = element; | |
84 var optionalMethods = descriptor.$methodsWithOptionalArgumentsField; | |
85 if (!optionalMethods) { | |
86 descriptor.$methodsWithOptionalArgumentsField = optionalMethods = {} | |
87 } | |
88 optionalMethods[property] = previousProperty; | |
89 } else if (typeof element === "function") { | |
90 globalObject[previousProperty = property] = element; | |
91 functions.push(property); | |
92 #[property] = element; // embedded globalFunctions. | |
93 } else if (element.constructor === Array) { | |
94 addStubs(globalObject, element, property, | |
95 true, descriptor, functions); | |
96 } else { | |
97 previousProperty = property; | |
98 var newDesc = {}; | |
99 var previousProp; | |
100 for (var prop in element) { | |
101 if (!hasOwnProperty.call(element, prop)) continue; | |
102 firstChar = prop.substring(0, 1); | |
103 if (prop === "static") { | |
104 processStatics(#[property] = element[prop]); // embedded statics. | |
105 } else if (firstChar === "+") { | |
106 mangledNames[previousProp] = prop.substring(1); | |
107 var flag = element[prop]; | |
108 if (flag > 0) | |
109 element[previousProp].$reflectableField = flag; | |
110 } else if (firstChar === "@" && prop !== "@") { | |
111 newDesc[prop.substring(1)][$metadataField] = element[prop]; | |
112 } else if (firstChar === "*") { | |
113 newDesc[previousProp].$defaultValuesField = element[prop]; | |
114 var optionalMethods = newDesc.$methodsWithOptionalArgumentsField; | |
115 if (!optionalMethods) { | |
116 newDesc.$methodsWithOptionalArgumentsField = optionalMethods={} | |
117 } | |
118 optionalMethods[prop] = previousProp; | |
119 } else { | |
120 var elem = element[prop]; | |
121 if (prop !== "${namer.classDescriptorProperty}" && | |
122 elem != null && | |
123 elem.constructor === Array && | |
124 prop !== "<>") { | |
125 addStubs(newDesc, elem, prop, false, element, []); | |
126 } else { | |
127 newDesc[previousProp = prop] = elem; | |
128 } | |
129 } | |
130 } | |
131 $classesCollector[property] = [globalObject, newDesc]; | |
132 classes.push(property); | |
133 } | |
134 } | |
135 } | |
136 ''', [typeInformationAccess, globalFunctionsAccess, staticsAccess]); | |
137 | |
138 | |
139 /** | |
140 * See [dart2js.js_emitter.ContainerBuilder.addMemberMethod] for format of | |
141 * [array]. | |
142 */ | |
143 jsAst.Statement addStubs = js.statement(''' | |
144 function addStubs(descriptor, array, name, isStatic, | |
145 originalDescriptor, functions) { | |
146 var f, funcs = | |
147 [originalDescriptor[name] = | |
148 descriptor[name] = f = ${readFunction("array", "$FUNCTION_INDEX")}]; | |
149 f.\$stubName = name; | |
150 functions.push(name); | |
151 for (var index = $FUNCTION_INDEX; index < array.length; index += 2) { | |
152 f = array[index + 1]; | |
153 if (typeof f != "function") break; | |
154 f.\$stubName = ${readString("array", "index + 2")}; | |
155 funcs.push(f); | |
156 if (f.\$stubName) { | |
157 originalDescriptor[f.\$stubName] = descriptor[f.\$stubName] = f; | |
158 functions.push(f.\$stubName); | |
159 } | |
160 } | |
161 for (var i = 0; i < funcs.length; index++, i++) { | |
162 funcs[i].\$callName = ${readString("array", "index + 1")}; | |
163 } | |
164 var getterStubName = ${readString("array", "++index")}; | |
165 array = array.slice(++index); | |
166 var requiredParameterInfo = ${readInt("array", "0")}; | |
167 var requiredParameterCount = requiredParameterInfo >> 1; | |
168 var isAccessor = (requiredParameterInfo & 1) === 1; | |
169 var isSetter = requiredParameterInfo === 3; | |
170 var isGetter = requiredParameterInfo === 1; | |
171 var optionalParameterInfo = ${readInt("array", "1")}; | |
172 var optionalParameterCount = optionalParameterInfo >> 1; | |
173 var optionalParametersAreNamed = (optionalParameterInfo & 1) === 1; | |
174 var isIntercepted = | |
175 requiredParameterCount + optionalParameterCount != funcs[0].length; | |
176 var functionTypeIndex = ${readFunctionType("array", "2")}; | |
177 var unmangledNameIndex = $unmangledNameIndex; | |
178 var isReflectable = array.length > unmangledNameIndex; | |
179 | |
180 if (getterStubName) { | |
181 f = tearOff(funcs, array, isStatic, name, isIntercepted); | |
182 descriptor[name].\$getter = f; | |
183 f.\$getterStub = true; | |
184 // Used to create an isolate using spawnFunction. | |
185 if (isStatic) #[name] = f; // embedded globalFunctions. | |
186 originalDescriptor[getterStubName] = descriptor[getterStubName] = f; | |
187 funcs.push(f); | |
188 if (getterStubName) functions.push(getterStubName); | |
189 f.\$stubName = getterStubName; | |
190 f.\$callName = null; | |
191 if (isIntercepted) #[getterStubName] = true; // embedded interceptedNames. | |
192 } | |
193 if (isReflectable) { | |
194 for (var i = 0; i < funcs.length; i++) { | |
195 funcs[i].$reflectableField = 1; | |
196 funcs[i].$reflectionInfoField = array; | |
197 } | |
198 var mangledNames = isStatic ? # : #; // embedded mangledGlobalNames, mang
ledNames | |
199 var unmangledName = ${readString("array", "unmangledNameIndex")}; | |
200 // The function is either a getter, a setter, or a method. | |
201 // If it is a method, it might also have a tear-off closure. | |
202 // The unmangledName is the same as the getter-name. | |
203 var reflectionName = unmangledName; | |
204 if (getterStubName) mangledNames[getterStubName] = reflectionName; | |
205 if (isSetter) { | |
206 reflectionName += "="; | |
207 } else if (!isGetter) { | |
208 reflectionName += ":" + requiredParameterCount + | |
209 ":" + optionalParameterCount; | |
210 } | |
211 mangledNames[name] = reflectionName; | |
212 funcs[0].$reflectionNameField = reflectionName; | |
213 funcs[0].$metadataIndexField = unmangledNameIndex + 1; | |
214 if (optionalParameterCount) descriptor[unmangledName + "*"] = funcs[0]; | |
215 } | |
216 } | |
217 ''', [globalFunctionsAccess, interceptedNamesAccess, | |
218 mangledGlobalNamesAccess, mangledNamesAccess]); | |
219 | |
220 List<jsAst.Statement> tearOffCode = buildTearOffCode(backend); | |
221 | |
222 jsAst.Statement init = js.statement('''{ | |
223 var functionCounter = 0; | |
224 var tearOffGetter = (typeof dart_precompiled == "function") | |
225 ? tearOffGetterCsp : tearOffGetterNoCsp; | |
226 if (!#) # = []; // embedded libraries. | |
227 if (!#) # = map(); // embedded mangledNames. | |
228 if (!#) # = map(); // embedded mangledGlobalNames. | |
229 if (!#) # = map(); // embedded statics. | |
230 if (!#) # = map(); // embedded typeInformation. | |
231 if (!#) # = map(); // embedded globalFunctions. | |
232 if (!#) # = map(); // embedded interceptedNames. | |
233 var libraries = #; // embeded libraries. | |
234 var mangledNames = #; // embedded mangledNames. | |
235 var mangledGlobalNames = #; // embedded mangledGlobalNames. | |
236 var hasOwnProperty = Object.prototype.hasOwnProperty; | |
237 var length = reflectionData.length; | |
238 for (var i = 0; i < length; i++) { | |
239 var data = reflectionData[i]; | |
240 | |
241 // [data] contains these elements: | |
242 // 0. The library name (not unique). | |
243 // 1. The library URI (unique). | |
244 // 2. A function returning the metadata associated with this library. | |
245 // 3. The global object to use for this library. | |
246 // 4. An object literal listing the members of the library. | |
247 // 5. This element is optional and if present it is true and signals that this | |
248 // library is the root library (see dart:mirrors IsolateMirror.rootLibrary). | |
249 // | |
250 // The entries of [data] are built in [assembleProgram] above. | |
251 | |
252 var name = data[0]; | |
253 var uri = data[1]; | |
254 var metadata = data[2]; | |
255 var globalObject = data[3]; | |
256 var descriptor = data[4]; | |
257 var isRoot = !!data[5]; | |
258 var fields = descriptor && descriptor["${namer.classDescriptorProperty}"]; | |
259 if (fields instanceof Array) fields = fields[0]; | |
260 var classes = []; | |
261 var functions = []; | |
262 processStatics(descriptor); | |
263 libraries.push([name, uri, classes, functions, metadata, fields, isRoot, | |
264 globalObject]); | |
265 } | |
266 }''', [librariesAccess, librariesAccess, | |
267 mangledNamesAccess, mangledNamesAccess, | |
268 mangledGlobalNamesAccess, mangledGlobalNamesAccess, | |
269 staticsAccess, staticsAccess, | |
270 typeInformationAccess, typeInformationAccess, | |
271 globalFunctionsAccess, globalFunctionsAccess, | |
272 interceptedNamesAccess, interceptedNamesAccess, | |
273 librariesAccess, | |
274 mangledNamesAccess, | |
275 mangledGlobalNamesAccess]); | |
276 | |
277 return js(''' | |
278 (function (reflectionData) { | |
279 "use strict"; | |
280 #; // header | |
281 #; // processStatics | |
282 #; // addStubs | |
283 #; // tearOffCode | |
284 #; // init | |
285 })''', [header, processStatics, addStubs, tearOffCode, init]); | |
286 } | |
287 | |
288 | |
289 List<jsAst.Statement> buildTearOffCode(JavaScriptBackend backend) { | |
290 Namer namer = backend.namer; | |
291 Compiler compiler = backend.compiler; | |
292 | |
293 Element closureFromTearOff = backend.findHelper('closureFromTearOff'); | |
294 String tearOffAccessText; | |
295 jsAst.Expression tearOffAccessExpression; | |
296 String tearOffGlobalObjectName; | |
297 String tearOffGlobalObject; | |
298 if (closureFromTearOff != null) { | |
299 // We need both the AST that references [closureFromTearOff] and a string | |
300 // for the NoCsp version that constructs a function. | |
301 tearOffAccessExpression = namer.elementAccess(closureFromTearOff); | |
302 tearOffAccessText = | |
303 jsAst.prettyPrint(tearOffAccessExpression, compiler).getText(); | |
304 tearOffGlobalObjectName = tearOffGlobalObject = | |
305 namer.globalObjectFor(closureFromTearOff); | |
306 } else { | |
307 // Default values for mocked-up test libraries. | |
308 tearOffAccessText = | |
309 r'''function() { throw 'Helper \'closureFromTearOff\' missing.' }'''; | |
310 tearOffAccessExpression = js(tearOffAccessText); | |
311 tearOffGlobalObjectName = 'MissingHelperFunction'; | |
312 tearOffGlobalObject = '($tearOffAccessText())'; | |
313 } | |
314 | |
315 // This template is uncached because it is constructed from code fragments | |
316 // that can change from compilation to compilation. Some of these could be | |
317 // avoided, except for the string literals that contain the compiled access | |
318 // path to 'closureFromTearOff'. | |
319 jsAst.Statement tearOffGetterNoCsp = js.uncachedStatementTemplate(''' | |
320 function tearOffGetterNoCsp(funcs, reflectionInfo, name, isIntercepted) { | |
321 return isIntercepted | |
322 ? new Function("funcs", "reflectionInfo", "name", | |
323 "$tearOffGlobalObjectName", "c", | |
324 "return function tearOff_" + name + (functionCounter++)+ "(x) {" + | |
325 "if (c === null) c = $tearOffAccessText(" + | |
326 "this, funcs, reflectionInfo, false, [x], name);" + | |
327 "return new c(this, funcs[0], x, name);" + | |
328 "}")(funcs, reflectionInfo, name, $tearOffGlobalObject, null) | |
329 : new Function("funcs", "reflectionInfo", "name", | |
330 "$tearOffGlobalObjectName", "c", | |
331 "return function tearOff_" + name + (functionCounter++)+ "() {" + | |
332 "if (c === null) c = $tearOffAccessText(" + | |
333 "this, funcs, reflectionInfo, false, [], name);" + | |
334 "return new c(this, funcs[0], null, name);" + | |
335 "}")(funcs, reflectionInfo, name, $tearOffGlobalObject, null); | |
336 }''').instantiate([]); | |
337 | |
338 jsAst.Statement tearOffGetterCsp = js.statement(''' | |
339 function tearOffGetterCsp(funcs, reflectionInfo, name, isIntercepted) { | |
340 var cache = null; | |
341 return isIntercepted | |
342 ? function(x) { | |
343 if (cache === null) cache = #( | |
344 this, funcs, reflectionInfo, false, [x], name); | |
345 return new cache(this, funcs[0], x, name); | |
346 } | |
347 : function() { | |
348 if (cache === null) cache = #( | |
349 this, funcs, reflectionInfo, false, [], name); | |
350 return new cache(this, funcs[0], null, name); | |
351 }; | |
352 }''', [tearOffAccessExpression, tearOffAccessExpression]); | |
353 | |
354 jsAst.Statement tearOff = js.statement(''' | |
355 function tearOff(funcs, reflectionInfo, isStatic, name, isIntercepted) { | |
356 var cache; | |
357 return isStatic | |
358 ? function() { | |
359 if (cache === void 0) cache = #( | |
360 this, funcs, reflectionInfo, true, [], name).prototype; | |
361 return cache; | |
362 } | |
363 : tearOffGetter(funcs, reflectionInfo, name, isIntercepted); | |
364 }''', tearOffAccessExpression); | |
365 | |
366 return <jsAst.Statement>[tearOffGetterNoCsp, tearOffGetterCsp, tearOff]; | |
367 } | |
368 | |
369 | |
370 String readString(String array, String index) { | |
371 return readChecked( | |
372 array, index, 'result != null && typeof result != "string"', 'string'); | |
373 } | |
374 | |
375 String readInt(String array, String index) { | |
376 return readChecked( | |
377 array, index, | |
378 'result != null && (typeof result != "number" || (result|0) !== result)', | |
379 'int'); | |
380 } | |
381 | |
382 String readFunction(String array, String index) { | |
383 return readChecked( | |
384 array, index, 'result != null && typeof result != "function"', | |
385 'function'); | |
386 } | |
387 | |
388 String readFunctionType(String array, String index) { | |
389 return readChecked( | |
390 array, index, | |
391 'result != null && ' | |
392 '(typeof result != "number" || (result|0) !== result) && ' | |
393 'typeof result != "function"', | |
394 'function or int'); | |
395 } | |
396 | |
397 String readChecked(String array, String index, String check, String type) { | |
398 if (!VALIDATE_DATA) return '$array[$index]'; | |
399 return ''' | |
400 (function() { | |
401 var result = $array[$index]; | |
402 if ($check) { | |
403 throw new Error( | |
404 name + ": expected value of type \'$type\' at index " + ($index) + | |
405 " but got " + (typeof result)); | |
406 } | |
407 return result; | |
408 })()'''; | |
409 } | |
OLD | NEW |