Skip to content
This repository was archived by the owner on Aug 17, 2022. It is now read-only.
This repository was archived by the owner on Aug 17, 2022. It is now read-only.

allow parent functions to be passed to nested instances? #12

Closed
@lukewagner

Description

@lukewagner

Currently, the explainer disallows nested instances from importing any of the parent module's function, memory, table or global definitions. E.g., you can't write:

(module $M
  (func $foo ...)
  (instance $j (instantiate ... (func $func))))

The reason is that $foo closes over $M's instance which is created after the $j instance. With memories, tables and non-immutable-ref.func-initialized globals, there is no such problem, so we could perhaps allow them (which feels a bit irregular, which is why I didn't put this in the initial explainer writeup... but we could).

There is a coherent way that could allow functions too, though, which would make the instantiation rules nicely uniform. But I'm not convinced it's a good idea; I mostly just wanted to post the idea for discussion and future reference.

The observation is that there's only a problem in $M if $j calls $foo during $j's start function. But calling imports from start functions is generally a bad idea; start functions should only do instance-internal stuff like setting up memory/tables/etc. So what if $foo was initially created in a state with no instance, such that calling $foo traps, and once $M's instance is created, $foo is updated to be a proper callable funcinst. Then $foo could be passed to $j as above, and as long as $j didn't call $foo during its start function, everything would be peachy.

Incidentally, functions are already set up in the spec to be "stateful" in this manner, since a funcaddr is the address of a funcinst in the store, which means that the funcinsts can technically be mutated just like memories/tables/globals (not that we should, but we could ;).

Performance/complexity is obviously a concern, but I think this could be pretty simple and cheap. Basically, for the small subset of functions locally statically observed to be passed into instantiate, their prologue would start with a cheap branch.

One use case would be if you have a parent module P that wants to reuse a child module C that imports callback functions as function imports (say, C is a hash table and the callback is the hash function) and P wants to supply its own callbacks. Without the above relaxation, P will have to resort to gross indirections like those in the dynamic linking cyclic dependencies example which will be significantly slower than the "branch in prologue" implementation mentioned above. But I'm not sure how compelling this use cases is?

Incidentally, with the Interface Types rebase onto Module Linking, there is a very strong use case since this situation arises with every import adapter. In particular, import adapters both need to be imported by the nested core wasm module (so they can adapt its imports) and be able to call back into core wasm (for malloc). But this relaxation could be kept to the Interface Types layer and out of core wasm, so this isn't necessarily a use case either.

Thoughts?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions