Chromium Code Reviews| Index: pkg/compiler/lib/src/js_emitter/parameter_stub_generator.dart |
| diff --git a/pkg/compiler/lib/src/js_emitter/parameter_stub_generator.dart b/pkg/compiler/lib/src/js_emitter/parameter_stub_generator.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..65339d406bea38b73c9d3f84b5e484cce523b087 |
| --- /dev/null |
| +++ b/pkg/compiler/lib/src/js_emitter/parameter_stub_generator.dart |
| @@ -0,0 +1,250 @@ |
| +// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
|
floitsch
2015/01/30 12:33:03
2015
zarah
2015/01/30 12:46:39
Done.
|
| +// for details. All rights reserved. Use of this source code is governed by a |
| +// BSD-style license that can be found in the LICENSE file. |
| + |
| +part of dart2js.js_emitter; |
| + |
| +class ParameterStubGenerator { |
|
floitsch
2015/01/30 12:24:45
I think this class could have been merged into the
floitsch
2015/01/30 12:25:24
Please don't merge it now, since I'm going to reba
zarah
2015/01/30 12:46:39
I was also considering that. So I agree.
|
| + final Namer namer; |
| + final Compiler compiler; |
| + final JavaScriptBackend backend; |
| + |
| + ParameterStubGenerator(this.compiler, this.namer, this.backend); |
| + |
| + Emitter get emitter => backend.emitter.emitter; |
| + CodeEmitterTask get emitterTask => backend.emitter; |
| + |
| + bool needsSuperGetter(FunctionElement element) => |
| + compiler.codegenWorld.methodsNeedingSuperGetter.contains(element); |
| + |
| + /** |
| + * Generate stubs to handle invocation of methods with optional |
| + * arguments. |
| + * |
| + * A method like [: foo([x]) :] may be invoked by the following |
| + * calls: [: foo(), foo(1), foo(x: 1) :]. See the sources of this |
| + * function for detailed examples. |
| + */ |
| + jsAst.Expression generateParameterStub(FunctionElement member, |
| + Selector selector) { |
|
floitsch
2015/01/30 12:24:45
nit: indentation.
zarah
2015/01/30 12:46:39
Done.
|
| + FunctionSignature parameters = member.functionSignature; |
| + int positionalArgumentCount = selector.positionalArgumentCount; |
| + if (positionalArgumentCount == parameters.parameterCount) { |
| + assert(selector.namedArgumentCount == 0); |
| + return null; |
| + } |
| + if (parameters.optionalParametersAreNamed |
| + && selector.namedArgumentCount == parameters.optionalParameterCount) { |
| + // If the selector has the same number of named arguments as the element, |
| + // we don't need to add a stub. The call site will hit the method |
| + // directly. |
| + return null; |
| + } |
| + JavaScriptConstantCompiler handler = backend.constants; |
| + List<String> names = selector.getOrderedNamedArguments(); |
| + |
| + String invocationName = namer.invocationName(selector); |
| + |
| + bool isInterceptedMethod = backend.isInterceptedMethod(member); |
| + |
| + // If the method is intercepted, we need to also pass the actual receiver. |
| + int extraArgumentCount = isInterceptedMethod ? 1 : 0; |
| + // Use '$receiver' to avoid clashes with other parameter names. Using |
| + // '$receiver' works because [:namer.safeName:] used for getting parameter |
| + // names never returns a name beginning with a single '$'. |
| + String receiverArgumentName = r'$receiver'; |
| + |
| + // The parameters that this stub takes. |
| + List<jsAst.Parameter> parametersBuffer = |
| + new List<jsAst.Parameter>(selector.argumentCount + extraArgumentCount); |
| + // The arguments that will be passed to the real method. |
| + List<jsAst.Expression> argumentsBuffer = |
| + new List<jsAst.Expression>( |
| + parameters.parameterCount + extraArgumentCount); |
| + |
| + int count = 0; |
| + if (isInterceptedMethod) { |
| + count++; |
| + parametersBuffer[0] = new jsAst.Parameter(receiverArgumentName); |
| + argumentsBuffer[0] = js('#', receiverArgumentName); |
| + } |
| + |
| + int optionalParameterStart = positionalArgumentCount + extraArgumentCount; |
| + // Includes extra receiver argument when using interceptor convention |
| + int indexOfLastOptionalArgumentInParameters = optionalParameterStart - 1; |
| + |
| + int parameterIndex = 0; |
| + parameters.orderedForEachParameter((ParameterElement element) { |
| + String jsName = backend.namer.safeName(element.name); |
| + assert(jsName != receiverArgumentName); |
| + if (count < optionalParameterStart) { |
| + parametersBuffer[count] = new jsAst.Parameter(jsName); |
| + argumentsBuffer[count] = js('#', jsName); |
| + } else { |
| + int index = names.indexOf(element.name); |
| + if (index != -1) { |
| + indexOfLastOptionalArgumentInParameters = count; |
| + // The order of the named arguments is not the same as the |
| + // one in the real method (which is in Dart source order). |
| + argumentsBuffer[count] = js('#', jsName); |
| + parametersBuffer[optionalParameterStart + index] = |
| + new jsAst.Parameter(jsName); |
| + } else { |
| + ConstantExpression constant = handler.getConstantForVariable(element); |
| + if (constant == null) { |
| + argumentsBuffer[count] = |
| + emitter.constantReference(new NullConstantValue()); |
| + } else { |
| + ConstantValue value = constant.value; |
| + if (!value.isNull) { |
| + // If the value is the null constant, we should not pass it |
| + // down to the native method. |
| + indexOfLastOptionalArgumentInParameters = count; |
| + } |
| + argumentsBuffer[count] = emitter.constantReference(value); |
| + } |
| + } |
| + } |
| + count++; |
| + }); |
| + |
| + var body; // List or jsAst.Statement. |
| + if (member.hasFixedBackendName) { |
| + body = emitterTask.nativeEmitter.generateParameterStubStatements( |
| + member, isInterceptedMethod, invocationName, |
| + parametersBuffer, argumentsBuffer, |
| + indexOfLastOptionalArgumentInParameters); |
| + } else if (member.isInstanceMember) { |
| + if (needsSuperGetter(member)) { |
| + ClassElement superClass = member.enclosingClass; |
| + String methodName = namer.getNameOfInstanceMember(member); |
| + // When redirecting, we must ensure that we don't end up in a subclass. |
| + // We thus can't just invoke `this.foo$1.call(filledInArguments)`. |
| + // Instead we need to call the statically resolved target. |
| + // `<class>.prototype.bar$1.call(this, argument0, ...)`. |
| + body = js.statement( |
| + 'return #.#.call(this, #);', |
| + [backend.emitter.prototypeAccess(superClass, |
| + hasBeenInstantiated: true), |
| + methodName, |
| + argumentsBuffer]); |
| + } else { |
| + body = js.statement( |
| + 'return this.#(#);', |
| + [namer.getNameOfInstanceMember(member), argumentsBuffer]); |
| + } |
| + } else { |
| + body = js.statement('return #(#)', |
| + [emitter.staticFunctionAccess(member), argumentsBuffer]); |
| + } |
| + |
| + jsAst.Fun function = js('function(#) { #; }', [parametersBuffer, body]); |
| + |
| + return function; |
| + } |
| + |
| + Map<Selector, jsAst.Expression> generateParameterStubs(FunctionElement member, |
| + [bool canTearOff = false]) { |
| + Map<Selector, jsAst.Expression> generatedStubs |
| + = <Selector, jsAst.Expression>{}; |
| + |
| + if (member.enclosingElement.isClosure) { |
| + ClosureClassElement cls = member.enclosingElement; |
| + if (cls.supertype.element == backend.boundClosureClass) { |
| + compiler.internalError(cls.methodElement, 'Bound closure1.'); |
| + } |
| + if (cls.methodElement.isInstanceMember) { |
| + compiler.internalError(cls.methodElement, 'Bound closure2.'); |
| + } |
| + } |
| + |
| + // We fill the lists depending on the selector. For example, |
| + // take method foo: |
| + // foo(a, b, {c, d}); |
| + // |
| + // We may have multiple ways of calling foo: |
| + // (1) foo(1, 2); |
| + // (2) foo(1, 2, c: 3); |
| + // (3) foo(1, 2, d: 4); |
| + // (4) foo(1, 2, c: 3, d: 4); |
| + // (5) foo(1, 2, d: 4, c: 3); |
| + // |
| + // What we generate at the call sites are: |
| + // (1) foo$2(1, 2); |
| + // (2) foo$3$c(1, 2, 3); |
| + // (3) foo$3$d(1, 2, 4); |
| + // (4) foo$4$c$d(1, 2, 3, 4); |
| + // (5) foo$4$c$d(1, 2, 3, 4); |
| + // |
| + // The stubs we generate are (expressed in Dart): |
| + // (1) foo$2(a, b) => foo$4$c$d(a, b, null, null) |
| + // (2) foo$3$c(a, b, c) => foo$4$c$d(a, b, c, null); |
| + // (3) foo$3$d(a, b, d) => foo$4$c$d(a, b, null, d); |
| + // (4) No stub generated, call is direct. |
| + // (5) No stub generated, call is direct. |
| + // |
| + // We need to pay attention if this stub is for a function that has been |
| + // invoked from a subclass. Then we cannot just redirect, since that |
| + // would invoke the methods of the subclass. We have to compile to: |
| + // (1) foo$2(a, b) => MyClass.foo$4$c$d.call(this, a, b, null, null) |
| + // (2) foo$3$c(a, b, c) => MyClass.foo$4$c$d(this, a, b, c, null); |
| + // (3) foo$3$d(a, b, d) => MyClass.foo$4$c$d(this, a, b, null, d); |
| + |
| + Set<Selector> selectors = member.isInstanceMember |
| + ? compiler.codegenWorld.invokedNames[member.name] |
| + : null; // No stubs needed for static methods. |
| + |
| + /// Returns all closure call selectors renamed to match this member. |
| + Set<Selector> callSelectorsAsNamed() { |
| + if (!canTearOff) return null; |
| + Set<Selector> callSelectors = compiler.codegenWorld.invokedNames[ |
| + namer.closureInvocationSelectorName]; |
| + if (callSelectors == null) return null; |
| + return callSelectors.map((Selector callSelector) { |
| + return new Selector.call( |
| + member.name, member.library, |
| + callSelector.argumentCount, callSelector.namedArguments); |
| + }).toSet(); |
| + } |
| + if (selectors == null) { |
| + selectors = callSelectorsAsNamed(); |
| + if (selectors == null) return generatedStubs; |
| + } else { |
| + Set<Selector> callSelectors = callSelectorsAsNamed(); |
| + if (callSelectors != null) { |
| + selectors = selectors.union(callSelectors); |
| + } |
| + } |
| + Set<Selector> untypedSelectors = new Set<Selector>(); |
| + if (selectors != null) { |
| + for (Selector selector in selectors) { |
| + if (!selector.appliesUnnamed(member, compiler.world)) continue; |
| + if (untypedSelectors.add(selector.asUntyped)) { |
| + jsAst.Expression stub = generateParameterStub(member, selector); |
| + if (stub != null) { |
| + generatedStubs[selector] = stub; |
| + } |
| + } |
| + } |
| + } |
| + if (canTearOff) { |
| + selectors = compiler.codegenWorld.invokedNames[ |
| + namer.closureInvocationSelectorName]; |
| + if (selectors != null) { |
| + for (Selector selector in selectors) { |
| + selector = new Selector.call( |
| + member.name, member.library, |
| + selector.argumentCount, selector.namedArguments); |
| + if (!selector.appliesUnnamed(member, compiler.world)) continue; |
| + if (untypedSelectors.add(selector)) { |
| + jsAst.Expression stub = generateParameterStub(member, selector); |
| + if (stub != null) { |
| + generatedStubs[selector] = stub; |
| + } |
| + } |
| + } |
| + } |
| + } |
| + return generatedStubs; |
| + } |
| +} |