blob: 559e4fb72f479a3056acf74b9c63b8795d97365a [file] [log] [blame]
// Copyright (c) 2012, 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.
import 'dart:collection';
import 'package:js_runtime/shared/embedded_names.dart';
import '../closure.dart';
import '../common.dart';
import '../common/codegen.dart' show CodegenRegistry;
import '../common/names.dart' show Identifiers, Selectors;
import '../common/tasks.dart' show CompilerTask;
import '../compiler.dart';
import '../constants/constant_system.dart';
import '../constants/expressions.dart';
import '../constants/values.dart';
import '../diagnostics/messages.dart' show Message, MessageTemplate;
import '../dump_info.dart' show InfoReporter;
import '../elements/elements.dart';
import '../elements/entities.dart';
import '../elements/jumps.dart';
import '../elements/modelx.dart' show ConstructorBodyElementX;
import '../elements/names.dart';
import '../elements/operators.dart';
import '../elements/resolution_types.dart';
import '../elements/types.dart';
import '../io/source_information.dart';
import '../js/js.dart' as js;
import '../js_backend/backend.dart' show JavaScriptBackend;
import '../js_backend/element_strategy.dart' show ElementCodegenWorkItem;
import '../js_backend/runtime_types.dart';
import '../js_emitter/js_emitter.dart' show CodeEmitterTask, NativeEmitter;
import '../native/native.dart' as native;
import '../resolution/deferred_load.dart' show AstDeferredLoadTask;
import '../resolution/semantic_visitor.dart';
import '../resolution/tree_elements.dart' show TreeElements;
import '../tree/tree.dart' as ast;
import '../types/types.dart';
import '../universe/call_structure.dart' show CallStructure;
import '../universe/selector.dart' show Selector;
import '../universe/side_effects.dart' show SideEffects;
import '../universe/use.dart' show ConstantUse, DynamicUse, StaticUse;
import '../util/util.dart';
import '../world.dart' show ClosedWorld;
import 'graph_builder.dart';
import 'jump_handler.dart';
import 'locals_handler.dart';
import 'loop_handler.dart';
import 'nodes.dart';
import 'optimize.dart';
import 'ssa.dart';
import 'ssa_branch_builder.dart';
import 'type_builder.dart';
import 'types.dart';
abstract class SsaAstBuilderBase extends SsaBuilderFieldMixin
implements SsaBuilder {
final CompilerTask task;
final JavaScriptBackend backend;
SsaAstBuilderBase(this.task, this.backend);
ConstantValue getFieldInitialConstantValue(FieldElement field) {
ConstantExpression constant = field.constant;
if (constant != null) {
ConstantValue initialValue = backend.constants.getConstantValue(constant);
if (initialValue == null) {
assert(
field.isInstanceMember ||
constant.isImplicit ||
constant.isPotential,
failedAt(
field,
"Constant expression without value: "
"${constant.toStructuredText()}."));
}
return initialValue;
}
return null;
}
}
class SsaAstBuilder extends SsaAstBuilderBase {
final CodeEmitterTask emitter;
final SourceInformationStrategy sourceInformationFactory;
SsaAstBuilder(CompilerTask task, JavaScriptBackend backend,
this.sourceInformationFactory)
: emitter = backend.emitter,
super(task, backend);
DiagnosticReporter get reporter => backend.reporter;
HGraph build(covariant ElementCodegenWorkItem work, ClosedWorld closedWorld) {
return task.measure(() {
if (handleConstantField(work.element, work.registry, closedWorld)) {
// No code is generated for `work.element`.
return null;
}
MemberElement element = work.element.implementation;
return reporter.withCurrentElement(element, () {
SsaAstGraphBuilder builder = new SsaAstGraphBuilder(
work.element.implementation,
work.resolvedAst,
work.registry,
backend,
closedWorld,
emitter.nativeEmitter,
sourceInformationFactory);
HGraph graph = builder.build();
// Default arguments are handled elsewhere, but we must ensure
// that the default values are computed during codegen.
if (!identical(element.kind, ElementKind.FIELD)) {
MethodElement function = element;
FunctionSignature signature = function.functionSignature;
signature.forEachOptionalParameter((_parameter) {
ParameterElement parameter = _parameter;
// This ensures the default value will be computed.
ConstantValue constant =
backend.constants.getConstantValue(parameter.constant);
work.registry.registerConstantUse(new ConstantUse.init(constant));
});
}
if (backend.tracer.isEnabled) {
String name;
if (element.isClassMember) {
String className = element.enclosingClass.name;
String memberName = element.name;
name = "$className.$memberName";
if (element.isGenerativeConstructorBody) {
name = "$name (body)";
}
} else {
name = "${element.name}";
}
backend.tracer.traceCompilation(name);
backend.tracer.traceGraph('builder', graph);
}
return graph;
});
});
}
}
/**
* This class builds SSA nodes for functions represented in AST.
*/
class SsaAstGraphBuilder extends ast.Visitor
with
BaseImplementationOfCompoundsMixin,
BaseImplementationOfSetIfNullsMixin,
BaseImplementationOfSuperIndexSetIfNullMixin,
SemanticSendResolvedMixin,
NewBulkMixin,
ErrorBulkMixin,
GraphBuilder
implements SemanticSendVisitor {
/// The element for which this SSA builder is being used.
final MemberElement target;
final ClosedWorld closedWorld;
ResolvedAst resolvedAst;
/// Used to report information about inlining (which occurs while building the
/// SSA graph), when dump-info is enabled.
final InfoReporter infoReporter;
/// Registry used to enqueue work during codegen, may be null to avoid
/// enqueing any work.
// TODO(sigmund,johnniwinther): get rid of registry entirely. We should be
// able to return the impact as a result after building and avoid enqueing
// things here. Later the codegen task can decide whether to enqueue
// something. In the past this didn't matter as much because the SSA graph was
// used only for codegen, but currently we want to experiment using it for
// code-analysis too.
final CodegenRegistry registry;
/// Results from the global type-inference analysis corresponding to the
/// current element being visited.
///
/// Invariant: this property is updated together with [resolvedAst].
GlobalTypeInferenceElementResult elementInferenceResults;
final JavaScriptBackend backend;
Compiler get compiler => backend.compiler;
AstDeferredLoadTask get deferredLoadTask => super.deferredLoadTask;
final ConstantSystem constantSystem;
final RuntimeTypesSubstitutions rtiSubstitutions;
SourceInformationBuilder sourceInformationBuilder;
bool inLazyInitializerExpression = false;
// TODO(sigmund): make all comments /// instead of /* */
/* This field is used by the native handler. */
final NativeEmitter nativeEmitter;
/**
* True if we are visiting the expression of a throw statement; we assume this
* is a slow path.
*/
bool inExpressionOfThrow = false;
/**
* This stack contains declaration elements of the functions being built
* or inlined by this builder.
*/
final List<MemberElement> sourceElementStack = <MemberElement>[];
HInstruction rethrowableException;
/// Returns `true` if the current element is an `async` function.
bool get isBuildingAsyncFunction {
Element element = sourceElement;
return (element is FunctionElement &&
element.asyncMarker == AsyncMarker.ASYNC);
}
/// Handles the building of loops.
LoopHandler<ast.Node> loopHandler;
/// Handles type check building.
TypeBuilder typeBuilder;
// TODO(sigmund): make most args optional
SsaAstGraphBuilder(
this.target,
this.resolvedAst,
this.registry,
JavaScriptBackend backend,
this.closedWorld,
this.nativeEmitter,
SourceInformationStrategy sourceInformationFactory)
: this.infoReporter = backend.compiler.dumpInfoTask,
this.backend = backend,
this.constantSystem = backend.constantSystem,
this.rtiSubstitutions = backend.rtiSubstitutions {
assert(target.isImplementation);
elementInferenceResults = _resultOf(target.declaration);
assert(elementInferenceResults != null);
graph.element = target;
sourceElementStack.add(target.declaration);
sourceInformationBuilder =
sourceInformationFactory.createBuilderForContext(target);
graph.sourceInformation =
sourceInformationBuilder.buildVariableDeclaration();
localsHandler = new LocalsHandler(
this,
target,
target.memberContext,
target.contextClass?.thisType,
closedWorld.nativeData,
closedWorld.interceptorData);
loopHandler = new SsaLoopHandler(this);
typeBuilder = new AstTypeBuilder(this);
}
MemberElement get targetElement => target;
/// Reference to resolved elements in [target]'s AST.
TreeElements get elements => resolvedAst.elements;
@override
SemanticSendVisitor get sendVisitor => this;
@override
void visitNode(ast.Node node) {
internalError(node, "Unhandled node: $node");
}
@override
void apply(ast.Node node, [_]) {
node.accept(this);
}
/// Returns the current source element.
///
/// The returned element is a declaration element.
// TODO(johnniwinther): Check that all usages of sourceElement agree on
// implementation/declaration distinction.
@override
MemberElement get sourceElement => sourceElementStack.last;
/// Helper to retrieve global inference results for [element] with special
/// care for `ConstructorBodyElement`s which don't exist at the time the
/// global analysis run.
///
/// Note: this helper is used selectively. When we know that we are in a
/// context were we don't expect to see a constructor body element, we
/// directly fetch the data from the global inference results.
GlobalTypeInferenceElementResult _resultOf(MemberElement element) {
assert(element.isDeclaration);
return globalInferenceResults.resultOfMember(
element is ConstructorBodyElementX ? element.constructor : element);
}
/// Build the graph for [target].
HGraph build() {
assert(target.isImplementation, failedAt(target));
HInstruction.idCounter = 0;
// TODO(sigmund): remove `result` and return graph directly, need to ensure
// that it can never be null (see result in buildFactory for instance).
var result;
if (target.isGenerativeConstructor) {
result = buildFactory(resolvedAst);
} else if (target.isGenerativeConstructorBody ||
target.isFactoryConstructor ||
target.isFunction ||
target.isGetter ||
target.isSetter) {
result = buildMethod(target);
} else if (target.isField) {
if (target.isInstanceMember) {
assert(options.enableTypeAssertions);
result = buildCheckedSetter(target);
} else {
result = buildLazyInitializer(target);
}
} else {
reporter.internalError(target, 'Unexpected element kind $target.');
}
assert(result.isValid());
return result;
}
void addWithPosition(HInstruction instruction, ast.Node node) {
add(attachPosition(instruction, node));
}
/**
* Returns a complete argument list for a call of [function].
*/
List<HInstruction> completeSendArgumentsList(
FunctionElement function,
Selector selector,
List<HInstruction> providedArguments,
ast.Node currentNode) {
assert(function.isImplementation, failedAt(function));
assert(providedArguments != null);
bool isInstanceMember = function.isInstanceMember;
// For static calls, [providedArguments] is complete, default arguments
// have been included if necessary, see [makeStaticArgumentList].
if (!isInstanceMember ||
currentNode == null || // In erroneous code, currentNode can be null.
providedArgumentsKnownToBeComplete(currentNode) ||
function.isGenerativeConstructorBody ||
selector.isGetter) {
// For these cases, the provided argument list is known to be complete.
return providedArguments;
} else {
return completeDynamicSendArgumentsList(
selector, function, providedArguments);
}
}
/**
* Returns a complete argument list for a dynamic call of [function]. The
* initial argument list [providedArguments], created by
* [addDynamicSendArgumentsToList], does not include values for default
* arguments used in the call. The reason is that the target function (which
* defines the defaults) is not known.
*
* However, inlining can only be performed when the target function can be
* resolved statically. The defaults can therefore be included at this point.
*
* The [providedArguments] list contains first all positional arguments, then
* the provided named arguments (the named arguments that are defined in the
* [selector]) in a specific order (see [addDynamicSendArgumentsToList]).
*/
List<HInstruction> completeDynamicSendArgumentsList(Selector selector,
MethodElement function, List<HInstruction> providedArguments) {
assert(selector.applies(function));
FunctionSignature signature = function.functionSignature;
List<HInstruction> compiledArguments = new List<HInstruction>(
signature.parameterCount + 1); // Plus one for receiver.
compiledArguments[0] = providedArguments[0]; // Receiver.
int index = 1;
for (; index <= signature.requiredParameterCount; index++) {
compiledArguments[index] = providedArguments[index];
}
if (!signature.optionalParametersAreNamed) {
signature.forEachOptionalParameter((element) {
if (index < providedArguments.length) {
compiledArguments[index] = providedArguments[index];
} else {
compiledArguments[index] =
handleConstantForOptionalParameter(element);
}
index++;
});
} else {
/* Example:
* void foo(a, {b, d, c})
* foo(0, d = 1, b = 2)
*
* providedArguments = [0, 2, 1]
* selectorArgumentNames = [b, d]
* signature.orderedOptionalParameters = [b, c, d]
*
* For each parameter name in the signature, if the argument name matches
* we use the next provided argument, otherwise we get the default.
*/
List<String> selectorArgumentNames =
selector.callStructure.getOrderedNamedArguments();
int namedArgumentIndex = 0;
int firstProvidedNamedArgument = index;
signature.orderedOptionalParameters.forEach((element) {
if (namedArgumentIndex < selectorArgumentNames.length &&
element.name == selectorArgumentNames[namedArgumentIndex]) {
// The named argument was provided in the function invocation.
compiledArguments[index] = providedArguments[
firstProvidedNamedArgument + namedArgumentIndex++];
} else {
compiledArguments[index] =
handleConstantForOptionalParameter(element);
}
index++;
});
}
return compiledArguments;
}
/**
* Try to inline [element] within the correct context of the builder. The
* insertion point is the state of the builder.
*/
bool tryInlineMethod(MethodElement element, Selector selector, TypeMask mask,
List<HInstruction> providedArguments, ast.Node currentNode,
{ResolutionInterfaceType instanceType}) {
if (nativeData.isJsInteropMember(element) &&
!element.isFactoryConstructor) {
// We only inline factory JavaScript interop constructors.
return false;
}
// Ensure that [element] is an implementation element.
element = element.implementation;
if (compiler.elementHasCompileTimeError(element)) return false;
MethodElement function = element;
MethodElement declaration = function.declaration;
ResolvedAst functionResolvedAst = function.resolvedAst;
bool insideLoop = loopDepth > 0 || graph.calledInLoop;
// Bail out early if the inlining decision is in the cache and we can't
// inline (no need to check the hard constraints).
bool cachedCanBeInlined =
inlineCache.canInline(declaration, insideLoop: insideLoop);
if (cachedCanBeInlined == false) return false;
bool meetsHardConstraints() {
if (options.disableInlining) return false;
assert(
selector != null ||
Elements.isStaticOrTopLevel(function) ||
function.isGenerativeConstructorBody,
failedAt(currentNode ?? function,
"Missing selector for inlining of $function."));
if (selector != null) {
if (!selector.applies(function)) return false;
if (mask != null && !mask.canHit(function, selector, closedWorld)) {
return false;
}
}
if (nativeData.isJsInteropMember(function)) return false;
// Don't inline operator== methods if the parameter can be null.
if (function.name == '==') {
if (function.enclosingClass != commonElements.objectClass &&
providedArguments[1].canBeNull()) {
return false;
}
}
// Generative constructors of native classes should not be called directly
// and have an extra argument that causes problems with inlining.
if (function.isGenerativeConstructor &&
nativeData.isNativeOrExtendsNative(function.enclosingClass)) {
return false;
}
// A generative constructor body is not seen by global analysis,
// so we should not query for its type.
if (!function.isGenerativeConstructorBody) {
if (globalInferenceResults.resultOfMember(declaration).throwsAlways) {
isReachable = false;
return false;
}
}
return true;
}
bool doesNotContainCode() {
// A function with size 1 does not contain any code.
return InlineWeeder.canBeInlined(functionResolvedAst, 1,
enableUserAssertions: options.enableUserAssertions);
}
bool reductiveHeuristic() {
// The call is on a path which is executed rarely, so inline only if it
// does not make the program larger.
if (isCalledOnce(declaration)) {
return InlineWeeder.canBeInlined(functionResolvedAst, null,
enableUserAssertions: options.enableUserAssertions);
}
// TODO(sra): Measure if inlining would 'reduce' the size. One desirable
// case we miss by doing nothing is inlining very simple constructors
// where all fields are initialized with values from the arguments at this
// call site. The code is slightly larger (`new Foo(1)` vs `Foo$(1)`) but
// that usually means the factory constructor is left unused and not
// emitted.
// We at least inline bodies that are empty (and thus have a size of 1).
return doesNotContainCode();
}
bool heuristicSayGoodToGo() {
// Don't inline recursively
if (inliningStack.any((entry) => entry.function == function)) {
return false;
}
if (function.isSynthesized) return true;
// Don't inline across deferred import to prevent leaking code. The only
// exception is an empty function (which does not contain code).
bool hasOnlyNonDeferredImportPaths = backend.outputUnitData
.hasOnlyNonDeferredImportPaths(compiler.currentElement, function);
if (!hasOnlyNonDeferredImportPaths) {
return doesNotContainCode();
}
// Do not inline code that is rarely executed unless it reduces size.
if (inExpressionOfThrow || inLazyInitializerExpression) {
return reductiveHeuristic();
}
if (cachedCanBeInlined == true) {
// We may have forced the inlining of some methods. Therefore check
// if we can inline this method regardless of size.
assert(InlineWeeder.canBeInlined(functionResolvedAst, null,
allowLoops: true,
enableUserAssertions: options.enableUserAssertions));
return true;
}
int numParameters = function.functionSignature.parameterCount;
int maxInliningNodes;
if (insideLoop) {
maxInliningNodes = InlineWeeder.INLINING_NODES_INSIDE_LOOP +
InlineWeeder.INLINING_NODES_INSIDE_LOOP_ARG_FACTOR * numParameters;
} else {
maxInliningNodes = InlineWeeder.INLINING_NODES_OUTSIDE_LOOP +
InlineWeeder.INLINING_NODES_OUTSIDE_LOOP_ARG_FACTOR * numParameters;
}
// If a method is called only once, and all the methods in the
// inlining stack are called only once as well, we know we will
// save on output size by inlining this method.
if (isCalledOnce(declaration)) {
maxInliningNodes = null;
}
bool canInline = InlineWeeder.canBeInlined(
functionResolvedAst, maxInliningNodes,
enableUserAssertions: options.enableUserAssertions);
if (canInline) {
inlineCache.markAsInlinable(declaration, insideLoop: insideLoop);
} else {
inlineCache.markAsNonInlinable(declaration, insideLoop: insideLoop);
}
return canInline;
}
void doInlining() {
registry
.registerStaticUse(new StaticUse.inlining(declaration, instanceType));
// Add an explicit null check on the receiver before doing the
// inlining. We use [element] to get the same name in the
// NoSuchMethodError message as if we had called it.
if (function.isInstanceMember &&
!function.isGenerativeConstructorBody &&
(mask == null || mask.isNullable)) {
addWithPosition(
new HFieldGet(null, providedArguments[0], commonMasks.dynamicType,
isAssignable: false),
currentNode);
}
List<HInstruction> compiledArguments = completeSendArgumentsList(
function, selector, providedArguments, currentNode);
enterInlinedMethod(
function, functionResolvedAst, compiledArguments, instanceType);
inlinedFrom(functionResolvedAst, () {
if (!isReachable) {
emitReturn(graph.addConstantNull(closedWorld), null);
} else {
doInline(functionResolvedAst);
}
});
leaveInlinedMethod();
}
if (meetsHardConstraints() && heuristicSayGoodToGo()) {
doInlining();
MemberElement inlinedFrom = inliningStack.isEmpty
? target.declaration
: inliningStack.last.function.declaration;
infoReporter?.reportInlined(declaration, inlinedFrom);
return true;
}
return false;
}
bool get allInlinedFunctionsCalledOnce {
return inliningStack.isEmpty || inliningStack.last.allFunctionsCalledOnce;
}
bool isFunctionCalledOnce(MethodElement element) {
// ConstructorBodyElements are not in the type inference graph.
if (element is ConstructorBodyEntity) return false;
return globalInferenceResults.resultOfMember(element).isCalledOnce;
}
bool isCalledOnce(MethodElement element) {
assert(element.isDeclaration);
return allInlinedFunctionsCalledOnce && isFunctionCalledOnce(element);
}
inlinedFrom(ResolvedAst resolvedAst, f()) {
MemberElement element = resolvedAst.element;
assert(element is FunctionElement || element is VariableElement);
return reporter.withCurrentElement(element.implementation, () {
// The [sourceElementStack] contains declaration elements.
SourceInformationBuilder oldSourceInformationBuilder =
sourceInformationBuilder;
sourceInformationBuilder = sourceInformationBuilder.forContext(element);
sourceElementStack.add(element);
var result = f();
sourceInformationBuilder = oldSourceInformationBuilder;
sourceElementStack.removeLast();
return result;
});
}
/**
* Return null so it is simple to remove the optional parameters completely
* from interop methods to match JavaScript semantics for omitted arguments.
*/
HInstruction handleConstantForOptionalParameterJsInterop(Element parameter) =>
null;
HInstruction handleConstantForOptionalParameter(ParameterElement parameter) {
ConstantValue constantValue =
constants.getConstantValue(parameter.constant);
assert(constantValue != null,
failedAt(parameter, 'No constant computed for $parameter'));
return graph.addConstant(constantValue, closedWorld);
}
ClassElement get currentNonClosureClass {
ClassElement cls = sourceElement.enclosingClass;
if (cls != null && cls.isClosure) {
dynamic closureClass = cls;
// ignore: UNDEFINED_GETTER
return closureClass.methodElement.enclosingClass;
} else {
return cls;
}
}
/// A stack of [ResolutionDartType]s that have been seen during inlining of
/// factory constructors. These types are preserved in [HInvokeStatic]s and
/// [HCreate]s inside the inline code and registered during code generation
/// for these nodes.
// TODO(karlklose): consider removing this and keeping the (substituted) types
// of the type variables in an environment (like the [LocalsHandler]).
final List<ResolutionDartType> currentInlinedInstantiations =
<ResolutionDartType>[];
final List<AstInliningState> inliningStack = <AstInliningState>[];
Local returnLocal;
ResolutionDartType returnType;
ConstantValue getConstantForNode(ast.Node node) {
ConstantValue constantValue =
constants.getConstantValueForNode(node, elements);
assert(constantValue != null,
failedAt(node, 'No constant computed for $node'));
return constantValue;
}
HInstruction addConstant(ast.Node node) {
return graph.addConstant(getConstantForNode(node), closedWorld);
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [functionElement] must be an implementation element.
*/
HGraph buildMethod(MethodElement functionElement) {
assert(functionElement.isImplementation, failedAt(functionElement));
MethodElement declaration = functionElement.declaration;
graph.calledInLoop = closedWorld.isCalledInLoop(declaration);
ast.FunctionExpression function = resolvedAst.node;
assert(function != null);
assert(elements.getFunctionDefinition(function) != null);
openFunction(functionElement, function);
String name = functionElement.name;
if (nativeData.isJsInteropMember(functionElement)) {
push(invokeJsInteropFunction(functionElement, parameters.values.toList(),
sourceInformationBuilder.buildGeneric(function)));
var value = pop();
closeAndGotoExit(new HReturn(
value, sourceInformationBuilder.buildReturn(functionElement.node)));
return closeFunction();
}
assert(!function.modifiers.isExternal, failedAt(functionElement));
// If [functionElement] is `operator==` we explicitly add a null check at
// the beginning of the method. This is to avoid having call sites do the
// null check.
if (name == '==') {
if (!commonElements.operatorEqHandlesNullArgument(functionElement)) {
handleIf(
node: function,
visitCondition: () {
HParameterValue parameter = parameters.values.first;
push(new HIdentity(parameter, graph.addConstantNull(closedWorld),
null, commonMasks.boolType));
},
visitThen: () {
closeAndGotoExit(new HReturn(
graph.addConstantBool(false, closedWorld),
sourceInformationBuilder
.buildImplicitReturn(functionElement)));
},
visitElse: null,
sourceInformation: sourceInformationBuilder.buildIf(function.body));
}
}
if (const bool.fromEnvironment('unreachable-throw')) {
var emptyParameters =
parameters.values.where((p) => p.instructionType.isEmpty);
if (emptyParameters.length > 0) {
addComment('${emptyParameters} inferred as [empty]');
pushInvokeStatic(
function.body, commonElements.assertUnreachableMethod, []);
pop();
return closeFunction();
}
}
function.body.accept(this);
return closeFunction();
}
/// Adds a JavaScript comment to the output. The comment will be omitted in
/// minified mode. Each line in [text] is preceded with `//` and indented.
/// Use sparingly. In order for the comment to be retained it is modeled as
/// having side effects which will inhibit code motion.
// TODO(sra): Figure out how to keep comment anchored without effects.
void addComment(String text) {
add(new HForeignCode(js.js.statementTemplateYielding(new js.Comment(text)),
commonMasks.dynamicType, <HInstruction>[],
isStatement: true));
}
HGraph buildCheckedSetter(FieldElement field) {
ResolvedAst resolvedAst = field.resolvedAst;
openFunction(field, resolvedAst.node);
HInstruction thisInstruction = localsHandler.readThis(
sourceInformation: sourceInformationBuilder.buildDeclaration(field));
// Use dynamic type because the type computed by the inferrer is
// narrowed to the type annotation.
HInstruction parameter =
new HParameterValue(field, commonMasks.dynamicType);
// Add the parameter as the last instruction of the entry block.
// If the method is intercepted, we want the actual receiver
// to be the first parameter.
graph.entry.addBefore(graph.entry.last, parameter);
HInstruction value =
typeBuilder.potentiallyCheckOrTrustType(parameter, field.type);
add(new HFieldSet(field, thisInstruction, value));
return closeFunction();
}
HGraph buildLazyInitializer(FieldElement variable) {
assert(resolvedAst.element == variable,
failedAt(variable, "Unexpected variable $variable for $resolvedAst."));
inLazyInitializerExpression = true;
ast.VariableDefinitions node = resolvedAst.node;
ast.Node initializer = resolvedAst.body;
assert(
initializer != null,
failedAt(
variable, "Non-constant variable $variable has no initializer."));
openFunction(variable, node);
visit(initializer);
HInstruction value = pop();
value = typeBuilder.potentiallyCheckOrTrustType(value, variable.type);
// In the case of multiple declarations (and some definitions) on the same
// line, the source pointer needs to point to the right initialized
// variable. So find the specific initialized variable we are referring to.
ast.Node sourceInfoNode = initializer;
for (var definition in node.definitions) {
if (definition is ast.SendSet &&
definition.selector.asIdentifier().source == variable.name) {
sourceInfoNode = definition.assignmentOperator;
break;
}
}
closeAndGotoExit(new HReturn(
value, sourceInformationBuilder.buildReturn(sourceInfoNode)));
return closeFunction();
}
/**
* This method sets up the local state of the builder for inlining [function].
* The arguments of the function are inserted into the [localsHandler].
*
* When inlining a function, [:return:] statements are not emitted as
* [HReturn] instructions. Instead, the value of a synthetic element is
* updated in the [localsHandler]. This function creates such an element and
* stores it in the [returnLocal] field.
*/
void setupStateForInlining(
MethodElement function, List<HInstruction> compiledArguments,
{ResolutionInterfaceType instanceType}) {
ResolvedAst resolvedAst = function.resolvedAst;
assert(resolvedAst != null);
localsHandler = new LocalsHandler(
this,
function,
function.memberContext,
instanceType ?? function.contextClass?.thisType,
nativeData,
interceptorData);
localsHandler.scopeInfo = closureDataLookup.getScopeInfo(function);
returnLocal =
new SyntheticLocal("result", function, function.memberContext);
localsHandler.updateLocal(returnLocal, graph.addConstantNull(closedWorld));
inTryStatement = false; // TODO(lry): why? Document.
int argumentIndex = 0;
if (function.isInstanceMember) {
localsHandler.updateLocal(localsHandler.scopeInfo.thisLocal,
compiledArguments[argumentIndex++]);
}
FunctionSignature signature = function.functionSignature;
signature.orderedForEachParameter((_parameter) {
ParameterElement parameter = _parameter;
HInstruction argument = compiledArguments[argumentIndex++];
localsHandler.updateLocal(parameter, argument);
});
ClassElement enclosing = function.enclosingClass;
if ((function.isConstructor || function.isGenerativeConstructorBody) &&
rtiNeed.classNeedsRti(enclosing)) {
enclosing.typeVariables.forEach((_typeVariable) {
ResolutionTypeVariableType typeVariable = _typeVariable;
HInstruction argument = compiledArguments[argumentIndex++];
localsHandler.updateLocal(
localsHandler.getTypeVariableAsLocal(typeVariable), argument);
});
}
assert(argumentIndex == compiledArguments.length);
returnType = signature.type.returnType;
stack = <HInstruction>[];
insertTraceCall(function);
insertCoverageCall(function);
}
void restoreState(AstInliningState state) {
localsHandler = state.oldLocalsHandler;
returnLocal = state.oldReturnLocal;
inTryStatement = state.inTryStatement;
resolvedAst = state.oldResolvedAst;
elementInferenceResults = state.oldElementInferenceResults;
returnType = state.oldReturnType;
assert(stack.isEmpty);
stack = state.oldStack;
}
/**
* Run this builder on the body of the [function] to be inlined.
*/
void visitInlinedFunction(ResolvedAst resolvedAst) {
MethodElement function = resolvedAst.element.implementation;
typeBuilder.potentiallyCheckInlinedParameterTypes(function);
if (resolvedAst.element.isGenerativeConstructor) {
buildFactory(resolvedAst);
} else {
ast.FunctionExpression functionNode = resolvedAst.node;
functionNode.body.accept(this);
}
}
addInlinedInstantiation(ResolutionDartType type) {
if (type != null) {
currentInlinedInstantiations.add(type);
}
}
removeInlinedInstantiation(ResolutionDartType type) {
if (type != null) {
currentInlinedInstantiations.removeLast();
}
}
bool providedArgumentsKnownToBeComplete(ast.Node currentNode) {
/* When inlining the iterator methods generated for a [:for-in:] loop, the
* [currentNode] is the [ForIn] tree. The compiler-generated iterator
* invocations are known to have fully specified argument lists, no default
* arguments are used. See invocations of [pushInvokeDynamic] in
* [visitForIn].
*/
return currentNode.asForIn() != null;
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [constructors] must contain only implementation elements.
*/
void inlineSuperOrRedirect(
ResolvedAst constructorResolvedAst,
List<HInstruction> compiledArguments,
List<ResolvedAst> constructorResolvedAsts,
Map<FieldElement, HInstruction> fieldValues,
FunctionElement caller) {
ConstructorElement callee = constructorResolvedAst.element.implementation;
reporter.withCurrentElement(callee, () {
Set<ClassElement> includedClasses = new Set<ClassElement>();
constructorResolvedAsts.add(constructorResolvedAst);
ClassElement currentClass = caller.enclosingClass;
/// Include locals for type variable used in [member].
void includeTypeVariables(MemberElement member) {
ClassElement enclosingClass = member.enclosingClass;
if (!includedClasses.add(enclosingClass)) return;
if (rtiNeed.classNeedsRti(enclosingClass)) {
// If [enclosingClass] needs RTI, we have to give a value to its
// type parameters.
// For a super constructor call, the type is the supertype of
// [currentClass]. For a redirecting constructor, the type is
// the current type. [InterfaceType.asInstanceOf] takes care
// of both.
ResolutionInterfaceType type =
currentClass.thisType.asInstanceOf(enclosingClass);
type = localsHandler.substInContext(type);
List<ResolutionDartType> arguments = type.typeArguments;
List<ResolutionDartType> typeVariables = enclosingClass.typeVariables;
if (!type.isRaw) {
assert(arguments.length == typeVariables.length);
Iterator<ResolutionDartType> variables = typeVariables.iterator;
type.typeArguments.forEach((ResolutionDartType argument) {
variables.moveNext();
ResolutionTypeVariableType typeVariable = variables.current;
localsHandler.updateLocal(
localsHandler.getTypeVariableAsLocal(typeVariable),
typeBuilder.analyzeTypeArgument(argument, sourceElement));
});
} else {
// If the supertype is a raw type, we need to set to null the
// type variables.
for (ResolutionTypeVariableType variable in typeVariables) {
localsHandler.updateLocal(
localsHandler.getTypeVariableAsLocal(variable),
graph.addConstantNull(closedWorld));
}
}
}
}
includeTypeVariables(callee);
// For redirecting constructors, the fields will be initialized later
// by the effective target.
if (!callee.isRedirectingGenerative) {
callee.enclosingClass.implementation.forEachInstanceField(
(ClassElement enclosingClass, FieldElement member) {
includeTypeVariables(member);
});
inlinedFrom(constructorResolvedAst, () {
buildFieldInitializers(
callee.enclosingClass.implementation, fieldValues);
});
}
int index = 0;
FunctionSignature params = callee.functionSignature;
params.orderedForEachParameter((_parameter) {
ParameterElement parameter = _parameter;
HInstruction argument = compiledArguments[index++];
// Because we are inlining the initializer, we must update
// what was given as parameter. This will be used in case
// there is a parameter check expression in the initializer.
parameters[parameter] = argument;
localsHandler.updateLocal(parameter, argument);
// Don't forget to update the field, if the parameter is of the
// form [:this.x:].
if (parameter.isInitializingFormal) {
InitializingFormalElement fieldParameterElement = parameter;
fieldValues[fieldParameterElement.fieldElement] = argument;
}
});
// Build the initializers in the context of the new constructor.
ResolvedAst oldResolvedAst = resolvedAst;
resolvedAst = callee.resolvedAst;
final oldElementInferenceResults = elementInferenceResults;
elementInferenceResults = globalInferenceResults.resultOfMember(callee);
ScopeInfo oldScopeInfo = localsHandler.scopeInfo;
ScopeInfo newScopeInfo = closureDataLookup.getScopeInfo(callee);
localsHandler.scopeInfo = newScopeInfo;
if (resolvedAst.kind == ResolvedAstKind.PARSED) {
localsHandler.enterScope(closureDataLookup.getCapturedScope(callee),
sourceInformationBuilder.buildDeclaration(callee),
forGenerativeConstructorBody: callee.isGenerativeConstructorBody);
}
buildInitializers(callee, constructorResolvedAsts, fieldValues);
localsHandler.scopeInfo = oldScopeInfo;
resolvedAst = oldResolvedAst;
elementInferenceResults = oldElementInferenceResults;
});
}
void buildInitializers(
ConstructorElement constructor,
List<ResolvedAst> constructorResolvedAsts,
Map<FieldElement, HInstruction> fieldValues) {
assert(
resolvedAst.element == constructor.declaration,
failedAt(constructor,
"Expected ResolvedAst for $constructor, found $resolvedAst"));
if (resolvedAst.kind == ResolvedAstKind.PARSED) {
buildParsedInitializers(
constructor, constructorResolvedAsts, fieldValues);
} else {
buildSynthesizedConstructorInitializers(
constructor, constructorResolvedAsts, fieldValues);
}
}
void buildSynthesizedConstructorInitializers(
ConstructorElement constructor,
List<ResolvedAst> constructorResolvedAsts,
Map<FieldElement, HInstruction> fieldValues) {
assert(
constructor.isSynthesized,
failedAt(
constructor, "Unexpected unsynthesized constructor: $constructor"));
List<HInstruction> arguments = <HInstruction>[];
HInstruction compileArgument(ParameterElement parameter) {
return localsHandler.readLocal(parameter);
}
ConstructorElement target = constructor.definingConstructor.implementation;
bool match = !target.isMalformed &&
Elements.addForwardingElementArgumentsToList<HInstruction>(
constructor,
arguments,
target,
compileArgument,
handleConstantForOptionalParameter);
if (!match) {
if (compiler.elementHasCompileTimeError(constructor)) {
return;
}
// If this fails, the selector we constructed for the call to a
// forwarding constructor in a mixin application did not match the
// constructor (which, for example, may happen when the libraries are
// not compatible for private names, see issue 20394).
reporter.internalError(
constructor, 'forwarding constructor call does not match');
}
inlineSuperOrRedirect(target.resolvedAst, arguments,
constructorResolvedAsts, fieldValues, constructor);
}
/**
* Run through the initializers and inline all field initializers. Recursively
* inlines super initializers.
*
* The constructors of the inlined initializers is added to [constructors]
* with sub constructors having a lower index than super constructors.
*
* Invariant: The [constructor] and elements in [constructors] must all be
* implementation elements.
*/
void buildParsedInitializers(
ConstructorElement constructor,
List<ResolvedAst> constructorResolvedAsts,
Map<FieldElement, HInstruction> fieldValues) {
assert(
resolvedAst.element == constructor.declaration, failedAt(constructor));
assert(constructor.isImplementation, failedAt(constructor));
assert(
!constructor.isSynthesized,
failedAt(
constructor, "Unexpected synthesized constructor: $constructor"));
ast.FunctionExpression functionNode = resolvedAst.node;
bool foundSuperOrRedirect = false;
if (functionNode.initializers != null) {
Link<ast.Node> initializers = functionNode.initializers.nodes;
for (Link<ast.Node> link = initializers;
!link.isEmpty;
link = link.tail) {
assert(link.head is ast.Send);
if (link.head is! ast.SendSet) {
// A super initializer or constructor redirection.
foundSuperOrRedirect = true;
ast.Send call = link.head;
assert(ast.Initializers.isSuperConstructorCall(call) ||
ast.Initializers.isConstructorRedirect(call));
ConstructorElement target = elements[call];
CallStructure callStructure =
elements.getSelector(call).callStructure;
Link<ast.Node> arguments = call.arguments;
List<HInstruction> compiledArguments;
inlinedFrom(resolvedAst, () {
compiledArguments =
makeStaticArgumentList(callStructure, arguments, target);
});
inlineSuperOrRedirect(target.resolvedAst, compiledArguments,
constructorResolvedAsts, fieldValues, constructor);
} else {
// A field initializer.
ast.SendSet init = link.head;
Link<ast.Node> arguments = init.arguments;
assert(!arguments.isEmpty && arguments.tail.isEmpty);
inlinedFrom(resolvedAst, () {
visit(arguments.head);
});
fieldValues[elements[init]] = pop();
}
}
}
if (!foundSuperOrRedirect) {
// No super initializer found. Try to find the default constructor if
// the class is not Object.
ClassElement enclosingClass = constructor.enclosingClass;
ClassElement superClass = enclosingClass.superclass;
if (!enclosingClass.isObject) {
assert(superClass != null);
assert(superClass.isResolved);
// TODO(johnniwinther): Should we find injected constructors as well?
FunctionElement target = superClass.lookupDefaultConstructor();
if (target == null) {
reporter.internalError(
superClass, "No default constructor available.");
}
List<HInstruction> arguments = Elements.makeArgumentsList<HInstruction>(
CallStructure.NO_ARGS,
const Link<ast.Node>(),
target.implementation,
null,
handleConstantForOptionalParameter);
inlineSuperOrRedirect(target.resolvedAst, arguments,
constructorResolvedAsts, fieldValues, constructor);
}
}
}
/**
* Run through the fields of [cls] and add their potential
* initializers.
*
* Invariant: [classElement] must be an implementation element.
*/
void buildFieldInitializers(
ClassElement classElement, Map<Element, HInstruction> fieldValues) {
assert(classElement.isImplementation, failedAt(classElement));
classElement.forEachInstanceField(
(ClassElement enclosingClass, FieldElement member) {
if (compiler.elementHasCompileTimeError(member)) return;
reporter.withCurrentElement(member, () {
ResolvedAst fieldResolvedAst = member.resolvedAst;
ast.Expression initializer = fieldResolvedAst.body;
if (initializer == null) {
// Unassigned fields of native classes are not initialized to
// prevent overwriting pre-initialized native properties.
if (!nativeData.isNativeOrExtendsNative(classElement)) {
fieldValues[member] = graph.addConstantNull(closedWorld);
}
} else {
ast.Node right = initializer;
ResolvedAst savedResolvedAst = resolvedAst;
resolvedAst = fieldResolvedAst;
final oldElementInferenceResults = elementInferenceResults;
elementInferenceResults =
globalInferenceResults.resultOfMember(member);
inlinedFrom(fieldResolvedAst, () => right.accept(this));
resolvedAst = savedResolvedAst;
elementInferenceResults = oldElementInferenceResults;
fieldValues[member] = pop();
}
});
});
}
/**
* Build the factory function corresponding to the constructor
* [functionElement]:
* - Initialize fields with the values of the field initializers of the
* current constructor and super constructors or constructors redirected
* to, starting from the current constructor.
* - Call the constructor bodies, starting from the constructor(s) in the
* super class(es).
*/
HGraph buildFactory(ResolvedAst resolvedAst) {
ConstructorElement functionElement = resolvedAst.element;
functionElement = functionElement.implementation;
ClassElement classElement = functionElement.enclosingClass.implementation;
bool isNativeUpgradeFactory =
nativeData.isNativeOrExtendsNative(classElement) &&
!nativeData.isJsInteropClass(classElement);
ast.FunctionExpression function;
if (resolvedAst.kind == ResolvedAstKind.PARSED) {
function = resolvedAst.node;
}
// Note that constructors (like any other static function) do not need
// to deal with optional arguments. It is the callers job to provide all
// arguments as if they were positional.
if (inliningStack.isEmpty) {
// The initializer list could contain closures.
openFunction(functionElement, function);
}
Map<FieldElement, HInstruction> fieldValues =
new Map<FieldElement, HInstruction>();
// Compile the possible initialization code for local fields and
// super fields, unless this is a redirecting constructor, in which case
// the effective target will initialize these.
if (!functionElement.isRedirectingGenerative) {
buildFieldInitializers(classElement, fieldValues);
}
// Compile field-parameters such as [:this.x:].
FunctionSignature params = functionElement.functionSignature;
params.orderedForEachParameter((_parameter) {
ParameterElement parameter = _parameter;
if (parameter.isInitializingFormal) {
// If the [element] is a field-parameter then
// initialize the field element with its value.
InitializingFormalElement fieldParameter = parameter;
HInstruction parameterValue = localsHandler.readLocal(fieldParameter);
fieldValues[fieldParameter.fieldElement] = parameterValue;
}
});
// Analyze the constructor and all referenced constructors and collect
// initializers and constructor bodies.
List<ResolvedAst> constructorResolvedAsts = <ResolvedAst>[resolvedAst];
buildInitializers(functionElement, constructorResolvedAsts, fieldValues);
// Call the JavaScript constructor with the fields as argument.
List<HInstruction> constructorArguments = <HInstruction>[];
List<FieldEntity> fields = <FieldEntity>[];
classElement.forEachInstanceField(
(ClassElement enclosingClass, FieldElement member) {
HInstruction value = fieldValues[member];
if (value == null) {
// Uninitialized native fields are pre-initialized by the native
// implementation.
assert(isNativeUpgradeFactory || reporter.hasReportedError,
failedAt(member));
} else {
fields.add(member);
ResolutionDartType type = localsHandler.substInContext(member.type);
constructorArguments
.add(typeBuilder.potentiallyCheckOrTrustType(value, type));
}
}, includeSuperAndInjectedMembers: true);
ResolutionInterfaceType type = classElement.thisType;
TypeMask ssaType =
new TypeMask.nonNullExact(classElement.declaration, closedWorld);
List<DartType> instantiatedTypes;
addInlinedInstantiation(type);
if (!currentInlinedInstantiations.isEmpty) {
instantiatedTypes =
new List<ResolutionInterfaceType>.from(currentInlinedInstantiations);
}
HInstruction newObject;
if (!isNativeUpgradeFactory) {
// Create the runtime type information, if needed.
bool hasRtiInput = false;
if (rtiNeed.classNeedsRtiField(classElement.declaration)) {
// Read the values of the type arguments and create a
// HTypeInfoExpression to set on the newly create object.
hasRtiInput = true;
List<HInstruction> typeArguments = <HInstruction>[];
classElement.typeVariables.forEach((_typeVariable) {
ResolutionTypeVariableType typeVariable = _typeVariable;
HInstruction argument = localsHandler
.readLocal(localsHandler.getTypeVariableAsLocal(typeVariable));
typeArguments.add(argument);
});
HInstruction typeInfo = new HTypeInfoExpression(
TypeInfoExpressionKind.INSTANCE,
classElement.thisType,
typeArguments,
commonMasks.dynamicType);
add(typeInfo);
constructorArguments.add(typeInfo);
}
newObject = new HCreate(
classElement,
constructorArguments,
ssaType,
function != null
? sourceInformationBuilder.buildCreate(function)
: sourceInformationBuilder.buildDeclaration(functionElement),
instantiatedTypes: instantiatedTypes,
hasRtiInput: hasRtiInput);
add(newObject);
} else {
// Bulk assign to the initialized fields.
newObject = graph.explicitReceiverParameter;
// Null guard ensures an error if we are being called from an explicit
// 'new' of the constructor instead of via an upgrade. It is optimized out
// if there are field initializers.
add(new HFieldGet(null, newObject, commonMasks.dynamicType,
isAssignable: false));
for (int i = 0; i < fields.length; i++) {
add(new HFieldSet(fields[i], newObject, constructorArguments[i]));
}
}
removeInlinedInstantiation(type);
// Generate calls to the constructor bodies.
HInstruction interceptor = null;
for (int index = constructorResolvedAsts.length - 1; index >= 0; index--) {
ResolvedAst constructorResolvedAst = constructorResolvedAsts[index];
ConstructorElement constructor =
constructorResolvedAst.element.implementation;
ConstructorBodyElement body =
ConstructorBodyElementX.createFromResolvedAst(constructorResolvedAst);
if (body == null) continue;
List bodyCallInputs = <HInstruction>[];
if (isNativeUpgradeFactory) {
if (interceptor == null) {
ConstantValue constant = new InterceptorConstantValue(classElement);
interceptor = graph.addConstant(constant, closedWorld);
}
bodyCallInputs.add(interceptor);
}
bodyCallInputs.add(newObject);
FunctionSignature functionSignature = body.functionSignature;
// Provide the parameters to the generative constructor body.
functionSignature.orderedForEachParameter((_parameter) {
ParameterElement parameter = _parameter;
// If [parameter] is boxed, it will be a field in the box passed as the
// last parameter. So no need to directly pass it.
if (!localsHandler.isBoxed(parameter)) {
bodyCallInputs.add(localsHandler.readLocal(parameter));
}
});
// If there are locals that escape (ie mutated in closures), we
// pass the box to the constructor.
// The box must be passed before any type variable.
CapturedScope scopeData = closureDataLookup.getCapturedScope(constructor);
if (scopeData.requiresContextBox) {
bodyCallInputs.add(localsHandler.readLocal(scopeData.context));
}
// Type variables arguments must come after the box (if there is one).
ClassElement currentClass = constructor.enclosingClass;
if (rtiNeed.classNeedsRti(currentClass)) {
// If [currentClass] needs RTI, we add the type variables as
// parameters of the generative constructor body.
currentClass.typeVariables.forEach((_argument) {
ResolutionTypeVariableType argument = _argument;
// TODO(johnniwinther): Substitute [argument] with
// `localsHandler.substInContext(argument)`.
bodyCallInputs.add(localsHandler
.readLocal(localsHandler.getTypeVariableAsLocal(argument)));
});
}
if (!isNativeUpgradeFactory && // TODO(13836): Fix inlining.
tryInlineMethod(body, null, null, bodyCallInputs, function)) {
pop();
} else {
ConstructorBodyElement declaration = body.declaration;
HInvokeConstructorBody invoke = new HInvokeConstructorBody(
declaration,
bodyCallInputs,
commonMasks.nonNullType,
sourceInformationBuilder.buildDeclaration(constructor));
invoke.sideEffects = closedWorld.getSideEffectsOfElement(constructor);
add(invoke);
}
}
if (inliningStack.isEmpty) {
closeAndGotoExit(new HReturn(newObject,
sourceInformationBuilder.buildImplicitReturn(functionElement)));
return closeFunction();
} else {
localsHandler.updateLocal(returnLocal, newObject);
return null;
}
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [functionElement] must be the implementation element.
*/
void openFunction(MemberElement element, ast.Node node) {
assert(element.isImplementation, failedAt(element));
HBasicBlock block = graph.addNewBlock();
open(graph.entry);
Map<Local, TypeMask> parameters = <Local, TypeMask>{};
if (element is MethodElement) {
element.functionSignature.orderedForEachParameter((_parameter) {
ParameterElement parameter = _parameter;
parameters[parameter] = TypeMaskFactory.inferredTypeForParameter(
parameter, globalInferenceResults);
});
}
localsHandler.startFunction(
element,
closureDataLookup.getScopeInfo(element),
closureDataLookup.getCapturedScope(element),
parameters,
sourceInformationBuilder.buildDeclaration(element),
isGenerativeConstructorBody: element.isGenerativeConstructorBody);
close(new HGoto()).addSuccessor(block);
open(block);
// Add the type parameters of the class as parameters of this method. This
// must be done before adding the normal parameters, because their types
// may contain references to type variables.
ClassElement cls = element.enclosingClass;
if ((element.isConstructor || element.isGenerativeConstructorBody) &&
rtiNeed.classNeedsRti(cls)) {
cls.typeVariables.forEach((_typeVariable) {
ResolutionTypeVariableType typeVariable = _typeVariable;
HParameterValue param =
addParameter(typeVariable.element, commonMasks.nonNullType);
localsHandler.directLocals[
localsHandler.getTypeVariableAsLocal(typeVariable)] = param;
});
}
if (element is MethodElement) {
MethodElement functionElement = element;
FunctionSignature signature = functionElement.functionSignature;
// Put the type checks in the first successor of the entry,
// because that is where the type guards will also be inserted.
// This way we ensure that a type guard will dominate the type
// check.
signature.forEachParameter((_parameterElement) {
ParameterElement parameterElement = _parameterElement;
if (element.isGenerativeConstructorBody) {
if (closureDataLookup
.getCapturedScope(element)
.isBoxed(parameterElement)) {
// The parameter will be a field in the box passed as the
// last parameter. So no need to have it.
return;
}
}
HInstruction newParameter =
localsHandler.directLocals[parameterElement];
if (!element.isConstructor ||
!(element as ConstructorElement).isRedirectingFactory) {
// Redirection factories must not check their argument types.
// Example:
//
// class A {
// A(String foo) = A.b;
// A.b(int foo) { print(foo); }
// }
// main() {
// new A(499); // valid even in checked mode.
// new A("foo"); // invalid in checked mode.
//
// Only the final target is allowed to check for the argument types.
newParameter = typeBuilder.potentiallyCheckOrTrustType(
newParameter, parameterElement.type);
}
localsHandler.directLocals[parameterElement] = newParameter;
});
returnType = signature.type.returnType;
} else {
// Otherwise it is a lazy initializer which does not have parameters.
assert(element is VariableElement);
}
insertTraceCall(element);
insertCoverageCall(element);
}
insertTraceCall(Element element) {
if (JavaScriptBackend.TRACE_METHOD == 'console') {
if (element == commonElements.traceHelper) return;
n(e) => e == null ? '' : e.name;
String name = "${n(element.library)}:${n(element.enclosingClass)}."
"${n(element)}";
HConstant nameConstant = addConstantString(name);
add(new HInvokeStatic(commonElements.traceHelper,
<HInstruction>[nameConstant], commonMasks.dynamicType));
}
}
insertCoverageCall(Element element) {
if (JavaScriptBackend.TRACE_METHOD == 'post') {
if (element == commonElements.traceHelper) return;
// TODO(sigmund): create a better uuid for elements.
HConstant idConstant =
graph.addConstantInt(element.hashCode, closedWorld);
HConstant nameConstant = addConstantString(element.name);
add(new HInvokeStatic(commonElements.traceHelper,
<HInstruction>[idConstant, nameConstant], commonMasks.dynamicType));
}
}
void assertIsSubtype(ast.Node node, ResolutionDartType subtype,
ResolutionDartType supertype, String message) {
HInstruction subtypeInstruction = typeBuilder.analyzeTypeArgument(
localsHandler.substInContext(subtype), sourceElement);
HInstruction supertypeInstruction = typeBuilder.analyzeTypeArgument(
localsHandler.substInContext(supertype), sourceElement);
HInstruction messageInstruction =
graph.addConstantString(message, closedWorld);
MethodElement element = commonElements.assertIsSubtype;
var inputs = <HInstruction>[
subtypeInstruction,
supertypeInstruction,
messageInstruction
];
HInstruction assertIsSubtype =
new HInvokeStatic(element, inputs, subtypeInstruction.instructionType);
registry?.registerTypeVariableBoundsSubtypeCheck(subtype, supertype);
add(assertIsSubtype);
}
HGraph closeFunction() {
// TODO(kasperl): Make this goto an implicit return.
if (!isAborted()) closeAndGotoExit(new HGoto());
graph.finalize();
return graph;
}
void pushWithPosition(HInstruction instruction, ast.Node node) {
push(attachPosition(instruction, node));
}
/// Pops the most recent instruction from the stack and 'boolifies' it.
///
/// Boolification is checking if the value is '=== true'.
@override
HInstruction popBoolified() {
HInstruction value = pop();
if (typeBuilder.checkOrTrustTypes) {
ResolutionInterfaceType boolType = commonElements.boolType;
return typeBuilder.potentiallyCheckOrTrustType(value, boolType,
kind: HTypeConversion.BOOLEAN_CONVERSION_CHECK);
}
HInstruction result = new HBoolify(value, commonMasks.boolType)
..sourceInformation = value.sourceInformation;
add(result);
return result;
}
HInstruction attachPosition(HInstruction target, ast.Node node) {
if (node != null) {
target.sourceInformation = sourceInformationBuilder.buildGeneric(node);
}
return target;
}
void visit(ast.Node node) {
if (node != null) node.accept(this);
}
/// Visit [node] and pop the resulting [HInstruction].
HInstruction visitAndPop(ast.Node node) {
node.accept(this);
return pop();
}
visitAssert(ast.Assert node) {
if (!options.enableUserAssertions) return;
if (!node.hasMessage) {
// Generate:
//
// assertHelper(condition);
//
visit(node.condition);
pushInvokeStatic(node, commonElements.assertHelper, [pop()]);
pop();
return;
}
// Assert has message. Generate:
//
// if (assertTest(condition)) assertThrow(message);
//
void buildCondition() {
visit(node.condition);
pushInvokeStatic(node, commonElements.assertTest, [pop()]);
}
void fail() {
visit(node.message);
pushInvokeStatic(node, commonElements.assertThrow, [pop()]);
pop();
}
handleIf(node: node, visitCondition: buildCondition, visitThen: fail);
}
visitBlock(ast.Block node) {
assert(!isAborted());
if (!isReachable) return; // This can only happen when inlining.
for (Link<ast.Node> link = node.statements.nodes;
!link.isEmpty;
link = link.tail) {
visit(link.head);
if (!isReachable) {
// The block has been aborted by a return or a throw.
if (!stack.isEmpty) {
reporter.internalError(node, 'Non-empty instruction stack.');
}
return;
}
}
assert(!current.isClosed());
if (!stack.isEmpty) {
reporter.internalError(node, 'Non-empty instruction stack.');
}
}
visitClassNode(ast.ClassNode node) {
reporter.internalError(
node, 'SsaBuilder.visitClassNode should not be called.');
}
visitThrowExpression(ast.Expression expression) {
bool old = inExpressionOfThrow;
try {
inExpressionOfThrow = true;
visit(expression);
} finally {
inExpressionOfThrow = old;
}
}
visitExpressionStatement(ast.ExpressionStatement node) {
if (!isReachable) return;
ast.Throw throwExpression = node.expression.asThrow();
if (throwExpression != null && inliningStack.isEmpty) {
visitThrowExpression(throwExpression.expression);
handleInTryStatement();
closeAndGotoExit(
new HThrow(pop(), sourceInformationBuilder.buildThrow(node)));
} else {
visit(node.expression);
pop();
}
}
visitFor(ast.For node) {
assert(isReachable);
assert(node.body != null);
void buildInitializer() {
ast.Node initializer = node.initializer;
if (initializer == null) return;
visit(initializer);
if (initializer.asExpression() != null) {
pop();
}
}
HInstruction buildCondition() {
if (node.condition == null) {
return graph.addConstantBool(true, closedWorld);
}
visit(node.condition);
return popBoolified();
}
void buildUpdate() {
for (ast.Expression expression in node.update) {
visit(expression);
assert(!isAborted());
// The result of the update instruction isn't used, and can just
// be dropped.
pop();
}
}
void buildBody() {
visit(node.body);
}
loopHandler.handleLoop(
node,
closureDataLookup.getCapturedLoopScope(node),
elements.getTargetDefinition(node),
buildInitializer,
buildCondition,
buildUpdate,
buildBody,
sourceInformationBuilder.buildLoop(node));
}
visitWhile(ast.While node) {
assert(isReachable);
HInstruction buildCondition() {
visit(node.condition);
return popBoolified();
}
loopHandler.handleLoop(node, closureDataLookup.getCapturedLoopScope(node),
elements.getTargetDefinition(node), () {}, buildCondition, () {}, () {
visit(node.body);
}, sourceInformationBuilder.buildLoop(node));
}
visitDoWhile(ast.DoWhile node) {
assert(isReachable);
SourceInformation sourceInformation =
sourceInformationBuilder.buildLoop(node);
LocalsHandler savedLocals = new LocalsHandler.from(localsHandler);
var loopClosureInfo = closureDataLookup.getCapturedLoopScope(node);
localsHandler.startLoop(loopClosureInfo, sourceInformation);
loopDepth++;
JumpTarget target = elements.getTargetDefinition(node);
JumpHandler jumpHandler = loopHandler.beginLoopHeader(node, target);
HLoopInformation loopInfo = current.loopInformation;
HBasicBlock loopEntryBlock = current;
HBasicBlock bodyEntryBlock = current;
bool hasContinues = target != null && target.isContinueTarget;
if (hasContinues) {
// Add extra block to hang labels on.
// It doesn't currently work if they are on the same block as the
// HLoopInfo. The handling of HLabeledBlockInformation will visit a
// SubGraph that starts at the same block again, so the HLoopInfo is
// either handled twice, or it's handled after the labeled block info,
// both of which generate the wrong code.
// Using a separate block is just a simple workaround.
bodyEntryBlock = openNewBlock();
}
localsHandler.enterLoopBody(loopClosureInfo, sourceInformation);
visit(node.body);
// If there are no continues we could avoid the creation of the condition
// block. This could also lead to a block having multiple entries and exits.
HBasicBlock bodyExitBlock;
bool isAbortingBody = false;
if (current != null) {
bodyExitBlock = close(new HGoto());
} else {
isAbortingBody = true;
bodyExitBlock = lastOpenedBlock;
}
SubExpression conditionExpression;
bool loopIsDegenerate = isAbortingBody && !hasContinues;
if (!loopIsDegenerate) {
HBasicBlock conditionBlock = addNewBlock();
List<LocalsHandler> continueHandlers = <LocalsHandler>[];
jumpHandler
.forEachContinue((HContinue instruction, LocalsHandler locals) {
instruction.block.addSuccessor(conditionBlock);
continueHandlers.add(locals);
});
if (!isAbortingBody) {
bodyExitBlock.addSuccessor(conditionBlock);
}
if (!continueHandlers.isEmpty) {
if (!isAbortingBody) continueHandlers.add(localsHandler);
localsHandler =
savedLocals.mergeMultiple(continueHandlers, conditionBlock);
SubGraph bodyGraph = new SubGraph(bodyEntryBlock, bodyExitBlock);
List<LabelDefinition> labels = jumpHandler.labels;
HSubGraphBlockInformation bodyInfo =
new HSubGraphBlockInformation(bodyGraph);
HLabeledBlockInformation info;
if (!labels.isEmpty) {
info =
new HLabeledBlockInformation(bodyInfo, labels, isContinue: true);
} else {
info = new HLabeledBlockInformation.implicit(bodyInfo, target,
isContinue: true);
}
bodyEntryBlock.setBlockFlow(info, conditionBlock);
}
open(conditionBlock);
visit(node.condition);
assert(!isAborted());
HInstruction conditionInstruction = popBoolified();
HBasicBlock conditionEndBlock = close(
new HLoopBranch(conditionInstruction, HLoopBranch.DO_WHILE_LOOP));
HBasicBlock avoidCriticalEdge = addNewBlock();
conditionEndBlock.addSuccessor(avoidCriticalEdge);
open(avoidCriticalEdge);
close(new HGoto());
avoidCriticalEdge.addSuccessor(loopEntryBlock); // The back-edge.
conditionExpression =
new SubExpression(conditionBlock, conditionEndBlock);
// Avoid a critical edge from the condition to the loop-exit body.
HBasicBlock conditionExitBlock = addNewBlock();
open(conditionExitBlock);
close(new HGoto());
conditionEndBlock.addSuccessor(conditionExitBlock);
loopHandler.endLoop(
loopEntryBlock, conditionExitBlock, jumpHandler, localsHandler);
loopEntryBlock.postProcessLoopHeader();
SubGraph bodyGraph = new SubGraph(loopEntryBlock, bodyExitBlock);
HLoopBlockInformation loopBlockInfo = new HLoopBlockInformation(
HLoopBlockInformation.DO_WHILE_LOOP,
null,
wrapExpressionGraph(conditionExpression),
wrapStatementGraph(bodyGraph),
null,
loopEntryBlock.loopInformation.target,
loopEntryBlock.loopInformation.labels,
sourceInformation);
loopEntryBlock.setBlockFlow(loopBlockInfo, current);
loopInfo.loopBlockInformation = loopBlockInfo;
} else {
// Since the loop has no back edge, we remove the loop information on the
// header.
loopEntryBlock.loopInformation = null;
if (jumpHandler.hasAnyBreak()) {
// Null branchBlock because the body of the do-while loop always aborts,
// so we never get to the condition.
loopHandler.endLoop(loopEntryBlock, null, jumpHandler, localsHandler);
// Since the body of the loop has a break, we attach a synthesized label
// to the body.
SubGraph bodyGraph = new SubGraph(bodyEntryBlock, bodyExitBlock);
JumpTarget target = elements.getTargetDefinition(node);
LabelDefinition label =
target.addLabel(null, 'loop', isBreakTarget: true);
HLabeledBlockInformation info = new HLabeledBlockInformation(
new HSubGraphBlockInformation(bodyGraph), <LabelDefinition>[label]);
loopEntryBlock.setBlockFlow(info, current);
jumpHandler.forEachBreak((HBreak breakInstruction, _) {
HBasicBlock block = breakInstruction.block;
block.addAtExit(new HBreak.toLabel(label, sourceInformation));
block.remove(breakInstruction);
});
}
}
jumpHandler.close();
loopDepth--;
}
visitFunctionExpression(ast.FunctionExpression node) {
ClosureRepresentationInfo closureInfo =
closureDataLookup.getClosureInfo(node);
ClassEntity closureClassEntity = closureInfo.closureClassEntity;
List<HInstruction> capturedVariables = <HInstruction>[];
compiler.codegenWorldBuilder.forEachInstanceField(closureClassEntity,
(_, FieldEntity field) {
capturedVariables
.add(localsHandler.readLocal(closureInfo.getLocalForField(field)));
});
TypeMask type = new TypeMask.nonNullExact(closureClassEntity, closedWorld);
push(new HCreate(closureClassEntity, capturedVariables, type,
sourceInformationBuilder.buildCreate(node),
callMethod: closureInfo.callMethod));
}
visitFunctionDeclaration(ast.FunctionDeclaration node) {
assert(isReachable);
visit(node.function);
LocalFunctionElement localFunction =
elements.getFunctionDefinition(node.function);
localsHandler.updateLocal(localFunction, pop());
}
@override
void visitThisGet(ast.Identifier node, [_]) {
stack.add(localsHandler.readThis(
sourceInformation: sourceInformationBuilder.buildGet(node)));
}
visitIdentifier(ast.Identifier node) {
if (node.isThis()) {
visitThisGet(node);
} else {
reporter.internalError(
node, "SsaFromAstMixin.visitIdentifier on non-this.");
}
}
void handleIf(
{ast.Node node,
void visitCondition(),
void visitThen(),
void visitElse(),
SourceInformation sourceInformation}) {
SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, node);
branchBuilder.handleIf(visitCondition, visitThen, visitElse,
sourceInformation: sourceInformation);
}
visitIf(ast.If node) {
assert(isReachable);
handleIf(
node: node,
visitCondition: () => visit(node.condition),
visitThen: () => visit(node.thenPart),
visitElse: node.elsePart != null ? () => visit(node.elsePart) : null,
sourceInformation: sourceInformationBuilder.buildIf(node));
}
@override
void visitIfNull(ast.Send node, ast.Node left, ast.Node right, _) {
SsaBranchBuilder brancher = new SsaBranchBuilder(this, node);
brancher.handleIfNull(() => visit(left), () => visit(right));
}
/// Optimizes logical binary where the left is also a logical binary.
///
/// This method transforms the operator by optimizing the case where [left] is
/// a logical "and" or logical "or". Then it uses [branchBuilder] to build the
/// graph for the optimized expression.
///
/// For example, `(x && y) && z` is transformed into `x && (y && z)`:
///
/// t0 = boolify(x);
/// if (t0) {
/// t1 = boolify(y);
/// if (t1) {
/// t2 = boolify(z);
/// }
/// t3 = phi(t2, false);
/// }
/// result = phi(t3, false);
void handleLogicalBinaryWithLeftNode(ast.Node left, void visitRight(),
SsaBranchBuilder branchBuilder, SourceInformation sourceInformation,
{bool isAnd}) {
ast.Send send = left.asSend();
if (send != null && (isAnd ? send.isLogicalAnd : send.isLogicalOr)) {
ast.Node newLeft = send.receiver;
Link<ast.Node> link = send.argumentsNode.nodes;
assert(link.tail.isEmpty);
ast.Node middle = link.head;
handleLogicalBinaryWithLeftNode(
newLeft,
() => handleLogicalBinaryWithLeftNode(middle, visitRight,
branchBuilder, sourceInformationBuilder.buildBinary(middle),
isAnd: isAnd),
branchBuilder,
sourceInformation,
isAnd: isAnd);
} else {
branchBuilder.handleLogicalBinary(
() => visit(left), visitRight, sourceInformation,
isAnd: isAnd);
}
}
@override
void visitLogicalAnd(ast.Send node, ast.Node left, ast.Node right, _) {
SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, node);
handleLogicalBinaryWithLeftNode(left, () => visit(right), branchBuilder,
sourceInformationBuilder.buildBinary(node),
isAnd: true);
}
@override
void visitLogicalOr(ast.Send node, ast.Node left, ast.Node right, _) {
SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, node);
handleLogicalBinaryWithLeftNode(left, () => visit(right), branchBuilder,
sourceInformationBuilder.buildBinary(node),
isAnd: false);
}
@override
void visitNot(ast.Send node, ast.Node expression, _) {
assert(node.argumentsNode is ast.Prefix);
visit(expression);
SourceInformation sourceInformation =
sourceInformationBuilder.buildGeneric(node);
push(new HNot(popBoolified(), commonMasks.boolType)
..sourceInformation = sourceInformation);
}
@override
void visitUnary(
ast.Send node, UnaryOperator operator, ast.Node expression, _) {
assert(node.argumentsNode is ast.Prefix);
HInstruction operand = visitAndPop(expression);
// See if we can constant-fold right away. This avoids rewrites later on.
if (operand is HConstant) {
UnaryOperation operation = constantSystem.lookupUnary(operator);
HConstant constant = operand;
ConstantValue folded = operation.fold(constant.constant);
if (folded != null) {
stack.add(graph.addConstant(folded, closedWorld));
return;
}
}
pushInvokeDynamic(node, elements.getSelector(node),
elementInferenceResults.typeOfSend(node), [operand],
sourceInformation: sourceInformationBuilder.buildGeneric(node));
}
@override
void visitBinary(ast.Send node, ast.Node left, BinaryOperator operator,
ast.Node right, _) {
handleBinary(node, left, right);
}
@override
void visitIndex(ast.Send node, ast.Node receiver, ast.Node index, _) {
generateDynamicSend(node);
}
@override
void visitEquals(ast.Send node, ast.Node left, ast.Node right, _) {
handleBinary(node, left, right);
}
@override
void visitNotEquals(ast.Send node, ast.Node left, ast.Node right, _) {
handleBinary(node, left, right);
pushWithPosition(
new HNot(popBoolified(), commonMasks.boolType), node.selector);
}
void handleBinary(ast.Send node, ast.Node left, ast.Node right) {
visitBinarySend(
visitAndPop(left),
visitAndPop(right),
elements.getSelector(node),
elementInferenceResults.typeOfSend(node),
node,
sourceInformation:
sourceInformationBuilder.buildGeneric(node.selector));
}
/// TODO(johnniwinther): Merge [visitBinarySend] with [handleBinary] and
/// remove use of [location] for source information.
void visitBinarySend(HInstruction left, HInstruction right, Selector selector,
TypeMask mask, ast.Send send,
{SourceInformation sourceInformation}) {
pushInvokeDynamic(send, selector, mask, [left, right],
sourceInformation: sourceInformation);
}
HInstruction generateInstanceSendReceiver(ast.Send send) {
assert(Elements.isInstanceSend(send, elements));
if (send.receiver == null) {
return localsHandler.readThis(
sourceInformation: sourceInformationBuilder.buildGet(send));
}
visit(send.receiver);
return pop();
}
String noSuchMethodTargetSymbolString(Element error, [String prefix]) {
String result = error.name;
if (prefix == "set") return "$result=";
return result;
}
/**
* Returns a set of interceptor classes that contain the given
* [selector].
*/
void generateInstanceGetterWithCompiledReceiver(
ast.Send send, Selector selector, TypeMask mask, HInstruction receiver) {
assert(Elements.isInstanceSend(send, elements));
assert(selector.isGetter);
pushInvokeDynamic(send, selector, mask, [receiver],
sourceInformation: sourceInformationBuilder.buildGet(send));
}
/// Inserts a call to checkDeferredIsLoaded for a deferred [import].
/// If [import] is [null], do nothing.
void generateIsDeferredLoadedCheckIfNeeded(
ImportElement import, ast.Node location) {
if (import == null) return;
String loadId = deferredLoadTask.getImportDeferName(location, import);
HInstruction loadIdConstant = addConstantString(loadId);
String uri = import.uri.toString();
HInstruction uriConstant = addConstantString(uri);
MethodElement helper = commonElements.checkDeferredIsLoaded;
pushInvokeStatic(location, helper, [loadIdConstant, uriConstant]);
pop();
}
/// Inserts a call to checkDeferredIsLoaded if the send has a prefix that
/// resolves to a deferred library.
void generateIsDeferredLoadedCheckOfSend(ast.Send node) {
generateIsDeferredLoadedCheckIfNeeded(
deferredLoadTask.deferredImportElement(node, elements), node);
}
void handleInvalidStaticGet(ast.Send node, Element element) {
SourceInformation sourceInformation =
sourceInformationBuilder.buildGet(node);
generateThrowNoSuchMethod(
node, noSuchMethodTargetSymbolString(element, 'get'),
argumentNodes: const Link<ast.Node>(),
sourceInformation: sourceInformation);
}
/// Generate read access of an unresolved static or top level entity.
void generateStaticUnresolvedGet(ast.Send node, Element element) {
if (element is ErroneousElement) {
// An erroneous element indicates an unresolved static getter.
handleInvalidStaticGet(node, element);
} else {
// This happens when [element] has parse errors.
assert(element == null || element.isMalformed, failedAt(node));
// TODO(ahe): Do something like the above, that is, emit a runtime
// error.
stack.add(graph.addConstantNull(closedWorld));
}
}
/// Read a static or top level [field] of constant value.
void generateStaticConstGet(ast.Send node, FieldElement field,
ConstantExpression constant, SourceInformation sourceInformation) {
ConstantValue value = constants.getConstantValue(constant);
HConstant instruction;
// Constants that are referred via a deferred prefix should be referred
// by reference.
ImportElement deferredImport =
deferredLoadTask.deferredImportElement(node, elements);
if (deferredImport != null) {
instruction = graph.addDeferredConstant(
value, deferredImport, sourceInformation, compiler, closedWorld);
} else {
instruction = graph.addConstant(value, closedWorld,
sourceInformation: sourceInformation);
}
stack.add(instruction);
// The inferrer may have found a better type than the constant
// handler in the case of lists, because the constant handler
// does not look at elements in the list.
TypeMask type =
TypeMaskFactory.inferredTypeForMember(field, globalInferenceResults);
if (!type.containsAll(closedWorld) && !instruction.isConstantNull()) {
// TODO(13429): The inferrer should know that an element
// cannot be null.
instruction.instructionType = type.nonNullable();
}
}
@override
void previsitDeferredAccess(ast.Send node, PrefixElement prefix, _) {
generateIsDeferredLoadedCheckIfNeeded(prefix.deferredImport, node);
}
/// Read a static or top level [field].
void generateStaticFieldGet(ast.Send node, FieldElement field) {
ConstantExpression constant = field.constant;
SourceInformation sourceInformation =
sourceInformationBuilder.buildGet(node.selector);
if (constant != null) {
if (!field.isAssignable) {
// A static final or const. Get its constant value and inline it if
// the value can be compiled eagerly.
generateStaticConstGet(node, field, constant, sourceInformation);
} else {
// TODO(5346): Try to avoid the need for calling [declaration] before
// creating an [HStatic].
HInstruction instruction = new HStatic(
field,
TypeMaskFactory.inferredTypeForMember(
field, globalInferenceResults),
sourceInformation);
push(instruction);
}
} else {
HInstruction instruction = new HLazyStatic(
field,
TypeMaskFactory.inferredTypeForMember(field, globalInferenceResults),
sourceInformation);
push(instruction);
}
}
/// Generate a getter invocation of the static or top level [getter].
void generateStaticGetterGet(ast.Send node, MethodElement getter) {
SourceInformation sourceInformation =
sourceInformationBuilder.buildGet(node.selector);
if (getter.isDeferredLoaderGetter) {
generateDeferredLoaderGet(node, getter, sourceInformation);
} else {
pushInvokeStatic(node, getter, <HInstruction>[],
sourceInformation: sourceInformation);
}
}
/// Generate a dynamic getter invocation.
void generateDynamicGet(ast.Send node) {
HInstruction receiver = generateInstanceSendReceiver(node);
generateInstanceGetterWithCompiledReceiver(node, elements.getSelector(node),
elementInferenceResults.typeOfSend(node), receiver);
}
/// Generate a closurization of the static or top level [method].
void generateStaticFunctionGet(ast.Send node, MethodElement method) {
assert(method.isDeclaration);
// TODO(5346): Try to avoid the need for calling [declaration] before
// creating an [HStatic].
SourceInformation sourceInformation =
sourceInformationBuilder.buildGet(node.selector);
push(new HStatic(method, commonMasks.nonNullType, sourceInformation));
}
/// Read a local variable, function or parameter.
void buildLocalGet(LocalElement local, SourceInformation sourceInformation) {
stack.add(
localsHandler.readLocal(local, sourceInformation: sourceInformation));
}
void handleLocalGet(ast.Send node, LocalElement local) {
buildLocalGet(local, sourceInformationBuilder.buildGet(node));
}
@override
void visitDynamicPropertyGet(ast.Send node, ast.Node receiver, Name name, _) {
generateDynamicGet(node);
}
@override
void visitIfNotNullDynamicPropertyGet(
ast.Send node, ast.Node receiver, Name name, _) {
// exp?.x compiled as:
// t1 = exp;
// result = t1 == null ? t1 : t1.x;
// This is equivalent to t1 == null ? null : t1.x, but in the current form
// we will be able to later compress it as:
// t1 || t1.x
HInstruction expression;
SsaBranchBuilder brancher = new SsaBranchBuilder(this, node);
brancher.handleConditional(
() {
expression = visitAndPop(receiver);
pushCheckNull(expression);
},
() => stack.add(expression),
() {
generateInstanceGetterWithCompiledReceiver(
node,
elements.getSelector(node),
elementInferenceResults.typeOfSend(node),
expression);
});
}
@override
void visitLocalVariableGet(ast.Send node, LocalVariableElement variable, _) {
handleLocalGet(node, variable);
}
@override
void visitParameterGet(ast.Send node, ParameterElement parameter, _) {
handleLocalGet(node, parameter);
}
@override
void visitLocalFunctionGet(ast.Send node, LocalFunctionElement function, _) {
handleLocalGet(node, function);
}
@override
void visitStaticFieldGet(ast.Send node, FieldElement field, _) {
generateStaticFieldGet(node, field);
}
@override
void visitStaticFunctionGet(ast.Send node, MethodElement function, _) {
generateStaticFunctionGet(node, function);
}
@override
void visitStaticGetterGet(ast.Send node, FunctionElement getter, _) {
generateStaticGetterGet(node, getter);
}
@override
void visitThisPropertyGet(ast.Send node, Name name, _) {
generateDynamicGet(node);
}
@override
void visitTopLevelFieldGet(ast.Send node, FieldElement field, _) {
generateStaticFieldGet(node, field);
}
@override
void visitTopLevelFunctionGet(ast.Send node, MethodElement function, _) {
generateStaticFunctionGet(node, function);
}
@override
void visitTopLevelGetterGet(ast.Send node, FunctionElement getter, _) {
generateStaticGetterGet(node, getter);
}
void generateInstanceSetterWithCompiledReceiver(
ast.Send send, HInstruction receiver, HInstruction value,
{Selector selector, TypeMask mask, ast.Node location}) {
assert(
send == null || Elements.isInstanceSend(send, elements),
failedAt(
send ?? location,
"Unexpected instance setter"
"${send != null ? " element: ${elements[send]}" : ""}"));
if (selector == null) {
assert(send != null);
selector = elements.getSelector(send);
mask ??= elementInferenceResults.typeOfSend(send);
}
if (location == null) {
assert(send != null);
location = send;
}
assert(selector.isSetter);
pushInvokeDynamic(location, selector, mask, [receiver, value],
sourceInformation: sourceInformationBuilder.buildAssignment(location));
pop();
stack.add(value);
}
void generateNoSuchSetter(
ast.Node location, Element element, HInstruction value) {
List<HInstruction> arguments =
value == null ? const <HInstruction>[] : <HInstruction>[value];
// An erroneous element indicates an unresolved static setter.
generateThrowNoSuchMethod(
location, noSuchMethodTargetSymbolString(element, 'set'),
argumentValues: arguments);
}
void generateNonInstanceSetter(
ast.SendSet send, Element element, HInstruction value,
{ast.Node location}) {
if (location == null) {
assert(send != null);
location = send;
}
assert(send == null || !Elements.isInstanceSend(send, elements),
failedAt(location, "Unexpected non instance setter: $element."));
if (Elements.isStaticOrTopLevelField(element)) {
if (element.isSetter) {
pushInvokeStatic(location, element, <HInstruction>[value]);
pop();
} else {
FieldElement field = element;
value = typeBuilder.potentiallyCheckOrTrustType(value, field.type);
addWithPosition(new HStaticStore(field, value), location);
}
stack.add(value);
} else if (Elements.isError(element)) {
generateNoSuchSetter(location, element, send == null ? null : value);
} else if (Elements.isMalformed(element)) {
// TODO(ahe): Do something like [generateWrongArgumentCountError].
stack.add(graph.addConstantNull(closedWorld));
} else {
stack.add(value);
LocalElement local = element;
// If the value does not already have a name, give it here.
if (value.sourceElement == null) {
value.sourceElement = local;
}
HInstruction checkedOrTrusted =
typeBuilder.potentiallyCheckOrTrustType(value, local.type);
if (!identical(checkedOrTrusted, value)) {
pop();
stack.add(checkedOrTrusted);
}
localsHandler.updateLocal(local, checkedOrTrusted,
sourceInformation:
sourceInformationBuilder.buildAssignment(location));
}
}
HInstruction invokeInterceptor(HInstruction receiver) {
HInterceptor interceptor =
new HInterceptor(receiver, commonMasks.nonNullType);
add(interceptor);
return interceptor;
}
@override
void visitAs(ast.Send node, ast.Node expression, ResolutionDartType type, _) {
HInstruction expressionInstruction = visitAndPop(expression);
if (type.isMalformed) {
if (type is MalformedType) {
ErroneousElement element = type.element;
generateTypeError(node, element.message);
} else {
assert(type is MethodTypeVariableType);
stack.add(expressionInstruction);
}
} else {
HInstruction converted = typeBuilder.buildTypeConversion(
expressionInstruction,
localsHandler.substInContext(type),
HTypeConversion.CAST_TYPE_CHECK,
sourceInformation: sourceInformationBuilder.buildAs(node));
if (converted != expressionInstruction) add(converted);
stack.add(converted);
}
}
@override
void visitIs(ast.Send node, ast.Node expression, ResolutionDartType type, _) {
HInstruction expressionInstruction = visitAndPop(expression);
push(buildIsNode(node, type, expressionInstruction,
sourceInformationBuilder.buildIs(node)));
}
@override
void visitIsNot(
ast.Send node, ast.Node expression, ResolutionDartType type, _) {
HInstruction expressionInstruction = visitAndPop(expression);
HInstruction instruction = buildIsNode(node, type, expressionInstruction,
sourceInformationBuilder.buildIs(node));
add(instruction);
push(new HNot(instruction, commonMasks.boolType));
}
HInstruction buildIsNode(ast.Node node, ResolutionDartType type,
HInstruction expression, SourceInformation sourceInformation) {
type = localsHandler.substInContext(type).unaliased;
if (type.isMalformed) {
String message;
if (type is MethodTypeVariableType) {
message = "Method type variables are not reified, "
"so they cannot be tested with an `is` expression.";
} else {
assert(type is MalformedType);
ErroneousElement element = type.element;
message = element.message;
}
generateTypeError(node, message);
HInstruction call = pop();
return new HIs.compound(
type, expression, call, commonMasks.boolType, sourceInformation);
} else if (type.isFunctionType) {
HInstruction representation =
typeBuilder.analyzeTypeArgument(type, sourceElement);
List<HInstruction> inputs = <HInstruction>[
expression,
representation,
];
pushInvokeStatic(node, commonElements.functionTypeTest, inputs,
typeMask: commonMasks.boolType, sourceInformation: sourceInformation);
HInstruction call = pop();
return new HIs.compound(
type, expression, call, commonMasks.boolType, sourceInformation);
} else if (type.isTypeVariable) {
ResolutionTypeVariableType typeVariable = type;
HInstruction runtimeType =
typeBuilder.addTypeVariableReference(typeVariable, sourceElement);
MethodElement helper = commonElements.checkSubtypeOfRuntimeType;
List<HInstruction> inputs = <HInstruction>[expression, runtimeType];
pushInvokeStatic(null, helper, inputs,
typeMask: commonMasks.boolType, sourceInformation: sourceInformation);
HInstruction call = pop();
return new HIs.variable(
type, expression, call, commonMasks.boolType, sourceInformation);
} else if (RuntimeTypesSubstitutions.hasTypeArguments(type)) {
ClassElement element = type.element;
MethodElement helper = commonElements.checkSubtype;
HInstruction representations =
typeBuilder.buildTypeArgumentRepresentations(type, sourceElement);
add(representations);
js.Name operator = namer.operatorIs(element);
HInstruction isFieldName = addConstantStringFromName(operator);
HInstruction asFieldName = closedWorld.hasAnyStrictSubtype(element)
? addConstantStringFromName(namer.substitutionName(element))
: graph.addConstantNull(closedWorld);
List<HInstruction> inputs = <HInstruction>[
expression,
isFieldName,
representations,
asFieldName
];
pushInvokeStatic(node, helper, inputs,
typeMask: commonMasks.boolType, sourceInformation: sourceInformation);
HInstruction call = pop();
return new HIs.compound(
type, expression, call, commonMasks.boolType, sourceInformation);
} else {
if (backend.hasDirectCheckFor(closedWorld.commonElements, type)) {
return new HIs.direct(
type, expression, commonMasks.boolType, sourceInformation);
}
// The interceptor is not always needed. It is removed by optimization
// when the receiver type or tested type permit.
return new HIs.raw(type, expression, invokeInterceptor(expression),
commonMasks.boolType, sourceInformation);
}
}
void addDynamicSendArgumentsToList(ast.Send node, List<HInstruction> list) {
CallStructure callStructure = elements.getSelector(node).callStructure;
if (callStructure.namedArgumentCount == 0) {
addGenericSendArgumentsToList(node.arguments, list);
} else {
// Visit positional arguments and add them to the list.
Link<ast.Node> arguments = node.arguments;
int positionalArgumentCount = callStructure.positionalArgumentCount;
for (int i = 0;
i < positionalArgumentCount;
arguments = arguments.tail, i++) {
visit(arguments.head);
list.add(pop());
}
// Visit named arguments and add them into a temporary map.
Map<String, HInstruction> instructions = new Map<String, HInstruction>();
List<String> namedArguments = callStructure.namedArguments;
int nameIndex = 0;
for (; !arguments.isEmpty; arguments = arguments.tail) {
visit(arguments.head);
instructions[namedArguments[nameIndex++]] = pop();
}
// Iterate through the named arguments to add them to the list
// of instructions, in an order that can be shared with
// selectors with the same named arguments.
List<String> orderedNames = callStructure.getOrderedNamedArguments();
for (String name in orderedNames) {
list.add(instructions[name]);
}
}
}
/**
* Returns a list with the evaluated [arguments] in the normalized order.
*
* Precondition: `this.applies(element, world)`.
* Invariant: [element] must be an implementation element.
*/
List<HInstruction> makeStaticArgumentList(CallStructure callStructure,
Link<ast.Node> arguments, MethodElement element) {
assert(element.isDeclaration, failedAt(element));
HInstruction compileArgument(ast.Node argument) {
visit(argument);
return pop();
}
return Elements.makeArgumentsList<HInstruction>(
callStructure,
arguments,
element.implementation,
compileArgument,
nativeData.isJsInteropMember(element)
? handleConstantForOptionalParameterJsInterop
: handleConstantForOptionalParameter);
}
void addGenericSendArgumentsToList(
Link<ast.Node> link, List<HInstruction> list) {
for (; !link.isEmpty; link = link.tail) {
visit(link.head);
list.add(pop());
}
}
/// Generate a dynamic method, getter or setter invocation.
void generateDynamicSend(ast.Send node) {
HInstruction receiver = generateInstanceSendReceiver(node);
_generateDynamicSend(node, receiver);
}
void _generateDynamicSend(ast.Send node, HInstruction receiver) {
Selector selector = elements.getSelector(node);
TypeMask mask = elementInferenceResults.typeOfSend(node);
SourceInformation sourceInformation =
sourceInformationBuilder.buildCall(node, node.selector);
List<HInstruction> inputs = <HInstruction>[];
inputs.add(receiver);
addDynamicSendArgumentsToList(node, inputs);
pushInvokeDynamic(node, selector, mask, inputs,
sourceInformation: sourceInformation);
if (selector.isSetter || selector.isIndexSet) {
pop();
stack.add(inputs.last);
}
}
@override
visitDynamicPropertyInvoke(ast.Send node, ast.Node receiver,
ast.NodeList arguments, Selector selector, _) {
generateDynamicSend(node);
}
@override
visitIfNotNullDynamicPropertyInvoke(ast.Send node, ast.Node receiver,
ast.NodeList arguments, Selector selector, _) {
/// Desugar `exp?.m()` to `(t1 = exp) == null ? t1 : t1.m()`
HInstruction receiver;
SsaBranchBuilder brancher = new SsaBranchBuilder(this, node);
brancher.handleConditional(() {
receiver = generateInstanceSendReceiver(node);
pushCheckNull(receiver);
}, () => stack.add(receiver), () => _generateDynamicSend(node, receiver));
}
@override
visitThisPropertyInvoke(
ast.Send node, ast.NodeList arguments, Selector selector, _) {
generateDynamicSend(node);
}
@override
visitExpressionInvoke(ast.Send node, ast.Node expression,
ast.NodeList arguments, CallStructure callStructure, _) {
generateCallInvoke(node, visitAndPop(expression),
sourceInformationBuilder.buildCall(node, node.argumentsNode));
}
@override
visitThisInvoke(
ast.Send node, ast.NodeList arguments, CallStructure callStructure, _) {
generateCallInvoke(
node,
localsHandler.readThis(
sourceInformation: sourceInformationBuilder.buildGet(node)),
sourceInformationBuilder.buildCall(node, node.argumentsNode));
}
@override
visitParameterInvoke(ast.Send node, ParameterElement parameter,
ast.NodeList arguments, CallStructure callStructure, _) {
generateCallInvoke(node, localsHandler.readLocal(parameter),
sourceInformationBuilder.buildCall(node, node.argumentsNode));
}
@override
visitLocalVariableInvoke(ast.Send node, LocalVariableElement variable,
ast.NodeList arguments, CallStructure callStructure, _) {
generateCallInvoke(node, localsHandler.readLocal(variable),
sourceInformationBuilder.buildCall(node, node.argumentsNode));
}
@override
visitLocalFunctionInvoke(ast.Send node, LocalFunctionElement function,
ast.NodeList arguments, CallStructure callStructure, _) {
generateCallInvoke(node, localsHandler.readLocal(function),
sourceInformationBuilder.buildCall(node, node.argumentsNode));
}
@override
visitLocalFunctionIncompatibleInvoke(
ast.Send node,
LocalFunctionElement function,
ast.NodeList arguments,
CallStructure callStructure,
_) {
generateCallInvoke(node, localsHandler.readLocal(function),
sourceInformationBuilder.buildCall(node, node.argumentsNode));
}
void handleForeignJs(ast.Send node) {
Link<ast.Node> link = node.arguments;
// Don't visit the first argument, which is the type, and the second
// argument, which is the foreign code.
if (link.isEmpty || link.tail.isEmpty) {
// We should not get here because the call should be compiled to NSM.
reporter.internalError(
node.argumentsNode, 'At least two arguments expected.');
}
native.NativeBehavior nativeBehavior = elements.getNativeData(node);
assert(
nativeBehavior != null, failedAt(node, "No NativeBehavior for $node"));
List<HInstruction> inputs = <HInstruction>[];
addGenericSendArgumentsToList(link.tail.tail, inputs);
if (nativeBehavior.codeTemplate.positionalArgumentCount != inputs.length) {
reporter.reportErrorMessage(node, MessageKind.GENERIC, {
'text': 'Mismatch between number of placeholders'
' and number of arguments.'
});
// Result expected on stack.
stack.add(graph.addConstantNull(closedWorld));
return;
}
if (native.HasCapturedPlaceholders.check(nativeBehavior.codeTemplate.ast)) {
reporter.reportErrorMessage(node, MessageKind.JS_PLACEHOLDER_CAPTURE);
}
TypeMask ssaType =
TypeMaskFactory.fromNativeBehavior(nativeBehavior, closedWorld);
DartType typeArgument;
ast.NodeList typeArgumentsNode = node.typeArgumentsNode;
if (typeArgumentsNode != null) {
if (typeArgumentsNode.slowLength() == 1) {
ast.Node typeNode = typeArgumentsNode.single;
typeArgument = elements.getType(typeNode);
} else {
reporter.reportErrorMessage(typeArgumentsNode, MessageKind.GENERIC,
{'text': 'JS takes one type argument'});
}
}
SourceInformation sourceInformation =
sourceInformationBuilder.buildCall(node, node.argumentsNode);
push(new HForeignCode(nativeBehavior.codeTemplate, ssaType, inputs,
isStatement: !nativeBehavior.codeTemplate.isExpression,
effects: nativeBehavior.sideEffects,
nativeBehavior: nativeBehavior)
..sourceInformation = sourceInformation);
HInstruction code = stack.last;
TypeMask trustedMask = typeBuilder.trustTypeMask(typeArgument);
if (trustedMask != null) {
// We only allow the type argument to narrow `dynamic`, which probably
// comes from an unspecified return type in the NativeBehavior.
if (code.instructionType.containsAll(closedWorld)) {
// Overwrite the type with the narrower type.
code.instructionType = trustedMask;
} else if (trustedMask.containsMask(code.instructionType, closedWorld)) {
// It is acceptable for the type parameter to be broader than the
// specified type.
} else {
reporter.reportErrorMessage(typeArgumentsNode, MessageKind.GENERIC, {
'text': 'Type argument too narrow for specified behavior type '
'(${trustedMask} does not allow '
'all values in ${code.instructionType})'
});
}
}
}
void handleJsStringConcat(ast.Send node) {
List<HInstruction> inputs = <HInstruction>[];
addGenericSendArgumentsToList(node.arguments, inputs);
if (inputs.length != 2) {
reporter.internalError(node.argumentsNode, 'Two arguments expected.');
}
push(new HStringConcat(inputs[0], inputs[1], commonMasks.stringType));
}
void handleForeignJsCurrentIsolateContext(ast.Send node) {
if (!node.arguments.isEmpty) {
reporter.internalError(
node, 'Too many arguments to JS_CURRENT_ISOLATE_CONTEXT.');
}
if (!backendUsage.isIsolateInUse) {
// If the isolate library is not used, we just generate code
// to fetch the static state.
String name = namer.staticStateHolder;
push(new HForeignCode(
js.js.parseForeignJS(name), commonMasks.dynamicType, <HInstruction>[],
nativeBehavior: native.NativeBehavior.DEPENDS_OTHER));
} else {
// Call a helper method from the isolate library. The isolate
// library uses its own isolate structure, that encapsulates
// Leg's isolate.
MethodElement element = commonElements.currentIsolate;
if (element == null) {
reporter.internalError(node, 'Isolate library and compiler mismatch.');
}
pushInvokeStatic(null, element, [], typeMask: commonMasks.dynamicType);
}
}
void handleForeignJsGetFlag(ast.Send node) {
List<ast.Node> arguments = node.arguments.toList();
ast.Node argument;
switch (arguments.length) {
case 0:
reporter.reportErrorMessage(node, MessageKind.GENERIC,
{'text': 'Error: Expected one argument to JS_GET_FLAG.'});
return;
case 1:
argument = arguments[0];
break;
default:
for (int i = 1; i < arguments.length; i++) {
reporter.reportErrorMessage(arguments[i], MessageKind.GENERIC,
{'text': 'Error: Extra argument to JS_GET_FLAG.'});
}
return;
}
ast.LiteralString string = argument.asLiteralString();
if (string == null) {
reporter.reportErrorMessage(argument, MessageKind.GENERIC,
{'text': 'Error: Expected a literal string.'});
}
String name = string.dartString.slowToString();
bool value = getFlagValue(name);
if (value == null) {
reporter.reportErrorMessage(node, MessageKind.GENERIC,
{'text': 'Error: Unknown internal flag "$name".'});
} else {
stack.add(graph.addConstantBool(value, closedWorld));
}
}
void handleForeignJsGetName(ast.Send node) {
List<ast.Node> arguments = node.arguments.toList();
ast.Node argument;
switch (arguments.length) {
case 0:
reporter.reportErrorMessage(node, MessageKind.GENERIC,
{'text': 'Error: Expected one argument to JS_GET_NAME.'});
return;
case 1:
argument = arguments[0];
break;
default:
for (int i = 1; i < arguments.length; i++) {
reporter.reportErrorMessage(arguments[i], MessageKind.GENERIC,
{'text': 'Error: Extra argument to JS_GET_NAME.'});
}
return;
}
Element element = elements[argument];
if (element == null ||
element is! EnumConstantElement ||
element.enclosingClass != commonElements.jsGetNameEnum) {
reporter.reportErrorMessage(argument, MessageKind.GENERIC,
{'text': 'Error: Expected a JsGetName enum value.'});
}
EnumConstantElement enumConstant = element;
int index = enumConstant.index;
stack.add(addConstantStringFromName(
namer.getNameForJsGetName(argument, JsGetName.values[index])));
}
void handleForeignJsBuiltin(ast.Send node) {
List<ast.Node> arguments = node.arguments.toList();
ast.Node argument;
if (arguments.length < 2) {
reporter.reportErrorMessage(node, MessageKind.GENERIC,
{'text': 'Error: Expected at least two arguments to JS_BUILTIN.'});
}
Element builtinElement = elements[arguments[1]];
if (builtinElement == null ||
(builtinElement is! EnumConstantElement) ||
builtinElement.enclosingClass != commonElements.jsBuiltinEnum) {
reporter.reportErrorMessage(argument, MessageKind.GENERIC,
{'text': 'Error: Expected a JsBuiltin enum value.'});
}
EnumConstantElement enumConstant = builtinElement;
int index = enumConstant.index;
js.Template template = emitter.builtinTemplateFor(JsBuiltin.values[index]);
List<HInstruction> compiledArguments = <HInstruction>[];
for (int i = 2; i < arguments.length; i++) {
visit(arguments[i]);
compiledArguments.add(pop());
}
native.NativeBehavior nativeBehavior = elements.getNativeData(node);
assert(
nativeBehavior != null, failedAt(node, "No NativeBehavior for $node"));
TypeMask ssaType =
TypeMaskFactory.fromNativeBehavior(nativeBehavior, closedWorld);
push(new HForeignCode(template, ssaType, compiledArguments,
nativeBehavior: nativeBehavior));
}
void handleForeignJsEmbeddedGlobal(ast.Send node) {
List<ast.Node> arguments = node.arguments.toList();
ast.Node globalNameNode;
switch (arguments.length) {
case 0:
case 1:
reporter.reportErrorMessage(node, MessageKind.GENERIC,
{'text': 'Error: Expected two arguments to JS_EMBEDDED_GLOBAL.'});
return;
case 2:
// The type has been extracted earlier. We are only interested in the
// name in this function.
globalNameNode = arguments[1];
break;
default:
for (int i = 2; i < arguments.length; i++) {
reporter.reportErrorMessage(arguments[i], MessageKind.GENERIC,
{'text': 'Error: Extra argument to JS_EMBEDDED_GLOBAL.'});
}
return;
}
visit(globalNameNode);
HInstruction globalNameHNode = pop();
if (!globalNameHNode.isConstantString()) {
reporter.reportErrorMessage(arguments[1], MessageKind.GENERIC, {
'text': 'Error: Expected String as second argument '
'to JS_EMBEDDED_GLOBAL.'
});
return;
}
HConstant hConstant = globalNameHNode;
StringConstantValue constant = hConstant.constant;
String globalName = constant.primitiveValue;
js.Template expr = js.js.expressionTemplateYielding(
emitter.generateEmbeddedGlobalAccess(globalName));
native.NativeBehavior nativeBehavior = elements.getNativeData(node);
assert(
nativeBehavior != null, failedAt(node, "No NativeBehavior for $node"));
TypeMask ssaType =
TypeMaskFactory.fromNativeBehavior(nativeBehavior, closedWorld);
push(new HForeignCode(expr, ssaType, const [],
nativeBehavior: nativeBehavior));
}
void handleJsInterceptorConstant(ast.Send node) {
// Single argument must be a TypeConstant which is converted into a
// InterceptorConstant.
if (!node.arguments.isEmpty && node.arguments.tail.isEmpty) {
ast.Node argument = node.arguments.head;
visit(argument);
HInstruction argumentInstruction = pop();
if (argumentInstruction is HConstant) {
ConstantValue argumentConstant = argumentInstruction.constant;
if (argumentConstant is TypeConstantValue &&
argumentConstant.representedType is ResolutionInterfaceType) {
ResolutionInterfaceType type = argumentConstant.representedType;
ConstantValue constant = new InterceptorConstantValue(type.element);
HInstruction instruction = graph.addConstant(constant, closedWorld);
stack.add(instruction);
return;
}
}
}
reporter.reportErrorMessage(
node, MessageKind.WRONG_ARGUMENT_FOR_JS_INTERCEPTOR_CONSTANT);
stack.add(graph.addConstantNull(closedWorld));
}
void handleForeignJsCallInIsolate(ast.Send node) {
Link<ast.Node> link = node.arguments;
if (!backendUsage.isIsolateInUse) {
// If the isolate library is not used, we just invoke the
// closure.
visit(link.tail.head);
push(new HInvokeClosure(new Selector.callClosure(0),
<HInstruction>[pop()], commonMasks.dynamicType));
} else {
// Call a helper method from the isolate library.
MethodElement element = commonElements.callInIsolate;
if (element == null) {
reporter.internalError(node, 'Isolate library and compiler mismatch.');
}
List<HInstruction> inputs = <HInstruction>[];
addGenericSendArgumentsToList(link, inputs);
pushInvokeStatic(node, element, inputs,
typeMask: commonMasks.dynamicType);
}
}
FunctionSignature handleForeignRawFunctionRef(ast.Send node, String name) {
if (node.arguments.isEmpty || !node.arguments.tail.isEmpty) {
reporter.internalError(
node.argumentsNode, '"$name" requires exactly one argument.');
}
ast.Node closure = node.arguments.head;
Element element = elements[closure];
if (!Elements.isStaticOrTopLevelFunction(element)) {
reporter.internalError(
closure, '"$name" requires a static or top-level method.');
}
MethodElement function = element;
// TODO(johnniwinther): Try to eliminate the need to distinguish declaration
// and implementation signatures. Currently it is need because the
// signatures have different elements for parameters.
FunctionElement implementation = function.implementation;
FunctionSignature params = implementation.functionSignature;
if (params.optionalParameterCount != 0) {
reporter.internalError(
closure, '"$name" does not handle closure with optional parameters.');
}
push(new HForeignCode(
js.js
.expressionTemplateYielding(emitter.staticFunctionAccess(function)),
commonMasks.dynamicType,
<HInstruction>[],
nativeBehavior: native.NativeBehavior.PURE,
foreignFunction: function));
return params;
}
void handleForeignDartClosureToJs(ast.Send node, String name) {
// TODO(ahe): This implements DART_CLOSURE_TO_JS and should probably take
// care to wrap the closure in another closure that saves the current
// isolate.
handleForeignRawFunctionRef(node, name);
}
void handleForeignJsSetStaticState(ast.Send node) {
if (node.arguments.isEmpty || !node.arguments.tail.isEmpty) {
reporter.internalError(
node.argumentsNode, 'Exactly one argument required.');
}
visit(node.arguments.head);
String isolateName = namer.staticStateHolder;
SideEffects sideEffects = new SideEffects.empty();
sideEffects.setAllSideEffects();
push(new HForeignCode(js.js.parseForeignJS("$isolateName = #"),
commonMasks.dynamicType, <HInstruction>[pop()],
nativeBehavior: native.NativeBehavior.CHANGES_OTHER,
effects: sideEffects));
}
void handleForeignJsGetStaticState(ast.Send node) {
if (!node.arguments.isEmpty) {
reporter.internalError(node.argumentsNode, 'Too many arguments.');
}
push(new HForeignCode(js.js.parseForeignJS(namer.staticStateHolder),
commonMasks.dynamicType, <HInstruction>[],
nativeBehavior: native.NativeBehavior.DEPENDS_OTHER));
}
void handleForeignSend(ast.Send node, FunctionElement element) {
String name = element.name;
if (name == JavaScriptBackend.JS) {
handleForeignJs(node);
} else if (name == 'JS_CURRENT_ISOLATE_CONTEXT') {
handleForeignJsCurrentIsolateContext(node);
} else if (name == 'JS_CALL_IN_ISOLATE') {
handleForeignJsCallInIsolate(node);
} else if (name == 'DART_CLOSURE_TO_JS') {
handleForeignDartClosureToJs(node, 'DART_CLOSURE_TO_JS');
} else if (name == 'RAW_DART_FUNCTION_REF') {
handleForeignRawFunctionRef(node, 'RAW_DART_FUNCTION_REF');
} else if (name == 'JS_SET_STATIC_STATE') {
handleForeignJsSetStaticState(node);
} else if (name == 'JS_GET_STATIC_STATE') {
handleForeignJsGetStaticState(node);
} else if (name == 'JS_GET_NAME') {
handleForeignJsGetName(node);
} else if (name == JavaScriptBackend.JS_EMBEDDED_GLOBAL) {
handleForeignJsEmbeddedGlobal(node);
} else if (name == JavaScriptBackend.JS_BUILTIN) {
handleForeignJsBuiltin(node);
} else if (name == 'JS_GET_FLAG') {
handleForeignJsGetFlag(node);
} else if (name == 'JS_EFFECT') {
stack.add(graph.addConstantNull(closedWorld));
} else if (name == JavaScriptBackend.JS_INTERCEPTOR_CONSTANT) {
handleJsInterceptorConstant(node);
} else if (name == 'JS_STRING_CONCAT') {
handleJsStringConcat(node);
} else {
reporter.internalError(node, "Unknown foreign: ${element}");
}
}
generateDeferredLoaderGet(ast.Send node, FunctionElement deferredLoader,
SourceInformation sourceInformation) {
// Until now we only handle these as getters.
if (!deferredLoader.isDeferredLoaderGetter) {
failedAt(node);
}
FunctionEntity loadFunction = commonElements.loadLibraryWrapper;
PrefixElement prefixElement = deferredLoader.enclosingElement;
String loadId =
deferredLoadTask.getImportDeferName(node, prefixElement.deferredImport);
var inputs = [graph.addConstantString(loadId, closedWorld)];
push(new HInvokeStatic(loadFunction, inputs, commonMasks.nonNullType,
targetCanThrow: false)
..sourceInformation = sourceInformation);
}
generateSuperNoSuchMethodSend(
ast.Send node, Selector selector, List<HInstruction> arguments) {
String name = selector.name;
ClassElement cls = currentNonClosureClass;
MethodElement element = cls.lookupSuperMember(Identifiers.noSuchMethod_);
if (!Selectors.noSuchMethod_.signatureApplies(element)) {
ClassElement objectClass = commonElements.objectClass;
element = objectClass.lookupMember(Identifiers.noSuchMethod_);
}
if (backendUsage.isInvokeOnUsed && !element.enclosingClass.isObject) {
// Register the call as dynamic if [noSuchMethod] on the super
// class is _not_ the default implementation from [Object], in
// case the [noSuchMethod] implementation calls
// [JSInvocationMirror._invokeOn].
// TODO(johnniwinther): Register this more precisely.
registry?.registerDynamicUse(new DynamicUse(selector, null));
}
String publicName = name;
if (selector.isSetter) publicName += '=';
ConstantValue nameConstant = constantSystem.createString(publicName);
js.Name internalName = namer.invocationName(selector);
MethodElement createInvocationMirror =
commonElements.createInvocationMirror;
var argumentsInstruction = buildLiteralList(arguments);
add(argumentsInstruction);
var argumentNames = new List<HInstruction>();
for (String argumentName in selector.namedArguments) {
ConstantValue argumentNameConstant =
constantSystem.createString(argumentName);
argumentNames.add(graph.addConstant(argumentNameConstant, closedWorld));
}
var argumentNamesInstruction = buildLiteralList(argumentNames);
add(argumentNamesInstruction);
ConstantValue kindConstant =
constantSystem.createInt(selector.invocationMirrorKind);
pushInvokeStatic(
null,
createInvocationMirror,
[
graph.addConstant(nameConstant, closedWorld),
graph.addConstantStringFromName(internalName, closedWorld),
graph.addConstant(kindConstant, closedWorld),
argumentsInstruction,
argumentNamesInstruction
],
typeMask: commonMasks.dynamicType);
var inputs = <HInstruction>[pop()];
push(buildInvokeSuper(Selectors.noSuchMethod_, element, inputs,
sourceInformationBuilder.buildGeneric(node)));
}
/// Generate a call to a super method or constructor.
void generateSuperInvoke(ast.Send node, MethodElement method,
SourceInformation sourceInformation) {
// TODO(5347): Try to avoid the need for calling [implementation] before
// calling [makeStaticArgumentList].
Selector selector = elements.getSelector(node);
MethodElement implementation = method.implementation;
assert(selector.applies(implementation),
failedAt(node, "$selector does not apply to ${implementation}"));
List<HInstruction> inputs =
makeStaticArgumentList(selector.callStructure, node.arguments, method);
push(buildInvokeSuper(selector, method, inputs, sourceInformation));
}
/// Access the value from the super [element].
void handleSuperGet(ast.Send node, Element element) {
Selector selector = elements.getSelector(node);
SourceInformation sourceInformation =
sourceInformationBuilder.buildGet(node.selector);
push(buildInvokeSuper(
selector, element, const <HInstruction>[], sourceInformation));
}
/// Invoke .call on the value retrieved from the super [element].
void handleSuperCallInvoke(ast.Send node, Element element) {
Selector selector = elements.getSelector(node);
HInstruction target = buildInvokeSuper(
selector,
element,
const <HInstruction>[],
sourceInformationBuilder.buildGet(node.selector));
add(target);
generateCallInvoke(
node,
target,
sourceInformationBuilder.buildCall(
node.argumentsNode, node.argumentsNode));
}
/// Invoke super [method].
void handleSuperMethodInvoke(ast.Send node, MethodElement method) {
generateSuperInvoke(node, method,
sourceInformationBuilder.buildCall(node.selector, node.selector));
}
/// Access an unresolved super property.
void handleUnresolvedSuperInvoke(ast.Send node) {
Selector selector = elements.getSelector(node);
List<HInstruction> arguments = <HInstruction>[];
if (!node.isPropertyAccess) {
addGenericSendArgumentsToList(node.arguments, arguments);
}
generateSuperNoSuchMethodSend(node, selector, arguments);
}
@override
void visitUnresolvedSuperIndex(
ast.Send node, Element element, ast.Node index, _) {
handleUnresolvedSuperInvoke(node);
}
@override
void visitUnresolvedSuperUnary(
ast.Send node, UnaryOperator operator, Element element, _) {
handleUnresolvedSuperInvoke(node);
}
@override
void visitUnresolvedSuperBinary(ast.Send node, Element element,
BinaryOperator operator, ast.Node argument, _) {
handleUnresolvedSuperInvoke(node);
}
@override
void visitUnresolvedSuperGet(ast.Send node, Element element, _) {
handleUnresolvedSuperInvoke(node);
}
@override
void visitUnresolvedSuperSet(
ast.Send node, Element element, ast.Node rhs, _) {
handleSuperSendSet(node);
}
@override
void visitSuperSetterGet(ast.Send node, MethodElement setter, _) {
handleUnresolvedSuperInvoke(node);
}
@override
void visitUnresolvedSuperInvoke(
ast.Send node, Element element, ast.Node argument, Selector selector, _) {
handleUnresolvedSuperInvoke(node);
}
@override
void visitSuperFieldGet(ast.Send node, FieldElement field, _) {
handleSuperGet(node, field);
}
@override
void visitSuperGetterGet(ast.Send node, MethodElement method, _) {
handleSuperGet(node, method);
}
@override
void visitSuperMethodGet(ast.Send node, MethodElement method, _) {
handleSuperGet(node, method);
}
@override
void visitSuperFieldInvoke(ast.Send node, FieldElement field,
ast.NodeList arguments, CallStructure callStructure, _) {
handleSuperCallInvoke(node, field);
}
@override
void visitSuperGetterInvoke(ast.Send node, MethodElement getter,
ast.NodeList arguments, CallStructure callStructure, _) {
handleSuperCallInvoke(node, getter);
}
@override
void visitSuperMethodInvoke(ast.Send node, MethodElement method,
ast.NodeList arguments, CallStructure callStructure, _) {
handleSuperMethodInvoke(node, method);
}
@override
void visitSuperIndex(ast.Send node, MethodElement method, ast.Node index, _) {
handleSuperMethodInvoke(node, method);
}
@override
void visitSuperEquals(
ast.Send node, MethodElement method, ast.Node argument, _) {
handleSuperMethodInvoke(node, method);
}
@override
void visitSuperBinary(ast.Send node, MethodElement method,
BinaryOperator operator, ast.Node argument, _) {
handleSuperMethodInvoke(node, method);
}
@override
void visitSuperNotEquals(
ast.Send node, MethodElement method, ast.Node argument, _) {
handleSuperMethodInvoke(node, method);
pushWithPosition(
new HNot(popBoolified(), commonMasks.boolType), node.selector);
}
@override
void visitSuperUnary(
ast.Send node, UnaryOperator operator, MethodElement method, _) {
handleSuperMethodInvoke(node, method);
}
@override
void visitSuperMethodIncompatibleInvoke(ast.Send node, MethodElement method,
ast.NodeList arguments, CallStructure callStructure, _) {
handleInvalidSuperInvoke(node, arguments);
}
@override
void visitSuperSetterInvoke(ast.Send node, SetterElement setter,
ast.NodeList arguments, CallStructure callStructure, _) {
handleInvalidSuperInvoke(node, arguments);
}
void handleInvalidSuperInvoke(ast.Send node, ast.NodeList arguments) {
Selector selector = elements.getSelector(node);
List<HInstruction> inputs = <HInstruction>[];
addGenericSendArgumentsToList(arguments.nodes, inputs);
generateSuperNoSuchMethodSend(node, selector, inputs);
}
bool needsSubstitutionForTypeVariableAccess(ClassElement cls) {
if (closedWorld.isUsedAsMixin(cls)) return true;
return closedWorld.anyStrictSubclassOf(cls, (ClassEntity subclass) {
return !rtiSubstitutions.isTrivialSubstitution(subclass, cls);
});
}
HInstruction handleListConstructor(ResolutionInterfaceType type,
HInstruction newObject, SourceInformation sourceInformation) {
if (!rtiNeed.classNeedsRti(type.element) || type.treatAsRaw) {
return newObject;
}
List<HInstruction> inputs = <HInstruction>[];
type = localsHandler.substInContext(type);
type.typeArguments.forEach((ResolutionDartType argument) {
inputs.add(typeBuilder.analyzeTypeArgument(argument, sourceElement,
sourceInformation: sourceInformation));
});
// TODO(15489): Register at codegen.
registry?.registerInstantiation(type);
return callSetRuntimeTypeInfoWithTypeArguments(
type, inputs, newObject, sourceInformation);
}
HInstruction callSetRuntimeTypeInfo(HInstruction typeInfo,
HInstruction newObject, SourceInformation sourceInformation) {
// Set the runtime type information on the object.
MethodElement typeInfoSetterElement = commonElements.setRuntimeTypeInfo;
pushInvokeStatic(
null, typeInfoSetterElement, <HInstruction>[newObject, typeInfo],
typeMask: commonMasks.dynamicType,
sourceInformation: sourceInformation);
// The new object will now be referenced through the
// `setRuntimeTypeInfo` call. We therefore set the type of that
// instruction to be of the object's type.
assert(
stack.last is HInvokeStatic || stack.last == newObject,
failedAt(
CURRENT_ELEMENT_SPANNABLE,
"Unexpected `stack.last`: Found ${stack.last}, "
"expected ${newObject} or an HInvokeStatic. "
"State: typeInfo=$typeInfo, stack=$stack."));
stack.last.instructionType = newObject.instructionType;
return pop();
}
void handleNewSend(ast.NewExpression node) {
ast.Send send = node.send;
SourceInformation sourceInformation =
sourceInformationBuilder.buildNew(send);
generateIsDeferredLoadedCheckOfSend(send);
ConstructorElement constructor = elements[send];
bool isFixedList = false;
bool isFixedListConstructorCall = Elements.isFixedListConstructorCall(
constructor, send, closedWorld.commonElements);
bool isGrowableListConstructorCall = Elements.isGrowableListConstructorCall(
constructor, send, closedWorld.commonElements);
TypeMask computeType(element) {
ConstructorElement originalElement = elements[send];
if (isFixedListConstructorCall ||
Elements.isFilledListConstructorCall(
originalElement, send, closedWorld.commonElements)) {
isFixedList = true;
TypeMask inferred = _inferredTypeOfNewList(send);
return inferred.containsAll(closedWorld)
? commonMasks.fixedArrayType
: inferred;
} else if (isGrowableListConstructorCall) {
TypeMask inferred = _inferredTypeOfNewList(send);
return inferred.containsAll(closedWorld)
? commonMasks.extendableArrayType
: inferred;
} else if (Elements.isConstructorOfTypedArraySubclass(
originalElement, closedWorld)) {
isFixedList = true;
TypeMask inferred = _inferredTypeOfNewList(send);
ClassElement cls = element.enclosingClass;
assert(nativeData.isNativeClass(cls));
return inferred.containsAll(closedWorld)
? new TypeMask.nonNullExact(cls, closedWorld)
: inferred;
} else if (element.isGenerativeConstructor) {
ClassElement cls = element.enclosingClass;
if (cls.isAbstract) {
// An error will be thrown.
return new TypeMask.nonNullEmpty();
} else {
return new TypeMask.nonNullExact(cls, closedWorld);
}
} else {
return TypeMaskFactory.inferredReturnTypeForElement(
originalElement, globalInferenceResults);
}
}
CallStructure callStructure = elements.getSelector(send).callStructure;
ConstructorElement constructorDeclaration = constructor;
ConstructorElement constructorImplementation = constructor.implementation;
constructor = constructorImplementation.effectiveTarget;
final bool isSymbolConstructor =
closedWorld.commonElements.isSymbolConstructor(constructorDeclaration);
final bool isJSArrayTypedConstructor = constructorDeclaration ==
closedWorld.commonElements.jsArrayTypedConstructor;
if (isSymbolConstructor) {
constructor = commonElements.symbolValidatedConstructor;
assert(constructor != null,
failedAt(send, 'Constructor Symbol.validated is missing'));
callStructure =
commonElements.symbolValidatedConstructorSelector.callStructure;
assert(callStructure != null,
failedAt(send, 'Constructor Symbol.validated is missing'));
}
bool isRedirected = constructorDeclaration.isRedirectingFactory;
if (!constructorDeclaration.isCyclicRedirection) {
// Insert a check for every deferred redirection on the path to the
// final target.
ConstructorElement target = constructorDeclaration;
while (target.isRedirectingFactory) {
if (constructorDeclaration.redirectionDeferredPrefix != null) {
generateIsDeferredLoadedCheckIfNeeded(
target.redirectionDeferredPrefix.deferredImport, node);
}
target = target.immediateRedirectionTarget;
}
}
ResolutionInterfaceType type = elements.getType(node);
ResolutionDartType expectedType =
constructorDeclaration.computeEffectiveTargetType(type);
expectedType = localsHandler.substInContext(expectedType);
if (compiler.elementHasCompileTimeError(constructor) ||
compiler.elementHasCompileTimeError(constructor.enclosingClass)) {
// TODO(ahe): Do something like [generateWrongArgumentCountError].
stack.add(graph.addConstantNull(closedWorld));
return;
}
if (checkTypeVariableBounds(node, type)) return;
// Abstract class instantiation error takes precedence over argument
// mismatch.
ClassElement cls = constructor.enclosingClass;
if (cls.isAbstract && constructor.isGenerativeConstructor) {
// However, we need to ensure that all arguments are evaluated before we
// throw the ACIE exception.
send.arguments.forEach((arg) {
visit(arg);
pop();
});
generateAbstractClassInstantiationError(send, cls.name);
return;
}
// TODO(5347): Try to avoid the need for calling [implementation] before
// calling [makeStaticArgumentList].
constructorImplementation = constructor.implementation;
if (constructorImplementation.isMalformed ||
!callStructure
.signatureApplies(constructorImplementation.parameterStructure)) {
generateWrongArgumentCountError(send, constructor, send.arguments);
return;
}
List<HInstruction> inputs = <HInstruction>[];
if (constructor.isGenerativeConstructor &&
nativeData.isNativeOrExtendsNative(constructor.enclosingClass) &&
!nativeData.isJsInteropMember(constructor)) {
// Native class generative constructors take a pre-constructed object.
inputs.add(graph.addConstantNull(closedWorld));
}
inputs.addAll(makeStaticArgumentList(
callStructure, send.arguments, constructorImplementation.declaration));
TypeMask elementType = computeType(constructor);
if (isFixedListConstructorCall) {
if (!inputs[0].isNumber(closedWorld)) {
HTypeConversion conversion = new HTypeConversion(
null,
HTypeConversion.ARGUMENT_TYPE_CHECK,
commonMasks.numType,
inputs[0],
sourceInformation);
add(conversion);
inputs[0] = conversion;
}
js.Template code = js.js.parseForeignJS('new Array(#)');
var behavior = new native.NativeBehavior();
behavior.typesInstantiated.add(expectedType);
behavior.typesReturned.add(expectedType);
// The allocation can throw only if the given length is a double or
// outside the unsigned 32 bit range.
// TODO(sra): Array allocation should be an instruction so that canThrow
// can depend on a length type discovered in optimization.
bool canThrow = true;
if (inputs[0].isInteger(closedWorld) && inputs[0] is HConstant) {
dynamic constant = inputs[0];
int value = constant.constant.primitiveValue;
if (0 <= value && value < 0x100000000) canThrow = false;
}
HForeignCode foreign = new HForeignCode(code, elementType, inputs,
nativeBehavior: behavior,
throwBehavior: canThrow
? native.NativeThrowBehavior.MAY
: native.NativeThrowBehavior.NEVER);
push(foreign);
if (globalInferenceResults.isFixedArrayCheckedForGrowable(send)) {
js.Template code = js.js.parseForeignJS(r'#.fixed$length = Array');
// We set the instruction as [canThrow] to avoid it being dead code.
// We need a finer grained side effect.
add(new HForeignCode(code, commonMasks.nullType, [stack.last],
throwBehavior: native.NativeThrowBehavior.MAY));
}
} else if (isGrowableListConstructorCall) {
push(buildLiteralList(<HInstruction>[]));
stack.last.instructionType = elementType;
} else if (constructor.isExternal &&
constructor.isFromEnvironmentConstructor) {
generateUnsupportedError(
node,
'${cls.name}.${constructor.name} '
'can only be used as a const constructor');
} else {
potentiallyAddTypeArguments(inputs, cls, expectedType);
addInlinedInstantiation(expectedType);
pushInvokeStatic(node, constructor.declaration, inputs,
typeMask: elementType,
instanceType: expectedType,
sourceInformation: sourceInformation);
removeInlinedInstantiation(expectedType);
}
HInstruction newInstance = stack.last;
if (isFixedList) {
// Overwrite the element type, in case the allocation site has
// been inlined.
newInstance.instructionType = elementType;
graph.allocatedFixedLists?.add(newInstance);
}
// The List constructor forwards to a Dart static method that does
// not know about the type argument. Therefore we special case
// this constructor to have the setRuntimeTypeInfo called where
// the 'new' is done.
if (rtiNeed.classNeedsRti(commonElements.listClass) &&
(isFixedListConstructorCall ||
isGrowableListConstructorCall ||
isJSArrayTypedConstructor)) {
newInstance = handleListConstructor(type, pop(), sourceInformation);
stack.add(newInstance);
}
// Finally, if we called a redirecting factory constructor, check the type.
if (isRedirected) {
HInstruction checked =
typeBuilder.potentiallyCheckOrTrustType(newInstance, type);
if (checked != newInstance) {
pop();
stack.add(checked);
}
}
}
void potentiallyAddTypeArguments(List<HInstruction> inputs, ClassElement cls,
ResolutionInterfaceType expectedType,
{SourceInformation sourceInformation}) {
if (!rtiNeed.classNeedsRti(cls)) return;
assert(cls.typeVariables.length == expectedType.typeArguments.length);
expectedType.typeArguments.forEach((ResolutionDartType argument) {
inputs.add(typeBuilder.analyzeTypeArgument(argument, sourceElement,
sourceInformation: sourceInformation));
});
}
/// In checked mode checks the [type] of [node] to be well-bounded. The method
/// returns [:true:] if an error can be statically determined.
bool checkTypeVariableBounds(
ast.NewExpression node, ResolutionInterfaceType type) {
if (!options.enableTypeAssertions) return false;
Map<ResolutionDartType, Set<ResolutionDartType>> seenChecksMap =
new Map<ResolutionDartType, Set<ResolutionDartType>>();
bool definitelyFails = false;
void addTypeVariableBoundCheck(
InterfaceType _instance,
DartType _typeArgument,
TypeVariableType _typeVariable,
DartType _bound) {
if (definitelyFails) return;
ResolutionInterfaceType instance = _instance;
ResolutionDartType typeArgument = _typeArgument;
ResolutionTypeVariableType typeVariable = _typeVariable;
ResolutionDartType bound = _bound;
int subtypeRelation = types.computeSubtypeRelation(typeArgument, bound);
if (subtypeRelation == DartTypes.IS_SUBTYPE) return;
String message = "Can't create an instance of malbounded type '$type': "
"'${typeArgument}' is not a subtype of bound '${bound}' for "
"type variable '${typeVariable}' of type "
"${type == instance
? "'${type.element.thisType}'"
: "'${instance.element.thisType}' on the supertype "
"'${instance}' of '${type}'"
}.";
if (subtypeRelation == DartTypes.NOT_SUBTYPE) {
generateTypeError(node, message);
definitelyFails = true;
return;
} else if (subtypeRelation == DartTypes.MAYBE_SUBTYPE) {
Set<ResolutionDartType> seenChecks = seenChecksMap.putIfAbsent(
typeArgument, () => new Set<ResolutionDartType>());
if (!seenChecks.contains(bound)) {
seenChecks.add(bound);
assertIsSubtype(node, typeArgument, bound, message);
}
}
}
types.checkTypeVariableBounds(type, addTypeVariableBoundCheck);
if (definitelyFails) {
return true;
}
for (ResolutionInterfaceType supertype in type.element.allSupertypes) {
ResolutionInterfaceType instance = type.asInstanceOf(supertype.element);
types.checkTypeVariableBounds(instance, addTypeVariableBoundCheck);
if (definitelyFails) {
return true;
}
}
return false;
}
visitStaticSend(ast.Send node) {
internalError(node, "Unexpected visitStaticSend");
}
/// Generate an invocation to the static or top level [function].
void generateStaticFunctionInvoke(
ast.Send node, MethodElement function, CallStructure callStructure) {
List<HInstruction> inputs =
makeStaticArgumentList(callStructure, node.arguments, function);
pushInvokeStatic(node, function, inputs,
sourceInformation:
sourceInformationBuilder.buildCall(node.selector, node.selector));
}
/// Generate an invocation to a static or top level function with the wrong
/// number of arguments.
void generateStaticFunctionIncompatibleInvoke(
ast.Send node, Element element) {
generateWrongArgumentCountError(node, element, node.arguments);
}
@override
void visitStaticFieldInvoke(ast.Send node, FieldElement field,
ast.NodeList arguments, CallStructure callStructure, _) {
generateStaticFieldGet(node, field);
generateCallInvoke(
node,
pop(),
sourceInformationBuilder.buildCall(
node.argumentsNode, node.argumentsNode));
}
@override
void visitStaticFunctionInvoke(ast.Send node, MethodElement function,
ast.NodeList arguments, CallStructure callStructure, _) {
generateStaticFunctionInvoke(node, function, callStructure);
}
@override
void visitStaticFunctionIncompatibleInvoke(
ast.Send node,
MethodElement function,
ast.NodeList arguments,
CallStructure callStructure,
_) {
generateStaticFunctionIncompatibleInvoke(node, function);
}
@override
void visitStaticGetterInvoke(ast.Send node, FunctionElement getter,
ast.NodeList arguments, CallStructure callStructure, _) {
generateStaticGetterGet(node, getter);
generateCallInvoke(node, pop(),
sourceInformationBuilder.buildCall(node, node.argumentsNode));
}
@override
void visitTopLevelFieldInvoke(ast.Send node, FieldElement field,
ast.NodeList arguments, CallStructure callStructure, _) {
generateStaticFieldGet(node, field);
generateCallInvoke(node, pop(),
sourceInformationBuilder.buildCall(node, node.argumentsNode));
}
@override
void visitTopLevelFunctionInvoke(ast.Send node, MethodElement function,
ast.NodeList arguments, CallStructure callStructure, _) {
if (closedWorld.commonElements.isForeign(function)) {
handleForeignSend(node, function);
} else {
generateStaticFunctionInvoke(node, function, callStructure);
}
}
@override
void visitTopLevelFunctionIncompatibleInvoke(
ast.Send node,
MethodElement function,
ast.NodeList arguments,
CallStructure callStructure,
_) {
generateStaticFunctionIncompatibleInvoke(node, function);
}
@override
void visitTopLevelGetterInvoke(ast.Send node, FunctionElement getter,
ast.NodeList arguments, CallStructure callStructure, _) {
generateStaticGetterGet(node, getter);
generateCallInvoke(node, pop(),
sourceInformationBuilder.buildCall(node, node.argumentsNode));
}
@override
void visitTopLevelSetterGet(ast.Send node, MethodElement setter, _) {
handleInvalidStaticGet(node, setter);
}
@override
void visitStaticSetterGet(ast.Send node, MethodElement setter, _) {
handleInvalidStaticGet(node, setter);
}
@override
void visitUnresolvedGet(ast.Send node, Element element, _) {
generateStaticUnresolvedGet(node, element);
}
void handleInvalidStaticInvoke(ast.Send node, Element element) {
generateThrowNoSuchMethod(node, noSuchMethodTargetSymbolString(element),
argumentNodes: node.arguments);
}
@override
void visitStaticSetterInvoke(ast.Send node, MethodElement setter,
ast.NodeList arguments, CallStructure callStructure, _) {
handleInvalidStaticInvoke(node, setter);
}
@override
void visitTopLevelSetterInvoke(ast.Send node, MethodElement setter,
ast.NodeList arguments, CallStructure callStructure, _) {
handleInvalidStaticInvoke(node, setter);
}
@override
void visitUnresolvedInvoke(ast.Send node, Element element,
ast.NodeList arguments, Selector selector, _) {
if (element is ErroneousElement) {
// An erroneous element indicates that the function could not be
// resolved (a warning has been issued).
handleInvalidStaticInvoke(node, element);
} else {
// TODO(ahe): Do something like [generateWrongArgumentCountError].
stack.add(graph.addConstantNull(closedWorld));
}
return;
}
HConstant addConstantString(String string) {
return graph.addConstantString(string, closedWorld);
}
HConstant addConstantStringFromName(js.Name name) {
return graph.addConstantStringFromName(name, closedWorld);
}
visitClassTypeLiteralGet(ast.Send node, ConstantExpression constant, _) {
generateConstantTypeLiteral(node);
}
visitClassTypeLiteralInvoke(ast.Send node, ConstantExpression constant,
ast.NodeList arguments, CallStructure callStructure, _) {
generateConstantTypeLiteral(node);
generateTypeLiteralCall(node);
}
visitTypedefTypeLiteralGet(ast.Send node, ConstantExpression constant, _) {
generateConstantTypeLiteral(node);
}
visitTypedefTypeLiteralInvoke(ast.Send node, ConstantExpression constant,
ast.NodeList arguments, CallStructure callStructure, _) {
generateConstantTypeLiteral(node);
generateTypeLiteralCall(node);
}
visitTypeVariableTypeLiteralGet(
ast.Send node, TypeVariableElement element, _) {
generateTypeVariableLiteral(node, element.type);
}
visitTypeVariableTypeLiteralInvoke(ast.Send node, TypeVariableElement element,
ast.NodeList arguments, CallStructure callStructure, _) {
generateTypeVariableLiteral(node, element.type);
generateTypeLiteralCall(node);
}
visitDynamicTypeLiteralGet(ast.Send node, ConstantExpression constant, _) {
generateConstantTypeLiteral(node);
}
visitDynamicTypeLiteralInvoke(ast.Send node, ConstantExpression constant,
ast.NodeList arguments, CallStructure callStructure, _) {
generateConstantTypeLiteral(node);
generateTypeLiteralCall(node);
}
/// Generate the constant value for a constant type literal.
void generateConstantTypeLiteral(ast.Send node) {
// TODO(karlklose): add type representation
if (node.isCall) {
// The node itself is not a constant but we register the selector (the
// identifier that refers to the class/typedef) as a constant.
stack.add(addConstant(node.selector));
} else {
stack.add(addConstant(node));
}
}
/// Generate the literal for [typeVariable] in the current context.
void generateTypeVariableLiteral(
ast.Send node, ResolutionTypeVariableType typeVariable) {
// GENERIC_METHODS: This provides thin support for method type variables
// by treating them as malformed when evaluated as a literal. For full
// support of generic methods this must be revised.
if (typeVariable is MethodTypeVariableType) {
generateTypeError(node, "Method type variables are not reified");
} else {
ResolutionDartType type = localsHandler.substInContext(typeVariable);
HInstruction value = typeBuilder.analyzeTypeArgument(type, sourceElement,
sourceInformation: sourceInformationBuilder.buildGet(node));
pushInvokeStatic(node, commonElements.runtimeTypeToString, [value],
typeMask: commonMasks.stringType);
pushInvokeStatic(node, commonElements.createRuntimeType, [pop()]);
}
}
/// Generate a call to a type literal.
void generateTypeLiteralCall(ast.Send node) {
// This send is of the form 'e(...)', where e is resolved to a type
// reference. We create a regular closure call on the result of the type
// reference instead of creating a NoSuchMethodError to avoid pulling it
// in if it is not used (e.g., in a try/catch).
HInstruction target = pop();
generateCallInvoke(node, target,
sourceInformationBuilder.buildCall(node, node.argumentsNode));
}
/// Generate a '.call' invocation on [target].
void generateCallInvoke(
ast.Send node, HInstruction target, SourceInformation sourceInformation) {
Selector selector = elements.getSelector(node);
List<HInstruction> inputs = <HInstruction>[target];
addDynamicSendArgumentsToList(node, inputs);
push(new HInvokeClosure(
new Selector.callClosureFrom(selector), inputs, commonMasks.dynamicType)
..sourceInformation = sourceInformation);
}
visitGetterSend(ast.Send node) {
internalError(node, "Unexpected visitGetterSend");
}
// TODO(antonm): migrate rest of SsaFromAstMixin to internalError.
internalError(Spannable node, String reason) {
reporter.internalError(node, reason);
}
void generateError(ast.Node node, String message, Element helper) {
HInstruction errorMessage = addConstantString(message);
pushInvokeStatic(node, helper, [errorMessage]);
}
void generateRuntimeError(ast.Node node, String message) {
MethodElement helper = commonElements.throwRuntimeError;
generateError(node, message, helper);
}
void generateUnsupportedError(ast.Node node, String message) {
MethodElement helper = commonElements.throwUnsupportedError;
generateError(node, message, helper);
}
void generateTypeError(ast.Node node, String message) {
MethodElement helper = commonElements.throwTypeError;
generateError(node, message, helper);
}
void generateAbstractClassInstantiationError(ast.Node node, String message) {
MethodElement helper = commonElements.throwAbstractClassInstantiationError;
generateError(node, message, helper);
}
void generateThrowNoSuchMethod(ast.Node diagnosticNode, String methodName,
{Link<ast.Node> argumentNodes,
List<HInstruction> argumentValues,
List<String> existingArguments,
SourceInformation sourceInformation}) {
MethodElement helper = commonElements.throwNoSuchMethod;
ConstantValue receiverConstant = constantSystem.createString('');
HInstruction receiver = graph.addConstant(receiverConstant, closedWorld);
ConstantValue nameConstant = constantSystem.createString(methodName);
HInstruction name = graph.addConstant(nameConstant, closedWorld);
if (argumentValues == null) {
argumentValues = <HInstruction>[];
argumentNodes.forEach((argumentNode) {
visit(argumentNode);
HInstruction value = pop();
argumentValues.add(value);
});
}
HInstruction arguments = buildLiteralList(argumentValues);
add(arguments);
HInstruction existingNamesList;
if (existingArguments != null) {
List<HInstruction> existingNames = <HInstruction>[];
for (String name in existingArguments) {
HInstruction nameConstant = graph.addConstantString(name, closedWorld);
existingNames.add(nameConstant);
}
existingNamesList = buildLiteralList(existingNames);
add(existingNamesList);
} else {
existingNamesList = graph.addConstantNull(closedWorld);
}
pushInvokeStatic(
diagnosticNode, helper, [receiver, name, arguments, existingNamesList],
sourceInformation: sourceInformation);
}
/**
* Generate code to throw a [NoSuchMethodError] exception for calling a
* method with a wrong number of arguments or mismatching named optional
* arguments.
*/
void generateWrongArgumentCountError(ast.Node diagnosticNode,
FunctionElement function, Link<ast.Node> argumentNodes) {
List<String> existingArguments = <String>[];
FunctionSignature signature = function.functionSignature;
signature.forEachParameter((Element parameter) {
existingArguments.add(parameter.name);
});
generateThrowNoSuchMethod(diagnosticNode, function.name,
argumentNodes: argumentNodes, existingArguments: existingArguments);
}
@override
void bulkHandleNode(ast.Node node, String message, _) {
internalError(node, "Unexpected bulk handled node: $node");
}
@override
void bulkHandleNew(ast.NewExpression node, [_]) {
Element element = elements[node.send];
if (!Elements.isMalformed(element)) {
ConstructorElement function = element;
element = function.effectiveTarget;
}
final bool isSymbolConstructor =
element == commonElements.symbolConstructorTarget;
if (Elements.isError(element)) {
ErroneousElement error = element;
if (error.messageKind == MessageKind.CANNOT_FIND_CONSTRUCTOR ||
error.messageKind == MessageKind.CANNOT_FIND_UNNAMED_CONSTRUCTOR) {
generateThrowNoSuchMethod(
node.send, noSuchMethodTargetSymbolString(error, 'constructor'),
argumentNodes: node.send.arguments);
} else {
MessageTemplate template = MessageTemplate.TEMPLATES[error.messageKind];
Message message = template.message(error.messageArguments);
generateRuntimeError(node.send, message.toString());
}
} else if (Elements.isMalformed(element)) {
// TODO(ahe): Do something like [generateWrongArgumentCountError].
stack.add(graph.addConstantNull(closedWorld));
} else if (node.isConst) {
stack.add(addConstant(node));
if (isSymbolConstructor) {
ConstructedConstantValue symbol = getConstantForNode(node);
StringConstantValue stringConstant = symbol.fields.values.single;
String nameString = stringConstant.primitiveValue;
registry?.registerConstSymbol(nameString);
}
} else {
handleNewSend(node);
}
}
@override
void errorNonConstantConstructorInvoke(
ast.NewExpression node,
Element element,
ResolutionDartType type,
ast.NodeList arguments,
CallStructure callStructure,
_) {
bulkHandleNew(node);
}
void pushInvokeDynamic(ast.Node node, Selector selector, TypeMask mask,
List<HInstruction> arguments,
{SourceInformation sourceInformation}) {
// We prefer to not inline certain operations on indexables,
// because the constant folder will handle them better and turn
// them into simpler instructions that allow further
// optimizations.
bool isOptimizableOperationOnIndexable(Selector selector, Element element) {
bool isLength = selector.isGetter && selector.name == "length";
if (isLength || selector.isIndex) {
return closedWorld.isSubtypeOf(
element.enclosingClass, commonElements.jsIndexableClass);
} else if (selector.isIndexSet) {
return closedWorld.isSubtypeOf(
element.enclosingClass, commonElements.jsMutableIndexableClass);
} else {
return false;
}
}
bool isOptimizableOperation(Selector selector, Element element) {
ClassElement cls = element.enclosingClass;
if (isOptimizableOperationOnIndexable(selector, element)) return true;
if (!interceptorData.interceptedClasses.contains(cls)) return false;
if (selector.isOperator) return true;
if (selector.isSetter) return true;
if (selector.isIndex) return true;
if (selector.isIndexSet) return true;
if (element == commonElements.jsArrayAdd ||
element == commonElements.jsArrayRemoveLast ||
element == commonElements.jsStringSplit) {
return true;
}
return false;
}
MemberElement element = closedWorld.locateSingleElement(selector, mask);
if (element != null &&
!element.isField &&
!(element.isGetter && selector.isCall) &&
!(element.isFunction && selector.isGetter) &&
!isOptimizableOperation(selector, element)) {
if (tryInlineMethod(element, selector, mask, arguments, node)) {
return;
}
}
HInstruction receiver = arguments[0];
List<HInstruction> inputs = <HInstruction>[];
bool isIntercepted = interceptorData.isInterceptedSelector(selector);
if (isIntercepted) {
inputs.add(invokeInterceptor(receiver));
}
inputs.addAll(arguments);
TypeMask type = TypeMaskFactory.inferredTypeForSelector(
selector, mask, globalInferenceResults);
if (selector.isGetter) {
push(new HInvokeDynamicGetter(
selector, mask, null, inputs, type, sourceInformation));
} else if (selector.isSetter) {
push(new HInvokeDynamicSetter(
selector, mask, null, inputs, type, sourceInformation));
} else {
push(new HInvokeDynamicMethod(
selector, mask, inputs, type, sourceInformation, isIntercepted));
}
}
HForeignCode invokeJsInteropFunction(MethodElement element,
List<HInstruction> arguments, SourceInformation sourceInformation) {
assert(nativeData.isJsInteropMember(element));
nativeEmitter.nativeMethods.add(element);
if (element.isFactoryConstructor &&
nativeData.isAnonymousJsInteropClass(element.contextClass)) {
// Factory constructor that is syntactic sugar for creating a JavaScript
// object literal.
ConstructorElement constructor = element;
FunctionSignature params = constructor.functionSignature;
int i = 0;
int positions = 0;
var filteredArguments = <HInstruction>[];
var parameterNameMap = new Map<String, js.Expression>();
params.orderedForEachParameter((_parameter) {
ParameterElement parameter = _parameter;
// TODO(jacobr): consider throwing if parameter names do not match
// names of properties in the class.
assert(parameter.isNamed);
HInstruction argument = arguments[i];
if (argument != null) {
filteredArguments.add(argument);
var jsName = nativeData.computeUnescapedJSInteropName(parameter.name);
parameterNameMap[jsName] = new js.InterpolatedExpression(positions++);
}
i++;
});
var codeTemplate =
new js.Template(null, js.objectLiteral(parameterNameMap));
var nativeBehavior = new native.NativeBehavior()
..codeTemplate = codeTemplate;
if (options.trustJSInteropTypeAnnotations) {
nativeBehavior.typesReturned.add(constructor.enclosingClass.thisType);
}
return new HForeignCode(
codeTemplate, commonMasks.dynamicType, filteredArguments,
nativeBehavior: nativeBehavior)
..sourceInformation = sourceInformation;
}
var target = new HForeignCode(
js.js.parseForeignJS("${nativeData.getFixedBackendMethodPath(element)}."
"${nativeData.getFixedBackendName(element)}"),
commonMasks.dynamicType,
<HInstruction>[]);
add(target);
// Strip off trailing arguments that were not specified.
// we could assert that the trailing arguments are all null.
// TODO(jacobr): rewrite named arguments to an object literal matching
// the factory constructor case.
arguments = arguments.where((arg) => arg != null).toList();
var inputs = <HInstruction>[target]..addAll(arguments);
var nativeBehavior = new native.NativeBehavior()
..sideEffects.setAllSideEffects();
ResolutionDartType type = element.isConstructor
? element.enclosingClass.thisType
: element.type.returnType;
// Native behavior effects here are similar to native/behavior.dart.
// The return type is dynamic if we don't trust js-interop type
// declarations.
nativeBehavior.typesReturned.add(options.trustJSInteropTypeAnnotations
? type
: const ResolutionDynamicType());
// The allocation effects include the declared type if it is native (which
// includes js interop types).
if (type is ResolutionInterfaceType &&
nativeData.isNativeClass(type.element)) {
nativeBehavior.typesInstantiated.add(type);
}
// It also includes any other JS interop type if we don't trust the
// annotation or if is declared too broad.
if (!options.trustJSInteropTypeAnnotations ||
type.isObject ||
type.isDynamic) {
ClassElement cls = commonElements.jsJavaScriptObjectClass;
nativeBehavior.typesInstantiated.add(cls.thisType);
}
String code;
if (element.isGetter) {
code = "#";
} else if (element.isSetter) {
code = "# = #";
} else {
var args = new List.filled(arguments.length, '#').join(',');
code = element.isConstructor ? "new #($args)" : "#($args)";
}
js.Template codeTemplate = js.js.parseForeignJS(code);
nativeBehavior.codeTemplate = codeTemplate;
return new HForeignCode(codeTemplate, commonMasks.dynamicType, inputs,
nativeBehavior: nativeBehavior)
..sourceInformation = sourceInformation;
}
void pushInvokeStatic(
ast.Node location, MethodElement element, List<HInstruction> arguments,
{TypeMask typeMask,
ResolutionInterfaceType instanceType,
SourceInformation sourceInformation}) {
assert(element.isDeclaration);
// TODO(johnniwinther): Use [sourceInformation] instead of [location].
if (tryInlineMethod(element, null, null, arguments, location,
instanceType: instanceType)) {
return;
}
if (typeMask == null) {
typeMask = TypeMaskFactory.inferredReturnTypeForElement(
element, globalInferenceResults);
}
bool targetCanThrow = !closedWorld.getCannotThrow(element);
// TODO(5346): Try to avoid the need for calling [declaration] before
var instruction;
if (nativeData.isJsInteropMember(element)) {
instruction =
invokeJsInteropFunction(element, arguments, sourceInformation);
} else {
// creating an [HInvokeStatic].
instruction = new HInvokeStatic(element, arguments, typeMask,
targetCanThrow: targetCanThrow)
..sourceInformation = sourceInformation;
if (currentInlinedInstantiations.isNotEmpty) {
instruction.instantiatedTypes = new List<ResolutionInterfaceType>.from(
currentInlinedInstantiations);
}
instruction.sideEffects = closedWorld.getSideEffectsOfElement(element);
}
if (sourceInformation != null || location == null) {
push(instruction);
} else {
pushWithPosition(instruction, location);
}
}
HInstruction buildInvokeSuper(Selector selector, MemberElement element,
List<HInstruction> arguments, SourceInformation sourceInformation) {
HInstruction receiver =
localsHandler.readThis(sourceInformation: sourceInformation);
// TODO(5346): Try to avoid the need for calling [declaration] before
// creating an [HStatic].
List<HInstruction> inputs = <HInstruction>[];
if (interceptorData.isInterceptedSelector(selector) &&
// Fields don't need an interceptor; consider generating HFieldGet/Set
// instead.
element.kind != ElementKind.FIELD) {
inputs.add(invokeInterceptor(receiver));
}
inputs.add(receiver);
inputs.addAll(arguments);
TypeMask type;
if (!element.isGetter && selector.isGetter) {
type = TypeMaskFactory.inferredTypeForMember(
element, globalInferenceResults);
} else if (element.isFunction) {
MethodElement method = element;
type = TypeMaskFactory.inferredReturnTypeForElement(
method, globalInferenceResults);
} else {
type = closedWorld.commonMasks.dynamicType;
}
HInstruction instruction = new HInvokeSuper(element, currentNonClosureClass,
selector, inputs, type, sourceInformation,
isSetter: selector.isSetter || selector.isIndexSet);
instruction.sideEffects =
closedWorld.getSideEffectsOfSelector(selector, null);
return instruction;
}
void handleComplexOperatorSend(
ast.SendSet node, HInstruction receiver, Link<ast.Node> arguments) {
HInstruction rhs;
if (node.isPrefix || node.isPostfix) {
rhs = graph.addConstantInt(1, closedWorld);
} else {
visit(arguments.head);
assert(arguments.tail.isEmpty);
rhs = pop();
}
visitBinarySend(
receiver,
rhs,
elements.getOperatorSelectorInComplexSendSet(node),
elementInferenceResults.typeOfOperator(node),
node,
sourceInformation:
sourceInformationBuilder.buildGeneric(node.assignmentOperator));
}
void handleSuperSendSet(ast.SendSet node) {
MemberElement element = elements[node];
List<HInstruction> setterInputs = <HInstruction>[];
void generateSuperSendSet() {
Selector setterSelector = elements.getSelector(node);
if (Elements.isUnresolved(element) || !setterSelector.applies(element)) {
generateSuperNoSuchMethodSend(node, setterSelector, setterInputs);
pop();
} else {
add(buildInvokeSuper(setterSelector, element, setterInputs,
sourceInformationBuilder.buildAssignment(node)));
}
}
if (identical(node.assignmentOperator.source, '=')) {
addDynamicSendArgumentsToList(node, setterInputs);
generateSuperSendSet();
stack.add(setterInputs.last);
} else {
Element getter = elements[node.selector];
List<HInstruction> getterInputs = <HInstruction>[];
Link<ast.Node> arguments = node.arguments;
if (node.isIndex) {
// If node is of the form [:super.foo[0] += 2:], the send has
// two arguments: the index and the left hand side. We get
// the index and add it as input of the getter and the
// setter.
visit(arguments.head);
arguments = arguments.tail;
HInstruction index = pop();
getterInputs.add(index);
setterInputs.add(index);
}
HInstruction getterInstruction;
Selector getterSelector =
elements.getGetterSelectorInComplexSendSet(node);
if (Elements.isUnresolved(getter)) {
generateSuperNoSuchMethodSend(node, getterSelector, getterInputs);
getterInstruction = pop();
} else {
getterInstruction = buildInvokeSuper(getterSelector, getter,
getterInputs, sourceInformationBuilder.buildGet(node));
add(getterInstruction);
}
if (node.isIfNullAssignment) {
SsaBranchBuilder brancher = new SsaBranchBuilder(this, node);
brancher.handleIfNull(() => stack.add(getterInstruction), () {
addDynamicSendArgumentsToList(node, setterInputs);
generateSuperSendSet();
stack.add(setterInputs.last);
});
} else {
handleComplexOperatorSend(node, getterInstruction, arguments);
setterInputs.add(pop());
generateSuperSendSet();
stack.add(node.isPostfix ? getterInstruction : setterInputs.last);
}
}
}
@override
void handleSuperCompounds(
ast.SendSet node,
Element getter,
CompoundGetter getterKind,
Element setter,
CompoundSetter setterKind,
CompoundRhs rhs,
_) {
handleSuperSendSet(node);
}
@override
void visitFinalSuperFieldSet(
ast.SendSet node, FieldElement field, ast.Node rhs, _) {
handleSuperSendSet(node);
}
@override
void visitSuperFieldSet(
ast.SendSet node, FieldElement field, ast.Node rhs, _) {
handleSuperSendSet(node);
}
@override
void visitSuperGetterSet(
ast.SendSet node, FunctionElement getter, ast.Node rhs, _) {
handleSuperSendSet(node);
}
@override
void visitSuperIndexSet(ast.SendSet node, FunctionElement function,
ast.Node index, ast.Node rhs, _) {
handleSuperSendSet(node);
}
@override
void visitSuperMethodSet(
ast.SendSet node, MethodElement method, ast.Node rhs, _) {
handleSuperSendSet(node);
}
@override
void visitSuperSetterSet(
ast.SendSet node, FunctionElement setter, ast.Node rhs, _) {
handleSuperSendSet(node);
}
@override
void visitUnresolvedSuperIndexSet(ast.SendSet node, ErroneousElement element,
ast.Node index, ast.Node rhs, _) {
handleSuperSendSet(node);
}
@override
void visitSuperIndexPrefix(
ast.Send node,
MethodElement indexFunction,
MethodElement indexSetFunction,
ast.Node index,
IncDecOperator operator,
_) {
handleSuperSendSet(node);
}
@override
void visitSuperIndexPostfix(
ast.Send node,
MethodElement indexFunction,
MethodElement indexSetFunction,
ast.Node index,
IncDecOperator operator,
_) {
handleSuperSendSet(node);
}
@override
void visitUnresolvedSuperGetterIndexPrefix(ast.SendSet node, Element element,
MethodElement setter, ast.Node index, IncDecOperator operator, _) {
handleSuperSendSet(node);
}
@override
void visitUnresolvedSuperGetterIndexPostfix(ast.SendSet node, Element element,
MethodElement setter, ast.Node index, IncDecOperator operator, _) {
handleSuperSendSet(node);
}
@override
void visitUnresolvedSuperSetterIndexPrefix(
ast.SendSet node,
MethodElement indexFunction,
Element element,
ast.Node index,
IncDecOperator operator,
_) {
handleSuperSendSet(node);
}
@override
void visitUnresolvedSuperSetterIndexPostfix(
ast.SendSet node,
MethodElement indexFunction,
Element element,
ast.Node index,
IncDecOperator operator,
_) {
handleSuperSendSet(node);
}
@override
void visitUnresolvedSuperIndexPrefix(ast.Send node, Element element,
ast.Node index, IncDecOperator operator, _) {
handleSuperSendSet(node);
}
@override
void visitUnresolvedSuperIndexPostfix(ast.Send node, Element element,
ast.Node index, IncDecOperator operator, _) {
handleSuperSendSet(node);
}
@override
void visitSuperCompoundIndexSet(
ast.SendSet node,
MethodElement getter,
MethodElement setter,
ast.Node index,
AssignmentOperator operator,
ast.Node rhs,
_) {
handleSuperSendSet(node);
}
@override
void visitUnresolvedSuperGetterCompoundIndexSet(
ast.SendSet node,
Element element,
MethodElement setter,
ast.Node index,
AssignmentOperator operator,
ast.Node rhs,
_) {
handleSuperSendSet(node);
}
@override
void visitUnresolvedSuperSetterCompoundIndexSet(
ast.SendSet node,
MethodElement getter,
Element element,
ast.Node index,
AssignmentOperator operator,
ast.Node rhs,
_) {
handleSuperSendSet(node);
}
@override
void visitUnresolvedSuperCompoundIndexSet(ast.SendSet node, Element element,
ast.Node index, AssignmentOperator operator, ast.Node rhs, _) {
handleSuperSendSet(node);
}
@override
void visitSuperFieldCompound(ast.Send node, FieldElement field,
AssignmentOperator operator, ast.Node rhs, _) {
handleSuperSendSet(node);
}
@override
void visitFinalSuperFieldCompound(ast.Send node, FieldElement field,
AssignmentOperator operator, ast.Node rhs, _) {
handleSuperSendSet(node);
}
@override
void visitFinalSuperFieldPrefix(
ast.Send node, FieldElement field, IncDecOperator operator, _) {
handleSuperSendSet(node);
}
@override
void visitUnresolvedSuperPrefix(
ast.SendSet node, Element element, IncDecOperator operator, _) {
handleSuperSendSet(node);
}
@override
void visitUnresolvedSuperPostfix(
ast.SendSet node, Element element, IncDecOperator operator, _) {
handleSuperSendSet(node);
}
@override
void visitUnresolvedSuperCompound(ast.Send node, Element element,
AssignmentOperator operator, ast.Node rhs, _) {
handleSuperSendSet(node);
}
@override
void visitFinalSuperFieldPostfix(
ast.Send node, FieldElement field, IncDecOperator operator, _) {
handleSuperSendSet(node);
}
@override
void visitSuperFieldFieldCompound(ast.Send node, FieldElement readField,
FieldElement writtenField, AssignmentOperator operator, ast.Node rhs, _) {
handleSuperSendSet(node);
}
@override
void visitSuperGetterSetterCompound(ast.Send node, FunctionElement getter,
FunctionElement setter, AssignmentOperator operator, ast.Node rhs, _) {
handleSuperSendSet(node);
}
@override
void visitSuperMethodSetterCompound(ast.Send node, FunctionElement method,
FunctionElement setter, AssignmentOperator operator, ast.Node rhs, _) {
handleSuperSendSet(node);
}
@override
void visitSuperMethodCompound(ast.Send node, MethodElement method,
AssignmentOperator operator, ast.Node rhs, _) {
handleSuperSendSet(node);
}
@override
void visitUnresolvedSuperGetterCompound(ast.SendSet node, Element element,
SetterElement setter, AssignmentOperator operator, ast.Node rhs, _) {
handleSuperSendSet(node);
}
@override
void visitUnresolvedSuperSetterCompound(ast.Send node, GetterElement getter,
Element element, AssignmentOperator operator, ast.Node rhs, _) {
handleSuperSendSet(node);
}
@override
void visitSuperFieldSetterCompound(ast.Send node, FieldElement field,
FunctionElement setter, AssignmentOperator operator, ast.Node rhs, _) {
handleSuperSendSet(node);
}
@override
void visitSuperGetterFieldCompound(ast.Send node, FunctionElement getter,
FieldElement field, AssignmentOperator operator, ast.Node rhs, _) {
handleSuperSendSet(node);
}
@override
void visitIndexSet(
ast.SendSet node, ast.Node receiver, ast.Node index, ast.Node rhs, _) {
generateDynamicSend(node);
}
@override
void visitCompoundIndexSet(ast.SendSet node, ast.Node receiver,
ast.Node index, AssignmentOperator operator, ast.Node rhs, _) {
generateIsDeferredLoadedCheckOfSend(node);
handleIndexSendSet(node);
}
@override
void visitIndexPrefix(ast.Send node, ast.Node receiver, ast.Node index,
IncDecOperator operator, _) {
generateIsDeferredLoadedCheckOfSend(node);
handleIndexSendSet(node);
}
@override
void visitIndexPostfix(ast.Send node, ast.Node receiver, ast.Node index,
IncDecOperator operator, _) {
generateIsDeferredLoadedCheckOfSend(node);
handleIndexSendSet(node);
}
void handleIndexSendSet(ast.SendSet node) {
ast.Operator op = node.assignmentOperator;
if ("=" == op.source) {
internalError(node, "Unexpected index set.");
} else {
visit(node.receiver);
HInstruction receiver = pop();
Link<ast.Node> arguments = node.arguments;
HInstruction index;
if (node.isIndex) {
visit(arguments.head);
arguments = arguments.tail;
index = pop();
}
pushInvokeDynamic(node, elements.getGetterSelectorInComplexSendSet(node),
elementInferenceResults.typeOfGetter(node), [receiver, index]);
HInstruction getterInstruction = pop();
if (node.isIfNullAssignment) {
// Compile x[i] ??= e as:
// t1 = x[i]
// if (t1 == null)
// t1 = x[i] = e;
// result = t1
SsaBranchBuilder brancher = new SsaBranchBuilder(this, node);
brancher.handleIfNull(() => stack.add(getterInstruction), () {
visit(arguments.head);
HInstruction value = pop();
pushInvokeDynamic(
node,
elements.getSelector(node),
elementInferenceResults.typeOfSend(node),
[receiver, index, value]);
pop();
stack.add(value);
});
} else {
handleComplexOperatorSend(node, getterInstruction, arguments);
HInstruction value = pop();
pushInvokeDynamic(node, elements.getSelector(node),
elementInferenceResults.typeOfSend(node), [receiver, index, value]);
pop();
if (node.isPostfix) {
stack.add(getterInstruction);
} else {
stack.add(value);
}
}
}
}
@override
void visitThisPropertySet(ast.SendSet node, Name name, ast.Node rhs, _) {
generateInstanceSetterWithCompiledReceiver(
node,
localsHandler.readThis(
sourceInformation: sourceInformationBuilder.buildGet(node)),
visitAndPop(rhs));
}
@override
void visitDynamicPropertySet(
ast.SendSet node, ast.Node receiver, Name name, ast.Node rhs, _) {
generateInstanceSetterWithCompiledReceiver(
node, generateInstanceSendReceiver(node), visitAndPop(rhs));
}
@override
void visitIfNotNullDynamicPropertySet(
ast.SendSet node, ast.Node receiver, Name name, ast.Node rhs, _) {
// compile e?.x = e2 to:
//
// t1 = e
// if (t1 == null)
// result = t1 // same as result = null
// else
// result = e.x = e2
HInstruction receiverInstruction;
SsaBranchBuilder brancher = new SsaBranchBuilder(this, node);
brancher.handleConditional(
() {
receiverInstruction = generateInstanceSendReceiver(node);
pushCheckNull(receiverInstruction);
},
() => stack.add(receiverInstruction),
() {
generateInstanceSetterWithCompiledReceiver(
node, receiverInstruction, visitAndPop(rhs));
});
}
@override
void visitParameterSet(
ast.SendSet node, ParameterElement parameter, ast.Node rhs, _) {
generateNonInstanceSetter(node, parameter, visitAndPop(rhs));
}
@override
void visitFinalParameterSet(
ast.SendSet node, ParameterElement parameter, ast.Node rhs, _) {
generateNoSuchSetter(node, parameter, visitAndPop(rhs));
}
@override
void visitLocalVariableSet(
ast.SendSet node, LocalVariableElement variable, ast.Node rhs, _) {
generateNonInstanceSetter(node, variable, visitAndPop(rhs));
}
@override
void visitFinalLocalVariableSet(
ast.SendSet node, LocalVariableElement variable, ast.Node rhs, _) {
generateNoSuchSetter(node, variable, visitAndPop(rhs));
}
@override
void visitLocalFunctionSet(
ast.SendSet node, LocalFunctionElement function, ast.Node rhs, _) {
generateNoSuchSetter(node, function, visitAndPop(rhs));
}
@override
void visitTopLevelFieldSet(
ast.SendSet node, FieldElement field, ast.Node rhs, _) {
generateIsDeferredLoadedCheckOfSend(node);
generateNonInstanceSetter(node, field, visitAndPop(rhs));
}
@override
void visitFinalTopLevelFieldSet(
ast.SendSet node, FieldElement field, ast.Node rhs, _) {
generateIsDeferredLoadedCheckOfSend(node);
generateNoSuchSetter(node, field, visitAndPop(rhs));
}
@override
void visitTopLevelGetterSet(
ast.SendSet node, GetterElement getter, ast.Node rhs, _) {
generateIsDeferredLoadedCheckOfSend(node);
generateNoSuchSetter(node, getter, visitAndPop(rhs));
}
@override
void visitTopLevelSetterSet(
ast.SendSet node, SetterElement setter, ast.Node rhs, _) {
generateIsDeferredLoadedCheckOfSend(node);
generateNonInstanceSetter(node, setter, visitAndPop(rhs));
}
@override
void visitTopLevelFunctionSet(
ast.SendSet node, MethodElement function, ast.Node rhs, _) {
generateIsDeferredLoadedCheckOfSend(node);
generateNoSuchSetter(node, function, visitAndPop(rhs));
}
@override
void visitStaticFieldSet(
ast.SendSet node, FieldElement field, ast.Node rhs, _) {
generateIsDeferredLoadedCheckOfSend(node);
generateNonInstanceSetter(node, field, visitAndPop(rhs));
}
@override
void visitFinalStaticFieldSet(
ast.SendSet node, FieldElement field, ast.Node rhs, _) {
generateIsDeferredLoadedCheckOfSend(node);
generateNoSuchSetter(node, field, visitAndPop(rhs));
}
@override
void visitStaticGetterSet(
ast.SendSet node, GetterElement getter, ast.Node rhs, _) {
generateIsDeferredLoadedCheckOfSend(node);
generateNoSuchSetter(node, getter, visitAndPop(rhs));
}
@override
void visitStaticSetterSet(
ast.SendSet node, SetterElement setter, ast.Node rhs, _) {
generateIsDeferredLoadedCheckOfSend(node);
generateNonInstanceSetter(node, setter, visitAndPop(rhs));
}
@override
void visitStaticFunctionSet(
ast.SendSet node, MethodElement function, ast.Node rhs, _) {
generateIsDeferredLoadedCheckOfSend(node);
generateNoSuchSetter(node, function, visitAndPop(rhs));
}
@override
void visitUnresolvedSet(ast.SendSet node, Element element, ast.Node rhs, _) {
generateIsDeferredLoadedCheckOfSend(node);
generateNonInstanceSetter(node, element, visitAndPop(rhs));
}
@override
void visitClassTypeLiteralSet(
ast.SendSet node, TypeConstantExpression constant, ast.Node rhs, _) {
generateThrowNoSuchMethod(node, constant.name,
argumentNodes: node.arguments);
}
@override
void visitTypedefTypeLiteralSet(
ast.SendSet node, TypeConstantExpression constant, ast.Node rhs, _) {
generateThrowNoSuchMethod(node, constant.name,
argumentNodes: node.arguments);
}
@override
void visitDynamicTypeLiteralSet(
ast.SendSet node, TypeConstantExpression constant, ast.Node rhs, _) {
generateThrowNoSuchMethod(node, constant.name,
argumentNodes: node.arguments);
}
@override
void visitTypeVariableTypeLiteralSet(
ast.SendSet node, TypeVariableElement element, ast.Node rhs, _) {
generateThrowNoSuchMethod(node, element.name,
argumentNodes: node.arguments);
}
void handleCompoundSendSet(ast.SendSet node) {
Element element = elements[node];
Element getter = elements[node.selector];
if (!Elements.isUnresolved(getter) && getter.impliesType) {
if (node.isIfNullAssignment) {
// C ??= x is compiled just as C.
stack.add(addConstant(node.selector));
} else {
ast.Identifier selector = node.selector;
generateThrowNoSuchMethod(node, selector.source,
argumentNodes: node.arguments);
}
return;
}
if (Elements.isInstanceSend(node, elements)) {
void generateAssignment(HInstruction receiver) {
// desugars `e.x op= e2` to `e.x = e.x op e2`
generateInstanceGetterWithCompiledReceiver(
node,
elements.getGetterSelectorInComplexSendSet(node),
elementInferenceResults.typeOfGetter(node),
receiver);
HInstruction getterInstruction = pop();
if (node.isIfNullAssignment) {
SsaBranchBuilder brancher = new SsaBranchBuilder(this, node);
brancher.handleIfNull(() => stack.add(getterInstruction), () {
visit(node.arguments.head);
generateInstanceSetterWithCompiledReceiver(node, receiver, pop());
});
} else {
handleComplexOperatorSend(node, getterInstruction, node.arguments);
HInstruction value = pop();
generateInstanceSetterWithCompiledReceiver(node, receiver, value);
}
if (node.isPostfix) {
pop();
stack.add(getterInstruction);
}
}
if (node.isConditional) {
// generate `e?.x op= e2` as:
// t1 = e
// t1 == null ? t1 : (t1.x = t1.x op e2);
HInstruction receiver;
SsaBranchBuilder brancher = new SsaBranchBuilder(this, node);
brancher.handleConditional(() {
receiver = generateInstanceSendReceiver(node);
pushCheckNull(receiver);
}, () => stack.add(receiver), () => generateAssignment(receiver));
} else {
generateAssignment(generateInstanceSendReceiver(node));
}
return;
}
if (getter.isMalformed) {
generateStaticUnresolvedGet(node, getter);
} else if (getter.isField) {
generateStaticFieldGet(node, getter);
} else if (getter.isGetter) {
generateStaticGetterGet(node, getter);
} else if (getter.isFunction) {
generateStaticFunctionGet(node, getter);
} else if (getter.isLocal) {
handleLocalGet(node, getter);
} else {
internalError(node, "Unexpected getter: $getter");
}
HInstruction getterInstruction = pop();
if (node.isIfNullAssignment) {
SsaBranchBuilder brancher = new SsaBranchBuilder(this, node);
brancher.handleIfNull(() => stack.add(getterInstruction), () {
visit(node.arguments.head);
generateNonInstanceSetter(node, element, pop());
});
} else {
handleComplexOperatorSend(node, getterInstruction, node.arguments);
HInstruction value = pop();
generateNonInstanceSetter(node, element, value);
}
if (node.isPostfix) {
pop();
stack.add(getterInstruction);
}
}
@override
void handleDynamicCompounds(
ast.Send node, ast.Node receiver, Name name, CompoundRhs rhs, _) {
handleCompoundSendSet(node);
}
@override
void handleLocalCompounds(
ast.SendSet node, LocalElement local, CompoundRhs rhs, _,
{bool isSetterValid}) {
handleCompoundSendSet(node);
}
@override
void handleStaticCompounds(
ast.SendSet node,
Element getter,
CompoundGetter getterKind,
Element setter,
CompoundSetter setterKind,
CompoundRhs rhs,
_) {
handleCompoundSendSet(node);
}
@override
handleDynamicSetIfNulls(
ast.Send node, ast.Node receiver, Name name, ast.Node rhs, arg) {
handleCompoundSendSet(node);
}
@override
handleLocalSetIfNulls(ast.SendSet node, LocalElement local, ast.Node rhs, arg,
{bool isSetterValid}) {
handleCompoundSendSet(node);
}
@override
handleStaticSetIfNulls(
ast.SendSet node,
Element getter,
CompoundGetter getterKind,
Element setter,
CompoundSetter setterKind,
ast.Node rhs,
arg) {
handleCompoundSendSet(node);
}
@override
handleSuperSetIfNulls(
ast.SendSet node,
Element getter,
CompoundGetter getterKind,
Element setter,
CompoundSetter setterKind,
ast.Node rhs,
arg) {
handleSuperSendSet(node);
}
@override
handleSuperIndexSetIfNull(ast.SendSet node, Element indexFunction,
Element indexSetFunction, ast.Node index, ast.Node rhs, arg,
{bool isGetterValid, bool isSetterValid}) {
handleCompoundSendSet(node);
}
@override
visitIndexSetIfNull(
ast.SendSet node, ast.Node receiver, ast.Node index, ast.Node rhs, arg,
{bool isGetterValid, bool isSetterValid}) {
generateIsDeferredLoadedCheckOfSend(node);
handleIndexSendSet(node);
}
@override
handleTypeLiteralConstantSetIfNulls(
ast.SendSet node, ConstantExpression constant, ast.Node rhs, arg) {
// The type variable is never `null`.
generateConstantTypeLiteral(node);
}
@override
visitTypeVariableTypeLiteralSetIfNull(
ast.Send node, TypeVariableElement element, ast.Node rhs, arg) {
// The type variable is never `null`.
generateTypeVariableLiteral(node, element.type);
}
void visitLiteralInt(ast.LiteralInt node) {
stack.add(graph.addConstantInt(node.value, closedWorld));
}
void visitLiteralDouble(ast.LiteralDouble node) {
stack.add(graph.addConstantDouble(node.value, closedWorld));
}
void visitLiteralBool(ast.LiteralBool node) {
stack.add(graph.addConstantBool(node.value, closedWorld));
}
void visitLiteralString(ast.LiteralString node) {
stack.add(
graph.addConstantString(node.dartString.slowToString(), closedWorld));
}
void visitLiteralSymbol(ast.LiteralSymbol node) {
stack.add(addConstant(node));
registry?.registerConstSymbol(node.slowNameString);
}
void visitStringJuxtaposition(ast.StringJuxtaposition node) {
if (!node.isInterpolation) {
// This is a simple string with no interpolations.
stack.add(
graph.addConstantString(node.dartString.slowToString(), closedWorld));
return;
}
StringBuilderVisitor stringBuilder = new StringBuilderVisitor(this, node);
stringBuilder.visit(node);
stack.add(stringBuilder.result);
}
void visitLiteralNull(ast.LiteralNull node) {
stack.add(graph.addConstantNull(closedWorld));
}
visitNodeList(ast.NodeList node) {
for (Link<ast.Node> link = node.nodes; !link.isEmpty; link = link.tail) {
if (isAborted()) {
reporter.reportHintMessage(
link.head, MessageKind.GENERIC, {'text': 'dead code'});
} else {
visit(link.head);
}
}
}
void visitParenthesizedExpression(ast.ParenthesizedExpression node) {
visit(node.expression);
}
visitOperator(ast.Operator node) {
// Operators are intercepted in their surrounding Send nodes.
reporter.internalError(
node, 'SsaBuilder.visitOperator should not be called.');
}
visitCascade(ast.Cascade node) {
visit(node.expression);
// Remove the result and reveal the duplicated receiver on the stack.
pop();
}
visitCascadeReceiver(ast.CascadeReceiver node) {
visit(node.expression);
dup();
}
visitRethrow(ast.Rethrow node) {
HInstruction exception = rethrowableException;
if (exception == null) {
exception = graph.addConstantNull(closedWorld);
reporter.internalError(node, 'rethrowableException should not be null.');
}
handleInTryStatement();
closeAndGotoExit(new HThrow(
exception, sourceInformationBuilder.buildThrow(node),
isRethrow: true));
}
visitRedirectingFactoryBody(ast.RedirectingFactoryBody node) {
ConstructorElement targetConstructor =
elements.getRedirectingTargetConstructor(node).implementation;
ConstructorElement redirectingConstructor = sourceElement.implementation;
List<HInstruction> inputs = <HInstruction>[];
FunctionSignature targetSignature = targetConstructor.functionSignature;
FunctionSignature redirectingSignature =
redirectingConstructor.functionSignature;
List<Element> targetRequireds = targetSignature.requiredParameters;
List<Element> redirectingRequireds =
redirectingSignature.requiredParameters;
List<Element> targetOptionals = targetSignature.orderedOptionalParameters;
List<Element> redirectingOptionals =
redirectingSignature.orderedOptionalParameters;
// TODO(25579): This code can do the wrong thing redirecting constructor and
// the target do not correspond. It is correct if there is no
// warning. Ideally the redirecting constructor and the target would be the
// same function.
void loadLocal(ParameterElement parameter) {
inputs.add(localsHandler.readLocal(parameter));
}
void loadPosition(int position, ParameterElement optionalParameter) {
if (position < redirectingRequireds.length) {
loadLocal(redirectingRequireds[position]);
} else if (position < redirectingSignature.parameterCount &&
!redirectingSignature.optionalParametersAreNamed) {
loadLocal(redirectingOptionals[position - redirectingRequireds.length]);
} else if (optionalParameter != null) {
inputs.add(handleConstantForOptionalParameter(optionalParameter));
} else {
// Wrong.
inputs.add(graph.addConstantNull(closedWorld));
}
}
int position = 0;
for (ParameterElement _ in targetRequireds) {
loadPosition(position++, null);
}
if (targetOptionals.isNotEmpty) {
if (targetSignature.optionalParametersAreNamed) {
for (ParameterElement parameter in targetOptionals) {
ParameterElement redirectingParameter = redirectingOptionals
.firstWhere((p) => p.name == parameter.name, orElse: () => null);
if (redirectingParameter == null) {
inputs.add(handleConstantForOptionalParameter(parameter));
} else {
inputs.add(localsHandler.readLocal(redirectingParameter));
}
}
} else {
for (ParameterElement parameter in targetOptionals) {
loadPosition(position++, parameter);
}
}
}
ClassElement targetClass = targetConstructor.enclosingClass;
if (rtiNeed.classNeedsRti(targetClass)) {
ClassElement cls = redirectingConstructor.enclosingClass;
ResolutionDartType targetType =
redirectingConstructor.computeEffectiveTargetType(cls.thisType);
targetType = localsHandler.substInContext(targetType);
if (targetType is ResolutionInterfaceType) {
targetType.typeArguments.forEach((ResolutionDartType argument) {
inputs.add(typeBuilder.analyzeTypeArgument(argument, sourceElement));
});
}
}
pushInvokeStatic(node, targetConstructor.declaration, inputs);
HInstruction value = pop();
emitReturn(value, node);
}
/// Returns true if the [type] is a valid return type for an asynchronous
/// function.
///
/// Asynchronous functions return a `Future`, and a valid return is thus
/// either dynamic, Object, or Future.
///
/// We do not accept the internal Future implementation class.
bool isValidAsyncReturnType(ResolutionDartType type) {
assert(isBuildingAsyncFunction);
// TODO(sigurdm): In an internal library a function could be declared:
//
// _FutureImpl foo async => 1;
//
// This should be valid (because the actual value returned from an async
// function is a `_FutureImpl`), but currently false is returned in this
// case.
return type.isDynamic ||
type.isObject ||
(type is ResolutionInterfaceType &&
type.element == commonElements.futureClass);
}
visitReturn(ast.Return node) {
if (identical(node.beginToken.stringValue, 'native')) {
native.handleSsaNative(this, node.expression);
return;
}
HInstruction value;
if (node.expression == null) {
value = graph.addConstantNull(closedWorld);
} else {
visit(node.expression);
value = pop();
if (isBuildingAsyncFunction) {
if (options.enableTypeAssertions &&
!isValidAsyncReturnType(returnType)) {
String message = "Async function returned a Future, "
"was declared to return a $returnType.";
generateTypeError(node, message);
pop();
return;
}
} else {
value = typeBuilder.potentiallyCheckOrTrustType(value, returnType);
}
}
handleInTryStatement();
emitReturn(value, node);
}
visitThrow(ast.Throw node) {
visitThrowExpression(node.expression);
if (isReachable) {
handleInTryStatement();
push(new HThrowExpression(
pop(), sourceInformationBuilder.buildThrow(node)));
isReachable = false;
}
}
visitYield(ast.Yield node) {
visit(node.expression);
HInstruction yielded = pop();
add(new HYield(
yielded, node.hasStar, sourceInformationBuilder.buildYield(node)));
}
visitAwait(ast.Await node) {
visit(node.expression);
HInstruction awaited = pop();
// TODO(herhut): Improve this type.
push(new HAwait(
awaited, new TypeMask.subclass(commonElements.objectClass, closedWorld))
..sourceInformation = sourceInformationBuilder.buildAwait(node));
}
visitTypeAnnotation(ast.TypeAnnotation node) {
reporter.internalError(node, 'Visiting type annotation in SSA builder.');
}
visitVariableDefinitions(ast.VariableDefinitions node) {
assert(isReachable);
for (Link<ast.Node> link = node.definitions.nodes;
!link.isEmpty;
link = link.tail) {
ast.Node definition = link.head;
LocalElement local = elements[definition];
if (definition is ast.Identifier) {
HInstruction initialValue = graph.addConstantNull(closedWorld);
localsHandler.updateLocal(local, initialValue);
} else {
ast.SendSet node = definition;
generateNonInstanceSetter(
node, local, visitAndPop(node.arguments.first));
pop(); // Discard value.
}
}
}
HInstruction setRtiIfNeeded(
HInstruction object, ast.Node node, SourceInformation sourceInformation) {
ResolutionInterfaceType type =
localsHandler.substInContext(elements.getType(node));
if (!rtiNeed.classNeedsRti(type.element) || type.treatAsRaw) {
return object;
}
List<HInstruction> arguments = <HInstruction>[];
for (ResolutionDartType argument in type.typeArguments) {
arguments.add(typeBuilder.analyzeTypeArgument(argument, sourceElement,
sourceInformation: sourceInformation));
}
// TODO(15489): Register at codegen.
registry?.registerInstantiation(type);
return callSetRuntimeTypeInfoWithTypeArguments(
type, arguments, object, sourceInformation);
}
visitLiteralList(ast.LiteralList node) {
HInstruction instruction;
if (node.isConst) {
instruction = addConstant(node);
} else {
List<HInstruction> inputs = <HInstruction>[];
for (Link<ast.Node> link = node.elements.nodes;
!link.isEmpty;
link = link.tail) {
visit(link.head);
inputs.add(pop());
}
instruction = buildLiteralList(inputs);
add(instruction);
instruction = setRtiIfNeeded(
instruction, node, sourceInformationBuilder.buildListLiteral(node));
}
TypeMask type = _inferredTypeOfListLiteral(node);
if (!type.containsAll(closedWorld)) {
instruction.instructionType = type;
}
stack.add(instruction);
}
_inferredTypeOfNewList(ast.Send node) =>
_resultOf(sourceElement).typeOfNewList(node) ?? commonMasks.dynamicType;
_inferredTypeOfListLiteral(ast.LiteralList node) =>
_resultOf(sourceElement).typeOfListLiteral(node) ??
commonMasks.dynamicType;
visitConditional(ast.Conditional node) {
SsaBranchBuilder brancher = new SsaBranchBuilder(this, node);
brancher.handleConditional(() => visit(node.condition),
() => visit(node.thenExpression), () => visit(node.elseExpression));
}
visitStringInterpolation(ast.StringInterpolation node) {
StringBuilderVisitor stringBuilder = new StringBuilderVisitor(this, node);
stringBuilder.visit(node);
stack.add(stringBuilder.result);
}
visitStringInterpolationPart(ast.StringInterpolationPart node) {
// The parts are iterated in visitStringInterpolation.
reporter.internalError(
node, 'SsaBuilder.visitStringInterpolation should not be called.');
}
visitEmptyStatement(ast.EmptyStatement node) {
// Do nothing, empty statement.
}
visitModifiers(ast.Modifiers node) {
failedAt(node, 'SsaFromAstMixin.visitModifiers not implemented.');
}
visitBreakStatement(ast.BreakStatement node) {
assert(!isAborted());
handleInTryStatement();
JumpTarget target = elements.getTargetOf(node);
assert(target != null);
JumpHandler handler = jumpTargets[target];
assert(handler != null);
if (node.target == null) {
handler.generateBreak(sourceInformationBuilder.buildGoto(node));
} else {
LabelDefinition label = elements.getTargetLabel(node);
handler.generateBreak(sourceInformationBuilder.buildGoto(node), label);
}
}
visitContinueStatement(ast.ContinueStatement node) {
handleInTryStatement();
JumpTarget target = elements.getTargetOf(node);
assert(target != null);
JumpHandler handler = jumpTargets[target];
assert(handler != null);
if (node.target == null) {
handler.generateContinue(sourceInformationBuilder.buildGoto(node));
} else {
LabelDefinition label = elements.getTargetLabel(node);
assert(label != null);
handler.generateContinue(sourceInformationBuilder.buildGoto(node), label);
}
}
/**
* Creates a [JumpHandler] for a statement. The node must be a jump
* target. If there are no breaks or continues targeting the statement,
* a special "null handler" is returned.
*
* [isLoopJump] is [:true:] when the jump handler is for a loop. This is used
* to distinguish the synthesized loop created for a switch statement with
* continue statements from simple switch statements.
*/
JumpHandler createJumpHandler(ast.Statement node, JumpTarget jumpTarget,
{bool isLoopJump}) {
if (jumpTarget == null || !identical(jumpTarget.statement, node)) {
// No breaks or continues to this node.
return new NullJumpHandler(reporter);
}
if (isLoopJump && node is ast.SwitchStatement) {
// Create a special jump handler for loops created for switch statements
// with continue statements.
return new AstSwitchCaseJumpHandler(this, jumpTarget, node);
}
return new JumpHandler(this, jumpTarget);
}
visitAsyncForIn(ast.AsyncForIn node) {
// The async-for is implemented with a StreamIterator.
HInstruction streamIterator;
visit(node.expression);
HInstruction expression = pop();
ConstructorElement constructor = commonElements.streamIteratorConstructor;
pushInvokeStatic(
node, constructor, [expression, graph.addConstantNull(closedWorld)]);
streamIterator = pop();
void buildInitializer() {}
HInstruction buildCondition() {
Selector selector = Selectors.moveNext;
TypeMask mask = elementInferenceResults.typeOfIteratorMoveNext(node);
pushInvokeDynamic(node, selector, mask, [streamIterator]);
HInstruction future = pop();
push(new HAwait(future,
new TypeMask.subclass(commonElements.objectClass, closedWorld)));
return popBoolified();
}
void buildBody() {
Selector call = Selectors.current;
TypeMask callMask = elementInferenceResults.typeOfIteratorCurrent(node);
pushInvokeDynamic(node, call, callMask, [streamIterator]);
ast.Node identifier = node.declaredIdentifier;
Element variable = elements.getForInVariable(node);
Selector selector = elements.getSelector(identifier);
HInstruction value = pop();
if (identifier.asSend() != null &&
Elements.isInstanceSend(identifier, elements)) {
TypeMask mask = elementInferenceResults.typeOfSend(identifier);
HInstruction receiver = generateInstanceSendReceiver(identifier);
assert(receiver != null);
generateInstanceSetterWithCompiledReceiver(null, receiver, value,
selector: selector, mask: mask, location: identifier);
} else {
generateNonInstanceSetter(null, variable, value, location: identifier);
}
pop(); // Pop the value pushed by the setter call.
visit(node.body);
}
void buildUpdate() {}
buildProtectedByFinally(() {
loopHandler.handleLoop(
node,
closureDataLookup.getCapturedLoopScope(node),
elements.getTargetDefinition(node),
buildInitializer,
buildCondition,
buildUpdate,
buildBody,
sourceInformationBuilder.buildLoop(node));
}, () {
pushInvokeDynamic(node, Selectors.cancel, null, [streamIterator]);
add(new HAwait(pop(),
new TypeMask.subclass(commonElements.objectClass, closedWorld)));
});
}
visitSyncForIn(ast.SyncForIn node) {
// The 'get iterator' selector for this node has the inferred receiver type.
// If the receiver supports JavaScript indexing we generate an indexing loop
// instead of allocating an iterator object.
// This scheme recognizes for-in on direct lists. It does not recognize all
// uses of ArrayIterator. They still occur when the receiver is an Iterable
// with a `get iterator` method that delegates to another Iterable and the
// method is inlined. We would require full scalar replacement in that
// case.
TypeMask mask = elementInferenceResults.typeOfIterator(node);
if (mask != null &&
mask.satisfies(commonElements.jsIndexableClass, closedWorld) &&
// String is indexable but not iterable.
!mask.satisfies(commonElements.jsStringClass, closedWorld)) {
return buildSyncForInIndexable(node, mask);
}
buildSyncForInIterator(node);
}
buildSyncForInIterator(ast.SyncForIn node) {
// Generate a structure equivalent to:
// Iterator<E> $iter = <iterable>.iterator;
// while ($iter.moveNext()) {
// <declaredIdentifier> = $iter.current;
// <body>
// }
// The iterator is shared between initializer, condition and body.
HInstruction iterator;
void buildInitializer() {
Selector selector = Selectors.iterator;
TypeMask mask = elementInferenceResults.typeOfIterator(node);
visit(node.expression);
HInstruction receiver = pop();
pushInvokeDynamic(node, selector, mask, [receiver],
sourceInformation: sourceInformationBuilder.buildForInIterator(node));
iterator = pop();
}
HInstruction buildCondition() {
Selector selector = Selectors.moveNext;
TypeMask mask = elementInferenceResults.typeOfIteratorMoveNext(node);
pushInvokeDynamic(node, selector, mask, [iterator],
sourceInformation: sourceInformationBuilder.buildForInMoveNext(node));
return popBoolified();
}
void buildBody() {
Selector call = Selectors.current;
TypeMask mask = elementInferenceResults.typeOfIteratorCurrent(node);
pushInvokeDynamic(node, call, mask, [iterator],
sourceInformation: sourceInformationBuilder.buildForInCurrent(node));
buildAssignLoopVariable(node, pop());
visit(node.body);
}
loopHandler.handleLoop(
node,
closureDataLookup.getCapturedLoopScope(node),
elements.getTargetDefinition(node),
buildInitializer,
buildCondition,
() {},
buildBody,
sourceInformationBuilder.buildLoop(node));
}
buildAssignLoopVariable(ast.ForIn node, HInstruction value) {
ast.Node identifier = node.declaredIdentifier;
Element variable = elements.getForInVariable(node);
Selector selector = elements.getSelector(identifier);
if (identifier.asSend() != null &&
Elements.isInstanceSend(identifier, elements)) {
TypeMask mask = elementInferenceResults.typeOfSend(identifier);
HInstruction receiver = generateInstanceSendReceiver(identifier);
assert(receiver != null);
generateInstanceSetterWithCompiledReceiver(null, receiver, value,
selector: selector, mask: mask, location: identifier);
} else {
generateNonInstanceSetter(null, variable, value, location: identifier);
}
pop(); // Discard the value pushed by the setter call.
}
buildSyncForInIndexable(ast.ForIn node, TypeMask arrayType) {
// Generate a structure equivalent to:
//
// int end = a.length;
// for (int i = 0;
// i < a.length;
// checkConcurrentModificationError(a.length == end, a), ++i) {
// <declaredIdentifier> = a[i];
// <body>
// }
ExecutableElement loopVariable = elements.getForInVariable(node);
SyntheticLocal indexVariable =
new SyntheticLocal('_i', loopVariable, target);
TypeMask boolType = commonMasks.boolType;
// These variables are shared by initializer, condition, body and update.
HInstruction array; // Set in buildInitializer.
bool isFixed; // Set in buildInitializer.
HInstruction originalLength = null; // Set for growable lists.
HInstruction buildGetLength() {
HInstruction result = new HGetLength(array, commonMasks.positiveIntType,
isAssignable: !isFixed);
add(result);
return result;
}
void buildConcurrentModificationErrorCheck() {
if (originalLength == null) return;
// The static call checkConcurrentModificationError() is expanded in
// codegen to:
//
// array.length == _end || throwConcurrentModificationError(array)
//
HInstruction length = buildGetLength();
push(new HIdentity(length, originalLength, null, boolType));
pushInvokeStatic(node, commonElements.checkConcurrentModificationError,
[pop(), array]);
pop();
}
void buildInitializer() {
visit(node.expression);
array = pop();
isFixed = isFixedLength(array.instructionType, closedWorld);
localsHandler.updateLocal(
indexVariable, graph.addConstantInt(0, closedWorld));
originalLength = buildGetLength();
}
HInstruction buildCondition() {
HInstruction index = localsHandler.readLocal(indexVariable);
HInstruction length = buildGetLength();
HInstruction compare = new HLess(index, length, null, boolType);
add(compare);
return compare;
}
void buildBody() {
// If we had mechanically inlined ArrayIterator.moveNext(), it would have
// inserted the ConcurrentModificationError check as part of the
// condition. It is not necessary on the first iteration since there is
// no code between calls to `get iterator` and `moveNext`, so the test is
// moved to the loop update.
// Find a type for the element. Use the element type of the indexer of the
// array, as this is stronger than the iterator's `get current` type, for
// example, `get current` includes null.
// TODO(sra): The element type of a container type mask might be better.
Selector selector = new Selector.index();
TypeMask type = TypeMaskFactory.inferredTypeForSelector(
selector, arrayType, globalInferenceResults);
HInstruction index = localsHandler.readLocal(indexVariable);
HInstruction value = new HIndex(array, index, null, type);
add(value);
buildAssignLoopVariable(node, value);
visit(node.body);
}
void buildUpdate() {
// See buildBody as to why we check here.
buildConcurrentModificationErrorCheck();
// TODO(sra): It would be slightly shorter to generate `a[i++]` in the
// body (and that more closely follows what an inlined iterator would do)
// but the code is horrible as `i+1` is carried around the loop in an
// additional variable.
HInstruction index = localsHandler.readLocal(indexVariable);
HInstruction one = graph.addConstantInt(1, closedWorld);
HInstruction addInstruction =
new HAdd(index, one, null, commonMasks.positiveIntType);
add(addInstruction);
localsHandler.updateLocal(indexVariable, addInstruction);
}
loopHandler.handleLoop(
node,
closureDataLookup.getCapturedLoopScope(node),
elements.getTargetDefinition(node),
buildInitializer,
buildCondition,
buildUpdate,
buildBody,
sourceInformationBuilder.buildLoop(node));
}
visitLabel(ast.Label node) {
reporter.internalError(node, 'SsaFromAstMixin.visitLabel.');
}
visitLabeledStatement(ast.LabeledStatement node) {
ast.Statement body = node.statement;
if (body is ast.Loop ||
body is ast.SwitchStatement ||
Elements.isUnusedLabel(node, elements)) {
// Loops and switches handle their own labels.
visit(body);
return;
}
JumpTarget targetElement = elements.getTargetDefinition(body);
LocalsHandler beforeLocals = new LocalsHandler.from(localsHandler);
assert(targetElement.isBreakTarget);
JumpHandler handler = new JumpHandler(this, targetElement);
// Introduce a new basic block.
HBasicBlock entryBlock = openNewBlock();
visit(body);
SubGraph bodyGraph = new SubGraph(entryBlock, lastOpenedBlock);
HBasicBlock joinBlock = graph.addNewBlock();
List<LocalsHandler> breakHandlers = <LocalsHandler>[];
handler.forEachBreak((HBreak breakInstruction, LocalsHandler locals) {
breakInstruction.block.addSuccessor(joinBlock);
breakHandlers.add(locals);
});
bool hasBreak = breakHandlers.length > 0;
if (!isAborted()) {
goto(current, joinBlock);
breakHandlers.add(localsHandler);
}
open(joinBlock);
localsHandler = beforeLocals.mergeMultiple(breakHandlers, joinBlock);
if (hasBreak) {
// There was at least one reachable break, so the label is needed.
entryBlock.setBlockFlow(
new HLabeledBlockInformation(
new HSubGraphBlockInformation(bodyGraph), handler.labels),
joinBlock);
}
handler.close();
}
visitLiteralMap(ast.LiteralMap node) {
if (node.isConst) {
stack.add(addConstant(node));
return;
}
List<HInstruction> listInputs = <HInstruction>[];
for (Link<ast.Node> link = node.entries.nodes;
!link.isEmpty;
link = link.tail) {
visit(link.head);
listInputs.add(pop());
listInputs.add(pop());
}
ConstructorElement listConstructor;
List<HInstruction> inputs = <HInstruction>[];
if (listInputs.isEmpty) {
listConstructor = commonElements.mapLiteralConstructorEmpty;
} else {
listConstructor = commonElements.mapLiteralConstructor;
HLiteralList keyValuePairs = buildLiteralList(listInputs);
add(keyValuePairs);
inputs.add(keyValuePairs);
}
assert(listConstructor.isFactoryConstructor);
ConstructorElement constructorElement = listConstructor;
listConstructor = constructorElement.effectiveTarget;
ResolutionInterfaceType type = elements.getType(node);
ResolutionDartType expectedType =
constructorElement.computeEffectiveTargetType(type);
expectedType = localsHandler.substInContext(expectedType);
ClassElement cls = listConstructor.enclosingClass;
MethodElement createFunction = listConstructor;
if (expectedType is ResolutionInterfaceType && rtiNeed.classNeedsRti(cls)) {
List<HInstruction> typeInputs = <HInstruction>[];
expectedType.typeArguments.forEach((ResolutionDartType argument) {
typeInputs
.add(typeBuilder.analyzeTypeArgument(argument, sourceElement));
});
// We lift this common call pattern into a helper function to save space
// in the output.
if (typeInputs.every((HInstruction input) => input.isNull())) {
if (listInputs.isEmpty) {
createFunction = commonElements.mapLiteralUntypedEmptyMaker;
} else {
createFunction = commonElements.mapLiteralUntypedMaker;
}
} else {
inputs.addAll(typeInputs);
}
}
// If rti is needed and the map literal has no type parameters,
// 'constructor' is a static function that forwards the call to the factory
// constructor without type parameters.
assert(createFunction is ConstructorElement ||
createFunction is FunctionElement);
// The instruction type will always be a subtype of the mapLiteralClass, but
// type inference might discover a more specific type, or find nothing (in
// dart2js unit tests).
TypeMask mapType = new TypeMask.nonNullSubtype(
commonElements.mapLiteralClass, closedWorld);
TypeMask returnTypeMask = TypeMaskFactory.inferredReturnTypeForElement(
createFunction, globalInferenceResults);
TypeMask instructionType =
mapType.intersection(returnTypeMask, closedWorld);
addInlinedInstantiation(expectedType);
pushInvokeStatic(node, createFunction, inputs,
typeMask: instructionType, instanceType: expectedType);
removeInlinedInstantiation(expectedType);
}
visitLiteralMapEntry(ast.LiteralMapEntry node) {
visit(node.value);
visit(node.key);
}
visitNamedArgument(ast.NamedArgument node) {
visit(node.expression);
}
Map<ast.CaseMatch, ConstantValue> buildSwitchCaseConstants(
ast.SwitchStatement node) {
Map<ast.CaseMatch, ConstantValue> constants =
new Map<ast.CaseMatch, ConstantValue>();
for (ast.SwitchCase switchCase in node.cases) {
for (ast.Node labelOrCase in switchCase.labelsAndCases) {
if (labelOrCase is ast.CaseMatch) {
ast.CaseMatch match = labelOrCase;
ConstantValue constant = getConstantForNode(match.expression);
constants[labelOrCase] = constant;
}
}
}
return constants;
}
visitSwitchStatement(ast.SwitchStatement node) {
SourceInformation sourceInformation =
sourceInformationBuilder.buildSwitch(node);
Map<ast.CaseMatch, ConstantValue> constants =
buildSwitchCaseConstants(node);
// The switch case indices must match those computed in
// [SwitchCaseJumpHandler].
bool hasContinue = false;
Map<ast.SwitchCase, int> caseIndex = new Map<ast.SwitchCase, int>();
int switchIndex = 1;
bool hasDefault = false;
for (ast.SwitchCase switchCase in node.cases) {
for (ast.Node labelOrCase in switchCase.labelsAndCases) {
ast.Node label = labelOrCase.asLabel();
if (label != null) {
LabelDefinition labelElement = elements.getLabelDefinition(label);
if (labelElement != null && labelElement.isContinueTarget) {
hasContinue = true;
}
}
}
if (switchCase.isDefaultCase) {
hasDefault = true;
}
caseIndex[switchCase] = switchIndex;
switchIndex++;
}
if (!hasContinue) {
// If the switch statement has no switch cases targeted by continue
// statements we encode the switch statement directly.
buildSimpleSwitchStatement(node, constants, sourceInformation);
} else {
buildComplexSwitchStatement(
node, constants, caseIndex, hasDefault, sourceInformation);
}
}
/**
* Builds a simple switch statement which does not handle uses of continue
* statements to labeled switch cases.
*/
void buildSimpleSwitchStatement(
ast.SwitchStatement node,
Map<ast.CaseMatch, ConstantValue> constants,
SourceInformation sourceInformation) {
JumpHandler jumpHandler = createJumpHandler(
node, elements.getTargetDefinition(node),
isLoopJump: false);
HInstruction buildExpression() {
visit(node.expression);
return pop();
}
Iterable<ConstantValue> getConstants(ast.SwitchCase switchCase) {
List<ConstantValue> constantList = <ConstantValue>[];
for (ast.Node labelOrCase in switchCase.labelsAndCases) {
if (labelOrCase is ast.CaseMatch) {
constantList.add(constants[labelOrCase]);
}
}
return constantList;
}
bool isDefaultCase(ast.SwitchCase switchCase) {
return switchCase.isDefaultCase;
}
void buildSwitchCase(ast.SwitchCase node) {
visit(node.statements);
}
handleSwitch(node, jumpHandler, buildExpression, node.cases, getConstants,
isDefaultCase, buildSwitchCase, sourceInformation);
jumpHandler.close();
}
/**
* Builds a switch statement that can handle arbitrary uses of continue
* statements to labeled switch cases.
*/
void buildComplexSwitchStatement(
ast.SwitchStatement node,
Map<ast.CaseMatch, ConstantValue> constants,
Map<ast.SwitchCase, int> caseIndex,
bool hasDefault,
SourceInformation sourceInformation) {
// If the switch statement has switch cases targeted by continue
// statements we create the following encoding:
//
// switch (e) {
// l_1: case e0: s_1; break;
// l_2: case e1: s_2; continue l_i;
// ...
// l_n: default: s_n; continue l_j;
// }
//
// is encoded as
//
// var target;
// switch (e) {
// case e1: target = 1; break;
// case e2: target = 2; break;
// ...
// default: target = n; break;
// }
// l: while (true) {
// switch (target) {
// case 1: s_1; break l;
// case 2: s_2; target = i; continue l;
// ...
// case n: s_n; target = j; continue l;
// }
// }
JumpTarget switchTarget = elements.getTargetDefinition(node);
HInstruction initialValue = graph.addConstantNull(closedWorld);
localsHandler.updateLocal(switchTarget, initialValue);
JumpHandler jumpHandler =
createJumpHandler(node, switchTarget, isLoopJump: false);
dynamic switchCases = node.cases;
if (!hasDefault) {
// Use [:null:] as the marker for a synthetic default clause.
// The synthetic default is added because otherwise, there would be no
// good place to give a default value to the local.
switchCases = node.cases.nodes.toList()..add(null);
}
HInstruction buildExpression() {
visit(node.expression);
return pop();
}
Iterable<ConstantValue> getConstants(ast.SwitchCase switchCase) {
List<ConstantValue> constantList = <ConstantValue>[];
if (switchCase != null) {
for (ast.Node labelOrCase in switchCase.labelsAndCases) {
if (labelOrCase is ast.CaseMatch) {
constantList.add(constants[labelOrCase]);
}
}
}
return constantList;
}
bool isDefaultCase(ast.SwitchCase switchCase) {
return switchCase == null || switchCase.isDefaultCase;
}
void buildSwitchCase(ast.SwitchCase switchCase) {
SourceInformation caseSourceInformation = sourceInformation;
if (switchCase != null) {
caseSourceInformation = sourceInformationBuilder.buildGoto(switchCase);
// Generate 'target = i; break;' for switch case i.
int index = caseIndex[switchCase];
HInstruction value = graph.addConstantInt(index, closedWorld);
localsHandler.updateLocal(switchTarget, value,
sourceInformation: caseSourceInformation);
} else {
// Generate synthetic default case 'target = null; break;'.
HInstruction value = graph.addConstantNull(closedWorld);
localsHandler.updateLocal(switchTarget, value,
sourceInformation: caseSourceInformation);
}
jumpTargets[switchTarget].generateBreak(caseSourceInformation);
}
handleSwitch(node, jumpHandler, buildExpression, switchCases, getConstants,
isDefaultCase, buildSwitchCase, sourceInformation);
jumpHandler.close();
HInstruction buildCondition() => graph.addConstantBool(true, closedWorld);
void buildSwitch() {
HInstruction buildExpression() {
return localsHandler.readLocal(switchTarget);
}
Iterable<ConstantValue> getConstants(ast.SwitchCase switchCase) {
return <ConstantValue>[constantSystem.createInt(caseIndex[switchCase])];
}
void buildSwitchCase(ast.SwitchCase switchCase) {
visit(switchCase.statements);
if (!isAborted()) {
// Ensure that we break the loop if the case falls through. (This
// is only possible for the last case.)
jumpTargets[switchTarget].generateBreak(sourceInformation);
}
}
// Pass a [NullJumpHandler] because the target for the contained break
// is not the generated switch statement but instead the loop generated
// in the call to [handleLoop] below.
handleSwitch(
node,
new NullJumpHandler(reporter),
buildExpression,
node.cases,
getConstants,
(_) => false, // No case is default.
buildSwitchCase,
sourceInformation);
}
void buildLoop() {
loopHandler.handleLoop(
node,
closureDataLookup.getCapturedLoopScope(node),
switchTarget,
() {},
buildCondition,
() {},
buildSwitch,
sourceInformationBuilder.buildLoop(node));
}
if (hasDefault) {
buildLoop();
} else {
// If the switch statement has no default case, surround the loop with
// a test of the target.
void buildCondition() {
js.Template code = js.js.parseForeignJS('#');
push(new HForeignCode(
code, commonMasks.boolType, [localsHandler.readLocal(switchTarget)],
nativeBehavior: native.NativeBehavior.PURE));
}
handleIf(
node: node,
visitCondition: buildCondition,
visitThen: buildLoop,
visitElse: () => {},
sourceInformation: sourceInformation);
}
}
/**
* Creates a switch statement.
*
* [jumpHandler] is the [JumpHandler] for the created switch statement.
* [buildExpression] creates the switch expression.
* [switchCases] must be either an [Iterable] of [ast.SwitchCase] nodes or
* a [Link] or a [ast.NodeList] of [ast.SwitchCase] nodes.
* [getConstants] returns the set of constants for a switch case.
* [isDefaultCase] returns true if the provided switch case should be
* considered default for the created switch statement.
* [buildSwitchCase] creates the statements for the switch case.
*/
void handleSwitch(
ast.Node errorNode,
JumpHandler jumpHandler,
HInstruction buildExpression(),
var switchCases,
Iterable<ConstantValue> getConstants(ast.SwitchCase switchCase),
bool isDefaultCase(ast.SwitchCase switchCase),
void buildSwitchCase(ast.SwitchCase switchCase),
SourceInformation sourceInformation) {
HBasicBlock expressionStart = openNewBlock();
HInstruction expression = buildExpression();
if (switchCases.isEmpty) {
return;
}
HSwitch switchInstruction = new HSwitch(<HInstruction>[expression]);
HBasicBlock expressionEnd = close(switchInstruction);
LocalsHandler savedLocals = localsHandler;
List<HStatementInformation> statements = <HStatementInformation>[];
bool hasDefault = false;
HasNextIterator<ast.Node> caseIterator =
new HasNextIterator<ast.Node>(switchCases.iterator);
while (caseIterator.hasNext) {
ast.SwitchCase switchCase = caseIterator.next();
HBasicBlock block = graph.addNewBlock();
for (ConstantValue constant in getConstants(switchCase)) {
HConstant hConstant = graph.addConstant(constant, closedWorld);
switchInstruction.inputs.add(hConstant);
hConstant.usedBy.add(switchInstruction);
expressionEnd.addSuccessor(block);
}
if (isDefaultCase(switchCase)) {
// An HSwitch has n inputs and n+1 successors, the last being the
// default case.
expressionEnd.addSuccessor(block);
hasDefault = true;
}
open(block);
localsHandler = new LocalsHandler.from(savedLocals);
buildSwitchCase(switchCase);
if (!isAborted()) {
if (caseIterator.hasNext && isReachable) {
pushInvokeStatic(switchCase, commonElements.fallThroughError, []);
HInstruction error = pop();
closeAndGotoExit(new HThrow(error, error.sourceInformation));
} else if (!isDefaultCase(switchCase)) {
// If there is no default, we will add one later to avoid
// the critical edge. So we generate a break statement to make
// sure the last case does not fall through to the default case.
jumpHandler.generateBreak(sourceInformation);
}
}
statements.add(
new HSubGraphBlockInformation(new SubGraph(block, lastOpenedBlock)));
}
// Add a join-block if necessary.
// We create [joinBlock] early, and then go through the cases that might
// want to jump to it. In each case, if we add [joinBlock] as a successor
// of another block, we also add an element to [caseHandlers] that is used
// to create the phis in [joinBlock].
// If we never jump to the join block, [caseHandlers] will stay empty, and
// the join block is never added to the graph.
HBasicBlock joinBlock = new HBasicBlock();
List<LocalsHandler> caseHandlers = <LocalsHandler>[];
jumpHandler.forEachBreak((HBreak instruction, LocalsHandler locals) {
instruction.block.addSuccessor(joinBlock);
caseHandlers.add(locals);
});
jumpHandler.forEachContinue((HContinue instruction, LocalsHandler locals) {
assert(false, failedAt(errorNode, 'Continue cannot target a switch.'));
});
if (!isAborted()) {
current.close(new HGoto());
lastOpenedBlock.addSuccessor(joinBlock);
caseHandlers.add(localsHandler);
}
if (!hasDefault) {
// Always create a default case, to avoid a critical edge in the
// graph.
HBasicBlock defaultCase = addNewBlock();
expressionEnd.addSuccessor(defaultCase);
open(defaultCase);
close(new HGoto());
defaultCase.addSuccessor(joinBlock);
caseHandlers.add(savedLocals);
statements.add(new HSubGraphBlockInformation(
new SubGraph(defaultCase, defaultCase)));
}
assert(caseHandlers.length == joinBlock.predecessors.length);
if (caseHandlers.length != 0) {
graph.addBlock(joinBlock);
open(joinBlock);
if (caseHandlers.length == 1) {
localsHandler = caseHandlers[0];
} else {
localsHandler = savedLocals.mergeMultiple(caseHandlers, joinBlock);
}
} else {
// The joinblock is not used.
joinBlock = null;
}
HSubExpressionBlockInformation expressionInfo =
new HSubExpressionBlockInformation(
new SubExpression(expressionStart, expressionEnd));
expressionStart.setBlockFlow(
new HSwitchBlockInformation(expressionInfo, statements,
jumpHandler.target, jumpHandler.labels, sourceInformation),
joinBlock);
jumpHandler.close();
}
visitSwitchCase(ast.SwitchCase node) {
reporter.internalError(node, 'SsaFromAstMixin.visitSwitchCase.');
}
visitCaseMatch(ast.CaseMatch node) {
reporter.internalError(node, 'SsaFromAstMixin.visitCaseMatch.');
}
/// Calls [buildTry] inside a synthetic try block with [buildFinally] in the
/// finally block.
///
/// Note that to get the right locals behavior, the code visited by [buildTry]
/// and [buildFinally] must have been analyzed as if inside a try-statement by
/// [ClosureTranslator].
void buildProtectedByFinally(void buildTry(), void buildFinally()) {
// Save the current locals. The finally block must not reuse the existing
// locals handler. None of the variables that have been defined in the
// body-block will be used, but for loops we will add (unnecessary) phis
// that will reference the body variables. This makes it look as if the
// variables were used in a non-dominated block.
LocalsHandler savedLocals = new LocalsHandler.from(localsHandler);
HBasicBlock enterBlock = openNewBlock();
HTry tryInstruction = new HTry();
close(tryInstruction);
bool oldInTryStatement = inTryStatement;
inTryStatement = true;
HBasicBlock startTryBlock;
HBasicBlock endTryBlock;
HBasicBlock startFinallyBlock;
HBasicBlock endFinallyBlock;
startTryBlock = graph.addNewBlock();
open(startTryBlock);
buildTry();
// We use a [HExitTry] instead of a [HGoto] for the try block
// because it will have two successors: the join block, and
// the finally block.
if (!isAborted()) endTryBlock = close(new HExitTry());
SubGraph bodyGraph = new SubGraph(startTryBlock, lastOpenedBlock);
SubGraph finallyGraph = null;
localsHandler = new LocalsHandler.from(savedLocals);
startFinallyBlock = graph.addNewBlock();
open(startFinallyBlock);
buildFinally();
if (!isAborted()) endFinallyBlock = close(new HGoto());
tryInstruction.finallyBlock = startFinallyBlock;
finallyGraph = new SubGraph(startFinallyBlock, lastOpenedBlock);
HBasicBlock exitBlock = graph.addNewBlock();
void addExitTrySuccessor(HBasicBlock successor) {
// Iterate over all blocks created inside this try/catch, and
// attach successor information to blocks that end with
// [HExitTry].
for (int i = startTryBlock.id; i < successor.id; i++) {
HBasicBlock block = graph.blocks[i];
var last = block.last;
if (last is HExitTry) {
block.addSuccessor(successor);
}
}
}
// Setup all successors. The entry block that contains the [HTry]
// has 1) the body 2) the finally, and 4) the exit
// blocks as successors.
enterBlock.addSuccessor(startTryBlock);
enterBlock.addSuccessor(startFinallyBlock);
enterBlock.addSuccessor(exitBlock);
// The body has the finally block as successor.
if (endTryBlock != null) {
endTryBlock.addSuccessor(startFinallyBlock);
endTryBlock.addSuccessor(exitBlock);
}
// The finally block has the exit block as successor.
endFinallyBlock.addSuccessor(exitBlock);
// If a block inside try/catch aborts (eg with a return statement),
// we explicitly mark this block a predecessor of the catch
// block and the finally block.
addExitTrySuccessor(startFinallyBlock);
// Use the locals handler not altered by the catch and finally
// blocks.
// TODO(sigurdm): We can probably do this, because try-variables are boxed.
// Need to verify.
localsHandler = savedLocals;
open(exitBlock);
enterBlock.setBlockFlow(
new HTryBlockInformation(
wrapStatementGraph(bodyGraph),
null, // No catch-variable.
null, // No catchGraph.
wrapStatementGraph(finallyGraph)),
exitBlock);
inTryStatement = oldInTryStatement;
}
visitTryStatement(ast.TryStatement node) {
// Save the current locals. The catch block and the finally block
// must not reuse the existing locals handler. None of the variables
// that have been defined in the body-block will be used, but for
// loops we will add (unnecessary) phis that will reference the body
// variables. This makes it look as if the variables were used
// in a non-dominated block.
LocalsHandler savedLocals = new LocalsHandler.from(localsHandler);
HBasicBlock enterBlock = openNewBlock();
HTry tryInstruction = new HTry();
close(tryInstruction);
bool oldInTryStatement = inTryStatement;
inTryStatement = true;
HBasicBlock startTryBlock;
HBasicBlock endTryBlock;
HBasicBlock startCatchBlock;
HBasicBlock endCatchBlock;
HBasicBlock startFinallyBlock;
HBasicBlock endFinallyBlock;
startTryBlock = graph.addNewBlock();
open(startTryBlock);
visit(node.tryBlock);
// We use a [HExitTry] instead of a [HGoto] for the try block
// because it will have multiple successors: the join block, and
// the catch or finally block.
if (!isAborted()) endTryBlock = close(new HExitTry());
SubGraph bodyGraph = new SubGraph(startTryBlock, lastOpenedBlock);
SubGraph catchGraph = null;
HLocalValue exception = null;
if (!node.catchBlocks.isEmpty) {
localsHandler = new LocalsHandler.from(savedLocals);
startCatchBlock = graph.addNewBlock();
open(startCatchBlock);
// Note that the name of this local is irrelevant.
SyntheticLocal local = localsHandler.createLocal('exception');
SourceInformation trySourceInformation =
sourceInformationBuilder.buildTry(node);
exception = new HLocalValue(local, commonMasks.nonNullType)
..sourceInformation = trySourceInformation;
add(exception);
HInstruction oldRethrowableException = rethrowableException;
rethrowableException = exception;
pushInvokeStatic(node, commonElements.exceptionUnwrapper, [exception],
sourceInformation: trySourceInformation);
HInvokeStatic unwrappedException = pop();
tryInstruction.exception = exception;
Link<ast.Node> link = node.catchBlocks.nodes;
void pushCondition(ast.CatchBlock catchBlock) {
if (catchBlock.onKeyword != null) {
ResolutionDartType type = elements.getType(catchBlock.type);
if (type == null) {
reporter.internalError(catchBlock.type, 'On with no type.');
}
HInstruction condition = buildIsNode(
catchBlock.type,
type,
unwrappedException,
sourceInformationBuilder.buildCatch(catchBlock.type));
push(condition);
} else {
ast.VariableDefinitions declaration = catchBlock.formals.nodes.head;
HInstruction condition = null;
if (declaration.type == null) {
condition = graph.addConstantBool(true, closedWorld);
stack.add(condition);
} else {
// TODO(aprelev@gmail.com): Once old catch syntax is removed
// "if" condition above and this "else" branch should be deleted as
// type of declared variable won't matter for the catch
// condition.
ResolutionDartType type = elements.getType(declaration.type);
if (type == null) {
reporter.internalError(catchBlock, 'Catch with unresolved type.');
}
condition = buildIsNode(declaration.type, type, unwrappedException,
sourceInformationBuilder.buildCatch(declaration));
push(condition);
}
}
}
void visitThen() {
ast.CatchBlock catchBlock = link.head;
link = link.tail;
if (catchBlock.exception != null) {
LocalVariableElement exceptionVariable =
elements[catchBlock.exception];
localsHandler.updateLocal(exceptionVariable, unwrappedException,
sourceInformation:
sourceInformationBuilder.buildCatch(catchBlock.exception));
}
ast.Node trace = catchBlock.trace;
if (trace != null) {
pushInvokeStatic(
trace, commonElements.traceFromException, [exception]);
HInstruction traceInstruction = pop();
LocalVariableElement traceVariable = elements[trace];
localsHandler.updateLocal(traceVariable, traceInstruction);
}
visit(catchBlock);
}
void visitElse() {
if (link.isEmpty) {
closeAndGotoExit(new HThrow(exception, exception.sourceInformation,
isRethrow: true));
} else {
ast.CatchBlock newBlock = link.head;
handleIf(
node: node,
visitCondition: () {
pushCondition(newBlock);
},
visitThen: visitThen,
visitElse: visitElse,
sourceInformation: sourceInformationBuilder.buildCatch(newBlock));
}
}
ast.CatchBlock firstBlock = link.head;
handleIf(
node: node,
visitCondition: () {
pushCondition(firstBlock);
},
visitThen: visitThen,
visitElse: visitElse,
sourceInformation: sourceInformationBuilder.buildCatch(firstBlock));
if (!isAborted()) endCatchBlock = close(new HGoto());
rethrowableException = oldRethrowableException;
tryInstruction.catchBlock = startCatchBlock;
catchGraph = new SubGraph(startCatchBlock, lastOpenedBlock);
}
SubGraph finallyGraph = null;
if (node.finallyBlock != null) {
localsHandler = new LocalsHandler.from(savedLocals);
startFinallyBlock = graph.addNewBlock();
open(startFinallyBlock);
visit(node.finallyBlock);
if (!isAborted()) endFinallyBlock = close(new HGoto());
tryInstruction.finallyBlock = startFinallyBlock;
finallyGraph = new SubGraph(startFinallyBlock, lastOpenedBlock);
}
HBasicBlock exitBlock = graph.addNewBlock();
addOptionalSuccessor(b1, b2) {
if (b2 != null) b1.addSuccessor(b2);
}
addExitTrySuccessor(successor) {
if (successor == null) return;
// Iterate over all blocks created inside this try/catch, and
// attach successor information to blocks that end with
// [HExitTry].
for (int i = startTryBlock.id; i < successor.id; i++) {
HBasicBlock block = graph.blocks[i];
var last = block.last;
if (last is HExitTry) {
block.addSuccessor(successor);
}
}
}
// Setup all successors. The entry block that contains the [HTry]
// has 1) the body, 2) the catch, 3) the finally, and 4) the exit
// blocks as successors.
enterBlock.addSuccessor(startTryBlock);
addOptionalSuccessor(enterBlock, startCatchBlock);
addOptionalSuccessor(enterBlock, startFinallyBlock);
enterBlock.addSuccessor(exitBlock);
// The body has either the catch or the finally block as successor.
if (endTryBlock != null) {
assert(startCatchBlock != null || startFinallyBlock != null);
endTryBlock.addSuccessor(
startCatchBlock != null ? startCatchBlock : startFinallyBlock);
endTryBlock.addSuccessor(exitBlock);
}
// The catch block has either the finally or the exit block as
// successor.
if (endCatchBlock != null) {
endCatchBlock.addSuccessor(
startFinallyBlock != null ? startFinallyBlock : exitBlock);
}
// The finally block has the exit block as successor.
if (endFinallyBlock != null) {
endFinallyBlock.addSuccessor(exitBlock);
}
// If a block inside try/catch aborts (eg with a return statement),
// we explicitly mark this block a predecessor of the catch
// block and the finally block.
addExitTrySuccessor(startCatchBlock);
addExitTrySuccessor(startFinallyBlock);
// Use the locals handler not altered by the catch and finally
// blocks.
localsHandler = savedLocals;
open(exitBlock);
enterBlock.setBlockFlow(
new HTryBlockInformation(wrapStatementGraph(bodyGraph), exception,
wrapStatementGraph(catchGraph), wrapStatementGraph(finallyGraph)),
exitBlock);
inTryStatement = oldInTryStatement;
}
visitCatchBlock(ast.CatchBlock node) {
visit(node.block);
}
visitTypedef(ast.Typedef node) {
failedAt(node, 'SsaFromAstMixin.visitTypedef not implemented.');
}
visitTypeVariable(ast.TypeVariable node) {
failedAt(node, 'SsaFromAstMixin.visitTypeVariable not implemented.');
}
/**
* This method is invoked before inlining the body of [function] into this
* [SsaBuilder].
*/
void enterInlinedMethod(
MethodElement function,
ResolvedAst functionResolvedAst,
List<HInstruction> compiledArguments,
ResolutionInterfaceType instanceType) {
AstInliningState state = new AstInliningState(
function,
returnLocal,
returnType,
resolvedAst,
stack,
localsHandler,
inTryStatement,
isCalledOnce(functionResolvedAst.element),
elementInferenceResults);
resolvedAst = functionResolvedAst;
elementInferenceResults = _resultOf(functionResolvedAst.element);
inliningStack.add(state);
// Setting up the state of the (AST) builder is performed even when the
// inlined function is in IR, because the irInliner uses the [returnElement]
// of the AST builder.
setupStateForInlining(function, compiledArguments,
instanceType: instanceType);
}
void leaveInlinedMethod() {
HInstruction result = localsHandler.readLocal(returnLocal);
AstInliningState state = inliningStack.removeLast();
restoreState(state);
stack.add(result);
}
void doInline(ResolvedAst resolvedAst) {
visitInlinedFunction(resolvedAst);
}
void emitReturn(HInstruction value, ast.Node node) {
if (inliningStack.isEmpty) {
closeAndGotoExit(
new HReturn(value, sourceInformationBuilder.buildReturn(node)));
} else {
localsHandler.updateLocal(returnLocal, value);
}
}
@override
void handleTypeLiteralConstantCompounds(
ast.SendSet node, ConstantExpression constant, CompoundRhs rhs, _) {
handleTypeLiteralCompound(node);
}
@override
void handleTypeVariableTypeLiteralCompounds(
ast.SendSet node, TypeVariableElement typeVariable, CompoundRhs rhs, _) {
handleTypeLiteralCompound(node);
}
void handleTypeLiteralCompound(ast.SendSet node) {
generateIsDeferredLoadedCheckOfSend(node);
ast.Identifier selector = node.selector;
generateThrowNoSuchMethod(node, selector.source,
argumentNodes: node.arguments);
}
@override
void visitConstantGet(ast.Send node, ConstantExpression constant, _) {
visitNode(node);
}
@override
void visitConstantInvoke(ast.Send node, ConstantExpression constant,
ast.NodeList arguments, CallStructure callStreucture, _) {
visitNode(node);
}
@override
void errorUndefinedBinaryExpression(
ast.Send node, ast.Node left, ast.Operator operator, ast.Node right, _) {
visitNode(node);
}
@override
void errorUndefinedUnaryExpression(
ast.Send node, ast.Operator operator, ast.Node expression, _) {
visitNode(node);
}
@override
void bulkHandleError(ast.Node node, ErroneousElement error, _) {
// TODO(johnniwinther): Use an uncatchable error when supported.
generateRuntimeError(node, error.message);
}
}
/**
* Visitor that handles generation of string literals (LiteralString,
* StringInterpolation), and otherwise delegates to the given visitor for
* non-literal subexpressions.
*/
class StringBuilderVisitor extends ast.Visitor {
final SsaAstGraphBuilder builder;
final ast.Node diagnosticNode;
/**
* The string value generated so far.
*/
HInstruction result = null;
StringBuilderVisitor(this.builder, this.diagnosticNode);
void visit(ast.Node node) {
node.accept(this);
}
visitNode(ast.Node node) {
builder.reporter.internalError(node, 'Unexpected node.');
}
void visitExpression(ast.Node node) {
node.accept(builder);
HInstruction expression = builder.pop();
// We want to use HStringify when:
// 1. The value is known to be a primitive type, because it might get
// constant-folded and codegen has some tricks with JavaScript
// conversions.
// 2. The value can be primitive, because the library stringifier has
// fast-path code for most primitives.
if (expression.canBePrimitive(builder.closedWorld)) {
append(stringify(node, expression));
return;
}
// If the `toString` method is guaranteed to return a string we can call it
// directly.
Selector selector = Selectors.toString_;
TypeMask type = TypeMaskFactory.inferredTypeForSelector(
selector, expression.instructionType, builder.globalInferenceResults);
if (type.containsOnlyString(builder.closedWorld)) {
builder.pushInvokeDynamic(node, selector, expression.instructionType,
<HInstruction>[expression]);
append(builder.pop());
return;
}
append(stringify(node, expression));
}
void visitStringInterpolation(ast.StringInterpolation node) {
node.visitChildren(this);
}
void visitStringInterpolationPart(ast.StringInterpolationPart node) {
visit(node.expression);
visit(node.string);
}
void visitStringJuxtaposition(ast.StringJuxtaposition node) {
node.visitChildren(this);
}
void visitNodeList(ast.NodeList node) {
node.visitChildren(this);
}
void append(HInstruction expression) {
result = (result == null) ? expression : concat(result, expression);
}
HInstruction concat(HInstruction left, HInstruction right) {
HInstruction instruction =
new HStringConcat(left, right, builder.commonMasks.stringType);
builder.add(instruction);
return instruction;
}
HInstruction stringify(ast.Node node, HInstruction expression) {
HInstruction instruction =
new HStringify(expression, builder.commonMasks.stringType)
..sourceInformation = expression.sourceInformation;
builder.add(instruction);
return instruction;
}
}
/**
* This class visits the method that is a candidate for inlining and
* finds whether it is too difficult to inline.
*/
// TODO(karlklose): refactor to make it possible to distinguish between
// implementation restrictions (for example, we *can't* inline multiple returns)
// and heuristics (we *shouldn't* inline large functions).
class InlineWeeder extends ast.Visitor {
// Invariant: *INSIDE_LOOP* > *OUTSIDE_LOOP*
static const INLINING_NODES_OUTSIDE_LOOP = 18;
static const INLINING_NODES_OUTSIDE_LOOP_ARG_FACTOR = 3;
static const INLINING_NODES_INSIDE_LOOP = 42;
static const INLINING_NODES_INSIDE_LOOP_ARG_FACTOR = 4;
bool seenReturn = false;
String tooDifficultReason;
int nodeCount = 0;
final int maxInliningNodes; // `null` for unbounded.
final bool allowLoops;
final bool enableUserAssertions;
final TreeElements elements;
InlineWeeder._(this.elements, this.maxInliningNodes, this.allowLoops,
this.enableUserAssertions);
bool get tooDifficult => tooDifficultReason != null;
static bool canBeInlined(ResolvedAst resolvedAst, int maxInliningNodes,
{bool allowLoops: false, bool enableUserAssertions: null}) {
return cannotBeInlinedReason(resolvedAst, maxInliningNodes,
allowLoops: allowLoops,
enableUserAssertions: enableUserAssertions) ==
null;
}
static String cannotBeInlinedReason(
ResolvedAst resolvedAst, int maxInliningNodes,
{bool allowLoops: false, bool enableUserAssertions: null}) {
assert(enableUserAssertions is bool); // Ensure we passed it.
if (resolvedAst.elements.containsTryStatement) return 'try';
InlineWeeder weeder = new InlineWeeder._(resolvedAst.elements,
maxInliningNodes, allowLoops, enableUserAssertions);
ast.FunctionExpression functionExpression = resolvedAst.node;
weeder.visit(functionExpression.initializers);
weeder.visit(functionExpression.body);
weeder.visit(functionExpression.asyncModifier);
return weeder.tooDifficultReason;
}
bool registerNode() {
if (maxInliningNodes == null) return true;
if (nodeCount++ > maxInliningNodes) {
tooDifficultReason = 'too many nodes';
return false;
} else {
return true;
}
}
void visit(ast.Node node) {
if (node != null) node.accept(this);
}
void visitNode(ast.Node node) {
if (!registerNode()) return;
if (seenReturn) {
tooDifficultReason = 'code after return';
} else {
node.visitChildren(this);
}
}
@override
void visitAssert(ast.Assert node) {
if (enableUserAssertions) {
visitNode(node);
}
}
@override
void visitAsyncModifier(ast.AsyncModifier node) {
if (node.isYielding || node.isAsynchronous) {
tooDifficultReason = 'async/await';
}
}
void visitFunctionExpression(ast.Node node) {
if (!registerNode()) return;
tooDifficultReason = 'closure';
}
void visitFunctionDeclaration(ast.Node node) {
if (!registerNode()) return;
tooDifficultReason = 'closure';
}
void visitSend(ast.Send node) {
// TODO(sra): Investigate following, and possibly count occurrences, since
// repeated references might cause a temporary to be assigned.
//
// Element element = elements[node];
// if (element != null && element.isParameter) {
// // Don't count as additional node, since it's likely that passing
// // the argument would cost us as much space as we inline.
// return;
// }
if (!registerNode()) return;
node.visitChildren(this);
}
visitLoop(ast.Node node) {
// It's actually not difficult to inline a method with a loop, but
// our measurements show that it's currently better to not inline a
// method that contains a loop.
if (!allowLoops) {
tooDifficultReason = 'loop';
}
}
void visitRedirectingFactoryBody(ast.RedirectingFactoryBody node) {
if (!registerNode()) return;
tooDifficultReason = 'redirecting factory';
}
void visitConditional(ast.Conditional node) {
// Heuristic: In "parameter ? A : B" there is a high probability that
// parameter is a constant. Assuming the parameter is constant, we can
// compute a count that is bounded by the largest arm rather than the sum of
// both arms.
visit(node.condition);
if (tooDifficult) return;
int commonPrefixCount = nodeCount;
visit(node.thenExpression);
if (tooDifficult) return;
int thenCount = nodeCount - commonPrefixCount;
nodeCount = commonPrefixCount;
visit(node.elseExpression);
if (tooDifficult) return;
int elseCount = nodeCount - commonPrefixCount;
nodeCount = commonPrefixCount + thenCount + elseCount;
if (node.condition.asSend() != null &&
elements[node.condition]?.isParameter == true) {
nodeCount =
commonPrefixCount + (thenCount > elseCount ? thenCount : elseCount);
}
// This is last so that [tooDifficult] is always updated.
if (!registerNode()) return;
}
void visitRethrow(ast.Rethrow node) {
if (!registerNode()) return;
tooDifficultReason = 'rethrow';
}
void visitReturn(ast.Return node) {
if (!registerNode()) return;
if (seenReturn || identical(node.beginToken.stringValue, 'native')) {
tooDifficultReason = 'code after return';
return;
}
node.visitChildren(this);
seenReturn = true;
}
void visitThrow(ast.Throw node) {
if (!registerNode()) return;
// For now, we don't want to handle throw after a return even if
// it is in an "if".
if (seenReturn) {
tooDifficultReason = 'code after return';
} else {
node.visitChildren(this);
}
}
}
abstract class InliningState {
/**
* Invariant: [function] must be an implementation element.
*/
final MethodElement function;
InliningState(this.function) {
assert(function.isImplementation);
}
}
class AstInliningState extends InliningState {
final Local oldReturnLocal;
final ResolutionDartType oldReturnType;
final ResolvedAst oldResolvedAst;
final List<HInstruction> oldStack;
final LocalsHandler oldLocalsHandler;
final bool inTryStatement;
final bool allFunctionsCalledOnce;
final GlobalTypeInferenceElementResult oldElementInferenceResults;
AstInliningState(
MethodElement function,
this.oldReturnLocal,
this.oldReturnType,
this.oldResolvedAst,
this.oldStack,
this.oldLocalsHandler,
this.inTryStatement,
this.allFunctionsCalledOnce,
this.oldElementInferenceResults)
: super(function);
}
class AstTypeBuilder extends TypeBuilder {
AstTypeBuilder(GraphBuilder builder) : super(builder);
ClassTypeVariableAccess computeTypeVariableAccess(MemberEntity member) {
bool isClosure = member.enclosingClass.isClosure;
if (isClosure) {
ClosureClassElement closureClass = member.enclosingClass;
LocalFunctionElement localFunction = closureClass.methodElement;
member = localFunction.memberContext;
}
bool isInConstructorContext =
member.isConstructor || member is ConstructorBodyEntity;
if (isClosure) {
if ((member is ConstructorEntity && member.isFactoryConstructor) ||
(isInConstructorContext)) {
// The type variable is used from a closure in a factory constructor.
// The value of the type argument is stored as a local on the closure
// itself.
return ClassTypeVariableAccess.parameter;
} else if (member.isFunction ||
member.isGetter ||
member.isSetter ||
isInConstructorContext) {
// The type variable is stored on the "enclosing object" and needs to be
// accessed using the this-reference in the closure.
return ClassTypeVariableAccess.property;
} else {
assert(member.isField);
// The type variable is stored in a parameter of the method.
return ClassTypeVariableAccess.parameter;
}
} else if (isInConstructorContext) {
// The type variable is stored in a parameter of the method.
return ClassTypeVariableAccess.parameter;
} else if (member.isInstanceMember) {
if (member.isField) {
// The type variable is stored in a parameter or on `this` depending
// on the context.
return ClassTypeVariableAccess.instanceField;
} else {
// The type variable is stored on the object.
return ClassTypeVariableAccess.property;
}
} else {
return ClassTypeVariableAccess.none;
}
}
/// In checked mode, generate type tests for the parameters of the inlined
/// function.
void potentiallyCheckInlinedParameterTypes(covariant MethodElement function) {
if (!checkOrTrustTypes) return;
FunctionSignature signature = function.functionSignature;
signature.forEachParameter((_parameter) {
ParameterElement parameter = _parameter;
HInstruction argument = builder.localsHandler.readLocal(parameter);
potentiallyCheckOrTrustType(argument, parameter.type);
});
}
}