Skip to main content

for_each_vmctx_type

Macro for_each_vmctx_type 

Source
macro_rules! for_each_vmctx_type {
    ($mac:ident) => { ... };
}
Expand description

Invoke the given macro $mac once, passing it the layout of each of Wasmtime’s “vmctx” types.

This is a higher-order macro: callers define a macro_rules! macro that matches the grammar described below and pass its name as an argument to this macro’s invocation, e.g. for_each_vmctx_type!(define_vmctx_offsets).

§Grammar

The layout below is written in exactly the same grammar that it is handed to $mac in: this macro has a single rule, and that rule does nothing but forward those tokens along. Writing the layout in the grammar that consumers match makes it more verbose than a bespoke input syntax would be, but in exchange there is no normalization pass in between, so what a consumer matches is exactly what a reader of the layout sees.

$mac receives one brace-delimited group per vmctx type:

{
    VMContext vmctx
    static { ...entries... }
    dynamic { ...entries... }
}

where vmctx is the name used for accessor methods of that type, static holds the fixed-width prefix whose offsets depend only on the target pointer size, and dynamic holds the rest, whose offsets additionally depend on the module or component being compiled.

Each entry within a section is a keyword naming the entry’s shape followed by a single delimited group, so that a consumer can iterate over a section with $( ...$kind:ident $entry:tt... )* and dispatch on one whole entry at a time instead of munching the section token by token. An entry is one of:

  • align { ptr } or align { N }: round the running offset up to the target pointer size, or to N bytes. Alignment is always explicit: it is never derived from a field’s type, because the layout being described here does not necessarily align every field to its natural alignment, and inserting padding that the layout does not actually have would silently corrupt every subsequent offset.

  • field { <attrs> <name>: <ty> }: a single field.

  • array { <attrs> <name>[<count>; <IndexType>]: <ty> } (dynamic only): an array of self.<count> elements, indexed by <IndexType>.

  • optional { <attrs> <name>[if <flag>]: <ty> } (dynamic only): a field that is present when self.<flag> is true and absent (zero-sized) otherwise.

A field’s type is always the last thing in its entry. Consumers capture it as a trailing $($fty:tt)* and re-dispatch on its tokens only where they actually need to classify it into a size or a Cranelift type; a :ty capture would be opaque forever, and so could never be classified at all.

<attrs> is a possibly-empty sequence of marker attributes, which consumers match with $(# $fattr:tt)*. They do not affect the layout, but they do affect the accesses generated for the field:

  • #[aggregate]: this field is a composite (a nested struct, or an array of them) rather than a single scalar. Compiled code accesses such a field’s interior piecewise, so there is no one Cranelift type for the field as a whole and no alias-region accessor is generated for it. Its offset is still generated, since that is what interior accesses are computed relative to.

  • #[readonly] and/or #[can_move]: describe how Cranelift may treat loads and stores of this field.

  • #[access_as = Type]: this field is declared with one type (because that is what determines its size and stride) but accessed as another. For example, the component context’s may_leave flags are each stored in a whole VMGlobalDefinition but only ever accessed as a u32.

  • #[ptr_size_offset] (dynamic only): this field’s offset is a function of the target pointer size alone, even though it lives in the dynamic section, because it and everything before it have sizes that do not depend on the vmctx’s shape. These fields get generated accessors that do not need to be parameterized over VMOffsets, only GetPtrSize.

  • #[pointee(<attrs> <Region> as <name> $([<IndexType>])? : <ty>)]: this field is a pointer to memory that lives outside the vmctx and so is not part of its layout at all, but that compiled code reaches only by loading this field first. <Region> names the alias region that memory belongs to, <name> is the accessor generated for it, and <ty> is the type it is accessed as. With an [<IndexType>], the pointee is an array of <ty> indexed by <IndexType> and each element gets its own alias region; without one, it is a single <ty>. The pointee’s <attrs> are its own: how compiled code may treat a load of the pointee is independent of how it may treat a load of the pointer.

Doc comments are deliberately not accepted on fields; use // comments for prose about the layout. Accessor documentation is synthesized from the field names instead, so that there is only one place a field can be described.

A consumer that only cares about one of the two types can filter with a literal-name arm followed by a catch-all, e.g.

(@one VMContext $snake:ident dynamic { $($dyn:tt)* }) => { ...generate... };
(@one $other:ident $snake:ident dynamic { $($dyn:tt)* }) => {};