Index: dart/sdk/lib/_internal/lib/js_helper.dart |
=================================================================== |
--- dart/sdk/lib/_internal/lib/js_helper.dart (revision 31466) |
+++ dart/sdk/lib/_internal/lib/js_helper.dart (working copy) |
@@ -5,29 +5,33 @@ |
library _js_helper; |
import 'dart:collection'; |
-import 'dart:_foreign_helper' show DART_CLOSURE_TO_JS, |
- JS, |
- JS_CALL_IN_ISOLATE, |
- JS_CONST, |
- JS_CURRENT_ISOLATE, |
- JS_CURRENT_ISOLATE_CONTEXT, |
- JS_DART_OBJECT_CONSTRUCTOR, |
- JS_FUNCTION_CLASS_NAME, |
- JS_FUNCTION_TYPE_NAMED_PARAMETERS_TAG, |
- JS_FUNCTION_TYPE_OPTIONAL_PARAMETERS_TAG, |
- JS_FUNCTION_TYPE_REQUIRED_PARAMETERS_TAG, |
- JS_FUNCTION_TYPE_RETURN_TYPE_TAG, |
- JS_FUNCTION_TYPE_TAG, |
- JS_FUNCTION_TYPE_VOID_RETURN_TAG, |
- JS_GET_NAME, |
- JS_HAS_EQUALS, |
- JS_IS_INDEXABLE_FIELD_NAME, |
- JS_OBJECT_CLASS_NAME, |
- JS_NULL_CLASS_NAME, |
- JS_OPERATOR_AS_PREFIX, |
- JS_OPERATOR_IS_PREFIX, |
- JS_SIGNATURE_NAME, |
- RAW_DART_FUNCTION_REF; |
+ |
+import 'dart:_foreign_helper' show |
+ DART_CLOSURE_TO_JS, |
+ JS, |
+ JS_CALL_IN_ISOLATE, |
+ JS_CONST, |
+ JS_CURRENT_ISOLATE, |
+ JS_CURRENT_ISOLATE_CONTEXT, |
+ JS_DART_OBJECT_CONSTRUCTOR, |
+ JS_EFFECT, |
+ JS_FUNCTION_CLASS_NAME, |
+ JS_FUNCTION_TYPE_NAMED_PARAMETERS_TAG, |
+ JS_FUNCTION_TYPE_OPTIONAL_PARAMETERS_TAG, |
+ JS_FUNCTION_TYPE_REQUIRED_PARAMETERS_TAG, |
+ JS_FUNCTION_TYPE_RETURN_TYPE_TAG, |
+ JS_FUNCTION_TYPE_TAG, |
+ JS_FUNCTION_TYPE_VOID_RETURN_TAG, |
+ JS_GET_NAME, |
+ JS_HAS_EQUALS, |
+ JS_IS_INDEXABLE_FIELD_NAME, |
+ JS_NULL_CLASS_NAME, |
+ JS_OBJECT_CLASS_NAME, |
+ JS_OPERATOR_AS_PREFIX, |
+ JS_OPERATOR_IS_PREFIX, |
+ JS_SIGNATURE_NAME, |
+ RAW_DART_FUNCTION_REF; |
+ |
import 'dart:_interceptors'; |
import 'dart:_collection-dev' as _symbol_dev; |
import 'dart:_collection-dev' show MappedIterable; |
@@ -1741,10 +1745,21 @@ |
static const OPTIONAL_PARAMETER_INDEX = 4; |
static const DEFAULT_ARGUMENTS_INDEX = 5; |
+ /** |
+ * Global counter to prevent reusing function code objects. |
+ * |
+ * V8 will share the underlying function code objects when the same string is |
+ * passed to "new Function". Shared function code objects can lead to |
+ * sub-optimal performance due to polymorhism, and can be prevented by |
+ * ensuring the strings are different. |
+ */ |
+ static int functionCounter = 0; |
+ |
Closure(); |
/** |
- * Creates a closure for use by implicit getters associated with a method. |
+ * Creates a new closure class for use by implicit getters associated with a |
+ * method. |
* |
* In other words, creates a tear-off closure. |
* |
@@ -1762,46 +1777,83 @@ |
* Caution: this function may be called when building constants. |
* TODO(ahe): Don't call this function when building constants. |
*/ |
- factory Closure.fromTearOff(receiver, |
- List functions, |
- List reflectionInfo, |
- bool isStatic, |
- jsArguments, |
- String propertyName) { |
+ static fromTearOff(receiver, |
+ List functions, |
+ List reflectionInfo, |
+ bool isStatic, |
+ jsArguments, |
+ String propertyName) { |
+ JS_EFFECT(() { |
+ BoundClosure.receiverOf(JS('BoundClosure', 'void 0')); |
+ BoundClosure.selfOf(JS('BoundClosure', 'void 0')); |
+ }); |
// TODO(ahe): All the place below using \$ should be rewritten to go |
// through the namer. |
var function = JS('', '#[#]', functions, 0); |
- // TODO(ahe): Try fetching the property directly instead of using "in". |
- if (isStatic && JS('bool', '"\$tearOff" in #', function)) { |
- // The implicit closure of a static function is always the same. |
- return JS('Closure', '#.\$tearOff', function); |
- } |
- |
String name = JS('String|Null', '#.\$stubName', function); |
String callName = JS('String|Null', '#.\$callName', function); |
- JS('', r'#.$reflectionInfo = #', function, reflectionInfo); |
+ JS('', '#.\$reflectionInfo = #', function, reflectionInfo); |
ReflectionInfo info = new ReflectionInfo(function); |
var functionType = info.functionType; |
+ // function tmp() {}; |
+ // tmp.prototype = BC.prototype; |
+ // var proto = new tmp; |
+ // for each computed prototype property: |
+ // proto[property] = ...; |
+ // proto._init = BC; |
+ // var dynClosureConstructor = |
+ // new Function('self', 'target', 'receiver', 'name', |
+ // 'this._init(self, target, receiver, name)'); |
+ // proto.constructor = dynClosureConstructor; // Necessary? |
+ // dynClosureConstructor.prototype = proto; |
+ // return dynClosureConstructor; |
+ |
+ // We need to create a new subclass of either TearOffClosure or |
+ // BoundClosure. For this, we need to create an object whose prototype is |
+ // the prototype is either TearOffClosure.prototype or |
+ // BoundClosure.prototype, respectively in pseudo JavaScript code. The |
+ // simplest way to access the JavaScript construction function of a Dart |
+ // class is to create an instance and access its constructor property. The |
+ // newly created instance could in theory be used directly as the |
+ // prototype, but it might include additional fields that we don't need. |
+ // So we only use the new instance to access the constructor property and |
+ // use Object.create to create the desired prototype. |
+ var prototype = isStatic |
+ // TODO(ahe): Safe to use Object.create? |
+ ? JS('TearOffClosure', 'Object.create(#.constructor.prototype)', |
+ new TearOffClosure()) |
+ : JS('BoundClosure', 'Object.create(#.constructor.prototype)', |
+ new BoundClosure(null, null, null, null)); |
+ |
+ JS('', '#.\$initialize = #', prototype, JS('', '#.constructor', prototype)); |
+ var constructor = isStatic |
+ ? JS('', 'function(){this.\$initialize()}') |
+ : isCsp |
+ ? JS('', 'function(a,b,c,d) {this.\$initialize(a,b,c,d)}') |
+ : JS('', |
+ 'new Function("a","b","c","d",' |
+ '"this.\$initialize(a,b,c,d);"+#)', |
+ functionCounter++); |
+ |
+ // TODO(ahe): Is it necessary to set the constructor property? |
+ JS('', '#.constructor = #', prototype, constructor); |
+ |
+ JS('', '#.prototype = #', constructor, prototype); |
+ |
// Create a closure and "monkey" patch it with call stubs. |
- Closure closure; |
var trampoline = function; |
+ var isIntercepted = false; |
if (!isStatic) { |
if (JS('bool', '#.length == 1', jsArguments)) { |
// Intercepted call. |
- var argument = JS('', '#[0]', jsArguments); |
- trampoline = forwardInterceptedCallTo(argument, receiver, function); |
- closure = new BoundClosure(receiver, function, argument, name); |
- } else { |
- trampoline = forwardTo(receiver, function); |
- closure = new BoundClosure(receiver, function, null, name); |
+ isIntercepted = true; |
} |
+ trampoline = forwardCallTo(function, isIntercepted); |
} else { |
- closure = new TearOffClosure(); |
- JS('', '#.\$tearOff = #', function, closure); |
- JS('', r'#.$name = #', closure, propertyName); |
+ JS('', '#.\$name = #', prototype, propertyName); |
} |
var signatureFunction; |
@@ -1811,54 +1863,241 @@ |
functionType); |
} else if (!isStatic |
&& JS('bool', 'typeof # == "function"', functionType)) { |
- signatureFunction = functionType; |
- JS('', r'#.$receiver = #', closure, receiver); |
+ var getReceiver = isIntercepted |
+ ? RAW_DART_FUNCTION_REF(BoundClosure.receiverOf) |
+ : RAW_DART_FUNCTION_REF(BoundClosure.selfOf); |
+ signatureFunction = JS( |
+ '', |
+ 'function(f,r){' |
+ 'return function(){' |
+ 'return f.apply({\$receiver:r(this)},arguments)' |
+ '}' |
+ '}(#,#)', functionType, getReceiver); |
} else { |
throw 'Error in reflectionInfo.'; |
} |
- JS('', '#.\$signature = #', closure, signatureFunction); |
+ JS('', '#.\$signature = #', prototype, signatureFunction); |
- JS('', '#[#] = #', closure, callName, trampoline); |
+ JS('', '#[#] = #', prototype, callName, trampoline); |
for (int i = 1; i < functions.length; i++) { |
var stub = functions[i]; |
var stubCallName = JS('String|Null', '#.\$callName', stub); |
- // TODO(ahe): Support interceptors here. |
- JS('', '#[#] = #', closure, stubCallName, |
- isStatic ? stub : forwardTo(receiver, stub)); |
+ if (stubCallName != null) { |
+ JS('', '#[#] = #', prototype, stubCallName, |
+ isStatic ? stub : forwardCallTo(stub, isIntercepted)); |
+ } |
} |
- JS('', '#["call*"] = #', closure, function); |
+ JS('', '#["call*"] = #', prototype, function); |
- return closure; |
+ return constructor; |
} |
- static forwardTo(receiver, function) { |
- return JS( |
- '', |
- 'function(r,f){return function(){return f.apply(r,arguments)}}(#,#)', |
- receiver, function); |
+ static cspForwardCall(int arity, function) { |
+ var getSelf = RAW_DART_FUNCTION_REF(BoundClosure.selfOf); |
+ switch (arity) { |
+ case 0: |
+ return JS( |
+ '', |
+ 'function(F,S){' |
+ 'return function(){' |
+ 'return F.call(S(this))' |
+ '}' |
+ '}(#,#)', function, getSelf); |
+ case 1: |
+ return JS( |
+ '', |
+ 'function(F,S){' |
+ 'return function(a){' |
+ 'return F.call(S(this),a)' |
+ '}' |
+ '}(#,#)', function, getSelf); |
+ case 2: |
+ return JS( |
+ '', |
+ 'function(F,S){' |
+ 'return function(a,b){' |
+ 'return F.call(S(this),a,b)' |
+ '}' |
+ '}(#,#)', function, getSelf); |
+ case 3: |
+ return JS( |
+ '', |
+ 'function(F,S){' |
+ 'return function(a,b,c){' |
+ 'return F.call(S(this),a,b,c)' |
+ '}' |
+ '}(#,#)', function, getSelf); |
+ case 4: |
+ return JS( |
+ '', |
+ 'function(F,S){' |
+ 'return function(a,b,c,d){' |
+ 'return F.call(S(this),a,b,c,d)' |
+ '}' |
+ '}(#,#)', function, getSelf); |
+ case 5: |
+ return JS( |
+ '', |
+ 'function(F,S){' |
+ 'return function(a,b,c,d,e){' |
+ 'return F.call(S(this),a,b,c,d,e)' |
+ '}' |
+ '}(#,#)', function, getSelf); |
+ default: |
+ return JS( |
+ '', |
+ 'function(f,s){' |
+ 'return function(){' |
+ 'return f.apply(s(this),arguments)' |
+ '}' |
+ '}(#,#)', function, getSelf); |
+ } |
} |
- static forwardInterceptedCallTo(self, interceptor, function) { |
- return JS( |
- '', |
- 'function(i,s,f){return function(){' |
- 'return f.call.bind(f,i,s).apply(i,arguments)}}(#,#,#)', |
- interceptor, self, function); |
+ static bool get isCsp => JS('bool', 'typeof dart_precompiled == "function"'); |
+ |
+ static forwardCallTo(function, bool isIntercepted) { |
+ if (isIntercepted) return forwardInterceptedCallTo(function); |
+ int arity = JS('int', '#.length', function); |
+ if (isCsp) { |
+ return cspForwardCall(arity, function); |
+ } else if (arity == 0) { |
+ return JS( |
+ '', |
+ '(new Function("F",#))(#)', |
+ 'return function(){' |
+ 'return F.call(this.${BoundClosure.selfFieldName()});${functionCounter++}' |
+ '}', |
+ function); |
+ } else if (1 <= arity && arity < 27) { |
+ String arguments = JS( |
+ 'String', |
+ '"abcdefghijklmnopqrstuvwxyz".split("").splice(0,#).join(",")', |
+ arity); |
+ return JS( |
+ '', |
+ '(new Function("F",#))(#)', |
+ 'return function($arguments){' |
+ 'return F.call(this.${BoundClosure.selfFieldName()},$arguments);' |
+ '${functionCounter++}' |
+ '}', |
+ function); |
+ } else { |
+ return cspForwardCall(arity, function); |
+ } |
} |
+ static cspForwardInterceptedCall(int arity, String name, function) { |
+ var getSelf = RAW_DART_FUNCTION_REF(BoundClosure.selfOf); |
+ var getReceiver = RAW_DART_FUNCTION_REF(BoundClosure.receiverOf); |
+ switch (arity) { |
+ case 0: |
+ // Intercepted functions always takes at least one argument (the |
+ // receiver). |
+ throw new RuntimeError('Intercepted function with no arguments.'); |
+ case 1: |
+ return JS( |
+ '', |
+ 'function(n,s,r){' |
+ 'return function(){' |
+ 'return s(this)[n](r(this))' |
+ '}' |
+ '}(#,#,#)', name, getSelf, getReceiver); |
+ case 2: |
+ return JS( |
+ '', |
+ 'function(n,s,r){' |
+ 'return function(a){' |
+ 'return s(this)[n](r(this),a)' |
+ '}' |
+ '}(#,#,#)', name, getSelf, getReceiver); |
+ case 3: |
+ return JS( |
+ '', |
+ 'function(n,s,r){' |
+ 'return function(a,b){' |
+ 'return s(this)[n](r(this),a,b)' |
+ '}' |
+ '}(#,#,#)', name, getSelf, getReceiver); |
+ case 4: |
+ return JS( |
+ '', |
+ 'function(n,s,r){' |
+ 'return function(a,b,c){' |
+ 'return s(this)[n](r(this),a,b,c)' |
+ '}' |
+ '}(#,#,#)', name, getSelf, getReceiver); |
+ case 5: |
+ return JS( |
+ '', |
+ 'function(n,s,r){' |
+ 'return function(a,b,c,d){' |
+ 'return s(this)[n](r(this),a,b,c,d)' |
+ '}' |
+ '}(#,#,#)', name, getSelf, getReceiver); |
+ case 6: |
+ return JS( |
+ '', |
+ 'function(n,s,r){' |
+ 'return function(a,b,c,d,e){' |
+ 'return s(this)[n](r(this),a,b,c,d,e)' |
+ '}' |
+ '}(#,#,#)', name, getSelf, getReceiver); |
+ default: |
+ return JS( |
+ '', |
+ 'function(f,s,r,a){' |
+ 'return function(){' |
+ 'a=[r(this)];' |
+ 'Array.prototype.push.apply(a,arguments);' |
+ 'return f.apply(s(this),a)' |
+ '}' |
+ '}(#,#,#)', function, getSelf, getReceiver); |
+ } |
+ } |
+ |
+ static forwardInterceptedCallTo(function) { |
+ String stubName = JS('String|Null', '#.\$stubName', function); |
+ int arity = JS('int', '#.length', function); |
+ bool isCsp = JS('bool', 'typeof dart_precompiled == "function"'); |
+ if (isCsp) { |
+ return cspForwardInterceptedCall(arity, stubName, function); |
+ } else if (arity == 1) { |
+ return JS('', 'new Function(#)', |
+ 'return this.${BoundClosure.selfFieldName()}.$stubName(' |
+ 'this.${BoundClosure.receiverFieldName()});' |
+ '${functionCounter++}'); |
+ } else if (1 < arity && arity < 28) { |
+ String arguments = JS( |
+ 'String', |
+ '"abcdefghijklmnopqrstuvwxyz".split("").splice(0,#).join(",")', |
+ arity - 1); |
+ return JS( |
+ '', |
+ '(new Function(#))()', |
+ 'return function($arguments){' |
+ 'return this.${BoundClosure.selfFieldName()}.$stubName(' |
+ 'this.${BoundClosure.receiverFieldName()},$arguments);' |
+ '${functionCounter++}' |
+ '}'); |
+ } else { |
+ return cspForwardInterceptedCall(arity, stubName, function); |
+ } |
+ } |
+ |
String toString() => "Closure"; |
} |
/// Called from implicit method getter (aka tear-off). |
-Closure closureFromTearOff(receiver, |
- functions, |
- reflectionInfo, |
- isStatic, |
- jsArguments, |
- name) { |
- return new Closure.fromTearOff( |
+closureFromTearOff(receiver, |
+ functions, |
+ reflectionInfo, |
+ isStatic, |
+ jsArguments, |
+ name) { |
+ return Closure.fromTearOff( |
receiver, |
JSArray.markFixedList(functions), |
JSArray.markFixedList(reflectionInfo), |
@@ -1917,13 +2156,46 @@ |
return receiverHashCode ^ Primitives.objectHashCode(_target); |
} |
+ @NoInline |
static selfOf(BoundClosure closure) => closure._self; |
static targetOf(BoundClosure closure) => closure._target; |
+ @NoInline |
static receiverOf(BoundClosure closure) => closure._receiver; |
static nameOf(BoundClosure closure) => closure._name; |
+ |
+ static String selfFieldNameCache; |
+ |
+ static String selfFieldName() { |
+ if (selfFieldNameCache == null) { |
+ selfFieldNameCache = computeFieldNamed('self'); |
+ } |
+ return selfFieldNameCache; |
+ } |
+ |
+ static String receiverFieldNameCache; |
+ |
+ static String receiverFieldName() { |
+ if (receiverFieldNameCache == null) { |
+ receiverFieldNameCache = computeFieldNamed('receiver'); |
+ } |
+ return receiverFieldNameCache; |
+ } |
+ |
+ @NoInline() @NoSideEffects() |
+ static String computeFieldNamed(String fieldName) { |
+ var template = new BoundClosure('self', 'target', 'receiver', 'name'); |
+ var names = JSArray.markFixedList( |
+ JS('', 'Object.getOwnPropertyNames(#)', template)); |
+ for (int i = 0; i < names.length; i++) { |
+ var name = names[i]; |
+ if (JS('bool', '#[#] === #', template, name, fieldName)) { |
+ return JS('String', '#', name); |
+ } |
+ } |
+ } |
} |
bool jsHasOwnProperty(var jsObject, String property) { |