Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove native keyword, replace with @native annotation #5979

Closed
kasperl opened this issue Oct 17, 2012 · 12 comments
Closed

Remove native keyword, replace with @native annotation #5979

kasperl opened this issue Oct 17, 2012 · 12 comments
Assignees
Labels
area-web Use area-web for Dart web related issues, including the DDC and dart2js compilers and JS interop. P3 A lower priority bug or feature request type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) web-dart2js

Comments

@kasperl
Copy link

kasperl commented Oct 17, 2012

We should try to get rid of our use of the native keyword and instead use @­native annotations (perhaps of various kinds).

@peter-ahe-google
Copy link
Contributor

Set owner to @rakudrama.
Removed this from the M2 milestone.
Added this to the Later milestone.

@rakudrama
Copy link
Member

Marked this as being blocked by #9850.

@kasperl
Copy link
Author

kasperl commented May 23, 2013

Added TriageForM5 label.

@kasperl
Copy link
Author

kasperl commented May 28, 2013

Removed TriageForM5 label.

@kasperl
Copy link
Author

kasperl commented Jul 10, 2014

Removed this from the Later milestone.
Added Oldschool-Milestone-Later label.

@rakudrama
Copy link
Member

We have removed 'native' from class declarations.
There are three remaining uses of native used to mark various kinds of methods:

  1. On methods of a native class

    T foo() native;

can be replaced by

    external T foo();

The compiler needs to be fixed to recognize an 'external instance method of a native class' as a native instance method. Important details that need to be retained:

(a) Types in the declaration (or annotations) drive compiler knowledge of instantiated types and the insertion of conversions.
(b) Missing optional arguments are treated differently to standard Dart methods.

The other cases are used only in tests:

  1. Defining a top level Dart function with a given JavaScript body. This form is restricted to zero arguments and is used exclusively to set up a JavaScript environment.

    void setup() native '''js-code''';

We can't quite replace this with:

    void setup() {
      JS('', r'''js-code''');
    }

The problem is that the code from JS-forms is included in minification, but test cases rely on the original naming of internal functions in 'js-code'. This is probably best handled by a new annotation:

    @­JSBody(r'''js-code''')
    external void setup();

  1. Defining external top-level method.

   T foo() native;
   bar() native;

This is used in tests to access globals assigned in the js-code of the examples above.
It can be replaced with:

Can be replaced with:

   import 'dart:_foreign_helper' show JS;
   T foo() => JS('T|Null', 'foo()');
   bar() => JS('Object', 'bar()');

Note that in some cases the JS-form will require returns: and creates: in the first argument string.

There are some other cases with fake Dart bodies. These tests should be upgraded to avoid the old frog syntax.


Set owner to @johnniwinther.

@johnniwinther
Copy link
Member

I would prefer to keep the semantics of 'external' to be that the implementation is provided in terms of patching. Therefore I propose the following encodings:

  1. Before patching is used:

   @­Returns('T')
   @­nativeCall
   T foo() => null;

When patching is used: In the origin file:

   external T foo();

In the patch file:

   @­patch
   @­Returns('T')
   @­nativeCall
   T foo() => null;
   
2) Use @­JSBody to declare 'setup' to be a foreign function:

   @­JSBody(r'''js-code''')
   void setup() {}

@rakudrama
Copy link
Member

I disagree with #­7.

We should avoid providing fake and incorrect bodies. It make the code harder to process by tools (for example, some inference by the tool may conclude it has no effects or always returns null).

'external' is the correct language marker to use for methods that have implementations not written in Dart.
We can make an external method in the origin file be implemented by an external patch method in the patch file that is implemented by a dart2js-specific annotation driven recipe.

@peter-ahe-google
Copy link
Contributor

The patch mechanism isn't part of the Dart language. As far as I know, the external keyword was added to also cover the suggestion in #­6.

However, we want both dart2js and the VM to read the same library files (origin file), but have different patch files. For this reason, it seems suboptimal to put the @­native annotation in the shared library definition (origin file).

Another reason for strictly separating dart2js implementation details from the origin files is reflection. Arguably, all annotations in patch files can be omitted from reflection, whereas annotations in origin files must be visible to reflection.

I don't understand why all instance methods of native classes of this form:

 T foo() native;

Can't be replaced by:

  foo() => JS('T_impl', '#.foo()', this);

This leads me to the following definition of a native class in the origin file:

class NativeClass {
  external T foo();
}

No special annotations for dart2js, they are all in the patch file:

@patch @­native
class NativeClass {
  @­patch
  T foo() => JS('T_impl', '#.foo()', this);
}

@rakudrama
Copy link
Member

Re: #­9: Nobody is suggesting putting annotations in the origin file.

Yes, you can can replace the specific example:

   T foo() native;
-->
   T foo() => JS('T|Null', '#.foo()', this);

In the general case there are problems that are difficult or error-prone to express or cannot be expressed like this.

  1. The compiler injects special conversion code for some types. Functions are converted to a JavaScript function that captures isolate affinity. This could probably be lifted into Dart code, but only if the generator has access to the function signature.

  T1 foo(CALLBACKTYPE callback) => ?

In the IDL we sometimes don't actually know the signature of CALLBACKTYPE. We could list the signature in a table in the generator, but it is safer for dart2js to generate code dependent on the Dart function signature, where we can also enforce other constraints. This enforcement is important so we can detect when the generated libraries require features that we have not yet implemented.

  1. It is awkward to expression the tree-shaking behaviour of escaping callbacks with the pseudo-types in the JS call first argument. It is not possible to check the awkward expression is correct, whereas the default behaviour of escape analysis based on the function signature is correct for all but a few weird cases.
  2. Optional arguments have different semantics in JavaScript and there is no efficient way in Dart to fake it. The generated JavaScript call must have the correct number of arguments, since some methods dispatch on the number of arguments.

For
   T1 foo([T2 a]) native;

we generate these stubs on the interceptor, which are different to the usual stubs:

   foo$0: function (receiver) { return receiver.foo(); }
   foo$1: function (receiver, arg) { return receiver.foo(arg); }

  1. DOM benchmark performance relies on being able to inline DOM calls during SSA optimization. The compiler currently can only inline Dart method during SSA construction. We would have to see if the inlining of the Dart methods using JS-forms is competitive with the SSA optimization.

This is what I am proposing for instance methods:

-------- origin file:
class AnElement {
  external T foo([CALLBACK cb]);
}

-------- dart2js patch file:
@patch
@Native('HTMLAnElement')
class AnElement {
  // An external method on a native class is understood to be implemented by the native class.
  // The JavaScript property name can be adjusted with the @­JSName annotation.
  external T foo([CALLBACK cb]);
}


The interceptor for AnElement might contains the following depending on selectors used in the program:

   foo$0: function (receiver) { return receiver.foo(); }
   foo$1: function (receiver, arg) { return receiver.foo(convertDartClosureToJS(arg,1)); }

If dart2js can infer or type propagate that x is AnElement,

   x.foo() is compiled to x.foo()
   x.foo(fn) is compiled to $.AnElement_methods.foo$1(x, fn)

The effort required to implement the above is approximately "Replace the isNative method on FunctionElement with an additional clause 'or is an external method defined on a native class'".
Changes to the generator are simple too.

@kasperl
Copy link
Author

kasperl commented Aug 4, 2014

Removed Oldschool-Milestone-Later label.

@sigmundch sigmundch added P3 A lower priority bug or feature request and removed Priority-Medium labels Dec 10, 2015
@kevmoo kevmoo added type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) and removed triaged labels Feb 29, 2016
@vsmenon vsmenon added area-web Use area-web for Dart web related issues, including the DDC and dart2js compilers and JS interop. and removed area-web Use area-web for Dart web related issues, including the DDC and dart2js compilers and JS interop. labels Jul 20, 2019
@mraleph
Copy link
Member

mraleph commented Sep 3, 2021

Duplicate of #28791

@mraleph mraleph marked this as a duplicate of #28791 Sep 3, 2021
@mraleph mraleph closed this as completed Sep 3, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-web Use area-web for Dart web related issues, including the DDC and dart2js compilers and JS interop. P3 A lower priority bug or feature request type-bug Incorrect behavior (everything from a crash to more subtle misbehavior) web-dart2js
Projects
None yet
Development

No branches or pull requests

8 participants