blob: 045e7664a039764ab37e32a628d66fd21b20b9d9 [file] [log] [blame]
library dart2js.unsugar_cps;
import '../../cps_ir/cps_ir_nodes.dart';
import '../../cps_ir/optimizers.dart' show ParentVisitor;
import '../../constants/expressions.dart';
import '../../constants/values.dart';
import '../../elements/elements.dart' show
ClassElement,
FieldElement,
FunctionElement,
Local,
ExecutableElement;
import '../../js_backend/codegen/glue.dart';
import '../../dart2jslib.dart' show Selector, World;
import '../../cps_ir/cps_ir_builder.dart' show ThisParameterLocal;
class ExplicitReceiverParameterEntity implements Local {
String get name => 'receiver';
final ExecutableElement executableContext;
ExplicitReceiverParameterEntity(this.executableContext);
toString() => 'ExplicitReceiverParameterEntity($executableContext)';
}
/// Rewrites the initial CPS IR to make Dart semantics explicit and inserts
/// special nodes that respect JavaScript behavior.
///
/// Performs the following rewrites:
/// - Rewrite [IsTrue] in a [Branch] to do boolean conversion.
/// - Add interceptors at call sites that use interceptor calling convention.
/// - Add explicit receiver argument for methods that are called in interceptor
/// calling convention.
/// - Convert two-parameter exception handlers to one-parameter ones.
class UnsugarVisitor extends RecursiveVisitor {
Glue _glue;
ParentVisitor _parentVisitor = new ParentVisitor();
Parameter thisParameter;
Parameter explicitReceiverParameter;
// In a catch block, rethrow implicitly throws the block's exception
// parameter. This is the exception parameter when nested in a catch
// block and null otherwise.
Parameter _exceptionParameter = null;
UnsugarVisitor(this._glue);
void rewrite(FunctionDefinition function) {
bool inInterceptedMethod = _glue.isInterceptedMethod(function.element);
if (function.element.name == '==' &&
function.parameters.length == 1 &&
!_glue.operatorEqHandlesNullArgument(function.element)) {
// Insert the null check that the language semantics requires us to
// perform before calling operator ==.
insertEqNullCheck(function);
}
if (inInterceptedMethod) {
thisParameter = function.thisParameter;
ThisParameterLocal holder = thisParameter.hint;
explicitReceiverParameter = new Parameter(
new ExplicitReceiverParameterEntity(
holder.executableContext));
function.parameters.insert(0, explicitReceiverParameter);
}
// Set all parent pointers.
_parentVisitor.visit(function);
if (inInterceptedMethod) {
explicitReceiverParameter.substituteFor(thisParameter);
}
visit(function);
}
@override
visit(Node node) {
Node result = node.accept(this);
return result != null ? result : node;
}
Constant get trueConstant {
return new Constant(
new BoolConstantExpression(true),
new TrueConstantValue());
}
Constant get falseConstant {
return new Constant(
new BoolConstantExpression(false),
new FalseConstantValue());
}
Constant get nullConstant {
return new Constant(
new NullConstantExpression(),
new NullConstantValue());
}
void insertLetPrim(Primitive primitive, Expression node) {
LetPrim let = new LetPrim(primitive);
InteriorNode parent = node.parent;
parent.body = let;
let.body = node;
node.parent = let;
let.parent = parent;
}
void insertEqNullCheck(FunctionDefinition function) {
// Replace
//
// body;
//
// with
//
// if (identical(arg, null))
// return false;
// else
// body;
//
Continuation originalBody = new Continuation(<Parameter>[]);
originalBody.body = function.body;
Continuation returnFalse = new Continuation(<Parameter>[]);
Primitive falsePrimitive = falseConstant;
returnFalse.body =
new LetPrim(falsePrimitive,
new InvokeContinuation(
function.returnContinuation, <Primitive>[falsePrimitive]));
Primitive nullPrimitive = nullConstant;
Primitive test = new Identical(function.parameters.single, nullPrimitive);
Expression newBody =
new LetCont.many(<Continuation>[returnFalse, originalBody],
new LetPrim(nullPrimitive,
new LetPrim(test,
new Branch(
new IsTrue(test),
returnFalse,
originalBody))));
function.body = newBody;
}
/// Insert a static call to [function] at the point of [node] with result
/// [result].
///
/// Rewrite [node] to
///
/// let cont continuation(result) = node
/// in invoke function arguments continuation
void insertStaticCall(FunctionElement function, List<Primitive> arguments,
Parameter result,
Expression node) {
InteriorNode parent = node.parent;
Continuation continuation = new Continuation([result]);
continuation.body = node;
_parentVisitor.processContinuation(continuation);
Selector selector = new Selector.fromElement(function);
// TODO(johnniwinther): Come up with an implementation of SourceInformation
// for calls such as this one that don't appear in the original source.
InvokeStatic invoke =
new InvokeStatic(function, selector, arguments, continuation, null);
_parentVisitor.processInvokeStatic(invoke);
LetCont letCont = new LetCont(continuation, invoke);
_parentVisitor.processLetCont(letCont);
parent.body = letCont;
letCont.parent = parent;
}
processLetHandler(LetHandler node) {
// BEFORE: Handlers have two parameters, exception and stack trace.
// AFTER: Handlers have a single parameter, which is unwrapped to get
// the exception and stack trace.
_exceptionParameter = node.handler.parameters.first;
Parameter stackTraceParameter = node.handler.parameters.last;
Expression body = node.handler.body;
if (_exceptionParameter.hasAtLeastOneUse ||
stackTraceParameter.hasAtLeastOneUse) {
Parameter exceptionValue = new Parameter(null);
exceptionValue.substituteFor(_exceptionParameter);
insertStaticCall(_glue.getExceptionUnwrapper(), [_exceptionParameter],
exceptionValue, body);
if (stackTraceParameter.hasAtLeastOneUse) {
Parameter stackTraceValue = new Parameter(null);
stackTraceValue.substituteFor(stackTraceParameter);
insertStaticCall(_glue.getTraceFromException(), [_exceptionParameter],
stackTraceValue, body);
}
}
assert(stackTraceParameter.hasNoUses);
node.handler.parameters.removeLast();
}
@override
visitLetHandler(LetHandler node) {
assert(node.handler.parameters.length == 2);
Parameter previousExceptionParameter = _exceptionParameter;
_exceptionParameter = node.handler.parameters.first;
processLetHandler(node);
visit(node.handler);
_exceptionParameter = previousExceptionParameter;
visit(node.body);
}
processThrow(Throw node) {
// The subexpression of throw is wrapped in the JavaScript output.
Parameter value = new Parameter(null);
insertStaticCall(_glue.getWrapExceptionHelper(), [node.value.definition],
value, node);
node.value.unlink();
node.value = new Reference<Primitive>(value);
}
processRethrow(Rethrow node) {
// Rethrow can only appear in a catch block. It throws that block's
// (wrapped) caught exception.
Throw replacement = new Throw(_exceptionParameter);
InteriorNode parent = node.parent;
parent.body = replacement;
replacement.parent = parent;
// The original rethrow does not have any references that we need to
// worry about unlinking.
}
processInvokeMethod(InvokeMethod node) {
Selector selector = node.selector;
if (!_glue.isInterceptedSelector(selector)) return;
Primitive receiver = node.receiver.definition;
Primitive newReceiver;
if (receiver == explicitReceiverParameter) {
// If the receiver is the explicit receiver, we are calling a method in
// the same interceptor:
// Change 'receiver.foo()' to 'this.foo(receiver)'.
newReceiver = thisParameter;
} else {
// TODO(sra): Move the computation of interceptedClasses to a much later
// phase and take into account the remaining uses of the interceptor.
Set<ClassElement> interceptedClasses =
_glue.getInterceptedClassesOn(selector);
_glue.registerSpecializedGetInterceptor(interceptedClasses);
newReceiver = new Interceptor(receiver, interceptedClasses);
insertLetPrim(newReceiver, node);
}
node.arguments.insert(0, node.receiver);
node.receiver = new Reference<Primitive>(newReceiver);
}
processInvokeMethodDirectly(InvokeMethodDirectly node) {
if (_glue.isInterceptedMethod(node.target)) {
Primitive nullPrim = nullConstant;
insertLetPrim(nullPrim, node);
node.arguments.insert(0, node.receiver);
// TODO(sra): `null` is not adequate. Interceptors project the class
// hierarchy onto an interceptor hierarchy. A super call that does a
// method call will use the javascript 'this' parameter to avoid calling
// getInterceptor again, so the receiver must be the interceptor (likely
// `this`), not `null`.
node.receiver = new Reference<Primitive>(nullPrim);
}
}
processBranch(Branch node) {
// TODO(karlklose): implement the checked mode part of boolean conversion.
InteriorNode parent = node.parent;
IsTrue condition = node.condition;
Primitive t = trueConstant;
Primitive i = new Identical(condition.value.definition, t);
LetPrim newNode = new LetPrim(t,
new LetPrim(i,
new Branch(new IsTrue(i),
node.trueContinuation.definition,
node.falseContinuation.definition)));
condition.value.unlink();
node.trueContinuation.unlink();
node.falseContinuation.unlink();
parent.body = newNode;
}
processInterceptor(Interceptor node) {
_glue.registerSpecializedGetInterceptor(node.interceptedClasses);
}
}