blob: a493c86f3a0576d2b548d8cd39290a9b6eff510f [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, CodegenWorkItem;
import '../common/names.dart' show Identifiers, Selectors;
import '../common/tasks.dart' show CompilerTask;
import '../compiler.dart' show Compiler;
import '../constants/constant_system.dart';
import '../constants/expressions.dart';
import '../constants/values.dart';
import '../common_elements.dart' show CommonElements;
import '../elements/resolution_types.dart';
import '../diagnostics/messages.dart' show Message, MessageTemplate;
import '../dump_info.dart' show InfoReporter;
import '../elements/elements.dart';
import '../elements/entities.dart';
import '../elements/modelx.dart' show ConstructorBodyElementX;
import '../io/source_information.dart';
import '../js/js.dart' as js;
import '../js_backend/backend_helpers.dart' show BackendHelpers;
import '../js_backend/js_backend.dart';
import '../js_emitter/js_emitter.dart' show CodeEmitterTask, NativeEmitter;
import '../native/native.dart' as native;
import '../resolution/operators.dart';
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_branch_builder.dart';
import 'type_builder.dart';
import 'types.dart';
class SsaBuilderTask extends CompilerTask {
final CodeEmitterTask emitter;
final JavaScriptBackend backend;
final SourceInformationStrategy sourceInformationFactory;
final Compiler compiler;
String get name => 'SSA builder';
SsaBuilderTask(JavaScriptBackend backend, this.sourceInformationFactory)
: emitter = backend.emitter,
backend = backend,
compiler = backend.compiler,
super(backend.compiler.measurer);
DiagnosticReporter get reporter => compiler.reporter;
HGraph build(CodegenWorkItem work, ClosedWorld closedWorld) {
return measure(() {
MemberElement element = work.element.implementation;
return reporter.withCurrentElement(element, () {
SsaBuilder builder = new SsaBuilder(
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((ParameterElement 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 SsaBuilder 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;
/// All results from the global type-inference analysis.
final GlobalTypeInferenceResults inferenceResults;
/// 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;
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
SsaBuilder(
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,
this.inferenceResults = backend.compiler.globalInference.results {
assert(target.isImplementation);
compiler = backend.compiler;
elementInferenceResults = _resultOf(target);
assert(elementInferenceResults != null);
graph.element = target;
sourceElementStack.add(target);
sourceInformationBuilder =
sourceInformationFactory.createBuilderForContext(resolvedAst);
graph.sourceInformation =
sourceInformationBuilder.buildVariableDeclaration();
localsHandler = new LocalsHandler(this, target, null, compiler);
loopHandler = new SsaLoopHandler(this);
typeBuilder = new TypeBuilder(this);
}
BackendHelpers get helpers => backend.helpers;
RuntimeTypesEncoder get rtiEncoder => backend.rtiEncoder;
DiagnosticReporter get reporter => compiler.reporter;
CommonElements get commonElements => closedWorld.commonElements;
Element 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(AstElement element) =>
inferenceResults.resultOf(
element is ConstructorBodyElementX ? element.constructor : element);
/// Build the graph for [target].
HGraph build() {
assert(invariant(target, target.isImplementation));
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(compiler.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(invariant(function, function.isImplementation));
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}) {
registry.registerStaticUse(new StaticUse.inlining(element));
if (backend.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;
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 =
backend.inlineCache.canInline(function, insideLoop: insideLoop);
if (cachedCanBeInlined == false) return false;
bool meetsHardConstraints() {
if (compiler.options.disableInlining) return false;
assert(invariant(
currentNode != null ? currentNode : function,
selector != null ||
Elements.isStaticOrTopLevel(function) ||
function.isGenerativeConstructorBody,
message: "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 (backend.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 &&
backend.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 (inferenceResults.resultOf(function).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: compiler.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(function)) {
return InlineWeeder.canBeInlined(functionResolvedAst, null,
enableUserAssertions: compiler.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 = compiler.deferredLoadTask
.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: compiler.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(function)) {
maxInliningNodes = null;
}
bool canInline = InlineWeeder.canBeInlined(
functionResolvedAst, maxInliningNodes,
enableUserAssertions: compiler.options.enableUserAssertions);
if (canInline) {
backend.inlineCache.markAsInlinable(function, insideLoop: insideLoop);
} else {
backend.inlineCache
.markAsNonInlinable(function, insideLoop: insideLoop);
}
return canInline;
}
void doInlining() {
// 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: instanceType);
inlinedFrom(functionResolvedAst, () {
if (!isReachable) {
emitReturn(graph.addConstantNull(closedWorld), null);
} else {
doInline(functionResolvedAst);
}
});
leaveInlinedMethod();
}
if (meetsHardConstraints() && heuristicSayGoodToGo()) {
doInlining();
infoReporter?.reportInlined(function,
inliningStack.isEmpty ? target : inliningStack.last.function);
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 ConstructorBodyElement) return false;
return inferenceResults.resultOf(element).isCalledOnce;
}
bool isCalledOnce(MethodElement element) {
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(resolvedAst);
sourceElementStack.add(element.declaration);
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 =
backend.constants.getConstantValue(parameter.constant);
assert(invariant(parameter, constantValue != null,
message: 'No constant computed for $parameter'));
return graph.addConstant(constantValue, closedWorld);
}
ClassElement get currentNonClosureClass {
ClassElement cls = sourceElement.enclosingClass;
if (cls != null && cls.isClosure) {
var closureClass = cls;
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 =
backend.constants.getConstantValueForNode(node, elements);
assert(invariant(node, constantValue != null,
message: '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(invariant(functionElement, functionElement.isImplementation));
graph.calledInLoop = closedWorld.isCalledInLoop(functionElement);
ast.FunctionExpression function = resolvedAst.node;
assert(function != null);
assert(elements.getFunctionDefinition(function) != null);
openFunction(functionElement, function);
String name = functionElement.name;
if (backend.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(invariant(functionElement, !function.modifiers.isExternal));
// 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 (!backend.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, helpers.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();
// 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(invariant(variable, resolvedAst.element == variable,
message: "Unexpected variable $variable for $resolvedAst."));
inLazyInitializerExpression = true;
ast.VariableDefinitions node = resolvedAst.node;
ast.Node initializer = resolvedAst.body;
assert(invariant(variable, initializer != null,
message: "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(
FunctionElement function, List<HInstruction> compiledArguments,
{ResolutionInterfaceType instanceType}) {
ResolvedAst resolvedAst = function.resolvedAst;
assert(resolvedAst != null);
localsHandler = new LocalsHandler(this, function, instanceType, compiler);
localsHandler.closureData =
compiler.closureToClassMapper.getClosureToClassMapping(resolvedAst);
returnLocal = new SyntheticLocal("result", function);
localsHandler.updateLocal(returnLocal, graph.addConstantNull(closedWorld));
inTryStatement = false; // TODO(lry): why? Document.
int argumentIndex = 0;
if (function.isInstanceMember) {
localsHandler.updateLocal(localsHandler.closureData.thisLocal,
compiledArguments[argumentIndex++]);
}
FunctionSignature signature = function.functionSignature;
signature.orderedForEachParameter((ParameterElement parameter) {
HInstruction argument = compiledArguments[argumentIndex++];
localsHandler.updateLocal(parameter, argument);
});
ClassElement enclosing = function.enclosingClass;
if ((function.isConstructor || function.isGenerativeConstructorBody) &&
backend.rtiNeed.classNeedsRti(enclosing)) {
enclosing.typeVariables
.forEach((ResolutionTypeVariableType 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) {
typeBuilder.potentiallyCheckInlinedParameterTypes(
resolvedAst.element.implementation);
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<Element, HInstruction> fieldValues,
FunctionElement caller) {
ConstructorElement callee = constructorResolvedAst.element.implementation;
reporter.withCurrentElement(callee, () {
constructorResolvedAsts.add(constructorResolvedAst);
ClassElement enclosingClass = callee.enclosingClass;
if (backend.rtiNeed.classNeedsRti(enclosingClass)) {
// If [enclosingClass] needs RTI, we have to give a value to its
// type parameters.
ClassElement currentClass = caller.enclosingClass;
// 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));
}
}
}
// For redirecting constructors, the fields will be initialized later
// by the effective target.
if (!callee.isRedirectingGenerative) {
inlinedFrom(constructorResolvedAst, () {
buildFieldInitializers(
callee.enclosingClass.implementation, fieldValues);
});
}
int index = 0;
FunctionSignature params = callee.functionSignature;
params.orderedForEachParameter((ParameterElement 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 = inferenceResults.resultOf(callee);
ClosureClassMap oldClosureData = localsHandler.closureData;
ClosureClassMap newClosureData =
compiler.closureToClassMapper.getClosureToClassMapping(resolvedAst);
localsHandler.closureData = newClosureData;
if (resolvedAst.kind == ResolvedAstKind.PARSED) {
localsHandler.enterScope(resolvedAst.node, callee);
}
buildInitializers(callee, constructorResolvedAsts, fieldValues);
localsHandler.closureData = oldClosureData;
resolvedAst = oldResolvedAst;
elementInferenceResults = oldElementInferenceResults;
});
}
void buildInitializers(
ConstructorElement constructor,
List<ResolvedAst> constructorResolvedAsts,
Map<Element, HInstruction> fieldValues) {
assert(invariant(
constructor, resolvedAst.element == constructor.declaration,
message: "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<Element, HInstruction> fieldValues) {
assert(invariant(constructor, constructor.isSynthesized,
message: "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<Element, HInstruction> fieldValues) {
assert(
invariant(constructor, resolvedAst.element == constructor.declaration));
assert(invariant(constructor, constructor.isImplementation));
assert(invariant(constructor, !constructor.isSynthesized,
message: "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(invariant(classElement, classElement.isImplementation));
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 (!backend.nativeData.isNativeOrExtendsNative(classElement)) {
fieldValues[member] = graph.addConstantNull(closedWorld);
}
} else {
ast.Node right = initializer;
ResolvedAst savedResolvedAst = resolvedAst;
resolvedAst = fieldResolvedAst;
final oldElementInferenceResults = elementInferenceResults;
elementInferenceResults = inferenceResults.resultOf(member);
// In case the field initializer uses closures, run the
// closure to class mapper.
compiler.closureToClassMapper.getClosureToClassMapping(resolvedAst);
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 =
backend.nativeData.isNativeOrExtendsNative(classElement) &&
!backend.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<Element, HInstruction> fieldValues = new Map<Element, 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((ParameterElement 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(invariant(
member, isNativeUpgradeFactory || compiler.compilationFailed));
} 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<ResolutionDartType> instantiatedTypes;
addInlinedInstantiation(type);
if (!currentInlinedInstantiations.isEmpty) {
instantiatedTypes =
new List<ResolutionDartType>.from(currentInlinedInstantiations);
}
HInstruction newObject;
if (!isNativeUpgradeFactory) {
// Create the runtime type information, if needed.
bool hasRtiInput = false;
if (backend.rtiNeed.classNeedsRtiField(classElement)) {
// 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((ResolutionTypeVariableType 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,
instantiatedTypes: instantiatedTypes, hasRtiInput: hasRtiInput);
if (function != null) {
// TODO(johnniwinther): Provide source information for creation through
// synthetic constructors.
newObject.sourceInformation =
sourceInformationBuilder.buildCreate(function);
}
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];
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);
ast.Node node = constructorResolvedAst.node;
ClosureClassMap parameterClosureData = compiler.closureToClassMapper
.getClosureToClassMapping(constructorResolvedAst);
FunctionSignature functionSignature = body.functionSignature;
// Provide the parameters to the generative constructor body.
functionSignature.orderedForEachParameter((ParameterElement 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.
ClosureScope scopeData = parameterClosureData.capturingScopes[node];
if (scopeData != null) {
bodyCallInputs.add(localsHandler.readLocal(scopeData.boxElement));
}
// Type variables arguments must come after the box (if there is one).
ConstructorElement constructor =
constructorResolvedAst.element.implementation;
ClassElement currentClass = constructor.enclosingClass;
if (backend.rtiNeed.classNeedsRti(currentClass)) {
// If [currentClass] needs RTI, we add the type variables as
// parameters of the generative constructor body.
currentClass.typeVariables
.forEach((ResolutionTypeVariableType 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 {
HInvokeConstructorBody invoke = new HInvokeConstructorBody(
body.declaration, bodyCallInputs, commonMasks.nonNullType);
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(invariant(element, element.isImplementation));
HBasicBlock block = graph.addNewBlock();
open(graph.entry);
localsHandler.startFunction(element, node);
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.
var enclosing = element.enclosingElement;
if ((element.isConstructor || element.isGenerativeConstructorBody) &&
backend.rtiNeed.classNeedsRti(enclosing)) {
enclosing.typeVariables
.forEach((ResolutionTypeVariableType 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.
ClosureScope scopeData = localsHandler.closureData.capturingScopes[node];
signature.orderedForEachParameter((ParameterElement parameterElement) {
if (element.isGenerativeConstructorBody) {
if (scopeData != null &&
scopeData.isCapturedVariable(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(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 == backend.helpers.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(backend.helpers.traceHelper,
<HInstruction>[nameConstant], commonMasks.dynamicType));
}
}
insertCoverageCall(Element element) {
if (JavaScriptBackend.TRACE_METHOD == 'post') {
if (element == backend.helpers.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(backend.helpers.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(
new ast.DartString.literal(message), closedWorld);
MethodElement element = helpers.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 = compiler.commonElements.boolType;
return typeBuilder.potentiallyCheckOrTrustType(value, boolType,
kind: HTypeConversion.BOOLEAN_CONVERSION_CHECK);
}
HInstruction result = new HBoolify(value, commonMasks.boolType);
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 (!compiler.options.enableUserAssertions) return;
if (!node.hasMessage) {
// Generate:
//
// assertHelper(condition);
//
visit(node.condition);
pushInvokeStatic(node, helpers.assertHelper, [pop()]);
pop();
return;
}
// Assert has message. Generate:
//
// if (assertTest(condition)) assertThrow(message);
//
void buildCondition() {
visit(node.condition);
pushInvokeStatic(node, helpers.assertTest, [pop()]);
}
void fail() {
visit(node.message);
pushInvokeStatic(node, helpers.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, buildInitializer, buildCondition, buildUpdate, buildBody);
}
visitWhile(ast.While node) {
assert(isReachable);
HInstruction buildCondition() {
visit(node.condition);
return popBoolified();
}
loopHandler.handleLoop(node, () {}, buildCondition, () {}, () {
visit(node.body);
});
}
visitDoWhile(ast.DoWhile node) {
assert(isReachable);
LocalsHandler savedLocals = new LocalsHandler.from(localsHandler);
localsHandler.startLoop(node);
loopDepth++;
JumpHandler jumpHandler = loopHandler.beginLoopHeader(node);
HLoopInformation loopInfo = current.loopInformation;
HBasicBlock loopEntryBlock = current;
HBasicBlock bodyEntryBlock = current;
JumpTarget target = elements.getTargetDefinition(node);
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(node);
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,
sourceInformationBuilder.buildLoop(node));
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');
label.setBreakTarget();
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));
block.remove(breakInstruction);
});
}
}
jumpHandler.close();
loopDepth--;
}
visitFunctionExpression(ast.FunctionExpression node) {
LocalFunctionElement methodElement = elements[node];
ClosureClassMap nestedClosureData = compiler.closureToClassMapper
.getClosureToClassMapping(methodElement.resolvedAst);
assert(nestedClosureData != null);
assert(nestedClosureData.closureClassElement != null);
ClosureClassElement closureClassElement =
nestedClosureData.closureClassElement;
MethodElement callElement = nestedClosureData.callElement;
List<HInstruction> capturedVariables = <HInstruction>[];
closureClassElement.closureFields.forEach((ClosureFieldElement field) {
Local capturedLocal =
nestedClosureData.getLocalVariableForClosureField(field);
assert(capturedLocal != null);
capturedVariables.add(localsHandler.readLocal(capturedLocal));
});
TypeMask type = new TypeMask.nonNullExact(closureClassElement, closedWorld);
push(new HCreate(closureClassElement, capturedVariables, type,
callMethod: callElement, localFunction: methodElement)
..sourceInformation = sourceInformationBuilder.buildCreate(node));
}
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());
}
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, compiler, 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, compiler, 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,
{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,
isAnd: isAnd),
branchBuilder,
isAnd: isAnd);
} else {
branchBuilder.handleLogicalBinary(() => visit(left), visitRight,
isAnd: isAnd);
}
}
@override
void visitLogicalAnd(ast.Send node, ast.Node left, ast.Node right, _) {
SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, compiler, node);
handleLogicalBinaryWithLeftNode(left, () => visit(right), branchBuilder,
isAnd: true);
}
@override
void visitLogicalOr(ast.Send node, ast.Node left, ast.Node right, _) {
SsaBranchBuilder branchBuilder = new SsaBranchBuilder(this, compiler, node);
handleLogicalBinaryWithLeftNode(left, () => visit(right), branchBuilder,
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();
}
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 [prefixElement].
/// If [prefixElement] is [null] ndo nothing.
void generateIsDeferredLoadedCheckIfNeeded(
PrefixElement prefixElement, ast.Node location) {
if (prefixElement == null) return;
String loadId =
compiler.deferredLoadTask.getImportDeferName(location, prefixElement);
HInstruction loadIdConstant = addConstantString(loadId);
String uri = prefixElement.deferredImport.uri.toString();
HInstruction uriConstant = addConstantString(uri);
MethodElement helper = helpers.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(
compiler.deferredLoadTask.deferredPrefixElement(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(invariant(node, element == null || element.isMalformed));
// 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 = backend.constants.getConstantValue(constant);
HConstant instruction;
// Constants that are referred via a deferred prefix should be referred
// by reference.
PrefixElement prefix =
compiler.deferredLoadTask.deferredPrefixElement(node, elements);
if (prefix != null) {
instruction = graph.addDeferredConstant(
value, prefix, 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.inferredTypeForElement(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, node);
}
/// Read a static or top level [field].
void generateStaticFieldGet(ast.Send node, FieldElement field) {
ConstantExpression constant = field.constant;
SourceInformation sourceInformation =
sourceInformationBuilder.buildGet(node);
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.inferredTypeForElement(
field, globalInferenceResults))
..sourceInformation = sourceInformation;
push(instruction);
}
} else {
HInstruction instruction = new HLazyStatic(field,
TypeMaskFactory.inferredTypeForElement(field, globalInferenceResults))
..sourceInformation = 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);
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);
push(new HStatic(method, commonMasks.nonNullType)
..sourceInformation = 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, compiler, 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(invariant(send == null ? location : send,
send == null || Elements.isInstanceSend(send, elements),
message: "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(invariant(
location, send == null || !Elements.isInstanceSend(send, elements),
message: "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;
}
HLiteralList buildLiteralList(List<HInstruction> inputs) {
return new HLiteralList(inputs, commonMasks.extendableArrayType);
}
@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);
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));
}
@override
void visitIsNot(
ast.Send node, ast.Node expression, ResolutionDartType type, _) {
HInstruction expressionInstruction = visitAndPop(expression);
HInstruction instruction = buildIsNode(node, type, expressionInstruction);
add(instruction);
push(new HNot(instruction, commonMasks.boolType));
}
HInstruction buildIsNode(
ast.Node node, ResolutionDartType type, HInstruction expression) {
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);
} else if (type.isFunctionType) {
HInstruction representation =
typeBuilder.analyzeTypeArgument(type, sourceElement);
List<HInstruction> inputs = <HInstruction>[
expression,
representation,
];
pushInvokeStatic(node, helpers.functionTypeTest, inputs,
typeMask: commonMasks.boolType);
HInstruction call = pop();
return new HIs.compound(type, expression, call, commonMasks.boolType);
} else if (type.isTypeVariable) {
HInstruction runtimeType =
typeBuilder.addTypeVariableReference(type, sourceElement);
MethodElement helper = helpers.checkSubtypeOfRuntimeType;
List<HInstruction> inputs = <HInstruction>[expression, runtimeType];
pushInvokeStatic(null, helper, inputs, typeMask: commonMasks.boolType);
HInstruction call = pop();
return new HIs.variable(type, expression, call, commonMasks.boolType);
} else if (RuntimeTypesSubstitutions.hasTypeArguments(type)) {
ClassElement element = type.element;
MethodElement helper = helpers.checkSubtype;
HInstruction representations =
typeBuilder.buildTypeArgumentRepresentations(type, sourceElement);
add(representations);
js.Name operator = backend.namer.operatorIs(element);
HInstruction isFieldName = addConstantStringFromName(operator);
HInstruction asFieldName = closedWorld.hasAnyStrictSubtype(element)
? addConstantStringFromName(backend.namer.substitutionName(element))
: graph.addConstantNull(closedWorld);
List<HInstruction> inputs = <HInstruction>[
expression,
isFieldName,
representations,
asFieldName
];
pushInvokeStatic(node, helper, inputs, typeMask: commonMasks.boolType);
HInstruction call = pop();
return new HIs.compound(type, expression, call, commonMasks.boolType);
} else {
if (backend.hasDirectCheckFor(type)) {
return new HIs.direct(type, expression, commonMasks.boolType);
}
// 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);
}
}
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(invariant(element, element.isDeclaration));
HInstruction compileArgument(ast.Node argument) {
visit(argument);
return pop();
}
return Elements.makeArgumentsList<HInstruction>(
callStructure,
arguments,
element.implementation,
compileArgument,
backend.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, compiler, 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(),
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(invariant(node, nativeBehavior != null,
message: "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);
SourceInformation sourceInformation =
sourceInformationBuilder.buildCall(node, node.argumentsNode);
if (nativeBehavior.codeTemplate.isExpression) {
push(new HForeignCode(nativeBehavior.codeTemplate, ssaType, inputs,
effects: nativeBehavior.sideEffects, nativeBehavior: nativeBehavior)
..sourceInformation = sourceInformation);
} else {
push(new HForeignCode(nativeBehavior.codeTemplate, ssaType, inputs,
isStatement: true,
effects: nativeBehavior.sideEffects,
nativeBehavior: nativeBehavior)
..sourceInformation = sourceInformation);
}
}
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 (!backend.backendUsage.isIsolateInUse) {
// If the isolate library is not used, we just generate code
// to fetch the static state.
String name = backend.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 = helpers.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 = false;
switch (name) {
case 'MUST_RETAIN_METADATA':
value = backend.mirrorsData.mustRetainMetadata;
break;
case 'USE_CONTENT_SECURITY_POLICY':
value = compiler.options.useContentSecurityPolicy;
break;
default:
reporter.reportErrorMessage(node, MessageKind.GENERIC,
{'text': 'Error: Unknown internal flag "$name".'});
}
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 != helpers.jsGetNameEnum) {
reporter.reportErrorMessage(argument, MessageKind.GENERIC,
{'text': 'Error: Expected a JsGetName enum value.'});
}
EnumConstantElement enumConstant = element;
int index = enumConstant.index;
stack.add(addConstantStringFromName(
backend.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 != helpers.jsBuiltinEnum) {
reporter.reportErrorMessage(argument, MessageKind.GENERIC,
{'text': 'Error: Expected a JsBuiltin enum value.'});
}
EnumConstantElement enumConstant = builtinElement;
int index = enumConstant.index;
js.Template template =
backend.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(invariant(node, nativeBehavior != null,
message: "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.slowToString();
js.Template expr = js.js.expressionTemplateYielding(
backend.emitter.generateEmbeddedGlobalAccess(globalName));
native.NativeBehavior nativeBehavior = elements.getNativeData(node);
assert(invariant(node, nativeBehavior != null,
message: "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 (!backend.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 = helpers.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(
backend.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.