blob: 7927ba0277d21597e7657f93bdfbc29cf98e419d [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 '../closure.dart' as closure;
import '../common.dart';
import '../common/names.dart' show Names, Selectors;
import '../compile_time_constants.dart' show BackendConstantEnvironment;
import '../constants/constant_system.dart';
import '../constants/values.dart' show ConstantValue, PrimitiveConstantValue;
import '../dart_types.dart';
import '../elements/elements.dart';
import '../io/source_information.dart';
import '../js/js.dart' as js
show
js,
objectLiteral,
Expression,
LiteralStatement,
Template,
InterpolatedExpression,
isIdentityTemplate;
import '../native/native.dart' show NativeBehavior;
import '../tree/tree.dart' as ast;
import '../types/types.dart' show TypeMask;
import '../universe/call_structure.dart' show CallStructure;
import '../universe/selector.dart' show Selector, SelectorKind;
import 'cps_ir_builder_task.dart' show GlobalProgramInformation;
import 'cps_ir_nodes.dart' as ir;
/// 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 locals to their environment index.
final Map<Local, int> variable2index;
/// A reverse map from environment indexes to the variable.
final List<Local> index2variable;
/// A map from environment indexes to their value.
final List<ir.Primitive> index2value;
Environment.empty()
: variable2index = <Local, int>{},
index2variable = <Local>[],
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<Local>.from(other.index2variable),
index2value = new List<ir.Primitive>.from(other.index2value);
/// Construct an environment that is shaped like another one but with a
/// fresh parameter for each variable.
///
/// The mapping from elements to indexes is shared, not copied.
Environment.fresh(Environment other)
: variable2index = other.variable2index,
index2variable = new List<Local>.from(other.index2variable),
index2value = other.index2variable.map((Local local) {
return new ir.Parameter(local);
}).toList();
get length => index2variable.length;
ir.Primitive operator [](int index) => index2value[index];
void extend(Local element, ir.Primitive value) {
// Assert that the name is not already in the environment. `null` is used
// as the name of anonymous variables.
assert(!variable2index.containsKey(element));
if (element != null) variable2index[element] = index2variable.length;
index2variable.add(element);
index2value.add(value);
}
/// Drop [count] values from the environment.
///
/// Return the previous last value in the environment for convenience.
ir.Primitive discard(int count) {
assert(count > 0);
assert(count <= index2variable.length);
ir.Primitive value = index2value.last;
// The map from variables to their index are shared, so we cannot remove
// the mapping in `variable2index`.
index2variable.length -= count;
index2value.length -= count;
return value;
}
ir.Primitive lookup(Local element) {
assert(invariant(element, variable2index.containsKey(element),
message: "Unknown variable: $element."));
return index2value[variable2index[element]];
}
void update(Local 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.
Local variable = index2variable[i];
if (variable != other.index2variable[i]) return false;
// A named variable maps to the same index in both environments.
if (variable != null) {
int index = variable2index[variable];
if (index == null || index != other.variable2index[variable]) {
return false;
}
}
}
return true;
}
bool contains(Local local) => variable2index.containsKey(local);
}
/// The abstract base class of objects that emit jumps to a continuation and
/// give a handle to the continuation and its environment.
abstract class JumpCollector {
final JumpTarget target;
ir.Continuation _continuation = null;
final Environment _continuationEnvironment;
final List<Iterable<LocalVariableElement>> _boxedTryVariables =
<Iterable<LocalVariableElement>>[];
/// Construct a collector for a given environment and optionally a target.
///
/// The environment is the one in effect at the point where the jump's
/// continuation will be bound. Continuations can take an extra argument
/// (see [addJump]).
JumpCollector(
this._continuationEnvironment, this.target, bool hasExtraArgument) {
if (hasExtraArgument) _continuationEnvironment.extend(null, null);
}
/// Construct a collector for collecting only return jumps.
///
/// There is no jump target, it is implicitly the exit from the function.
/// There is no environment at the destination.
JumpCollector.retrn(this._continuation)
: _continuationEnvironment = null,
target = null;
/// Construct a collector for collecting goto jumps.
///
/// There is no continuation or environment at the destination.
JumpCollector.goto(this.target) : _continuationEnvironment = null;
/// True if the collector has not recorded any jumps to its continuation.
bool get isEmpty;
/// The continuation encapsulated by this collector.
ir.Continuation get continuation;
/// The compile-time environment to be used for translating code in the body
/// of the continuation.
Environment get environment;
/// Emit a jump to the continuation for a given [IrBuilder].
///
/// Jumps can take a single extra argument. This is used to pass return
/// values to finally blocks for returns inside try/finally and to pass
/// values of expressions that have internal control flow to their join-point
/// continuations.
void addJump(IrBuilder builder,
[ir.Primitive value, SourceInformation sourceInformation]);
/// Add a set of variables that were boxed on entry to a try block.
///
/// All jumps from a try block to targets outside have to unbox the
/// variables that were boxed on entry before invoking the target
/// continuation. Call this function before translating a try block and
/// call [leaveTry] after translating it.
void enterTry(Iterable<LocalVariableElement> boxedOnEntry) {
// The boxed variables are maintained as a stack to make leaving easy.
_boxedTryVariables.add(boxedOnEntry);
}
/// Remove the most recently added set of variables boxed on entry to a try
/// block.
///
/// Call [enterTry] before translating a try block and call this function
/// after translating it.
void leaveTry() {
_boxedTryVariables.removeLast();
}
void _buildTryExit(IrBuilder builder) {
for (Iterable<LocalVariableElement> boxedOnEntry in _boxedTryVariables) {
for (LocalVariableElement variable in boxedOnEntry) {
assert(builder.isInMutableVariable(variable));
ir.Primitive value = builder.buildLocalGet(variable);
builder.environment.update(variable, value);
}
}
}
/// True if a jump inserted now will escape from a try block.
///
/// Concretely, this is true when [enterTry] has been called without
/// its corresponding [leaveTry] call.
bool get isEscapingTry => _boxedTryVariables.isNotEmpty;
}
/// A class to collect 'forward' jumps.
///
/// A forward jump to a continuation in the sense of the CPS translation is
/// a jump where the jump is emitted before any code in the body of the
/// continuation is translated. They have the property that continuation
/// parameters and the environment for the translation of the body can be
/// determined based on the invocations, before translating the body. A
/// [ForwardJumpCollector] can encapsulate a continuation where all the
/// jumps are forward ones.
///
/// Examples of forward jumps in the translation are join points of
/// if-then-else and breaks from loops.
///
/// The implementation strategy is that the collector collects invocation
/// sites and the environments at those sites. Then it constructs a
/// continuation 'on demand' after all the jumps are seen. It determines
/// continuation parameters, the environment for the translation of code in
/// the continuation body, and the arguments at the invocation site only
/// after all the jumps to the continuation are seen.
class ForwardJumpCollector extends JumpCollector {
final List<ir.InvokeContinuation> _invocations = <ir.InvokeContinuation>[];
final List<Environment> _invocationEnvironments = <Environment>[];
/// Construct a collector with a given base environment.
///
/// The base environment is the one in scope at the site that the
/// continuation represented by this collector will be bound. The
/// environment is copied by the collector. Subsequent mutation of the
/// original environment will not affect the collector.
ForwardJumpCollector(Environment environment,
{JumpTarget target, bool hasExtraArgument: false})
: super(new Environment.from(environment), target, hasExtraArgument);
bool get isEmpty => _invocations.isEmpty;
ir.Continuation get continuation {
if (_continuation == null) _setContinuation();
return _continuation;
}
Environment get environment {
if (_continuation == null) _setContinuation();
return _continuationEnvironment;
}
void addJump(IrBuilder builder,
[ir.Primitive value, SourceInformation sourceInformation]) {
assert(_continuation == null);
_buildTryExit(builder);
ir.InvokeContinuation invoke =
new ir.InvokeContinuation.uninitialized(isEscapingTry: isEscapingTry);
builder.add(invoke);
_invocations.add(invoke);
// Truncate the environment at the invocation site so it only includes
// values that will be continuation arguments. If an extra value is passed
// it will already be included in the continuation environment, but it is
// not present in the invocation environment.
int delta = builder.environment.length - _continuationEnvironment.length;
if (value != null) ++delta;
if (delta > 0) builder.environment.discard(delta);
if (value != null) builder.environment.extend(null, value);
_invocationEnvironments.add(builder.environment);
builder._current = null;
// TODO(kmillikin): Can we set builder.environment to null to make it
// less likely to mutate it?
}
void _setContinuation() {
assert(_continuation == null);
// We have seen all invocations of this continuation, and recorded the
// environment in effect at each invocation site.
// Compute the union of the assigned variables reaching the continuation.
//
// There is a continuation parameter for each environment variable
// that has a different value (from the environment in scope at the
// continuation binding) on some path. `_environment` is initially a copy
// of the environment in scope at the continuation binding. Compute the
// continuation parameters and add them to `_environment` so it will become
// the one in scope for the continuation body.
List<ir.Parameter> parameters = <ir.Parameter>[];
if (_invocationEnvironments.isNotEmpty) {
int length = _continuationEnvironment.length;
for (int varIndex = 0; varIndex < length; ++varIndex) {
for (Environment invocationEnvironment in _invocationEnvironments) {
assert(invocationEnvironment.sameDomain(
length, _continuationEnvironment));
if (invocationEnvironment[varIndex] !=
_continuationEnvironment[varIndex]) {
ir.Parameter parameter = new ir.Parameter(
_continuationEnvironment.index2variable[varIndex]);
_continuationEnvironment.index2value[varIndex] = parameter;
parameters.add(parameter);
break;
}
}
}
}
_continuation = new ir.Continuation(parameters);
// Compute the intersection of the parameters with the environments at
// each continuation invocation. Initialize the invocations.
for (int jumpIndex = 0; jumpIndex < _invocations.length; ++jumpIndex) {
Environment invocationEnvironment = _invocationEnvironments[jumpIndex];
List<ir.Reference> arguments = <ir.Reference>[];
int varIndex = 0;
for (ir.Parameter parameter in parameters) {
varIndex =
_continuationEnvironment.index2value.indexOf(parameter, varIndex);
arguments.add(new ir.Reference(invocationEnvironment[varIndex]));
}
ir.InvokeContinuation invocation = _invocations[jumpIndex];
invocation.continuationRef = new ir.Reference(_continuation);
invocation.argumentRefs = arguments;
}
}
}
/// A class to collect 'backward' jumps.
///
/// A backward jump to a continuation in the sense of the CPS translation is
/// a jump where some code in the body of the continuation is translated
/// before the jump is emitted. They have the property that the
/// continuation parameters and the environment for the translation of the
/// body must be determined before emitting all the invocations. A
/// [BackwardJumpCollector] can ecapsulate a continuation where some jumps
/// are backward ones.
///
/// Examples of backward jumps in the translation are the recursive
/// invocations of loop continuations.
///
/// The implementation strategy is that the collector inserts a continuation
/// parameter for each variable in scope at the entry to the continuation,
/// before emitting any jump to the continuation. When a jump is added, it
/// is given an argument for each continuation parameter.
class BackwardJumpCollector extends JumpCollector {
/// Construct a collector with a given base environment.
///
/// The base environment is the one in scope at the site that the
/// continuation represented by this collector will be bound. The
/// translation of the continuation body will use an environment with the
/// same shape, but with fresh continuation parameters for each variable.
BackwardJumpCollector(Environment environment,
{JumpTarget target, bool hasExtraArgument: false})
: super(new Environment.fresh(environment), target, hasExtraArgument) {
List<ir.Parameter> parameters =
new List<ir.Parameter>.from(_continuationEnvironment.index2value);
_continuation = new ir.Continuation(parameters, isRecursive: true);
}
bool isEmpty = true;
ir.Continuation get continuation => _continuation;
Environment get environment => _continuationEnvironment;
void addJump(IrBuilder builder,
[ir.Primitive value, SourceInformation sourceInformation]) {
assert(_continuation.parameters.length <= builder.environment.length);
isEmpty = false;
_buildTryExit(builder);
// Truncate the environment at the invocation site so it only includes
// values that will be continuation arguments. If an extra value is passed
// it will already be included in the continuation environment, but it is
// not present in the invocation environment.
int delta = builder.environment.length - _continuationEnvironment.length;
if (value != null) ++delta;
if (delta > 0) builder.environment.discard(delta);
if (value != null) builder.environment.extend(null, value);
builder.add(new ir.InvokeContinuation(
_continuation, builder.environment.index2value,
isRecursive: true, isEscapingTry: isEscapingTry));
builder._current = null;
}
}
/// Collect 'return' jumps.
///
/// A return jump is one that targets the return continuation of a function.
/// Thus, returns from inside try/finally are not return jumps because they are
/// intercepted by a block that contains the finally handler code.
class ReturnJumpCollector extends JumpCollector {
bool isEmpty = true;
ir.Continuation get continuation => _continuation;
Environment environment = null;
/// Construct a return jump collector for a given return continuation.
ReturnJumpCollector(ir.Continuation continuation) : super.retrn(continuation);
void addJump(IrBuilder builder,
[ir.Primitive value, SourceInformation sourceInformation]) {
isEmpty = false;
builder.add(new ir.InvokeContinuation(continuation, <ir.Primitive>[value],
isEscapingTry: isEscapingTry, sourceInformation: sourceInformation));
builder._current = null;
}
}
/// Collect 'goto' jumps, continue to a labeled case from within a switch.
///
/// These jumps are unrestricted within the switch. They can be forward or
/// backward. They are implemented by assigning to a state variable.
class GotoJumpCollector extends JumpCollector {
bool isEmpty = true;
final ir.Continuation continuation = null;
final Environment environment = null;
int _stateVariableIndex;
int _stateValue;
JumpCollector _breakJoin;
GotoJumpCollector(JumpTarget target, this._stateVariableIndex,
this._stateValue, this._breakJoin)
: super.goto(target);
void addJump(IrBuilder builder,
[ir.Primitive value, SourceInformation sourceInformation]) {
isEmpty = false;
ir.Primitive constant = builder.buildIntegerConstant(_stateValue);
builder.environment.index2value[_stateVariableIndex] = constant;
builder.jumpTo(_breakJoin);
}
}
/// 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 runs [f] in its context.
SubbuildFunction nested(f()) {
return (IrBuilder builder) => withBuilder(builder, f);
}
/// 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 delimited IrBuilders within the same function.
class IrBuilderSharedState {
final GlobalProgramInformation program;
final BackendConstantEnvironment constants;
ConstantSystem get constantSystem => constants.constantSystem;
/// A stack of collectors for breaks.
List<JumpCollector> breakCollectors = <JumpCollector>[];
/// A stack of collectors for continues.
List<JumpCollector> continueCollectors = <JumpCollector>[];
final ExecutableElement currentElement;
final ir.Continuation returnContinuation = new ir.Continuation.retrn();
/// The target of a return from the function.
///
/// A null value indicates that the target is the function's return
/// continuation. Otherwise, when inside the try block of try/finally
/// a return is intercepted to give a place to generate the finally code.
JumpCollector returnCollector;
/// Parameter holding the internal value of 'this' passed to the function.
///
/// For nested functions, this is *not* captured receiver, but the function
/// object itself.
ir.Parameter thisParameter;
/// If non-null, this refers to the receiver (`this`) in the enclosing method.
ir.Primitive enclosingThis;
final List<ir.Parameter> functionParameters = <ir.Parameter>[];
/// Maps boxed locals to their location. These locals are not part of
/// the environment.
final Map<Local, ClosureLocation> boxedVariables = {};
IrBuilderSharedState(this.program, this.constants, this.currentElement) {
returnCollector = new ReturnJumpCollector(returnContinuation);
}
}
class ThisParameterLocal implements Local {
final ExecutableElement executableContext;
ThisParameterLocal(this.executableContext);
String get name => 'this';
toString() => 'ThisParameterLocal($executableContext)';
}
/// The IR builder maintains an environment and an IR fragment.
///
/// The IR fragment is an expression with a hole in it. The hole represents
/// the focus where new expressions can be added. The fragment 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.
///
/// The environment maintains the reaching definition of each local variable,
/// including some synthetic locals such as [TypeVariableLocal].
///
/// Internally, IR builders also maintains a [JumpCollector] stack and tracks
/// which variables are currently boxed or held in a mutable local variable.
class IrBuilder {
final List<ir.Parameter> _parameters = <ir.Parameter>[];
final IrBuilderSharedState state;
/// A map from variable indexes to their values.
///
/// [BoxLocal]s map to their box. [LocalElement]s that are boxed are not
/// in the map; look up their [BoxLocal] instead.
Environment environment;
/// A map from mutable local variables to their [ir.MutableVariable]s.
///
/// Mutable variables are treated as boxed. Writes to them are observable
/// side effects.
Map<Local, ir.MutableVariable> mutableVariables;
ir.Expression root = null;
ir.Expression _current = null;
GlobalProgramInformation get program => state.program;
IrBuilder(GlobalProgramInformation program,
BackendConstantEnvironment constants, ExecutableElement currentElement)
: state = new IrBuilderSharedState(program, constants, currentElement),
environment = new Environment.empty(),
mutableVariables = <Local, ir.MutableVariable>{};
IrBuilder._internal(this.state, this.environment, this.mutableVariables);
/// Construct a delimited visitor for visiting a subtree.
///
/// Build a subterm that is not (yet) connected to the CPS term. The
/// delimited visitor has its own has its own context for building an IR
/// expression, so the built expression is not plugged into the parent's
/// context. It has its own compile-time environment mapping local
/// variables to their values. If an optional environment argument is
/// supplied, it is used as the builder's initial environment. Otherwise
/// the environment is initially a copy of the parent builder's environment.
IrBuilder makeDelimitedBuilder([Environment env = null]) {
return new IrBuilder._internal(
state,
env != null ? env : new Environment.from(environment),
mutableVariables);
}
/// True if [local] should currently be accessed from a [ir.MutableVariable].
bool isInMutableVariable(Local local) {
return mutableVariables.containsKey(local);
}
/// Creates a [ir.MutableVariable] for the given local.
void makeMutableVariable(Local local) {
mutableVariables[local] = new ir.MutableVariable(local);
}
/// Remove an [ir.MutableVariable] for a local.
///
/// Subsequent access to the local will be direct rather than through the
/// mutable variable.
void removeMutableVariable(Local local) {
mutableVariables.remove(local);
}
/// Gets the [MutableVariable] containing the value of [local].
ir.MutableVariable getMutableVariable(Local local) {
return mutableVariables[local];
}
bool get isOpen => root == null || _current != null;
List<ir.Primitive> buildFunctionHeader(Iterable<Local> parameters,
{ClosureScope closureScope, ClosureEnvironment env}) {
_createThisParameter();
_enterClosureEnvironment(env);
_enterScope(closureScope);
parameters.forEach(_createFunctionParameter);
return _parameters;
}
/// Creates a parameter for [local] and adds it to the current environment.
ir.Parameter _createLocalParameter(Local local) {
ir.Parameter parameter = new ir.Parameter(local);
_parameters.add(parameter);
environment.extend(local, parameter);
return parameter;
}
/// 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);
}
}
/// Create and add a new [LetPrim] for [primitive].
ir.Primitive addPrimitive(ir.Primitive primitive) {
add(new ir.LetPrim(primitive));
return primitive;
}
ir.Primitive buildInvokeStatic(Element element, Selector selector,
List<ir.Primitive> arguments, SourceInformation sourceInformation) {
assert(!element.isLocal);
assert(!element.isInstanceMember);
assert(isOpen);
if (program.isJsInterop(element)) {
return buildInvokeJsInteropMember(element, arguments, sourceInformation);
}
return addPrimitive(
new ir.InvokeStatic(element, selector, arguments, sourceInformation));
}
ir.Primitive _buildInvokeSuper(Element target, Selector selector,
List<ir.Primitive> arguments, SourceInformation sourceInformation) {
assert(target.isInstanceMember);
assert(isOpen);
return addPrimitive(new ir.InvokeMethodDirectly(
buildThis(), target, selector, arguments, sourceInformation));
}
ir.Primitive _buildInvokeDynamic(
ir.Primitive receiver,
Selector selector,
TypeMask mask,
List<ir.Primitive> arguments,
SourceInformation sourceInformation) {
assert(isOpen);
return addPrimitive(new ir.InvokeMethod(receiver, selector, mask, arguments,
sourceInformation: sourceInformation));
}
ir.Primitive _buildInvokeCall(
ir.Primitive target,
CallStructure callStructure,
TypeMask mask,
List<ir.Definition> arguments,
SourceInformation sourceInformation) {
Selector selector = callStructure.callSelector;
return _buildInvokeDynamic(
target, selector, mask, arguments, sourceInformation);
}
ir.Primitive buildStaticNoSuchMethod(Selector selector,
List<ir.Primitive> arguments, SourceInformation sourceInformation) {
ir.Primitive receiver = buildStringConstant('');
ir.Primitive name = buildStringConstant(selector.name);
ir.Primitive argumentList = buildListLiteral(null, arguments);
ir.Primitive expectedArgumentNames = buildNullConstant();
return buildStaticFunctionInvocation(
program.throwNoSuchMethod,
<ir.Primitive>[receiver, name, argumentList, expectedArgumentNames],
sourceInformation);
}
/// Create a [ir.Constant] from [value] and add it to the CPS term.
ir.Constant buildConstant(ConstantValue value,
{SourceInformation sourceInformation}) {
assert(isOpen);
return addPrimitive(
new ir.Constant(value, sourceInformation: sourceInformation));
}
/// Create an integer constant and add it to the CPS term.
ir.Constant buildIntegerConstant(int value) {
return buildConstant(state.constantSystem.createInt(value));
}
/// Create a double constant and add it to the CPS term.
ir.Constant buildDoubleConstant(double value) {
return buildConstant(state.constantSystem.createDouble(value));
}
/// Create a Boolean constant and add it to the CPS term.
ir.Constant buildBooleanConstant(bool value) {
return buildConstant(state.constantSystem.createBool(value));
}
/// Create a null constant and add it to the CPS term.
ir.Constant buildNullConstant() {
return buildConstant(state.constantSystem.createNull());
}
/// Create a string constant and add it to the CPS term.
ir.Constant buildStringConstant(String value) {
return buildConstant(
state.constantSystem.createString(new ast.DartString.literal(value)));
}
/// Create a string constant and add it to the CPS term.
ir.Constant buildDartStringConstant(ast.DartString value) {
return buildConstant(state.constantSystem.createString(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,
{TypeMask allocationSiteType}) {
assert(isOpen);
return addPrimitive(new ir.LiteralList(type, values.toList(),
allocationSiteType: allocationSiteType));
}
/// 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),
SourceInformation sourceInformation) {
assert(isOpen);
// The then and else expressions are delimited.
IrBuilder thenBuilder = makeDelimitedBuilder();
IrBuilder elseBuilder = makeDelimitedBuilder();
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. We know the environments are the right size because
// expressions cannot introduce variable bindings.
assert(environment.length == thenBuilder.environment.length);
assert(environment.length == elseBuilder.environment.length);
JumpCollector join =
new ForwardJumpCollector(environment, hasExtraArgument: true);
thenBuilder.jumpTo(join, thenValue);
elseBuilder.jumpTo(join, elseValue);
// Build the term
// let cont join(x, ..., result) = [] in
// let cont then() = [[thenPart]]; join(v, ...)
// and 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(
join.continuation,
new ir.LetCont.two(
thenContinuation,
elseContinuation,
new ir.Branch.strict(condition, thenContinuation, elseContinuation,
sourceInformation))));
environment = join.environment;
return environment.discard(1);
}
/**
* 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 = buildNullConstant();
add(new ir.InvokeContinuation(state.returnContinuation, [constant]));
_current = null;
}
/// Create a [ir.FunctionDefinition] using [root] as the body.
///
/// The protocol for building a function is:
/// 1. Call [buildFunctionHeader].
/// 2. Call `buildXXX` methods to build the body.
/// 3. Call [makeFunctionDefinition] to finish.
ir.FunctionDefinition makeFunctionDefinition(
SourceInformation sourceInformation) {
_ensureReturn();
return new ir.FunctionDefinition(state.currentElement, state.thisParameter,
state.functionParameters, state.returnContinuation, root,
sourceInformation: sourceInformation);
}
/// Create a invocation of the [method] on the super class where the call
/// structure is defined [callStructure] and the argument values are defined
/// by [arguments].
ir.Primitive buildSuperMethodInvocation(
MethodElement method,
CallStructure callStructure,
List<ir.Primitive> arguments,
SourceInformation sourceInformation) {
// TODO(johnniwinther): This shouldn't be necessary.
SelectorKind kind = Elements.isOperatorName(method.name)
? SelectorKind.OPERATOR
: SelectorKind.CALL;
Selector selector = new Selector(kind, method.memberName, callStructure);
return _buildInvokeSuper(method, selector, arguments, sourceInformation);
}
/// Create a read access of the [method] on the super class, i.e. a
/// closurization of [method].
ir.Primitive buildSuperMethodGet(
MethodElement method, SourceInformation sourceInformation) {
// TODO(johnniwinther): This should have its own ir node.
return _buildInvokeSuper(method, new Selector.getter(method.memberName),
const <ir.Primitive>[], sourceInformation);
}
/// Create a getter invocation of the [getter] on the super class.
ir.Primitive buildSuperGetterGet(
MethodElement getter, SourceInformation sourceInformation) {
// TODO(johnniwinther): This should have its own ir node.
return _buildInvokeSuper(getter, new Selector.getter(getter.memberName),
const <ir.Primitive>[], sourceInformation);
}
/// Create an setter invocation of the [setter] on the super class with
/// [value].
ir.Primitive buildSuperSetterSet(MethodElement setter, ir.Primitive value,
SourceInformation sourceInformation) {
// TODO(johnniwinther): This should have its own ir node.
_buildInvokeSuper(setter, new Selector.setter(setter.memberName),
<ir.Primitive>[value], sourceInformation);
return value;
}
/// Create an invocation of the index [method] on the super class with
/// the provided [index].
ir.Primitive buildSuperIndex(MethodElement method, ir.Primitive index,
SourceInformation sourceInformation) {
return _buildInvokeSuper(
method, new Selector.index(), <ir.Primitive>[index], sourceInformation);
}
/// Create an invocation of the index set [method] on the super class with
/// the provided [index] and [value].
ir.Primitive buildSuperIndexSet(MethodElement method, ir.Primitive index,
ir.Primitive value, SourceInformation sourceInformation) {
_buildInvokeSuper(method, new Selector.indexSet(),
<ir.Primitive>[index, value], sourceInformation);
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,
TypeMask mask,
List<ir.Primitive> arguments,
SourceInformation sourceInformation) {
return _buildInvokeDynamic(
receiver, selector, mask, arguments, sourceInformation);
}
/// Create a dynamic getter invocation on [receiver] where the getter name is
/// defined by [selector].
ir.Primitive buildDynamicGet(ir.Primitive receiver, Selector selector,
TypeMask mask, SourceInformation sourceInformation) {
assert(selector.isGetter);
FieldElement field = program.locateSingleField(selector, mask);
if (field != null) {
// If the world says this resolves to a unique field, then it MUST be
// treated as a field access, since the getter might not be emitted.
return buildFieldGet(receiver, field, sourceInformation);
} else {
return _buildInvokeDynamic(
receiver, selector, mask, const <ir.Primitive>[], sourceInformation);
}
}
/// 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,
TypeMask mask, ir.Primitive value, SourceInformation sourceInformation) {
assert(selector.isSetter);
FieldElement field = program.locateSingleField(selector, mask);
if (field != null) {
// If the world says this resolves to a unique field, then it MUST be
// treated as a field access, since the setter might not be emitted.
buildFieldSet(receiver, field, value, sourceInformation);
} else {
_buildInvokeDynamic(
receiver, selector, mask, <ir.Primitive>[value], sourceInformation);
}
return value;
}
/// Create a dynamic index set invocation on [receiver] with the provided
/// [index] and [value].
ir.Primitive buildDynamicIndexSet(
ir.Primitive receiver,
TypeMask mask,
ir.Primitive index,
ir.Primitive value,
SourceInformation sourceInformation) {
_buildInvokeDynamic(receiver, new Selector.indexSet(), mask,
<ir.Primitive>[index, value], sourceInformation);
return value;
}
/// Create an invocation of the local [function] where argument structure is
/// defined by [callStructure] and the argument values are defined by
/// [arguments].
ir.Primitive buildLocalFunctionInvocation(
LocalFunctionElement function,
CallStructure callStructure,
List<ir.Primitive> arguments,
SourceInformation sourceInformation) {
// TODO(johnniwinther): Maybe this should have its own ir node.
return buildCallInvocation(
buildLocalGet(function), callStructure, arguments, sourceInformation);
}
/// Create a static invocation of [function].
///
/// The arguments are not named and their values are defined by [arguments].
ir.Primitive buildStaticFunctionInvocation(MethodElement function,
List<ir.Primitive> arguments, SourceInformation sourceInformation) {
Selector selector = new Selector.call(
function.memberName, new CallStructure(arguments.length));
return buildInvokeStatic(function, selector, arguments, sourceInformation);
}
/// Create a getter invocation of the static [getter].
ir.Primitive buildStaticGetterGet(
MethodElement getter, SourceInformation sourceInformation) {
Selector selector = new Selector.getter(getter.memberName);
return buildInvokeStatic(
getter, selector, const <ir.Primitive>[], sourceInformation);
}
/// Create a write access to the static [field] with the [value].
ir.Primitive buildStaticFieldSet(FieldElement field, ir.Primitive value,
SourceInformation sourceInformation) {
addPrimitive(new ir.SetStatic(field, value, sourceInformation));
return value;
}
/// Create a setter invocation of the static [setter] with the [value].
ir.Primitive buildStaticSetterSet(MethodElement setter, ir.Primitive value,
SourceInformation sourceInformation) {
Selector selector = new Selector.setter(setter.memberName);
buildInvokeStatic(
setter, selector, <ir.Primitive>[value], sourceInformation);
return value;
}
/// Create an erroneous invocation where argument structure is defined by
/// [selector] and the argument values are defined by [arguments].
// TODO(johnniwinther): Make this more fine-grained.
ir.Primitive buildErroneousInvocation(Element element, Selector selector,
List<ir.Primitive> arguments, SourceInformation sourceInformation) {
// TODO(johnniwinther): This should have its own ir node.
return buildInvokeStatic(element, selector, arguments, sourceInformation);
}
/// Concatenate string values. The arguments must be strings.
ir.Primitive buildStringConcatenation(
List<ir.Primitive> arguments, SourceInformation sourceInformation) {
assert(isOpen);
return addPrimitive(new ir.ApplyBuiltinOperator(
ir.BuiltinOperator.StringConcatenate, arguments, sourceInformation));
}
/// Create an invocation of the `call` method of [functionExpression], where
/// the structure of arguments are given by [callStructure].
// TODO(johnniwinther): This should take a [TypeMask].
ir.Primitive buildCallInvocation(
ir.Primitive functionExpression,
CallStructure callStructure,
List<ir.Definition> arguments,
SourceInformation sourceInformation) {
return _buildInvokeCall(
functionExpression, callStructure, null, arguments, sourceInformation);
}
/// 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),
SourceInformation sourceInformation) {
assert(isOpen);
// The then and else parts are delimited.
IrBuilder thenBuilder = makeDelimitedBuilder();
IrBuilder elseBuilder = makeDelimitedBuilder();
buildThenPart(thenBuilder);
buildElsePart(elseBuilder);
// Build the term
// (Result =) let cont then() = [[thenPart]]
// and else() = [[elsePart]]
// in
// if condition (then, else)
ir.Continuation thenContinuation = new ir.Continuation([]);
ir.Continuation elseContinuation = new ir.Continuation([]);
// If exactly one of the then and else continuation bodies is open (i.e.,
// the other one has an exit on all paths), then Continuation.plug expects
// that continuation to be listed first. Arbitrarily use [then, else]
// order otherwise.
List<ir.Continuation> arms = !thenBuilder.isOpen && elseBuilder.isOpen
? <ir.Continuation>[elseContinuation, thenContinuation]
: <ir.Continuation>[thenContinuation, elseContinuation];
ir.Expression result = new ir.LetCont.many(
arms,
new ir.Branch.strict(
condition, thenContinuation, elseContinuation, sourceInformation));
JumpCollector join; // 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.
join = new ForwardJumpCollector(environment);
thenBuilder.jumpTo(join);
elseBuilder.jumpTo(join);
result = new ir.LetCont(join.continuation, 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 (join == null) {
// At least one subexpression is closed.
if (thenBuilder.isOpen) {
if (thenBuilder.root != null) _current = thenBuilder._current;
environment = thenBuilder.environment;
} else if (elseBuilder.isOpen) {
if (elseBuilder.root != null) _current = elseBuilder._current;
environment = elseBuilder.environment;
} else {
_current = null;
}
} else {
environment = join.environment;
}
}
void jumpTo(JumpCollector collector,
[ir.Primitive value, SourceInformation sourceInformation]) {
collector.addJump(this, value, sourceInformation);
}
void addRecursiveContinuation(BackwardJumpCollector collector) {
assert(environment.length == collector.environment.length);
add(new ir.LetCont(
collector.continuation,
new ir.InvokeContinuation(
collector.continuation, environment.index2value)));
environment = collector.environment;
}
/// 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.
///
/// The [closureScope] identifies variables that should be boxed in this loop.
/// This includes variables declared inside the body of the loop as well as
/// in the for-loop initializer.
///
/// [loopVariables] is the list of variables declared in the for-loop
/// initializer.
void buildFor(
{SubbuildFunction buildInitializer,
SubbuildFunction buildCondition,
SourceInformation conditionSourceInformation,
SubbuildFunction buildBody,
SubbuildFunction buildUpdate,
JumpTarget target,
ClosureScope closureScope,
List<LocalElement> loopVariables}) {
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:
//
// _enterForLoopInitializer();
// [[initializer]];
// let cont loop(x, ...) =
// let prim cond = [[condition]] in
// let cont break(x, ...) = [[successor]] in
// let cont exit() = break(v, ...) in
// let cont body() =
// _enterForLoopBody();
// let cont continue(x, ...) =
// _enterForLoopUpdate();
// [[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.
_enterForLoopInitializer(closureScope, loopVariables);
buildInitializer(this);
JumpCollector loop = new BackwardJumpCollector(environment);
addRecursiveContinuation(loop);
ir.Primitive condition = buildCondition(this);
if (condition == null) {
// If the condition is empty then the body is entered unconditionally.
condition = buildBooleanConstant(true);
}
JumpCollector breakCollector =
new ForwardJumpCollector(environment, target: target);
// Use a pair of builders for the body, one for the entry code if any
// and one for the body itself. We only decide whether to insert a
// continue continuation until after translating the body and there is no
// way to insert such a continuation between the entry code and the body
// if they are translated together.
IrBuilder outerBodyBuilder = makeDelimitedBuilder();
outerBodyBuilder._enterForLoopBody(closureScope, loopVariables);
JumpCollector continueCollector =
new ForwardJumpCollector(outerBodyBuilder.environment, target: target);
IrBuilder innerBodyBuilder = outerBodyBuilder.makeDelimitedBuilder();
state.breakCollectors.add(breakCollector);
state.continueCollectors.add(continueCollector);
buildBody(innerBodyBuilder);
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 translation of the loop body. In
// the case where there are no continues in the body, the updates are
// translated immediately after the body.
bool hasContinues = !continueCollector.isEmpty;
IrBuilder updateBuilder;
if (hasContinues) {
if (innerBodyBuilder.isOpen) innerBodyBuilder.jumpTo(continueCollector);
updateBuilder = makeDelimitedBuilder(continueCollector.environment);
} else {
updateBuilder = innerBodyBuilder;
}
updateBuilder._enterForLoopUpdate(closureScope, loopVariables);
buildUpdate(updateBuilder);
if (updateBuilder.isOpen) updateBuilder.jumpTo(loop);
// Connect the inner and outer body builders. This is done only after
// it is guaranteed that the updateBuilder has a non-empty term.
if (hasContinues) {
outerBodyBuilder.add(new ir.LetCont(
continueCollector.continuation, innerBodyBuilder.root));
continueCollector.continuation.body = updateBuilder.root;
} else {
outerBodyBuilder.add(innerBodyBuilder.root);
}
// Create loop exit and body entry continuations and a branch to them.
ir.Continuation exitContinuation = new ir.Continuation([]);
ir.Continuation bodyContinuation = new ir.Continuation([]);
bodyContinuation.body = outerBodyBuilder.root;
// Note the order of continuations: the first one is the one that will
// be filled by LetCont.plug.
ir.LetCont branch = new ir.LetCont.two(
exitContinuation,
bodyContinuation,
new ir.Branch.strict(condition, bodyContinuation, exitContinuation,
conditionSourceInformation));
// If there are breaks in the body, then there must be a join-point
// continuation for the normal exit and the breaks. Otherwise, the
// successor is translated in the hole in the exit continuation.
bool hasBreaks = !breakCollector.isEmpty;
ir.LetCont letBreak;
if (hasBreaks) {
IrBuilder exitBuilder = makeDelimitedBuilder();
exitBuilder.jumpTo(breakCollector);
exitContinuation.body = exitBuilder.root;
letBreak = new ir.LetCont(breakCollector.continuation, branch);
add(letBreak);
environment = breakCollector.environment;
} else {
add(branch);
}
}
/// 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,
TypeMask variableMask,
SourceInformation variableSetSourceInformation,
TypeMask currentMask,
SourceInformation currentSourceInformation,
TypeMask iteratorMask,
SourceInformation iteratorSourceInformation,
TypeMask moveNextMask,
SourceInformation moveNextSourceInformation,
SubbuildFunction buildBody,
JumpTarget target,
ClosureScope closureScope,
SourceInformation conditionSourceInformation}) {
// The for-in loop
//
// for (a in e) s;
//
// Is compiled analogously to:
//
// it = e.iterator;
// while (it.moveNext()) {
// var a = it.current;
// s;
// }
// Fill the current hole with:
// let prim expressionReceiver = [[e]] in
// let cont iteratorInvoked(iterator) =
// [ ]
// in expressionReceiver.iterator () iteratorInvoked
ir.Primitive expressionReceiver = buildExpression(this);
List<ir.Primitive> emptyArguments = <ir.Primitive>[];
ir.Primitive iterator = addPrimitive(new ir.InvokeMethod(
expressionReceiver, Selectors.iterator, iteratorMask, emptyArguments));
// Fill with:
// let cont loop(x, ...) =
// let cont moveNextInvoked(condition) =
// [ ]
// in iterator.moveNext () moveNextInvoked
// in loop(v, ...)
JumpCollector loop = new BackwardJumpCollector(environment, target: target);
addRecursiveContinuation(loop);
ir.Primitive condition = addPrimitive(new ir.InvokeMethod(
iterator, Selectors.moveNext, moveNextMask, emptyArguments));
// As a delimited term, build:
// <<BODY>> =
// _enterScope();
// [[variableDeclaration]]
// let cont currentInvoked(currentValue) =
// [[a = currentValue]];
// [ ]
// in iterator.current () currentInvoked
IrBuilder bodyBuilder = makeDelimitedBuilder();
bodyBuilder._enterScope(closureScope);
if (buildVariableDeclaration != null) {
buildVariableDeclaration(bodyBuilder);
}
ir.Primitive currentValue = bodyBuilder.addPrimitive(new ir.InvokeMethod(
iterator, Selectors.current, currentMask, emptyArguments,
sourceInformation: currentSourceInformation));
// TODO(johnniwinther): Extract this as a provided strategy.
if (Elements.isLocal(variableElement)) {
bodyBuilder.buildLocalVariableSet(
variableElement, currentValue, variableSetSourceInformation);
} else if (Elements.isError(variableElement) ||
Elements.isMalformed(variableElement)) {
Selector selector = new Selector.setter(
new Name(variableElement.name, variableElement.library));
List<ir.Primitive> value = <ir.Primitive>[currentValue];
// Note the comparison below. It can be the case that an element isError
// and isMalformed.
if (Elements.isError(variableElement)) {
bodyBuilder.buildStaticNoSuchMethod(
selector, value, variableSetSourceInformation);
} else {
bodyBuilder.buildErroneousInvocation(
variableElement, selector, value, variableSetSourceInformation);
}
} else if (Elements.isStaticOrTopLevel(variableElement)) {
if (variableElement.isField) {
bodyBuilder.addPrimitive(new ir.SetStatic(
variableElement, currentValue, variableSetSourceInformation));
} else {
bodyBuilder.buildStaticSetterSet(
variableElement, currentValue, variableSetSourceInformation);
}
} else {
ir.Primitive receiver = bodyBuilder.buildThis();
assert(receiver != null);
bodyBuilder.buildDynamicSet(receiver, variableSelector, variableMask,
currentValue, variableSetSourceInformation);
}
// Translate the body in the hole in the delimited term above, and add
// a jump to the loop if control flow is live after the body.
JumpCollector breakCollector =
new ForwardJumpCollector(environment, target: target);
state.breakCollectors.add(breakCollector);
state.continueCollectors.add(loop);
buildBody(bodyBuilder);
assert(state.breakCollectors.last == breakCollector);
assert(state.continueCollectors.last == loop);
state.breakCollectors.removeLast();
state.continueCollectors.removeLast();
if (bodyBuilder.isOpen) bodyBuilder.jumpTo(loop);
// Create body entry and loop exit continuations and a branch to them.
//
// let cont exit() = [ ]
// and body() = <<BODY>>
// in branch condition (body, exit)
ir.Continuation exitContinuation = new ir.Continuation([]);
ir.Continuation bodyContinuation = new ir.Continuation([]);
bodyContinuation.body = bodyBuilder.root;
// Note the order of continuations: the first one is the one that will
// be filled by LetCont.plug.
ir.LetCont branch = new ir.LetCont.two(
exitContinuation,
bodyContinuation,
new ir.Branch.strict(condition, bodyContinuation, exitContinuation,
conditionSourceInformation));
// If there are breaks in the body, then there must be a join-point
// continuation for the normal exit and the breaks. Otherwise, the
// successor is translated in the hole in the exit continuation.
bool hasBreaks = !breakCollector.isEmpty;
ir.LetCont letBreak;
if (hasBreaks) {
IrBuilder exitBuilder = makeDelimitedBuilder();
exitBuilder.jumpTo(breakCollector);
exitContinuation.body = exitBuilder.root;
letBreak = new ir.LetCont(breakCollector.continuation, branch);
add(letBreak);
environment = breakCollector.environment;
} else {
add(branch);
}
}
/// 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,
ClosureScope closureScope,
SourceInformation sourceInformation}) {
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(x, ...) = [[successor]] in
// let cont exit() = break(v, ...)
// and body() =
// _enterScope();
// [[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).
JumpCollector loop = new BackwardJumpCollector(environment, target: target);
addRecursiveContinuation(loop);
ir.Primitive condition = buildCondition(this);
JumpCollector breakCollector =
new ForwardJumpCollector(environment, target: target);
IrBuilder bodyBuilder = makeDelimitedBuilder();
bodyBuilder._enterScope(closureScope);
state.breakCollectors.add(breakCollector);
state.continueCollectors.add(loop);
buildBody(bodyBuilder);
assert(state.breakCollectors.last == breakCollector);
assert(state.continueCollectors.last == loop);
state.breakCollectors.removeLast();
state.continueCollectors.removeLast();
if (bodyBuilder.isOpen) bodyBuilder.jumpTo(loop);
// Create body entry and loop exit continuations and a branch to them.
ir.Continuation exitContinuation = new ir.Continuation([]);
ir.Continuation bodyContinuation = new ir.Continuation([]);
bodyContinuation.body = bodyBuilder.root;
// Note the order of continuations: the first one is the one that will
// be filled by LetCont.plug.
ir.LetCont branch = new ir.LetCont.two(
exitContinuation,
bodyContinuation,
new ir.Branch.strict(
condition, bodyContinuation, exitContinuation, sourceInformation));
// If there are breaks in the body, then there must be a join-point
// continuation for the normal exit and the breaks. Otherwise, the
// successor is translated in the hole in the exit continuation.
bool hasBreaks = !breakCollector.isEmpty;
ir.LetCont letBreak;
if (hasBreaks) {
IrBuilder exitBuilder = makeDelimitedBuilder();
exitBuilder.jumpTo(breakCollector);
exitContinuation.body = exitBuilder.root;
letBreak = new ir.LetCont(breakCollector.continuation, branch);
add(letBreak);
environment = breakCollector.environment;
} else {
add(branch);
}
}
/// Creates a do-while loop.
///
/// The body and condition are created by [buildBody] and [buildCondition].
/// The jump target [target] is the target of `break` and `continue`
/// statements in the body that have the loop as their target.
/// [closureScope] contains all the variables declared in the loop (but not
/// declared in some inner closure scope).
void buildDoWhile(
{SubbuildFunction buildBody,
SubbuildFunction buildCondition,
JumpTarget target,
ClosureScope closureScope,
SourceInformation sourceInformation}) {
assert(isOpen);
// The CPS translation of [[do body; while (condition); successor]] is:
//
// let cont break(x, ...) = [[successor]] in
// let cont rec loop(x, ...) =
// let cont continue(x, ...) =
// let prim cond = [[condition]] in
// let cont exit() = break(v, ...)
// and repeat() = loop(v, ...)
// in branch cond (repeat, exit)
// in [[body]]; continue(v, ...)
// in loop(v, ...)
IrBuilder loopBuilder = makeDelimitedBuilder();
JumpCollector loop =
new BackwardJumpCollector(loopBuilder.environment, target: target);
loopBuilder.addRecursiveContinuation(loop);
// Translate the body.
JumpCollector breakCollector =
new ForwardJumpCollector(environment, target: target);
JumpCollector continueCollector =
new ForwardJumpCollector(loopBuilder.environment, target: target);
IrBuilder bodyBuilder = loopBuilder.makeDelimitedBuilder();
bodyBuilder._enterScope(closureScope);
state.breakCollectors.add(breakCollector);
state.continueCollectors.add(continueCollector);
buildBody(bodyBuilder);
assert(state.breakCollectors.last == breakCollector);
assert(state.continueCollectors.last == continueCollector);
state.breakCollectors.removeLast();
state.continueCollectors.removeLast();
if (bodyBuilder.isOpen) bodyBuilder.jumpTo(continueCollector);
// Construct the body of the continue continuation (i.e., the condition).
// <Continue> =
// let prim cond = [[condition]] in
// let cont exit() = break(v, ...)
// and repeat() = loop(v, ...)
// in branch cond (repeat, exit)
IrBuilder continueBuilder = loopBuilder.makeDelimitedBuilder();
continueBuilder.environment = continueCollector.environment;
ir.Primitive condition = buildCondition(continueBuilder);
ir.Continuation exitContinuation = new ir.Continuation([]);
IrBuilder exitBuilder = continueBuilder.makeDelimitedBuilder();
exitBuilder.jumpTo(breakCollector);
exitContinuation.body = exitBuilder.root;
ir.Continuation repeatContinuation = new ir.Continuation([]);
IrBuilder repeatBuilder = continueBuilder.makeDelimitedBuilder();
repeatBuilder.jumpTo(loop);
repeatContinuation.body = repeatBuilder.root;
continueBuilder.add(new ir.LetCont.two(
exitContinuation,
repeatContinuation,
new ir.Branch.strict(condition, repeatContinuation, exitContinuation,
sourceInformation)));
continueCollector.continuation.body = continueBuilder.root;
// Construct the loop continuation (i.e., the body and condition).
// <Loop> =
// let cont continue(x, ...) =
// <Continue>
// in [[body]]; continue(v, ...)
loopBuilder
.add(new ir.LetCont(continueCollector.continuation, bodyBuilder.root));
// And tie it all together.
add(new ir.LetCont(breakCollector.continuation, loopBuilder.root));
environment = breakCollector.environment;
}
void buildSimpleSwitch(JumpCollector join, List<SwitchCaseInfo> cases,
SubbuildFunction buildDefaultBody) {
IrBuilder casesBuilder = makeDelimitedBuilder();
for (SwitchCaseInfo caseInfo in cases) {
ir.Primitive condition = caseInfo.buildCondition(casesBuilder);
IrBuilder thenBuilder = makeDelimitedBuilder();
caseInfo.buildBody(thenBuilder);
ir.Continuation thenContinuation = new ir.Continuation([]);
thenContinuation.body = thenBuilder.root;
ir.Continuation elseContinuation = new ir.Continuation([]);
// A LetCont.two term has a hole as the body of the first listed
// continuation, to be plugged by the translation. Therefore put the
// else continuation first.
casesBuilder.add(new ir.LetCont.two(
elseContinuation,
thenContinuation,
new ir.Branch.strict(condition, thenContinuation, elseContinuation,
caseInfo.sourceInformation)));
}
if (buildDefaultBody == null) {
casesBuilder.jumpTo(join);
} else {
buildDefaultBody(casesBuilder);
}
if (!join.isEmpty) {
add(new ir.LetCont(join.continuation, casesBuilder.root));
environment = join.environment;
} else if (casesBuilder.root != null) {
add(casesBuilder.root);
_current = casesBuilder._current;
environment = casesBuilder.environment;
} else {
// The translation of the cases did not emit any code.
}
}
/// Utility function to translate try/catch into the IR.
///
/// The translation treats try/finally and try/catch/finally as if they
/// were macro-expanded into try/catch. This utility function generates
/// that try/catch. The function is parameterized over a list of variables
/// that should be boxed on entry to the try, and over functions to emit
/// code for entering the try, building the try body, leaving the try body,
/// building the catch body, and leaving the entire try/catch.
///
/// Please see the function's implementation for where these functions are
/// called.
void _helpBuildTryCatch(
TryStatementInfo variables,
void enterTry(IrBuilder builder),
SubbuildFunction buildTryBlock,
void leaveTry(IrBuilder builder),
List<ir.Parameter> buildCatch(IrBuilder builder, JumpCollector join),
void leaveTryCatch(
IrBuilder builder, JumpCollector join, ir.Expression body)) {
JumpCollector join = new ForwardJumpCollector(environment);
IrBuilder tryCatchBuilder = makeDelimitedBuilder();
// Variables treated as mutable in a try are not mutable outside of it.
// Work with a copy of the outer builder's mutable variables.
tryCatchBuilder.mutableVariables =
new Map<Local, ir.MutableVariable>.from(mutableVariables);
for (LocalVariableElement variable in variables.boxedOnEntry) {
assert(!tryCatchBuilder.isInMutableVariable(variable));
ir.Primitive value = tryCatchBuilder.buildLocalGet(variable);
tryCatchBuilder.makeMutableVariable(variable);
tryCatchBuilder.declareLocalVariable(variable, initialValue: value);
}
IrBuilder tryBuilder = tryCatchBuilder.makeDelimitedBuilder();
enterTry(tryBuilder);
buildTryBlock(tryBuilder);
if (tryBuilder.isOpen) {
join.enterTry(variables.boxedOnEntry);
tryBuilder.jumpTo(join);
join.leaveTry();
}
leaveTry(tryBuilder);
IrBuilder catchBuilder = tryCatchBuilder.makeDelimitedBuilder();
for (LocalVariableElement variable in variables.boxedOnEntry) {
assert(catchBuilder.isInMutableVariable(variable));
ir.Primitive value = catchBuilder.buildLocalGet(variable);
// After this point, the variables that were boxed on entry to the try
// are no longer treated as mutable.
catchBuilder.removeMutableVariable(variable);
catchBuilder.environment.update(variable, value);
}
List<ir.Parameter> catchParameters = buildCatch(catchBuilder, join);
ir.Continuation catchContinuation = new ir.Continuation(catchParameters);
catchContinuation.body = catchBuilder.root;
tryCatchBuilder.add(new ir.LetHandler(catchContinuation, tryBuilder.root));
leaveTryCatch(this, join, tryCatchBuilder.root);
}
/// Translates a try/catch.
///
/// [variables] provides information on local variables declared and boxed
/// within the try body.
/// [buildTryBlock] builds the try block.
/// [catchClauseInfos] provides access to the catch type, exception variable,
/// and stack trace variable, and a function for building the catch block.
void buildTryCatch(TryStatementInfo variables, SubbuildFunction buildTryBlock,
List<CatchClauseInfo> catchClauseInfos) {
assert(isOpen);
// Catch handlers are in scope for their body. The CPS translation of
// [[try tryBlock catch (ex, st) catchBlock; successor]] is:
//
// let cont join(v0, v1, ...) = [[successor]] in
// let mutable m0 = x0 in
// let mutable m1 = x1 in
// ...
// let handler catch_(ex, st) =
// let prim p0 = GetMutable(m0) in
// let prim p1 = GetMutable(m1) in
// ...
// [[catchBlock]]
// join(p0, p1, ...)
// in
// [[tryBlock]]
// let prim p0' = GetMutable(m0) in
// let prim p1' = GetMutable(m1) in
// ...
// join(p0', p1', ...)
//
// In other words, both the try and catch block are in the scope of the
// join-point continuation, and they are both in the scope of a sequence
// of mutable bindings for the variables assigned in the try. The join-
// point continuation is not in the scope of these mutable bindings.
// The tryBlock is in the scope of a binding for the catch handler. Each
// instruction (specifically, each call) in the tryBlock is in the dynamic
// scope of the handler. The mutable bindings are dereferenced at the end
// of the try block and at the beginning of the catch block, so the
// variables are unboxed in the catch block and at the join point.
void enterTry(IrBuilder builder) {
// On entry to try of try/catch, update the builder's state to reflect the
// variables that have been boxed.
void interceptJump(JumpCollector collector) {
collector.enterTry(variables.boxedOnEntry);
}
builder.state.breakCollectors.forEach(interceptJump);
builder.state.continueCollectors.forEach(interceptJump);
interceptJump(builder.state.returnCollector);
}
void leaveTry(IrBuilder builder) {
// On exit from try of try/catch, update the builder's state to reflect
// the variables that are no longer boxed.
void restoreJump(JumpCollector collector) {
collector.leaveTry();
}
builder.state.breakCollectors.forEach(restoreJump);
builder.state.continueCollectors.forEach(restoreJump);
restoreJump(builder.state.returnCollector);
}
List<ir.Parameter> buildCatch(IrBuilder builder, JumpCollector join) {
// Translate the catch clauses. Multiple clauses are translated as if
// they were explicitly cascaded if/else type tests.
// Handlers are always translated as having both exception and stack trace
// parameters. Multiple clauses do not have to use the same names for
// them. Choose the first of each as the name hint for the respective
// handler parameter.
ir.Parameter exceptionParameter =
new ir.Parameter(catchClauseInfos.first.exceptionVariable);
LocalVariableElement traceVariable;
CatchClauseInfo catchAll;
for (int i = 0; i < catchClauseInfos.length; ++i) {
CatchClauseInfo info = catchClauseInfos[i];
if (info.type == null) {
catchAll = info;
catchClauseInfos.length = i;
break;
}
if (traceVariable == null) {
traceVariable = info.stackTraceVariable;
}
}
ir.Parameter traceParameter = new ir.Parameter(traceVariable);
ir.Expression buildCatchClause(CatchClauseInfo clause) {
IrBuilder clauseBuilder = builder.makeDelimitedBuilder();
if (clause.exceptionVariable != null) {
clauseBuilder.declareLocalVariable(clause.exceptionVariable,
initialValue: exceptionParameter);
}
if (clause.stackTraceVariable != null) {
clauseBuilder.declareLocalVariable(clause.stackTraceVariable,
initialValue: traceParameter);
}
clause.buildCatchBlock(clauseBuilder);
if (clauseBuilder.isOpen) clauseBuilder.jumpTo(join);
return clauseBuilder.root;
}
// Expand multiple catch clauses into an explicit if/then/else. Iterate
// them in reverse so the current block becomes the next else block.
ir.Expression catchBody =
(catchAll == null) ? new ir.Rethrow() : buildCatchClause(catchAll);
for (CatchClauseInfo clause in catchClauseInfos.reversed) {
ir.Continuation thenContinuation = new ir.Continuation([]);
ir.Continuation elseContinuation = new ir.Continuation([]);
thenContinuation.body = buildCatchClause(clause);
elseContinuation.body = catchBody;
// Build the type test guarding this clause. We can share the
// environment with the nested builder because this part cannot mutate
// it.
IrBuilder checkBuilder = builder.makeDelimitedBuilder(environment);
ir.Primitive typeMatches = checkBuilder.buildTypeOperator(
exceptionParameter, clause.type, clause.sourceInformation,
isTypeTest: true);
checkBuilder.add(new ir.LetCont.two(
thenContinuation,
elseContinuation,
new ir.Branch.strict(typeMatches, thenContinuation,
elseContinuation, clause.sourceInformation)));
catchBody = checkBuilder.root;
}
builder.add(catchBody);
return <ir.Parameter>[exceptionParameter, traceParameter];
}
void leaveTryCatch(
IrBuilder builder, JumpCollector join, ir.Expression body) {
// Add the binding for the join-point continuation and continue the
// translation in its body.
builder.add(new ir.LetCont(join.continuation, body));
builder.environment = join.environment;
}
_helpBuildTryCatch(variables, enterTry, buildTryBlock, leaveTry, buildCatch,
leaveTryCatch);
}
/// Translates a try/finally.
///
/// [variables] provides information on local variables declared and boxed
/// within the try body.
/// [buildTryBlock] builds the try block.
/// [buildFinallyBlock] builds the finally block.
void buildTryFinally(TryStatementInfo variables,
SubbuildFunction buildTryBlock, SubbuildFunction buildFinallyBlock) {
assert(isOpen);
// Try/finally is implemented in terms of try/catch and by duplicating the
// code for finally at all exits. The encoding is:
//
// try tryBlock finally finallyBlock
// ==>
// try tryBlock catch (ex, st) { finallyBlock; rethrow } finallyBlock
//
// Where in tryBlock, all of the break, continue, and return exits are
// translated as jumps to continuations (bound outside the catch handler)
// that include the finally code followed by a break, continue, or
// return respectively.
List<JumpCollector> savedBreaks, newBreaks, savedContinues, newContinues;
JumpCollector savedReturn, newReturn;
void enterTry(IrBuilder builder) {
// On entry to the try of try/finally, update the builder's state to
// relfect the variables that have been boxed. Then intercept all break,
// continue, and return jumps out of the try so that they can go to
// continuations that include the finally code.
JumpCollector interceptJump(JumpCollector collector) {
JumpCollector result =
new ForwardJumpCollector(environment, target: collector.target);
result.enterTry(variables.boxedOnEntry);
return result;
}
savedBreaks = builder.state.breakCollectors;
savedContinues = builder.state.continueCollectors;
savedReturn = builder.state.returnCollector;
builder.state.breakCollectors =
newBreaks = savedBreaks.map(interceptJump).toList();
builder.state.continueCollectors =
newContinues = savedContinues.map(interceptJump).toList();
builder.state.returnCollector = newReturn =
new ForwardJumpCollector(environment, hasExtraArgument: true)
..enterTry(variables.boxedOnEntry);
}
void leaveTry(IrBuilder builder) {
// On exit from the try of try/finally, update the builder's state to
// reflect the variables that are no longer boxed and restore the
// original, unintercepted break, continue, and return targets.
void restoreJump(JumpCollector collector) {
collector.leaveTry();
}
newBreaks.forEach(restoreJump);
newContinues.forEach(restoreJump);
newReturn.leaveTry();
builder.state.breakCollectors = savedBreaks;
builder.state.continueCollectors = savedContinues;
builder.state.returnCollector = savedReturn;
}
List<ir.Parameter> buildCatch(IrBuilder builder, JumpCollector join) {
// The catch block of the try/catch used for try/finally is the finally
// code followed by a rethrow.
buildFinallyBlock(builder);
if (builder.isOpen) {
builder.add(new ir.Rethrow());
builder._current = null;
}
return <ir.Parameter>[new ir.Parameter(null), new ir.Parameter(null)];
}
void leaveTryCatch(
IrBuilder builder, JumpCollector join, ir.Expression body) {
// Build a list of continuations for jumps from the try block and
// duplicate the finally code before jumping to the actual target.
List<ir.Continuation> exits = <ir.Continuation>[join.continuation];
void addJump(
JumpCollector newCollector, JumpCollector originalCollector) {
if (newCollector.isEmpty) return;
IrBuilder builder = makeDelimitedBuilder(newCollector.environment);
buildFinallyBlock(builder);
if (builder.isOpen) builder.jumpTo(originalCollector);
newCollector.continuation.body = builder.root;
exits.add(newCollector.continuation);
}
for (int i = 0; i < newBreaks.length; ++i) {
addJump(newBreaks[i], savedBreaks[i]);
}
for (int i = 0; i < newContinues.length; ++i) {
addJump(newContinues[i], savedContinues[i]);
}
if (!newReturn.isEmpty) {
IrBuilder builder = makeDelimitedBuilder(newReturn.environment);
ir.Primitive value = builder.environment.discard(1);
buildFinallyBlock(builder);
if (builder.isOpen) builder.buildReturn(value: value);
newReturn.continuation.body = builder.root;
exits.add(newReturn.continuation);
}
builder.add(new ir.LetCont.many(exits, body));
builder.environment = join.environment;
buildFinallyBlock(builder);
}
_helpBuildTryCatch(variables, enterTry, buildTryBlock, leaveTry, buildCatch,
leaveTryCatch);
}
/// Create a return statement `return value;` or `return;` if [value] is
/// null.
void buildReturn({ir.Primitive value, SourceInformation sourceInformation}) {
// 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 = buildNullConstant();
}
jumpTo(state.returnCollector, value, sourceInformation);
}
/// Generate the body for a native function [function] that is annotated with
/// an implementation in JavaScript (provided as string in [javaScriptCode]).
void buildNativeFunctionBody(FunctionElement function, String javaScriptCode,
SourceInformation sourceInformation) {
NativeBehavior behavior = new NativeBehavior();
behavior.sideEffects.setAllSideEffects();
// Generate a [ForeignCode] statement from the given native code.
buildForeignCode(
js.js
.statementTemplateYielding(new js.LiteralStatement(javaScriptCode)),
<ir.Primitive>[],
behavior,
sourceInformation);
}
/// Generate the body for a native function that redirects to a native
/// JavaScript function, getter, or setter.
///
/// Generates a call to the real target, which is given by [functions]'s
/// `fixedBackendName`, passing all parameters as arguments. The target can
/// be the JavaScript implementation of a function, getter, or setter.
void buildRedirectingNativeFunctionBody(FunctionElement function, String name,
SourceInformation sourceInformation) {
List<ir.Primitive> arguments = <ir.Primitive>[];
NativeBehavior behavior = new NativeBehavior();
behavior.sideEffects.setAllSideEffects();
program.addNativeMethod(function);
// Construct the access of the target element.
String code = function.isInstanceMember ? '#.$name' : name;
if (function.isInstanceMember) {
arguments.add(state.thisParameter);
}
// Collect all parameters of the function and templates for them to be
// inserted into the JavaScript code.
List<String> argumentTemplates = <String>[];
function.functionSignature.forEachParameter((ParameterElement parameter) {
ir.Primitive input = environment.lookup(parameter);
DartType type = program.unaliasType(parameter.type);
if (type is FunctionType) {
// The parameter type is a function type either directly or through
// typedef(s).
ir.Constant arity = buildIntegerConstant(type.computeArity());
input = buildStaticFunctionInvocation(program.closureConverter,
<ir.Primitive>[input, arity], sourceInformation);
}
arguments.add(input);
argumentTemplates.add('#');
});
// Construct the application of parameters for functions and setters.
if (function.kind == ElementKind.FUNCTION) {
code = "$code(${argumentTemplates.join(', ')})";
} else if (function.kind == ElementKind.SETTER) {
code = "$code = ${argumentTemplates.single}";
} else {
assert(argumentTemplates.isEmpty);
assert(function.kind == ElementKind.GETTER);
}
// Generate the [ForeignCode] expression and a return statement to return
// its value.
ir.Primitive value = buildForeignCode(
js.js.uncachedExpressionTemplate(code),
arguments,
behavior,
sourceInformation,
type: program.getTypeMaskForNativeFunction(function));
buildReturn(value: value, sourceInformation: sourceInformation);
}
static _isNotNull(ir.Primitive value) =>
!(value is ir.Constant && value.value.isNull);
/// Builds a call to a resolved js-interop element.
ir.Primitive buildInvokeJsInteropMember(FunctionElement element,
List<ir.Primitive> arguments, SourceInformation sourceInformation) {
program.addNativeMethod(element);
String target = program.getJsInteropTargetPath(element);
// Strip off trailing arguments that were not specified.
// TODO(jacobr,sigmund): assert that the trailing arguments are all null.
// TODO(jacobr): rewrite named arguments to an object literal matching
// the factory constructor case.
var inputs = arguments.where(_isNotNull).toList();
var behavior = new NativeBehavior()..sideEffects.setAllSideEffects();
DartType type = element.isConstructor
? element.enclosingClass.thisType
: element.type.returnType;
// Native behavior effects here are similar to native/behavior.dart.
// The return type is dynamic if we don't trust js-interop type
// declarations.
behavior.typesReturned.add(
program.trustJSInteropTypeAnnotations ? type : const DynamicType());
// The allocation effects include the declared type if it is native (which
// includes js interop types).
if (type.element != null && program.isNative(type.element)) {
behavior.typesInstantiated.add(type);
}
// It also includes any other JS interop type if we don't trust the
// annotation or if is declared too broad.
if (!program.trustJSInteropTypeAnnotations ||
type.isObject ||
type.isDynamic) {
behavior.typesInstantiated.add(program.jsJavascriptObjectType);
}
String code;
if (element.isGetter) {
code = target;
} else if (element.isSetter) {
code = "$target = #";
} else {
var args = new List.filled(inputs.length, '#').join(',');
code = element.isConstructor ? "new $target($args)" : "$target($args)";
}
return buildForeignCode(
js.js.parseForeignJS(code), inputs, behavior, sourceInformation);
// TODO(sigmund): should we record the source-information here?
}
/// Builds an object literal that results from invoking a factory constructor
/// of a js-interop anonymous type.
ir.Primitive buildJsInteropObjectLiteral(ConstructorElement constructor,
List<ir.Primitive> arguments, SourceInformation sourceInformation) {
assert(program.isJsInteropAnonymous(constructor));
program.addNativeMethod(constructor);
FunctionSignature params = constructor.functionSignature;
int i = 0;
var filteredArguments = <ir.Primitive>[];
var entries = new Map<String, js.Expression>();
params.orderedForEachParameter((ParameterElement parameter) {
// TODO(jacobr): throw if parameter names do not match names of property
// names in the class.
assert(parameter.isNamed);
ir.Primitive argument = arguments[i++];
if (_isNotNull(argument)) {
filteredArguments.add(argument);
entries[parameter.name] =
new js.InterpolatedExpression(filteredArguments.length - 1);
}
});
var code = new js.Template(null, js.objectLiteral(entries));
var behavior = new NativeBehavior();
if (program.trustJSInteropTypeAnnotations) {
behavior.typesReturned.add(constructor.enclosingClass.thisType);
}
return buildForeignCode(
code, filteredArguments, behavior, sourceInformation);
}
/// 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);
}
}
/// Creates a labeled statement
void buildLabeledStatement({SubbuildFunction buildBody, JumpTarget target}) {
JumpCollector join = new ForwardJumpCollector(environment, target: target);
IrBuilder innerBuilder = makeDelimitedBuilder();
innerBuilder.state.breakCollectors.add(join);
buildBody(innerBuilder);
innerBuilder.state.breakCollectors.removeLast();
bool hasBreaks = !join.isEmpty;
if (hasBreaks) {
if (innerBuilder.isOpen) innerBuilder.jumpTo(join);
add(new ir.LetCont(join.continuation, innerBuilder.root));
environment = join.environment;
} else if (innerBuilder.root != null) {
add(innerBuilder.root);
_current = innerBuilder._current;
environment = innerBuilder.environment;
} else {
// The translation of the body did not emit any CPS term.
}
}
// 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) {
jumpTo(collector);
return true;
}
}
return false;
}
void buildThrow(ir.Primitive value) {
assert(isOpen);
add(new ir.Throw(value));
_current = null;
}
ir.Primitive buildNonTailThrow(ir.Primitive value) {
assert(isOpen);
ir.Parameter param = new ir.Parameter(null);
ir.Continuation cont = new ir.Continuation(<ir.Parameter>[param]);
add(new ir.LetCont(cont, new ir.Throw(value)));
return param;
}
void buildRethrow() {
assert(isOpen);
add(new ir.Rethrow());
_current = null;
}
/// Create a negation of [condition].
ir.Primitive buildNegation(
ir.Primitive condition, SourceInformation sourceInformation) {
// ! 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(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.two(
thenContinuation,
elseContinuation,
new ir.Branch.strict(condition, thenContinuation, elseContinuation,
sourceInformation))));
return resultParameter;
}
/// 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),
SourceInformation sourceInformation,
{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 = makeDelimitedBuilder();
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 = makeDelimitedBuilder();
// 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 = rightBuilder.makeDelimitedBuilder();
IrBuilder rightFalseBuilder = rightBuilder.makeDelimitedBuilder();
// If we don't evaluate the right subexpression, the value of the whole
// expression is this constant.
ir.Constant leftBool = emptyBuilder.buildBooleanConstant(isLazyOr);
// If we do evaluate the right subexpression, the value of the expression
// is a true or false constant.
ir.Constant rightTrue = rightTrueBuilder.buildBooleanConstant(true);
ir.Constant rightFalse = rightFalseBuilder.buildBooleanConstant(false);
// Result values are passed as continuation arguments, which are
// constructed based on environments. These assertions are a sanity check.
assert(environment.length == emptyBuilder.environment.length);
assert(environment.length == rightTrueBuilder.environment.length);
assert(environment.length == rightFalseBuilder.environment.length);
// Wire up two continuations for the left subexpression, two continuations
// for the right subexpression, and a three-way join continuation.
JumpCollector join =
new ForwardJumpCollector(environment, hasExtraArgument: true);
emptyBuilder.jumpTo(join, leftBool);
rightTrueBuilder.jumpTo(join, rightTrue);
rightFalseBuilder.jumpTo(join, rightFalse);
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.two(
rightTrueContinuation,
rightFalseContinuation,
new ir.Branch.strict(rightValue, rightTrueContinuation,
rightFalseContinuation, sourceInformation)));
// 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(
join.continuation,
new ir.LetCont.two(
leftTrueContinuation,
leftFalseContinuation,
new ir.Branch.strict(leftValue, leftTrueContinuation,
leftFalseContinuation, sourceInformation))));
environment = join.environment;
return environment.discard(1);
}
ir.Primitive buildIdentical(ir.Primitive x, ir.Primitive y,
{SourceInformation sourceInformation}) {
return addPrimitive(new ir.ApplyBuiltinOperator(
ir.BuiltinOperator.Identical, <ir.Primitive>[x, y], sourceInformation));
}
/// Called when entering a nested function with free variables.
///
/// The free variables must subsequently be accessible using [buildLocalGet]
/// and [buildLocalSet].
void _enterClosureEnvironment(ClosureEnvironment env) {
if (env == null) return;
// Obtain a reference to the function object (this).
ir.Parameter thisPrim = state.thisParameter;
// Obtain access to the free variables.
env.freeVariables.forEach((Local local, ClosureLocation location) {
if (location.isBox) {
// Boxed variables are loaded from their box on-demand.
state.boxedVariables[local] = location;
} else {
// Unboxed variables are loaded from the function object immediately.
// This includes BoxLocals which are themselves unboxed variables.
environment.extend(
local, addPrimitive(new ir.GetField(thisPrim, location.field)));
}
});
// If the function captures a reference to the receiver from the
// enclosing method, remember which primitive refers to the receiver object.
if (env.thisLocal != null && env.freeVariables.containsKey(env.thisLocal)) {
state.enclosingThis = environment.lookup(env.thisLocal);
}
// If the function has a self-reference, use the value of `this`.
if (env.selfReference != null) {
environment.extend(env.selfReference, thisPrim);
}
}
/// Creates a box for [scope.box] and binds the captured variables to
/// that box.
///
/// The captured variables can subsequently be manipulated with
/// [declareLocalVariable], [buildLocalGet], and [buildLocalSet].
void enterScope(ClosureScope scope) => _enterScope(scope);
/// Called when entering a function body or loop body.
///
/// This is not called for for-loops, which instead use the methods
/// [_enterForLoopInitializer], [_enterForLoopBody], and [_enterForLoopUpdate]
/// due to their special scoping rules.
///
/// The boxed variables declared in this scope must subsequently be available
/// using [buildLocalGet], [buildLocalSet], etc.
void _enterScope(ClosureScope scope) {
if (scope == null) return;
ir.CreateBox boxPrim = addPrimitive(new ir.CreateBox());
environment.extend(scope.box, boxPrim);
boxPrim.useElementAsHint(scope.box);
scope.capturedVariables.forEach((Local local, ClosureLocation location) {
assert(!state.boxedVariables.containsKey(local));
if (location.isBox) {
state.boxedVariables[local] = location;
}
});
}
/// Add the given function parameter to the IR, and bind it in the environment
/// or put it in its box, if necessary.
void _createFunctionParameter(Local parameterElement) {
ir.Parameter parameter = new ir.Parameter(parameterElement);
_parameters.add(parameter);
state.functionParameters.add(parameter);
ClosureLocation location = state.boxedVariables[parameterElement];
if (location != null) {
addPrimitive(new ir.SetField(
environment.lookup(location.box), location.field, parameter));
} else {
environment.extend(parameterElement, parameter);
}
}
void _createThisParameter() {
assert(state.thisParameter == null);
if (Elements.isStaticOrTopLevel(state.currentElement)) return;
if (state.currentElement.isLocal) return;
state.thisParameter =
new ir.Parameter(new ThisParameterLocal(state.currentElement));
}
void declareLocalVariable(LocalElement variableElement,
{ir.Primitive initialValue}) {
assert(isOpen);
if (initialValue == null) {
initialValue = buildNullConstant();
}
ClosureLocation location = state.boxedVariables[variableElement];
if (location != null) {
addPrimitive(new ir.SetField(
environment.lookup(location.box), location.field, initialValue));
} else if (isInMutableVariable(variableElement)) {
add(new ir.LetMutable(getMutableVariable(variableElement), initialValue));
} else {
initialValue.useElementAsHint(variableElement);
environment.extend(variableElement, initialValue);
}
}
/// Add [functionElement] to the environment with provided [definition].
void declareLocalFunction(
LocalFunctionElement functionElement,
closure.ClosureClassElement classElement,
SourceInformation sourceInformation) {
ir.Primitive closure =
buildFunctionExpression(classElement, sourceInformation);
declareLocalVariable(functionElement, initialValue: closure);
}
ir.Primitive buildFunctionExpression(closure.ClosureClassElement classElement,
SourceInformation sourceInformation) {
List<ir.Primitive> arguments = <ir.Primitive>[];
for (closure.ClosureFieldElement field in classElement.closureFields) {
// Captured 'this' and type variables are not always available as locals
// in the environment, so treat those specially.
ir.Primitive value;
if (field.local is closure.ThisLocal) {
value = buildThis();
} else if (field.local is closure.TypeVariableLocal) {
closure.TypeVariableLocal variable = field.local;
value = buildTypeVariableAccess(variable.typeVariable);
} else {
value = environment.lookup(field.local);
}
arguments.add(value);
}
return addPrimitive(new ir.CreateInstance(
classElement, arguments, null, sourceInformation));
}
/// Create a read access of [local] function, variable, or parameter.
// TODO(johnniwinther): Make [sourceInformation] mandatory.
ir.Primitive buildLocalGet(LocalElement local,
{SourceInformation sourceInformation}) {
assert(isOpen);
ClosureLocation location = state.boxedVariables[local];
if (location != null) {
ir.Primitive result = new ir.GetField(
environment.lookup(location.box), location.field,
sourceInformation: sourceInformation);
result.useElementAsHint(local);
return addPrimitive(result);
} else if (isInMutableVariable(local)) {
return addPrimitive(new ir.GetMutable(getMutableVariable(local),
sourceInformation: sourceInformation));
} else {
return environment.lookup(local);
}
}
/// Create a write access to [local] variable or parameter with the provided
/// [value].
ir.Primitive buildLocalVariableSet(LocalElement local, ir.Primitive value,
SourceInformation sourceInformation) {
assert(isOpen);
ClosureLocation location = state.boxedVariables[local];
if (location != null) {
addPrimitive(new ir.SetField(
environment.lookup(location.box), location.field, value,
sourceInformation: sourceInformation));
} else if (isInMutableVariable(local)) {
addPrimitive(new ir.SetMutable(getMutableVariable(local), value,
sourceInformation: sourceInformation));
} else {
value.useElementAsHint(local);
environment.update(local, value);
}
return value;
}
/// Called before building the initializer of a for-loop.
///
/// The loop variables will subsequently be declared using
/// [declareLocalVariable].
void _enterForLoopInitializer(
ClosureScope scope, List<LocalElement> loopVariables) {
if (scope == null) return;
// If there are no boxed loop variables, don't create the box here, let
// it be created inside the body instead.
if (scope.boxedLoopVariables.isEmpty) return;
_enterScope(scope);
}
/// Called before building the body of a for-loop.
void _enterForLoopBody(ClosureScope scope, List<LocalElement> loopVariables) {
if (scope == null) return;
// If there are boxed loop variables, the box has already been created
// at the initializer.
if (!scope.boxedLoopVariables.isEmpty) return;
_enterScope(scope);
}
/// Called before building the update of a for-loop.
void _enterForLoopUpdate(
ClosureScope scope, List<LocalElement> loopVariables) {
if (scope == null) return;
// If there are no boxed loop variables, then the box is created inside the
// body, so there is no need to explicitly renew it.
if (scope.boxedLoopVariables.isEmpty) return;
ir.Primitive box = environment.lookup(scope.box);
ir.Primitive newBox = addPrimitive(new ir.CreateBox());
newBox.useElementAsHint(scope.box);
for (VariableElement loopVar in scope.boxedLoopVariables) {
ClosureLocation location = scope.capturedVariables[loopVar];
ir.Primitive value = addPrimitive(new ir.GetField(box, location.field));
addPrimitive(new ir.SetField(newBox, location.field, value));
}
environment.update(scope.box, newBox);
}
/// Creates an access to the receiver from the current (or enclosing) method.
///
/// If inside a closure class, [buildThis] will redirect access through
/// closure fields in order to access the receiver from the enclosing method.
ir.Primitive buildThis() {
if (state.enclosingThis != null) return state.enclosingThis;
assert(state.thisParameter != null);
return state.thisParameter;
}
ir.Primitive buildFieldGet(ir.Primitive receiver, FieldElement target,
SourceInformation sourceInformation) {
return addPrimitive(new ir.GetField(receiver, target,
sourceInformation: sourceInformation,
isFinal: program.fieldNeverChanges(target)));
}
void buildFieldSet(ir.Primitive receiver, FieldElement target,
ir.Primitive value, SourceInformation sourceInformation) {
addPrimitive(new ir.SetField(receiver, target, value,
sourceInformation: sourceInformation));
}
ir.Primitive buildSuperFieldGet(
FieldElement target, SourceInformation sourceInformation) {
return addPrimitive(new ir.GetField(buildThis(), target,
sourceInformation: sourceInformation));
}
ir.Primitive buildSuperFieldSet(FieldElement target, ir.Primitive value,
SourceInformation sourceInformation) {
addPrimitive(new ir.SetField(buildThis(), target, value,
sourceInformation: sourceInformation));
return value;
}
/// Loads parameters to a constructor body into the environment.
///
/// The header for a constructor body differs from other functions in that
/// some parameters are already boxed, and the box is passed as an argument
/// instead of being created in the header.
void buildConstructorBodyHeader(
Iterable<Local> parameters, ClosureScope closureScope) {
_createThisParameter();
for (Local param in parameters) {
ir.Parameter parameter = _createLocalParameter(param);
state.functionParameters.add(parameter);
}
if (closureScope != null) {
state.boxedVariables.addAll(closureScope.capturedVariables);
}
}
/// Create a constructor invocation of [element] on [type] where the
/// constructor name and argument structure are defined by [callStructure] and
/// the argument values are defined by [arguments].
ir.Primitive buildConstructorInvocation(
ConstructorElement element,
CallStructure callStructure,
DartType type,
List<ir.Primitive> arguments,
SourceInformation sourceInformation,
{TypeMask allocationSiteType}) {
assert(isOpen);
Selector selector =
new Selector(SelectorKind.CALL, element.memberName, callStructure);
ClassElement cls = element.enclosingClass;
if (program.isJsInterop(element)) {
if (program.isJsInteropAnonymous(element)) {
return buildJsInteropObjectLiteral(
element, arguments, sourceInformation);
}
return buildInvokeJsInteropMember(element, arguments, sourceInformation);
}
if (program.requiresRuntimeTypesFor(cls)) {
InterfaceType interface = type;
Iterable<ir.Primitive> typeArguments =
interface.typeArguments.map((DartType argument) {
return type.treatAsRaw
? buildNullConstant()
: buildTypeExpression(argument);
});
arguments = new List<ir.Primitive>.from(arguments)..addAll(typeArguments);
}
return addPrimitive(new ir.InvokeConstructor(
type, element, selector, arguments, sourceInformation,
allocationSiteType: allocationSiteType));
}
ir.Primitive buildTypeExpression(DartType type) {
type = program.unaliasType(type);
if (type is TypeVariableType) {
return buildTypeVariableAccess(type);
} else if (type is InterfaceType || type is FunctionType) {
List<ir.Primitive> arguments = <ir.Primitive>[];
type.forEachTypeVariable((TypeVariableType variable) {
ir.Primitive value = buildTypeVariableAccess(variable);
arguments.add(value);
});
return addPrimitive(new ir.TypeExpression(
ir.TypeExpressionKind.COMPLETE, type, arguments));
} else if (type.treatAsDynamic) {
return buildNullConstant();
} else {
// TypedefType can reach here, and possibly other things.
throw 'unimplemented translation of type expression $type (${type.kind})';
}
}
/// Obtains the internal type representation of the type held in [variable].
///
/// The value of [variable] is taken from the current receiver object, or
/// if we are currently building a constructor field initializer, from the
/// corresponding type argument (field initializers are evaluated before the
/// receiver object is created).
ir.Primitive buildTypeVariableAccess(TypeVariableType variable,
{SourceInformation sourceInformation}) {
// If the local exists in the environment, use that.
// This is put here when we are inside a constructor or field initializer,
// (or possibly a closure inside one of these).
Local local = new closure.TypeVariableLocal(variable, state.currentElement);
if (environment.contains(local)) {
return environment.lookup(local);
}
// If the type variable is not in a local, read its value from the
// receiver object.
ir.Primitive target = buildThis();
return addPrimitive(
new ir.ReadTypeVariable(variable, target, sourceInformation));
}
/// Make the given type variable accessible through the local environment
/// with the value of [binding].
void declareTypeVariable(TypeVariableType variable, DartType binding) {
environment.extend(
new closure.TypeVariableLocal(variable, state.currentElement),
buildTypeExpression(binding));
}
/// Reifies the value of [variable] on the current receiver object.
ir.Primitive buildReifyTypeVariable(
TypeVariableType variable, SourceInformation sourceInformation) {
ir.Primitive typeArgument =
buildTypeVariableAccess(variable, sourceInformation: sourceInformation);
return addPrimitive(
new ir.ReifyRuntimeType(typeArgument, sourceInformation));
}
ir.Primitive buildInvocationMirror(
Selector selector, List<ir.Primitive> arguments) {
return addPrimitive(new ir.CreateInvocationMirror(selector, arguments));
}
ir.Primitive buildForeignCode(
js.Template codeTemplate,
List<ir.Primitive> arguments,
NativeBehavior behavior,
SourceInformation sourceInformation,
{Element dependency,
TypeMask type}) {
assert(behavior != null);
if (type == null) {
type = program.getTypeMaskForForeign(behavior);
}
if (js.isIdentityTemplate(codeTemplate) && !program.isArrayType(type)) {
// JS expression is just a refinement.
// Do not do this for arrays - those are special because array types can
// change after creation. The input and output must therefore be modeled
// as distinct values.
return addPrimitive(new ir.Refinement(arguments.single, type));
}
ir.Primitive result = addPrimitive(new ir.ForeignCode(
codeTemplate, type, arguments, behavior, sourceInformation,
dependency: dependency));
if (!codeTemplate.isExpression) {
// Close the term if this is a "throw" expression or native body.
add(new ir.Unreachable());
_current = null;
}
return result;
}
/// Creates a type test or type cast of [value] against [type].
ir.Primitive buildTypeOperator(
ir.Primitive value, DartType type, SourceInformation sourceInformation,
{bool isTypeTest}) {
assert(isOpen);
assert(isTypeTest != null);
type = program.unaliasType(type);
if (type.isMalformed) {
ErroneousElement element = type.element;
ir.Primitive message = buildStringConstant(element.message);
return buildStaticFunctionInvocation(program.throwTypeErrorHelper,
<ir.Primitive>[message], sourceInformation);
}
List<ir.Primitive> typeArguments = const <ir.Primitive>[];
if (type is GenericType && type.typeArguments.isNotEmpty) {
typeArguments = type.typeArguments.map(buildTypeExpression).toList();
} else if (type is TypeVariableType) {
typeArguments = <ir.Primitive>[buildTypeVariableAccess(type)];
} else if (type is FunctionType) {
typeArguments = <ir.Primitive>[buildTypeExpression(type)];
}
if (isTypeTest) {
// For type tests, we must treat specially the rare cases where `null`
// satisfies the test (which otherwise never satisfies a type test).
// This is not an optimization: the TypeOperator assumes that `null`
// cannot satisfy the type test unless the type is a type variable.
if (type.isObject || type.isDynamic) {
// `x is Object` and `x is dynamic` are always true, even if x is null.
return buildBooleanConstant(true);
}
if (type is InterfaceType && type.element == program.nullClass) {
// `x is Null` is true if and only if x is null.
return _buildCheckNull(value, sourceInformation);
}
return addPrimitive(new ir.TypeTest(value, type, typeArguments));
} else {
if (type.isObject || type.isDynamic) {
// `x as Object` and `x as dynamic` are the same as `x`.
return value;
}
return addPrimitive(new ir.TypeCast(value, type, typeArguments));
}
}
/// Create an if-null expression. This is equivalent to a conditional
/// expression whose result is either [value] if [value] is not null, or
/// `right` if [value] is null. Only when [value] is null, [buildRight] is
/// evaluated to produce the `right` value.
ir.Primitive buildIfNull(
ir.Primitive value,
ir.Primitive buildRight(IrBuilder builder),
SourceInformation sourceInformation) {
ir.Primitive condition = _buildCheckNull(value, sourceInformation);
return buildConditional(
condition, buildRight, (_) => value, sourceInformation);
}
/// Create a conditional send. This is equivalent to a conditional expression
/// that checks if [receiver] is null, if so, it returns null, otherwise it
/// evaluates the [buildSend] expression.
ir.Primitive buildIfNotNullSend(
ir.Primitive receiver,
ir.Primitive buildSend(IrBuilder builder),
SourceInformation sourceInformation) {
ir.Primitive condition = _buildCheckNull(receiver, sourceInformation);
return buildConditional(
condition, (_) => receiver, buildSend, sourceInformation);
}
/// Creates a type test checking whether [value] is null.
ir.Primitive _buildCheckNull(
ir.Primitive value, SourceInformation sourceInformation) {
assert(isOpen);
return buildIdentical(value, buildNullConstant(),
sourceInformation: sourceInformation);
}
}
/// Location of a variable relative to a given closure.
class ClosureLocation {
/// If not `null`, this location is [box].[field].
/// The location of [box] can be obtained separately from an
/// enclosing [ClosureEnvironment] or [ClosureScope].
/// If `null`, then the location is [field] on the enclosing function object.
final closure.BoxLocal box;
/// The field in which the variable is stored.
final Entity field;
bool get isBox => box != null;
ClosureLocation(this.box, this.field);
/// Converts a map containing closure.dart's [CapturedVariable]s into one
/// containing [ClosureLocation]s.
///
/// There is a 1:1 corresponce between these; we do this because the
/// IR builder should not depend on synthetic elements.
static Map<Local, ClosureLocation> mapFrom(
Map<Local, closure.CapturedVariable> map) {
Map result = {};
map.forEach((Local k, closure.CapturedVariable v) {
closure.BoxLocal box = v is closure.BoxFieldElement ? v.box : null;
result[k] = new ClosureLocation(box, v);
});
return result;
}
}
/// Introduces a new box and binds local variables to this box.
///
/// A [ClosureScope] may exist for each function and for each loop.
/// Generally, one may pass `null` to the [IrBuilder] instead of a
/// [ClosureScope] when a given scope has no boxed variables.
class ClosureScope {
/// This box is now in scope and [capturedVariables] may use it.
final closure.BoxLocal box;
/// Maps [LocalElement]s to their location.
final Map<Local, ClosureLocation> capturedVariables;
/// If this is the scope of a for-loop, [boxedLoopVariables] is the list
/// of boxed variables that are declared in the initializer.
final List<VariableElement> boxedLoopVariables;
factory ClosureScope(closure.ClosureScope scope) {
return scope == null ? null : new ClosureScope._internal(scope);
}
ClosureScope._internal(closure.ClosureScope scope)
: box = scope.boxElement,
capturedVariables = ClosureLocation.mapFrom(scope.capturedVariables),
boxedLoopVariables = scope.boxedLoopVariables;
}
/// Environment passed when building a nested function, describing how
/// to access variables from the enclosing scope.
class ClosureEnvironment {
/// References to this local should be treated as recursive self-reference.
/// (This is *not* in [freeVariables]).
final LocalFunctionElement selfReference;
/// If non-null, [thisLocal] has an entry in [freeVariables] describing where
/// to find the captured value of `this`.
final closure.ThisLocal thisLocal;
/// Maps [LocalElement]s, [BoxLocal]s and [ThisLocal] to their location.
final Map<Local, ClosureLocation> freeVariables;
factory ClosureEnvironment(closure.ClosureClassMap closureClassMap) {
if (closureClassMap.closureElement == null) return null;
return new ClosureEnvironment._internal(closureClassMap);
}
ClosureEnvironment._internal(closure.ClosureClassMap closureClassMap)
: selfReference = closureClassMap.closureElement,
thisLocal = closureClassMap.thisLocal,
freeVariables =
ClosureLocation.mapFrom(closureClassMap.freeVariableMap);
}
class TryStatementInfo {
final Set<LocalVariableElement> declared = new Set<LocalVariableElement>();
final Set<LocalVariableElement> boxedOnEntry =
new Set<LocalVariableElement>();
}
class CatchClauseInfo {
final DartType type;
final LocalVariableElement exceptionVariable;
final LocalVariableElement stackTraceVariable;
final SubbuildFunction buildCatchBlock;
final SourceInformation sourceInformation;
CatchClauseInfo(
{this.type,
this.exceptionVariable,
this.stackTraceVariable,
this.buildCatchBlock,
this.sourceInformation});
}
class SwitchCaseInfo {
final SubbuildFunction buildCondition;
final SubbuildFunction buildBody;
final SourceInformation sourceInformation;
SwitchCaseInfo(this.buildCondition, this.buildBody, this.sourceInformation);
}