blob: 984df98062135fb66a56e7bebfc2d5b2864d882e [file] [log] [blame]
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library dart2js.ir_builder;
import 'ir_nodes.dart' as ir;
import '../elements/elements.dart';
import '../dart2jslib.dart';
import '../dart_types.dart';
import '../source_file.dart';
import '../tree/tree.dart' as ast;
import '../scanner/scannerlib.dart' show Token, isUserDefinableOperator;
import '../dart_backend/dart_backend.dart' show DartBackend;
import '../universe/universe.dart' show SelectorKind;
import '../util/util.dart' show Link;
/**
* This task iterates through all resolved elements and builds [ir.Node]s. The
* nodes are stored in the [nodes] map and accessible through [hasIr] and
* [getIr].
*
* The functionality of the IrNodes is added gradually, therefore elements might
* have an IR or not, depending on the language features that are used. For
* elements that do have an IR, the tree [ast.Node]s and the [Token]s are not
* used in the rest of the compilation. This is ensured by setting the element's
* cached tree to `null` and also breaking the token stream to crash future
* attempts to parse.
*
* The type inferrer works on either IR nodes or tree nodes. The IR nodes are
* then translated into the SSA form for optimizations and code generation.
* Long-term, once the IR supports the full language, the backend can be
* re-implemented to work directly on the IR.
*/
class IrBuilderTask extends CompilerTask {
final Map<Element, ir.FunctionDefinition> nodes =
<Element, ir.FunctionDefinition>{};
IrBuilderTask(Compiler compiler) : super(compiler);
String get name => 'IR builder';
bool hasIr(Element element) => nodes.containsKey(element.implementation);
ir.FunctionDefinition getIr(Element element) => nodes[element.implementation];
void buildNodes() {
if (!irEnabled()) return;
measure(() {
Map<Element, TreeElements> resolved =
compiler.enqueuer.resolution.resolvedElements;
resolved.forEach((Element element, TreeElements elementsMapping) {
if (canBuild(element)) {
element = element.implementation;
SourceFile sourceFile = elementSourceFile(element);
IrBuilder builder =
new IrBuilder(elementsMapping, compiler, sourceFile);
ir.FunctionDefinition function;
ElementKind kind = element.kind;
if (kind == ElementKind.GENERATIVE_CONSTRUCTOR) {
// TODO(lry): build ir for constructors.
} else if (element.isDeferredLoaderGetter) {
// TODO(sigurdm): Build ir for deferred loader functions.
} else if (kind == ElementKind.GENERATIVE_CONSTRUCTOR_BODY ||
kind == ElementKind.FUNCTION ||
kind == ElementKind.GETTER ||
kind == ElementKind.SETTER) {
function = builder.buildFunction(element);
} else if (kind == ElementKind.FIELD) {
// TODO(lry): build ir for lazy initializers of static fields.
} else {
compiler.internalError(element, 'Unexpected element kind $kind.');
}
if (function != null) {
nodes[element] = function;
compiler.tracer.traceCompilation(element.name, null, compiler);
compiler.tracer.traceGraph("IR Builder", function);
}
}
});
});
}
bool irEnabled() {
// TODO(lry): support checked-mode checks.
return const bool.fromEnvironment('USE_NEW_BACKEND') &&
compiler.backend is DartBackend &&
!compiler.enableTypeAssertions &&
!compiler.enableConcreteTypeInference;
}
bool canBuild(Element element) {
// TODO(lry): support lazy initializers.
FunctionElement function = element.asFunctionElement();
if (function == null) return false;
// TODO(kmillikin): support functions with optional parameters.
FunctionSignature signature = function.functionSignature;
if (signature.optionalParameterCount > 0) return false;
SupportedTypeVerifier typeVerifier = new SupportedTypeVerifier();
if (!typeVerifier.visit(signature.type.returnType, null)) return false;
bool parameters_ok = true;
signature.forEachParameter((parameter) {
parameters_ok =
parameters_ok && typeVerifier.visit(parameter.type, null);
});
if (!parameters_ok) return false;
// TODO(kmillikin): support getters and setters and static class members.
// With the current Dart Tree emitter they just require recognizing them
// and generating the correct syntax.
if (element.isGetter || element.isSetter) return false;
if (element.enclosingElement.isClass) return false;
// TODO(lry): support native functions (also in [visitReturn]).
if (function.isNative) return false;
return true;
}
bool get inCheckedMode {
bool result = false;
assert((result = true));
return result;
}
SourceFile elementSourceFile(Element element) {
if (element is FunctionElement) {
FunctionElement functionElement = element;
if (functionElement.patch != null) element = functionElement.patch;
}
return element.compilationUnit.script.file;
}
}
/**
* A tree visitor that builds [IrNodes]. The visit methods add statements using
* to the [builder] and return the last added statement for trees that represent
* an expression.
*/
class IrBuilder extends ResolvedVisitor<ir.Primitive> {
final SourceFile sourceFile;
final ir.Continuation returnContinuation;
final List<ir.Parameter> parameters;
// The IR builder maintains a context, which is an expression with a hole in
// it. The hole represents the focus where new expressions can be added.
// The context is implemented by 'root' which is the root of the expression
// and 'current' which is the expression that immediately contains the hole.
// Not all expressions have a hole (e.g., invocations, which always occur in
// tail position, do not have a hole). Expressions with a hole have a plug
// method.
//
// Conceptually, visiting a statement takes a context as input and returns
// either a new context or else an expression without a hole if all
// control-flow paths through the statement have exited. An expression
// without a hole is represented by a (root, current) pair where root is the
// expression and current is null.
//
// Conceptually again, visiting an expression takes a context as input and
// returns either a pair of a new context and a definition denoting
// the expression's value, or else an expression without a hole if all
// control-flow paths through the expression have exited.
//
// We do not pass contexts as arguments or return them. Rather we use the
// current context (root, current) as the visitor state and mutate current.
// Visiting a statement returns null; visiting an expression returns the
// primitive denoting its value.
ir.Expression root = null;
ir.Expression current = null;
// In SSA terms, join-point continuation parameters are the phis and the
// continuation invocation arguments are the corresponding phi inputs. To
// support name introduction and renaming for source level variables, we use
// nested (delimited) visitors for constructing subparts of the IR that will
// need renaming. Each source variable is assigned an index.
//
// Each nested visitor maintains a list of free variable uses in the body.
// These are implemented as a list of parameters, each with their own use
// list of references. When the delimited subexpression is plugged into the
// surrounding context, the free occurrences can be captured or become free
// occurrences in the next outer delimited subexpression.
//
// Each nested visitor maintains a list that maps indexes of variables
// assigned in the delimited subexpression to their reaching definition ---
// that is, the definition in effect at the hole in 'current'. These are
// used to determine if a join-point continuation needs to be passed
// arguments, and what the arguments are.
final Map<Element, int> variableIndex;
final List<ir.Parameter> freeVars;
final List<ir.Primitive> assignedVars;
/// Construct a top-level visitor.
IrBuilder(TreeElements elements, Compiler compiler, this.sourceFile)
: returnContinuation = new ir.Continuation.retrn(),
parameters = <ir.Parameter>[],
variableIndex = <Element, int>{},
freeVars = null,
assignedVars = <ir.Primitive>[],
super(elements, compiler);
/// Construct a delimited visitor.
IrBuilder.delimited(IrBuilder parent)
: sourceFile = parent.sourceFile,
returnContinuation = parent.returnContinuation,
parameters = parent.parameters,
variableIndex = parent.variableIndex,
freeVars = new List<ir.Parameter>.generate(
parent.assignedVars.length, (_) => new ir.Parameter(null),
growable: false),
assignedVars = new List<ir.Primitive>.generate(
parent.assignedVars.length, (_) => null),
super(parent.elements, parent.compiler);
/**
* Builds the [ir.FunctionDefinition] for a function element. In case the
* function uses features that cannot be expressed in the IR, this function
* returns `null`.
*/
ir.FunctionDefinition buildFunction(FunctionElement functionElement) {
return nullIfGiveup(() => buildFunctionInternal(functionElement));
}
ir.FunctionDefinition buildFunctionInternal(FunctionElement element) {
assert(invariant(element, element.isImplementation));
ast.FunctionExpression function = element.node;
assert(function != null);
assert(!function.modifiers.isExternal);
assert(elements[function] != null);
root = current = null;
FunctionSignature signature = element.functionSignature;
signature.orderedForEachParameter((parameterElement) {
ir.Parameter parameter = new ir.Parameter(parameterElement);
parameters.add(parameter);
variableIndex[parameterElement] = assignedVars.length;
assignedVars.add(parameter);
});
visit(function.body);
ensureReturn(function);
return new ir.FunctionDefinition(returnContinuation, parameters, root);
}
ConstantSystem get constantSystem => compiler.backend.constantSystem;
bool get isOpen => root == null || current != null;
// Plug an expression into the 'hole' in the context being accumulated. The
// empty context (just a hole) is represented by root (and current) being
// null. Since the hole in the current context is filled by this function,
// the new hole must be in the newly added expression---which becomes the
// new value of current.
void add(ir.Expression expr) {
assert(isOpen);
if (root == null) {
root = current = expr;
} else {
current = current.plug(expr);
}
}
/**
* Add an explicit `return null` for functions that don't have a return
* statement on each branch. This includes functions with an empty body,
* such as `foo(){ }`.
*/
void ensureReturn(ast.FunctionExpression node) {
if (!isOpen) return;
ir.Constant constant = new ir.Constant(constantSystem.createNull());
add(new ir.LetPrim(constant));
add(new ir.InvokeContinuation(returnContinuation, [constant]));
current = null;
}
ir.Primitive visit(ast.Node node) => node.accept(this);
// ==== Statements ====
// Build(Block(stamements), C) = C'
// where C' = statements.fold(Build, C)
ir.Primitive visitBlock(ast.Block node) {
assert(isOpen);
for (ast.Node n in node.statements.nodes) {
visit(n);
if (!isOpen) return null;
}
return null;
}
// Build(EmptyStatement, C) = C
ir.Primitive visitEmptyStatement(ast.EmptyStatement node) {
assert(isOpen);
return null;
}
// Build(ExpressionStatement(e), C) = C'
// where (C', _) = Build(e, C)
ir.Primitive visitExpressionStatement(ast.ExpressionStatement node) {
assert(isOpen);
visit(node.expression);
return null;
}
/// Create branch join continuation parameters and fill in arguments.
///
/// Given delimited builders for the arms of a branch, return a list of
/// fresh join-point continuation parameters for the join continuation.
/// Fill in [leftArguments] and [rightArguments] with the left and right
/// continuation invocation arguments.
List<ir.Parameter> createBranchJoinParametersAndFillArguments(
IrBuilder leftBuilder,
IrBuilder rightBuilder,
List<ir.Primitive> leftArguments,
List<ir.Primitive> rightArguments) {
// The sets of free and assigned variables for a delimited builder is
// initially the length of the assigned variables of the parent. The free
// variables cannot grow because there cannot be free occurrences of
// variables that were not declared before the entrance to the delimited
// subgraph. The assigned variables can grow when new variables are
// declared in the delimited graph, but we only inspect the prefix
// corresponding to the parent's declared variables.
assert(leftBuilder.isOpen);
assert(rightBuilder.isOpen);
assert(assignedVars.length <= leftBuilder.assignedVars.length);
assert(assignedVars.length <= rightBuilder.assignedVars.length);
List<ir.Parameter> parameters = <ir.Parameter>[];
// If a variable was assigned on either the left or the right (and control
// flow reaches the end of the corresponding subterm) then the variable has
// different values reaching the join point and needs to be passed as an
// argument to the join point continuation.
for (int i = 0; i < assignedVars.length; ++i) {
// The last assignments, if any, reaching the end of the two subterms.
ir.Primitive leftAssignment = leftBuilder.assignedVars[i];
ir.Primitive rightAssignment = rightBuilder.assignedVars[i];
if (leftAssignment != null || rightAssignment != null) {
// The corresponsing argument is the reaching definition if any, or a
// free occurrence. In the case that control does not reach both the
// left and right subterms we will still have a join continuation with
// possibly arguments passed to it. Such singly-used continuations
// are eliminated by the shrinking conversions.
parameters.add(new ir.Parameter(null));
ir.Primitive reachingDefinition =
assignedVars[i] == null ? freeVars[i] : assignedVars[i];
leftArguments.add(
leftAssignment == null ? reachingDefinition : leftAssignment);
rightArguments.add(
rightAssignment == null ? reachingDefinition : rightAssignment);
}
}
return parameters;
}
/// Allocate loop join continuation parameters and fill in arguments.
///
/// Given delimited builders for a test at the top (while, for, or for-in)
/// loop's condition and for the loop body, return a list of fresh
/// join-point continuation parameters for the loop join. Fill in
/// [entryArguments] with the arguments to the non-recursive continuation
/// invocation and [loopArguments] with the arguments to the recursive
/// continuation invocation.
///
/// The [bodyBuilder] is assumed to be open, otherwise there is no join
/// necessary.
List<ir.Parameter> createLoopJoinParametersAndFillArguments(
List<ir.Primitive> entryArguments,
IrBuilder condBuilder,
IrBuilder bodyBuilder,
List<ir.Primitive> loopArguments) {
assert(bodyBuilder.isOpen);
// The loop condition and body are delimited --- assignedVars are still
// those reaching the entry to the loop.
assert(assignedVars.length == condBuilder.freeVars.length);
assert(assignedVars.length == bodyBuilder.freeVars.length);
assert(assignedVars.length <= condBuilder.assignedVars.length);
assert(assignedVars.length <= bodyBuilder.assignedVars.length);
List<ir.Parameter> parameters = <ir.Parameter>[];
// When the free variables in the loop body are computed later, the
// parameters are assumed to appear in the same order as they appear in
// the assignedVars list.
for (int i = 0; i < assignedVars.length; ++i) {
// Was there an assignment in the body?
ir.Definition reachingAssignment = bodyBuilder.assignedVars[i];
// If not, was there an assignment in the condition?
if (reachingAssignment == null) {
reachingAssignment = condBuilder.assignedVars[i];
}
// If not, no value needs to be passed to the join point.
if (reachingAssignment == null) continue;
parameters.add(new ir.Parameter(null));
ir.Definition entryAssignment = assignedVars[i];
entryArguments.add(
entryAssignment == null ? freeVars[i] : entryAssignment);
loopArguments.add(reachingAssignment);
}
return parameters;
}
/// Capture free variables in the arms of a branch.
///
/// Capture the free variables in the left and right arms of a conditional
/// branch. The free variables are captured by the current definition.
/// Also update the builder's assigned variables to be those reaching the
/// branch join. If there is no join, [parameters] should be `null` and
/// at least one of [leftBuilder] or [rightBuilder] should not be open.
void captureFreeBranchVariables(IrBuilder leftBuilder,
IrBuilder rightBuilder,
List<ir.Parameter> parameters) {
// Parameters is non-null when there is a join, if and only if both left
// and right subterm contexts are open.
assert((leftBuilder.isOpen && rightBuilder.isOpen) ==
(parameters != null));
int parameterIndex = 0;
for (int i = 0; i < assignedVars.length; ++i) {
// This is the definition that reaches the left and right subterms. All
// free uses in either term are uses of this definition.
ir.Primitive reachingDefinition =
assignedVars[i] == null ? freeVars[i] : assignedVars[i];
reachingDefinition
..substituteFor(leftBuilder.freeVars[i])
..substituteFor(rightBuilder.freeVars[i]);
// Also add join continuation parameters as assignments for the join
// body. This is done last because the assigned variables are updated
// in place.
ir.Primitive leftAssignment = leftBuilder.assignedVars[i];
ir.Primitive rightAssignment = rightBuilder.assignedVars[i];
if (parameters != null) {
if (leftAssignment != null || rightAssignment != null) {
assignedVars[i] = parameters[parameterIndex++];
}
} else if (leftBuilder.isOpen) {
if (leftAssignment != null) assignedVars[i] = leftAssignment;
} else if (rightBuilder.isOpen) {
if (rightAssignment != null) assignedVars[i] = rightAssignment;
}
}
}
/// Capture free variables in a test at the top loop.
///
/// Capture the free variables in the condition and the body of a test at
/// the top loop (e.g., while, for, or for-in). Also updates the
/// builder's assigned variables to be those reaching the loop successor
/// statement.
void captureFreeLoopVariables(IrBuilder condBuilder,
IrBuilder bodyBuilder,
List<ir.Parameter> parameters) {
// Capturing loop-body variables differs from capturing variables for
// the predecessors of a non-recursive join-point continuation. The
// join point continuation parameters are in scope for the condition
// and body in the case of a loop.
int parameterIndex = 0;
// The parameters are assumed to be in the same order as the corresponding
// variables appear in the assignedVars list.
for (int i = 0; i < assignedVars.length; ++i) {
// Add recursive join continuation parameters as assignments for the
// join body, if there is a join continuation (parameters != null).
// This is done first because free occurrences in the loop should be
// captured by the join continuation parameters.
if (parameters != null &&
(condBuilder.assignedVars[i] != null ||
bodyBuilder.assignedVars[i] != null)) {
assignedVars[i] = parameters[parameterIndex++];
}
ir.Definition reachingDefinition =
assignedVars[i] == null ? freeVars[i] : assignedVars[i];
// Free variables in the body can be captured by assignments in the
// condition.
if (condBuilder.assignedVars[i] == null) {
reachingDefinition.substituteFor(bodyBuilder.freeVars[i]);
} else {
condBuilder.assignedVars[i].substituteFor(bodyBuilder.freeVars[i]);
}
reachingDefinition.substituteFor(condBuilder.freeVars[i]);
}
}
ir.Primitive visitIf(ast.If node) {
assert(isOpen);
ir.Primitive condition = visit(node.condition);
// The then and else parts are delimited.
IrBuilder thenBuilder = new IrBuilder.delimited(this);
IrBuilder elseBuilder = new IrBuilder.delimited(this);
thenBuilder.visit(node.thenPart);
if (node.hasElsePart) elseBuilder.visit(node.elsePart);
// Build the term
// (Result =) let cont then() = [[thenPart]] in
// let cont else() = [[elsePart]] in
// if condition (then, else)
ir.Continuation thenContinuation = new ir.Continuation([]);
ir.Continuation elseContinuation = new ir.Continuation([]);
ir.Expression letElse =
new ir.LetCont(elseContinuation,
new ir.Branch(new ir.IsTrue(condition),
thenContinuation,
elseContinuation));
ir.Expression letThen = new ir.LetCont(thenContinuation, letElse);
ir.Expression result = letThen;
List<ir.Parameter> parameters; // Null if there is no join.
if (thenBuilder.isOpen && elseBuilder.isOpen) {
// There is a join-point continuation. Build the term
// 'let cont join(x, ...) = [] in Result' and plug invocations of the
// join-point continuation into the then and else continuations.
List<ir.Primitive> thenArguments = <ir.Primitive>[];
List<ir.Primitive> elseArguments = <ir.Primitive>[];
// Compute the join-point continuation parameters. Fill in the
// arguments to the join-point continuation invocations.
parameters = createBranchJoinParametersAndFillArguments(
thenBuilder, elseBuilder, thenArguments, elseArguments);
ir.Continuation joinContinuation = new ir.Continuation(parameters);
thenBuilder.add(
new ir.InvokeContinuation(joinContinuation, thenArguments));
elseBuilder.add(
new ir.InvokeContinuation(joinContinuation, elseArguments));
result = new ir.LetCont(joinContinuation, result);
}
// Capture free occurrences in the then and else bodies and update the
// assigned variables for the successor. This is done after creating
// invocations of the join continuation so free join continuation
// arguments are properly captured.
captureFreeBranchVariables(thenBuilder, elseBuilder, parameters);
// The then or else term root could be null, but not both. If there is
// a join then an InvokeContinuation was just added to both of them. If
// there is no join, then at least one of them is closed and thus has a
// non-null root by the definition of the predicate isClosed. In the
// case that one of them is null, it must be the only one that is open
// and thus contains the new hole in the context. This case is handled
// after the branch is plugged into the current hole.
thenContinuation.body = thenBuilder.root;
elseContinuation.body = elseBuilder.root;
add(result);
if (parameters == null) {
// At least one subter is closed.
if (thenBuilder.isOpen) {
current = (thenBuilder.root == null) ? letThen : thenBuilder.current;
} else if (elseBuilder.isOpen) {
current = (elseBuilder.root == null) ? letElse : elseBuilder.current;
} else {
current = null;
}
}
return null;
}
ir.Primitive visitWhile(ast.While node) {
assert(isOpen);
// While loops use three named continuations: the entry to the body,
// the loop exit (break), and the loop back edge (continue).
// The CPS translation [[while (condition) body; successor]] is:
//
// let cont continue(x, ...) =
// let cont break() = [[successor]] in
// let cont body() = [[body]]; continue(v, ...) in
// let prim cond = [[condition]] in
// branch cond (body, break) in
// continue(v, ...)
// The condition and body are delimited.
IrBuilder condBuilder = new IrBuilder.delimited(this);
IrBuilder bodyBuilder = new IrBuilder.delimited(this);
ir.Primitive condition = condBuilder.visit(node.condition);
bodyBuilder.visit(node.body);
// Create body entry and loop exit continuations and a join-point
// continuation if control flow reaches the end of the body.
ir.Continuation bodyContinuation = new ir.Continuation([]);
ir.Continuation breakContinuation = new ir.Continuation([]);
condBuilder.add(new ir.Branch(new ir.IsTrue(condition),
bodyContinuation,
breakContinuation));
ir.Continuation continueContinuation;
List<ir.Parameter> parameters;
List<ir.Primitive> entryArguments = <ir.Primitive>[]; // The forward edge.
if (bodyBuilder.isOpen) {
List<ir.Primitive> loopArguments = <ir.Primitive>[]; // The back edge.
parameters =
createLoopJoinParametersAndFillArguments(entryArguments, condBuilder,
bodyBuilder, loopArguments);
continueContinuation = new ir.Continuation(parameters);
bodyBuilder.add(
new ir.InvokeContinuation(continueContinuation, loopArguments,
recursive:true));
}
bodyContinuation.body = bodyBuilder.root;
// Capture free variable occurrences in the loop body.
captureFreeLoopVariables(condBuilder, bodyBuilder, parameters);
ir.Expression resultContext =
new ir.LetCont(breakContinuation,
new ir.LetCont(bodyContinuation,
condBuilder.root));
if (continueContinuation != null) {
continueContinuation.body = resultContext;
add(new ir.LetCont(continueContinuation,
new ir.InvokeContinuation(continueContinuation,
entryArguments)));
current = resultContext;
} else {
add(resultContext);
}
return null;
}
ir.Primitive visitVariableDefinitions(ast.VariableDefinitions node) {
assert(isOpen);
for (ast.Node definition in node.definitions.nodes) {
Element element = elements[definition];
// Definitions are either SendSets if there is an initializer, or
// Identifiers if there is no initializer.
if (definition is ast.SendSet) {
assert(!definition.arguments.isEmpty);
assert(definition.arguments.tail.isEmpty);
ir.Primitive initialValue = visit(definition.arguments.head);
variableIndex[element] = assignedVars.length;
assignedVars.add(initialValue);
} else {
assert(definition is ast.Identifier);
// The initial value is null.
// TODO(kmillikin): Consider pooling constants.
ir.Constant constant = new ir.Constant(constantSystem.createNull());
add(new ir.LetPrim(constant));
variableIndex[element] = assignedVars.length;
assignedVars.add(constant);
}
}
return null;
}
// Build(Return(e), C) = C'[InvokeContinuation(return, x)]
// where (C', x) = Build(e, C)
//
// Return without a subexpression is translated as if it were return null.
ir.Primitive visitReturn(ast.Return node) {
assert(isOpen);
// TODO(lry): support native returns.
if (node.beginToken.value == 'native') return giveup();
ir.Primitive value;
if (node.expression == null) {
value = new ir.Constant(constantSystem.createNull());
add(new ir.LetPrim(value));
} else {
value = visit(node.expression);
}
add(new ir.InvokeContinuation(returnContinuation, [value]));
current = null;
return null;
}
// ==== Expressions ====
ir.Primitive visitConditional(ast.Conditional node) {
assert(isOpen);
ir.Primitive condition = visit(node.condition);
// The then and else expressions are delimited.
IrBuilder thenBuilder = new IrBuilder.delimited(this);
IrBuilder elseBuilder = new IrBuilder.delimited(this);
ir.Primitive thenValue = thenBuilder.visit(node.thenExpression);
ir.Primitive elseValue = elseBuilder.visit(node.elseExpression);
// Compute the join-point continuation parameters. Fill in the
// arguments to the join-point continuation invocations.
List<ir.Primitive> thenArguments = <ir.Primitive>[];
List<ir.Primitive> elseArguments = <ir.Primitive>[];
List<ir.Parameter> parameters =
createBranchJoinParametersAndFillArguments(
thenBuilder, elseBuilder, thenArguments, elseArguments);
// Add a continuation parameter for the result of the expression.
ir.Parameter resultParameter = new ir.Parameter(null);
parameters.add(resultParameter);
thenArguments.add(thenValue);
elseArguments.add(elseValue);
// Build the term
// let cont join(x, ..., result) = [] in
// let cont then() = [[thenPart]]; join(v, ...) in
// let cont else() = [[elsePart]]; join(v, ...) in
// if condition (then, else)
ir.Continuation joinContinuation = new ir.Continuation(parameters);
ir.Continuation thenContinuation = new ir.Continuation([]);
ir.Continuation elseContinuation = new ir.Continuation([]);
thenBuilder.add(
new ir.InvokeContinuation(joinContinuation, thenArguments));
elseBuilder.add(
new ir.InvokeContinuation(joinContinuation, elseArguments));
// Capture free occurrences in the then and else bodies and update the
// assigned variables for the successor. This is done after creating
// invocations of the join continuation so free join continuation
// arguments are properly captured.
captureFreeBranchVariables(thenBuilder, elseBuilder, parameters);
thenContinuation.body = thenBuilder.root;
elseContinuation.body = elseBuilder.root;
add(new ir.LetCont(joinContinuation,
new ir.LetCont(thenContinuation,
new ir.LetCont(elseContinuation,
new ir.Branch(new ir.IsTrue(condition),
thenContinuation,
elseContinuation)))));
return resultParameter;
}
// For all simple literals:
// Build(Literal(c), C) = C[let val x = Constant(c) in [], x]
ir.Primitive visitLiteralBool(ast.LiteralBool node) {
assert(isOpen);
ir.Constant constant =
new ir.Constant(constantSystem.createBool(node.value));
add(new ir.LetPrim(constant));
return constant;
}
ir.Primitive visitLiteralDouble(ast.LiteralDouble node) {
assert(isOpen);
ir.Constant constant =
new ir.Constant(constantSystem.createDouble(node.value));
add(new ir.LetPrim(constant));
return constant;
}
ir.Primitive visitLiteralInt(ast.LiteralInt node) {
assert(isOpen);
ir.Constant constant =
new ir.Constant(constantSystem.createInt(node.value));
add(new ir.LetPrim(constant));
return constant;
}
ir.Primitive visitLiteralNull(ast.LiteralNull node) {
assert(isOpen);
ir.Constant constant = new ir.Constant(constantSystem.createNull());
add(new ir.LetPrim(constant));
return constant;
}
ir.Primitive visitLiteralString(ast.LiteralString node) {
assert(isOpen);
ir.Constant constant =
new ir.Constant(constantSystem.createString(node.dartString));
add(new ir.LetPrim(constant));
return constant;
}
Constant getConstantForNode(ast.Node node) {
Constant constant =
compiler.backend.constants.getConstantForNode(node, elements);
assert(invariant(node, constant != null,
message: 'No constant computed for $node'));
return constant;
}
bool isSupportedConst(Constant constant) {
return const SupportedConstantVisitor().visit(constant);
}
ir.Primitive visitLiteralList(ast.LiteralList node) {
assert(isOpen);
ir.Primitive result;
if (node.isConst) {
// TODO(sigurdm): Remove when all constants are supported.
Constant constant = getConstantForNode(node);
if (!isSupportedConst(constant)) return giveup();
result = new ir.Constant(constant);
} else {
List<ir.Primitive> values = new List<ir.Primitive>();
node.elements.nodes.forEach((ast.Node node) {
values.add(visit(node));
});
result = new ir.LiteralList(values);
}
add(new ir.LetPrim(result));
return result;
}
ir.Primitive visitLiteralMap(ast.LiteralMap node) {
assert(isOpen);
ir.Primitive result;
if (node.isConst) {
// TODO(sigurdm): Remove when all constants are supported.
Constant constant = getConstantForNode(node);
if (!isSupportedConst(constant)) return giveup();
result = new ir.Constant(constant);
} else {
List<ir.Primitive> keys = new List<ir.Primitive>();
List<ir.Primitive> values = new List<ir.Primitive>();
node.entries.nodes.forEach((ast.LiteralMapEntry node) {
keys.add(visit(node.key));
values.add(visit(node.value));
});
result = new ir.LiteralMap(keys, values);
}
add(new ir.LetPrim(result));
return result;
}
// TODO(kmillikin): other literals.
// LiteralSymbol
ir.Primitive visitParenthesizedExpression(
ast.ParenthesizedExpression node) {
assert(isOpen);
return visit(node.expression);
}
// Stores the result of visiting a CascadeReceiver, so we can return it from
// its enclosing Cascade.
ir.Primitive _currentCascadeReceiver;
ir.Primitive visitCascadeReceiver(ast.CascadeReceiver node) {
assert(isOpen);
return _currentCascadeReceiver = visit(node.expression);
}
ir.Primitive visitCascade(ast.Cascade node) {
assert(isOpen);
var oldCascadeReceiver = _currentCascadeReceiver;
// Throw away the result of visiting the expression.
// Instead we return the result of visiting the CascadeReceiver.
this.visit(node.expression);
ir.Primitive receiver = _currentCascadeReceiver;
_currentCascadeReceiver = oldCascadeReceiver;
return receiver;
}
ir.Primitive lookupLocal(Element element) {
int index = variableIndex[element];
ir.Primitive value = assignedVars[index];
return value == null ? freeVars[index] : value;
}
// ==== Sends ====
ir.Primitive visitAssert(ast.Send node) {
assert(isOpen);
return giveup();
}
ir.Primitive visitNamedArgument(ast.NamedArgument node) {
assert(isOpen);
return visit(node.expression);
}
ir.Primitive visitClosureSend(ast.Send node) {
assert(isOpen);
Selector closureSelector = elements.getSelector(node);
Selector namedCallSelector = new Selector(closureSelector.kind,
"call",
closureSelector.library,
closureSelector.argumentCount,
closureSelector.namedArguments);
assert(node.receiver == null);
Element element = elements[node];
ir.Primitive closureTarget;
if (element == null) {
closureTarget = visit(node.selector);
} else {
assert(Elements.isLocal(element));
closureTarget = lookupLocal(element);
}
List<ir.Primitive> arguments = new List<ir.Primitive>();
for (ast.Node n in node.arguments) {
arguments.add(visit(n));
}
ir.Parameter v = new ir.Parameter(null);
ir.Continuation k = new ir.Continuation([v]);
ir.Expression invoke =
new ir.InvokeMethod(closureTarget, namedCallSelector, k, arguments);
add(new ir.LetCont(k, invoke));
return v;
}
ir.Primitive visitDynamicSend(ast.Send node) {
assert(isOpen);
if (node.receiver == null || node.receiver.isSuper()) {
return giveup();
}
Selector selector = elements.getSelector(node);
ir.Primitive receiver = visit(node.receiver);
List<ir.Primitive> arguments = new List<ir.Primitive>();
for (ast.Node n in node.arguments) {
arguments.add(visit(n));
}
ir.Parameter v = new ir.Parameter(null);
ir.Continuation k = new ir.Continuation([v]);
ir.Expression invoke =
new ir.InvokeMethod(receiver, selector, k, arguments);
add(new ir.LetCont(k, invoke));
return v;
}
ir.Primitive visitGetterSend(ast.Send node) {
assert(isOpen);
Element element = elements[node];
if (Elements.isLocal(element)) {
return lookupLocal(element);
} else if (element == null || Elements.isInstanceField(element)) {
ir.Primitive receiver = visit(node.receiver);
ir.Parameter v = new ir.Parameter(null);
ir.Continuation k = new ir.Continuation([v]);
Selector selector = elements.getSelector(node);
assert(selector.kind == SelectorKind.GETTER);
ir.InvokeMethod invoke = new ir.InvokeMethod(receiver, selector, k, []);
add(new ir.LetCont(k, invoke));
return v;
} else {
// TODO(asgerf): static and top-level
// NOTE: Index-getters are OperatorSends, not GetterSends
return giveup();
}
}
ir.Primitive translateLogicalOperator(ast.Operator op,
ast.Expression left,
ast.Expression right) {
// e0 && e1 is translated as if e0 ? (e1 == true) : false.
// e0 || e1 is translated as if e0 ? true : (e1 == true).
// The translation must convert both e0 and e1 to booleans and handle
// local variable assignments in e1.
ir.Primitive leftValue = visit(left);
IrBuilder rightBuilder = new IrBuilder.delimited(this);
ir.Primitive rightValue = rightBuilder.visit(right);
// A dummy empty target for the branch on the left subexpression branch.
// This enables using the same infrastructure for continuation arguments
// and free variable capture as in visitIf and visitConditional. It will
// hold an invocation of the join-point continuation. It cannot have
// assigned variables but may have free variables as arguments to the
// join-point continuation.
IrBuilder emptyBuilder = new IrBuilder.delimited(this);
List <ir.Primitive> leftArguments = <ir.Primitive>[];
List <ir.Primitive> rightArguments = <ir.Primitive>[];
List <ir.Parameter> parameters =
createBranchJoinParametersAndFillArguments(
emptyBuilder, rightBuilder, leftArguments, rightArguments);
// Add a continuation parameter for the result of the expression.
ir.Parameter resultParameter = new ir.Parameter(null);
parameters.add(resultParameter);
// If we don't evaluate the right subexpression, the value of the whole
// expression is this constant.
ir.Constant leftBool =
new ir.Constant(constantSystem.createBool(op.source == '||'));
leftArguments.add(leftBool);
// If we do evaluate the right subexpression, the value of the expression
// is a true or false constant.
ir.Constant rightTrue = new ir.Constant(constantSystem.createBool(true));
ir.Constant rightFalse = new ir.Constant(constantSystem.createBool(false));
// Wire up two continuations for the left subexpression, two continuations
// for the right subexpression, and a three-way join continuation.
ir.Continuation joinContinuation = new ir.Continuation(parameters);
ir.Continuation leftTrueContinuation = new ir.Continuation([]);
ir.Continuation leftFalseContinuation = new ir.Continuation([]);
ir.Continuation rightTrueContinuation = new ir.Continuation([]);
ir.Continuation rightFalseContinuation = new ir.Continuation([]);
// If right is true, invoke the join with a true value for the result.
rightArguments.add(rightTrue);
rightTrueContinuation.body = new ir.LetPrim(rightTrue)
..plug(new ir.InvokeContinuation(joinContinuation, rightArguments));
// And if false, invoke the join continuation with a false value. The
// argument list of definitions can be mutated, because fresh Reference
// objects are allocated by the InvokeContinuation constructor.
rightArguments[rightArguments.length - 1] = rightFalse;
rightFalseContinuation.body = new ir.LetPrim(rightFalse)
..plug(new ir.InvokeContinuation(joinContinuation, rightArguments));
// The right subexpression has two continuations.
rightBuilder.add(
new ir.LetCont(rightTrueContinuation,
new ir.LetCont(rightFalseContinuation,
new ir.Branch(new ir.IsTrue(rightValue),
rightTrueContinuation,
rightFalseContinuation))));
// Depending on the operator, the left subexpression's continuations are
// either the right subexpression or an invocation of the join-point
// continuation.
if (op.source == '&&') {
leftTrueContinuation.body = rightBuilder.root;
leftFalseContinuation.body = new ir.LetPrim(leftBool)
..plug(new ir.InvokeContinuation(joinContinuation, leftArguments));
} else {
leftTrueContinuation.body = new ir.LetPrim(leftBool)
..plug(new ir.InvokeContinuation(joinContinuation, leftArguments));
leftFalseContinuation.body = rightBuilder.root;
}
// Capture free local variable occurrences in the right subexpression
// and update the reaching definitions for the join-point continuation
// body to include the continuation's parameters.
captureFreeBranchVariables(rightBuilder, emptyBuilder, parameters);
add(new ir.LetCont(joinContinuation,
new ir.LetCont(leftTrueContinuation,
new ir.LetCont(leftFalseContinuation,
new ir.Branch(new ir.IsTrue(leftValue),
leftTrueContinuation,
leftFalseContinuation)))));
return resultParameter;
}
ir.Primitive visitOperatorSend(ast.Send node) {
assert(isOpen);
ast.Operator op = node.selector;
if (isUserDefinableOperator(op.source)) {
return visitDynamicSend(node);
}
if (op.source == '&&' || op.source == '||') {
assert(node.receiver != null);
assert(!node.arguments.isEmpty);
assert(node.arguments.tail.isEmpty);
return translateLogicalOperator(op, node.receiver, node.arguments.head);
}
return giveup();
}
// Build(StaticSend(f, arguments), C) = C[C'[InvokeStatic(f, xs)]]
// where (C', xs) = arguments.fold(Build, C)
ir.Primitive visitStaticSend(ast.Send node) {
assert(isOpen);
Element element = elements[node];
// TODO(lry): support static fields. (separate IR instruction?)
if (element.isField || element.isGetter) return giveup();
// TODO(kmillikin): support static setters.
if (element.isSetter) return giveup();
// TODO(lry): support constructors / factory calls.
if (element.isConstructor) return giveup();
// TODO(lry): support foreign functions.
if (element.isForeign(compiler)) return giveup();
// TODO(lry): for elements that could not be resolved emit code to throw a
// [NoSuchMethodError].
if (element.isErroneous) return giveup();
// TODO(lry): generate IR for object identicality.
if (element == compiler.identicalFunction) giveup();
Selector selector = elements.getSelector(node);
// TODO(kmillikin): support a receiver: A.m().
if (node.receiver != null) return giveup();
// TODO(lry): support default arguments, need support for locals.
List<ir.Definition> arguments = node.arguments.toList(growable:false)
.map(visit).toList(growable:false);
ir.Parameter v = new ir.Parameter(null);
ir.Continuation k = new ir.Continuation([v]);
ir.Expression invoke =
new ir.InvokeStatic(element, selector, k, arguments);
add(new ir.LetCont(k, invoke));
return v;
}
ir.Primitive visitSuperSend(ast.Send node) {
assert(isOpen);
return giveup();
}
ir.Primitive visitTypeReferenceSend(ast.Send node) {
assert(isOpen);
return giveup();
}
ir.Primitive visitSendSet(ast.SendSet node) {
assert(isOpen);
Element element = elements[node];
if (node.assignmentOperator.source != '=') return giveup();
if (Elements.isLocal(element)) {
// Exactly one argument expected for a simple assignment.
assert(!node.arguments.isEmpty);
assert(node.arguments.tail.isEmpty);
ir.Primitive result = visit(node.arguments.head);
assignedVars[variableIndex[element]] = result;
return result;
} else if (Elements.isStaticOrTopLevel(element)) {
// TODO(asgerf): static and top-level
return giveup();
} else if (node.receiver == null) {
// Nodes that fall in this case:
// - Unresolved top-level
// - Assignment to final variable (will not be resolved)
return giveup();
} else {
// Setter or index-setter invocation
assert(node.receiver != null);
if (node.receiver.isSuper()) return giveup();
ir.Primitive receiver = visit(node.receiver);
ir.Parameter v = new ir.Parameter(null);
ir.Continuation k = new ir.Continuation([v]);
Selector selector = elements.getSelector(node);
assert(selector.kind == SelectorKind.SETTER ||
selector.kind == SelectorKind.INDEX);
List<ir.Definition> args = node.arguments.toList(growable:false)
.map(visit).toList(growable:false);
ir.InvokeMethod invoke = new ir.InvokeMethod(receiver, selector, k, args);
add(new ir.LetCont(k, invoke));
return args.last;
}
}
ir.Primitive visitNewExpression(ast.NewExpression node) {
if (node.isConst) {
return giveup(); // TODO(asgerf): Const constructor call.
}
FunctionElement element = elements[node.send];
if (Elements.isUnresolved(element)) {
return giveup();
}
ast.Node selector = node.send.selector;
GenericType type = elements.getType(node);
ir.Parameter v = new ir.Parameter(null);
ir.Continuation k = new ir.Continuation([v]);
List<ir.Definition> args = node.send.arguments.toList(growable:false)
.map(visit).toList(growable:false);
ir.InvokeConstructor invoke = new ir.InvokeConstructor(
type,
element,
elements.getSelector(node.send),
k,
args);
add(new ir.LetCont(k, invoke));
return v;
}
ir.Primitive visitStringJuxtaposition(ast.StringJuxtaposition node) {
ir.Primitive first = visit(node.first);
ir.Primitive second = visit(node.second);
ir.Parameter v = new ir.Parameter(null);
ir.Continuation k = new ir.Continuation([v]);
ir.ConcatenateStrings concat =
new ir.ConcatenateStrings(k, [first, second]);
add(new ir.LetCont(k, concat));
return v;
}
ir.Primitive visitStringInterpolation(ast.StringInterpolation node) {
List<ir.Primitive> arguments = [];
arguments.add(visitLiteralString(node.string));
var it = node.parts.iterator;
while (it.moveNext()) {
ast.StringInterpolationPart part = it.current;
arguments.add(visit(part.expression));
arguments.add(visitLiteralString(part.string));
}
ir.Parameter v = new ir.Parameter(null);
ir.Continuation k = new ir.Continuation([v]);
ir.ConcatenateStrings concat = new ir.ConcatenateStrings(k, arguments);
add(new ir.LetCont(k, concat));
return v;
}
static final String ABORT_IRNODE_BUILDER = "IrNode builder aborted";
ir.Primitive giveup() => throw ABORT_IRNODE_BUILDER;
ir.FunctionDefinition nullIfGiveup(ir.FunctionDefinition action()) {
try {
return action();
} catch(e) {
if (e == ABORT_IRNODE_BUILDER) return null;
rethrow;
}
}
void internalError(String reason, {ast.Node node}) {
giveup();
}
}
// While we don't support all constants we need to filter out the unsupported
// ones:
class SupportedConstantVisitor extends ConstantVisitor<bool> {
const SupportedConstantVisitor();
bool visit(Constant constant) => constant.accept(this);
bool visitFunction(FunctionConstant constant) => false;
bool visitNull(NullConstant constant) => true;
bool visitInt(IntConstant constant) => true;
bool visitDouble(DoubleConstant constant) => true;
bool visitTrue(TrueConstant constant) => true;
bool visitFalse(FalseConstant constant) => true;
bool visitString(StringConstant constant) => true;
bool visitList(ListConstant constant) {
return constant.entries.every(visit);
}
bool visitMap(MapConstant constant) {
return visit(constant.keys) && constant.values.every(visit);
}
bool visitConstructed(ConstructedConstant constant) => false;
bool visitType(TypeConstant constant) => false;
bool visitInterceptor(InterceptorConstant constant) => false;
bool visitDummy(DummyConstant constant) => false;
bool visitDeferred(DeferredConstant constant) => false;
}
// Verify that types are ones that can be reconstructed by the type emitter.
class SupportedTypeVerifier extends DartTypeVisitor<bool, Null> {
bool visit(DartType type, Null _) => type.accept(this, null);
bool visitType(DartType type, Null _) => false;
bool visitVoidType(VoidType type, Null _) => true;
// Currently, InterfaceType and TypedefType are supported so long as they
// do not have type parameters. They are subclasses of GenericType.
bool visitGenericType(GenericType type, Null _) => !type.isGeneric;
}