blob: d16fd8c84ed136bb8520ef030f4740f142b60ddb [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.
// IrNodes are kept in a separate library to have precise control over their
// dependencies on other parts of the system.
library dart2js.ir_nodes;
import '../constants/expressions.dart';
import '../constants/values.dart' as values show ConstantValue;
import '../cps_ir/optimizers.dart';
import '../dart_types.dart' show DartType, GenericType, TypeVariableType;
import '../dart2jslib.dart' as dart2js show
CURRENT_ELEMENT_SPANNABLE,
InternalErrorFunction,
invariant;
import '../elements/elements.dart';
import '../io/source_information.dart' show SourceInformation;
import '../universe/universe.dart' show Selector, SelectorKind;
abstract class Node {
/// A pointer to the parent node. Is null until set by optimization passes.
Node parent;
accept(Visitor visitor);
}
abstract class Expression extends Node {
Expression plug(Expression expr) => throw 'impossible';
}
/// The base class of things that variables can refer to: primitives,
/// continuations, function and continuation parameters, etc.
abstract class Definition<T extends Definition<T>> extends Node {
// The head of a linked-list of occurrences, in no particular order.
Reference<T> firstRef;
bool get hasAtMostOneUse => firstRef == null || firstRef.next == null;
bool get hasExactlyOneUse => firstRef != null && firstRef.next == null;
bool get hasAtLeastOneUse => firstRef != null;
bool get hasMultipleUses => !hasAtMostOneUse;
void substituteFor(Definition<T> other) {
if (other.firstRef == null) return;
Reference<T> previous, current = other.firstRef;
do {
current.definition = this;
previous = current;
current = current.next;
} while (current != null);
previous.next = firstRef;
if (firstRef != null) firstRef.previous = previous;
firstRef = other.firstRef;
}
}
/// An expression that cannot throw or diverge and has no side-effects.
/// All primitives are named using the identity of the [Primitive] object.
///
/// Primitives may allocate objects, this is not considered side-effect here.
///
/// Although primitives may not mutate state, they may depend on state.
abstract class Primitive extends Definition<Primitive> {
/// The [VariableElement] or [ParameterElement] from which the primitive
/// binding originated.
Entity hint;
/// Register in which the variable binding this primitive can be allocated.
/// Separate register spaces are used for primitives with different [element].
/// Assigned by [RegisterAllocator], is null before that phase.
int registerIndex;
/// Use the given element as a hint for naming this primitive.
///
/// Has no effect if this primitive already has a non-null [element].
void useElementAsHint(Entity hint) {
if (this.hint == null) {
this.hint = hint;
}
}
}
/// Operands to invocations and primitives are always variables. They point to
/// their definition and are doubly-linked into a list of occurrences.
class Reference<T extends Definition<T>> {
T definition;
Reference<T> previous;
Reference<T> next;
/// A pointer to the parent node. Is null until set by optimization passes.
Node parent;
Reference(this.definition) {
next = definition.firstRef;
if (next != null) next.previous = this;
definition.firstRef = this;
}
/// Unlinks this reference from the list of occurrences.
void unlink() {
if (previous == null) {
assert(definition.firstRef == this);
definition.firstRef = next;
} else {
previous.next = next;
}
if (next != null) next.previous = previous;
}
}
/// Binding a value (primitive or constant): 'let val x = V in E'. The bound
/// value is in scope in the body.
/// During one-pass construction a LetVal with an empty body is used to
/// represent the one-hole context 'let val x = V in []'.
class LetPrim extends Expression implements InteriorNode {
final Primitive primitive;
Expression body;
LetPrim(this.primitive, [this.body = null]);
Expression plug(Expression expr) {
assert(body == null);
return body = expr;
}
accept(Visitor visitor) => visitor.visitLetPrim(this);
}
/// Binding continuations.
///
/// let cont k0(v0 ...) = E0
/// k1(v1 ...) = E1
/// ...
/// in E
///
/// The bound continuations are in scope in the body and the continuation
/// parameters are in scope in the respective continuation bodies.
/// During one-pass construction a LetCont whose first continuation has an empty
/// body is used to represent the one-hole context
/// 'let cont ... k(v) = [] ... in E'.
class LetCont extends Expression implements InteriorNode {
List<Continuation> continuations;
Expression body;
LetCont(Continuation continuation, this.body)
: continuations = <Continuation>[continuation];
LetCont.many(this.continuations, this.body);
Expression plug(Expression expr) {
assert(continuations != null &&
continuations.isNotEmpty &&
continuations.first.body == null);
return continuations.first.body = expr;
}
accept(Visitor visitor) => visitor.visitLetCont(this);
}
// Binding an exception handler.
//
// let handler h(v0, v1) = E0 in E1
//
// The handler is a two-argument (exception, stack trace) continuation which
// is implicitly the error continuation of all the code in its body E1.
// [LetHandler] differs from a [LetCont] binding in that it (1) has the
// runtime semantics of pushing/popping a handler from the dynamic exception
// handler stack and (2) it does not have any explicit invocations.
class LetHandler extends Expression implements InteriorNode {
Continuation handler;
Expression body;
LetHandler(this.handler, this.body);
accept(Visitor visitor) => visitor.visitLetHandler(this);
}
/// Binding mutable variables.
///
/// let mutable v = P in E
///
/// [MutableVariable]s can be seen as ref cells that are not first-class
/// values. They are therefore not [Primitive]s and not bound by [LetPrim]
/// to prevent unrestricted use of references to them. During one-pass
/// construction, a [LetMutable] with an empty body is use to represent the
/// one-hole context 'let mutable v = P in []'.
class LetMutable extends Expression implements InteriorNode {
final MutableVariable variable;
final Reference<Primitive> value;
Expression body;
LetMutable(this.variable, Primitive value)
: this.value = new Reference<Primitive>(value);
Expression plug(Expression expr) {
return body = expr;
}
accept(Visitor visitor) => visitor.visitLetMutable(this);
}
abstract class Invoke {
Selector get selector;
List<Reference<Primitive>> get arguments;
}
/// Represents a node with a child node, which can be accessed through the
/// `body` member. A typical usage is when removing a node from the CPS graph:
///
/// Node child = node.body;
/// InteriorNode parent = node.parent;
///
/// child.parent = parent;
/// parent.body = child;
abstract class InteriorNode extends Node {
Expression body;
}
/// Invoke a static function or static field getter/setter.
class InvokeStatic extends Expression implements Invoke {
/// [FunctionElement] or [FieldElement].
final Entity target;
/**
* The selector encodes how the function is invoked: number of positional
* arguments, names used in named arguments. This information is required
* to build the [StaticCallSiteTypeInformation] for the inference graph.
*/
final Selector selector;
final Reference<Continuation> continuation;
final List<Reference<Primitive>> arguments;
final SourceInformation sourceInformation;
InvokeStatic(this.target,
this.selector,
Continuation cont,
List<Primitive> args,
this.sourceInformation)
: continuation = new Reference<Continuation>(cont),
arguments = _referenceList(args) {
assert(target is ErroneousElement || selector.name == target.name);
}
accept(Visitor visitor) => visitor.visitInvokeStatic(this);
}
/// A [CallingConvention] codifies how arguments are matched to parameters when
/// emitting code for a function call.
class CallingConvention {
final String name;
const CallingConvention(this.name);
/// The normal way of calling a Dart function: Positional arguments are
/// matched with (mandatory and optional) positionals parameters from left to
/// right and named arguments are matched by name.
static const CallingConvention DART = const CallingConvention("Dart call");
/// Intercepted calls have an additional first argument that is the actual
/// receiver of the call. See the documentation of [Interceptor] for more
/// information.
static const CallingConvention JS_INTERCEPTED =
const CallingConvention("intercepted JavaScript call");
}
/// Invoke a method, operator, getter, setter, or index getter/setter.
/// Converting a method to a function object is treated as a getter invocation.
class InvokeMethod extends Expression implements Invoke {
Reference<Primitive> receiver;
Selector selector;
CallingConvention callingConvention;
final Reference<Continuation> continuation;
final List<Reference<Primitive>> arguments;
InvokeMethod(Primitive receiver,
Selector selector,
Continuation continuation,
List<Primitive> arguments)
: this.internal(new Reference<Primitive>(receiver),
selector,
new Reference<Continuation>(continuation),
_referenceList(arguments));
InvokeMethod.internal(this.receiver,
this.selector,
this.continuation,
this.arguments,
[this.callingConvention = CallingConvention.DART]) {
assert(isValid);
}
/// Returns whether the arguments match the selector under the given calling
/// convention.
///
/// This check is designed to be used in an assert, as it also checks that the
/// selector, arguments, and calling convention have meaningful values.
bool get isValid {
if (selector == null || callingConvention == null) return false;
if (callingConvention != CallingConvention.DART &&
callingConvention != CallingConvention.JS_INTERCEPTED) {
return false;
}
int numberOfArguments =
callingConvention == CallingConvention.JS_INTERCEPTED
? arguments.length - 1
: arguments.length;
return selector.kind == SelectorKind.CALL ||
selector.kind == SelectorKind.OPERATOR ||
(selector.kind == SelectorKind.GETTER && numberOfArguments == 0) ||
(selector.kind == SelectorKind.SETTER && numberOfArguments == 1) ||
(selector.kind == SelectorKind.INDEX && numberOfArguments == 1) ||
(selector.kind == SelectorKind.INDEX && numberOfArguments == 2);
}
accept(Visitor visitor) => visitor.visitInvokeMethod(this);
}
/// Invoke [target] on [receiver], bypassing dispatch and override semantics.
///
/// That is, if [receiver] is an instance of a class that overrides [target]
/// with a different implementation, the overriding implementation is bypassed
/// and [target]'s implementation is invoked.
///
/// As with [InvokeMethod], this can be used to invoke a method, operator,
/// getter, setter, or index getter/setter.
///
/// If it is known that [target] does not use its receiver argument, then
/// [receiver] may refer to a null constant primitive. This happens for direct
/// invocations to intercepted methods, where the effective receiver is instead
/// passed as a formal parameter.
///
/// When targeting Dart, this instruction is used to represent super calls.
/// Here, [receiver] must always be a reference to `this`, and [target] must be
/// a method that is available in the super class.
class InvokeMethodDirectly extends Expression implements Invoke {
Reference<Primitive> receiver;
final Element target;
final Selector selector;
final Reference<Continuation> continuation;
final List<Reference<Primitive>> arguments;
InvokeMethodDirectly(Primitive receiver,
this.target,
this.selector,
Continuation cont,
List<Primitive> args)
: this.receiver = new Reference<Primitive>(receiver),
continuation = new Reference<Continuation>(cont),
arguments = _referenceList(args) {
assert(selector != null);
assert(selector.kind == SelectorKind.CALL ||
selector.kind == SelectorKind.OPERATOR ||
(selector.kind == SelectorKind.GETTER && arguments.isEmpty) ||
(selector.kind == SelectorKind.SETTER && arguments.length == 1) ||
(selector.kind == SelectorKind.INDEX && arguments.length == 1) ||
(selector.kind == SelectorKind.INDEX && arguments.length == 2));
}
accept(Visitor visitor) => visitor.visitInvokeMethodDirectly(this);
}
/// Non-const call to a constructor. The [target] may be a generative
/// constructor, factory, or redirecting factory.
class InvokeConstructor extends Expression implements Invoke {
final DartType type;
final FunctionElement target;
final Reference<Continuation> continuation;
final List<Reference<Primitive>> arguments;
final Selector selector;
/// The class being instantiated. This is the same as `target.enclosingClass`
/// and `type.element`.
ClassElement get targetClass => target.enclosingElement;
/// True if this is an invocation of a factory constructor.
bool get isFactory => target.isFactoryConstructor;
InvokeConstructor(this.type,
this.target,
this.selector,
Continuation cont,
List<Primitive> args)
: continuation = new Reference<Continuation>(cont),
arguments = _referenceList(args) {
assert(dart2js.invariant(target,
target.isErroneous || target.isConstructor,
message: "Constructor invocation target is not a constructor: "
"$target."));
assert(dart2js.invariant(target,
target.isErroneous ||
type.isDynamic ||
type.element == target.enclosingClass.declaration,
message: "Constructor invocation type ${type} does not match enclosing "
"class of target ${target}."));
}
accept(Visitor visitor) => visitor.visitInvokeConstructor(this);
}
/// "as" casts and "is" checks.
// We might want to turn "is"-checks into a [Primitive] as it can never diverge.
// But then we need to special-case for is-checks with an erroneous .type as
// these will throw.
class TypeOperator extends Expression {
final Reference<Primitive> receiver;
final DartType type;
final Reference<Continuation> continuation;
// TODO(johnniwinther): Use `Operator` class to encapsule the operator type.
final bool isTypeTest;
TypeOperator(Primitive receiver,
this.type,
Continuation cont,
{bool this.isTypeTest})
: this.receiver = new Reference<Primitive>(receiver),
this.continuation = new Reference<Continuation>(cont) {
assert(isTypeTest != null);
}
bool get isTypeCast => !isTypeTest;
accept(Visitor visitor) => visitor.visitTypeOperator(this);
}
/// Invoke [toString] on each argument and concatenate the results.
class ConcatenateStrings extends Expression {
final Reference<Continuation> continuation;
final List<Reference<Primitive>> arguments;
ConcatenateStrings(Continuation cont, List<Primitive> args)
: continuation = new Reference<Continuation>(cont),
arguments = _referenceList(args);
accept(Visitor visitor) => visitor.visitConcatenateStrings(this);
}
/// Gets the value from a [MutableVariable].
///
/// [MutableVariable]s can be seen as ref cells that are not first-class
/// values. A [LetPrim] with a [GetMutableVariable] can then be seen as:
///
/// let prim p = ![variable] in [body]
///
class GetMutableVariable extends Primitive {
final Reference<MutableVariable> variable;
GetMutableVariable(MutableVariable variable)
: this.variable = new Reference<MutableVariable>(variable);
accept(Visitor visitor) => visitor.visitGetMutableVariable(this);
}
/// Assign a [MutableVariable].
///
/// [MutableVariable]s can be seen as ref cells that are not first-class
/// values. This can be seen as a dereferencing assignment:
///
/// { [variable] := [value]; [body] }
class SetMutableVariable extends Expression implements InteriorNode {
final Reference<MutableVariable> variable;
final Reference<Primitive> value;
Expression body;
SetMutableVariable(MutableVariable variable, Primitive value)
: this.variable = new Reference<MutableVariable>(variable),
this.value = new Reference<Primitive>(value);
accept(Visitor visitor) => visitor.visitSetMutableVariable(this);
Expression plug(Expression expr) {
assert(body == null);
return body = expr;
}
}
/// Create a potentially recursive function and store it in a [MutableVariable].
/// The function can access itself using [GetMutableVariable] on [variable].
/// There must not exist a [SetMutableVariable] to [variable].
///
/// This can be seen as a let rec binding:
///
/// let rec [variable] = [definition] in [body]
///
class DeclareFunction extends Expression
implements InteriorNode, DartSpecificNode {
final MutableVariable variable;
final FunctionDefinition definition;
Expression body;
DeclareFunction(this.variable, this.definition);
Expression plug(Expression expr) {
assert(body == null);
return body = expr;
}
accept(Visitor visitor) => visitor.visitDeclareFunction(this);
}
/// Invoke a continuation in tail position.
class InvokeContinuation extends Expression {
Reference<Continuation> continuation;
List<Reference<Primitive>> arguments;
// An invocation of a continuation is recursive if it occurs in the body of
// the continuation itself.
bool isRecursive;
InvokeContinuation(Continuation cont, List<Primitive> args,
{recursive: false})
: continuation = new Reference<Continuation>(cont),
arguments = _referenceList(args),
isRecursive = recursive {
assert(cont.parameters == null ||
cont.parameters.length == args.length);
if (recursive) cont.isRecursive = true;
}
/// A continuation invocation whose target and arguments will be filled
/// in later.
///
/// Used as a placeholder for a jump whose target is not yet created
/// (e.g., in the translation of break and continue).
InvokeContinuation.uninitialized({recursive: false})
: continuation = null,
arguments = null,
isRecursive = recursive;
accept(Visitor visitor) => visitor.visitInvokeContinuation(this);
}
/// The base class of things which can be tested and branched on.
abstract class Condition extends Node {
}
class IsTrue extends Condition {
final Reference<Primitive> value;
IsTrue(Primitive val) : value = new Reference<Primitive>(val);
accept(Visitor visitor) => visitor.visitIsTrue(this);
}
/// Choose between a pair of continuations based on a condition value.
class Branch extends Expression {
final Condition condition;
final Reference<Continuation> trueContinuation;
final Reference<Continuation> falseContinuation;
Branch(this.condition, Continuation trueCont, Continuation falseCont)
: trueContinuation = new Reference<Continuation>(trueCont),
falseContinuation = new Reference<Continuation>(falseCont);
accept(Visitor visitor) => visitor.visitBranch(this);
}
/// Marker interface for nodes that are only handled in the JavaScript backend.
///
/// These nodes are generated by the unsugar step or the [JsIrBuilder] and need
/// special translation to the Tree IR, which is implemented in JsTreeBuilder.
abstract class JsSpecificNode implements Node {}
/// Marker interface for nodes that are only handled inthe Dart backend.
abstract class DartSpecificNode implements Node {}
/// Directly assigns to a field on a given object.
class SetField extends Expression implements InteriorNode, JsSpecificNode {
final Reference<Primitive> object;
Element field;
final Reference<Primitive> value;
Expression body;
SetField(Primitive object, this.field, Primitive value)
: this.object = new Reference<Primitive>(object),
this.value = new Reference<Primitive>(value);
Expression plug(Expression expr) {
assert(body == null);
return body = expr;
}
accept(Visitor visitor) => visitor.visitSetField(this);
}
/// Directly reads from a field on a given object.
class GetField extends Primitive implements JsSpecificNode {
final Reference<Primitive> object;
Element field;
GetField(Primitive object, this.field)
: this.object = new Reference<Primitive>(object);
accept(Visitor visitor) => visitor.visitGetField(this);
}
/// Creates an object for holding boxed variables captured by a closure.
class CreateBox extends Primitive implements JsSpecificNode {
accept(Visitor visitor) => visitor.visitCreateBox(this);
}
/// Creates an instance of a class and initializes its fields.
class CreateInstance extends Primitive implements JsSpecificNode {
final ClassElement classElement;
/// Initial values for the fields on the class.
/// The order corresponds to the order of fields on the class.
final List<Reference<Primitive>> arguments;
CreateInstance(this.classElement, List<Primitive> arguments)
: this.arguments = _referenceList(arguments);
accept(Visitor visitor) => visitor.visitCreateInstance(this);
}
class Identical extends Primitive implements JsSpecificNode {
final Reference<Primitive> left;
final Reference<Primitive> right;
Identical(Primitive left, Primitive right)
: left = new Reference<Primitive>(left),
right = new Reference<Primitive>(right);
accept(Visitor visitor) => visitor.visitIdentical(this);
}
class Interceptor extends Primitive implements JsSpecificNode {
final Reference<Primitive> input;
final Set<ClassElement> interceptedClasses;
Interceptor(Primitive input, this.interceptedClasses)
: this.input = new Reference<Primitive>(input);
accept(Visitor visitor) => visitor.visitInterceptor(this);
}
class Constant extends Primitive {
final ConstantExpression expression;
Constant(this.expression);
values.ConstantValue get value => expression.value;
accept(Visitor visitor) => visitor.visitConstant(this);
}
/// Reify the given type variable as a [Type].
/// This depends on the current binding of 'this'.
class ReifyTypeVar extends Primitive implements DartSpecificNode {
final TypeVariableElement typeVariable;
ReifyTypeVar(this.typeVariable);
values.ConstantValue get constant => null;
accept(Visitor visitor) => visitor.visitReifyTypeVar(this);
}
class LiteralList extends Primitive {
/// The List type being created; this is not the type argument.
final GenericType type;
final List<Reference<Primitive>> values;
LiteralList(this.type, Iterable<Primitive> values)
: this.values = _referenceList(values);
accept(Visitor visitor) => visitor.visitLiteralList(this);
}
class LiteralMapEntry {
final Reference<Primitive> key;
final Reference<Primitive> value;
LiteralMapEntry(Primitive key, Primitive value)
: this.key = new Reference<Primitive>(key),
this.value = new Reference<Primitive>(value);
}
class LiteralMap extends Primitive {
final GenericType type;
final List<LiteralMapEntry> entries;
LiteralMap(this.type, this.entries);
accept(Visitor visitor) => visitor.visitLiteralMap(this);
}
/// Create a non-recursive function.
class CreateFunction extends Primitive implements DartSpecificNode {
final FunctionDefinition definition;
CreateFunction(this.definition);
accept(Visitor visitor) => visitor.visitCreateFunction(this);
}
class Parameter extends Primitive {
Parameter(Entity hint) {
super.hint = hint;
}
// In addition to a parent pointer to the containing Continuation or
// FunctionDefinition, parameters have an index into the list of parameters
// bound by the parent. This gives constant-time access to the continuation
// from the parent.
int parentIndex;
accept(Visitor visitor) => visitor.visitParameter(this);
String toString() => 'Parameter(${hint == null ? null : hint.name})';
}
/// Continuations are normally bound by 'let cont'. A continuation with one
/// parameter and no body is used to represent a function's return continuation.
/// The return continuation is bound by the Function, not by 'let cont'.
class Continuation extends Definition<Continuation> implements InteriorNode {
final List<Parameter> parameters;
Expression body = null;
// In addition to a parent pointer to the containing LetCont, continuations
// have an index into the list of continuations bound by the LetCont. This
// gives constant-time access to the continuation from the parent.
int parent_index;
// A continuation is recursive if it has any recursive invocations.
bool isRecursive = false;
bool get isReturnContinuation => body == null;
Continuation(this.parameters);
Continuation.retrn() : parameters = <Parameter>[new Parameter(null)];
accept(Visitor visitor) => visitor.visitContinuation(this);
}
abstract class ExecutableDefinition implements Node {
RunnableBody get body;
Element get element;
applyPass(Pass pass);
}
// This is basically a function definition with an empty parameter list and a
// field element instead of a function element and no const declarations, and
// never a getter or setter, though that's less important.
class FieldDefinition extends Node implements ExecutableDefinition {
final FieldElement element;
RunnableBody body;
FieldDefinition(this.element, this.body);
FieldDefinition.withoutInitializer(this.element)
: this.body = null;
accept(Visitor visitor) => visitor.visitFieldDefinition(this);
applyPass(Pass pass) => pass.rewriteFieldDefinition(this);
/// `true` if this field has no initializer.
///
/// If `true` [body] is `null`.
///
/// This is different from a initializer that is `null`. Consider this class:
///
/// class Class {
/// final field;
/// Class.a(this.field);
/// Class.b() : this.field = null;
/// Class.c();
/// }
///
/// If `field` had an initializer, possibly `null`, constructors `Class.a` and
/// `Class.b` would be invalid, and since `field` has no initializer
/// constructor `Class.c` is invalid. We therefore need to distinguish the two
/// cases.
bool get hasInitializer => body != null;
}
/// Identifies a mutable variable.
class MutableVariable extends Definition {
/// Body of source code that declares this mutable variable.
ExecutableElement host;
Entity hint;
MutableVariable(this.host, this.hint);
accept(Visitor v) => v.visitMutableVariable(this);
}
class RunnableBody extends InteriorNode {
Expression body;
final Continuation returnContinuation;
RunnableBody(this.body, this.returnContinuation);
accept(Visitor visitor) => visitor.visitRunnableBody(this);
}
/// A function definition, consisting of parameters and a body. The parameters
/// include a distinguished continuation parameter (held by the body).
class FunctionDefinition extends Node
implements ExecutableDefinition {
final FunctionElement element;
final Parameter thisParameter;
/// Mixed list of [Parameter]s and [MutableVariable]s.
final List<Definition> parameters;
final RunnableBody body;
final List<ConstDeclaration> localConstants;
/// Values for optional parameters.
final List<ConstantExpression> defaultParameterValues;
FunctionDefinition(this.element,
this.thisParameter,
this.parameters,
this.body,
this.localConstants,
this.defaultParameterValues);
FunctionDefinition.abstract(this.element,
this.thisParameter,
this.parameters,
this.defaultParameterValues)
: body = null,
localConstants = const <ConstDeclaration>[];
accept(Visitor visitor) => visitor.visitFunctionDefinition(this);
applyPass(Pass pass) => pass.rewriteFunctionDefinition(this);
/// Returns `true` if this function is abstract or external.
///
/// If `true`, [body] is `null` and [localConstants] is empty.
bool get isAbstract => body == null;
}
abstract class Initializer extends Node implements DartSpecificNode {}
class FieldInitializer extends Initializer {
final FieldElement element;
final RunnableBody body;
FieldInitializer(this.element, this.body);
accept(Visitor visitor) => visitor.visitFieldInitializer(this);
}
class SuperInitializer extends Initializer {
final ConstructorElement target;
final List<RunnableBody> arguments;
final Selector selector;
SuperInitializer(this.target, this.arguments, this.selector);
accept(Visitor visitor) => visitor.visitSuperInitializer(this);
}
class ConstructorDefinition extends FunctionDefinition {
final List<Initializer> initializers;
ConstructorDefinition(ConstructorElement element,
Definition thisParameter, // only Dart
List<Definition> parameters,
RunnableBody body,
this.initializers,
List<ConstDeclaration> localConstants,
List<ConstantExpression> defaultParameterValues)
: super(element, thisParameter, parameters, body, localConstants,
defaultParameterValues);
// 'Abstract' here means "has no body" and is used to represent external
// constructors.
ConstructorDefinition.abstract(
ConstructorElement element,
List<Definition> parameters,
List<ConstantExpression> defaultParameterValues)
: initializers = null,
super.abstract(element, null, parameters, defaultParameterValues);
accept(Visitor visitor) => visitor.visitConstructorDefinition(this);
applyPass(Pass pass) => pass.rewriteConstructorDefinition(this);
}
/// Converts the internal representation of a type to a Dart object of type
/// [Type].
class ReifyRuntimeType extends Primitive implements JsSpecificNode {
/// Reference to the internal representation of a type (as produced, for
/// example, by [ReadTypeVariable]).
final Reference<Primitive> value;
ReifyRuntimeType(Primitive value)
: this.value = new Reference<Primitive>(value);
@override
accept(Visitor visitor) => visitor.visitReifyRuntimeType(this);
}
/// Read the value the type variable [variable] from the target object.
///
/// The resulting value is an internal representation (and not neccessarily a
/// Dart object), and must be reified by [ReifyRuntimeType], if it should be
/// used as a Dart value.
class ReadTypeVariable extends Primitive implements JsSpecificNode {
final TypeVariableType variable;
final Reference<Primitive> target;
ReadTypeVariable(this.variable, Primitive target)
: this.target = new Reference<Primitive>(target);
@override
accept(Visitor visitor) => visitor.visitReadTypeVariable(this);
}
List<Reference<Primitive>> _referenceList(Iterable<Primitive> definitions) {
return definitions.map((e) => new Reference<Primitive>(e)).toList();
}
abstract class Visitor<T> {
const Visitor();
T visit(Node node);
// Concrete classes.
T visitFieldDefinition(FieldDefinition node);
T visitFunctionDefinition(FunctionDefinition node);
T visitConstructorDefinition(ConstructorDefinition node);
T visitRunnableBody(RunnableBody node);
// Initializers
T visitFieldInitializer(FieldInitializer node);
T visitSuperInitializer(SuperInitializer node);
// Expressions.
T visitLetPrim(LetPrim node);
T visitLetCont(LetCont node);
T visitLetHandler(LetHandler node);
T visitLetMutable(LetMutable node);
T visitInvokeContinuation(InvokeContinuation node);
T visitInvokeStatic(InvokeStatic node);
T visitInvokeMethod(InvokeMethod node);
T visitInvokeMethodDirectly(InvokeMethodDirectly node);
T visitInvokeConstructor(InvokeConstructor node);
T visitConcatenateStrings(ConcatenateStrings node);
T visitBranch(Branch node);
T visitTypeOperator(TypeOperator node);
T visitSetMutableVariable(SetMutableVariable node);
T visitDeclareFunction(DeclareFunction node);
// Definitions.
T visitLiteralList(LiteralList node);
T visitLiteralMap(LiteralMap node);
T visitConstant(Constant node);
T visitReifyTypeVar(ReifyTypeVar node);
T visitCreateFunction(CreateFunction node);
T visitGetMutableVariable(GetMutableVariable node);
T visitParameter(Parameter node);
T visitContinuation(Continuation node);
T visitMutableVariable(MutableVariable node);
// JavaScript specific nodes.
// Conditions.
T visitIsTrue(IsTrue node);
// Expressions.
T visitSetField(SetField node);
// Definitions.
T visitIdentical(Identical node);
T visitInterceptor(Interceptor node);
T visitCreateInstance(CreateInstance node);
T visitGetField(GetField node);
T visitCreateBox(CreateBox node);
T visitReifyRuntimeType(ReifyRuntimeType node);
T visitReadTypeVariable(ReadTypeVariable node);
}
/// Recursively visits the entire CPS term, and calls abstract `process*`
/// (i.e. `processLetPrim`) functions in pre-order.
class RecursiveVisitor implements Visitor {
const RecursiveVisitor();
visit(Node node) => node.accept(this);
processReference(Reference ref) {}
processRunnableBody(RunnableBody node) {}
visitRunnableBody(RunnableBody node) {
processRunnableBody(node);
visit(node.returnContinuation);
visit(node.body);
}
processFieldDefinition(FieldDefinition node) {}
visitFieldDefinition(FieldDefinition node) {
processFieldDefinition(node);
if (node.hasInitializer) {
visit(node.body);
}
}
processFunctionDefinition(FunctionDefinition node) {}
visitFunctionDefinition(FunctionDefinition node) {
processFunctionDefinition(node);
if (node.thisParameter != null) visit(node.thisParameter);
node.parameters.forEach(visit);
if (!node.isAbstract) {
visit(node.body);
}
}
processConstructorDefinition(ConstructorDefinition node) {}
visitConstructorDefinition(ConstructorDefinition node) {
processConstructorDefinition(node);
if (node.thisParameter != null) visit(node.thisParameter);
node.parameters.forEach(visit);
node.initializers.forEach(visit);
visit(node.body);
}
processFieldInitializer(FieldInitializer node) {}
visitFieldInitializer(FieldInitializer node) {
processFieldInitializer(node);
visit(node.body);
}
processSuperInitializer(SuperInitializer node) {}
visitSuperInitializer(SuperInitializer node) {
processSuperInitializer(node);
node.arguments.forEach(visit);
}
// Expressions.
processLetPrim(LetPrim node) {}
visitLetPrim(LetPrim node) {
processLetPrim(node);
visit(node.primitive);
visit(node.body);
}
processLetCont(LetCont node) {}
visitLetCont(LetCont node) {
processLetCont(node);
node.continuations.forEach(visit);
visit(node.body);
}
processLetHandler(LetHandler node) {}
visitLetHandler(LetHandler node) {
processLetHandler(node);
visit(node.handler);
visit(node.body);
}
processLetMutable(LetMutable node) {}
visitLetMutable(LetMutable node) {
processLetMutable(node);
visit(node.variable);
processReference(node.value);
visit(node.body);
}
processInvokeStatic(InvokeStatic node) {}
visitInvokeStatic(InvokeStatic node) {
processInvokeStatic(node);
processReference(node.continuation);
node.arguments.forEach(processReference);
}
processInvokeContinuation(InvokeContinuation node) {}
visitInvokeContinuation(InvokeContinuation node) {
processInvokeContinuation(node);
processReference(node.continuation);
node.arguments.forEach(processReference);
}
processInvokeMethod(InvokeMethod node) {}
visitInvokeMethod(InvokeMethod node) {
processInvokeMethod(node);
processReference(node.receiver);
processReference(node.continuation);
node.arguments.forEach(processReference);
}
processInvokeMethodDirectly(InvokeMethodDirectly node) {}
visitInvokeMethodDirectly(InvokeMethodDirectly node) {
processInvokeMethodDirectly(node);
processReference(node.receiver);
processReference(node.continuation);
node.arguments.forEach(processReference);
}
processInvokeConstructor(InvokeConstructor node) {}
visitInvokeConstructor(InvokeConstructor node) {
processInvokeConstructor(node);
processReference(node.continuation);
node.arguments.forEach(processReference);
}
processConcatenateStrings(ConcatenateStrings node) {}
visitConcatenateStrings(ConcatenateStrings node) {
processConcatenateStrings(node);
processReference(node.continuation);
node.arguments.forEach(processReference);
}
processBranch(Branch node) {}
visitBranch(Branch node) {
processBranch(node);
processReference(node.trueContinuation);
processReference(node.falseContinuation);
visit(node.condition);
}
processTypeOperator(TypeOperator node) {}
visitTypeOperator(TypeOperator node) {
processTypeOperator(node);
processReference(node.continuation);
processReference(node.receiver);
}
processSetMutableVariable(SetMutableVariable node) {}
visitSetMutableVariable(SetMutableVariable node) {
processSetMutableVariable(node);
processReference(node.variable);
processReference(node.value);
visit(node.body);
}
processDeclareFunction(DeclareFunction node) {}
visitDeclareFunction(DeclareFunction node) {
processDeclareFunction(node);
visit(node.variable);
visit(node.definition);
visit(node.body);
}
// Definitions.
processLiteralList(LiteralList node) {}
visitLiteralList(LiteralList node) {
processLiteralList(node);
node.values.forEach(processReference);
}
processLiteralMap(LiteralMap node) {}
visitLiteralMap(LiteralMap node) {
processLiteralMap(node);
for (LiteralMapEntry entry in node.entries) {
processReference(entry.key);
processReference(entry.value);
}
}
processConstant(Constant node) {}
visitConstant(Constant node) => processConstant(node);
processReifyTypeVar(ReifyTypeVar node) {}
visitReifyTypeVar(ReifyTypeVar node) => processReifyTypeVar(node);
processCreateFunction(CreateFunction node) {}
visitCreateFunction(CreateFunction node) {
processCreateFunction(node);
visit(node.definition);
}
processMutableVariable(node) {}
visitMutableVariable(MutableVariable node) {
processMutableVariable(node);
}
processGetMutableVariable(GetMutableVariable node) {}
visitGetMutableVariable(GetMutableVariable node) {
processGetMutableVariable(node);
processReference(node.variable);
}
processParameter(Parameter node) {}
visitParameter(Parameter node) => processParameter(node);
processContinuation(Continuation node) {}
visitContinuation(Continuation node) {
processContinuation(node);
node.parameters.forEach(visitParameter);
if (node.body != null) visit(node.body);
}
// Conditions.
processIsTrue(IsTrue node) {}
visitIsTrue(IsTrue node) {
processIsTrue(node);
processReference(node.value);
}
// JavaScript specific nodes.
processIdentical(Identical node) {}
visitIdentical(Identical node) {
processIdentical(node);
processReference(node.left);
processReference(node.right);
}
processInterceptor(Interceptor node) {}
visitInterceptor(Interceptor node) {
processInterceptor(node);
processReference(node.input);
}
processCreateInstance(CreateInstance node) {}
visitCreateInstance(CreateInstance node) {
processCreateInstance(node);
node.arguments.forEach(processReference);
}
processSetField(SetField node) {}
visitSetField(SetField node) {
processSetField(node);
processReference(node.object);
processReference(node.value);
visit(node.body);
}
processGetField(GetField node) {}
visitGetField(GetField node) {
processGetField(node);
processReference(node.object);
}
processCreateBox(CreateBox node) {}
visitCreateBox(CreateBox node) {
processCreateBox(node);
}
processReifyRuntimeType(ReifyRuntimeType node) {}
visitReifyRuntimeType(ReifyRuntimeType node) {
processReifyRuntimeType(node);
processReference(node.value);
}
processReadTypeVariable(ReadTypeVariable node) {}
visitReadTypeVariable(ReadTypeVariable node) {
processReadTypeVariable(node);
processReference(node.target);
}
}
/// Keeps track of currently unused register indices.
class RegisterArray {
int nextIndex = 0;
final List<int> freeStack = <int>[];
/// Returns an index that is currently unused.
int makeIndex() {
if (freeStack.isEmpty) {
return nextIndex++;
} else {
return freeStack.removeLast();
}
}
void releaseIndex(int index) {
freeStack.add(index);
}
}
/// Assigns indices to each primitive in the IR such that primitives that are
/// live simultaneously never get assigned the same index.
/// This information is used by the dart tree builder to generate fewer
/// redundant variables.
/// Currently, the liveness analysis is very simple and is often inadequate
/// for removing all of the redundant variables.
class RegisterAllocator implements Visitor {
final dart2js.InternalErrorFunction internalError;
/// Separate register spaces for each source-level variable/parameter.
/// Note that null is used as key for primitives without hints.
final Map<Local, RegisterArray> elementRegisters = <Local, RegisterArray>{};
RegisterAllocator(this.internalError);
RegisterArray getRegisterArray(Local local) {
RegisterArray registers = elementRegisters[local];
if (registers == null) {
registers = new RegisterArray();
elementRegisters[local] = registers;
}
return registers;
}
void allocate(Primitive primitive) {
if (primitive.registerIndex == null) {
primitive.registerIndex = getRegisterArray(primitive.hint).makeIndex();
}
}
void release(Primitive primitive) {
// Do not share indices for temporaries as this may obstruct inlining.
if (primitive.hint == null) return;
if (primitive.registerIndex != null) {
getRegisterArray(primitive.hint).releaseIndex(primitive.registerIndex);
}
}
void visit(Node node) => node.accept(this);
void visitReference(Reference reference) {
allocate(reference.definition);
}
void visitFieldDefinition(FieldDefinition node) {
if (node.hasInitializer) {
visit(node.body);
}
}
void visitRunnableBody(RunnableBody node) {
visit(node.body);
}
void visitFunctionDefinition(FunctionDefinition node) {
if (!node.isAbstract) {
visit(node.body);
}
// Assign indices to unused parameters.
for (Definition param in node.parameters) {
if (param is Primitive) {
allocate(param);
}
}
}
void visitConstructorDefinition(ConstructorDefinition node) {
if (!node.isAbstract) {
node.initializers.forEach(visit);
visit(node.body);
}
// Assign indices to unused parameters.
for (Definition param in node.parameters) {
if (param is Primitive) {
allocate(param);
}
}
}
void visitFieldInitializer(FieldInitializer node) {
visit(node.body.body);
}
void visitSuperInitializer(SuperInitializer node) {
node.arguments.forEach(visit);
}
void visitLetPrim(LetPrim node) {
visit(node.body);
release(node.primitive);
visit(node.primitive);
}
void visitLetCont(LetCont node) {
node.continuations.forEach(visit);
visit(node.body);
}
void visitLetHandler(LetHandler node) {
visit(node.handler);
// Handler parameters that were not used in the handler body will not have
// had register indexes assigned. Assign them here, otherwise they will
// be eliminated later and they should not be (i.e., a catch clause that
// does not use the exception parameter should not have the exception
// parameter eliminated, because it would not be well-formed anymore).
// In any case release the parameter indexes because the parameters are
// not live in the try block.
node.handler.parameters.forEach((Parameter parameter) {
allocate(parameter);
release(parameter);
});
visit(node.body);
}
void visitLetMutable(LetMutable node) {
visit(node.body);
visitReference(node.value);
}
void visitInvokeStatic(InvokeStatic node) {
node.arguments.forEach(visitReference);
}
void visitInvokeContinuation(InvokeContinuation node) {
node.arguments.forEach(visitReference);
}
void visitInvokeMethod(InvokeMethod node) {
visitReference(node.receiver);
node.arguments.forEach(visitReference);
}
void visitInvokeMethodDirectly(InvokeMethodDirectly node) {
visitReference(node.receiver);
node.arguments.forEach(visitReference);
}
void visitInvokeConstructor(InvokeConstructor node) {
node.arguments.forEach(visitReference);
}
void visitConcatenateStrings(ConcatenateStrings node) {
node.arguments.forEach(visitReference);
}
void visitBranch(Branch node) {
visit(node.condition);
}
void visitLiteralList(LiteralList node) {
node.values.forEach(visitReference);
}
void visitLiteralMap(LiteralMap node) {
for (LiteralMapEntry entry in node.entries) {
visitReference(entry.key);
visitReference(entry.value);
}
}
void visitTypeOperator(TypeOperator node) {
visitReference(node.receiver);
}
void visitConstant(Constant node) {
}
void visitReifyTypeVar(ReifyTypeVar node) {
}
void visitCreateFunction(CreateFunction node) {
new RegisterAllocator(internalError).visit(node.definition);
}
void visitGetMutableVariable(GetMutableVariable node) {
}
void visitSetMutableVariable(SetMutableVariable node) {
visit(node.body);
visitReference(node.value);
}
void visitDeclareFunction(DeclareFunction node) {
new RegisterAllocator(internalError).visit(node.definition);
visit(node.body);
}
void visitParameter(Parameter node) {
// Parameters are handled differently depending on whether they are
// function parameters, continuation parameters, exception handler
// parameters, etc. Thus we do not call visitParameter directly and
// handle them explicitly in their parent IR node.
internalError(dart2js.CURRENT_ELEMENT_SPANNABLE,
'tried to allocate a parameter');
}
void visitMutableVariable(MutableVariable node) {}
void visitContinuation(Continuation node) {
visit(node.body);
// Arguments get allocated left-to-right, so we release parameters
// right-to-left. This increases the likelihood that arguments can be
// transferred without intermediate assignments.
for (int i = node.parameters.length - 1; i >= 0; --i) {
release(node.parameters[i]);
}
}
void visitIsTrue(IsTrue node) {
visitReference(node.value);
}
// JavaScript specific nodes.
void visitSetField(SetField node) {
visit(node.body);
visitReference(node.value);
visitReference(node.object);
}
void visitGetField(GetField node) {
visitReference(node.object);
}
void visitCreateBox(CreateBox node) {
}
void visitCreateInstance(CreateInstance node) {
node.arguments.forEach(visitReference);
}
void visitIdentical(Identical node) {
visitReference(node.left);
visitReference(node.right);
}
void visitInterceptor(Interceptor node) {
visitReference(node.input);
}
void visitReifyRuntimeType(ReifyRuntimeType node) {
visitReference(node.value);
}
void visitReadTypeVariable(ReadTypeVariable node) {
visitReference(node.target);
}
}