Index: pkg/compiler/lib/src/js_backend/codegen/unsugar.dart |
diff --git a/pkg/compiler/lib/src/js_backend/codegen/unsugar.dart b/pkg/compiler/lib/src/js_backend/codegen/unsugar.dart |
index f439e95de4fdab81de1f84f64b2903319cbc4feb..dff9f1519bc19c09c1af521328a9cd33baa82013 100644 |
--- a/pkg/compiler/lib/src/js_backend/codegen/unsugar.dart |
+++ b/pkg/compiler/lib/src/js_backend/codegen/unsugar.dart |
@@ -6,26 +6,67 @@ import '../../cps_ir/cps_ir_nodes.dart'; |
import '../../cps_ir/optimizers.dart'; |
import '../../constants/expressions.dart'; |
import '../../constants/values.dart'; |
-import '../../elements/elements.dart' |
- show ClassElement, FieldElement, FunctionElement, Element; |
+import '../../elements/elements.dart' show |
+ ClassElement, |
+ FieldElement, |
+ FunctionElement, |
+ Local, |
+ ExecutableElement; |
import '../../js_backend/codegen/glue.dart'; |
import '../../dart2jslib.dart' show Selector, World; |
+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. |
-/// - converts two-parameter exception handlers to one-parameter ones. |
+/// - 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; |
+ |
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); |
} |
@@ -42,6 +83,19 @@ class UnsugarVisitor extends RecursiveVisitor { |
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; |
@@ -51,6 +105,42 @@ class UnsugarVisitor extends RecursiveVisitor { |
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.body; |
+ |
+ Continuation returnFalse = new Continuation(<Parameter>[]); |
+ Primitive falsePrimitive = falseConstant; |
+ returnFalse.body = |
+ new LetPrim(falsePrimitive, |
+ new InvokeContinuation( |
+ function.body.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.body = newBody; |
+ } |
+ |
/// Insert a static call to [function] at the point of [node] with result |
/// [result]. |
/// |
@@ -118,32 +208,42 @@ class UnsugarVisitor extends RecursiveVisitor { |
processInvokeMethod(InvokeMethod node) { |
Selector selector = node.selector; |
- // TODO(karlklose): should we rewrite all selectors? |
if (!_glue.isInterceptedSelector(selector)) return; |
Primitive receiver = node.receiver.definition; |
- Set<ClassElement> interceptedClasses = |
+ 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); |
+ _glue.registerSpecializedGetInterceptor(interceptedClasses); |
+ newReceiver = new Interceptor(receiver, interceptedClasses); |
+ insertLetPrim(newReceiver, node); |
+ } |
- Primitive intercepted = new Interceptor(receiver, interceptedClasses); |
- insertLetPrim(intercepted, node); |
node.arguments.insert(0, node.receiver); |
node.callingConvention = CallingConvention.JS_INTERCEPTED; |
assert(node.isValid); |
- node.receiver = new Reference<Primitive>(intercepted); |
- } |
- |
- Primitive makeNull() { |
- NullConstantValue nullConst = new NullConstantValue(); |
- return new Constant(new NullConstantExpression(nullConst)); |
+ node.receiver = new Reference<Primitive>(newReceiver); |
} |
processInvokeMethodDirectly(InvokeMethodDirectly node) { |
if (_glue.isInterceptedMethod(node.target)) { |
- Primitive nullPrim = makeNull(); |
+ 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); |
} |
} |