Deduplicate some size/align calculations (#4658)

This commit is an effort to reduce the amount of complexity around
managing the size/alignment calculations of types in the canonical ABI.
Previously the logic for the size/alignment of a type was spread out
across a number of locations. While each individual calculation is not
really the most complicated thing in the world having the duplication in
so many places was constantly worrying me.

I've opted in this commit to centralize all of this within the runtime
at least, and now there's only one "duplicate" of this information in
the fuzzing infrastructure which is to some degree less important to
deduplicate. This commit introduces a new `CanonicalAbiInfo` type to
house all abi size/align information for both memory32 and memory64.
This new type is then used pervasively throughout fused adapter
compilation, dynamic `Val` management, and typed functions. This type
was also able to reduce the complexity of the macro-generated code
meaning that even `wasmtime-component-macro` is performing less math
than it was before.

One other major feature of this commit is that this ABI information is
now saved within a `ComponentTypes` structure. This avoids recursive
querying of size/align information frequently and instead effectively
caching it. This was a worry I had for the fused adapter compiler which
frequently sought out size/align information and would recursively
descend each type tree each time. The `fact-valid-module` fuzzer is now
nearly 10x faster in terms of iterations/s which I suspect is due to
this caching.
This commit is contained in:
Alex Crichton
2022-08-09 14:52:20 -05:00
committed by GitHub
parent d5de91b953
commit bd70dbebbd
15 changed files with 845 additions and 763 deletions

View File

@@ -1,5 +1,5 @@
use crate::component::instance::{Instance, InstanceData};
use crate::component::types::{SizeAndAlignment, Type};
use crate::component::types::Type;
use crate::component::values::Val;
use crate::store::{StoreOpaque, Stored};
use crate::{AsContext, AsContextMut, StoreContextMut, ValRaw};
@@ -8,8 +8,8 @@ use std::mem::{self, MaybeUninit};
use std::ptr::NonNull;
use std::sync::Arc;
use wasmtime_environ::component::{
CanonicalOptions, ComponentTypes, CoreDef, RuntimeComponentInstanceIndex, TypeFuncIndex,
MAX_FLAT_PARAMS, MAX_FLAT_RESULTS,
CanonicalAbiInfo, CanonicalOptions, ComponentTypes, CoreDef, RuntimeComponentInstanceIndex,
TypeFuncIndex, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS,
};
use wasmtime_runtime::{Export, ExportFunction, VMTrampoline};
@@ -558,18 +558,15 @@ impl Func {
args: &[Val],
dst: &mut MaybeUninit<[ValRaw; MAX_FLAT_PARAMS]>,
) -> Result<()> {
let mut size = 0;
let mut alignment = 1;
for ty in params {
alignment = alignment.max(ty.size_and_alignment().alignment);
ty.next_field(&mut size);
}
let abi = CanonicalAbiInfo::record(params.iter().map(|t| t.canonical_abi()));
let mut memory = MemoryMut::new(store.as_context_mut(), options);
let ptr = memory.realloc(0, 0, alignment, size)?;
let size = usize::try_from(abi.size32).unwrap();
let ptr = memory.realloc(0, 0, abi.align32, size)?;
let mut offset = ptr;
for (ty, arg) in params.iter().zip(args) {
arg.store(&mut memory, ty.next_field(&mut offset))?;
let abi = ty.canonical_abi();
arg.store(&mut memory, abi.next_field32_size(&mut offset))?;
}
map_maybe_uninit!(dst[0]).write(ValRaw::i64(ptr as i64));
@@ -582,17 +579,17 @@ impl Func {
ty: &Type,
src: &mut std::slice::Iter<'_, ValRaw>,
) -> Result<Val> {
let SizeAndAlignment { size, alignment } = ty.size_and_alignment();
let abi = ty.canonical_abi();
// FIXME: needs to read an i64 for memory64
let ptr = usize::try_from(src.next().unwrap().get_u32())?;
if ptr % usize::try_from(alignment)? != 0 {
if ptr % usize::try_from(abi.align32)? != 0 {
bail!("return pointer not aligned");
}
let bytes = mem
.as_slice()
.get(ptr..)
.and_then(|b| b.get(..size))
.and_then(|b| b.get(..usize::try_from(abi.size32).unwrap()))
.ok_or_else(|| anyhow::anyhow!("pointer out of bounds of memory"))?;
Val::load(ty, mem, bytes)

View File

@@ -1,5 +1,4 @@
use crate::component::func::{Memory, MemoryMut, Options};
use crate::component::types::SizeAndAlignment;
use crate::component::{ComponentParams, ComponentType, Lift, Lower, Type, Val};
use crate::{AsContextMut, StoreContextMut, ValRaw};
use anyhow::{anyhow, bail, Context, Result};
@@ -9,7 +8,8 @@ use std::panic::{self, AssertUnwindSafe};
use std::ptr::NonNull;
use std::sync::Arc;
use wasmtime_environ::component::{
ComponentTypes, StringEncoding, TypeFuncIndex, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS,
CanonicalAbiInfo, ComponentTypes, StringEncoding, TypeFuncIndex, MAX_FLAT_PARAMS,
MAX_FLAT_RESULTS,
};
use wasmtime_runtime::component::{
InstanceFlags, VMComponentContext, VMLowering, VMLoweringCallee,
@@ -413,26 +413,19 @@ where
.collect::<Result<Box<[_]>>>()?;
ret_index = param_count;
} else {
let param_layout = {
let mut size = 0;
let mut alignment = 1;
for ty in params.iter() {
alignment = alignment.max(ty.size_and_alignment().alignment);
ty.next_field(&mut size);
}
SizeAndAlignment { size, alignment }
};
let param_abi = CanonicalAbiInfo::record(params.iter().map(|t| t.canonical_abi()));
let memory = Memory::new(cx.0, &options);
let mut offset = validate_inbounds_dynamic(param_layout, memory.as_slice(), &storage[0])?;
let mut offset = validate_inbounds_dynamic(&param_abi, memory.as_slice(), &storage[0])?;
args = params
.iter()
.map(|ty| {
let abi = ty.canonical_abi();
let size = usize::try_from(abi.size32).unwrap();
Val::load(
ty,
&memory,
&memory.as_slice()[ty.next_field(&mut offset)..]
[..ty.size_and_alignment().size],
&memory.as_slice()[abi.next_field32_size(&mut offset)..][..size],
)
})
.collect::<Result<Box<[_]>>>()?;
@@ -451,7 +444,7 @@ where
let ret_ptr = &storage[ret_index];
let mut memory = MemoryMut::new(cx.as_context_mut(), &options);
let ptr =
validate_inbounds_dynamic(result.size_and_alignment(), memory.as_slice_mut(), ret_ptr)?;
validate_inbounds_dynamic(result.canonical_abi(), memory.as_slice_mut(), ret_ptr)?;
ret.store(&mut memory, ptr)?;
}
@@ -460,17 +453,13 @@ where
return Ok(());
}
fn validate_inbounds_dynamic(
SizeAndAlignment { size, alignment }: SizeAndAlignment,
memory: &[u8],
ptr: &ValRaw,
) -> Result<usize> {
fn validate_inbounds_dynamic(abi: &CanonicalAbiInfo, memory: &[u8], ptr: &ValRaw) -> Result<usize> {
// FIXME: needs memory64 support
let ptr = usize::try_from(ptr.get_u32())?;
if ptr % usize::try_from(alignment)? != 0 {
if ptr % usize::try_from(abi.align32)? != 0 {
bail!("pointer not aligned");
}
let end = match ptr.checked_add(size) {
let end = match ptr.checked_add(usize::try_from(abi.size32).unwrap()) {
Some(n) => n,
None => bail!("pointer size overflow"),
};

View File

@@ -8,7 +8,8 @@ use std::marker;
use std::mem::{self, MaybeUninit};
use std::str;
use wasmtime_environ::component::{
ComponentTypes, InterfaceType, StringEncoding, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS,
CanonicalAbiInfo, ComponentTypes, InterfaceType, StringEncoding, VariantInfo, MAX_FLAT_PARAMS,
MAX_FLAT_RESULTS,
};
/// A statically-typed version of [`Func`] which takes `Params` as input and
@@ -363,13 +364,14 @@ pub unsafe trait ComponentType {
#[doc(hidden)]
type Lower: Copy;
/// The size, in bytes, that this type has in the canonical ABI.
/// The information about this type's canonical ABI (size/align/etc).
#[doc(hidden)]
const SIZE32: usize;
const ABI: CanonicalAbiInfo;
/// The alignment, in bytes, that this type has in the canonical ABI.
#[doc(hidden)]
const ALIGN32: u32;
const SIZE32: usize = Self::ABI.size32 as usize;
#[doc(hidden)]
const ALIGN32: u32 = Self::ABI.align32;
/// Returns the number of core wasm abi values will be used to represent
/// this type in its lowered form.
@@ -382,14 +384,19 @@ pub unsafe trait ComponentType {
mem::size_of::<Self::Lower>() / mem::size_of::<ValRaw>()
}
// FIXME: need SIZE64 and ALIGN64 probably
/// Performs a type-check to see whether this component value type matches
/// the interface type `ty` provided.
#[doc(hidden)]
fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()>;
}
#[doc(hidden)]
pub unsafe trait ComponentVariant: ComponentType {
const CASES: &'static [CanonicalAbiInfo];
const INFO: VariantInfo = VariantInfo::new_static(Self::CASES);
const PAYLOAD_OFFSET32: usize = Self::INFO.payload_offset32 as usize;
}
/// Host types which can be passed to WebAssembly components.
///
/// This trait is implemented for all types that can be passed to components
@@ -475,8 +482,7 @@ macro_rules! forward_type_impls {
unsafe impl <$($generics)*> ComponentType for $a {
type Lower = <$b as ComponentType>::Lower;
const SIZE32: usize = <$b as ComponentType>::SIZE32;
const ALIGN32: u32 = <$b as ComponentType>::ALIGN32;
const ABI: CanonicalAbiInfo = <$b as ComponentType>::ABI;
#[inline]
fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()> {
@@ -570,17 +576,11 @@ forward_list_lifts! {
// Macro to help generate `ComponentType` implementations for primitive types
// such as integers, char, bool, etc.
macro_rules! integers {
($($primitive:ident = $ty:ident in $field:ident/$get:ident,)*) => ($(
($($primitive:ident = $ty:ident in $field:ident/$get:ident with abi:$abi:ident,)*) => ($(
unsafe impl ComponentType for $primitive {
type Lower = ValRaw;
const SIZE32: usize = mem::size_of::<$primitive>();
// Note that this specifically doesn't use `align_of` as some
// host platforms have a 4-byte alignment for primitive types but
// the canonical abi always has the same size/alignment for these
// types.
const ALIGN32: u32 = mem::size_of::<$primitive>() as u32;
const ABI: CanonicalAbiInfo = CanonicalAbiInfo::$abi;
fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> {
match ty {
@@ -624,18 +624,18 @@ macro_rules! integers {
}
integers! {
i8 = S8 in i32/get_i32,
u8 = U8 in u32/get_u32,
i16 = S16 in i32/get_i32,
u16 = U16 in u32/get_u32,
i32 = S32 in i32/get_i32,
u32 = U32 in u32/get_u32,
i64 = S64 in i64/get_i64,
u64 = U64 in u64/get_u64,
i8 = S8 in i32/get_i32 with abi:SCALAR1,
u8 = U8 in u32/get_u32 with abi:SCALAR1,
i16 = S16 in i32/get_i32 with abi:SCALAR2,
u16 = U16 in u32/get_u32 with abi:SCALAR2,
i32 = S32 in i32/get_i32 with abi:SCALAR4,
u32 = U32 in u32/get_u32 with abi:SCALAR4,
i64 = S64 in i64/get_i64 with abi:SCALAR8,
u64 = U64 in u64/get_u64 with abi:SCALAR8,
}
macro_rules! floats {
($($float:ident/$get_float:ident = $ty:ident)*) => ($(const _: () = {
($($float:ident/$get_float:ident = $ty:ident with abi:$abi:ident)*) => ($(const _: () = {
/// All floats in-and-out of the canonical abi always have their nan
/// payloads canonicalized. conveniently the `NAN` constant in rust has
/// the same representation as canonical nan, so we can use that for the
@@ -652,11 +652,7 @@ macro_rules! floats {
unsafe impl ComponentType for $float {
type Lower = ValRaw;
const SIZE32: usize = mem::size_of::<$float>();
// note that like integers size is used here instead of alignment to
// respect the canonical abi, not host platforms.
const ALIGN32: u32 = mem::size_of::<$float>() as u32;
const ABI: CanonicalAbiInfo = CanonicalAbiInfo::$abi;
fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> {
match ty {
@@ -701,15 +697,14 @@ macro_rules! floats {
}
floats! {
f32/get_f32 = Float32
f64/get_f64 = Float64
f32/get_f32 = Float32 with abi:SCALAR4
f64/get_f64 = Float64 with abi:SCALAR8
}
unsafe impl ComponentType for bool {
type Lower = ValRaw;
const SIZE32: usize = 1;
const ALIGN32: u32 = 1;
const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR1;
fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> {
match ty {
@@ -758,8 +753,7 @@ unsafe impl Lift for bool {
unsafe impl ComponentType for char {
type Lower = ValRaw;
const SIZE32: usize = 4;
const ALIGN32: u32 = 4;
const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR4;
fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> {
match ty {
@@ -810,8 +804,7 @@ const MAX_STRING_BYTE_LENGTH: usize = (1 << 31) - 1;
unsafe impl ComponentType for str {
type Lower = [ValRaw; 2];
const SIZE32: usize = 8;
const ALIGN32: u32 = 4;
const ABI: CanonicalAbiInfo = CanonicalAbiInfo::POINTER_PAIR;
fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> {
match ty {
@@ -1078,8 +1071,7 @@ impl WasmStr {
unsafe impl ComponentType for WasmStr {
type Lower = <str as ComponentType>::Lower;
const SIZE32: usize = <str as ComponentType>::SIZE32;
const ALIGN32: u32 = <str as ComponentType>::ALIGN32;
const ABI: CanonicalAbiInfo = CanonicalAbiInfo::POINTER_PAIR;
fn typecheck(ty: &InterfaceType, _types: &ComponentTypes) -> Result<()> {
match ty {
@@ -1114,8 +1106,7 @@ where
{
type Lower = [ValRaw; 2];
const SIZE32: usize = 8;
const ALIGN32: u32 = 4;
const ABI: CanonicalAbiInfo = CanonicalAbiInfo::POINTER_PAIR;
fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()> {
match ty {
@@ -1324,8 +1315,7 @@ raw_wasm_list_accessors! {
unsafe impl<T: ComponentType> ComponentType for WasmList<T> {
type Lower = <[T] as ComponentType>::Lower;
const SIZE32: usize = <[T] as ComponentType>::SIZE32;
const ALIGN32: u32 = <[T] as ComponentType>::ALIGN32;
const ABI: CanonicalAbiInfo = CanonicalAbiInfo::POINTER_PAIR;
fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()> {
match ty {
@@ -1354,24 +1344,6 @@ unsafe impl<T: Lift> Lift for WasmList<T> {
}
}
/// Round `a` up to the next multiple of `align`, assuming that `align` is a power of 2.
#[inline]
pub const fn align_to(a: usize, align: u32) -> usize {
debug_assert!(align.is_power_of_two());
let align = align as usize;
(a + (align - 1)) & !(align - 1)
}
/// For a field of type T starting after `offset` bytes, updates the offset to reflect the correct
/// alignment and size of T. Returns the correctly aligned offset for the start of the field.
#[inline]
pub fn next_field<T: ComponentType>(offset: &mut usize) -> usize {
*offset = align_to(*offset, T::ALIGN32);
let result = *offset;
*offset += T::SIZE32;
result
}
/// Verify that the given wasm type is a tuple with the expected fields in the right order.
fn typecheck_tuple(
ty: &InterfaceType,
@@ -1585,17 +1557,24 @@ where
{
type Lower = TupleLower2<<u32 as ComponentType>::Lower, T::Lower>;
const SIZE32: usize = align_to(1, T::ALIGN32) + T::SIZE32;
const ALIGN32: u32 = T::ALIGN32;
const ABI: CanonicalAbiInfo =
CanonicalAbiInfo::variant_static(&[<() as ComponentType>::ABI, T::ABI]);
fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()> {
match ty {
InterfaceType::Option(t) => T::typecheck(&types[*t], types),
InterfaceType::Option(t) => T::typecheck(&types[*t].ty, types),
other => bail!("expected `option` found `{}`", desc(other)),
}
}
}
unsafe impl<T> ComponentVariant for Option<T>
where
T: ComponentType,
{
const CASES: &'static [CanonicalAbiInfo] = &[<() as ComponentType>::ABI, T::ABI];
}
unsafe impl<T> Lower for Option<T>
where
T: Lower,
@@ -1635,7 +1614,7 @@ where
}
Some(val) => {
mem.get::<1>(offset)[0] = 1;
val.store(mem, offset + align_to(1, T::ALIGN32))?;
val.store(mem, offset + (Self::INFO.payload_offset32 as usize))?;
}
}
Ok(())
@@ -1657,7 +1636,7 @@ where
fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
debug_assert!((bytes.as_ptr() as usize) % (Self::ALIGN32 as usize) == 0);
let discrim = bytes[0];
let payload = &bytes[align_to(1, T::ALIGN32)..];
let payload = &bytes[Self::INFO.payload_offset32 as usize..];
match discrim {
0 => Ok(None),
1 => Ok(Some(T::load(memory, payload)?)),
@@ -1687,17 +1666,7 @@ where
{
type Lower = ResultLower<T::Lower, E::Lower>;
const SIZE32: usize = align_to(1, Self::ALIGN32)
+ if T::SIZE32 > E::SIZE32 {
T::SIZE32
} else {
E::SIZE32
};
const ALIGN32: u32 = if T::ALIGN32 > E::ALIGN32 {
T::ALIGN32
} else {
E::ALIGN32
};
const ABI: CanonicalAbiInfo = CanonicalAbiInfo::variant_static(&[T::ABI, E::ABI]);
fn typecheck(ty: &InterfaceType, types: &ComponentTypes) -> Result<()> {
match ty {
@@ -1712,6 +1681,14 @@ where
}
}
unsafe impl<T, E> ComponentVariant for Result<T, E>
where
T: ComponentType,
E: ComponentType,
{
const CASES: &'static [CanonicalAbiInfo] = &[T::ABI, E::ABI];
}
unsafe impl<T, E> Lower for Result<T, E>
where
T: Lower,
@@ -1756,14 +1733,15 @@ where
fn store<U>(&self, mem: &mut MemoryMut<'_, U>, offset: usize) -> Result<()> {
debug_assert!(offset % (Self::ALIGN32 as usize) == 0);
let payload_offset = Self::INFO.payload_offset32 as usize;
match self {
Ok(e) => {
mem.get::<1>(offset)[0] = 0;
e.store(mem, offset + align_to(1, Self::ALIGN32))?;
e.store(mem, offset + payload_offset)?;
}
Err(e) => {
mem.get::<1>(offset)[0] = 1;
e.store(mem, offset + align_to(1, Self::ALIGN32))?;
e.store(mem, offset + payload_offset)?;
}
}
Ok(())
@@ -1804,9 +1782,8 @@ where
fn load(memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
debug_assert!((bytes.as_ptr() as usize) % (Self::ALIGN32 as usize) == 0);
let align = Self::ALIGN32;
let discrim = bytes[0];
let payload = &bytes[align_to(1, align)..];
let payload = &bytes[Self::INFO.payload_offset32 as usize..];
match discrim {
0 => Ok(Ok(T::load(memory, &payload[..T::SIZE32])?)),
1 => Ok(Err(E::load(memory, &payload[..E::SIZE32])?)),
@@ -1832,22 +1809,9 @@ macro_rules! impl_component_ty_for_tuples {
{
type Lower = [<TupleLower$n>]<$($t::Lower),*>;
const SIZE32: usize = {
let mut _size = 0;
$(
_size = align_to(_size, $t::ALIGN32);
_size += $t::SIZE32;
)*
align_to(_size, Self::ALIGN32)
};
const ALIGN32: u32 = {
let mut _align = 1;
$(if $t::ALIGN32 > _align {
_align = $t::ALIGN32;
})*
_align
};
const ABI: CanonicalAbiInfo = CanonicalAbiInfo::record_static(&[
$($t::ABI),*
]);
fn typecheck(
ty: &InterfaceType,
@@ -1875,7 +1839,7 @@ macro_rules! impl_component_ty_for_tuples {
fn store<U>(&self, _memory: &mut MemoryMut<'_, U>, mut _offset: usize) -> Result<()> {
debug_assert!(_offset % (Self::ALIGN32 as usize) == 0);
let ($($t,)*) = self;
$($t.store(_memory, next_field::<$t>(&mut _offset))?;)*
$($t.store(_memory, $t::ABI.next_field32_size(&mut _offset))?;)*
Ok(())
}
}
@@ -1891,7 +1855,7 @@ macro_rules! impl_component_ty_for_tuples {
fn load(_memory: &Memory<'_>, bytes: &[u8]) -> Result<Self> {
debug_assert!((bytes.as_ptr() as usize) % (Self::ALIGN32 as usize) == 0);
let mut _offset = 0;
$(let $t = $t::load(_memory, &bytes[next_field::<$t>(&mut _offset)..][..$t::SIZE32])?;)*
$(let $t = $t::load(_memory, &bytes[$t::ABI.next_field32_size(&mut _offset)..][..$t::SIZE32])?;)*
Ok(($($t,)*))
}
}

View File

@@ -28,14 +28,14 @@ pub use wasmtime_component_macro::{flags, ComponentType, Lift, Lower};
#[doc(hidden)]
pub mod __internal {
pub use super::func::{
align_to, format_flags, next_field, typecheck_enum, typecheck_flags, typecheck_record,
typecheck_union, typecheck_variant, MaybeUninitExt, Memory, MemoryMut, Options,
format_flags, typecheck_enum, typecheck_flags, typecheck_record, typecheck_union,
typecheck_variant, ComponentVariant, MaybeUninitExt, Memory, MemoryMut, Options,
};
pub use crate::map_maybe_uninit;
pub use crate::store::StoreOpaque;
pub use anyhow;
pub use wasmtime_environ;
pub use wasmtime_environ::component::{ComponentTypes, InterfaceType};
pub use wasmtime_environ::component::{CanonicalAbiInfo, ComponentTypes, InterfaceType};
}
pub(crate) use self::store::ComponentStoreData;

View File

@@ -1,16 +1,15 @@
//! This module defines the `Type` type, representing the dynamic form of a component interface type.
use crate::component::func;
use crate::component::values::{self, Val};
use anyhow::{anyhow, Result};
use std::fmt;
use std::mem;
use std::ops::Deref;
use std::sync::Arc;
use wasmtime_component_util::{DiscriminantSize, FlagsSize};
use wasmtime_environ::component::{
ComponentTypes, InterfaceType, TypeEnumIndex, TypeExpectedIndex, TypeFlagsIndex,
TypeInterfaceIndex, TypeRecordIndex, TypeTupleIndex, TypeUnionIndex, TypeVariantIndex,
CanonicalAbiInfo, ComponentTypes, InterfaceType, TypeEnumIndex, TypeExpectedIndex,
TypeFlagsIndex, TypeInterfaceIndex, TypeOptionIndex, TypeRecordIndex, TypeTupleIndex,
TypeUnionIndex, TypeVariantIndex, VariantInfo,
};
#[derive(Clone)]
@@ -125,6 +124,10 @@ impl Variant {
ty: Type::from(&case.ty, &self.0.types),
})
}
pub(crate) fn variant_info(&self) -> &VariantInfo {
&self.0.types[self.0.index].info
}
}
/// An `enum` interface type
@@ -144,6 +147,10 @@ impl Enum {
.iter()
.map(|name| name.deref())
}
pub(crate) fn variant_info(&self) -> &VariantInfo {
&self.0.types[self.0.index].info
}
}
/// A `union` interface type
@@ -163,11 +170,15 @@ impl Union {
.iter()
.map(|ty| Type::from(ty, &self.0.types))
}
pub(crate) fn variant_info(&self) -> &VariantInfo {
&self.0.types[self.0.index].info
}
}
/// An `option` interface type
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Option(Handle<TypeInterfaceIndex>);
pub struct Option(Handle<TypeOptionIndex>);
impl Option {
/// Instantiate this type with the specified `value`.
@@ -177,7 +188,11 @@ impl Option {
/// Retrieve the type parameter for this `option`.
pub fn ty(&self) -> Type {
Type::from(&self.0.types[self.0.index], &self.0.types)
Type::from(&self.0.types[self.0.index].ty, &self.0.types)
}
pub(crate) fn variant_info(&self) -> &VariantInfo {
&self.0.types[self.0.index].info
}
}
@@ -200,6 +215,10 @@ impl Expected {
pub fn err(&self) -> Type {
Type::from(&self.0.types[self.0.index].err, &self.0.types)
}
pub(crate) fn variant_info(&self) -> &VariantInfo {
&self.0.types[self.0.index].info
}
}
/// A `flags` interface type
@@ -221,13 +240,6 @@ impl Flags {
}
}
/// Represents the size and alignment requirements of the heap-serialized form of a type
#[derive(Debug)]
pub(crate) struct SizeAndAlignment {
pub(crate) size: usize,
pub(crate) alignment: u32,
}
/// Represents a component model interface type
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum Type {
@@ -554,119 +566,22 @@ impl Type {
}
/// Calculate the size and alignment requirements for the specified type.
pub(crate) fn size_and_alignment(&self) -> SizeAndAlignment {
pub(crate) fn canonical_abi(&self) -> &CanonicalAbiInfo {
match self {
Type::Unit => SizeAndAlignment {
size: 0,
alignment: 1,
},
Type::Bool | Type::S8 | Type::U8 => SizeAndAlignment {
size: 1,
alignment: 1,
},
Type::S16 | Type::U16 => SizeAndAlignment {
size: 2,
alignment: 2,
},
Type::S32 | Type::U32 | Type::Char | Type::Float32 => SizeAndAlignment {
size: 4,
alignment: 4,
},
Type::S64 | Type::U64 | Type::Float64 => SizeAndAlignment {
size: 8,
alignment: 8,
},
Type::String | Type::List(_) => SizeAndAlignment {
size: 8,
alignment: 4,
},
Type::Record(handle) => {
record_size_and_alignment(handle.fields().map(|field| field.ty))
}
Type::Tuple(handle) => record_size_and_alignment(handle.types()),
Type::Variant(handle) => variant_size_and_alignment(handle.cases().map(|case| case.ty)),
Type::Enum(handle) => variant_size_and_alignment(handle.names().map(|_| Type::Unit)),
Type::Union(handle) => variant_size_and_alignment(handle.types()),
Type::Option(handle) => {
variant_size_and_alignment([Type::Unit, handle.ty()].into_iter())
}
Type::Expected(handle) => {
variant_size_and_alignment([handle.ok(), handle.err()].into_iter())
}
Type::Flags(handle) => match FlagsSize::from_count(handle.names().len()) {
FlagsSize::Size0 => SizeAndAlignment {
size: 0,
alignment: 1,
},
FlagsSize::Size1 => SizeAndAlignment {
size: 1,
alignment: 1,
},
FlagsSize::Size2 => SizeAndAlignment {
size: 2,
alignment: 2,
},
FlagsSize::Size4Plus(n) => SizeAndAlignment {
size: n * 4,
alignment: 4,
},
},
Type::Unit => &CanonicalAbiInfo::ZERO,
Type::Bool | Type::S8 | Type::U8 => &CanonicalAbiInfo::SCALAR1,
Type::S16 | Type::U16 => &CanonicalAbiInfo::SCALAR2,
Type::S32 | Type::U32 | Type::Char | Type::Float32 => &CanonicalAbiInfo::SCALAR4,
Type::S64 | Type::U64 | Type::Float64 => &CanonicalAbiInfo::SCALAR8,
Type::String | Type::List(_) => &CanonicalAbiInfo::POINTER_PAIR,
Type::Record(handle) => &handle.0.types[handle.0.index].abi,
Type::Tuple(handle) => &handle.0.types[handle.0.index].abi,
Type::Variant(handle) => &handle.0.types[handle.0.index].abi,
Type::Enum(handle) => &handle.0.types[handle.0.index].abi,
Type::Union(handle) => &handle.0.types[handle.0.index].abi,
Type::Option(handle) => &handle.0.types[handle.0.index].abi,
Type::Expected(handle) => &handle.0.types[handle.0.index].abi,
Type::Flags(handle) => &handle.0.types[handle.0.index].abi,
}
}
/// Calculate the aligned offset of a field of this type, updating `offset` to point to just after that field.
pub(crate) fn next_field(&self, offset: &mut usize) -> usize {
let SizeAndAlignment { size, alignment } = self.size_and_alignment();
*offset = func::align_to(*offset, alignment);
let result = *offset;
*offset += size;
result
}
}
fn record_size_and_alignment(types: impl Iterator<Item = Type>) -> SizeAndAlignment {
let mut offset = 0;
let mut align = 1;
for ty in types {
let SizeAndAlignment { size, alignment } = ty.size_and_alignment();
offset = func::align_to(offset, alignment) + size;
align = align.max(alignment);
}
SizeAndAlignment {
size: func::align_to(offset, align),
alignment: align,
}
}
fn variant_size_and_alignment(types: impl ExactSizeIterator<Item = Type>) -> SizeAndAlignment {
let discriminant_size = DiscriminantSize::from_count(types.len()).unwrap();
let mut alignment = u32::from(discriminant_size);
let mut size = 0;
for ty in types {
let size_and_alignment = ty.size_and_alignment();
alignment = alignment.max(size_and_alignment.alignment);
size = size.max(size_and_alignment.size);
}
SizeAndAlignment {
size: func::align_to(
func::align_to(usize::from(discriminant_size), alignment) + size,
alignment,
),
alignment,
}
}

View File

@@ -1,5 +1,5 @@
use crate::component::func::{self, Lift, Lower, Memory, MemoryMut, Options};
use crate::component::types::{self, SizeAndAlignment, Type};
use crate::component::func::{Lift, Lower, Memory, MemoryMut, Options};
use crate::component::types::{self, Type};
use crate::store::StoreOpaque;
use crate::{AsContextMut, StoreContextMut, ValRaw};
use anyhow::{anyhow, bail, Context, Error, Result};
@@ -9,6 +9,7 @@ use std::iter;
use std::mem::MaybeUninit;
use std::ops::Deref;
use wasmtime_component_util::{DiscriminantSize, FlagsSize};
use wasmtime_environ::component::VariantInfo;
#[derive(PartialEq, Eq, Clone)]
pub struct List {
@@ -700,8 +701,12 @@ impl Val {
values: load_record(handle.types(), mem, bytes)?,
}),
Type::Variant(handle) => {
let (discriminant, value) =
load_variant(ty, handle.cases().map(|case| case.ty), mem, bytes)?;
let (discriminant, value) = load_variant(
handle.variant_info(),
handle.cases().map(|case| case.ty),
mem,
bytes,
)?;
Val::Variant(Variant {
ty: handle.clone(),
@@ -710,8 +715,12 @@ impl Val {
})
}
Type::Enum(handle) => {
let (discriminant, _) =
load_variant(ty, handle.names().map(|_| Type::Unit), mem, bytes)?;
let (discriminant, _) = load_variant(
handle.variant_info(),
handle.names().map(|_| Type::Unit),
mem,
bytes,
)?;
Val::Enum(Enum {
ty: handle.clone(),
@@ -719,7 +728,8 @@ impl Val {
})
}
Type::Union(handle) => {
let (discriminant, value) = load_variant(ty, handle.types(), mem, bytes)?;
let (discriminant, value) =
load_variant(handle.variant_info(), handle.types(), mem, bytes)?;
Val::Union(Union {
ty: handle.clone(),
@@ -728,8 +738,12 @@ impl Val {
})
}
Type::Option(handle) => {
let (discriminant, value) =
load_variant(ty, [Type::Unit, handle.ty()].into_iter(), mem, bytes)?;
let (discriminant, value) = load_variant(
handle.variant_info(),
[Type::Unit, handle.ty()].into_iter(),
mem,
bytes,
)?;
Val::Option(Option {
ty: handle.clone(),
@@ -738,8 +752,12 @@ impl Val {
})
}
Type::Expected(handle) => {
let (discriminant, value) =
load_variant(ty, [handle.ok(), handle.err()].into_iter(), mem, bytes)?;
let (discriminant, value) = load_variant(
handle.variant_info(),
[handle.ok(), handle.err()].into_iter(),
mem,
bytes,
)?;
Val::Expected(Expected {
ty: handle.clone(),
@@ -845,7 +863,7 @@ impl Val {
/// Serialize this value to the heap at the specified memory location.
pub(crate) fn store<T>(&self, mem: &mut MemoryMut<'_, T>, offset: usize) -> Result<()> {
debug_assert!(offset % usize::try_from(self.ty().size_and_alignment().alignment)? == 0);
debug_assert!(offset % usize::try_from(self.ty().canonical_abi().align32)? == 0);
match self {
Val::Unit => (),
@@ -871,35 +889,39 @@ impl Val {
Val::Record(Record { values, .. }) | Val::Tuple(Tuple { values, .. }) => {
let mut offset = offset;
for value in values.deref() {
value.store(mem, value.ty().next_field(&mut offset))?;
value.store(
mem,
value.ty().canonical_abi().next_field32_size(&mut offset),
)?;
}
}
Val::Variant(Variant {
discriminant,
value,
ty,
}) => self.store_variant(*discriminant, value, ty.cases().len(), mem, offset)?,
}) => self.store_variant(*discriminant, value, ty.variant_info(), mem, offset)?,
Val::Enum(Enum { discriminant, ty }) => {
self.store_variant(*discriminant, &Val::Unit, ty.names().len(), mem, offset)?
self.store_variant(*discriminant, &Val::Unit, ty.variant_info(), mem, offset)?
}
Val::Union(Union {
discriminant,
value,
ty,
}) => self.store_variant(*discriminant, value, ty.types().len(), mem, offset)?,
}) => self.store_variant(*discriminant, value, ty.variant_info(), mem, offset)?,
Val::Option(Option {
discriminant,
value,
..
})
| Val::Expected(Expected {
ty,
}) => self.store_variant(*discriminant, value, ty.variant_info(), mem, offset)?,
Val::Expected(Expected {
discriminant,
value,
..
}) => self.store_variant(*discriminant, value, 2, mem, offset)?,
ty,
}) => self.store_variant(*discriminant, value, ty.variant_info(), mem, offset)?,
Val::Flags(Flags { count, value, .. }) => {
match FlagsSize::from_count(*count as usize) {
@@ -924,34 +946,26 @@ impl Val {
&self,
discriminant: u32,
value: &Val,
case_count: usize,
info: &VariantInfo,
mem: &mut MemoryMut<'_, T>,
offset: usize,
) -> Result<()> {
let discriminant_size = DiscriminantSize::from_count(case_count).unwrap();
match discriminant_size {
match info.size {
DiscriminantSize::Size1 => u8::try_from(discriminant).unwrap().store(mem, offset)?,
DiscriminantSize::Size2 => u16::try_from(discriminant).unwrap().store(mem, offset)?,
DiscriminantSize::Size4 => (discriminant).store(mem, offset)?,
DiscriminantSize::Size4 => discriminant.store(mem, offset)?,
}
value.store(
mem,
offset
+ func::align_to(
discriminant_size.into(),
self.ty().size_and_alignment().alignment,
),
)
let offset = offset + usize::try_from(info.payload_offset32).unwrap();
value.store(mem, offset)
}
}
fn load_list(handle: &types::List, mem: &Memory, ptr: usize, len: usize) -> Result<Val> {
let element_type = handle.ty();
let SizeAndAlignment {
size: element_size,
alignment: element_alignment,
} = element_type.size_and_alignment();
let abi = element_type.canonical_abi();
let element_size = usize::try_from(abi.size32).unwrap();
let element_alignment = abi.align32;
match len
.checked_mul(element_size)
@@ -986,25 +1000,24 @@ fn load_record(
let mut offset = 0;
types
.map(|ty| {
Val::load(
&ty,
mem,
&bytes[ty.next_field(&mut offset)..][..ty.size_and_alignment().size],
)
let abi = ty.canonical_abi();
let offset = abi.next_field32(&mut offset);
let offset = usize::try_from(offset).unwrap();
let size = usize::try_from(abi.size32).unwrap();
Val::load(&ty, mem, &bytes[offset..][..size])
})
.collect()
}
fn load_variant(
ty: &Type,
info: &VariantInfo,
mut types: impl ExactSizeIterator<Item = Type>,
mem: &Memory,
bytes: &[u8],
) -> Result<(u32, Val)> {
let discriminant_size = DiscriminantSize::from_count(types.len()).unwrap();
let discriminant = match discriminant_size {
DiscriminantSize::Size1 => u8::load(mem, &bytes[..1])? as u32,
DiscriminantSize::Size2 => u16::load(mem, &bytes[..2])? as u32,
let discriminant = match info.size {
DiscriminantSize::Size1 => u32::from(u8::load(mem, &bytes[..1])?),
DiscriminantSize::Size2 => u32::from(u16::load(mem, &bytes[..2])?),
DiscriminantSize::Size4 => u32::load(mem, &bytes[..4])?,
};
let case_ty = types.nth(discriminant as usize).ok_or_else(|| {
@@ -1014,14 +1027,9 @@ fn load_variant(
types.len()
)
})?;
let value = Val::load(
&case_ty,
mem,
&bytes[func::align_to(
usize::from(discriminant_size),
ty.size_and_alignment().alignment,
)..][..case_ty.size_and_alignment().size],
)?;
let payload_offset = usize::try_from(info.payload_offset32).unwrap();
let case_size = usize::try_from(case_ty.canonical_abi().size32).unwrap();
let value = Val::load(&case_ty, mem, &bytes[payload_offset..][..case_size])?;
Ok((discriminant, value))
}
@@ -1050,19 +1058,18 @@ fn lower_list<T>(
mem: &mut MemoryMut<'_, T>,
items: &[Val],
) -> Result<(usize, usize)> {
let SizeAndAlignment {
size: element_size,
alignment: element_alignment,
} = element_type.size_and_alignment();
let abi = element_type.canonical_abi();
let elt_size = usize::try_from(abi.size32)?;
let elt_align = abi.align32;
let size = items
.len()
.checked_mul(element_size)
.checked_mul(elt_size)
.ok_or_else(|| anyhow::anyhow!("size overflow copying a list"))?;
let ptr = mem.realloc(0, 0, element_alignment, size)?;
let ptr = mem.realloc(0, 0, elt_align, size)?;
let mut element_ptr = ptr;
for item in items {
item.store(mem, element_ptr)?;
element_ptr += element_size;
element_ptr += elt_size;
}
Ok((ptr, items.len()))
}