On Feb 17, 2010, at 9:29 PM, Brendan Eich wrote:

Bound methods need not be reified unless extracted as funargs (even if only as operands of ===, e.g.), or frozen unless tampered with. If they're (typically, or only ever) called on the right receiver, then the cost of creating a bind result can be avoided.

Optimizing to defer layered freeze/bind combinations until method value extraction or mutation attempt requires more than fancy pattern-matching on the part of an implementation. It requires a read barrier.

In case it wasn't clear, a read barrier is enough, even to cope with mutation attempts (deferred freeze), since method extraction is a read, and to mutate a method value (function object) you have to read (not invoke) the method value:

o.m();       // ok, invoking with bound receiver, not reading o.m
foo(o.m);    // not ok, must bind and freeze o.m
o.m.bar = 1; // mutation on o.m reference base, first bind and freeze o.m

The barrier can thus be compiled into forms that read o.m without immediately invoking it. Of course the overhead is still too high if we only compile it, so there has to be a runtime fast path (a polymorphic inline cache), but that is already the case due to getters, as noted.

If the method body uses arguments and is not strict-mode code, then it could somehow use arguments.callee and leak an unbound, unfrozen (and "joined", see ES3 -- the first optimization to do here, prior to traits and anything in ES5, is to defer evaluating a function expression into a fresh object per evaluation -- SpiderMonkey does this optimization under some conditions) method reference.

So arguments is a hazard (it's hard to analyze precisely for arguments.callee, if you see arguments[i] or baz(arguments) you have to assume the worst).

Same goes for named function expressions as methods, where the method body or a function nested in it uses the name of the function expression.

These are more places that need the barrier, or (to simplify compilation) defeat the optimization of deferring bind and freeze (and disjoining).

But let's back up again, to traits.js:

var Trait = (function(){
 . . .
  var SUPPORTS_DEFINEPROP = !!Object.defineProperty;

  var call = Function.prototype.call;
 . . .

Static analysis of just this file cannot be sure that Object and Function have their original meanings -- these are writable properties of the global object. So some speculation, at the least, is required to get off the ground at all, in trying to pattern-match freeze(bind(...)) etc.

Recognizing when Object and Function still mean what they originally meant is something optimizing VMs may do, but it's a high bar to set for traits performance to scale well.

The method read barrier complexity on top is also on the boards or already partly implemented in at least one VM, but again why should this be required of implementors, on top of the syn-tax on users?

/be
_______________________________________________
es-discuss mailing list
es-discuss@mozilla.org
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to