pchickey commented on issue #734:
wiggle exists now! its neat to go back and remember the bad old days
pchickey closed issue #734:
wigpresently generateswasi-commontypes fromwitxdescriptions of the standard. This is a great start, but there are a handful of ways in which the design ofwasi-commonis difficult to automatically generate more code for. I want to generate as much of the boilerplate for each hostcall as I can, but before we get there, we need to sort out some difficulties with datatypes.This proposal seeks to redesign we manage 1. validating and chasing pointers into guest memory, ensuring that all references into guest memory are safe, and 2. validating enum and flag values.
We currently depend on some hand-written
enc_/dec_functions to serialize and deserialize some types to and from guest memory. I want to replace those with automatically generated implementations without losing any of the zero-copy optimizations we currently have.Generated Types
There are two purposes for generated types:
Using a value that resides in guest memory. This includes reading a value
from the guest memory (via a pointer), or writing a value into guest memory.
These should only be used in the host calls, and not by any business logic,
except for logic that determines if a value has a valid encoding (memory
out-of-bounds, memory alignment, or value out-of-range).
struct GuestMemory<'a>encapsulates a Linear Memory and ensures all
borrows of that memory are safe Rust - there can be multiple immutable borrows of the same memory, but at most one mutable borrow. Constructed from a*mut u8pointer
to the start of linear memory, andusizelength. The base pointer for linear
memory be pointer-aligned to simplify alignment checks for pointers to structs
in this memory, but this can be relaxed in the future. Lifetime parameter
ensures that use of memory doesn't survive longer than the host call.* The biggest problem that
GuestMemoryneeds to solve is to track
all of the mutable and immutable borrows at run-time to make sure they
do not cause UB. It will keep a map of the immutable and mutable
borrows, and use the drop impls onGuestPtrand friends to remove
those borrows from the map.
MemoryErrordescribes an out-of-bounds memory access or misaligned pointer.
struct GuestPtr<'a, T>is a pointer to a read-only T in guest memory.
Constructed fromGuestMemory<'a>::ptr(&self, ptr: i32) -> Result<GuestPtr<'a, T>, MemoryError>
struct GuestPtrMut<'a, T>is a pointer to a writable T in guest memory.
Constructed fromGuestMemory<'a>::ptr_mut(&self, ptr: i32) -> Result<GuestPtrMut<'a, T>, MemoryError>
struct GuestArray<'a, T>is an array of read-only T in guest memory.
Constructed fromGuestMemory<'a>::array(&self, ptr: i32, len: i32) -> Result<GuestArray<'a, T>, MemoryError>All
Tabove have aGuestValuetrait constraint. This trait describes
the memory layout of the value.Construction of the above are only possible if they point to a valid guest
memory location.GuestPtr and GuestPtrMut each have a
fn offset(&self, elems: i32) -> Result<GuestPtr{Mut}<'a, T>, MemoryError>for accessing subsequent
elements. This allows them to be used like an array, in types likeiovec
andciovec.
ReprErrordescribes an invalid representation of a type in guest memory.
impl GuestPtr<'a, T> { pub fn read(&self) -> Result<T, ReprError> }
dereferences a pointer. As part of dereferencing, it validates the pointee
- that will check if enum and flag values are in bounds, and recursively
for each member of a struct. It won't do any validation on a union - you
have to pick a variant in order to validate it.
impl GuestPtrMut<'a, T> { pub fn write(&self, T) }writes aT: GuestValue
into the memory it references.The
T: GuestValuefor a witx enum should be the hostenum $Typename. The
GuestValueimpl will be used to validate and decode memory into this
owned type. This decoding can be done insideGuestPtr::read, or on a bare
integer of the enum's repr size.The
T: GuestValuefor a witx flags should be the hoststruct $Typename.
As above,GuestValuedoes the validation into an owned type.The
T: GuestValuefor a witx struct has layout defined by witx. The generated
struct depends on whether the struct recursively contains any unions, pointers,
or arrays:
If it does contain a guest memory reference - lets call these
complex GuestValuesuntil I think of a better name, it is amod guest { struct $Typename }. Rather than have public members, the struct has methods
corresponding to each field, with a constructor function to create complex GuestValues.* All fields have a
fn $fieldname_{ref,mut}(&self) -> Result<GuestPtr{Mut}<T>, MemoryError>
accessor to take references of fields* Fields containing flat values are
fn $fieldname(&self) -> Result<T, ReprError>
* Fields which are a (mut) pointer arefn $fieldname(&self) -> Result<GuestPtr{Mut}<’a, T>, MemoryError>. Same for arrays.* Fields which are a union are
fn $fieldname(&self) -> TIf it does not - lets call these
flat GuestValues- then additionally deriveToOwned
to be used without ceremony by the hostThe
T: GuestValuefor a witx union is an opaque struct with a method
for each variant:fn $variantname(&self) -> Result<T, ReprError>Using a value in the Rust implementation of wasi-common. This should be an
idiomatic, wholly owned Rust value. It should look like a Rust type: use modules
for namespacing instead of repeated prefixes, useCamelCasenot
__SHOUTING_SNAKE_CASE.
- A witx struct should be a rust struct
- A witx union should be a rust enum
- A witx enum should be a rust enum
A witx flags should be a rust
struct $Typename { val: repr_type }, and a
rustenum $TypenameFlag { flag_variants... }, and Typename should have
setter and getter methods in terms of $TypenameFlag.A witx array should be a rust Vec
- A witx (const,) pointer should be a
Box<T>whereTis the owned rust value.
pchickey edited a comment on issue #734:
wiggle has been done for a while now! its neat to go back and remember the bad old days
Last updated: Dec 06 2025 at 06:05 UTC