Start drafting out flags datatype generation (#11)
* Adds support for flags datatype This commit adds support for `FlagsDatatype`. In WASI, flags are represented as bitfields/bitflags, therefore, it is important for the type to have bitwise operations implemented, and some way of checking whether the current set of flags contains some other set (i.e., they intersect). Thus, this commit automatically derives `BitAnd`, etc. for the derived flags datatype. It also automatically provides an `ALL_FLAGS` value which corresponds to a bitwise-or of all flag values present and is provided for convenience. * Simplify read_from_guest
This commit is contained in:
@@ -153,6 +153,7 @@ fn marshal_arg(
|
||||
|
||||
match &*tref.type_() {
|
||||
witx::Type::Enum(_e) => try_into_conversion,
|
||||
witx::Type::Flags(_f) => try_into_conversion,
|
||||
witx::Type::Builtin(b) => match b {
|
||||
witx::BuiltinType::U8 | witx::BuiltinType::U16 | witx::BuiltinType::Char8 => {
|
||||
try_into_conversion
|
||||
@@ -329,7 +330,7 @@ where
|
||||
| witx::BuiltinType::Char8 => write_val_to_ptr,
|
||||
witx::BuiltinType::String => unimplemented!("string types"),
|
||||
},
|
||||
witx::Type::Enum(_e) => write_val_to_ptr,
|
||||
witx::Type::Enum(_) | witx::Type::Flags(_) => write_val_to_ptr,
|
||||
_ => unimplemented!("marshal result"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use heck::{CamelCase, SnakeCase};
|
||||
use heck::{CamelCase, ShoutySnakeCase, SnakeCase};
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use witx::{AtomType, BuiltinType, Id, TypeRef};
|
||||
@@ -82,6 +82,10 @@ impl Names {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flag_member(&self, id: &Id) -> Ident {
|
||||
format_ident!("{}", id.as_str().to_shouty_snake_case())
|
||||
}
|
||||
|
||||
pub fn struct_member(&self, id: &Id) -> Ident {
|
||||
format_ident!("{}", id.as_str().to_snake_case())
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::names::Names;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use proc_macro2::{Literal, TokenStream};
|
||||
use quote::quote;
|
||||
use std::convert::TryFrom;
|
||||
use witx::Layout;
|
||||
|
||||
pub fn define_datatype(names: &Names, namedtype: &witx::NamedType) -> TokenStream {
|
||||
@@ -10,7 +11,7 @@ pub fn define_datatype(names: &Names, namedtype: &witx::NamedType) -> TokenStrea
|
||||
witx::TypeRef::Value(v) => match &**v {
|
||||
witx::Type::Enum(e) => define_enum(names, &namedtype.name, &e),
|
||||
witx::Type::Int(_) => unimplemented!("int types"),
|
||||
witx::Type::Flags(_) => unimplemented!("flag types"),
|
||||
witx::Type::Flags(f) => define_flags(names, &namedtype.name, &f),
|
||||
witx::Type::Struct(s) => {
|
||||
if struct_is_copy(s) {
|
||||
define_copy_struct(names, &namedtype.name, &s)
|
||||
@@ -45,6 +46,151 @@ fn define_alias(names: &Names, name: &witx::Id, to: &witx::NamedType) -> TokenSt
|
||||
}
|
||||
}
|
||||
|
||||
fn define_flags(names: &Names, name: &witx::Id, f: &witx::FlagsDatatype) -> TokenStream {
|
||||
let ident = names.type_(&name);
|
||||
let repr = int_repr_tokens(f.repr);
|
||||
let abi_repr = atom_token(match f.repr {
|
||||
witx::IntRepr::U8 | witx::IntRepr::U16 | witx::IntRepr::U32 => witx::AtomType::I32,
|
||||
witx::IntRepr::U64 => witx::AtomType::I64,
|
||||
});
|
||||
|
||||
let mut flag_constructors = vec![];
|
||||
let mut all_values = 0;
|
||||
for (i, f) in f.flags.iter().enumerate() {
|
||||
let name = names.flag_member(&f.name);
|
||||
let value = 1u128
|
||||
.checked_shl(u32::try_from(i).expect("flag value overflow"))
|
||||
.expect("flag value overflow");
|
||||
let value_token = Literal::u128_unsuffixed(value);
|
||||
flag_constructors.push(quote!(pub const #name: #ident = #ident(#value_token)));
|
||||
all_values += value;
|
||||
}
|
||||
let all_values_token = Literal::u128_unsuffixed(all_values);
|
||||
|
||||
quote! {
|
||||
#[repr(transparent)]
|
||||
#[derive(Copy, Clone, Debug, ::std::hash::Hash, Eq, PartialEq)]
|
||||
pub struct #ident(#repr);
|
||||
|
||||
impl #ident {
|
||||
#(#flag_constructors);*;
|
||||
pub const ALL_FLAGS: #ident = #ident(#all_values_token);
|
||||
|
||||
pub fn contains(&self, other: &#ident) -> bool {
|
||||
#repr::from(!*self & *other) == 0 as #repr
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::BitAnd for #ident {
|
||||
type Output = Self;
|
||||
fn bitand(self, rhs: Self) -> Self::Output {
|
||||
#ident(self.0 & rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::BitAndAssign for #ident {
|
||||
fn bitand_assign(&mut self, rhs: Self) {
|
||||
*self = *self & rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::BitOr for #ident {
|
||||
type Output = Self;
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
#ident(self.0 | rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::BitOrAssign for #ident {
|
||||
fn bitor_assign(&mut self, rhs: Self) {
|
||||
*self = *self | rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::BitXor for #ident {
|
||||
type Output = Self;
|
||||
fn bitxor(self, rhs: Self) -> Self::Output {
|
||||
#ident(self.0 ^ rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::BitXorAssign for #ident {
|
||||
fn bitxor_assign(&mut self, rhs: Self) {
|
||||
*self = *self ^ rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::ops::Not for #ident {
|
||||
type Output = Self;
|
||||
fn not(self) -> Self::Output {
|
||||
#ident(!self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::convert::TryFrom<#repr> for #ident {
|
||||
type Error = wiggle_runtime::GuestError;
|
||||
fn try_from(value: #repr) -> Result<Self, wiggle_runtime::GuestError> {
|
||||
if #repr::from(!#ident::ALL_FLAGS) & value != 0 {
|
||||
Err(wiggle_runtime::GuestError::InvalidFlagValue(stringify!(#ident)))
|
||||
} else {
|
||||
Ok(#ident(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ::std::convert::TryFrom<#abi_repr> for #ident {
|
||||
type Error = wiggle_runtime::GuestError;
|
||||
fn try_from(value: #abi_repr) -> Result<#ident, wiggle_runtime::GuestError> {
|
||||
#ident::try_from(value as #repr)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<#ident> for #repr {
|
||||
fn from(e: #ident) -> #repr {
|
||||
e.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<#ident> for #abi_repr {
|
||||
fn from(e: #ident) -> #abi_repr {
|
||||
#repr::from(e) as #abi_repr
|
||||
}
|
||||
}
|
||||
|
||||
impl wiggle_runtime::GuestType for #ident {
|
||||
fn size() -> u32 {
|
||||
::std::mem::size_of::<#repr>() as u32
|
||||
}
|
||||
|
||||
fn align() -> u32 {
|
||||
::std::mem::align_of::<#repr>() as u32
|
||||
}
|
||||
|
||||
fn name() -> String {
|
||||
stringify!(#ident).to_owned()
|
||||
}
|
||||
|
||||
fn validate<'a>(location: &wiggle_runtime::GuestPtr<'a, #ident>) -> Result<(), wiggle_runtime::GuestError> {
|
||||
use ::std::convert::TryFrom;
|
||||
let raw: #repr = unsafe { (location.as_raw() as *const #repr).read() };
|
||||
let _ = #ident::try_from(raw)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl wiggle_runtime::GuestTypeCopy for #ident {}
|
||||
impl wiggle_runtime::GuestTypeClone for #ident {
|
||||
fn read_from_guest(location: &wiggle_runtime::GuestPtr<#ident>) -> Result<#ident, wiggle_runtime::GuestError> {
|
||||
Ok(*location.as_ref()?)
|
||||
}
|
||||
fn write_to_guest(&self, location: &wiggle_runtime::GuestPtrMut<#ident>) {
|
||||
let val: #repr = #repr::from(*self);
|
||||
unsafe { (location.as_raw() as *mut #repr).write(val) };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn define_enum(names: &Names, name: &witx::Id, e: &witx::EnumDatatype) -> TokenStream {
|
||||
let ident = names.type_(&name);
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error, PartialEq, Eq)]
|
||||
pub enum GuestError {
|
||||
#[error("Invalid flag value {0}")]
|
||||
InvalidFlagValue(&'static str),
|
||||
#[error("Invalid enum value {0}")]
|
||||
InvalidEnumValue(&'static str),
|
||||
#[error("Pointer out of bounds: {0:?}")]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use proptest::prelude::*;
|
||||
use std::convert::TryFrom;
|
||||
use wiggle_runtime::{
|
||||
GuestArray, GuestError, GuestErrorType, GuestMemory, GuestPtr, GuestPtrMut, GuestRef,
|
||||
GuestRefMut,
|
||||
@@ -121,6 +122,18 @@ impl foo::Foo for WasiCtx {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn configure_car(
|
||||
&mut self,
|
||||
old_config: types::CarConfig,
|
||||
other_config_ptr: GuestPtr<types::CarConfig>,
|
||||
) -> Result<types::CarConfig, types::Errno> {
|
||||
let other_config = *other_config_ptr.as_ref().map_err(|e| {
|
||||
eprintln!("old_config_ptr error: {}", e);
|
||||
types::Errno::InvalidArg
|
||||
})?;
|
||||
Ok(old_config ^ other_config)
|
||||
}
|
||||
}
|
||||
// Errno is used as a first return value in the functions above, therefore
|
||||
// it must implement GuestErrorType with type Context = WasiCtx.
|
||||
@@ -774,3 +787,82 @@ proptest! {
|
||||
e.test()
|
||||
}
|
||||
}
|
||||
|
||||
fn car_config_strat() -> impl Strategy<Value = types::CarConfig> {
|
||||
(1u8..=types::CarConfig::ALL_FLAGS.into())
|
||||
.prop_map(|v| {
|
||||
types::CarConfig::try_from(v).expect("invalid value for types::CarConfig flag")
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ConfigureCarExercise {
|
||||
old_config: types::CarConfig,
|
||||
other_config: types::CarConfig,
|
||||
other_config_by_ptr: MemArea,
|
||||
return_ptr_loc: MemArea,
|
||||
}
|
||||
|
||||
impl ConfigureCarExercise {
|
||||
pub fn strat() -> BoxedStrategy<Self> {
|
||||
(
|
||||
car_config_strat(),
|
||||
car_config_strat(),
|
||||
HostMemory::mem_area_strat(4),
|
||||
HostMemory::mem_area_strat(4),
|
||||
)
|
||||
.prop_map(
|
||||
|(old_config, other_config, other_config_by_ptr, return_ptr_loc)| Self {
|
||||
old_config,
|
||||
other_config,
|
||||
other_config_by_ptr,
|
||||
return_ptr_loc,
|
||||
},
|
||||
)
|
||||
.prop_filter("non-overlapping ptrs", |e| {
|
||||
non_overlapping_set(&[&e.other_config_by_ptr, &e.return_ptr_loc])
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
pub fn test(&self) {
|
||||
let mut ctx = WasiCtx::new();
|
||||
let mut host_memory = HostMemory::new();
|
||||
let mut guest_memory = GuestMemory::new(host_memory.as_mut_ptr(), host_memory.len() as u32);
|
||||
|
||||
// Populate input ptr
|
||||
*guest_memory
|
||||
.ptr_mut(self.other_config_by_ptr.ptr)
|
||||
.expect("ptr mut to CarConfig")
|
||||
.as_ref_mut()
|
||||
.expect("deref ptr mut to CarConfig") = self.other_config;
|
||||
|
||||
let res = foo::configure_car(
|
||||
&mut ctx,
|
||||
&mut guest_memory,
|
||||
self.old_config.into(),
|
||||
self.other_config_by_ptr.ptr as i32,
|
||||
self.return_ptr_loc.ptr as i32,
|
||||
);
|
||||
assert_eq!(res, types::Errno::Ok.into(), "configure car errno");
|
||||
|
||||
let res_config = *guest_memory
|
||||
.ptr::<types::CarConfig>(self.return_ptr_loc.ptr)
|
||||
.expect("ptr to returned CarConfig")
|
||||
.as_ref()
|
||||
.expect("deref to CarConfig value");
|
||||
|
||||
assert_eq!(
|
||||
self.old_config ^ self.other_config,
|
||||
res_config,
|
||||
"returned CarConfig should be an XOR of inputs"
|
||||
);
|
||||
}
|
||||
}
|
||||
proptest! {
|
||||
#[test]
|
||||
fn configure_car(e in ConfigureCarExercise::strat()) {
|
||||
e.test()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
$traffic
|
||||
$sleeping))
|
||||
|
||||
(typename $car_config
|
||||
(flags u8
|
||||
$automatic
|
||||
$awd
|
||||
$suv))
|
||||
|
||||
(typename $pair_ints
|
||||
(struct
|
||||
(field $first s32)
|
||||
@@ -60,4 +66,10 @@
|
||||
(param $excuses $excuse_array)
|
||||
(result $error $errno)
|
||||
)
|
||||
(@interface func (export "configure_car")
|
||||
(param $old_config $car_config)
|
||||
(param $old_config_by_ptr (@witx const_pointer $car_config))
|
||||
(result $error $errno)
|
||||
(result $new_config $car_config)
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user