blob: 3a1ea5d47e1adabff139e5fcf01f7eb8da9e672b [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 '../constants/expressions.dart';
import '../constants/values.dart' show PrimitiveConstantValue;
import '../dart_types.dart';
import '../dart2jslib.dart';
import '../elements/elements.dart';
import '../source_file.dart';
import '../tree/tree.dart' as ast;
import '../scanner/scannerlib.dart' show Token, isUserDefinableOperator;
import '../universe/universe.dart' show SelectorKind;
import 'cps_ir_nodes.dart' as ir;
import '../elements/modelx.dart' show SynthesizedConstructorElementX;
part 'cps_ir_builder_visitor.dart';
/// A mapping from variable elements to their compile-time values.
///
/// Map elements denoted by parameters and local variables to the
/// [ir.Primitive] that is their value. Parameters and locals are
/// assigned indexes which can be used to refer to them.
class Environment {
/// A map from elements to their environment index.
final Map<Element, int> variable2index;
/// A reverse map from environment indexes to the variable.
final List<Element> index2variable;
/// A map from environment indexes to their value.
final List<ir.Primitive> index2value;
Environment.empty()
: variable2index = <Element, int>{},
index2variable = <Element>[],
index2value = <ir.Primitive>[];
/// Construct an environment that is a copy of another one.
///
/// The mapping from elements to indexes is shared, not copied.
Environment.from(Environment other)
: variable2index = other.variable2index,
index2variable = new List<Element>.from(other.index2variable),
index2value = new List<ir.Primitive>.from(other.index2value);
get length => index2variable.length;
ir.Primitive operator [](int index) => index2value[index];
void extend(Element element, ir.Primitive value) {
// Assert that the name is not already in the environment. `null` is used
// as the name of anonymous variables. Because the variable2index map is
// shared, `null` can already occur. This is safe because such variables
// are not looked up by name.
//
// TODO(kmillikin): This is still kind of fishy. Refactor to not share
// name maps or else garbage collect unneeded names.
assert(element == null || !variable2index.containsKey(element));
variable2index[element] = index2variable.length;
index2variable.add(element);
index2value.add(value);
}
ir.Primitive lookup(Element element) {
assert(!element.isConst);
assert(invariant(element, variable2index.containsKey(element),
message: "Unknown variable: $element."));
return index2value[variable2index[element]];
}
void update(Element element, ir.Primitive value) {
index2value[variable2index[element]] = value;
}
/// Verify that the variable2index and index2variable maps agree up to the
/// index [length] exclusive.
bool sameDomain(int length, Environment other) {
assert(this.length >= length);
assert(other.length >= length);
for (int i = 0; i < length; ++i) {
// An index maps to the same variable in both environments.
Element variable = index2variable[i];
if (variable != other.index2variable[i]) return false;
// The variable maps to the same index in both environments.
int index = variable2index[variable];
if (index == null || index != other.variable2index[variable]) {
return false;
}
}
return true;
}
}
/// A class to collect breaks or continues.
///
/// When visiting a potential target of breaks or continues, any breaks or
/// continues are collected by a JumpCollector and processed later, on demand.
/// The site of the break or continue is represented by a continuation
/// invocation that will have its target and arguments filled in later.
///
/// The environment of the builder at that point is captured and should not
/// be subsequently mutated until the jump is resolved.
class JumpCollector {
final JumpTarget target;
final List<ir.InvokeContinuation> _invocations = <ir.InvokeContinuation>[];
final List<Environment> _environments = <Environment>[];
JumpCollector(this.target);
bool get isEmpty => _invocations.isEmpty;
int get length => _invocations.length;
List<ir.InvokeContinuation> get invocations => _invocations;
List<Environment> get environments => _environments;
void addJump(IrBuilder builder) {
ir.InvokeContinuation invoke = new ir.InvokeContinuation.uninitialized();
builder.add(invoke);
_invocations.add(invoke);
_environments.add(builder.environment);
builder._current = null;
// TODO(kmillikin): Can we set builder.environment to null to make it
// less likely to mutate it?
}
}
/// Function for building a node in the context of the current builder.
typedef ir.Node BuildFunction(node);
/// Function for building nodes in the context of the provided [builder].
typedef ir.Node SubbuildFunction(IrBuilder builder);
/// Mixin that provides encapsulated access to nested builders.
abstract class IrBuilderMixin<N> {
IrBuilder _irBuilder;
/// Execute [f] with [builder] as the current builder.
withBuilder(IrBuilder builder, f()) {
assert(builder != null);
IrBuilder prev = _irBuilder;
_irBuilder = builder;
var result = f();
_irBuilder = prev;
return result;
}
/// The current builder.
IrBuilder get irBuilder {
assert(_irBuilder != null);
return _irBuilder;
}
/// Visits the [node].
ir.Primitive visit(N node);
/// Builds and returns the [ir.Node] for [node] or returns `null` if
/// [node] is `null`.
ir.Node build(N node) => node != null ? visit(node) : null;
/// Returns a closure that takes an [IrBuilder] and builds [node] in its
/// context using [build].
SubbuildFunction subbuild(N node) {
return (IrBuilder builder) => withBuilder(builder, () => build(node));
}
/// Returns a closure that takes an [IrBuilder] and builds the sequence of
/// [nodes] in its context using [build].
// TODO(johnniwinther): Type [nodes] as `Iterable<N>` when `NodeList` uses
// `List` instead of `Link`.
SubbuildFunction subbuildSequence(/*Iterable<N>*/ nodes) {
return (IrBuilder builder) {
return withBuilder(builder, () => builder.buildSequence(nodes, build));
};
}
}
/// Shared state between IrBuilders of nested functions.
class IrBuilderClosureState {
final Iterable<Entity> closureLocals;
/// Maps local variables to their corresponding [ClosureVariable] object.
final Map<Local, ir.ClosureVariable> local2closure =
<Local, ir.ClosureVariable>{};
/// Maps functions to the list of closure variables declared in that function.
final Map<ExecutableElement, List<ir.ClosureVariable>> function2closures =
<ExecutableElement, List<ir.ClosureVariable>>{};
/// Returns the closure variables declared in the given function.
List<ir.ClosureVariable> getClosureList(ExecutableElement element) {
return function2closures.putIfAbsent(element, () => <ir.ClosureVariable>[]);
}
IrBuilderClosureState(this.closureLocals) {
for (Local local in closureLocals) {
ExecutableElement context = local.executableContext;
ir.ClosureVariable variable = new ir.ClosureVariable(context, local);
local2closure[local] = variable;
getClosureList(context).add(variable);
}
}
}
/// Shared state between delimited IrBuilders within the same function.
class IrBuilderSharedState {
final ConstantSystem constantSystem;
/// A stack of collectors for breaks.
final List<JumpCollector> breakCollectors = <JumpCollector>[];
/// A stack of collectors for continues.
final List<JumpCollector> continueCollectors = <JumpCollector>[];
final List<ConstDeclaration> localConstants = <ConstDeclaration>[];
final ExecutableElement currentElement;
final ir.Continuation returnContinuation = new ir.Continuation.retrn();
final List<ir.Definition> functionParameters = <ir.Definition>[];
IrBuilderSharedState(this.constantSystem, this.currentElement);
}
/// A factory for building the cps IR.
class IrBuilder {
// TODO(johnniwinther): Make these field final and remove the default values
// when [IrBuilder] is a property of [IrBuilderVisitor] instead of a mixin.
final List<ir.Parameter> _parameters = <ir.Parameter>[];
final IrBuilderSharedState state;
final IrBuilderClosureState closure;
/// A map from variable indexes to their values.
Environment environment;
// 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;
IrBuilder(ConstantSystem constantSystem,
ExecutableElement currentElement,
Iterable<Entity> closureLocals)
: this.state = new IrBuilderSharedState(constantSystem, currentElement),
this.closure = new IrBuilderClosureState(closureLocals),
this.environment = new Environment.empty();
/// Construct a delimited visitor for visiting a subtree.
///
/// The delimited visitor has its own compile-time environment mapping
/// local variables to their values, which is initially a copy of the parent
/// environment. It has its own context for building an IR expression, so
/// the built expression is not plugged into the parent's context.
IrBuilder.delimited(IrBuilder parent)
: this.state = parent.state,
this.closure = parent.closure,
this.environment = new Environment.from(parent.environment);
/// Construct a visitor for a recursive continuation.
///
/// The recursive continuation builder has fresh parameters (i.e. SSA phis)
/// for all the local variables in the parent, because the invocation sites
/// of the continuation are not all known when the builder is created. The
/// recursive invocations will be passed values for all the local variables,
/// which may be eliminated later if they are redundant---if they take on
/// the same value at all invocation sites.
IrBuilder.recursive(IrBuilder parent)
: this.state = parent.state,
this.closure = parent.closure,
this.environment = new Environment.empty() {
parent.environment.index2variable.forEach(createLocalParameter);
}
/// Construct a builder for an inner function.
IrBuilder.innerFunction(IrBuilder parent,
ExecutableElement currentElement)
: this.state = new IrBuilderSharedState(parent.state.constantSystem,
currentElement),
this.closure = parent.closure,
this.environment = new Environment.empty();
bool get isOpen => _root == null || _current != null;
/// True if [element] is a local variable, local function, or parameter that
/// is accessed from an inner function. Recursive self-references in a local
/// function count as closure accesses.
///
/// If `true`, [element] is a [LocalElement].
bool isClosureVariable(Element element) {
return closure.closureLocals.contains(element);
}
/// Returns the [ClosureVariable] corresponding to the given variable.
/// Returns `null` for non-closure variables.
ir.ClosureVariable getClosureVariable(LocalElement element) {
return closure.local2closure[element];
}
/// Adds the given parameter to the function currently being built.
void createFunctionParameter(ParameterElement parameterElement) {
if (isClosureVariable(parameterElement)) {
state.functionParameters.add(getClosureVariable(parameterElement));
} else {
state.functionParameters.add(createLocalParameter(parameterElement));
}
}
/// Create a parameter for [parameterElement] and add it to the current
/// environment.
ir.Parameter createLocalParameter(LocalElement parameterElement) {
ir.Parameter parameter = new ir.Parameter(parameterElement);
_parameters.add(parameter);
environment.extend(parameterElement, parameter);
return parameter;
}
/// Add the constant [variableElement] to the environment with [value] as its
/// constant value.
void declareLocalConstant(LocalVariableElement variableElement,
ConstantExpression value) {
state.localConstants.add(new ConstDeclaration(variableElement, value));
}
/// Add [variableElement] to the environment with [initialValue] as its
/// initial value.
void declareLocalVariable(LocalVariableElement variableElement,
{ir.Primitive initialValue}) {
assert(isOpen);
if (initialValue == null) {
// TODO(kmillikin): Consider pooling constants.
// The initial value is null.
initialValue = buildNullLiteral();
}
if (isClosureVariable(variableElement)) {
add(new ir.SetClosureVariable(getClosureVariable(variableElement),
initialValue,
isDeclaration: true));
} else {
// In case a primitive was introduced for the initializer expression,
// use this variable element to help derive a good name for it.
initialValue.useElementAsHint(variableElement);
environment.extend(variableElement, initialValue);
}
}
/// Add [functionElement] to the environment with provided [definition].
void declareLocalFunction(LocalFunctionElement functionElement,
ir.FunctionDefinition definition) {
assert(isOpen);
if (isClosureVariable(functionElement)) {
ir.ClosureVariable variable = getClosureVariable(functionElement);
add(new ir.DeclareFunction(variable, definition));
} else {
ir.CreateFunction prim = new ir.CreateFunction(definition);
add(new ir.LetPrim(prim));
environment.extend(functionElement, prim);
prim.useElementAsHint(functionElement);
}
}
// 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);
}
}
ir.Primitive _continueWithExpression(ir.Expression build(ir.Continuation k)) {
ir.Parameter v = new ir.Parameter(null);
ir.Continuation k = new ir.Continuation([v]);
ir.Expression expression = build(k);
add(new ir.LetCont(k, expression));
return v;
}
ir.Primitive _buildInvokeStatic(Element element,
Selector selector,
List<ir.Primitive> arguments) {
assert(isOpen);
return _continueWithExpression(
(k) => new ir.InvokeStatic(element, selector, k, arguments));
}
ir.Primitive _buildInvokeSuper(Selector selector,
List<ir.Primitive> arguments) {
assert(isOpen);
return _continueWithExpression(
(k) => new ir.InvokeSuperMethod(selector, k, arguments));
}
ir.Primitive _buildInvokeDynamic(ir.Primitive receiver,
Selector selector,
List<ir.Primitive> arguments) {
assert(isOpen);
return _continueWithExpression(
(k) => new ir.InvokeMethod(receiver, selector, k, arguments));
}
ir.Primitive _buildInvokeCall(ir.Primitive target,
Selector selector,
List<ir.Definition> arguments) {
Selector callSelector = new Selector.callClosure(
selector.argumentCount,
selector.namedArguments);
return _buildInvokeDynamic(target, callSelector, arguments);
}
/// Create a constant literal from [constant].
ir.Constant buildConstantLiteral(ConstantExpression constant) {
assert(isOpen);
ir.Constant prim = new ir.Constant(constant);
add(new ir.LetPrim(prim));
return prim;
}
// Helper for building primitive literals.
ir.Constant _buildPrimitiveConstant(PrimitiveConstantValue constant) {
return buildConstantLiteral(new PrimitiveConstantExpression(constant));
}
/// Create an integer literal.
ir.Constant buildIntegerLiteral(int value) {
return _buildPrimitiveConstant(state.constantSystem.createInt(value));
}
/// Create an double literal.
ir.Constant buildDoubleLiteral(double value) {
return _buildPrimitiveConstant(state.constantSystem.createDouble(value));
}
/// Create an bool literal.
ir.Constant buildBooleanLiteral(bool value) {
return _buildPrimitiveConstant(state.constantSystem.createBool(value));
}
/// Create an null literal.
ir.Constant buildNullLiteral() {
return _buildPrimitiveConstant(state.constantSystem.createNull());
}
/// Create a string literal.
ir.Constant buildStringLiteral(String value) {
return _buildPrimitiveConstant(
state.constantSystem.createString(new ast.DartString.literal(value)));
}
/// Creates a non-constant list literal of the provided [type] and with the
/// provided [values].
ir.Primitive buildListLiteral(InterfaceType type,
Iterable<ir.Primitive> values) {
assert(isOpen);
ir.Primitive result = new ir.LiteralList(type, values);
add(new ir.LetPrim(result));
return result;
}
/// Creates a non-constant map literal of the provided [type] and with the
/// entries build from the [keys] and [values] using [build].
ir.Primitive buildMapLiteral(InterfaceType type,
Iterable keys,
Iterable values,
BuildFunction build) {
assert(isOpen);
List<ir.LiteralMapEntry> entries = <ir.LiteralMapEntry>[];
Iterator key = keys.iterator;
Iterator value = values.iterator;
while (key.moveNext() && value.moveNext()) {
entries.add(new ir.LiteralMapEntry(
build(key.current), build(value.current)));
}
assert(!key.moveNext() && !value.moveNext());
ir.Primitive result = new ir.LiteralMap(type, entries);
add(new ir.LetPrim(result));
return result;
}
/// Creates a conditional expression with the provided [condition] where the
/// then and else expression are created through the [buildThenExpression] and
/// [buildElseExpression] functions, respectively.
ir.Primitive buildConditional(
ir.Primitive condition,
ir.Primitive buildThenExpression(IrBuilder builder),
ir.Primitive buildElseExpression(IrBuilder builder)) {
assert(isOpen);
// The then and else expressions are delimited.
IrBuilder thenBuilder = new IrBuilder.delimited(this);
IrBuilder elseBuilder = new IrBuilder.delimited(this);
ir.Primitive thenValue = buildThenExpression(thenBuilder);
ir.Primitive elseValue = buildElseExpression(elseBuilder);
// Treat the values of the subexpressions as named values in the
// environment, so they will be treated as arguments to the join-point
// continuation.
assert(environment.length == thenBuilder.environment.length);
assert(environment.length == elseBuilder.environment.length);
thenBuilder.environment.extend(null, thenValue);
elseBuilder.environment.extend(null, elseValue);
JumpCollector jumps = new JumpCollector(null);
jumps.addJump(thenBuilder);
jumps.addJump(elseBuilder);
ir.Continuation joinContinuation =
createJoin(environment.length + 1, jumps);
// 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 thenContinuation = new ir.Continuation([]);
ir.Continuation elseContinuation = new ir.Continuation([]);
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 (thenValue == elseValue)
? thenValue
: joinContinuation.parameters.last;
}
/// Create a function expression from [definition].
ir.Primitive buildFunctionExpression(ir.FunctionDefinition definition) {
ir.CreateFunction prim = new ir.CreateFunction(definition);
add(new ir.LetPrim(prim));
return prim;
}
/**
* 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() {
if (!isOpen) return;
ir.Constant constant = buildNullLiteral();
add(new ir.InvokeContinuation(state.returnContinuation, [constant]));
_current = null;
}
ir.SuperInitializer makeSuperInitializer(ConstructorElement target,
List<ir.RunnableBody> arguments,
Selector selector) {
return new ir.SuperInitializer(target, arguments, selector);
}
ir.FieldInitializer makeFieldInitializer(FieldElement element,
ir.RunnableBody body) {
return new ir.FieldInitializer(element, body);
}
/// Create a [ir.FieldDefinition] for the current [Element] using [_root] as
/// the body using [initializer] as the initial value.
ir.FieldDefinition makeFieldDefinition(ir.Primitive initializer) {
if (initializer == null) {
return new ir.FieldDefinition.withoutInitializer(state.currentElement);
} else {
ir.RunnableBody body = makeRunnableBody(initializer);
return new ir.FieldDefinition(state.currentElement, body);
}
}
ir.RunnableBody makeRunnableBody([ir.Primitive value]) {
if (value == null) {
_ensureReturn();
} else {
buildReturn(value);
}
return new ir.RunnableBody(_root, state.returnContinuation);
}
/// Create a [ir.FunctionDefinition] for [element] using [_root] as the body.
///
/// Parameters must be created before the construction of the body using
/// [createFunctionParameter].
ir.FunctionDefinition makeFunctionDefinition(
List<ConstantExpression> defaults) {
FunctionElement element = state.currentElement;
if (element.isAbstract || element.isExternal) {
assert(invariant(element, _root == null,
message: "Non-empty body for abstract method $element: $_root"));
assert(invariant(element, state.localConstants.isEmpty,
message: "Local constants for abstract method $element: "
"${state.localConstants}"));
return new ir.FunctionDefinition.abstract(
element, state.functionParameters, defaults);
} else {
ir.RunnableBody body = makeRunnableBody();
return new ir.FunctionDefinition(
element, state.functionParameters, body,
state.localConstants, defaults, closure.getClosureList(element));
}
}
ir.ConstructorDefinition makeConstructorDefinition(
List<ConstantExpression> defaults, List<ir.Initializer> initializers) {
FunctionElement element = state.currentElement;
if (element.isExternal) {
assert(invariant(element, _root == null,
message: "Non-empty body for external constructor $element: $_root"));
assert(invariant(element, state.localConstants.isEmpty,
message: "Local constants for external constructor $element: "
"${state.localConstants}"));
return new ir.ConstructorDefinition.abstract(
element, state.functionParameters, defaults);
}
ir.RunnableBody body = makeRunnableBody();
return new ir.ConstructorDefinition(
element, state.functionParameters, body, initializers,
state.localConstants, defaults,
closure.getClosureList(element));
}
/// Create a super invocation where the method name and the argument structure
/// are defined by [selector] and the argument values are defined by
/// [arguments].
ir.Primitive buildSuperInvocation(Selector selector,
List<ir.Primitive> arguments) {
return _buildInvokeSuper(selector, arguments);
}
/// Create a getter invocation on the super class where the getter name is
/// defined by [selector].
ir.Primitive buildSuperGet(Selector selector) {
assert(selector.isGetter);
return _buildInvokeSuper(selector, const <ir.Primitive>[]);
}
/// Create a setter invocation on the super class where the setter name and
/// argument are defined by [selector] and [value], respectively.
ir.Primitive buildSuperSet(Selector selector, ir.Primitive value) {
assert(selector.isSetter);
_buildInvokeSuper(selector, <ir.Primitive>[value]);
return value;
}
/// Create an index set invocation on the super class with the provided
/// [index] and [value].
ir.Primitive buildSuperIndexSet(ir.Primitive index,
ir.Primitive value) {
_buildInvokeSuper(new Selector.indexSet(), <ir.Primitive>[index, value]);
return value;
}
/// Create a dynamic invocation on [receiver] where the method name and
/// argument structure are defined by [selector] and the argument values are
/// defined by [arguments].
ir.Primitive buildDynamicInvocation(ir.Primitive receiver,
Selector selector,
List<ir.Primitive> arguments) {
return _buildInvokeDynamic(receiver, selector, arguments);
}
/// Create a dynamic getter invocation on [receiver] where the getter name is
/// defined by [selector].
ir.Primitive buildDynamicGet(ir.Primitive receiver, Selector selector) {
assert(selector.isGetter);
return _buildInvokeDynamic(receiver, selector, const <ir.Primitive>[]);
}
/// Create a dynamic setter invocation on [receiver] where the setter name and
/// argument are defined by [selector] and [value], respectively.
ir.Primitive buildDynamicSet(ir.Primitive receiver,
Selector selector,
ir.Primitive value) {
assert(selector.isSetter);
_buildInvokeDynamic(receiver, selector, <ir.Primitive>[value]);
return value;
}
/// Create a dynamic index set invocation on [receiver] with the provided
/// [index] and [value].
ir.Primitive buildDynamicIndexSet(ir.Primitive receiver,
ir.Primitive index,
ir.Primitive value) {
_buildInvokeDynamic(
receiver, new Selector.indexSet(), <ir.Primitive>[index, value]);
return value;
}
/// Create a static invocation of [element] where argument structure is
/// defined by [selector] and the argument values are defined by [arguments].
ir.Primitive buildStaticInvocation(Element element,
Selector selector,
List<ir.Primitive> arguments) {
return _buildInvokeStatic(element, selector, arguments);
}
/// Create a static getter invocation of [element] where the getter name is
/// defined by [selector].
ir.Primitive buildStaticGet(Element element, Selector selector) {
assert(selector.isGetter);
// TODO(karlklose,sigurdm): build different nodes for getters.
return _buildInvokeStatic(element, selector, const <ir.Primitive>[]);
}
/// Create a static setter invocation of [element] where the setter name and
/// argument are defined by [selector] and [value], respectively.
ir.Primitive buildStaticSet(Element element,
Selector selector,
ir.Primitive value) {
assert(selector.isSetter);
// TODO(karlklose,sigurdm): build different nodes for setters.
_buildInvokeStatic(element, selector, <ir.Primitive>[value]);
return value;
}
/// Create a constructor invocation of [element] on [type] where the
/// constructor name and argument structure are defined by [selector] and the
/// argument values are defined by [arguments].
ir.Primitive buildConstructorInvocation(FunctionElement element,
Selector selector,
DartType type,
List<ir.Primitive> arguments) {
assert(isOpen);
return _continueWithExpression(
(k) => new ir.InvokeConstructor(type, element, selector, k, arguments));
}
/// Create a string concatenation of the [arguments].
ir.Primitive buildStringConcatenation(List<ir.Primitive> arguments) {
assert(isOpen);
return _continueWithExpression(
(k) => new ir.ConcatenateStrings(k, arguments));
}
/// Create a read access of [local].
ir.Primitive buildLocalGet(LocalElement local) {
assert(isOpen);
if (isClosureVariable(local)) {
ir.Primitive result =
new ir.GetClosureVariable(getClosureVariable(local));
add(new ir.LetPrim(result));
return result;
} else {
return environment.lookup(local);
}
}
/// Create a write access to [local] with the provided [value].
ir.Primitive buildLocalSet(LocalElement local, ir.Primitive value) {
assert(isOpen);
if (isClosureVariable(local)) {
add(new ir.SetClosureVariable(getClosureVariable(local), value));
} else {
value.useElementAsHint(local);
environment.update(local, value);
}
return value;
}
/// Create an invocation of [local] where the argument structure is defined
/// by [selector] and the argument values are defined by [arguments].
ir.Primitive buildLocalInvocation(LocalElement local,
Selector selector,
List<ir.Definition> arguments) {
ir.Primitive receiver;
if (isClosureVariable(local)) {
receiver = new ir.GetClosureVariable(getClosureVariable(local));
add(new ir.LetPrim(receiver));
} else {
receiver = environment.lookup(local);
}
return _buildInvokeCall(receiver, selector, arguments);
}
/// Create an invocation of the [functionExpression] where the argument
/// structure are defined by [selector] and the argument values are defined by
/// [arguments].
ir.Primitive buildFunctionExpressionInvocation(
ir.Primitive functionExpression,
Selector selector,
List<ir.Definition> arguments) {
return _buildInvokeCall(functionExpression, selector, arguments);
}
/// Creates an if-then-else statement with the provided [condition] where the
/// then and else branches are created through the [buildThenPart] and
/// [buildElsePart] functions, respectively.
///
/// An if-then statement is created if [buildElsePart] is a no-op.
// TODO(johnniwinther): Unify implementation with [buildConditional] and
// [_buildLogicalOperator].
void buildIf(ir.Primitive condition,
void buildThenPart(IrBuilder builder),
void buildElsePart(IrBuilder builder)) {
assert(isOpen);
// The then and else parts are delimited.
IrBuilder thenBuilder = new IrBuilder.delimited(this);
IrBuilder elseBuilder = new IrBuilder.delimited(this);
buildThenPart(thenBuilder);
buildElsePart(elseBuilder);
// 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;
ir.Continuation joinContinuation; // 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.
JumpCollector jumps = new JumpCollector(null);
jumps.addJump(thenBuilder);
jumps.addJump(elseBuilder);
joinContinuation = createJoin(environment.length, jumps);
result = new ir.LetCont(joinContinuation, result);
}
// 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 (joinContinuation == null) {
// At least one subexpression is closed.
if (thenBuilder.isOpen) {
_current =
(thenBuilder._root == null) ? letThen : thenBuilder._current;
environment = thenBuilder.environment;
} else if (elseBuilder.isOpen) {
_current =
(elseBuilder._root == null) ? letElse : elseBuilder._current;
environment = elseBuilder.environment;
} else {
_current = null;
}
}
}
/// Invoke a join-point continuation that contains arguments for all local
/// variables.
///
/// Given the continuation and a list of uninitialized invocations, fill
/// in each invocation with the continuation and appropriate arguments.
void invokeFullJoin(ir.Continuation join,
JumpCollector jumps,
{recursive: false}) {
join.isRecursive = recursive;
for (int i = 0; i < jumps.length; ++i) {
Environment currentEnvironment = jumps.environments[i];
ir.InvokeContinuation invoke = jumps.invocations[i];
invoke.continuation = new ir.Reference(join);
invoke.arguments = new List<ir.Reference>.generate(
join.parameters.length,
(i) => new ir.Reference(currentEnvironment[i]));
invoke.isRecursive = recursive;
}
}
/// Creates a for loop in which the initializer, condition, body, update are
/// created by [buildInitializer], [buildCondition], [buildBody] and
/// [buildUpdate], respectively.
///
/// The jump [target] is used to identify which `break` and `continue`
/// statements that have this `for` statement as their target.
void buildFor({SubbuildFunction buildInitializer,
SubbuildFunction buildCondition,
SubbuildFunction buildBody,
SubbuildFunction buildUpdate,
JumpTarget target}) {
assert(isOpen);
// For loops use four named continuations: the entry to the condition,
// the entry to the body, the loop exit, and the loop successor (break).
// The CPS translation of
// [[for (initializer; condition; update) body; successor]] is:
//
// [[initializer]];
// let cont loop(x, ...) =
// let prim cond = [[condition]] in
// let cont break() = [[successor]] in
// let cont exit() = break(v, ...) in
// let cont body() =
// let cont continue(x, ...) = [[update]]; loop(v, ...) in
// [[body]]; continue(v, ...) in
// branch cond (body, exit) in
// loop(v, ...)
//
// If there are no breaks in the body, the break continuation is inlined
// in the exit continuation (i.e., the translation of the successor
// statement occurs in the exit continuation). If there is only one
// invocation of the continue continuation (i.e., no continues in the
// body), the continue continuation is inlined in the body.
buildInitializer(this);
IrBuilder condBuilder = new IrBuilder.recursive(this);
ir.Primitive condition = buildCondition(condBuilder);
if (condition == null) {
// If the condition is empty then the body is entered unconditionally.
condition = condBuilder.buildBooleanLiteral(true);
}
JumpCollector breakCollector = new JumpCollector(target);
JumpCollector continueCollector = new JumpCollector(target);
state.breakCollectors.add(breakCollector);
state.continueCollectors.add(continueCollector);
IrBuilder bodyBuilder = new IrBuilder.delimited(condBuilder);
buildBody(bodyBuilder);
assert(state.breakCollectors.last == breakCollector);
assert(state.continueCollectors.last == continueCollector);
state.breakCollectors.removeLast();
state.continueCollectors.removeLast();
// The binding of the continue continuation should occur as late as
// possible, that is, at the nearest common ancestor of all the continue
// sites in the body. However, that is difficult to compute here, so it
// is instead placed just outside the body of the body continuation.
bool hasContinues = !continueCollector.isEmpty;
IrBuilder updateBuilder = hasContinues
? new IrBuilder.recursive(condBuilder)
: bodyBuilder;
buildUpdate(updateBuilder);
// Create body entry and loop exit continuations and a branch to them.
ir.Continuation bodyContinuation = new ir.Continuation([]);
ir.Continuation exitContinuation = new ir.Continuation([]);
ir.LetCont branch =
new ir.LetCont(exitContinuation,
new ir.LetCont(bodyContinuation,
new ir.Branch(new ir.IsTrue(condition),
bodyContinuation,
exitContinuation)));
// If there are breaks in the body, then there must be a join-point
// continuation for the normal exit and the breaks.
bool hasBreaks = !breakCollector.isEmpty;
ir.LetCont letJoin;
if (hasBreaks) {
letJoin = new ir.LetCont(null, branch);
condBuilder.add(letJoin);
condBuilder._current = branch;
} else {
condBuilder.add(branch);
}
ir.Continuation continueContinuation;
if (hasContinues) {
// If there are continues in the body, we need a named continue
// continuation as a join point.
continueContinuation = new ir.Continuation(updateBuilder._parameters);
if (bodyBuilder.isOpen) continueCollector.addJump(bodyBuilder);
invokeFullJoin(continueContinuation, continueCollector);
}
ir.Continuation loopContinuation =
new ir.Continuation(condBuilder._parameters);
if (updateBuilder.isOpen) {
JumpCollector backEdges = new JumpCollector(null);
backEdges.addJump(updateBuilder);
invokeFullJoin(loopContinuation, backEdges, recursive: true);
}
// Fill in the body and possible continue continuation bodies. Do this
// only after it is guaranteed that they are not empty.
if (hasContinues) {
continueContinuation.body = updateBuilder._root;
bodyContinuation.body =
new ir.LetCont(continueContinuation, bodyBuilder._root);
} else {
bodyContinuation.body = bodyBuilder._root;
}
loopContinuation.body = condBuilder._root;
add(new ir.LetCont(loopContinuation,
new ir.InvokeContinuation(loopContinuation,
environment.index2value)));
if (hasBreaks) {
_current = branch;
environment = condBuilder.environment;
breakCollector.addJump(this);
letJoin.continuation = createJoin(environment.length, breakCollector);
_current = letJoin;
} else {
_current = condBuilder._current;
environment = condBuilder.environment;
}
}
/// Creates a for-in loop, `for (v in e) b`.
///
/// [buildExpression] creates the expression, `e`. The variable, `v`, can
/// take one of three forms:
/// 1) `v` can be declared within the for-in statement, like in
/// `for (var v in e)`, in which case, [buildVariableDeclaration]
/// creates its declaration and [variableElement] is the element for
/// the declared variable,
/// 2) `v` is predeclared statically known variable, that is top-level,
/// static, or local variable, in which case [variableElement] is the
/// variable element, and [variableSelector] defines its write access,
/// 3) `v` is an instance variable in which case [variableSelector]
/// defines its write access.
/// [buildBody] creates the body, `b`, of the loop. The jump [target] is used
/// to identify which `break` and `continue` statements that have this for-in
/// statement as their target.
void buildForIn({SubbuildFunction buildExpression,
SubbuildFunction buildVariableDeclaration,
Element variableElement,
Selector variableSelector,
SubbuildFunction buildBody,
JumpTarget target}) {
// The for-in loop
//
// for (a in e) s;
//
// Is compiled analogously to:
//
// a = e.iterator;
// while (a.moveNext()) {
// var n0 = a.current;
// s;
// }
// The condition and body are delimited.
IrBuilder condBuilder = new IrBuilder.recursive(this);
ir.Primitive expressionReceiver = buildExpression(this);
List<ir.Primitive> emptyArguments = new List<ir.Primitive>();
ir.Parameter iterator = new ir.Parameter(null);
ir.Continuation iteratorInvoked = new ir.Continuation([iterator]);
add(new ir.LetCont(iteratorInvoked,
new ir.InvokeMethod(expressionReceiver,
new Selector.getter("iterator", null), iteratorInvoked,
emptyArguments)));
ir.Parameter condition = new ir.Parameter(null);
ir.Continuation moveNextInvoked = new ir.Continuation([condition]);
condBuilder.add(new ir.LetCont(moveNextInvoked,
new ir.InvokeMethod(iterator,
new Selector.call("moveNext", null, 0),
moveNextInvoked, emptyArguments)));
JumpCollector breakCollector = new JumpCollector(target);
JumpCollector continueCollector = new JumpCollector(target);
state.breakCollectors.add(breakCollector);
state.continueCollectors.add(continueCollector);
IrBuilder bodyBuilder = new IrBuilder.delimited(condBuilder);
if (buildVariableDeclaration != null) {
buildVariableDeclaration(bodyBuilder);
}
ir.Parameter currentValue = new ir.Parameter(null);
ir.Continuation currentInvoked = new ir.Continuation([currentValue]);
bodyBuilder.add(new ir.LetCont(currentInvoked,
new ir.InvokeMethod(iterator, new Selector.getter("current", null),
currentInvoked, emptyArguments)));
if (Elements.isLocal(variableElement)) {
bodyBuilder.buildLocalSet(variableElement, currentValue);
} else if (Elements.isStaticOrTopLevel(variableElement)) {
bodyBuilder.buildStaticSet(
variableElement, variableSelector, currentValue);
} else {
ir.Primitive receiver = bodyBuilder.buildThis();
bodyBuilder.buildDynamicSet(receiver, variableSelector, currentValue);
}
buildBody(bodyBuilder);
assert(state.breakCollectors.last == breakCollector);
assert(state.continueCollectors.last == continueCollector);
state.breakCollectors.removeLast();
state.continueCollectors.removeLast();
// Create body entry and loop exit continuations and a branch to them.
ir.Continuation bodyContinuation = new ir.Continuation([]);
ir.Continuation exitContinuation = new ir.Continuation([]);
ir.LetCont branch =
new ir.LetCont(exitContinuation,
new ir.LetCont(bodyContinuation,
new ir.Branch(new ir.IsTrue(condition),
bodyContinuation,
exitContinuation)));
// If there are breaks in the body, then there must be a join-point
// continuation for the normal exit and the breaks.
bool hasBreaks = !breakCollector.isEmpty;
ir.LetCont letJoin;
if (hasBreaks) {
letJoin = new ir.LetCont(null, branch);
condBuilder.add(letJoin);
condBuilder._current = branch;
} else {
condBuilder.add(branch);
}
ir.Continuation loopContinuation =
new ir.Continuation(condBuilder._parameters);
if (bodyBuilder.isOpen) continueCollector.addJump(bodyBuilder);
invokeFullJoin(
loopContinuation, continueCollector, recursive: true);
bodyContinuation.body = bodyBuilder._root;
loopContinuation.body = condBuilder._root;
add(new ir.LetCont(loopContinuation,
new ir.InvokeContinuation(loopContinuation,
environment.index2value)));
if (hasBreaks) {
_current = branch;
environment = condBuilder.environment;
breakCollector.addJump(this);
letJoin.continuation = createJoin(environment.length, breakCollector);
_current = letJoin;
} else {
_current = condBuilder._current;
environment = condBuilder.environment;
}
}
/// Creates a while loop in which the condition and body are created by
/// [buildCondition] and [buildBody], respectively.
///
/// The jump [target] is used to identify which `break` and `continue`
/// statements that have this `while` statement as their target.
void buildWhile({SubbuildFunction buildCondition,
SubbuildFunction buildBody,
JumpTarget target}) {
assert(isOpen);
// While loops use four named continuations: the entry to the body, the
// loop exit, the loop back edge (continue), and the loop exit (break).
// The CPS translation of [[while (condition) body; successor]] is:
//
// let cont continue(x, ...) =
// let prim cond = [[condition]] in
// let cont break() = [[successor]] in
// let cont exit() = break(v, ...) in
// let cont body() = [[body]]; continue(v, ...) in
// branch cond (body, exit) in
// continue(v, ...)
//
// If there are no breaks in the body, the break continuation is inlined
// in the exit continuation (i.e., the translation of the successor
// statement occurs in the exit continuation).
// The condition and body are delimited.
IrBuilder condBuilder = new IrBuilder.recursive(this);
ir.Primitive condition = buildCondition(condBuilder);
JumpCollector breakCollector = new JumpCollector(target);
JumpCollector continueCollector = new JumpCollector(target);
state.breakCollectors.add(breakCollector);
state.continueCollectors.add(continueCollector);
IrBuilder bodyBuilder = new IrBuilder.delimited(condBuilder);
buildBody(bodyBuilder);
assert(state.breakCollectors.last == breakCollector);
assert(state.continueCollectors.last == continueCollector);
state.breakCollectors.removeLast();
state.continueCollectors.removeLast();
// Create body entry and loop exit continuations and a branch to them.
ir.Continuation bodyContinuation = new ir.Continuation([]);
ir.Continuation exitContinuation = new ir.Continuation([]);
ir.LetCont branch =
new ir.LetCont(exitContinuation,
new ir.LetCont(bodyContinuation,
new ir.Branch(new ir.IsTrue(condition),
bodyContinuation,
exitContinuation)));
// If there are breaks in the body, then there must be a join-point
// continuation for the normal exit and the breaks.
bool hasBreaks = !breakCollector.isEmpty;
ir.LetCont letJoin;
if (hasBreaks) {
letJoin = new ir.LetCont(null, branch);
condBuilder.add(letJoin);
condBuilder._current = branch;
} else {
condBuilder.add(branch);
}
ir.Continuation loopContinuation =
new ir.Continuation(condBuilder._parameters);
if (bodyBuilder.isOpen) continueCollector.addJump(bodyBuilder);
invokeFullJoin(loopContinuation, continueCollector, recursive: true);
bodyContinuation.body = bodyBuilder._root;
loopContinuation.body = condBuilder._root;
add(new ir.LetCont(loopContinuation,
new ir.InvokeContinuation(loopContinuation,
environment.index2value)));
if (hasBreaks) {
_current = branch;
environment = condBuilder.environment;
breakCollector.addJump(this);
letJoin.continuation = createJoin(environment.length, breakCollector);
_current = letJoin;
} else {
_current = condBuilder._current;
environment = condBuilder.environment;
}
}
/// Create a return statement `return value;` or `return;` if [value] is
/// null.
void buildReturn([ir.Primitive value]) {
// 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.
assert(isOpen);
if (value == null) {
value = buildNullLiteral();
}
add(new ir.InvokeContinuation(state.returnContinuation, [value]));
_current = null;
}
/// Create a blocks of [statements] by applying [build] to all reachable
/// statements. The first statement is assumed to be reachable.
// TODO(johnniwinther): Type [statements] as `Iterable` when `NodeList` uses
// `List` instead of `Link`.
void buildBlock(var statements, BuildFunction build) {
// Build(Block(stamements), C) = C'
// where C' = statements.fold(Build, C)
assert(isOpen);
return buildSequence(statements, build);
}
/// Creates a sequence of [nodes] by applying [build] to all reachable nodes.
///
/// The first node in the sequence does not need to be reachable.
// TODO(johnniwinther): Type [nodes] as `Iterable` when `NodeList` uses
// `List` instead of `Link`.
void buildSequence(var nodes, BuildFunction build) {
for (var node in nodes) {
if (!isOpen) return;
build(node);
}
}
// Build(BreakStatement L, C) = C[InvokeContinuation(...)]
//
// The continuation and arguments are filled in later after translating
// the body containing the break.
bool buildBreak(JumpTarget target) {
return buildJumpInternal(target, state.breakCollectors);
}
// Build(ContinueStatement L, C) = C[InvokeContinuation(...)]
//
// The continuation and arguments are filled in later after translating
// the body containing the continue.
bool buildContinue(JumpTarget target) {
return buildJumpInternal(target, state.continueCollectors);
}
bool buildJumpInternal(JumpTarget target,
Iterable<JumpCollector> collectors) {
assert(isOpen);
for (JumpCollector collector in collectors) {
if (target == collector.target) {
collector.addJump(this);
return true;
}
}
return false;
}
/// Create a negation of [condition].
ir.Primitive buildNegation(ir.Primitive condition) {
// ! e is translated as e ? false : true
// Add a continuation parameter for the result of the expression.
ir.Parameter resultParameter = new ir.Parameter(null);
ir.Continuation joinContinuation = new ir.Continuation([resultParameter]);
ir.Continuation thenContinuation = new ir.Continuation([]);
ir.Continuation elseContinuation = new ir.Continuation([]);
ir.Constant makeBoolConstant(bool value) {
return new ir.Constant(new PrimitiveConstantExpression(
state.constantSystem.createBool(value)));
}
ir.Constant trueConstant = makeBoolConstant(true);
ir.Constant falseConstant = makeBoolConstant(false);
thenContinuation.body = new ir.LetPrim(falseConstant)
..plug(new ir.InvokeContinuation(joinContinuation, [falseConstant]));
elseContinuation.body = new ir.LetPrim(trueConstant)
..plug(new ir.InvokeContinuation(joinContinuation, [trueConstant]));
add(new ir.LetCont(joinContinuation,
new ir.LetCont(thenContinuation,
new ir.LetCont(elseContinuation,
new ir.Branch(new ir.IsTrue(condition),
thenContinuation,
elseContinuation)))));
return resultParameter;
}
/// Creates a type test or type cast of [receiver] against [type].
///
/// Set [isTypeTest] to `true` to create a type test and furthermore set
/// [isNotCheck] to `true` to create a negated type test.
ir.Primitive buildTypeOperator(ir.Primitive receiver,
DartType type,
{bool isTypeTest: false,
bool isNotCheck: false}) {
assert(isOpen);
assert(isTypeTest != null);
assert(!isNotCheck || isTypeTest);
ir.Primitive check = _continueWithExpression(
(k) => new ir.TypeOperator(receiver, type, k, isTypeTest: isTypeTest));
return isNotCheck ? buildNegation(check) : check;
}
/// Create a lazy and/or expression. [leftValue] is the value of the left
/// operand and [buildRightValue] is called to process the value of the right
/// operand in the context of its own [IrBuilder].
ir.Primitive buildLogicalOperator(
ir.Primitive leftValue,
ir.Primitive buildRightValue(IrBuilder builder),
{bool isLazyOr: false}) {
// 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.
IrBuilder rightBuilder = new IrBuilder.delimited(this);
ir.Primitive rightValue = buildRightValue(rightBuilder);
// A dummy empty target for the branch on the left subexpression branch.
// This enables using the same infrastructure for join-point continuations
// as in visitIf and visitConditional. It will hold a definition of the
// appropriate constant and an invocation of the join-point continuation.
IrBuilder emptyBuilder = new IrBuilder.delimited(this);
// Dummy empty targets for right true and right false. They hold
// definitions of the appropriate constant and an invocation of the
// join-point continuation.
IrBuilder rightTrueBuilder = new IrBuilder.delimited(rightBuilder);
IrBuilder rightFalseBuilder = new IrBuilder.delimited(rightBuilder);
// If we don't evaluate the right subexpression, the value of the whole
// expression is this constant.
ir.Constant leftBool = emptyBuilder.buildBooleanLiteral(isLazyOr);
// If we do evaluate the right subexpression, the value of the expression
// is a true or false constant.
ir.Constant rightTrue = rightTrueBuilder.buildBooleanLiteral(true);
ir.Constant rightFalse = rightFalseBuilder.buildBooleanLiteral(false);
// Treat the result values as named values in the environment, so they
// will be treated as arguments to the join-point continuation.
assert(environment.length == emptyBuilder.environment.length);
assert(environment.length == rightTrueBuilder.environment.length);
assert(environment.length == rightFalseBuilder.environment.length);
emptyBuilder.environment.extend(null, leftBool);
rightTrueBuilder.environment.extend(null, rightTrue);
rightFalseBuilder.environment.extend(null, rightFalse);
// Wire up two continuations for the left subexpression, two continuations
// for the right subexpression, and a three-way join continuation.
JumpCollector jumps = new JumpCollector(null);
jumps.addJump(emptyBuilder);
jumps.addJump(rightTrueBuilder);
jumps.addJump(rightFalseBuilder);
ir.Continuation joinContinuation =
createJoin(environment.length + 1, jumps);
ir.Continuation leftTrueContinuation = new ir.Continuation([]);
ir.Continuation leftFalseContinuation = new ir.Continuation([]);
ir.Continuation rightTrueContinuation = new ir.Continuation([]);
ir.Continuation rightFalseContinuation = new ir.Continuation([]);
rightTrueContinuation.body = rightTrueBuilder._root;
rightFalseContinuation.body = rightFalseBuilder._root;
// 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 (isLazyOr) {
leftTrueContinuation.body = emptyBuilder._root;
leftFalseContinuation.body = rightBuilder._root;
} else {
leftTrueContinuation.body = rightBuilder._root;
leftFalseContinuation.body = emptyBuilder._root;
}
add(new ir.LetCont(joinContinuation,
new ir.LetCont(leftTrueContinuation,
new ir.LetCont(leftFalseContinuation,
new ir.Branch(new ir.IsTrue(leftValue),
leftTrueContinuation,
leftFalseContinuation)))));
// There is always a join parameter for the result value, because it
// is different on at least two paths.
return joinContinuation.parameters.last;
}
/// Creates an access to `this`.
ir.Primitive buildThis() {
assert(isOpen);
ir.Primitive result = new ir.This();
add(new ir.LetPrim(result));
return result;
}
/// Create a non-recursive join-point continuation.
///
/// Given the environment length at the join point and a list of
/// jumps that should reach the join point, create a join-point
/// continuation. The join-point continuation has a parameter for each
/// variable that has different values reaching on different paths.
///
/// The jumps are uninitialized [ir.InvokeContinuation] expressions.
/// They are filled in with the target continuation and appropriate
/// arguments.
///
/// As a side effect, the environment of this builder is updated to include
/// the join-point continuation parameters.
ir.Continuation createJoin(int environmentLength, JumpCollector jumps) {
assert(jumps.length >= 2);
// Compute which values are identical on all paths reaching the join.
// Handle the common case of a pair of contexts efficiently.
Environment first = jumps.environments[0];
Environment second = jumps.environments[1];
assert(environmentLength <= first.length);
assert(environmentLength <= second.length);
assert(first.sameDomain(environmentLength, second));
// A running count of the join-point parameters.
int parameterCount = 0;
// The null elements of common correspond to required parameters of the
// join-point continuation.
List<ir.Primitive> common =
new List<ir.Primitive>.generate(environmentLength,
(i) {
ir.Primitive candidate = first[i];
if (second[i] == candidate) {
return candidate;
} else {
++parameterCount;
return null;
}
});
// If there is already a parameter for each variable, the other
// environments do not need to be considered.
if (parameterCount < environmentLength) {
for (int i = 0; i < environmentLength; ++i) {
ir.Primitive candidate = common[i];
if (candidate == null) continue;
for (Environment current in jumps.environments.skip(2)) {
assert(environmentLength <= current.length);
assert(first.sameDomain(environmentLength, current));
if (candidate != current[i]) {
common[i] = null;
++parameterCount;
break;
}
}
if (parameterCount >= environmentLength) break;
}
}
// Create the join point continuation.
List<ir.Parameter> parameters = <ir.Parameter>[];
parameters.length = parameterCount;
int index = 0;
for (int i = 0; i < environmentLength; ++i) {
if (common[i] == null) {
parameters[index++] = new ir.Parameter(first.index2variable[i]);
}
}
assert(index == parameterCount);
ir.Continuation join = new ir.Continuation(parameters);
// Fill in all the continuation invocations.
for (int i = 0; i < jumps.length; ++i) {
Environment currentEnvironment = jumps.environments[i];
ir.InvokeContinuation invoke = jumps.invocations[i];
// Sharing this.environment with one of the invocations will not do
// the right thing (this.environment has already been mutated).
List<ir.Reference> arguments = <ir.Reference>[];
arguments.length = parameterCount;
int index = 0;
for (int i = 0; i < environmentLength; ++i) {
if (common[i] == null) {
arguments[index++] = new ir.Reference(currentEnvironment[i]);
}
}
invoke.continuation = new ir.Reference(join);
invoke.arguments = arguments;
}
// Mutate this.environment to be the environment at the join point. Do
// this after adding the continuation invocations, because this.environment
// might be collected by the jump collector and so the old environment
// values are needed for the continuation invocation.
//
// Iterate to environment.length because environmentLength includes values
// outside the environment which are 'phantom' variables used for the
// values of expressions like &&, ||, and ?:.
index = 0;
for (int i = 0; i < environment.length; ++i) {
if (common[i] == null) {
environment.index2value[i] = parameters[index++];
}
}
return join;
}
}