WebAssembly / interface-types

Other
641 stars 57 forks source link

function reference lifting/lowering instructions #106

Closed lukewagner closed 4 years ago

lukewagner commented 4 years ago

In the same way that array.lift and own use nested blocks instead of lambdas, I think we should frame the lifting/lowering of Interface Function values using nested expressions, for symmetry.

The lifting instruction would have the form:

function.lift #coretypeidx #ittypeidx
  lower-params: interface-instr*
then
  lift-results: interface-instr*
end

and have type:

[ $F ] -> [ $F' ]

where

Here the token then is a syntactic separator between the lower-params and lift-results child blocks, similar to else in core wasm. Intuitively speaking, the lifted core wasm function is call_ref'd at the point of the then (after parameters have been lowered and before results have been lifted).

The lowering instruction would have the symmetric form:

function.lower #ittypeidx #coretypeidx
  lift-params: interface-instr*
then
  lower-results: interface-instr*
end

and have type:

[ $F ] -> [ $F' ]

where

When a function.lift and function.lower are fused, their corresponding lift/lower-params and lift/lower-results blocks get fused, symmetric to what we've discussed with arrays and variants; the difference is that the resulting fused code goes into a new core function that gets logically func.binded to each new function reference that flows through.


Note, here I'm using typed function references, but we don't strictly block on that proposal: we could just as well depend only on reference-types changing the operand of function.lift and result of function.lower to funcref and having the implicitly call to the lifted funcref execute as-if by (call_indirect #coretypeidx). It'd be nicer to have the typed function references, though.

jgravelle-google commented 4 years ago

Don't really have much to add here, sgtm. Agree that we should model things in one consistent fashion, so inline-blocks it is (if we ever want to change that we should do so globally).

and having the implicitly call to the lifted funcref execute as-if by (call_indirect #coretypeidx)

I'd be curious to see the "implicit call to the lifted funcref" spelled out in more detail, I'm not sure where the call_indirect is specified. In particular I'm in favor of cutting out as many dependencies as possible, so I'd think we could start from there and expand into the typed refs in the future, rather than the other way round.

fgmccabe commented 4 years ago

This is not consistent with how we have been writing adapters to date.

  1. We do not separate 'before the call' from 'after the call'; primarily because this is not always that obvious.

  2. You need to mirror what happens with export & import: there are two adapters for any given import/export pair. The same will be true for callback functions; because they are fundamentally symmetric with regular API calls.

  3. You do still need the equivalent of func.bind. In part because the actual call to the callback will be at a later time and from 'deep in core wasm land'. In addition, the actual function that will be passed is inherently dynamic (in the sense that every call to an API that mentions a callback will potentially have a different callback function value).

lukewagner commented 4 years ago

Ah hah, I can see your point now! Yes, I suppose func.bind is more general in that it allows you to capture an arbitrary set of state into the bound closure [edit: and also share values between the pre-call adapter instructions and post-call adapter instructions]. Can you give me a link to a PR or branch that contains a func.bind example as you're imagining it; I can't find anything atm.

fgmccabe commented 4 years ago

This has a use of func.bind  https://github.com/WebAssembly/interface-types/blob/others/proposals/interface-types/working-notes/scenarios/fetch.md#export

I am working on another example, and it is a bit out of date, but should carry the idea.

lukewagner commented 4 years ago

Ok, thinking about it a bit more, yes, this makes sense. I'll close this issue then, and we can add closures in a separate PR/issue.

One bikeshedding thing: when we update the instruction, it'd be useful to rename it to match the lift/lower naming scheme, and avoid reusing func.bind from function-references.