WebAssembly / component-model

Repository for design and specification of the Component Model
Other
914 stars 78 forks source link

question about locktdown state #234

Open yamt opened 11 months ago

yamt commented 11 months ago

the explainer says:

Components prevent unexpected reentrance by setting the "lockdown" state (in the previous bullet) whenever calling out through an import, clearing the lockdown state on return, thereby preventing reentrant export calls in the interim. This establishes a clear contract between separate components that both prevents obscure composition-time bugs and also enables more-efficient non-reentrant runtime glue code (particularly in the middle of the Canonical ABI). This implies that components by default don't allow concurrency and multi-threaded access will trap.

does it mean to prevent callback-style api as well?

lukewagner commented 11 months ago

Yes, at least by default.. In the future, we could add a way to opt-in in the component-type level (e.g., you imagine some sort of foo: reentrant func(...)) that turns off the reentrancy check. But, from experience, this type of coarse-grained reentrance tends to complicate a codebase (see for example: nested event loops in browsers) and often isn't supported anyways, so it seems like a good idea to make this an explicit part of the interface so that the contract is clear to both parties about what to allow/expect.

yamt commented 11 months ago

i'm not sure if a component is an appropriate unit for that kind of checks. does it mean that, combining two components into a single component can trigger the check, right? but i got the intention. thank you.

btw,

Components define a "lockdown" state that prevents continued execution after a trap. This both prevents continued execution with corrupt state and also allows more-aggressive compiler optimizations (e.g., store reordering). This was considered early in Core WebAssembly standardization but rejected due to the lack of clear trapping boundary.

i'm interested in this discussion in Core WebAssembly standardization. can you give me a pointer if it was public? trapping boundary, is it about threads?

lukewagner commented 11 months ago

does it mean that, combining two components into a single component can trigger the check, right?

Each component's "can be entered" state is tracked independently (as part of the component instance state), so if you link two together, they should basically preserve their independent behavior. If one component simply imports the other, it's not even be possible to cause reentrance; it's only possible when one component instantiates another (via instance definition) that the parent can be called from a child's import and then attempt to call the child's export (using a function table and call_indirect to close the loop).

i'm interested in this discussion in Core WebAssembly standardization. can you give me a pointer if it was public? trapping boundary, is it about threads?

This was in some of the very earliest wasm CG discussions, so I'm not sure where it showed up in minutes/issues; I wasn't able to find anything on GitHub from a quick search, but I might've missed it. The basic reasons not to have lockdown semantics at the core wasm level are:

  1. If you trap in module A, what all do you lockdown? the current A instance? what if it imports a memory imported by another instance, should that lockdown too? what if that other instance imports a second memory, should that get locked down too? It's hard to answer these questions b/c the primitives are so low-level.
  2. In some hypothetical scenarios, maybe the JS code embedding the core wasm does want to observe the state after the trap (say, to capture a crash-dump). (In the case of the C-M, such a feature would be implemented by the runtime/tooling a "layer down".)
yamt commented 11 months ago

does it mean that, combining two components into a single component can trigger the check, right?

Each component's "can be entered" state is tracked independently (as part of the component instance state), so if you link two together, they should basically preserve their independent behavior. If one component simply imports the other, it's not even be possible to cause reentrance; it's only possible when one component instantiates another (via instance definition) that the parent can be called from a child's import and then attempt to call the child's export (using a function table and call_indirect to close the loop).

i meant that, if you have a call graph like A->B->C, and you happened to merge A and C into A', it might break.

i'm interested in this discussion in Core WebAssembly standardization. can you give me a pointer if it was public? trapping boundary, is it about threads?

This was in some of the very earliest wasm CG discussions, so I'm not sure where it showed up in minutes/issues; I wasn't able to find anything on GitHub from a quick search, but I might've missed it. The basic reasons not to have lockdown semantics at the core wasm level are:

1. If you trap in module A, what all do you lockdown?  the current A instance?  what if it imports a memory imported by another instance, should that lockdown too?  what if that other instance imports a second memory, should that get locked down too?  It's hard to answer these questions b/c the primitives are so low-level.

2. In some hypothetical scenarios, maybe the JS code embedding the core wasm _does_ want to observe the state after the trap (say, to capture a crash-dump).  (In the case of the C-M, such a feature would be implemented by the runtime/tooling a "layer down".)

why hasn't it been implemented a "layer down" for core wasm? just for historical reasons?

lukewagner commented 11 months ago

i meant that, if you have a call graph like A->B->C, and you happened to merge A and C into A', it might break.

Ah, I see thanks. The case of parent-->child-->parent reentrance is explicitly allowed (see uses of "calling_enter" below here).

why hasn't it been implemented a "layer down" for core wasm? just for historical reasons?

Depending on the context, wasm may be the lowest layer (consider an OS where was is the ISA for all guest code). It's hard to rule things out at the core wasm layer.