Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 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 | 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; | 5 part of dart2js.js_emitter; |
| 6 | 6 |
| 7 /// This class should morph into something that makes it easy to build | 7 /// This class should morph into something that makes it easy to build |
| 8 /// JavaScript representations of libraries, class-sides, and instance-sides. | 8 /// JavaScript representations of libraries, class-sides, and instance-sides. |
| 9 /// Initially, it is just a placeholder for code that is moved from | 9 /// Initially, it is just a placeholder for code that is moved from |
| 10 /// [CodeEmitterTask]. | 10 /// [CodeEmitterTask]. |
| 11 class ContainerBuilder extends CodeEmitterHelper { | 11 class ContainerBuilder extends CodeEmitterHelper { |
| 12 final Map<Element, Element> staticGetters = new Map<Element, Element>(); | 12 final Map<Element, Element> staticGetters = new Map<Element, Element>(); |
| 13 | 13 |
| 14 /// A cache of synthesized closures for top-level, static or | 14 /// A cache of synthesized closures for top-level, static or |
| 15 /// instance methods. | 15 /// instance methods. |
| 16 final Map<String, Element> methodClosures = <String, Element>{}; | 16 final Map<String, Element> methodClosures = <String, Element>{}; |
| 17 | 17 |
| 18 /** | 18 /** |
| 19 * Generate stubs to handle invocation of methods with optional | 19 * Generate stubs to handle invocation of methods with optional |
| 20 * arguments. | 20 * arguments. |
| 21 * | 21 * |
| 22 * A method like [: foo([x]) :] may be invoked by the following | 22 * A method like [: foo([x]) :] may be invoked by the following |
| 23 * calls: [: foo(), foo(1), foo(x: 1) :]. See the sources of this | 23 * calls: [: foo(), foo(1), foo(x: 1) :]. See the sources of this |
| 24 * function for detailed examples. | 24 * function for detailed examples. |
| 25 */ | 25 */ |
| 26 void addParameterStub(FunctionElement member, | 26 void addParameterStub(FunctionElement member, |
| 27 Selector selector, | 27 Selector selector, |
| 28 DefineStubFunction defineStub, | 28 AddStubFunction addStub, |
| 29 Set<String> alreadyGenerated) { | 29 Set<String> alreadyGenerated) { |
| 30 FunctionSignature parameters = member.computeSignature(compiler); | 30 FunctionSignature parameters = member.computeSignature(compiler); |
| 31 int positionalArgumentCount = selector.positionalArgumentCount; | 31 int positionalArgumentCount = selector.positionalArgumentCount; |
| 32 if (positionalArgumentCount == parameters.parameterCount) { | 32 if (positionalArgumentCount == parameters.parameterCount) { |
| 33 assert(selector.namedArgumentCount == 0); | 33 assert(selector.namedArgumentCount == 0); |
| 34 return; | 34 return; |
| 35 } | 35 } |
| 36 if (parameters.optionalParametersAreNamed | 36 if (parameters.optionalParametersAreNamed |
| 37 && selector.namedArgumentCount == parameters.optionalParameterCount) { | 37 && selector.namedArgumentCount == parameters.optionalParameterCount) { |
| 38 // If the selector has the same number of named arguments as the element, | 38 // If the selector has the same number of named arguments as the element, |
| (...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 74 | 74 |
| 75 int optionalParameterStart = positionalArgumentCount + extraArgumentCount; | 75 int optionalParameterStart = positionalArgumentCount + extraArgumentCount; |
| 76 // Includes extra receiver argument when using interceptor convention | 76 // Includes extra receiver argument when using interceptor convention |
| 77 int indexOfLastOptionalArgumentInParameters = optionalParameterStart - 1; | 77 int indexOfLastOptionalArgumentInParameters = optionalParameterStart - 1; |
| 78 | 78 |
| 79 TreeElements elements = | 79 TreeElements elements = |
| 80 compiler.enqueuer.resolution.getCachedElements(member); | 80 compiler.enqueuer.resolution.getCachedElements(member); |
| 81 | 81 |
| 82 int parameterIndex = 0; | 82 int parameterIndex = 0; |
| 83 parameters.orderedForEachParameter((Element element) { | 83 parameters.orderedForEachParameter((Element element) { |
| 84 // Use generic names for closures to facilitate code sharing. | 84 String jsName = backend.namer.safeName(element.name); |
| 85 String jsName = member is ClosureInvocationElement | |
| 86 ? 'p${parameterIndex++}' | |
| 87 : backend.namer.safeName(element.name); | |
| 88 assert(jsName != receiverArgumentName); | 85 assert(jsName != receiverArgumentName); |
| 89 if (count < optionalParameterStart) { | 86 if (count < optionalParameterStart) { |
| 90 parametersBuffer[count] = new jsAst.Parameter(jsName); | 87 parametersBuffer[count] = new jsAst.Parameter(jsName); |
| 91 argumentsBuffer[count] = js(jsName); | 88 argumentsBuffer[count] = js(jsName); |
| 92 } else { | 89 } else { |
| 93 int index = names.indexOf(element.name); | 90 int index = names.indexOf(element.name); |
| 94 if (index != -1) { | 91 if (index != -1) { |
| 95 indexOfLastOptionalArgumentInParameters = count; | 92 indexOfLastOptionalArgumentInParameters = count; |
| 96 // The order of the named arguments is not the same as the | 93 // The order of the named arguments is not the same as the |
| 97 // one in the real method (which is in Dart source order). | 94 // one in the real method (which is in Dart source order). |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 114 } | 111 } |
| 115 count++; | 112 count++; |
| 116 }); | 113 }); |
| 117 | 114 |
| 118 List body; | 115 List body; |
| 119 if (member.hasFixedBackendName()) { | 116 if (member.hasFixedBackendName()) { |
| 120 body = task.nativeEmitter.generateParameterStubStatements( | 117 body = task.nativeEmitter.generateParameterStubStatements( |
| 121 member, isInterceptedMethod, invocationName, | 118 member, isInterceptedMethod, invocationName, |
| 122 parametersBuffer, argumentsBuffer, | 119 parametersBuffer, argumentsBuffer, |
| 123 indexOfLastOptionalArgumentInParameters); | 120 indexOfLastOptionalArgumentInParameters); |
| 124 } else { | 121 } else if (member.isInstanceMember()) { |
| 125 body = [js.return_( | 122 body = [js.return_( |
| 126 js('this')[namer.getNameOfInstanceMember(member)](argumentsBuffer))]; | 123 js('this')[namer.getNameOfInstanceMember(member)](argumentsBuffer))]; |
| 124 } else { | |
| 125 body = [js.return_(namer.elementAccess(member)(argumentsBuffer))]; | |
| 127 } | 126 } |
| 128 | 127 |
| 129 jsAst.Fun function = js.fun(parametersBuffer, body); | 128 jsAst.Fun function = js.fun(parametersBuffer, body); |
| 130 | 129 |
| 131 defineStub(invocationName, function); | 130 addStub(selector, function); |
| 132 | |
| 133 String reflectionName = task.getReflectionName(selector, invocationName); | |
| 134 if (reflectionName != null) { | |
| 135 var reflectable = | |
| 136 js(backend.isAccessibleByReflection(member) ? '1' : '0'); | |
| 137 defineStub('+$reflectionName', reflectable); | |
| 138 } | |
| 139 } | 131 } |
| 140 | 132 |
| 141 void addParameterStubs(FunctionElement member, | 133 void addParameterStubs(FunctionElement member, AddStubFunction defineStub, |
| 142 DefineStubFunction defineStub) { | 134 [bool canTearOff = false]) { |
| 135 if (member.enclosingElement.isClosure()) { | |
| 136 ClosureClassElement cls = member.enclosingElement; | |
| 137 if (cls.supertype.element == compiler.boundClosureClass) { | |
| 138 compiler.internalErrorOnElement(cls.methodElement, 'Bound closure1.'); | |
|
ngeoffray
2013/12/09 11:15:58
Maybe add a better error message.
ahe
2013/12/09 17:06:47
Johnni said the same, so I'm going to repeat my an
ngeoffray
2013/12/09 17:14:55
BoundClosure1 may be enough for you, but besides t
| |
| 139 } | |
| 140 if (cls.methodElement.isInstanceMember()) { | |
| 141 compiler.internalErrorOnElement(cls.methodElement, 'Bound closure2.'); | |
|
ngeoffray
2013/12/09 11:15:58
Ditto.
ahe
2013/12/09 17:06:47
Ditto :-)
| |
| 142 } | |
| 143 } | |
| 144 | |
| 143 // We fill the lists depending on the selector. For example, | 145 // We fill the lists depending on the selector. For example, |
| 144 // take method foo: | 146 // take method foo: |
| 145 // foo(a, b, {c, d}); | 147 // foo(a, b, {c, d}); |
| 146 // | 148 // |
| 147 // We may have multiple ways of calling foo: | 149 // We may have multiple ways of calling foo: |
| 148 // (1) foo(1, 2); | 150 // (1) foo(1, 2); |
| 149 // (2) foo(1, 2, c: 3); | 151 // (2) foo(1, 2, c: 3); |
| 150 // (3) foo(1, 2, d: 4); | 152 // (3) foo(1, 2, d: 4); |
| 151 // (4) foo(1, 2, c: 3, d: 4); | 153 // (4) foo(1, 2, c: 3, d: 4); |
| 152 // (5) foo(1, 2, d: 4, c: 3); | 154 // (5) foo(1, 2, d: 4, c: 3); |
| 153 // | 155 // |
| 154 // What we generate at the call sites are: | 156 // What we generate at the call sites are: |
| 155 // (1) foo$2(1, 2); | 157 // (1) foo$2(1, 2); |
| 156 // (2) foo$3$c(1, 2, 3); | 158 // (2) foo$3$c(1, 2, 3); |
| 157 // (3) foo$3$d(1, 2, 4); | 159 // (3) foo$3$d(1, 2, 4); |
| 158 // (4) foo$4$c$d(1, 2, 3, 4); | 160 // (4) foo$4$c$d(1, 2, 3, 4); |
| 159 // (5) foo$4$c$d(1, 2, 3, 4); | 161 // (5) foo$4$c$d(1, 2, 3, 4); |
| 160 // | 162 // |
| 161 // The stubs we generate are (expressed in Dart): | 163 // The stubs we generate are (expressed in Dart): |
| 162 // (1) foo$2(a, b) => foo$4$c$d(a, b, null, null) | 164 // (1) foo$2(a, b) => foo$4$c$d(a, b, null, null) |
| 163 // (2) foo$3$c(a, b, c) => foo$4$c$d(a, b, c, null); | 165 // (2) foo$3$c(a, b, c) => foo$4$c$d(a, b, c, null); |
| 164 // (3) foo$3$d(a, b, d) => foo$4$c$d(a, b, null, d); | 166 // (3) foo$3$d(a, b, d) => foo$4$c$d(a, b, null, d); |
| 165 // (4) No stub generated, call is direct. | 167 // (4) No stub generated, call is direct. |
| 166 // (5) No stub generated, call is direct. | 168 // (5) No stub generated, call is direct. |
| 167 | 169 |
| 168 // Keep a cache of which stubs have already been generated, to | 170 Set<Selector> selectors = member.isInstanceMember() |
|
ngeoffray
2013/12/09 11:15:58
For readability, I would not initialize this set h
ahe
2013/12/09 17:06:47
Done.
| |
| 169 // avoid duplicates. Note that even if selectors are | 171 ? compiler.codegenWorld.invokedNames[member.name] |
| 170 // canonicalized, we would still need this cache: a typed selector | 172 : null; // No stubs needed for static methods. |
| 171 // on A and a typed selector on B could yield the same stub. | 173 |
| 172 Set<String> generatedStubNames = new Set<String>(); | 174 /// Returns all closure call selectors renamed to match this member. |
| 173 bool isClosureInvocation = | 175 Set<Selector> callSelectorsAsNamed() { |
| 174 member.name == namer.closureInvocationSelectorName; | 176 if (!canTearOff) return null; |
| 175 if (backend.isNeededForReflection(member) || | 177 Set<Selector> callSelectors = compiler.codegenWorld.invokedNames[ |
| 176 (compiler.enabledFunctionApply && isClosureInvocation)) { | 178 namer.closureInvocationSelectorName]; |
| 177 // If [Function.apply] is called, we pessimistically compile all | 179 if (callSelectors == null) return null; |
| 178 // possible stubs for this closure. | 180 return callSelectors.map((Selector callSelector) { |
| 179 FunctionSignature signature = member.computeSignature(compiler); | 181 return new Selector.call( |
| 180 Set<Selector> selectors = signature.optionalParametersAreNamed | 182 member.name, member.getLibrary(), |
| 181 ? computeSeenNamedSelectors(member) | 183 callSelector.argumentCount, callSelector.namedArguments); |
| 182 : computeOptionalSelectors(signature, member); | 184 }).toSet(); |
| 183 for (Selector selector in selectors) { | 185 } |
| 184 addParameterStub(member, selector, defineStub, generatedStubNames); | 186 if (selectors == null) { |
| 185 } | 187 selectors = callSelectorsAsNamed(); |
| 186 if (signature.optionalParametersAreNamed && isClosureInvocation) { | 188 if (selectors == null) return; |
| 187 addCatchAllParameterStub(member, signature, defineStub); | |
| 188 } | |
| 189 } else { | 189 } else { |
| 190 Set<Selector> selectors = compiler.codegenWorld.invokedNames[member.name]; | 190 Set<Selector> callSelectors = callSelectorsAsNamed(); |
| 191 if (selectors == null) return; | 191 if (callSelectors != null) { |
| 192 for (Selector selector in selectors) { | 192 selectors = selectors.union(callSelectors); |
| 193 if (!selector.applies(member, compiler)) continue; | |
| 194 addParameterStub(member, selector, defineStub, generatedStubNames); | |
| 195 } | 193 } |
| 196 } | 194 } |
| 197 } | 195 Set<Selector> untypedSelectors = new Set<Selector>(); |
| 198 | 196 if (selectors != null) { |
| 199 Set<Selector> computeSeenNamedSelectors(FunctionElement element) { | 197 for (Selector selector in selectors) { |
| 200 Set<Selector> selectors = compiler.codegenWorld.invokedNames[element.name]; | 198 if (!selector.appliesUnnamed(member, compiler)) continue; |
| 201 Set<Selector> result = new Set<Selector>(); | 199 if (untypedSelectors.add(selector.asUntyped)) { |
| 202 if (selectors == null) return result; | 200 // TODO(ahe): Is the last argument to [addParameterStub] needed? |
| 203 for (Selector selector in selectors) { | 201 addParameterStub(member, selector, defineStub, new Set<String>()); |
| 204 if (!selector.applies(element, compiler)) continue; | 202 } |
| 205 result.add(selector); | 203 } |
| 206 } | 204 } |
| 207 return result; | 205 if (canTearOff) { |
| 208 } | 206 selectors = compiler.codegenWorld.invokedNames[ |
| 209 | 207 namer.closureInvocationSelectorName]; |
| 210 void addCatchAllParameterStub(FunctionElement member, | 208 if (selectors != null) { |
| 211 FunctionSignature signature, | 209 for (Selector selector in selectors) { |
| 212 DefineStubFunction defineStub) { | 210 selector = new Selector.call( |
| 213 // See Primities.applyFunction in js_helper.dart for details. | 211 member.name, member.getLibrary(), |
| 214 List<jsAst.Property> properties = <jsAst.Property>[]; | 212 selector.argumentCount, selector.namedArguments); |
| 215 for (Element element in signature.orderedOptionalParameters) { | 213 if (!selector.appliesUnnamed(member, compiler)) continue; |
| 216 String jsName = backend.namer.safeName(element.name); | 214 if (untypedSelectors.add(selector)) { |
| 217 Constant value = compiler.constantHandler.initialVariableValues[element]; | 215 // TODO(ahe): Is the last argument to [addParameterStub] needed? |
| 218 jsAst.Expression reference = null; | 216 addParameterStub(member, selector, defineStub, new Set<String>()); |
| 219 if (value == null) { | 217 } |
| 220 reference = new jsAst.LiteralNull(); | 218 } |
| 221 } else { | |
| 222 reference = task.constantReference(value); | |
| 223 } | 219 } |
| 224 properties.add(new jsAst.Property(js.string(jsName), reference)); | |
| 225 } | 220 } |
| 226 defineStub( | |
| 227 backend.namer.callCatchAllName, | |
| 228 js.fun([], js.return_(new jsAst.ObjectInitializer(properties)))); | |
| 229 } | |
| 230 | |
| 231 /** | |
| 232 * Compute the set of possible selectors in the presence of optional | |
| 233 * non-named parameters. | |
| 234 */ | |
| 235 Set<Selector> computeOptionalSelectors(FunctionSignature signature, | |
| 236 FunctionElement element) { | |
| 237 Set<Selector> selectors = new Set<Selector>(); | |
| 238 // Add the selector that does not have any optional argument. | |
| 239 selectors.add(new Selector(SelectorKind.CALL, | |
| 240 element.name, | |
| 241 element.getLibrary(), | |
| 242 signature.requiredParameterCount, | |
| 243 <String>[])); | |
| 244 | |
| 245 // For each optional parameter, we increment the number of passed | |
| 246 // argument. | |
| 247 for (int i = 1; i <= signature.optionalParameterCount; i++) { | |
| 248 selectors.add(new Selector(SelectorKind.CALL, | |
| 249 element.name, | |
| 250 element.getLibrary(), | |
| 251 signature.requiredParameterCount + i, | |
| 252 <String>[])); | |
| 253 } | |
| 254 return selectors; | |
| 255 } | |
| 256 | |
| 257 void emitStaticFunctionGetters(CodeBuffer eagerBuffer) { | |
| 258 task.addComment('Static function getters', task.mainBuffer); | |
| 259 for (FunctionElement element in | |
| 260 Elements.sortedByPosition(staticGetters.keys)) { | |
| 261 Element closure = staticGetters[element]; | |
| 262 CodeBuffer buffer = | |
| 263 task.isDeferred(element) ? task.deferredConstants : eagerBuffer; | |
| 264 String closureClass = namer.isolateAccess(closure); | |
| 265 String name = namer.getStaticClosureName(element); | |
| 266 | |
| 267 String closureName = namer.getStaticClosureName(element); | |
| 268 jsAst.Node assignment = js( | |
| 269 'init.globalFunctions["$closureName"] =' | |
| 270 ' ${namer.globalObjectFor(element)}.$name =' | |
| 271 ' new $closureClass(#, "$closureName")', | |
| 272 namer.elementAccess(element)); | |
| 273 buffer.write(jsAst.prettyPrint(assignment, compiler)); | |
| 274 buffer.write('$N'); | |
| 275 } | |
| 276 } | |
| 277 | |
| 278 void emitStaticFunctionClosures() { | |
| 279 Set<FunctionElement> functionsNeedingGetter = | |
| 280 compiler.codegenWorld.staticFunctionsNeedingGetter; | |
| 281 for (FunctionElement element in | |
| 282 Elements.sortedByPosition(functionsNeedingGetter)) { | |
| 283 String superName = namer.getNameOfClass(compiler.closureClass); | |
| 284 int parameterCount = element.functionSignature.parameterCount; | |
| 285 String name = 'Closure\$$parameterCount'; | |
| 286 assert(task.instantiatedClasses.contains(compiler.closureClass)); | |
| 287 | |
| 288 ClassElement closureClassElement = new ClosureClassElement( | |
| 289 null, name, compiler, element, | |
| 290 element.getCompilationUnit()); | |
| 291 // Now add the methods on the closure class. The instance method does not | |
| 292 // have the correct name. Since [addParameterStubs] use the name to create | |
| 293 // its stubs we simply create a fake element with the correct name. | |
| 294 // Note: the callElement will not have any enclosingElement. | |
| 295 FunctionElement callElement = | |
| 296 new ClosureInvocationElement(namer.closureInvocationSelectorName, | |
| 297 element); | |
| 298 | |
| 299 String invocationName = namer.instanceMethodName(callElement); | |
| 300 String mangledName = namer.getNameOfClass(closureClassElement); | |
| 301 | |
| 302 // Define the constructor with a name so that Object.toString can | |
| 303 // find the class name of the closure class. | |
| 304 ClassBuilder closureBuilder = new ClassBuilder(); | |
| 305 // If a static function is used as a closure we need to add its name | |
| 306 // in case it is used in spawnFunction. | |
| 307 String methodName = namer.STATIC_CLOSURE_NAME_NAME; | |
| 308 List<String> fieldNames = <String>[invocationName, methodName]; | |
| 309 closureBuilder.addProperty('', | |
| 310 js.string("$superName;${fieldNames.join(',')}")); | |
| 311 | |
| 312 addParameterStubs(callElement, closureBuilder.addProperty); | |
| 313 | |
| 314 void emitFunctionTypeSignature(Element method, FunctionType methodType) { | |
| 315 RuntimeTypes rti = backend.rti; | |
| 316 // [:() => null:] is dummy encoding of [this] which is never needed for | |
| 317 // the encoding of the type of the static [method]. | |
| 318 jsAst.Expression encoding = | |
| 319 rti.getSignatureEncoding(methodType, js('null')); | |
| 320 String operatorSignature = namer.operatorSignature(); | |
| 321 // TODO(johnniwinther): Make MiniJsParser support function expressions. | |
| 322 closureBuilder.addProperty(operatorSignature, encoding); | |
| 323 } | |
| 324 | |
| 325 FunctionType methodType = element.computeType(compiler); | |
| 326 Map<FunctionType, bool> functionTypeChecks = | |
| 327 task.typeTestEmitter.getFunctionTypeChecksOn(methodType); | |
| 328 task.typeTestEmitter.generateFunctionTypeTests( | |
| 329 element, methodType, functionTypeChecks, | |
| 330 emitFunctionTypeSignature); | |
| 331 | |
| 332 closureClassElement = | |
| 333 addClosureIfNew(closureBuilder, closureClassElement, fieldNames); | |
| 334 staticGetters[element] = closureClassElement; | |
| 335 | |
| 336 } | |
| 337 } | |
| 338 | |
| 339 ClassElement addClosureIfNew(ClassBuilder builder, | |
| 340 ClassElement closure, | |
| 341 List<String> fieldNames) { | |
| 342 String key = | |
| 343 jsAst.prettyPrint(builder.toObjectInitializer(), compiler).getText(); | |
| 344 return methodClosures.putIfAbsent(key, () { | |
| 345 String mangledName = namer.getNameOfClass(closure); | |
| 346 emitClosureInPrecompiledFunction(mangledName, fieldNames); | |
| 347 return closure; | |
| 348 }); | |
| 349 } | |
| 350 | |
| 351 void emitClosureInPrecompiledFunction(String mangledName, | |
| 352 List<String> fieldNames) { | |
| 353 List<String> fields = fieldNames; | |
| 354 String constructorName = mangledName; | |
| 355 task.precompiledFunction.add(new jsAst.FunctionDeclaration( | |
| 356 new jsAst.VariableDeclaration(constructorName), | |
| 357 js.fun(fields, fields.map( | |
| 358 (name) => js('this.$name = $name')).toList()))); | |
| 359 task.precompiledFunction.addAll([ | |
| 360 js('$constructorName.builtin\$cls = "$constructorName"'), | |
| 361 js('\$desc=\$collectedClasses.$constructorName'), | |
| 362 js.if_('\$desc instanceof Array', js('\$desc = \$desc[1]')), | |
| 363 js('$constructorName.prototype = \$desc'), | |
| 364 ]); | |
| 365 | |
| 366 task.precompiledConstructorNames.add(js(constructorName)); | |
| 367 } | 221 } |
| 368 | 222 |
| 369 /** | 223 /** |
| 370 * Documentation wanted -- johnniwinther | 224 * Documentation wanted -- johnniwinther |
| 371 * | 225 * |
| 372 * Invariant: [member] must be a declaration element. | |
| 373 */ | |
| 374 void emitDynamicFunctionGetter(FunctionElement member, | |
| 375 DefineStubFunction defineStub) { | |
| 376 assert(invariant(member, member.isDeclaration)); | |
| 377 assert(task.instantiatedClasses.contains(compiler.boundClosureClass)); | |
| 378 // For every method that has the same name as a property-get we create a | |
| 379 // getter that returns a bound closure. Say we have a class 'A' with method | |
| 380 // 'foo' and somewhere in the code there is a dynamic property get of | |
| 381 // 'foo'. Then we generate the following code (in pseudo Dart/JavaScript): | |
| 382 // | |
| 383 // class A { | |
| 384 // foo(x, y, z) { ... } // Original function. | |
| 385 // get foo { return new BoundClosure499(this, "foo"); } | |
| 386 // } | |
| 387 // class BoundClosure499 extends BoundClosure { | |
| 388 // BoundClosure499(this.self, this.name); | |
| 389 // $call3(x, y, z) { return self[name](x, y, z); } | |
| 390 // } | |
| 391 | |
| 392 bool hasOptionalParameters = member.optionalParameterCount(compiler) != 0; | |
| 393 int parameterCount = member.parameterCount(compiler); | |
| 394 | |
| 395 // Intercepted methods take an extra parameter, which is the | |
| 396 // receiver of the call. | |
| 397 bool inInterceptor = backend.isInterceptedMethod(member); | |
| 398 List<String> fieldNames = <String>[]; | |
| 399 compiler.boundClosureClass.forEachInstanceField((_, Element field) { | |
| 400 fieldNames.add(namer.instanceFieldPropertyName(field)); | |
| 401 }); | |
| 402 | |
| 403 ClassElement classElement = member.getEnclosingClass(); | |
| 404 String name = inInterceptor | |
| 405 ? 'BoundClosure\$i${parameterCount}' | |
| 406 : 'BoundClosure\$${parameterCount}'; | |
| 407 | |
| 408 ClassElement closureClassElement = new ClosureClassElement( | |
| 409 null, name, compiler, member, | |
| 410 member.getCompilationUnit()); | |
| 411 String superName = namer.getNameOfClass(closureClassElement.superclass); | |
| 412 | |
| 413 // Define the constructor with a name so that Object.toString can | |
| 414 // find the class name of the closure class. | |
| 415 ClassBuilder boundClosureBuilder = new ClassBuilder(); | |
| 416 boundClosureBuilder.addProperty('', | |
| 417 js.string("$superName;${fieldNames.join(',')}")); | |
| 418 // Now add the methods on the closure class. The instance method does not | |
| 419 // have the correct name. Since [addParameterStubs] use the name to create | |
| 420 // its stubs we simply create a fake element with the correct name. | |
| 421 // Note: the callElement will not have any enclosingElement. | |
| 422 FunctionElement callElement = new ClosureInvocationElement( | |
| 423 namer.closureInvocationSelectorName, member); | |
| 424 | |
| 425 String invocationName = namer.instanceMethodName(callElement); | |
| 426 | |
| 427 List<String> parameters = <String>[]; | |
| 428 List<jsAst.Expression> arguments = | |
| 429 <jsAst.Expression>[js('this')[fieldNames[0]]]; | |
| 430 if (inInterceptor) { | |
| 431 arguments.add(js('this')[fieldNames[2]]); | |
| 432 } | |
| 433 for (int i = 0; i < parameterCount; i++) { | |
| 434 String name = 'p$i'; | |
| 435 parameters.add(name); | |
| 436 arguments.add(js(name)); | |
| 437 } | |
| 438 | |
| 439 jsAst.Expression fun = js.fun( | |
| 440 parameters, | |
| 441 js.return_( | |
| 442 js('this')[fieldNames[1]]['call'](arguments))); | |
| 443 boundClosureBuilder.addProperty(invocationName, fun); | |
| 444 | |
| 445 addParameterStubs(callElement, boundClosureBuilder.addProperty); | |
| 446 | |
| 447 void emitFunctionTypeSignature(Element method, FunctionType methodType) { | |
| 448 jsAst.Expression encoding = backend.rti.getSignatureEncoding( | |
| 449 methodType, js('this')[fieldNames[0]]); | |
| 450 String operatorSignature = namer.operatorSignature(); | |
| 451 boundClosureBuilder.addProperty(operatorSignature, encoding); | |
| 452 } | |
| 453 | |
| 454 DartType memberType = member.computeType(compiler); | |
| 455 Map<FunctionType, bool> functionTypeChecks = | |
| 456 task.typeTestEmitter.getFunctionTypeChecksOn(memberType); | |
| 457 | |
| 458 task.typeTestEmitter.generateFunctionTypeTests( | |
| 459 member, memberType, functionTypeChecks, | |
| 460 emitFunctionTypeSignature); | |
| 461 | |
| 462 closureClassElement = | |
| 463 addClosureIfNew(boundClosureBuilder, closureClassElement, fieldNames); | |
| 464 | |
| 465 String closureClass = namer.isolateAccess(closureClassElement); | |
| 466 | |
| 467 // And finally the getter. | |
| 468 String getterName = namer.getterName(member); | |
| 469 String targetName = namer.instanceMethodName(member); | |
| 470 | |
| 471 parameters = <String>[]; | |
| 472 jsAst.PropertyAccess method = | |
| 473 backend.namer.elementAccess(classElement)['prototype'][targetName]; | |
| 474 arguments = <jsAst.Expression>[js('this'), method]; | |
| 475 | |
| 476 if (inInterceptor) { | |
| 477 String receiverArg = fieldNames[2]; | |
| 478 parameters.add(receiverArg); | |
| 479 arguments.add(js(receiverArg)); | |
| 480 } else { | |
| 481 // Put null in the intercepted receiver field. | |
| 482 arguments.add(new jsAst.LiteralNull()); | |
| 483 } | |
| 484 | |
| 485 arguments.add(js.string(targetName)); | |
| 486 | |
| 487 jsAst.Expression getterFunction = js.fun( | |
| 488 parameters, js.return_(js(closureClass).newWith(arguments))); | |
| 489 | |
| 490 defineStub(getterName, getterFunction); | |
| 491 } | |
| 492 | |
| 493 /** | |
| 494 * Documentation wanted -- johnniwinther | |
| 495 * | |
| 496 * Invariant: [member] must be a declaration element. | 226 * Invariant: [member] must be a declaration element. |
| 497 */ | 227 */ |
| 498 void emitCallStubForGetter(Element member, | 228 void emitCallStubForGetter(Element member, |
| 499 Set<Selector> selectors, | 229 Set<Selector> selectors, |
| 500 DefineStubFunction defineStub) { | 230 AddPropertyFunction addProperty) { |
| 501 assert(invariant(member, member.isDeclaration)); | 231 assert(invariant(member, member.isDeclaration)); |
| 502 LibraryElement memberLibrary = member.getLibrary(); | 232 LibraryElement memberLibrary = member.getLibrary(); |
| 503 // If the method is intercepted, the stub gets the | 233 // If the method is intercepted, the stub gets the |
| 504 // receiver explicitely and we need to pass it to the getter call. | 234 // receiver explicitely and we need to pass it to the getter call. |
| 505 bool isInterceptedMethod = backend.isInterceptedMethod(member); | 235 bool isInterceptedMethod = backend.isInterceptedMethod(member); |
| 506 | 236 |
| 507 const String receiverArgumentName = r'$receiver'; | 237 const String receiverArgumentName = r'$receiver'; |
| 508 | 238 |
| 509 jsAst.Expression buildGetter() { | 239 jsAst.Expression buildGetter() { |
| 510 if (member.isGetter()) { | 240 if (member.isGetter()) { |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 542 for (int i = 0; i < selector.argumentCount; i++) { | 272 for (int i = 0; i < selector.argumentCount; i++) { |
| 543 String name = 'arg$i'; | 273 String name = 'arg$i'; |
| 544 parameters.add(new jsAst.Parameter(name)); | 274 parameters.add(new jsAst.Parameter(name)); |
| 545 arguments.add(js(name)); | 275 arguments.add(js(name)); |
| 546 } | 276 } |
| 547 | 277 |
| 548 jsAst.Fun function = js.fun( | 278 jsAst.Fun function = js.fun( |
| 549 parameters, | 279 parameters, |
| 550 js.return_(buildGetter()[closureCallName](arguments))); | 280 js.return_(buildGetter()[closureCallName](arguments))); |
| 551 | 281 |
| 552 defineStub(invocationName, function); | 282 addProperty(invocationName, function); |
| 553 } | 283 } |
| 554 } | 284 } |
| 555 } | 285 } |
| 556 | 286 |
| 557 /** | 287 /** |
| 558 * Documentation wanted -- johnniwinther | 288 * Documentation wanted -- johnniwinther |
| 559 * | 289 * |
| 560 * Invariant: [member] must be a declaration element. | 290 * Invariant: [member] must be a declaration element. |
| 561 */ | 291 */ |
| 562 void emitExtraAccessors(Element member, ClassBuilder builder) { | 292 void emitExtraAccessors(Element member, ClassBuilder builder) { |
| 563 assert(invariant(member, member.isDeclaration)); | 293 assert(invariant(member, member.isDeclaration)); |
| 564 if (member.isGetter() || member.isField()) { | 294 if (member.isGetter() || member.isField()) { |
| 565 Set<Selector> selectors = compiler.codegenWorld.invokedNames[member.name]; | 295 Set<Selector> selectors = compiler.codegenWorld.invokedNames[member.name]; |
| 566 if (selectors != null && !selectors.isEmpty) { | 296 if (selectors != null && !selectors.isEmpty) { |
| 567 emitCallStubForGetter(member, selectors, builder.addProperty); | 297 emitCallStubForGetter(member, selectors, builder.addProperty); |
| 568 } | 298 } |
| 569 } else if (member.isFunction()) { | |
| 570 if (compiler.codegenWorld.hasInvokedGetter(member, compiler)) { | |
| 571 emitDynamicFunctionGetter(member, builder.addProperty); | |
| 572 } | |
| 573 } | 299 } |
| 574 } | 300 } |
| 575 | 301 |
| 576 void addMember(Element member, ClassBuilder builder) { | 302 void addMember(Element member, ClassBuilder builder) { |
| 577 assert(invariant(member, member.isDeclaration)); | 303 assert(invariant(member, member.isDeclaration)); |
| 578 | 304 |
| 579 if (member.isField()) { | 305 if (member.isField()) { |
| 580 addMemberField(member, builder); | 306 addMemberField(member, builder); |
| 581 } else if (member.isFunction() || | 307 } else if (member.isFunction() || |
| 582 member.isGenerativeConstructorBody() || | 308 member.isGenerativeConstructorBody() || |
| 583 member.isGenerativeConstructor() || | 309 member.isGenerativeConstructor() || |
| 584 member.isAccessor()) { | 310 member.isAccessor()) { |
| 585 addMemberMethod(member, builder); | 311 addMemberMethod(member, builder); |
| 586 } else { | 312 } else { |
| 587 compiler.internalErrorOnElement( | 313 compiler.internalErrorOnElement( |
| 588 member, 'unexpected kind: "${member.kind}"'); | 314 member, 'unexpected kind: "${member.kind}"'); |
| 589 } | 315 } |
| 590 if (member.isInstanceMember()) emitExtraAccessors(member, builder); | 316 if (member.isInstanceMember()) emitExtraAccessors(member, builder); |
| 591 } | 317 } |
| 592 | 318 |
| 593 void addMemberMethod(FunctionElement member, ClassBuilder builder) { | 319 void addMemberMethod(FunctionElement member, ClassBuilder builder) { |
| 594 if (member.isAbstract) return; | 320 if (member.isAbstract) return; |
| 595 jsAst.Expression code = backend.generatedCode[member]; | 321 jsAst.Expression code = backend.generatedCode[member]; |
| 596 if (code == null) return; | 322 if (code == null) return; |
| 597 String name = namer.getNameOfMember(member); | 323 String name = namer.getNameOfMember(member); |
| 598 if (backend.isInterceptedMethod(member)) { | 324 task.interceptorEmitter.recordMangledNameOfMemberMethod(member, name); |
| 599 task.interceptorEmitter.interceptorInvocationNames.add(name); | 325 FunctionSignature parameters = member.computeSignature(compiler); |
| 326 bool needsStubs = !parameters.optionalParameters.isEmpty; | |
| 327 bool canTearOff = false; | |
| 328 bool isClosure = false; | |
| 329 String tearOffName; | |
| 330 if (!member.isFunction() || member.isConstructor() || member.isAccessor()) { | |
| 331 canTearOff = false; | |
| 332 } else if (member.isInstanceMember()) { | |
| 333 if (member.getEnclosingClass().isClosure()) { | |
| 334 canTearOff = false; | |
| 335 isClosure = true; | |
| 336 } else { | |
| 337 // Careful with operators. | |
|
ngeoffray
2013/12/09 11:15:58
I don't understand this comment.
ahe
2013/12/09 17:06:47
Changed it to:
// TODO(ahe): What happens
| |
| 338 canTearOff = compiler.codegenWorld.hasInvokedGetter(member, compiler); | |
| 339 tearOffName = namer.getterName(member); | |
| 340 } | |
| 341 } else { | |
| 342 canTearOff = | |
| 343 compiler.codegenWorld.staticFunctionsNeedingGetter.contains(member); | |
| 344 tearOffName = namer.getStaticClosureName(member); | |
| 600 } | 345 } |
| 601 code = task.metadataEmitter.extendWithMetadata(member, code); | 346 |
| 602 builder.addProperty(name, code); | 347 bool canBeReflected = backend.isAccessibleByReflection(member); |
| 603 String reflectionName = task.getReflectionName(member, name); | 348 bool needStructuredInfo = |
| 604 if (reflectionName != null) { | 349 canTearOff || canBeReflected || compiler.enabledFunctionApply; |
| 605 var reflectable = | 350 if (!needStructuredInfo) { |
| 606 js(backend.isAccessibleByReflection(member) ? '1' : '0'); | 351 builder.addProperty(name, code); |
| 607 builder.addProperty('+$reflectionName', reflectable); | 352 if (needsStubs) { |
| 608 jsAst.Node defaultValues = | 353 addParameterStubs( |
| 609 task.metadataEmitter.reifyDefaultArguments(member); | 354 member, |
| 610 if (defaultValues != null) { | 355 (Selector selector, jsAst.Fun function) { |
| 611 String unmangledName = member.name; | 356 builder.addProperty(namer.invocationName(selector), function); |
| 612 builder.addProperty('*$unmangledName', defaultValues); | 357 }); |
| 613 } | 358 } |
| 359 return; | |
| 614 } | 360 } |
| 615 if (member.isInstanceMember()) { | 361 |
| 616 // TODO(ahe): Where is this done for static/top-level methods? | 362 if (canTearOff) { |
| 617 FunctionSignature parameters = member.computeSignature(compiler); | 363 assert(invariant(member, !member.isGenerativeConstructor())); |
| 618 if (!parameters.optionalParameters.isEmpty) { | 364 assert(invariant(member, !member.isGenerativeConstructorBody())); |
| 619 addParameterStubs(member, builder.addProperty); | 365 assert(invariant(member, !member.isConstructor())); |
| 366 } | |
| 367 | |
| 368 // This element is needed for reflection or needs additional stubs. So we | |
| 369 // need to retain additional information. | |
| 370 | |
| 371 // The information is stored in an array with this format: | |
| 372 // | |
| 373 // 1. The JS function for this member. | |
| 374 // 2. First stub. | |
| 375 // 3. Name of first stub. | |
| 376 // ... | |
| 377 // M. Call name of this member. | |
| 378 // M+1. Call name of first stub. | |
| 379 // ... | |
| 380 // N. Getter name for tearOff. | |
| 381 // N+1. (Required parameter count << 1) + (member.isAccessor() ? 1 : 0). | |
| 382 // N+2. (Optional parameter count << 1) + | |
| 383 // (parameters.optionalParametersAreNamed ? 1 : 0). | |
| 384 // N+3. Index to function type in constant pool. | |
| 385 // N+4. First default argument. | |
| 386 // ... | |
| 387 // O. First parameter name (if needed for reflection or Function.apply). | |
| 388 // ... | |
| 389 // P. Unmangled name (if reflectable). | |
| 390 // P+1. First metadata (if reflectable). | |
| 391 // ... | |
| 392 | |
| 393 List expressions = []; | |
| 394 | |
| 395 String callSelectorString = 'null'; | |
| 396 if (member.isFunction()) { | |
| 397 Selector callSelector = | |
| 398 new Selector.fromElement(member, compiler).toCallSelector(); | |
| 399 callSelectorString = '"${namer.invocationName(callSelector)}"'; | |
| 400 } | |
| 401 | |
| 402 // On [requiredParameterCount], the lower bit is set if this method can be | |
| 403 // called reflectively. | |
| 404 int requiredParameterCount = parameters.requiredParameterCount << 1; | |
| 405 if (member.isAccessor()) requiredParameterCount++; | |
| 406 | |
| 407 int optionalParameterCount = parameters.optionalParameterCount << 1; | |
| 408 if (parameters.optionalParametersAreNamed) optionalParameterCount++; | |
| 409 | |
| 410 expressions.add(code); | |
| 411 | |
| 412 // TODO(ahe): Remove comments from output. | |
| 413 List tearOffInfo = | |
| 414 [new jsAst.LiteralString('$callSelectorString /* tearOffInfo */')]; | |
| 415 | |
| 416 if (needsStubs || canTearOff) { | |
| 417 addParameterStubs(member, (Selector selector, jsAst.Fun function) { | |
| 418 expressions.add(function); | |
| 419 if (member.isInstanceMember()) { | |
| 420 Set invokedSelectors = | |
| 421 compiler.codegenWorld.invokedNames[member.name]; | |
| 422 if (invokedSelectors != null && invokedSelectors.contains(selector)) { | |
| 423 expressions.add(js.string(namer.invocationName(selector))); | |
| 424 } else { | |
| 425 // Don't add a stub for calling this as a regular instance method, | |
| 426 // we only need the "call" stub for implicit closures of this | |
| 427 // method. | |
| 428 expressions.add("null"); | |
| 429 } | |
| 430 } else { | |
| 431 // Static methods don't need "named" stubs as the default arguments | |
| 432 // are inlined at call sites. But static methods might need "call" | |
| 433 // stubs for implicit closures. | |
| 434 expressions.add("null"); | |
| 435 // TOOD(ahe): Since we know when reading static data versus instance | |
| 436 // data, we can eliminate this element. | |
|
ngeoffray
2013/12/09 11:15:58
What does eliminate mean here?
ahe
2013/12/09 17:06:47
That it doesn't need to be added to the reflective
| |
| 437 } | |
| 438 Set<Selector> callSelectors = compiler.codegenWorld.invokedNames[ | |
| 439 namer.closureInvocationSelectorName]; | |
| 440 Selector callSelector = selector.toCallSelector(); | |
| 441 String callSelectorString = 'null'; | |
| 442 if (canTearOff && callSelectors != null && | |
| 443 callSelectors.contains(callSelector)) { | |
| 444 callSelectorString = '"${namer.invocationName(callSelector)}"'; | |
| 445 } | |
| 446 tearOffInfo.add( | |
| 447 new jsAst.LiteralString('$callSelectorString /* tearOffInfo */')); | |
| 448 }, canTearOff); | |
| 449 } | |
| 450 | |
| 451 jsAst.Expression memberTypeExpression; | |
| 452 if ((canTearOff || canBeReflected) && | |
| 453 !member.isGenerativeConstructorBody()) { | |
|
ngeoffray
2013/12/09 11:15:58
A generative constructor body can be reflected?
ahe
2013/12/09 17:06:47
Yeah. It is kind of a hack, but these end up being
| |
| 454 DartType memberType = member.computeType(compiler); | |
| 455 if (memberType.containsTypeVariables) { | |
| 456 jsAst.Expression thisAccess = js(r'this.$receiver'); | |
| 457 memberTypeExpression = | |
| 458 backend.rti.getSignatureEncoding(memberType, thisAccess); | |
| 459 } else { | |
| 460 memberTypeExpression = | |
| 461 js.toExpression(task.metadataEmitter.reifyType(memberType)); | |
| 620 } | 462 } |
| 463 } else { | |
| 464 memberTypeExpression = js('null'); | |
| 621 } | 465 } |
| 466 | |
| 467 expressions | |
| 468 ..addAll(tearOffInfo) | |
| 469 ..add((tearOffName == null || member.isAccessor()) | |
| 470 ? js("null") : js.string(tearOffName)) | |
| 471 ..add(requiredParameterCount) | |
| 472 ..add(optionalParameterCount) | |
| 473 ..add(memberTypeExpression) | |
| 474 ..addAll(task.metadataEmitter.reifyDefaultArguments(member)); | |
| 475 | |
| 476 if (canBeReflected || compiler.enabledFunctionApply) { | |
| 477 parameters.orderedForEachParameter((Element parameter) { | |
| 478 expressions.add(task.metadataEmitter.reifyName(parameter.name)); | |
| 479 }); | |
| 480 } | |
| 481 if (canBeReflected) { | |
| 482 jsAst.LiteralString reflectionName; | |
| 483 if (member.isConstructor()) { | |
| 484 String reflectionNameString = task.getReflectionName(member, name); | |
| 485 reflectionName = | |
| 486 new jsAst.LiteralString( | |
| 487 '"new ${Elements.reconstructConstructorName(member)}"' | |
| 488 ' /* $reflectionNameString */'); | |
| 489 } else { | |
| 490 reflectionName = js.string(member.name); | |
| 491 } | |
| 492 expressions | |
| 493 ..add(reflectionName) | |
| 494 ..addAll(task.metadataEmitter.computeMetadata(member)); | |
| 495 } else if (isClosure && compiler.enabledFunctionApply) { | |
| 496 expressions.add(js.string(member.name)); | |
| 497 } | |
| 498 | |
| 499 builder.addProperty(name, js.toExpression(expressions)); | |
| 622 } | 500 } |
| 623 | 501 |
| 624 void addMemberField(VariableElement member, ClassBuilder builder) { | 502 void addMemberField(VariableElement member, ClassBuilder builder) { |
| 625 // For now, do nothing. | 503 // For now, do nothing. |
| 626 } | 504 } |
| 627 } | 505 } |
| OLD | NEW |