alexcrichton opened PR #4198 from no-component-value
to main
:
Prior to this PR a major feature of calling component exports (#4039)
was the usage of theValue<T>
type. This type represents a value
stored in wasm linear memory (the typeT
stored there). This
implementation had a number of drawbacks though:
When returning a value it's ABI-specific whether you use
T
or
Value<T>
as a return value. IfT
is represented with one wasm
primitive then you have to returnT
, otherwise the return value must
beValue<T>
. This is somewhat non-obvious and leaks ABI-details into
the API which is unfortunate.The
T
inValue<T>
was somewhat non-obvious. For example a
wasm-owned string wasValue<String>
. UsingValue<&str>
didn't
work.Working with
Value<T>
was unergonomic in the sense that you had to
first "pair" it with a&Store<U>
to get aCursor<T>
and then you
could start reading the value.Custom structs and enums, while not implemented yet, were planned to
be quite wonky where when you hadCursor<MyStruct>
then you would
have to import aCursorMyStructExt
trait generated by a proc-macro
(think a#[derive]
on the definition ofMyStruct
) which would
enable field accessors, returning cursors of all the fields.In general there was no "generic way" to load a
T
from memory. Other
operations like lift/lower/store all had methods in the
ComponentValue
trait but load had no equivalent.None of these drawbacks were deal-breakers per-se. When I started
to implement imported functions, though, theValue<T>
type no longer
worked. The major difference between imports and exports is that when
receiving values from wasm an export returns at most one wasm primitive
where an import can yield (through arguments) up to 16 wasm primitives.
This means that if an export returned a string it would always be
Value<String>
but if an import took a string as an argument there was
actually no way to represent this withValue<String>
since the value
wasn't actually stored in memory but rather the pointer/length pair is
received as arguments. Overall this meant thatValue<T>
couldn't be
used for arguments-to-imports, which means that altogether something new
would be required.This PR completely removes the
Value<T>
andCursor<T>
type in favor
of a different implementation. The inspiration from this comes from the
fact that all primitives can be both lifted and lowered into wasm while
it's just some times which can only go one direction. For example
String
can be lowered into wasm but can't be lifted from wasm. Instead
some sort of "view" into wasm needs to be created during lifting.One of the realizations from #4039 was that we could leverage
run-time-type-checking to reject static constructions that don't make
sense. For example if an embedder asserts that a wasm function returns a
RustString
we can reject that at typechecking time because it's
impossible for a wasm module to ever do that.The new system of imports/exports in this PR now looks like:
Type-checking takes into accont an
Op
operation which indicates
whether we'll be lifting or lowering the type. This means that we can
allow the lowering operation forString
but disallow the lifting
operation. While we can't statically rule out an embedder saying that
a component returns aString
we can now reject it at runtime and
disallow it from being called.The
ComponentValue
trait now sports a newload
function. This
function will load and instance ofSelf
from the byte-array
provided. This is implemented for all types but only ever actually
executed when thelift
operation is allowed during type-checking.The
Lift
associated type is removed since it's now expected that the
lift operation returnsSelf
.The
ComponentReturn
trait is now no longer necessary and is removed.
Instead returns are bounded byComponentValue
. During type-checking
it's required that the return value can be lifted, disallowing, for
example, returning aString
or&str
.With
Value
gone there's no need to specify the ABI details of the
return value, or whether it's communicated through memory or not. This
means that handling return values through memory is transparently
handled by Wasmtime.Validation is in a sense more eagerly performed now. Whenever a value
T
is loaded the entire immediate structure ofT
is loaded and
validated. Note that recursive through memory validation still does
not happen, so the contents of lists or strings aren't validated, it's
just validated that the pointers are in-bounds.Overall this felt like a much clearer system to work with and should be
much easier to integrate with imported functions as well. The new
WasmStr
andWasmList<T>
types can be used in import arguments and
lifted from the immediate arguments provided rather than forcing them to
always be stored in memory.<!--
Please ensure that the following steps are all taken care of before submitting
the PR.
[ ] This has been discussed in issue #..., or if not, please tell us why
here.[ ] A short description of what this does, why it is needed; if the
description becomes long, the matter should probably be discussed in an issue
first.[ ] This PR contains test cases, if meaningful.
- [ ] A reviewer from the core maintainer team has been assigned for this PR.
If you don't know who could review this, please indicate so. The list of
suggested reviewers on the right can help you.Please ensure all communication adheres to the code of conduct.
-->
alexcrichton requested fitzgen for a review on PR #4198.
alexcrichton updated PR #4198 from no-component-value
to main
.
fitzgen submitted PR review.
alexcrichton merged PR #4198.
Last updated: Oct 23 2024 at 20:03 UTC