WebAssembly / interface-types

Other
641 stars 57 forks source link

Resolve the "TODO" in Explainer in "Export returning string (dynamically allocated)" #60

Closed lukewagner closed 4 years ago

lukewagner commented 4 years ago

There is a TODO at the end of the "Export returning string (dynamically allocated)" section which describes two important problems that we need to solve (not just for strings, but for any type when returned as dynamically-allocated linear memory from a wasm export).

The two issues that need to be solved are:

Since this is not an esoteric problem, but one everyone will hit early and often, we'd ideally offer a simple, compact, and "canonical" solution to both of these problems.


One possible solution is to have a defer instruction that says "call the given export with copies of the top-of-stack values (the count and types determined by the export's signature) at the end of the adapted call". Here, "adapted call" means treating the adapter function of the caller and the adapter function of the callee as a single call, as if the callee was inline into the caller (which is the whole point, from an optimization POV).

So, the current example:

  (@interface func (export "greeting") (result string)
    call-export "greeting_"
    memory-to-string "mem" "free"
  )

could be replaced with

  (@interface func (export "greeting") (result string)
    call-export "greeting_"
    defer "free"
    memory-to-string "mem"
  )

Noting that:

Spec-wise, I imagine that the configuration (the input and output of every instruction) would contain a vector of (export,arguments) pairs that is appened to by defer and executed (LIFO, presumably) at the end of the adapted call.

This solution does feel a little "special" and irregular, though, so I'm interested to hear about any other options that are more regular. One way to rationalize this is to consider the adapted function callee to be inlined into the adapted function caller, without introducing a new scope, and then the defer is like a C++ RAII stack object.

jgravelle-google commented 4 years ago

Prior art: Nim has a defer statement which is implemented in terms of try-finally.

The neat thing here is we don't need to specify a general try/catch/finally mechanism in the interface layer in order to get the right behavior here. The stack copying at the time of the defer is a good idea as well, mostly because it lets us duplicate values without needing to reimplement locals or something.

lukewagner commented 4 years ago

Two extra use cases to consider for any solution to this problem (which are supported by the above defer instruction, but not with the more-regular alternatives I've imagined):

  1. A single linear memory (or other resource) allocation can be used to create N string (or other interface values).
  2. A single string (or other interface value) can be consumed by N string-to-memory instructions.
devsnek commented 4 years ago

I would've assumed defer would be something in a higher level system that generates the section, not part of the section format itself. nvm i can't read. I like the idea of defer in this case.

lukewagner commented 4 years ago

Resolved, at least as an initial draft, in #63.