| 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 |