GC: replace ManuallyRooted with OwnedRooted.#11514
GC: replace ManuallyRooted with OwnedRooted.#11514cfallin merged 10 commits intobytecodealliance:mainfrom
Conversation
d1af10a to
cd66959
Compare
This implements the ideas from bytecodealliance#11445: it replaces `ManuallyRooted`, which requires an explicit unroot action with a mut borrow of the store (making it impossible to implement in a standard `Drop` impl), with `OwnedRooted`, which holds an `Arc` only to a small auxiliary memory allocation (an `Arc<()>`) and uses this externalized "liveness flag" to allow for a `Store`-less drop. These liveness flags are scanned during a "trim" pass, which happens both when new owned roots are created, and just before a GC. This should greatly increase safety for host-side users of GC: it provides a way to have a handle whose ownership works like any other Rust value, alive until dropped. It is still not quite as efficient as LIFO-scoped handles (by analogy, for the same reason that individually-freed RAII types are not as efficient as arena allocation), so those remain for efficiency-minded users that have a clear picture of reference lifetimes. At some later time we may wish to use `OwnedRooted` exclusively in our public APIs rather than `Rooted`, and we may wish to rename `Rooted` to `ScopedRooted`, but I haven't done either of those things yet. I opted to *replace* `ManuallyRooted` rather than add a third kind of root, after discussion with fitzgen. One implication of this is that the C API's `anyref` and `externref` types are now 24 or 20 bytes rather than 16 (because of the `Arc` pointer), and correspondingly the Val union grew to that size. I *believe* this is an acceptable tradeoff, but I'm happy to put `ManuallyRooted` back if not. Fixes bytecodealliance#11445.
cd66959 to
cb7f8f8
Compare
Subscribe to Label Actioncc @fitzgen DetailsThis issue or pull request has been labeled: "wasmtime:api", "wasmtime:c-api", "wasmtime:ref-types"Thus the following users have been cc'd because of the following labels:
To subscribe or unsubscribe from this label, edit the |
|
|
|
Updating C API bits I think is fine, but naively I'm surprised that this is still using a generational index. I thought that was only necessary due to the manual nature of Considering the C API some more too, this'll want to go over documentation with a fine-tooth comb as well because we've probably documented "you should call unroot, but nothing bad happens if you don't so long as you free the store". Now that needs to be changed to "you must call unroot or host memory will leak". That's a pretty big departure in behavior from before, which while not bad we need to be sure is clearly communicated. |
fitzgen
left a comment
There was a problem hiding this comment.
Fantastic! Thanks for whipping this up!
This is a plumbing issue, mostly, I think -- the
At least in
I suppose that we don't warn more explicitly than that, but it seems reasonably documented that the usual "free what you allocate" C API rules apply? Happy to iterate here as well in followup! |
|
As a minor request, could this use
My main worry about this is that it's the only allocated "value" in the C API (as a discriminant of I know I've had historical issues binding owned values to other languages like Python and Go in the past, but I don't recall the specifics. |
|
@alexcrichton sure thing!
Let me know what you think... |
|
Oh, sorry, I'm just bemoaning the state of managed values. I don't really want to get this working with the C API in other languages but I'm not sure anyone else wants to either. I don't have a great alternative, however, so all I can point out is that it's objectively more dangerous to switch to a "real pointer" from an index because mistakes are segfaults rather than panics. That doesn't mean we shouldn't do this, so that's sort of me just bemoaning that there's not really any motivation to fully flesh this out on the C side right now |
|
@fitzgen a review of one more bit if you don't mind -- I just pushed a07c3b0 that switches to a different amortized algorithm for root trimming. It doesn't grow the high-water mark exponentially; instead it sets it adaptively based on the last post-trim live-root set size. I realized that the former will still result in unbounded liveness-flag-array growth between GCs if one creates and drops many GC roots without mutating the GC graph itself. The new algorithm is "self-equilibriating" -- it provides a strong guarantee that the size is at most double the max true live-set size, and still has amortized constant-time root creation. I wrote a proof in the doc-comment to convince myself :-) Let me know what you think! |
fitzgen
left a comment
There was a problem hiding this comment.
Ah yes of course. This is actually what I was assuming we were going to do in the original issue discussion and then it fled my mind when I actually reviewed the code. Looks great! And with a proof as well!
|
(Added one more detail after staring at this again: should not have gated the high-water mark update on eager (GC) vs root-creation mode; only the early-out is gated on that.) |
This commit is a follow-up to bytecodealliance#11514 which was discovered through failing tests in the wasmtime-py repository when updating to Wasmtime 37.0.0. Effectively a combination of bugs in the Rust API meant that it wasn't possible to use `externref` or `anyref` bindings correctly. The Rust changes in this commit are: * `wasmtime_val_unroot` correctly drops the value now as opposed to effectively being a noop from before (typo of using `as_externref` vs `from_externref`). * `wasmtime_{anyref,externref,val}_t` now have a `Drop` implementation in Rust to correctly drop them if a value in Rust is dropped. This is required to correctly manage memory in the `wasmtime_func_{call,new}` implementations, for example. * `wasmtime_{anyref,externref,val}_clone` no longer have an unnecessary context parameter. * `wasmtime_{anyref,externref,val}_unroot` no longer have an unnecessary context parameter. Changes in the C/C++ APIs are: * `Result::{ok,err}_ref` APIs were added in addition to the preexisting rvalue accessors. * Loading/storing typed arguments now has an overload for `const T&` and `T&&` which behaves differently. Notably transferring ownership for `T&&` and not for `const T&`. This means that passing parameters when calling a wasm function uses `const T&`, but passing results from a host import uses `T&&`. * `TypedFunc::call` now uses `const Params&` instead of `Params` to explicitly specify it doesn't modify the parameters and forces using the `const T&` store method. * `Store::gc` is now a convenience method for `store.context().gc()` * `ExternRef`, `AnyRef`, and `Val` now have ownership semantics and destructors. This matches the spirit of bytecodealliance#11514 for Rust but models it in C++ as well. This required filling out move/copy constructors/assignments. * The explicit `ExternRef` now takes `std::any` instead of `T`. * Minor issues related to ownership are fixed in `Val` bindings. Valgrind was used to ensure that there were no leaks for the test suite which additionally resulted in a number of `*_delete` calls being added to tests using the C API (accidental omissions). The original goal of this change was to be a patch release for 37.0.1 to enable updating wasmtime-py to the 37.0.x releases of Wasmtime. In the end though the changes here were broad enough that I no longer feel that this is a good idea, so wasmtime-py will be skipping the 37 version of Wasmtime.
This commit is a follow-up to bytecodealliance#11514 which was discovered through failing tests in the wasmtime-py repository when updating to Wasmtime 37.0.0. This is a backport to the 37.0.x release branch which contains the minimal infrastructure necessary to get to parity with `main` in terms of avoiding leaks. Care is taken to avoid changing any public APIs here which means that some previously-provided parameters are no longer used, for example.
* Fix externref/anyref ownership in C/C++ API This commit is a follow-up to #11514 which was discovered through failing tests in the wasmtime-py repository when updating to Wasmtime 37.0.0. This is a backport to the 37.0.x release branch which contains the minimal infrastructure necessary to get to parity with `main` in terms of avoiding leaks. Care is taken to avoid changing any public APIs here which means that some previously-provided parameters are no longer used, for example. * Run `clang-format`
* Fix externref/anyref ownership in C/C++ API This commit is a follow-up to #11514 which was discovered through failing tests in the wasmtime-py repository when updating to Wasmtime 37.0.0. Effectively a combination of bugs in the Rust API meant that it wasn't possible to use `externref` or `anyref` bindings correctly. The Rust changes in this commit are: * `wasmtime_val_unroot` correctly drops the value now as opposed to effectively being a noop from before (typo of using `as_externref` vs `from_externref`). * `wasmtime_{anyref,externref,val}_t` now have a `Drop` implementation in Rust to correctly drop them if a value in Rust is dropped. This is required to correctly manage memory in the `wasmtime_func_{call,new}` implementations, for example. * `wasmtime_{anyref,externref,val}_clone` no longer have an unnecessary context parameter. * `wasmtime_{anyref,externref,val}_unroot` no longer have an unnecessary context parameter. Changes in the C/C++ APIs are: * `Result::{ok,err}_ref` APIs were added in addition to the preexisting rvalue accessors. * Loading/storing typed arguments now has an overload for `const T&` and `T&&` which behaves differently. Notably transferring ownership for `T&&` and not for `const T&`. This means that passing parameters when calling a wasm function uses `const T&`, but passing results from a host import uses `T&&`. * `TypedFunc::call` now uses `const Params&` instead of `Params` to explicitly specify it doesn't modify the parameters and forces using the `const T&` store method. * `Store::gc` is now a convenience method for `store.context().gc()` * `ExternRef`, `AnyRef`, and `Val` now have ownership semantics and destructors. This matches the spirit of #11514 for Rust but models it in C++ as well. This required filling out move/copy constructors/assignments. * The explicit `ExternRef` now takes `std::any` instead of `T`. * Minor issues related to ownership are fixed in `Val` bindings. Valgrind was used to ensure that there were no leaks for the test suite which additionally resulted in a number of `*_delete` calls being added to tests using the C API (accidental omissions). The original goal of this change was to be a patch release for 37.0.1 to enable updating wasmtime-py to the 37.0.x releases of Wasmtime. In the end though the changes here were broad enough that I no longer feel that this is a good idea, so wasmtime-py will be skipping the 37 version of Wasmtime. * Run `clang-format` prtest:full
* Fix externref/anyref ownership in C/C++ API This commit is a follow-up to bytecodealliance#11514 which was discovered through failing tests in the wasmtime-py repository when updating to Wasmtime 37.0.0. Effectively a combination of bugs in the Rust API meant that it wasn't possible to use `externref` or `anyref` bindings correctly. The Rust changes in this commit are: * `wasmtime_val_unroot` correctly drops the value now as opposed to effectively being a noop from before (typo of using `as_externref` vs `from_externref`). * `wasmtime_{anyref,externref,val}_t` now have a `Drop` implementation in Rust to correctly drop them if a value in Rust is dropped. This is required to correctly manage memory in the `wasmtime_func_{call,new}` implementations, for example. * `wasmtime_{anyref,externref,val}_clone` no longer have an unnecessary context parameter. * `wasmtime_{anyref,externref,val}_unroot` no longer have an unnecessary context parameter. Changes in the C/C++ APIs are: * `Result::{ok,err}_ref` APIs were added in addition to the preexisting rvalue accessors. * Loading/storing typed arguments now has an overload for `const T&` and `T&&` which behaves differently. Notably transferring ownership for `T&&` and not for `const T&`. This means that passing parameters when calling a wasm function uses `const T&`, but passing results from a host import uses `T&&`. * `TypedFunc::call` now uses `const Params&` instead of `Params` to explicitly specify it doesn't modify the parameters and forces using the `const T&` store method. * `Store::gc` is now a convenience method for `store.context().gc()` * `ExternRef`, `AnyRef`, and `Val` now have ownership semantics and destructors. This matches the spirit of bytecodealliance#11514 for Rust but models it in C++ as well. This required filling out move/copy constructors/assignments. * The explicit `ExternRef` now takes `std::any` instead of `T`. * Minor issues related to ownership are fixed in `Val` bindings. Valgrind was used to ensure that there were no leaks for the test suite which additionally resulted in a number of `*_delete` calls being added to tests using the C API (accidental omissions). The original goal of this change was to be a patch release for 37.0.1 to enable updating wasmtime-py to the 37.0.x releases of Wasmtime. In the end though the changes here were broad enough that I no longer feel that this is a good idea, so wasmtime-py will be skipping the 37 version of Wasmtime. * Run `clang-format` prtest:full
* Fix externref/anyref ownership in C/C++ API This commit is a follow-up to #11514 which was discovered through failing tests in the wasmtime-py repository when updating to Wasmtime 37.0.0. Effectively a combination of bugs in the Rust API meant that it wasn't possible to use `externref` or `anyref` bindings correctly. The Rust changes in this commit are: * `wasmtime_val_unroot` correctly drops the value now as opposed to effectively being a noop from before (typo of using `as_externref` vs `from_externref`). * `wasmtime_{anyref,externref,val}_t` now have a `Drop` implementation in Rust to correctly drop them if a value in Rust is dropped. This is required to correctly manage memory in the `wasmtime_func_{call,new}` implementations, for example. * `wasmtime_{anyref,externref,val}_clone` no longer have an unnecessary context parameter. * `wasmtime_{anyref,externref,val}_unroot` no longer have an unnecessary context parameter. Changes in the C/C++ APIs are: * `Result::{ok,err}_ref` APIs were added in addition to the preexisting rvalue accessors. * Loading/storing typed arguments now has an overload for `const T&` and `T&&` which behaves differently. Notably transferring ownership for `T&&` and not for `const T&`. This means that passing parameters when calling a wasm function uses `const T&`, but passing results from a host import uses `T&&`. * `TypedFunc::call` now uses `const Params&` instead of `Params` to explicitly specify it doesn't modify the parameters and forces using the `const T&` store method. * `Store::gc` is now a convenience method for `store.context().gc()` * `ExternRef`, `AnyRef`, and `Val` now have ownership semantics and destructors. This matches the spirit of #11514 for Rust but models it in C++ as well. This required filling out move/copy constructors/assignments. * The explicit `ExternRef` now takes `std::any` instead of `T`. * Minor issues related to ownership are fixed in `Val` bindings. Valgrind was used to ensure that there were no leaks for the test suite which additionally resulted in a number of `*_delete` calls being added to tests using the C API (accidental omissions). The original goal of this change was to be a patch release for 37.0.1 to enable updating wasmtime-py to the 37.0.x releases of Wasmtime. In the end though the changes here were broad enough that I no longer feel that this is a good idea, so wasmtime-py will be skipping the 37 version of Wasmtime. * Run `clang-format` prtest:full
* GC: replace ManuallyRooted with OwnedRooted. This implements the ideas from bytecodealliance#11445: it replaces `ManuallyRooted`, which requires an explicit unroot action with a mut borrow of the store (making it impossible to implement in a standard `Drop` impl), with `OwnedRooted`, which holds an `Arc` only to a small auxiliary memory allocation (an `Arc<()>`) and uses this externalized "liveness flag" to allow for a `Store`-less drop. These liveness flags are scanned during a "trim" pass, which happens both when new owned roots are created, and just before a GC. This should greatly increase safety for host-side users of GC: it provides a way to have a handle whose ownership works like any other Rust value, alive until dropped. It is still not quite as efficient as LIFO-scoped handles (by analogy, for the same reason that individually-freed RAII types are not as efficient as arena allocation), so those remain for efficiency-minded users that have a clear picture of reference lifetimes. At some later time we may wish to use `OwnedRooted` exclusively in our public APIs rather than `Rooted`, and we may wish to rename `Rooted` to `ScopedRooted`, but I haven't done either of those things yet. I opted to *replace* `ManuallyRooted` rather than add a third kind of root, after discussion with fitzgen. One implication of this is that the C API's `anyref` and `externref` types are now 24 or 20 bytes rather than 16 (because of the `Arc` pointer), and correspondingly the Val union grew to that size. I *believe* this is an acceptable tradeoff, but I'm happy to put `ManuallyRooted` back if not. Fixes bytecodealliance#11445. * Review feedback. * Fix for riscv32imac: loosen asserts on struct size slightly to allow for different padding. * C API: use a `*const ()` to pass the liveness-flag Arc through C. * Add some additional documentation warning about unrooting in the C API. * Fix size-of test on 32-bit platforms. * New amortized algorithm for root trimming. * core::cmp, not std::cmp * Always set high-water mark, even if eager. * Make val size-assert tolerant of padding bytes in crates/c-api/src/val.rs too.
* Fix externref/anyref ownership in C/C++ API This commit is a follow-up to bytecodealliance#11514 which was discovered through failing tests in the wasmtime-py repository when updating to Wasmtime 37.0.0. Effectively a combination of bugs in the Rust API meant that it wasn't possible to use `externref` or `anyref` bindings correctly. The Rust changes in this commit are: * `wasmtime_val_unroot` correctly drops the value now as opposed to effectively being a noop from before (typo of using `as_externref` vs `from_externref`). * `wasmtime_{anyref,externref,val}_t` now have a `Drop` implementation in Rust to correctly drop them if a value in Rust is dropped. This is required to correctly manage memory in the `wasmtime_func_{call,new}` implementations, for example. * `wasmtime_{anyref,externref,val}_clone` no longer have an unnecessary context parameter. * `wasmtime_{anyref,externref,val}_unroot` no longer have an unnecessary context parameter. Changes in the C/C++ APIs are: * `Result::{ok,err}_ref` APIs were added in addition to the preexisting rvalue accessors. * Loading/storing typed arguments now has an overload for `const T&` and `T&&` which behaves differently. Notably transferring ownership for `T&&` and not for `const T&`. This means that passing parameters when calling a wasm function uses `const T&`, but passing results from a host import uses `T&&`. * `TypedFunc::call` now uses `const Params&` instead of `Params` to explicitly specify it doesn't modify the parameters and forces using the `const T&` store method. * `Store::gc` is now a convenience method for `store.context().gc()` * `ExternRef`, `AnyRef`, and `Val` now have ownership semantics and destructors. This matches the spirit of bytecodealliance#11514 for Rust but models it in C++ as well. This required filling out move/copy constructors/assignments. * The explicit `ExternRef` now takes `std::any` instead of `T`. * Minor issues related to ownership are fixed in `Val` bindings. Valgrind was used to ensure that there were no leaks for the test suite which additionally resulted in a number of `*_delete` calls being added to tests using the C API (accidental omissions). The original goal of this change was to be a patch release for 37.0.1 to enable updating wasmtime-py to the 37.0.x releases of Wasmtime. In the end though the changes here were broad enough that I no longer feel that this is a good idea, so wasmtime-py will be skipping the 37 version of Wasmtime. * Run `clang-format` prtest:full
This implements the ideas from #11445: it replaces
ManuallyRooted, which requires an explicit unroot action with a mut borrow of the store (making it impossible to implement in a standardDropimpl), withOwnedRooted, which holds anArconly to a small auxiliary memory allocation (anArc<()>) and uses this externalized "liveness flag" to allow for aStore-less drop. These liveness flags are scanned during a "trim" pass, which happens both when new owned roots are created, and just before a GC.This should greatly increase safety for host-side users of GC: it provides a way to have a handle whose ownership works like any other Rust value, alive until dropped. It is still not quite as efficient as LIFO-scoped handles (by analogy, for the same reason that individually-freed RAII types are not as efficient as arena allocation), so those remain for efficiency-minded users that have a clear picture of reference lifetimes.
At some later time we may wish to use
OwnedRootedexclusively in our public APIs rather thanRooted, and we may wish to renameRootedtoScopedRooted, but I haven't done either of those things yet.I opted to replace
ManuallyRootedrather than add a third kind of root, after discussion with fitzgen. One implication of this is that the C API'sanyrefandexternreftypes are now 24 or 20 bytes rather than 16 (because of theArcpointer), and correspondingly the Val union grew to that size. I believe this is an acceptable tradeoff, but I'm happy to putManuallyRootedback if not.Fixes #11445.