-
Notifications
You must be signed in to change notification settings - Fork 57
How to handle JS methods/constructors? #87
Comments
Here are the two "primitive" procedures you'd have to support:
Even if you combine them somehow into a "JS calling convention": |
This is not forgotten. We currently have instructions for calling a
function and for invoking a method.
On Fri, Nov 22, 2019 at 2:49 PM Gus Caplan ***@***.***> wrote:
Here are the two "canonical" procedures you'd have to support:
- Call(F, thisValue, args)
- Construct(F, newTarget, args) (newTarget is a nullable constructor)
Even if you combine them somehow into a "JS calling convention": F(thisValue,
newTarget, ..args), and make sure you are only passing thisValue or
newTarget, you still have to decide somewhere to either perform a Call or a
Construct, you just move it up the chain.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#87?email_source=notifications&email_token=AAQAXUHYBLBRSGWDAE3MVV3QVBOYDA5CNFSM4JQWWYG2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEE7C3WI#issuecomment-557723097>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAQAXUCIEH7M5WFI6WQETG3QVBOYDANCNFSM4JQWWYGQ>
.
--
Francis McCabe
SWE
|
Yeah, the problem isn't methods, it's construct. A construct is not a call, it just sometimes looks like a call (in fact you can do |
It is not really within the remit to support all JS features. As far as
wasm is concerned; calling a constructor is indistinguishable from calling
a function; (think calling a factory method in Java).
There are two halves to each adapted function: an internal import adapter
and an external export adapter.
If the external adapter chooses to invoke a constructor that seems a
reasonable strategy to me.
On Sun, Nov 24, 2019 at 12:54 PM Gus Caplan ***@***.***> wrote:
Yeah, the problem isn't methods, it's construct. A construct is not a
call, it just sometimes looks like a call (in fact you can do new Bar,
without parentheses or arguments)
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#87?email_source=notifications&email_token=AAQAXUCRXCAWX6KBIHTUFTDQVLSXJA5CNFSM4JQWWYG2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEFAUTZQ#issuecomment-557926886>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAQAXUDKU5LLVAP3B6MOBADQVLSXJANCNFSM4JQWWYGQ>
.
--
Francis McCabe
SWE
|
I think the distinction between (static) functions and methods belongs on the interface-typed function signature. This distinction wouldn't have any meaning when both sides were wasm: the arguments simply get passed as normal. It's only when one side is non-wasm that the distinction might become meaningful for determining how to interpret the call for that other language. In particular, when a function's type was "method", a JS caller would know to pass the receiver as the first argument (shifting the actual args right by one), instead of ignoring it, and a JS callee would know to pass the first argument as the receiver (shifting the other actual args left by one), instead of passing There's also the question of whether we really need to have a "constructor" distinction. If we wanted total JS co-expressivity, we would (and we'd need an explicit |
I think I may have missed the method-related instructions perhaps? I don't think they're currently in the repository, so to confirm are they either in PRs or in people's heads so far? Or is there documentation I'm missing? I would also tend to agree that we can probably skip JS constructors until we hear otherwise. They're not necessarily the hot path we need to optimize for WebIDL integration. |
I don't currently see a need for a method-related instruction; just a "method" flag on interface function signatures. |
I believe that we only need two elements: an ‘interface type’ similar to
that in webidl and an ‘invoke method’ adapter instruction.
On Tue, Nov 26, 2019 at 10:53 AM Luke Wagner ***@***.***> wrote:
I don't currently see a need for a method-related instruction; just a
"method" flag on interface function signatures.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#87?email_source=notifications&email_token=AAQAXUEZE4YMBNMQVSRVISTQVVA6BA5CNFSM4JQWWYG2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEFGPWIA#issuecomment-558693152>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAQAXUHRY7E5K5MDNUJOPRTQVVA6BANCNFSM4JQWWYGQ>
.
--
Francis McCabe
SWE
|
Oh right, now I remember this idea. So what would a value of an |
Actually I think not.
Type imports as far as I understand them are used to introduce opaque
types. This is kind of the opposite: the interface says what you can do
with the type.
Some languages (such as java) have a concept of method which is effectively
an inseparable set of functions and instance variables.
JS does not have this really but it is implied by the way webidl is written.
One can rationalize this in terms of the related functionality; JS does not
honor the inseparability aspect of it but it is ‘a thing’ when you want to
model APIs
On Tue, Nov 26, 2019 at 12:56 PM Luke Wagner ***@***.***> wrote:
Oh right, now I remember this idea. So what would a value of an interface
interface type be lifted from and lowered into? Is the abstract interface
value semantically a record containing an abstract type and a bunch of
closures? Does this introduce a dependency on the type imports proposal
<https://github.com/WebAssembly/proposal-type-imports/blob/master/proposals/type-imports/Overview.md>
?
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#87?email_source=notifications&email_token=AAQAXUEKHUVT4YJ6JHDIXG3QVVPL5A5CNFSM4JQWWYG2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEFG44SI#issuecomment-558747209>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAQAXUARGAUOBWS3SRJVZ4LQVVPL5ANCNFSM4JQWWYGQ>
.
--
Francis McCabe
SWE
|
Ok, but what core values do you lift/lower an |
Actually, let me rewind and revise my earlier position; I actually think we don't need any new interface types or adapter instructions to be able to effectively and efficiently call Web API methods. The reasoning is basically the same as I gave above for why we don't need a "constructor" flag. In particular, since, as part of this overall proposal, we're defining a new "WebAssembly binding" in the Web IDL spec, we can simply say that, when the callee is a Web IDL method (as indicated by it being inside a Web IDL This is backwards compatible because all existing calls today necessarily go through the Web IDL ECMAScript binding (and must continue to do so for backcompat reasons); only (new) adapted import calls can go through the new Web IDL WebAssembly binding. I also think this is still polyfillable b/c if I want to polyfill Web API function The only use cases we lose are allowing wasm to call or be called by JS while directly passing a receiver. But, as already reasoned above, if we're calling to/from JS anyway, it's no big perf deal to use a little JS glue (which we are often using anyways, for other reasons). FWIW, if we do want to pursue an |
This is interesting. I am ok with leaving it to be post ‘minimum awesome product’ . |
I'm personally a bit lost in how this is expected to work, but it sounds like y'all have this in hand. I suspect this would also be clarified pretty quickly once we get closer to having spec text being written. If y'all are ok seems fine to close this since it sounds like it's expected to all still be handled reasonably enough! |
Actually, @ajklein pointed out a faulty assumption in my logic above (perhaps the same thing tripping up @alexcrichton): I was assuming that, when calling JS, the JS is catered to the wasm caller. But in the "JS polyfill" case (in which someone has monkeypatched the global prototype chain, or something analogous in import-maps world), the JS polyfill is being used for both JS and wasm callers, so it's actually expecting the receiver as the receiver, not the first argument. |
@jgravelle-google has also pointed out that the use-case works the other way, too: being able to be the target of a call-with-receiver is important if we want Wasm to be able to seamlessly be the polyfilling function for a JS API. |
Ooh, unless I've missed a constraint, I think there's another option that supports polyfilling while avoiding adding the concept of "method" to interface types: We can specify that, when an interface adapter is present and the caller/callee is JS or Web IDL, the first argument is always interpreted as the receiver. Thus, when wasm wants to call a Web IDL function, the wasm caller will take this fact into account and always pass the intended receiver as the first argument (passing WDYT? (That of course doesn't help with constructors, but perhaps they aren't as hot and therefore |
I see that that approach might work, but the general direction here still doesn't seem right to me. What will we do once we end up trying to interop Wasm with some other language with its own "quirks"? Adding hard-coded special cases for JS seems strictly worse to me than including things in interface types to allow configurable JS interop. |
One gotcha with an approach like that @lukewagner as well may be when you start using non-web-focused interface types modules. For example if WASI is defined with interface types we'd have to have dummy first arguments for all APIs in order to have a JS polyfill. Similarly if someone wrote a module not primarily for the web (but compatible with it) using interface types it may not work well with a JS polyfill of what needs to be imported. |
@ajklein The alternative seems to be for interface types to collect the union of quirks. But maybe that's fine, since a particular language binding can always ignore the quirk. (E.g., a functional language without a concept of "receiver" could simply ignore a "method" annotation, passing the receiver as the first arg.) @alexcrichton Ah, good point. Technically, the JS polyfill could know that Ok, mostly I just wanted to explore the space of options before defaulting to either adding quirks or something fancier, but between the "polyfilling a Web API in JS" and "polyfilling a non-Web-API in JS" use cases, we might not have a simpler option. |
Not sure I like either the automatic approach nor the special annotations. |
After some offline discussion, I think I can see the opportunity for a new interface type that has the same runtime behavior/performance as what I was imagining above, but lets us improve how wasm interfaces with non-wasm. Not to bikeshed, but "service" has connotations of a distributed system (with partial failure, concurrency, persistence, ...), which feels too "big" to me, so perhaps we could call this a "protocol", like Fuchsia does? So I think what we ultimately need in the core module is a type import and one function import for each Web IDL method the module calls. So, for firstChild, for example: (module
(type $Node (import "Node" "Type"))
(func $firstChild (import "Node" "firstChild") (result (ref $Node)))
) But using the new (module
... same two core imports as above
(@interface protocol $Node (import "Node")
(func $firstChild (export "firstChild") (result (ref $Node)))
)
(@interface type implement (import "Node" "Type") $Node)
(@interface func implement (import "Node" "firstChild")
(param $arg (ref $Node)) (result (ref $Node))
(call-method $firstChild (local.get $arg))
)
) From a performance/impl perspective, this is the same as not using a protocol, but just importing the type and function separately. So what does this buy us? Of course we don't need an ad hoc "method" attribute on functions as discussed above, but if that was the only benefit, then I'd question the value of having a whole separate interface type. But I think this new protocol type also allows better binding to non-wasm. I'll describe what we could do in the JS API, but I think other languages could do likewise.
WDYT? Is this kindof what you were thinking @fgmccabe ? |
approximately, yes. I believe that it is quite important that each method in a service/protocol/affordance has its own adapters. That gives us the necessary leverage to implement a given protocol the way that seems most pertinent. Aside: it may be useful (I am currently exploring) to 'do to JS what we have done to wasm': to have a DSL for JS that allows us to express import and export adapters in 'almost Javascript'. Simple example export adapter: export getEnv(key:string):string{ This would allow us to be precise about how JS can interoperate with WASM for particular APIs. |
Agreed that each method of a protocol should have its own adapter. E.g., although the E.g., to implement and export the above (module
(type $Node ...something using GC or an `i31`...)
(@interface func $firstChild (param (ref $Node)) (result (ref $Node))
... impl
)
(@interface protocol (export "Node") (type $Node)
(export "firstChild" (func $firstChild))
)
) and thus there would be an adapter function on each side of a I'm a little more skeptical of the need to put explicit adapter functions into high-level scripting languages; I feel like the ideal here is that the interface types are high-level enough already that each scripting language can define an automatic mapping between interface values and the languages' values. |
One feature of this proposal that may have gotten lost in the shuffle from the previous "WebIDL Bindings" moniker to interface types is the ability to configure how JS functions are invoked. Some imported functions want to be invoked as a
new
function (e.g.new Function(...)
) and others want to be invoked as a method where the first argument is thethis
of the call (e.g.foo.bar(other, arguments)
).In reviewing this again, I'm not sure if we have an avenue of introducing this with adapter functions? You sort of want to annotate that an adapter import is calling the imported function in a particular way, but this is very much a JS-ism that isn't really present in most other languages (methods, maybe, constructors, less so).
Do others have ideas of how we might reincorporate this JS feature back into the proposal? The only goal here is to hook up wasm/C++ engines directly without JS glue in the middle, so I don't think it really matters how we do it so long as the end goal is met for methods/constructors/etc.
The text was updated successfully, but these errors were encountered: