blob: 12373500e6a0b34fffdd666a72c7f85784b0f9d0 [file] [log] [blame]
// Copyright (c) 2014, 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 code_generator;
import 'glue.dart';
import '../../tree_ir/tree_ir_nodes.dart' as tree_ir;
import '../../js/js.dart' as js;
import '../../elements/elements.dart';
import '../../util/maplet.dart';
import '../../constants/values.dart';
import '../../dart2jslib.dart';
class CodegenBailout {
final tree_ir.Node node;
final String reason;
CodegenBailout(this.node, this.reason);
String get message {
return 'bailout${node != null ? " on $node" : ""}: $reason';
}
}
class CodeGenerator extends tree_ir.Visitor<dynamic, js.Expression> {
final CodegenRegistry registry;
final Glue glue;
ExecutableElement currentFunction;
/// Maps variables to their name.
Map<tree_ir.Variable, String> variableNames = <tree_ir.Variable, String>{};
/// Maps local constants to their name.
Maplet<VariableElement, String> constantNames =
new Maplet<VariableElement, String>();
/// Variable names that have already been used. Used to avoid name clashes.
Set<String> usedVariableNames = new Set<String>();
/// Input to [visitStatement]. Denotes the statement that will execute next
/// if the statements produced by [visitStatement] complete normally.
/// Set to null if control will fall over the end of the method.
tree_ir.Statement fallthrough = null;
Set<tree_ir.Label> usedLabels = new Set<tree_ir.Label>();
List<js.Statement> accumulator = new List<js.Statement>();
CodeGenerator(this.glue, this.registry);
/// Generates JavaScript code for the body of [function].
js.Fun buildFunction(tree_ir.FunctionDefinition function) {
currentFunction = function.element;
visitStatement(function.body);
List<js.Parameter> parameters = new List<js.Parameter>();
Set<tree_ir.Variable> parameterSet = new Set<tree_ir.Variable>();
for (tree_ir.Variable parameter in function.parameters) {
String name = getVariableName(parameter);
parameters.add(new js.Parameter(name));
parameterSet.add(parameter);
}
List<js.VariableInitialization> jsVariables = <js.VariableInitialization>[];
for (tree_ir.Variable variable in variableNames.keys) {
if (parameterSet.contains(variable)) continue;
String name = getVariableName(variable);
js.VariableInitialization jsVariable = new js.VariableInitialization(
new js.VariableDeclaration(name),
null);
jsVariables.add(jsVariable);
}
if (jsVariables.length > 0) {
// Would be nice to avoid inserting at the beginning of list.
accumulator.insert(0, new js.ExpressionStatement(
new js.VariableDeclarationList(jsVariables)));
}
return new js.Fun(parameters, new js.Block(accumulator));
}
@override
js.Expression visitExpression(tree_ir.Expression node) {
js.Expression result = node.accept(this);
if (result == null) {
glue.reportInternalError('$node did not produce code.');
}
return result;
}
/// Generates a name for the given variable. First trying with the name of
/// the [Variable.element] if it is non-null.
String getVariableName(tree_ir.Variable variable) {
// TODO(sigurdm): Handle case where the variable belongs to an enclosing
// function.
if (variable.host != currentFunction) giveup(variable);
// Get the name if we already have one.
String name = variableNames[variable];
if (name != null) {
return name;
}
// Synthesize a variable name that isn't used elsewhere.
// The [usedVariableNames] set is shared between nested emitters,
// so this also prevents clash with variables in an enclosing/inner scope.
// The renaming phase after codegen will further prefix local variables
// so they cannot clash with top-level variables or fields.
String prefix = variable.element == null ? 'v' : variable.element.name;
int counter = 0;
name = glue.safeVariableName(variable.element == null
? '$prefix$counter'
: variable.element.name);
while (!usedVariableNames.add(name)) {
++counter;
name = '$prefix$counter';
}
variableNames[variable] = name;
return name;
}
List<js.Expression> visitArguments(List<tree_ir.Expression> arguments) {
return arguments.map(visitExpression).toList();
}
giveup(tree_ir.Node node,
[String reason = 'unimplemented in CodeGenerator']) {
throw new CodegenBailout(node, reason);
}
@override
js.Expression visitConcatenateStrings(tree_ir.ConcatenateStrings node) {
return giveup(node);
// TODO: implement visitConcatenateStrings
}
@override
js.Expression visitConditional(tree_ir.Conditional node) {
return new js.Conditional(
visitExpression(node.condition),
visitExpression(node.thenExpression),
visitExpression(node.elseExpression));
}
js.Expression buildConstant(ConstantValue constant) {
registry.registerCompileTimeConstant(constant);
return glue.constantReference(constant);
}
@override
js.Expression visitConstant(tree_ir.Constant node) {
return buildConstant(node.expression.value);
}
@override
js.Expression visitFunctionExpression(tree_ir.FunctionExpression node) {
return giveup(node);
// TODO: implement visitFunctionExpression
}
js.Expression compileConstant(ParameterElement parameter) {
return buildConstant(glue.getConstantForVariable(parameter).value);
}
// TODO(karlklose): get rid of the selector argument.
js.Expression buildStaticInvoke(Selector selector,
Element target,
List<js.Expression> arguments) {
registry.registerStaticInvocation(target.declaration);
if (target == glue.getInterceptorMethod) {
// This generates a call to the specialized interceptor function, which
// does not have a specialized element yet, but is emitted as a stub from
// the emitter in [InterceptorStubGenerator].
// TODO(karlklose): Either change [InvokeStatic] to take an [Entity]
// instead of an [Element] and model the getInterceptor functions as
// [Entity]s or add a specialized Tree-IR node for interceptor calls.
registry.registerUseInterceptor();
js.VariableUse interceptorLibrary = glue.getInterceptorLibrary();
return js.propertyCall(interceptorLibrary, selector.name, arguments);
} else {
js.Expression elementAccess = glue.staticFunctionAccess(target);
return new js.Call(elementAccess, arguments);
}
}
List<js.Expression> compileStaticArgumentList(
Selector selector,
Element target, /* TODO(karlklose): this should be the signature. */
List<tree_ir.Expression> arguments) {
return selector.makeArgumentsList(
target.implementation,
visitArguments(arguments),
compileConstant);
}
@override
js.Expression visitInvokeConstructor(tree_ir.InvokeConstructor node) {
checkStaticTargetIsValid(node, node.target);
if (node.constant != null) return giveup(node);
registry.registerInstantiatedClass(node.target.enclosingClass);
Selector selector = node.selector;
FunctionElement target = node.target;
List<js.Expression> arguments =
compileStaticArgumentList(selector, target, node.arguments);
return buildStaticInvoke(selector, target, arguments);
}
void registerMethodInvoke(tree_ir.InvokeMethod node) {
Selector selector = node.selector;
if (selector.isGetter) {
registry.registerDynamicGetter(selector);
} else if (selector.isSetter) {
registry.registerDynamicSetter(selector);
} else {
assert(invariant(CURRENT_ELEMENT_SPANNABLE,
selector.isCall || selector.isOperator || selector.isIndex,
message: 'unexpected kind ${selector.kind}'));
// TODO(sigurdm): We should find a better place to register the call.
Selector call = new Selector.callClosureFrom(selector);
registry.registerDynamicInvocation(call);
registry.registerDynamicInvocation(selector);
}
}
@override
js.Expression visitInvokeMethod(tree_ir.InvokeMethod node) {
registerMethodInvoke(node);
return js.propertyCall(visitExpression(node.receiver),
glue.invocationName(node.selector),
visitArguments(node.arguments));
}
/// Checks that the target of the static call is not an [ErroneousElement].
///
/// This helper should be removed and the code to generate the CPS IR for
/// the dart2js backend should construct a call to a helper that throw an
/// appropriate error message instead of the static call.
///
/// See [SsaBuilder.visitStaticSend] as an example how to do this.
void checkStaticTargetIsValid(tree_ir.Node node, Element target) {
if (target.isErroneous) {
giveup(node, 'cannot generate error handling code'
' for call to unresolved target');
}
}
@override
js.Expression visitInvokeStatic(tree_ir.InvokeStatic node) {
checkStaticTargetIsValid(node, node.target);
if (node.target is! FunctionElement) {
giveup(node, 'static getters and setters are not supported.');
}
Selector selector = node.selector;
FunctionElement target = node.target;
List<js.Expression> arguments =
compileStaticArgumentList(selector, target, node.arguments);
return buildStaticInvoke(selector, target, arguments);
}
@override
js.Expression visitInvokeMethodDirectly(tree_ir.InvokeMethodDirectly node) {
registry.registerDirectInvocation(node.target.declaration);
if (node.target is ConstructorBodyElement) {
// A constructor body cannot be overriden or intercepted, so we can
// use the short form for this invocation.
// TODO(asgerf): prevent name clash between constructor bodies.
return js.js('#.#(#)',
[visitExpression(node.receiver),
glue.instanceMethodName(node.target),
visitArguments(node.arguments)]);
}
return js.js('#.#.call(#, #)',
[glue.prototypeAccess(node.target.enclosingClass),
glue.invocationName(node.selector),
visitExpression(node.receiver),
visitArguments(node.arguments)]);
}
@override
js.Expression visitLiteralList(tree_ir.LiteralList node) {
registry.registerInstantiatedClass(glue.listClass);
int length = node.values.length;
List<js.Expression> entries = node.values.map(visitExpression).toList();
return new js.ArrayInitializer(entries);
}
@override
js.Expression visitLiteralMap(tree_ir.LiteralMap node) {
ConstructorElement constructor;
if (node.entries.isEmpty) {
constructor = glue.mapLiteralConstructorEmpty;
} else {
constructor = glue.mapLiteralConstructor;
}
List<js.Expression> entries =
new List<js.Expression>(2 * node.entries.length);
for (int i = 0; i < node.entries.length; i++) {
entries[2 * i] = visitExpression(node.entries[i].key);
entries[2 * i + 1] = visitExpression(node.entries[i].value);
}
List<js.Expression> args = entries.isEmpty
? <js.Expression>[]
: <js.Expression>[new js.ArrayInitializer(entries)];
return buildStaticInvoke(
new Selector.call(constructor.name, constructor.library, 2),
constructor,
args);
}
@override
js.Expression visitLogicalOperator(tree_ir.LogicalOperator node) {
return new js.Binary(
node.operator,
visitExpression(node.left),
visitExpression(node.right));
}
@override
js.Expression visitNot(tree_ir.Not node) {
return new js.Prefix("!", visitExpression(node.operand));
}
@override
js.Expression visitReifyTypeVar(tree_ir.ReifyTypeVar node) {
return giveup(node);
// TODO: implement visitReifyTypeVar
}
@override
js.Expression visitThis(tree_ir.This node) {
// TODO(sigurdm): Inside a js closure this will not work.
return new js.This();
}
@override
js.Expression visitTypeOperator(tree_ir.TypeOperator node) {
return giveup(node);
// TODO: implement visitTypeOperator
}
@override
js.Expression visitVariable(tree_ir.Variable node) {
return new js.VariableUse(getVariableName(node));
}
@override
void visitContinue(tree_ir.Continue node) {
tree_ir.Statement fallthrough = this.fallthrough;
if (node.target.binding == fallthrough) {
// Fall through to continue target
} else if (fallthrough is tree_ir.Continue &&
fallthrough.target == node.target) {
// Fall through to equivalent continue
} else {
usedLabels.add(node.target);
accumulator.add(new js.Continue(node.target.name));
}
}
@override
void visitExpressionStatement(tree_ir.ExpressionStatement node) {
accumulator.add(new js.ExpressionStatement(
visitExpression(node.expression)));
visitStatement(node.next);
}
@override
void visitFunctionDeclaration(tree_ir.FunctionDeclaration node) {
giveup(node);
// TODO: implement visitFunctionDeclaration
}
@override
void visitIf(tree_ir.If node) {
accumulator.add(new js.If(visitExpression(node.condition),
buildBody(node.thenStatement),
buildBody(node.elseStatement)));
}
@override
void visitLabeledStatement(tree_ir.LabeledStatement node) {
accumulator.add(buildLabeled(() => buildBody(node.body),
node.label,
node.next));
visitStatement(node.next);
}
js.Statement buildLabeled(js.Statement buildBody(),
tree_ir.Label label,
tree_ir.Statement fallthroughStatement) {
tree_ir.Statement savedFallthrough = fallthrough;
fallthrough = fallthroughStatement;
js.Statement result = buildBody();
if (usedLabels.remove(label)) {
result = new js.LabeledStatement(label.name, result);
}
fallthrough = savedFallthrough;
return result;
}
@override
void visitAssign(tree_ir.Assign node) {
tree_ir.Expression value = node.definition;
js.Expression definition = visitExpression(value);
accumulator.add(new js.ExpressionStatement(new js.Assignment(
visitVariable(node.variable),
definition)));
visitStatement(node.next);
}
@override
void visitBreak(tree_ir.Break node) {
tree_ir.Statement fallthrough = this.fallthrough;
if (node.target.binding.next == fallthrough) {
// Fall through to break target
} else if (fallthrough is tree_ir.Break &&
fallthrough.target == node.target) {
// Fall through to equivalent break
} else {
usedLabels.add(node.target);
accumulator.add(new js.Break(node.target.name));
}
}
/// Returns the current [accumulator] wrapped in a block if neccessary.
js.Statement _bodyAsStatement() {
if (accumulator.length == 0) {
return new js.EmptyStatement();
}
if (accumulator.length == 1) {
return accumulator.single;
}
return new js.Block(accumulator);
}
/// Builds a nested statement.
js.Statement buildBody(tree_ir.Statement statement) {
List<js.Statement> savedAccumulator = accumulator;
accumulator = new List<js.Statement>();
visitStatement(statement);
js.Statement result = _bodyAsStatement();
accumulator = savedAccumulator;
return result;
}
js.Statement buildWhile(js.Expression condition,
tree_ir.Statement body,
tree_ir.Label label,
tree_ir.Statement fallthroughStatement) {
return buildLabeled(() => new js.While(condition, buildBody(body)),
label,
fallthroughStatement);
}
@override
void visitWhileCondition(tree_ir.WhileCondition node) {
accumulator.add(
buildWhile(visitExpression(node.condition),
node.body,
node.label,
node));
visitStatement(node.next);
}
@override
void visitWhileTrue(tree_ir.WhileTrue node) {
accumulator.add(
buildWhile(new js.LiteralBool(true), node.body, node.label, node));
}
@override
void visitReturn(tree_ir.Return node) {
accumulator.add(new js.Return(visitExpression(node.value)));
}
@override
js.Expression visitFieldInitializer(tree_ir.FieldInitializer node) {
return giveup(node);
// TODO: implement FieldInitializer
}
@override
js.Expression visitSuperInitializer(tree_ir.SuperInitializer node) {
return giveup(node);
// TODO: implement SuperInitializer
}
@override
js.Expression visitCreateBox(tree_ir.CreateBox node) {
return new js.ObjectInitializer([]);
}
@override
js.Expression visitCreateInstance(tree_ir.CreateInstance node) {
registry.registerInstantiatedClass(node.classElement);
return new js.New(glue.constructorAccess(node.classElement),
node.arguments.map(visitExpression).toList());
}
@override
js.Expression visitGetField(tree_ir.GetField node) {
return new js.PropertyAccess.field(
visitExpression(node.object),
glue.instanceFieldPropertyName(node.field));
}
@override
void visitSetField(tree_ir.SetField node) {
js.PropertyAccess field =
new js.PropertyAccess.field(
visitExpression(node.object),
glue.instanceFieldPropertyName(node.field));
js.Assignment asn = new js.Assignment(field, visitExpression(node.value));
accumulator.add(new js.ExpressionStatement(asn));
visitStatement(node.next);
}
}