| 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; | 5 part of dart2js.js_emitter; |
| 6 | 6 |
| 7 class ParameterStubGenerator { | 7 class ParameterStubGenerator { |
| 8 static final Set<Selector> emptySelectorSet = new Set<Selector>(); |
| 9 |
| 8 final Namer namer; | 10 final Namer namer; |
| 9 final Compiler compiler; | 11 final Compiler compiler; |
| 10 final JavaScriptBackend backend; | 12 final JavaScriptBackend backend; |
| 11 | 13 |
| 12 ParameterStubGenerator(this.compiler, this.namer, this.backend); | 14 ParameterStubGenerator(this.compiler, this.namer, this.backend); |
| 13 | 15 |
| 14 Emitter get emitter => backend.emitter.emitter; | 16 Emitter get emitter => backend.emitter.emitter; |
| 15 CodeEmitterTask get emitterTask => backend.emitter; | 17 CodeEmitterTask get emitterTask => backend.emitter; |
| 16 | 18 |
| 17 bool needsSuperGetter(FunctionElement element) => | 19 bool needsSuperGetter(FunctionElement element) => |
| 18 compiler.codegenWorld.methodsNeedingSuperGetter.contains(element); | 20 compiler.codegenWorld.methodsNeedingSuperGetter.contains(element); |
| 19 | 21 |
| 20 /** | 22 /** |
| 21 * Generate stubs to handle invocation of methods with optional | 23 * Generates stubs to handle invocation of methods with optional |
| 22 * arguments. | 24 * arguments. |
| 23 * | 25 * |
| 24 * A method like [: foo([x]) :] may be invoked by the following | 26 * A method like `foo([x])` may be invoked by the following |
| 25 * calls: [: foo(), foo(1), foo(x: 1) :]. See the sources of this | 27 * calls: `foo(), foo(1), foo(x: 1)`. This method generates the stub for the |
| 26 * function for detailed examples. | 28 * given [selector] and returns the generated [ParameterStubMethod]. |
| 29 * |
| 30 * Returns null if no stub is needed. |
| 31 * |
| 32 * Members may be invoked in two ways: directly, or through a closure. In the |
| 33 * latter case the caller invokes the closure's `call` method. This method |
| 34 * accepts two selectors. The returned stub method has the corresponding |
| 35 * name [ParameterStubMethod.name] and [ParameterStubMethod.callName] set if |
| 36 * the input selector is non-null (and the member needs a stub). |
| 27 */ | 37 */ |
| 28 jsAst.Expression generateParameterStub(FunctionElement member, | 38 ParameterStubMethod generateParameterStub(FunctionElement member, |
| 29 Selector selector) { | 39 Selector selector, |
| 40 Selector callSelector) { |
| 30 FunctionSignature parameters = member.functionSignature; | 41 FunctionSignature parameters = member.functionSignature; |
| 31 int positionalArgumentCount = selector.positionalArgumentCount; | 42 int positionalArgumentCount = selector.positionalArgumentCount; |
| 32 if (positionalArgumentCount == parameters.parameterCount) { | 43 if (positionalArgumentCount == parameters.parameterCount) { |
| 33 assert(selector.namedArgumentCount == 0); | 44 assert(selector.namedArgumentCount == 0); |
| 34 return null; | 45 return null; |
| 35 } | 46 } |
| 36 if (parameters.optionalParametersAreNamed | 47 if (parameters.optionalParametersAreNamed |
| 37 && selector.namedArgumentCount == parameters.optionalParameterCount) { | 48 && selector.namedArgumentCount == parameters.optionalParameterCount) { |
| 38 // If the selector has the same number of named arguments as the element, | 49 // If the selector has the same number of named arguments as the element, |
| 39 // we don't need to add a stub. The call site will hit the method | 50 // we don't need to add a stub. The call site will hit the method |
| 40 // directly. | 51 // directly. |
| 41 return null; | 52 return null; |
| 42 } | 53 } |
| 43 JavaScriptConstantCompiler handler = backend.constants; | 54 JavaScriptConstantCompiler handler = backend.constants; |
| 44 List<String> names = selector.getOrderedNamedArguments(); | 55 List<String> names = selector.getOrderedNamedArguments(); |
| 45 | 56 |
| 46 String invocationName = namer.invocationName(selector); | |
| 47 | |
| 48 bool isInterceptedMethod = backend.isInterceptedMethod(member); | 57 bool isInterceptedMethod = backend.isInterceptedMethod(member); |
| 49 | 58 |
| 50 // If the method is intercepted, we need to also pass the actual receiver. | 59 // If the method is intercepted, we need to also pass the actual receiver. |
| 51 int extraArgumentCount = isInterceptedMethod ? 1 : 0; | 60 int extraArgumentCount = isInterceptedMethod ? 1 : 0; |
| 52 // Use '$receiver' to avoid clashes with other parameter names. Using | 61 // Use '$receiver' to avoid clashes with other parameter names. Using |
| 53 // '$receiver' works because [:namer.safeName:] used for getting parameter | 62 // '$receiver' works because [:namer.safeName:] used for getting parameter |
| 54 // names never returns a name beginning with a single '$'. | 63 // names never returns a name beginning with a single '$'. |
| 55 String receiverArgumentName = r'$receiver'; | 64 String receiverArgumentName = r'$receiver'; |
| 56 | 65 |
| 57 // The parameters that this stub takes. | 66 // The parameters that this stub takes. |
| (...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 104 argumentsBuffer[count] = emitter.constantReference(value); | 113 argumentsBuffer[count] = emitter.constantReference(value); |
| 105 } | 114 } |
| 106 } | 115 } |
| 107 } | 116 } |
| 108 count++; | 117 count++; |
| 109 }); | 118 }); |
| 110 | 119 |
| 111 var body; // List or jsAst.Statement. | 120 var body; // List or jsAst.Statement. |
| 112 if (member.hasFixedBackendName) { | 121 if (member.hasFixedBackendName) { |
| 113 body = emitterTask.nativeEmitter.generateParameterStubStatements( | 122 body = emitterTask.nativeEmitter.generateParameterStubStatements( |
| 114 member, isInterceptedMethod, invocationName, | 123 member, isInterceptedMethod, namer.invocationName(selector), |
| 115 parametersBuffer, argumentsBuffer, | 124 parametersBuffer, argumentsBuffer, |
| 116 indexOfLastOptionalArgumentInParameters); | 125 indexOfLastOptionalArgumentInParameters); |
| 117 } else if (member.isInstanceMember) { | 126 } else if (member.isInstanceMember) { |
| 118 if (needsSuperGetter(member)) { | 127 if (needsSuperGetter(member)) { |
| 119 ClassElement superClass = member.enclosingClass; | 128 ClassElement superClass = member.enclosingClass; |
| 120 String methodName = namer.getNameOfInstanceMember(member); | 129 String methodName = namer.getNameOfInstanceMember(member); |
| 121 // When redirecting, we must ensure that we don't end up in a subclass. | 130 // When redirecting, we must ensure that we don't end up in a subclass. |
| 122 // We thus can't just invoke `this.foo$1.call(filledInArguments)`. | 131 // We thus can't just invoke `this.foo$1.call(filledInArguments)`. |
| 123 // Instead we need to call the statically resolved target. | 132 // Instead we need to call the statically resolved target. |
| 124 // `<class>.prototype.bar$1.call(this, argument0, ...)`. | 133 // `<class>.prototype.bar$1.call(this, argument0, ...)`. |
| 125 body = js.statement( | 134 body = js.statement( |
| 126 'return #.#.call(this, #);', | 135 'return #.#.call(this, #);', |
| 127 [backend.emitter.prototypeAccess(superClass, | 136 [backend.emitter.prototypeAccess(superClass, |
| 128 hasBeenInstantiated: true), | 137 hasBeenInstantiated: true), |
| 129 methodName, | 138 methodName, |
| 130 argumentsBuffer]); | 139 argumentsBuffer]); |
| 131 } else { | 140 } else { |
| 132 body = js.statement( | 141 body = js.statement( |
| 133 'return this.#(#);', | 142 'return this.#(#);', |
| 134 [namer.getNameOfInstanceMember(member), argumentsBuffer]); | 143 [namer.getNameOfInstanceMember(member), argumentsBuffer]); |
| 135 } | 144 } |
| 136 } else { | 145 } else { |
| 137 body = js.statement('return #(#)', | 146 body = js.statement('return #(#)', |
| 138 [emitter.staticFunctionAccess(member), argumentsBuffer]); | 147 [emitter.staticFunctionAccess(member), argumentsBuffer]); |
| 139 } | 148 } |
| 140 | 149 |
| 141 jsAst.Fun function = js('function(#) { #; }', [parametersBuffer, body]); | 150 jsAst.Fun function = js('function(#) { #; }', [parametersBuffer, body]); |
| 142 | 151 |
| 143 return function; | 152 String name = namer.invocationName(selector); |
| 153 String callName = |
| 154 (callSelector != null) ? namer.invocationName(callSelector) : null; |
| 155 return new ParameterStubMethod(name, callName, function); |
| 144 } | 156 } |
| 145 | 157 |
| 146 Map<Selector, jsAst.Expression> generateParameterStubs(FunctionElement member, | 158 // We fill the lists depending on possible/invoked selectors. For example, |
| 147 [bool canTearOff = false]) { | 159 // take method foo: |
| 148 Map<Selector, jsAst.Expression> generatedStubs | 160 // foo(a, b, {c, d}); |
| 149 = <Selector, jsAst.Expression>{}; | 161 // |
| 150 | 162 // We may have multiple ways of calling foo: |
| 163 // (1) foo(1, 2); |
| 164 // (2) foo(1, 2, c: 3); |
| 165 // (3) foo(1, 2, d: 4); |
| 166 // (4) foo(1, 2, c: 3, d: 4); |
| 167 // (5) foo(1, 2, d: 4, c: 3); |
| 168 // |
| 169 // What we generate at the call sites are: |
| 170 // (1) foo$2(1, 2); |
| 171 // (2) foo$3$c(1, 2, 3); |
| 172 // (3) foo$3$d(1, 2, 4); |
| 173 // (4) foo$4$c$d(1, 2, 3, 4); |
| 174 // (5) foo$4$c$d(1, 2, 3, 4); |
| 175 // |
| 176 // The stubs we generate are (expressed in Dart): |
| 177 // (1) foo$2(a, b) => foo$4$c$d(a, b, null, null) |
| 178 // (2) foo$3$c(a, b, c) => foo$4$c$d(a, b, c, null); |
| 179 // (3) foo$3$d(a, b, d) => foo$4$c$d(a, b, null, d); |
| 180 // (4) No stub generated, call is direct. |
| 181 // (5) No stub generated, call is direct. |
| 182 // |
| 183 // We need to pay attention if this stub is for a function that has been |
| 184 // invoked from a subclass. Then we cannot just redirect, since that |
| 185 // would invoke the methods of the subclass. We have to compile to: |
| 186 // (1) foo$2(a, b) => MyClass.foo$4$c$d.call(this, a, b, null, null) |
| 187 // (2) foo$3$c(a, b, c) => MyClass.foo$4$c$d(this, a, b, c, null); |
| 188 // (3) foo$3$d(a, b, d) => MyClass.foo$4$c$d(this, a, b, null, d); |
| 189 List<ParameterStubMethod> generateParameterStubs(FunctionElement member, |
| 190 {bool canTearOff: true}) { |
| 151 if (member.enclosingElement.isClosure) { | 191 if (member.enclosingElement.isClosure) { |
| 152 ClosureClassElement cls = member.enclosingElement; | 192 ClosureClassElement cls = member.enclosingElement; |
| 153 if (cls.supertype.element == backend.boundClosureClass) { | 193 if (cls.supertype.element == backend.boundClosureClass) { |
| 154 compiler.internalError(cls.methodElement, 'Bound closure1.'); | 194 compiler.internalError(cls.methodElement, 'Bound closure1.'); |
| 155 } | 195 } |
| 156 if (cls.methodElement.isInstanceMember) { | 196 if (cls.methodElement.isInstanceMember) { |
| 157 compiler.internalError(cls.methodElement, 'Bound closure2.'); | 197 compiler.internalError(cls.methodElement, 'Bound closure2.'); |
| 158 } | 198 } |
| 159 } | 199 } |
| 160 | 200 |
| 161 // We fill the lists depending on the selector. For example, | 201 // The set of selectors that apply to `member`. For example, for |
| 162 // take method foo: | 202 // a member `foo(x, [y])` the following selectors may apply: |
| 163 // foo(a, b, {c, d}); | 203 // `foo(x)`, and `foo(x, y)`. |
| 204 Set<Selector> selectors; |
| 205 // The set of selectors that apply to `member` if it's name was `call`. |
| 206 // This happens when a member is torn off. In that case calls to the |
| 207 // function use the name `call`, and we must be able to handle every |
| 208 // `call` invocation that matches the signature. For example, for |
| 209 // a member `foo(x, [y])` the following selectors would be possible |
| 210 // call-selectors: `call(x)`, and `call(x, y)`. |
| 211 Set<Selector> callSelectors; |
| 212 |
| 213 // Only instance members (not static methods) need stubs. |
| 214 if (member.isInstanceMember) { |
| 215 selectors = compiler.codegenWorld.invokedNames[member.name]; |
| 216 } |
| 217 |
| 218 if (canTearOff) { |
| 219 String call = namer.closureInvocationSelectorName; |
| 220 callSelectors = compiler.codegenWorld.invokedNames[call]; |
| 221 } |
| 222 |
| 223 assert(emptySelectorSet.isEmpty); |
| 224 if (selectors == null) selectors = emptySelectorSet; |
| 225 if (callSelectors == null) callSelectors = emptySelectorSet; |
| 226 |
| 227 List<ParameterStubMethod> stubs = <ParameterStubMethod>[]; |
| 228 |
| 229 if (selectors.isEmpty && callSelectors.isEmpty) { |
| 230 return stubs; |
| 231 } |
| 232 |
| 233 // For every call-selector the corresponding selector with the name of the |
| 234 // member. |
| 164 // | 235 // |
| 165 // We may have multiple ways of calling foo: | 236 // For example, for the call-selector `call(x, y)` the renamed selector |
| 166 // (1) foo(1, 2); | 237 // for member `foo` would be `foo(x, y)`. |
| 167 // (2) foo(1, 2, c: 3); | 238 Set<Selector> renamedCallSelectors = |
| 168 // (3) foo(1, 2, d: 4); | 239 callSelectors.isEmpty ? emptySelectorSet : new Set<Selector>(); |
| 169 // (4) foo(1, 2, c: 3, d: 4); | |
| 170 // (5) foo(1, 2, d: 4, c: 3); | |
| 171 // | |
| 172 // What we generate at the call sites are: | |
| 173 // (1) foo$2(1, 2); | |
| 174 // (2) foo$3$c(1, 2, 3); | |
| 175 // (3) foo$3$d(1, 2, 4); | |
| 176 // (4) foo$4$c$d(1, 2, 3, 4); | |
| 177 // (5) foo$4$c$d(1, 2, 3, 4); | |
| 178 // | |
| 179 // The stubs we generate are (expressed in Dart): | |
| 180 // (1) foo$2(a, b) => foo$4$c$d(a, b, null, null) | |
| 181 // (2) foo$3$c(a, b, c) => foo$4$c$d(a, b, c, null); | |
| 182 // (3) foo$3$d(a, b, d) => foo$4$c$d(a, b, null, d); | |
| 183 // (4) No stub generated, call is direct. | |
| 184 // (5) No stub generated, call is direct. | |
| 185 // | |
| 186 // We need to pay attention if this stub is for a function that has been | |
| 187 // invoked from a subclass. Then we cannot just redirect, since that | |
| 188 // would invoke the methods of the subclass. We have to compile to: | |
| 189 // (1) foo$2(a, b) => MyClass.foo$4$c$d.call(this, a, b, null, null) | |
| 190 // (2) foo$3$c(a, b, c) => MyClass.foo$4$c$d(this, a, b, c, null); | |
| 191 // (3) foo$3$d(a, b, d) => MyClass.foo$4$c$d(this, a, b, null, d); | |
| 192 | 240 |
| 193 Set<Selector> selectors = member.isInstanceMember | 241 Set<Selector> untypedSelectors = new Set<Selector>(); |
| 194 ? compiler.codegenWorld.invokedNames[member.name] | |
| 195 : null; // No stubs needed for static methods. | |
| 196 | 242 |
| 197 /// Returns all closure call selectors renamed to match this member. | 243 // Start with the callSelectors since they imply the generation of the |
| 198 Set<Selector> callSelectorsAsNamed() { | 244 // non-call version. |
| 199 if (!canTearOff) return null; | 245 for (Selector selector in callSelectors) { |
| 200 Set<Selector> callSelectors = compiler.codegenWorld.invokedNames[ | 246 Selector renamedSelector = new Selector.call( |
| 201 namer.closureInvocationSelectorName]; | 247 member.name, member.library, |
| 202 if (callSelectors == null) return null; | 248 selector.argumentCount, selector.namedArguments); |
| 203 return callSelectors.map((Selector callSelector) { | 249 renamedCallSelectors.add(renamedSelector); |
| 204 return new Selector.call( | 250 |
| 205 member.name, member.library, | 251 if (!renamedSelector.appliesUnnamed(member, compiler.world)) continue; |
| 206 callSelector.argumentCount, callSelector.namedArguments); | 252 |
| 207 }).toSet(); | 253 if (untypedSelectors.add(renamedSelector.asUntyped)) { |
| 208 } | 254 ParameterStubMethod stub = |
| 209 if (selectors == null) { | 255 generateParameterStub(member, renamedSelector, selector); |
| 210 selectors = callSelectorsAsNamed(); | 256 if (stub != null) { |
| 211 if (selectors == null) return generatedStubs; | 257 stubs.add(stub); |
| 212 } else { | |
| 213 Set<Selector> callSelectors = callSelectorsAsNamed(); | |
| 214 if (callSelectors != null) { | |
| 215 selectors = selectors.union(callSelectors); | |
| 216 } | |
| 217 } | |
| 218 Set<Selector> untypedSelectors = new Set<Selector>(); | |
| 219 if (selectors != null) { | |
| 220 for (Selector selector in selectors) { | |
| 221 if (!selector.appliesUnnamed(member, compiler.world)) continue; | |
| 222 if (untypedSelectors.add(selector.asUntyped)) { | |
| 223 jsAst.Expression stub = generateParameterStub(member, selector); | |
| 224 if (stub != null) { | |
| 225 generatedStubs[selector] = stub; | |
| 226 } | |
| 227 } | 258 } |
| 228 } | 259 } |
| 229 } | 260 } |
| 230 if (canTearOff) { | 261 |
| 231 selectors = compiler.codegenWorld.invokedNames[ | 262 // Now run through the actual member selectors (eg. `foo$2(x, y)` and not |
| 232 namer.closureInvocationSelectorName]; | 263 // `call$2(x, y)`. Some of them have already been generated because of the |
| 233 if (selectors != null) { | 264 // call-selectors (and they are in the renamedCallSelectors set. |
| 234 for (Selector selector in selectors) { | 265 for (Selector selector in selectors) { |
| 235 selector = new Selector.call( | 266 if (renamedCallSelectors.contains(selector)) continue; |
| 236 member.name, member.library, | 267 if (!selector.appliesUnnamed(member, compiler.world)) continue; |
| 237 selector.argumentCount, selector.namedArguments); | 268 |
| 238 if (!selector.appliesUnnamed(member, compiler.world)) continue; | 269 if (untypedSelectors.add(selector.asUntyped)) { |
| 239 if (untypedSelectors.add(selector)) { | 270 ParameterStubMethod stub = |
| 240 jsAst.Expression stub = generateParameterStub(member, selector); | 271 generateParameterStub(member, selector, null); |
| 241 if (stub != null) { | 272 if (stub != null) { |
| 242 generatedStubs[selector] = stub; | 273 stubs.add(stub); |
| 243 } | |
| 244 } | |
| 245 } | 274 } |
| 246 } | 275 } |
| 247 } | 276 } |
| 248 return generatedStubs; | 277 |
| 278 return stubs; |
| 249 } | 279 } |
| 250 } | 280 } |
| OLD | NEW |