Skip to content

dart2js: revisit interceptor calling convention #35970

Open
@rakudrama

Description

@rakudrama

The interceptor calling convention, where the receiver is an explicit parameter, is based on name, which captures methods and selectors that don't really need the convention. Consider add. JSArray.get$add and JSArray.add$1 need the convention, but there are user defined classes that define an add method with two arguments. add$2 does not need interceptor calling convention, but its tear-off getter might, if called from a dynamic call site.

There are currently a few optimizations around the intercepter calling convention

  • methods that are not on, or mixed into, a native class ignore the explicit receiver parameter
  • when it is determined that the target of an instance method call is one of these methods, a dummy value is passed.

We could make the calling convention decision on a per-callsite basis by putting the 'isIntercepted' bit in the selector. There would be two selectors for add methods with one argument - add$1U(x) using the unintercepted convention and add$1I(receiver,x) using the interceptor convention.
On the classes that currently have an add$1 method that ignores the receiver, the add$1I method would be a stub that drops the receiver and forwards to add$1U.
The hope is that most of the these stubs can be removed (i.e. tree-shaken).

We can estimate the size benefit by dropping these ignored receiver parameters and dummy arguments. The results vary widely, the best example being 0.67%, the worst ~0.3%

The cost is harder to estimate. It is difficult to estimate the number of methods that would have both the intercepted and plain calling convention. Worst case is that every method needs a forwarding method to drop the unused receiver. In the 0.67% case above, the size of these stubs wold grow the size by 2.52%, giving net 1.85% larger.

There might be a middle ground that is to implement the per-callsite decision mechanism but only apply it selectively. The current implementation would be equivalent to 'selectively never'.
The program could be analysed on the basis of the static types at call sites to anticipate the number of calls that would benefit from this scheme vs the number of forwarding methods. I think the Map indexer is used often enough to pay for the forwarding stubs on Map implementations (half of the 0.67% is map indexer calls), but I think very few selectors are that obvious a win.


It might be best to do an ad-hoc optimization for indexers. Indexers avoid the complications of tear-offs.
There would be the $index intercepted indexer and the $get unintercepted indexer.
In the 0.67% case above, there are 135 indexers that ignore the receiver, i.e. 135 forwarding stubs might be required.

There could be a universal stub on Object that is shadowed by a NoSuchMethod stub on Interceptor. I would not want to do this for List, since I it would penalize all the ListMixin code:

Object.$index: function(receiver, index) { return this.$get(index); }
Interceptor.$index: function(receiver, index) { throw no-such-method }
ListImpl.$index: function(receiver, index) { ... }
MapImpl.$get(index) { ... }

The specializer for indexing would lower the selector to the $get variant, remove the interceptor argument, and replace itself with another specializer that can still constant-fold but avoids repeated checks.
We would also have to ensure that no native class implements the Map interface.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-web-jsIssues related to JavaScript support for Dart Web, including DDC, dart2js, and JS interop.dart2js-interceptorsweb-dart2js

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions