-
Notifications
You must be signed in to change notification settings - Fork 57
add defer-call-export instruction to explainer #63
Conversation
Have you considered the case of nested defer calls? Eg, what happens when the following adapter code is run: (@interface func (export "foobar")
defer-call-export "free"
)
(@interface implement (import "foobar")
defer-call-export "foobar"
) I assume deferred calls would create a new "scope" with its own LIFO defer stack. |
As the explainer is currently written, with only There's a separate topic that should be discussed in a separate issue of adding a |
|
||
```wasm | ||
(@interface func (export "greeting") (result string) | ||
call-export "greeting_" | ||
memory-to-string "mem" "free" | ||
dup | ||
memory-to-string "mem" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to imply (with the preceding (memory (export "mem") 1) ) that we are exporting our memory. This is unfortunate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get around this in my polyfill by not re-exporting anything by default. I would say (@interface forward (export "mem"))
if I wanted to explicitly export something, but absent that it isn't present in the interface. Here for example we try to print the memories that the modules export, but they're undefined because the exports aren't forwarded.
We talked about hiding-by-default behavior in one of the video meetings, and I think it's a good idea. So, given that, this means we're exporting our memory from the core wasm module to the adapter, but not (necessarily) from the adapter to the interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed with Jacob. In fact, the explainer already explains that memory is not exported from the adapted module (unless it is explicitly re-exported via some as-yet-un-introduced @interface
statement).
```wasm | ||
(@interface func (export "greeting") (result string) | ||
call-export "greeting_" | ||
defer-call-export "free" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that it is better to have a general syntactic structure for performing deferred operations. This defer-call-export instruction seems super specific.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer just defer-call
and use helper functions to model generic structure, rather than reimplement blocks (with signatures). I'll try to actually get that written up today.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A helper function would be more generic and I think we couldn't do anything finer-grained without introducing fancier closures, which sounds more complicated than we need here. By capturing a call to a function (with no free variables), you only have to capture the call's argument values.
That being said, Olivier's question above is interesting: what happens if you have a defer-call
from within a defer-call
ed helper function. There are answers, of course, (basically, what promises do) but it makes defer-call
ing helper functions more complicated than defer-call
ing exports and so that makes me want to hear concrete use cases for the increased generality.
Also, in the meantime, the explainer doesn't mention helper functions, so I'd be inclined to start with just defer-call-export
and reconsider when helper functions are added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, in the meantime, the explainer doesn't mention helper functions, so I'd be inclined to start with just defer-call-export and reconsider when helper functions are added.
Agree. More so I'm saying I prefer helper functions in response to the desire for more general syntactic structure here, for most of the reasons you describe (e.g. how to arg.get from a defer'd block?). So rather than make a new generic structure, let's reuse the generic structure that is functions. But that's only relevant if/when we want generic structure in the first place.
Note also that `memory-to-string` has no `free` immediate in this example. While | ||
it would be *possible* to do so, since the caller of the adapter is wasm code, | ||
it's simpler and potentially more efficient to let the caller worry about when | ||
to free the string. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is one of the weakest aspects of C's memory model: lack of structure around who is responsible for free-ing allocated memory. It has to be done by application convention; but that is incompatible with generating adapter code automatically. (Our adapter instructions should not be C or C++ specific.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unique_ptr and shared_ptr might be able to do some of this automatically, but yeah in general this will need to be done manually.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Anything to change here or can I resolve this thread?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also keep in mind that there are use cases where you might not want to dynamically allocate/free anything; eg, maybe you're calling log
with a constant string, or a string generated on your linear memory stack.
In practice, the host should worry about dynamic allocation when passing data to/from the callee, not the caller.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but such cases are handled by simply not using any defer-call-export
(as in the walkthrough's first section).
|
||
```wasm | ||
(@interface func (export "greeting") (result string) | ||
call-export "greeting_" | ||
memory-to-string "mem" "free" | ||
dup | ||
memory-to-string "mem" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get around this in my polyfill by not re-exporting anything by default. I would say (@interface forward (export "mem"))
if I wanted to explicitly export something, but absent that it isn't present in the interface. Here for example we try to print the memories that the modules export, but they're undefined because the exports aren't forwarded.
We talked about hiding-by-default behavior in one of the video meetings, and I think it's a good idea. So, given that, this means we're exporting our memory from the core wasm module to the adapter, but not (necessarily) from the adapter to the interface.
```wasm | ||
(@interface func (export "greeting") (result string) | ||
call-export "greeting_" | ||
defer-call-export "free" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer just defer-call
and use helper functions to model generic structure, rather than reimplement blocks (with signatures). I'll try to actually get that written up today.
Note also that `memory-to-string` has no `free` immediate in this example. While | ||
it would be *possible* to do so, since the caller of the adapter is wasm code, | ||
it's simpler and potentially more efficient to let the caller worry about when | ||
to free the string. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unique_ptr and shared_ptr might be able to do some of this automatically, but yeah in general this will need to be done manually.
Actually, if you export memory then you export it. It is optional to use
the interface types
On Wed, Sep 4, 2019 at 12:10 PM Luke Wagner ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In proposals/interface-types/Explainer.md
<#63 (comment)>
:
> @@ -346,7 +369,8 @@ used in the same adapter function:
arg.get $str
string-to-memory "mem" "malloc"
call-export "frob_"
- memory-to-string "mem" "free"
As answered above: only from the core module, not the adapted module. The
core module is 100% encapsulated by the adapted module, so there is no
issue, I think. There's also no other way, that I know of, if we don't want
to change core wasm semantics.
—
You are receiving this because your review was requested.
Reply to this email directly, view it on GitHub
<#63?email_source=notifications&email_token=AAQAXUG5ULQ4JJH2GGPKBG3QIAB3LA5CNFSM4ISIFDW2YY3PNVWWK3TUL52HS4DFWFIHK3DMKJSXC5LFON2FEZLWNFSXPKTDN5WW2ZLOORPWSZGOCDVPPWI#discussion_r320927554>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAQAXUGDSPKG33LKUB6J7LTQIAB3LANCNFSM4ISIFDWQ>
.
--
Francis McCabe
SWE
|
@fgmccabe There seems to be some confusion here, so let me try to be more clear: if an interface adapter custom section is present and the host implements Interface Types, than an export from the core module only shows up as an export from the adapted module if it is explicitly re-exported via the adapter. The adapted module is the only module the outside world sees with the core module an encapsulated implementation detail. |
I understand that. However, someone can still access the wasm module
ignoring the interface adapters. If they do, or even without that, they can
directly access the now-shared memory. Or, am I missing something?
Francis
…On Wed, Sep 4, 2019 at 2:19 PM Luke Wagner ***@***.***> wrote:
@fgmccabe <https://github.com/fgmccabe> There seems to be some confusion
here, so let me try to be more clear: if an interface adapter custom
section is present and the host implements Interface Types, than an export
from the *core* module only shows up as an export from the *adapted*
module if it is explicitly re-exported via the adapter. The adapted module
is the only module the outside world sees with the core module an
encapsulated implementation detail.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#63?email_source=notifications&email_token=AAQAXUEKCSYM3EWIG56UBM3QIAQ5PA5CNFSM4ISIFDW2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD55ATGI#issuecomment-528091545>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAQAXUCTMLQRHVLJ7T46SFLQIAQ5PANCNFSM4ISIFDWQ>
.
--
Francis McCabe
SWE
|
How? Walk me through the specific way you're thinking of where you can run a wasm module and ignore the interface adapters. I can think of a couple ways to do it, I just don't think they're problems. Off the top of my head:
3) is probably the most problematic, but least probable because it relies on a host that ignores the adapters. If the host implements interface types at instantiate time there's no room to do it. |
What happens when a wasm module references another wasm module and claims
it does not need the help of interface adapters? (C++ talking to C++ e.g.)
Dynamic linking etc.
I want to be able to access a credit card processing module without giving
it my memory (or my allocator)
The only way that this may work is if interfaces are 'optional but
enforced'; i.e., a host is non-conforming if it can understand interface
adapters but allows them to be ignored.
…On Wed, Sep 4, 2019 at 2:40 PM Jacob Gravelle ***@***.***> wrote:
However, someone can still access the wasm module
ignoring the interface adapters.
How? Walk me through the specific way you're thinking of where you can run
a wasm module and ignore the interface adapters.
I can think of a couple ways to do it, I just don't think they're
problems. Off the top of my head:
1. you run two modules in a host that doesn't support interface types.
1. you polyfill it, and that hides the export, and there's no
problem
2. you don't, so the ABIs don't line up and the module either fails
to execute or fails to instantiate
2. you run a module in a host that supports interface types but does
sneaky things with the boundaries. This was already a problem because you
don't trust your host, and your host can do much worse things to mess with
you
3. you download a 3rd party module, include it in your application in
a host that doesn't support interface types, and subvert the expected
interface by providing your own polyfill that doesn't hide exports. This is
equivalent to dynamic linking with an undocumented ABI. It's subject to
break, but works at the moment. You can read memory in ways the library
author didn't intend.
3) is probably the most problematic, but least probable because it relies
on a host that ignores the adapters. If the host implements interface types
at instantiate time there's no room to do it.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#63?email_source=notifications&email_token=AAQAXUDBQE3SP2C4FNLWK4LQIATONA5CNFSM4ISIFDW2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD55CJXA#issuecomment-528098524>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAQAXUG3H5AC3GNFKWZWJPLQIATONANCNFSM4ISIFDWQ>
.
--
Francis McCabe
SWE
|
I think there's some confusion here as to what threat model wasm is supposed to guard against. AFAICT, there is nothing that WebAssembly can (or should) do to protect against misuse by a malicious host. Even if we added a "you are not allowed to use the export directly" boolean field, malicious hosts could always manually edit that field out before compiling (or just plain ingore that field). My understanding of interface types was that they would provide two types of security:
Both of those can be enforced as long as a host links adapted modules imports to adapted module exports, and doesn't link bare module imports/exports together. Making sure the host handles these cases correctly doesn't seem like something that could or should be enforced by the language. EDIT: Never mind. I misunderstood what @fgmccabe was saying. I think what it comes down to is what is the "default" linking model we want wasm to have.
I'm not sure how to resolve this. Though I'm thinking even the esm-integration model should probably have some way to differentiate between things you want to export to the host, and things you want to export to importing modules. |
As a follow-up from some offline discussion: I think the source of the confusion here is resolved by the observation that, once a host has implemented the interface types feature, when an interface adapter custom section is present in a module:
(This is briefly covered in the walkthrough which explicitly notes that the As a corollary to (2), adding a single |
16ec465
to
9a14437
Compare
As discussed in #60 and the video call today.