blob: c3b28b71ca4b93127708fda60598695b5219cc91 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// 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.
library dart2js.js_emitter.parameter_stub_generator;
import '../common_elements.dart' show JElementEnvironment;
import '../constants/values.dart';
import '../elements/entities.dart';
import '../elements/types.dart';
import '../io/source_information.dart';
import '../js/js.dart' as jsAst;
import '../js/js.dart' show js;
import '../js_backend/namer.dart' show Namer;
import '../js_backend/native_data.dart';
import '../js_backend/interceptor_data.dart';
import '../js_backend/runtime_types.dart';
import '../js_backend/runtime_types_new.dart' show RecipeEncoder;
import '../universe/call_structure.dart' show CallStructure;
import '../universe/codegen_world_builder.dart';
import '../universe/selector.dart' show Selector;
import '../universe/world_builder.dart' show SelectorConstraints;
import '../world.dart' show JClosedWorld;
import 'model.dart';
import 'code_emitter_task.dart' show Emitter;
import 'native_emitter.dart';
class ParameterStubGenerator {
static final Set<Selector> emptySelectorSet = new Set<Selector>();
final Emitter _emitter;
final NativeEmitter _nativeEmitter;
final Namer _namer;
final RuntimeTypesEncoder _rtiEncoder;
final RecipeEncoder _rtiRecipeEncoder; // `null` if not experimentNewRti.
final NativeData _nativeData;
final InterceptorData _interceptorData;
final CodegenWorld _codegenWorld;
final JClosedWorld _closedWorld;
final SourceInformationStrategy _sourceInformationStrategy;
ParameterStubGenerator(
this._emitter,
this._nativeEmitter,
this._namer,
this._rtiEncoder,
this._rtiRecipeEncoder,
this._nativeData,
this._interceptorData,
this._codegenWorld,
this._closedWorld,
this._sourceInformationStrategy);
JElementEnvironment get _elementEnvironment =>
_closedWorld.elementEnvironment;
bool needsSuperGetter(FunctionEntity element) =>
_codegenWorld.methodsNeedsSuperGetter(element);
/// Generates stubs to fill in missing optional named or positional arguments
/// and missing type arguments. Returns `null` if no stub is needed.
///
/// Methods like `foo([x])` and `bar({x})` may be invoked by the following
/// calls: `foo(), foo(1), bar(), bar(x: 1)`. This method generates the stub
/// for the given [selector] and returns the generated [ParameterStubMethod].
///
/// Members may be invoked in two ways: directly, or through a closure. In the
/// latter case the caller invokes the tear-off closure's `call` method. This
/// method [generateParameterStub] accepts two selectors. The returned stub
/// method has the corresponding name [ParameterStubMethod.name] and
/// [ParameterStubMethod.callName] set if the input selector is non-null (and
/// the member needs a stub).
ParameterStubMethod generateParameterStub(
FunctionEntity member, Selector selector, Selector callSelector) {
// The naming here can be a bit confusing. There is a call site somewhere
// that calls the stub via the [selector], which has a [CallStructure], so
// the *Call*Structure determines the *parameters* of the stub. The body of
// the stub calls the member which has a [ParameterStructure], so the
// *Parameter*Structure determines the *arguments* of the forwarding call.
CallStructure callStructure = selector.callStructure;
ParameterStructure parameterStructure = member.parameterStructure;
int positionalArgumentCount = callStructure.positionalArgumentCount;
assert(callStructure.typeArgumentCount == 0 ||
callStructure.typeArgumentCount == parameterStructure.typeParameters);
// We don't need a stub if the arguments match the target parameters,
// i.e. there are no missing optional arguments or types. The selector
// applies to the member, so we can check using counts.
if (callStructure.typeArgumentCount == parameterStructure.typeParameters) {
if (positionalArgumentCount == parameterStructure.totalParameters) {
// Positional optional arguments are all provided.
assert(callStructure.isUnnamed);
return null;
}
if (parameterStructure.namedParameters.isNotEmpty &&
callStructure.namedArgumentCount ==
parameterStructure.namedParameters.length) {
// Named optional arguments are all provided.
return null;
}
}
List<String> names = callStructure.getOrderedNamedArguments();
bool isInterceptedMethod = _interceptorData.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.safeVariableName 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> stubParameters = new List<jsAst.Parameter>(
extraArgumentCount +
selector.argumentCount +
selector.typeArgumentCount);
// The arguments that will be passed to the real method.
List<jsAst.Expression> targetArguments = new List<jsAst.Expression>(
extraArgumentCount +
parameterStructure.totalParameters +
parameterStructure.typeParameters);
int count = 0;
if (isInterceptedMethod) {
count++;
stubParameters[0] = new jsAst.Parameter(receiverArgumentName);
targetArguments[0] = js('#', receiverArgumentName);
}
int optionalParameterStart = positionalArgumentCount + extraArgumentCount;
// Includes extra receiver argument when using interceptor convention
int indexOfLastOptionalArgumentInParameters = optionalParameterStart - 1;
_elementEnvironment.forEachParameter(member,
(_, String name, ConstantValue value) {
String jsName = _namer.safeVariableName(name);
assert(jsName != receiverArgumentName);
if (count < optionalParameterStart) {
stubParameters[count] = new jsAst.Parameter(jsName);
targetArguments[count] = js('#', jsName);
} else {
int index = names.indexOf(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).
targetArguments[count] = js('#', jsName);
stubParameters[optionalParameterStart + index] =
new jsAst.Parameter(jsName);
} else {
if (value == null) {
targetArguments[count] =
_emitter.constantReference(new NullConstantValue());
} else {
if (!value.isNull) {
// If the value is the null constant, we should not pass it
// down to the native method.
indexOfLastOptionalArgumentInParameters = count;
}
targetArguments[count] = _emitter.constantReference(value);
}
}
}
count++;
});
if (parameterStructure.typeParameters > 0) {
int parameterIndex =
stubParameters.length - parameterStructure.typeParameters;
for (TypeVariableType typeVariable
in _closedWorld.elementEnvironment.getFunctionTypeVariables(member)) {
if (selector.typeArgumentCount == 0) {
DartType defaultType = _closedWorld.elementEnvironment
.getTypeVariableDefaultType(typeVariable.element);
if (_rtiRecipeEncoder != null) {
jsAst.Expression typeRti = _rtiRecipeEncoder.evaluateRecipe(
_emitter,
_rtiRecipeEncoder.encodeRecipeWithVariablesReplaceByAny(
_emitter, defaultType));
targetArguments[count++] = typeRti;
} else {
targetArguments[count++] = _rtiEncoder.getTypeRepresentation(
_emitter, defaultType, (_) => _emitter.constantReference(
// TODO(33422): Support type variables in default
// types. Temporarily using the "any" type (encoded as -2) to
// avoid failing on bounds checks.
new IntConstantValue(new BigInt.from(-2))));
}
} else {
String jsName = '\$${typeVariable.element.name}';
stubParameters[parameterIndex++] = new jsAst.Parameter(jsName);
targetArguments[count++] = js('#', jsName);
}
}
}
var body; // List or jsAst.Statement.
if (_nativeData.hasFixedBackendName(member)) {
body = _nativeEmitter.generateParameterStubStatements(
member,
isInterceptedMethod,
_namer.invocationName(selector),
stubParameters,
targetArguments,
indexOfLastOptionalArgumentInParameters);
} else if (member.isInstanceMember) {
if (needsSuperGetter(member)) {
ClassEntity superClass = member.enclosingClass;
jsAst.Name methodName = _namer.instanceMethodName(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, #);', [
_emitter.prototypeAccess(superClass, hasBeenInstantiated: true),
methodName,
targetArguments
]);
} else {
body = js.statement('return this.#(#);',
[_namer.instanceMethodName(member), targetArguments]);
}
} else {
body = js.statement('return #(#)',
[_emitter.staticFunctionAccess(member), targetArguments]);
}
SourceInformationBuilder sourceInformationBuilder =
_sourceInformationStrategy.createBuilderForContext(member);
SourceInformation sourceInformation =
sourceInformationBuilder.buildStub(member, callStructure);
jsAst.Fun function = js('function(#) { #; }', [stubParameters, body])
.withSourceInformation(sourceInformation);
jsAst.Name name = member.isStatic ? null : _namer.invocationName(selector);
jsAst.Name callName =
(callSelector != null) ? _namer.invocationName(callSelector) : null;
return new ParameterStubMethod(name, callName, function, element: member);
}
// We fill the lists depending on possible/invoked selectors. 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);
List<ParameterStubMethod> generateParameterStubs(FunctionEntity member,
{bool canTearOff, bool canBeApplied}) {
assert(canTearOff != null);
assert(canBeApplied != null);
// The set of selectors that apply to `member`. For example, for
// a member `foo(x, [y])` the following selectors may apply:
// `foo(x)`, and `foo(x, y)`.
Map<Selector, SelectorConstraints> liveSelectors;
// The set of selectors that apply to `member` if it's name was `call`.
// This happens when a member is torn off. In that case calls to the
// function use the name `call`, and we must be able to handle every
// `call` invocation that matches the signature. For example, for
// a member `foo(x, [y])` the following selectors would be possible
// call-selectors: `call(x)`, and `call(x, y)`.
Map<Selector, SelectorConstraints> callSelectors;
int memberTypeParameters = member.parameterStructure.typeParameters;
// Only instance members (not static methods) need stubs.
if (member.isInstanceMember) {
liveSelectors = _codegenWorld.invocationsByName(member.name);
}
if (canTearOff) {
String call = _namer.closureInvocationSelectorName;
callSelectors = _codegenWorld.invocationsByName(call);
}
assert(emptySelectorSet.isEmpty);
liveSelectors ??= const <Selector, SelectorConstraints>{};
callSelectors ??= const <Selector, SelectorConstraints>{};
List<ParameterStubMethod> stubs = <ParameterStubMethod>[];
if (liveSelectors.isEmpty &&
callSelectors.isEmpty &&
// Function.apply might need a stub to default the type parameter.
!(canBeApplied && memberTypeParameters > 0)) {
return stubs;
}
// For every call-selector the corresponding selector with the name of the
// member.
//
// For example, for the call-selector `call(x, y)` the renamed selector
// for member `foo` would be `foo(x, y)`.
Set<Selector> renamedCallSelectors = new Set<Selector>();
Set<Selector> stubSelectors = new Set<Selector>();
// Start with closure-call selectors, since since they imply the generation
// of the non-call version.
if (canBeApplied && memberTypeParameters > 0) {
// Function.apply calls the function with no type arguments, so generic
// methods need the stub to default the type arguments.
// This has to be the first stub.
Selector namedSelector = new Selector.fromElement(member).toNonGeneric();
Selector closureSelector =
namedSelector.isClosureCall ? null : namedSelector.toCallSelector();
renamedCallSelectors.add(namedSelector);
stubSelectors.add(namedSelector);
ParameterStubMethod stub =
generateParameterStub(member, namedSelector, closureSelector);
assert(stub != null);
stubs.add(stub);
}
for (Selector selector in callSelectors.keys) {
Selector renamedSelector =
new Selector.call(member.memberName, selector.callStructure);
renamedCallSelectors.add(renamedSelector);
if (!renamedSelector.appliesUnnamed(member)) {
continue;
}
if (stubSelectors.add(renamedSelector)) {
ParameterStubMethod stub =
generateParameterStub(member, renamedSelector, selector);
if (stub != null) {
stubs.add(stub);
}
}
// A generic method might need to support `call<T>(x)` for a generic
// instantiation stub without `call<T>(x)` being in [callSelectors].
// [selector] will be `call(x)` (that already passes the appliesUnnamed
// check by defaulting type arguments), and the method will be generic.
//
// This is basically the same logic as above, but with type arguments.
if (selector.callStructure.typeArgumentCount == 0) {
if (memberTypeParameters > 0) {
Selector renamedSelectorWithTypeArguments = new Selector.call(
member.memberName,
selector.callStructure
.withTypeArgumentCount(memberTypeParameters));
renamedCallSelectors.add(renamedSelectorWithTypeArguments);
if (stubSelectors.add(renamedSelectorWithTypeArguments)) {
Selector closureSelector =
new Selector.callClosureFrom(renamedSelectorWithTypeArguments);
ParameterStubMethod stub = generateParameterStub(
member, renamedSelectorWithTypeArguments, closureSelector);
if (stub != null) {
stubs.add(stub);
}
}
}
}
}
// Now run through the actual member selectors (eg. `foo$2(x, y)` and not
// `call$2(x, y)`). Some of them have already been generated because of the
// call-selectors and they are in the renamedCallSelectors set.
for (Selector selector in liveSelectors.keys) {
if (renamedCallSelectors.contains(selector)) continue;
if (!selector.appliesUnnamed(member)) continue;
if (!liveSelectors[selector]
.canHit(member, selector.memberName, _closedWorld)) {
continue;
}
if (stubSelectors.add(selector)) {
ParameterStubMethod stub =
generateParameterStub(member, selector, null);
if (stub != null) {
stubs.add(stub);
}
}
}
return stubs;
}
}