OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, 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 dart2js.js_emitter.startup_emitter.model_emitter; | 5 part of dart2js.js_emitter.startup_emitter.model_emitter; |
6 | 6 |
| 7 /// The name of the property that stores the tear-off getter on a static |
| 8 /// function. |
| 9 /// |
| 10 /// This property is only used when isolates are used. |
| 11 /// |
| 12 /// When serializing static functions we transmit the |
| 13 /// name of the static function, but not the name of the function's getter. We |
| 14 /// store the getter-function on the static function itself, which allows us to |
| 15 /// find it easily. |
| 16 const String tearOffPropertyName = r'$tearOff'; |
| 17 |
| 18 /// The name of the property that stores the list of fields on a constructor. |
| 19 /// |
| 20 /// This property is only used when isolates are used. |
| 21 /// |
| 22 /// When serializing objects we extract all fields from any given object. |
| 23 /// We extract the names of all fields from a fresh empty object. This list |
| 24 /// is cached on the constructor in this property to to avoid too many |
| 25 /// allocations. |
| 26 const String cachedClassFieldNames = r'$cachedFieldNames'; |
| 27 |
7 /// The fast startup emitter's goal is to minimize the amount of work that the | 28 /// The fast startup emitter's goal is to minimize the amount of work that the |
8 /// JavaScript engine has to do before it can start running user code. | 29 /// JavaScript engine has to do before it can start running user code. |
9 /// | 30 /// |
10 /// Whenever possible, the emitter uses object literals instead of updating | 31 /// Whenever possible, the emitter uses object literals instead of updating |
11 /// objects. | 32 /// objects. |
12 /// | 33 /// |
13 /// Example: | 34 /// Example: |
14 /// | 35 /// |
15 /// // Holders are initialized directly with the classes and static | 36 /// // Holders are initialized directly with the classes and static |
16 /// // functions. | 37 /// // functions. |
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
135 // The argument [funsOrNames] is an array of strings or functions. If it is a | 156 // The argument [funsOrNames] is an array of strings or functions. If it is a |
136 // name, then the function should be fetched from the container. The first | 157 // name, then the function should be fetched from the container. The first |
137 // entry in that array *must* be a string. | 158 // entry in that array *must* be a string. |
138 // | 159 // |
139 // TODO(floitsch): Change tearOffCode to accept the data directly, or create a | 160 // TODO(floitsch): Change tearOffCode to accept the data directly, or create a |
140 // different tearOffCode? | 161 // different tearOffCode? |
141 function installTearOff(container, getterName, | 162 function installTearOff(container, getterName, |
142 isStatic, isIntercepted, requiredParameterCount, | 163 isStatic, isIntercepted, requiredParameterCount, |
143 optionalParameterDefaultValues, | 164 optionalParameterDefaultValues, |
144 callNames, funsOrNames, funType) { | 165 callNames, funsOrNames, funType) { |
| 166 // A function can have several stubs (for example to fill in optional |
| 167 // arguments). We collect these functions in the `funs` array. |
145 var funs = []; | 168 var funs = []; |
146 for (var i = 0; i < funsOrNames.length; i++) { | 169 for (var i = 0; i < funsOrNames.length; i++) { |
147 var fun = funsOrNames[i]; | 170 var fun = funsOrNames[i]; |
148 if ((typeof fun) == 'string') fun = container[fun]; | 171 if ((typeof fun) == 'string') fun = container[fun]; |
149 fun.#callName = callNames[i]; | 172 fun.#callName = callNames[i]; |
150 funs.push(fun); | 173 funs.push(fun); |
151 } | 174 } |
152 | 175 |
153 funs[0][#argumentCount] = requiredParameterCount; | 176 // The main function to which all stubs redirect. |
154 funs[0][#defaultArgumentValues] = optionalParameterDefaultValues; | 177 var fun = funs[0]; |
| 178 |
| 179 fun[#argumentCount] = requiredParameterCount; |
| 180 fun[#defaultArgumentValues] = optionalParameterDefaultValues; |
155 var reflectionInfo = funType; | 181 var reflectionInfo = funType; |
156 if (typeof reflectionInfo == "number") { | 182 if (typeof reflectionInfo == "number") { |
157 // The reflectionInfo can either be a function, or a pointer into the types | 183 // The reflectionInfo can either be a function, or a pointer into the types |
158 // table. If it points into the types-table we need to update the index, | 184 // table. If it points into the types-table we need to update the index, |
159 // in case the tear-off is part of a deferred hunk. | 185 // in case the tear-off is part of a deferred hunk. |
160 reflectionInfo = reflectionInfo + typesOffset; | 186 reflectionInfo = reflectionInfo + typesOffset; |
161 } | 187 } |
162 var name = funsOrNames[0]; | 188 var name = funsOrNames[0]; |
163 container[getterName] = | 189 var getterFunction = |
164 tearOff(funs, reflectionInfo, isStatic, name, isIntercepted); | 190 tearOff(funs, reflectionInfo, isStatic, name, isIntercepted); |
| 191 container[getterName] = getterFunction; |
| 192 if (isStatic) { |
| 193 fun.$tearOffPropertyName = getterFunction; |
| 194 } |
165 } | 195 } |
166 | 196 |
167 // Instead of setting the interceptor tags directly we use this update | 197 // Instead of setting the interceptor tags directly we use this update |
168 // function. This makes it easier for deferred fragments to contribute to the | 198 // function. This makes it easier for deferred fragments to contribute to the |
169 // embedded global. | 199 // embedded global. |
170 function setOrUpdateInterceptorsByTag(newTags) { | 200 function setOrUpdateInterceptorsByTag(newTags) { |
171 var tags = #embeddedInterceptorTags; | 201 var tags = #embeddedInterceptorTags; |
172 if (!tags) { | 202 if (!tags) { |
173 #embeddedInterceptorTags = newTags; | 203 #embeddedInterceptorTags = newTags; |
174 return; | 204 return; |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
214 // Update the typesOffset for the next deferred library. | 244 // Update the typesOffset for the next deferred library. |
215 typesOffset = #embeddedTypes.length; | 245 typesOffset = #embeddedTypes.length; |
216 | 246 |
217 // TODO(floitsch): extend natives. | 247 // TODO(floitsch): extend natives. |
218 hunk(inherit, mixin, lazy, makeConstList, installTearOff, | 248 hunk(inherit, mixin, lazy, makeConstList, installTearOff, |
219 updateHolder, updateTypes, setOrUpdateInterceptorsByTag, | 249 updateHolder, updateTypes, setOrUpdateInterceptorsByTag, |
220 setOrUpdateLeafTags, | 250 setOrUpdateLeafTags, |
221 #embeddedGlobalsObject, #holdersList, #staticState); | 251 #embeddedGlobalsObject, #holdersList, #staticState); |
222 } | 252 } |
223 | 253 |
| 254 // Returns the global with the given [name]. |
| 255 function getGlobalFromName(name) { |
| 256 // TODO(floitsch): we are running through all holders. Since negative |
| 257 // lookups are expensive we might need to improve this. |
| 258 // Relies on the fact that all names are unique across all holders. |
| 259 for (var i = 0; i < holders.length; i++) { |
| 260 // The constant holder reuses the same names. Therefore we must skip it. |
| 261 if (holders[i] == #constantHolderReference) continue; |
| 262 // Relies on the fact that all variables are unique. |
| 263 if (holders[i][name]) return holders[i][name]; |
| 264 } |
| 265 } |
| 266 |
224 // Creates the holders. | 267 // Creates the holders. |
225 #holders; | 268 #holders; |
226 // TODO(floitsch): if name is not set (for example in IE), run through all | 269 // TODO(floitsch): if name is not set (for example in IE), run through all |
227 // functions and set the name. | 270 // functions and set the name. |
228 | 271 |
229 // TODO(floitsch): we should build this object as a literal. | 272 // TODO(floitsch): we should build this object as a literal. |
230 var #staticStateDeclaration = {}; | 273 var #staticStateDeclaration = {}; |
231 | 274 |
232 // Sets the prototypes of classes. | 275 // Sets the prototypes of classes. |
233 #prototypes; | 276 #prototypes; |
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
343 'embeddedInterceptorTags': | 386 'embeddedInterceptorTags': |
344 generateEmbeddedGlobalAccess(INTERCEPTORS_BY_TAG), | 387 generateEmbeddedGlobalAccess(INTERCEPTORS_BY_TAG), |
345 'embeddedLeafTags': generateEmbeddedGlobalAccess(LEAF_TAGS), | 388 'embeddedLeafTags': generateEmbeddedGlobalAccess(LEAF_TAGS), |
346 'embeddedGlobalsObject': js.js("init"), | 389 'embeddedGlobalsObject': js.js("init"), |
347 'holdersList': new js.ArrayInitializer(program.holders.map((holder) { | 390 'holdersList': new js.ArrayInitializer(program.holders.map((holder) { |
348 return js.js("#", holder.name); | 391 return js.js("#", holder.name); |
349 }).toList()), | 392 }).toList()), |
350 'staticStateDeclaration': new js.VariableDeclaration( | 393 'staticStateDeclaration': new js.VariableDeclaration( |
351 namer.staticStateHolder, allowRename: false), | 394 namer.staticStateHolder, allowRename: false), |
352 'staticState': js.js('#', namer.staticStateHolder), | 395 'staticState': js.js('#', namer.staticStateHolder), |
| 396 'constantHolderReference': buildConstantHolderReference(program), |
353 'holders': emitHolders(program.holders, fragment), | 397 'holders': emitHolders(program.holders, fragment), |
354 'callName': js.string(namer.callNameField), | 398 'callName': js.string(namer.callNameField), |
355 'argumentCount': js.string(namer.requiredParameterField), | 399 'argumentCount': js.string(namer.requiredParameterField), |
356 'defaultArgumentValues': js.string(namer.defaultValuesField), | 400 'defaultArgumentValues': js.string(namer.defaultValuesField), |
357 'prototypes': emitPrototypes(fragment), | 401 'prototypes': emitPrototypes(fragment), |
358 'inheritance': emitInheritance(fragment), | 402 'inheritance': emitInheritance(fragment), |
359 'aliases': emitInstanceMethodAliases(fragment), | 403 'aliases': emitInstanceMethodAliases(fragment), |
360 'tearOffs': emitInstallTearOffs(fragment), | 404 'tearOffs': emitInstallTearOffs(fragment), |
361 'constants': emitConstants(fragment), | 405 'constants': emitConstants(fragment), |
362 'staticNonFinalFields': emitStaticNonFinalFields(fragment), | 406 'staticNonFinalFields': emitStaticNonFinalFields(fragment), |
(...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
462 new js.VariableDeclarationList(holders | 506 new js.VariableDeclarationList(holders |
463 .map(emitHolderInitialization) | 507 .map(emitHolderInitialization) |
464 .toList())), | 508 .toList())), |
465 js.js.statement('var holders = #', new js.ArrayInitializer( | 509 js.js.statement('var holders = #', new js.ArrayInitializer( |
466 holders | 510 holders |
467 .map((holder) => new js.VariableUse(holder.name)) | 511 .map((holder) => new js.VariableUse(holder.name)) |
468 .toList(growable: false)))]; | 512 .toList(growable: false)))]; |
469 return new js.Block(statements); | 513 return new js.Block(statements); |
470 } | 514 } |
471 | 515 |
| 516 /// Returns a reference to the constant holder, or the JS-literal `null`. |
| 517 js.Expression buildConstantHolderReference(Program program) { |
| 518 Holder constantHolder = program.holders |
| 519 .firstWhere((Holder holder) => holder.isConstantsHolder, |
| 520 orElse: () => null); |
| 521 if (constantHolder == null) return new js.LiteralNull(); |
| 522 return new js.VariableUse(constantHolder.name); |
| 523 } |
| 524 |
472 /// Emits the given [method]. | 525 /// Emits the given [method]. |
473 /// | 526 /// |
474 /// A Dart method might result in several JavaScript functions, if it | 527 /// A Dart method might result in several JavaScript functions, if it |
475 /// requires stubs. The returned map contains the original method and all | 528 /// requires stubs. The returned map contains the original method and all |
476 /// the stubs it needs. | 529 /// the stubs it needs. |
477 Map<js.Name, js.Expression> emitStaticMethod(StaticMethod method) { | 530 Map<js.Name, js.Expression> emitStaticMethod(StaticMethod method) { |
478 Map<js.Name, js.Expression> jsMethods = <js.Name, js.Expression>{}; | 531 Map<js.Name, js.Expression> jsMethods = <js.Name, js.Expression>{}; |
479 | 532 |
480 // We don't need to install stub-methods. They can only be used when there | 533 // We don't need to install stub-methods. They can only be used when there |
481 // are tear-offs, in which case they are emitted there. | 534 // are tear-offs, in which case they are emitted there. |
(...skipping 466 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
948 | 1001 |
949 return new js.Property(js.string(MANGLED_GLOBAL_NAMES), | 1002 return new js.Property(js.string(MANGLED_GLOBAL_NAMES), |
950 new js.ObjectInitializer(names)); | 1003 new js.ObjectInitializer(names)); |
951 } | 1004 } |
952 | 1005 |
953 /// Emits the [GET_TYPE_FROM_NAME] embedded global. | 1006 /// Emits the [GET_TYPE_FROM_NAME] embedded global. |
954 /// | 1007 /// |
955 /// This embedded global provides a way to go from a class name (which is | 1008 /// This embedded global provides a way to go from a class name (which is |
956 /// also the constructor's name) to the constructor itself. | 1009 /// also the constructor's name) to the constructor itself. |
957 js.Property emitGetTypeFromName() { | 1010 js.Property emitGetTypeFromName() { |
958 // TODO(floitsch): Fix getTypeFromName. It's too inefficient. | 1011 js.Expression function = js.js("getGlobalFromName"); |
959 // The current implementation relies on the fact that the names in holders | |
960 // are unique across all holders. | |
961 // TODO(floitsch): constants and other globals may share the same name. | |
962 // If a global happens to have the same (minified) name this code breaks. | |
963 // A follow-up CL has a fix for this. | |
964 js.Expression function = | |
965 js.js( """function(name) { | |
966 for (var i = 0; i < holders.length; i++) { | |
967 // Relies on the fact that all variables are unique. | |
968 if (holders[i][name]) return holders[i][name]; | |
969 } | |
970 }"""); | |
971 return new js.Property(js.string(GET_TYPE_FROM_NAME), function); | 1012 return new js.Property(js.string(GET_TYPE_FROM_NAME), function); |
972 } | 1013 } |
973 | 1014 |
974 /// Emits the [METADATA] embedded global. | 1015 /// Emits the [METADATA] embedded global. |
975 /// | 1016 /// |
976 /// The metadata itself has already been computed earlier and is stored in | 1017 /// The metadata itself has already been computed earlier and is stored in |
977 /// the [program]. | 1018 /// the [program]. |
978 List<js.Property> emitMetadata(Program program) { | 1019 List<js.Property> emitMetadata(Program program) { |
979 List<js.Property> metadataGlobals = <js.Property>[]; | 1020 List<js.Property> metadataGlobals = <js.Property>[]; |
980 | 1021 |
(...skipping 26 matching lines...) Expand all Loading... |
1007 } | 1048 } |
1008 | 1049 |
1009 if (program.hasIsolateSupport) { | 1050 if (program.hasIsolateSupport) { |
1010 String staticStateName = namer.staticStateHolder; | 1051 String staticStateName = namer.staticStateHolder; |
1011 // TODO(floitsch): this doesn't create a new isolate, but just reuses | 1052 // TODO(floitsch): this doesn't create a new isolate, but just reuses |
1012 // the current static state. Since we don't run multiple isolates in the | 1053 // the current static state. Since we don't run multiple isolates in the |
1013 // same JavaScript context (except for testing) this shouldn't have any | 1054 // same JavaScript context (except for testing) this shouldn't have any |
1014 // impact on real-world programs, though. | 1055 // impact on real-world programs, though. |
1015 globals.add( | 1056 globals.add( |
1016 new js.Property(js.string(CREATE_NEW_ISOLATE), | 1057 new js.Property(js.string(CREATE_NEW_ISOLATE), |
1017 js.js('function () { return $staticStateName; }'))); | 1058 js.js('function () { return $staticStateName; }'))); |
1018 // TODO(floitsch): add remaining isolate functions. | 1059 |
| 1060 js.Expression nameToClosureFunction = js.js(''' |
| 1061 // First fetch the static function. From there we can execute its |
| 1062 // getter function which builds a Dart closure. |
| 1063 function(name) { |
| 1064 var staticFunction = getGlobalFromName(name); |
| 1065 var getterFunction = staticFunction.$tearOffPropertyName; |
| 1066 return getterFunction(); |
| 1067 }'''); |
| 1068 globals.add(new js.Property(js.string(STATIC_FUNCTION_NAME_TO_CLOSURE), |
| 1069 nameToClosureFunction)); |
| 1070 |
| 1071 globals.add( |
| 1072 new js.Property(js.string(CLASS_ID_EXTRACTOR), |
| 1073 js.js('function(o) { return o.constructor.name; }'))); |
| 1074 |
| 1075 js.Expression extractFieldsFunction = js.js(''' |
| 1076 function(o) { |
| 1077 var constructor = o.constructor; |
| 1078 var fieldNames = constructor.$cachedClassFieldNames; |
| 1079 if (!fieldNames) { |
| 1080 // Extract the fields from an empty unmodified object. |
| 1081 var empty = new constructor(); |
| 1082 // This gives us the keys that the constructor sets. |
| 1083 fieldNames = constructor.$cachedClassFieldNames = Object.keys(empty); |
| 1084 } |
| 1085 var result = new Array(fieldNames.length); |
| 1086 for (var i = 0; i < fieldNames.length; i++) { |
| 1087 result[i] = o[fieldNames[i]]; |
| 1088 } |
| 1089 return result; |
| 1090 }'''); |
| 1091 globals.add(new js.Property(js.string(CLASS_FIELDS_EXTRACTOR), |
| 1092 extractFieldsFunction)); |
| 1093 |
| 1094 js.Expression createInstanceFromClassIdFunction = js.js(''' |
| 1095 function(name) { |
| 1096 var constructor = getGlobalFromName(name); |
| 1097 return new constructor(); |
| 1098 } |
| 1099 '''); |
| 1100 globals.add(new js.Property(js.string(INSTANCE_FROM_CLASS_ID), |
| 1101 createInstanceFromClassIdFunction)); |
| 1102 |
| 1103 js.Expression initializeEmptyInstanceFunction = js.js(''' |
| 1104 function(name, o, fields) { |
| 1105 var constructor = o.constructor; |
| 1106 // By construction the object `o` is an empty object with the same |
| 1107 // keys as the one we used in the extract-fields function. |
| 1108 var fieldNames = Object.keys(o); |
| 1109 if (fieldNames.length != fields.length) { |
| 1110 throw new Error("Mismatch during deserialization."); |
| 1111 } |
| 1112 for (var i = 0; i < fields.length; i++) { |
| 1113 o[fieldNames[i]] = fields[i]; |
| 1114 } |
| 1115 return o; |
| 1116 }'''); |
| 1117 globals.add(new js.Property(js.string(INITIALIZE_EMPTY_INSTANCE), |
| 1118 initializeEmptyInstanceFunction)); |
1019 } | 1119 } |
1020 | 1120 |
1021 globals.add(emitMangledGlobalNames()); | 1121 globals.add(emitMangledGlobalNames()); |
1022 | 1122 |
1023 // The [MANGLED_NAMES] table is only relevant for reflection. | 1123 // The [MANGLED_NAMES] table is only relevant for reflection. |
1024 // TODO(floitsch): verify that this is correct. | 1124 // TODO(floitsch): verify that this is correct. |
1025 globals.add(new js.Property(js.string(MANGLED_NAMES), | 1125 globals.add(new js.Property(js.string(MANGLED_NAMES), |
1026 new js.ObjectInitializer([]))); | 1126 new js.ObjectInitializer([]))); |
1027 | 1127 |
1028 globals.add(emitGetTypeFromName()); | 1128 globals.add(emitGetTypeFromName()); |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1099 } | 1199 } |
1100 } | 1200 } |
1101 statements.add(js.js.statement("setOrUpdateInterceptorsByTag(#);", | 1201 statements.add(js.js.statement("setOrUpdateInterceptorsByTag(#);", |
1102 js.objectLiteral(interceptorsByTag))); | 1202 js.objectLiteral(interceptorsByTag))); |
1103 statements.add(js.js.statement("setOrUpdateLeafTags(#);", | 1203 statements.add(js.js.statement("setOrUpdateLeafTags(#);", |
1104 js.objectLiteral(leafTags))); | 1204 js.objectLiteral(leafTags))); |
1105 statements.add(subclassAssignment); | 1205 statements.add(subclassAssignment); |
1106 | 1206 |
1107 return new js.Block(statements); | 1207 return new js.Block(statements); |
1108 } | 1208 } |
1109 } | 1209 } |
OLD | NEW |