reorganize configuration into modules

This commit is contained in:
Pat Hickey
2020-06-23 17:42:04 -07:00
parent 69f81397a8
commit f66c1fbde9
3 changed files with 291 additions and 282 deletions

View File

@@ -1,5 +1,6 @@
use {
proc_macro2::{Span, TokenStream},
std::collections::HashMap,
syn::{
braced,
parse::{Parse, ParseStream},
@@ -14,9 +15,8 @@ pub struct Config {
pub target: TargetConf,
pub witx: WitxConf,
pub ctx: CtxConf,
pub instance: InstanceConf,
pub modules: ModulesConf,
pub missing_memory: MissingMemoryConf,
pub function_override: FunctionOverrideConf,
}
#[derive(Debug, Clone)]
@@ -24,9 +24,8 @@ pub enum ConfigField {
Target(TargetConf),
Witx(WitxConf),
Ctx(CtxConf),
Instance(InstanceConf),
Modules(ModulesConf),
MissingMemory(MissingMemoryConf),
FunctionOverride(FunctionOverrideConf),
}
mod kw {
@@ -34,7 +33,7 @@ mod kw {
syn::custom_keyword!(witx);
syn::custom_keyword!(witx_literal);
syn::custom_keyword!(ctx);
syn::custom_keyword!(instance);
syn::custom_keyword!(modules);
syn::custom_keyword!(name);
syn::custom_keyword!(docs);
syn::custom_keyword!(missing_memory);
@@ -60,18 +59,14 @@ impl Parse for ConfigField {
input.parse::<kw::ctx>()?;
input.parse::<Token![:]>()?;
Ok(ConfigField::Ctx(input.parse()?))
} else if lookahead.peek(kw::instance) {
input.parse::<kw::instance>()?;
} else if lookahead.peek(kw::modules) {
input.parse::<kw::modules>()?;
input.parse::<Token![:]>()?;
Ok(ConfigField::Instance(input.parse()?))
Ok(ConfigField::Modules(input.parse()?))
} else if lookahead.peek(kw::missing_memory) {
input.parse::<kw::missing_memory>()?;
input.parse::<Token![:]>()?;
Ok(ConfigField::MissingMemory(input.parse()?))
} else if lookahead.peek(kw::function_override) {
input.parse::<kw::function_override>()?;
input.parse::<Token![:]>()?;
Ok(ConfigField::FunctionOverride(input.parse()?))
} else {
Err(lookahead.error())
}
@@ -83,9 +78,8 @@ impl Config {
let mut target = None;
let mut witx = None;
let mut ctx = None;
let mut instance = None;
let mut modules = None;
let mut missing_memory = None;
let mut function_override = None;
for f in fields {
match f {
ConfigField::Target(c) => {
@@ -106,11 +100,11 @@ impl Config {
}
ctx = Some(c);
}
ConfigField::Instance(c) => {
if instance.is_some() {
return Err(Error::new(err_loc, "duplicate `instance` field"));
ConfigField::Modules(c) => {
if modules.is_some() {
return Err(Error::new(err_loc, "duplicate `modules` field"));
}
instance = Some(c);
modules = Some(c);
}
ConfigField::MissingMemory(c) => {
if missing_memory.is_some() {
@@ -118,31 +112,15 @@ impl Config {
}
missing_memory = Some(c);
}
ConfigField::FunctionOverride(c) => {
if function_override.is_some() {
return Err(Error::new(err_loc, "duplicate `function_override` field"));
}
function_override = Some(c);
}
}
}
Ok(Config {
target: target
.take()
.ok_or_else(|| Error::new(err_loc, "`target` field required"))?,
witx: witx
.take()
.ok_or_else(|| Error::new(err_loc, "`witx` field required"))?,
ctx: ctx
.take()
.ok_or_else(|| Error::new(err_loc, "`ctx` field required"))?,
instance: instance
.take()
.ok_or_else(|| Error::new(err_loc, "`instance` field required"))?,
target: target.ok_or_else(|| Error::new(err_loc, "`target` field required"))?,
witx: witx.ok_or_else(|| Error::new(err_loc, "`witx` field required"))?,
ctx: ctx.ok_or_else(|| Error::new(err_loc, "`ctx` field required"))?,
modules: modules.ok_or_else(|| Error::new(err_loc, "`modules` field required"))?,
missing_memory: missing_memory
.take()
.ok_or_else(|| Error::new(err_loc, "`missing_memory` field required"))?,
function_override: function_override.take().unwrap_or_default(),
})
}
@@ -179,23 +157,28 @@ impl Parse for TargetConf {
}
}
enum InstanceConfField {
enum ModuleConfField {
Name(Ident),
Docs(String),
FunctionOverride(FunctionOverrideConf),
}
impl Parse for InstanceConfField {
impl Parse for ModuleConfField {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::name) {
input.parse::<kw::name>()?;
input.parse::<Token![:]>()?;
Ok(InstanceConfField::Name(input.parse()?))
Ok(ModuleConfField::Name(input.parse()?))
} else if lookahead.peek(kw::docs) {
input.parse::<kw::docs>()?;
input.parse::<Token![:]>()?;
let docs: syn::LitStr = input.parse()?;
Ok(InstanceConfField::Docs(docs.value()))
Ok(ModuleConfField::Docs(docs.value()))
} else if lookahead.peek(kw::function_override) {
input.parse::<kw::function_override>()?;
input.parse::<Token![:]>()?;
Ok(ModuleConfField::FunctionOverride(input.parse()?))
} else {
Err(lookahead.error())
}
@@ -203,47 +186,82 @@ impl Parse for InstanceConfField {
}
#[derive(Debug, Clone)]
pub struct InstanceConf {
pub struct ModuleConf {
pub name: Ident,
pub docs: Option<String>,
pub function_override: FunctionOverrideConf,
}
impl InstanceConf {
fn build(fields: impl Iterator<Item = InstanceConfField>, err_loc: Span) -> Result<Self> {
impl ModuleConf {
fn build(fields: impl Iterator<Item = ModuleConfField>, err_loc: Span) -> Result<Self> {
let mut name = None;
let mut docs = None;
let mut function_override = None;
for f in fields {
match f {
InstanceConfField::Name(c) => {
ModuleConfField::Name(c) => {
if name.is_some() {
return Err(Error::new(err_loc, "duplicate `name` field"));
}
name = Some(c);
}
InstanceConfField::Docs(c) => {
ModuleConfField::Docs(c) => {
if docs.is_some() {
return Err(Error::new(err_loc, "duplicate `docs` field"));
}
docs = Some(c);
}
ModuleConfField::FunctionOverride(c) => {
if function_override.is_some() {
return Err(Error::new(err_loc, "duplicate `function_override` field"));
}
function_override = Some(c);
}
}
}
Ok(InstanceConf {
name: name
.take()
.ok_or_else(|| Error::new(err_loc, "`name` field required"))?,
Ok(ModuleConf {
name: name.ok_or_else(|| Error::new(err_loc, "`name` field required"))?,
docs,
function_override: function_override.unwrap_or_default(),
})
}
}
impl Parse for InstanceConf {
impl Parse for ModuleConf {
fn parse(input: ParseStream) -> Result<Self> {
let contents;
let _lbrace = braced!(contents in input);
let fields: Punctuated<InstanceConfField, Token![,]> =
contents.parse_terminated(InstanceConfField::parse)?;
Ok(InstanceConf::build(fields.into_iter(), input.span())?)
let fields: Punctuated<ModuleConfField, Token![,]> =
contents.parse_terminated(ModuleConfField::parse)?;
Ok(ModuleConf::build(fields.into_iter(), input.span())?)
}
}
#[derive(Debug, Clone)]
pub struct ModulesConf {
pub mods: HashMap<String, ModuleConf>,
}
impl ModulesConf {
pub fn iter(&self) -> impl Iterator<Item = (&String, &ModuleConf)> {
self.mods.iter()
}
}
impl Parse for ModulesConf {
fn parse(input: ParseStream) -> Result<Self> {
let contents;
let _lbrace = braced!(contents in input);
let fields: Punctuated<(String, ModuleConf), Token![,]> =
contents.parse_terminated(|i| {
let name = i.parse::<Ident>()?.to_string();
i.parse::<Token![=>]>()?;
let val = i.parse()?;
Ok((name, val))
})?;
Ok(ModulesConf {
mods: fields.into_iter().collect(),
})
}
}
@@ -266,10 +284,10 @@ pub struct FunctionOverrideConf {
pub funcs: Vec<FunctionOverrideField>,
}
impl FunctionOverrideConf {
pub fn find(&self, module: &str, field: &str) -> Option<&Ident> {
pub fn find(&self, name: &str) -> Option<&Ident> {
self.funcs
.iter()
.find(|f| f.module == module && f.field == field)
.find(|f| f.name == name)
.map(|f| &f.replacement)
}
}
@@ -288,21 +306,14 @@ impl Parse for FunctionOverrideConf {
#[derive(Debug, Clone)]
pub struct FunctionOverrideField {
pub module: String,
pub field: String,
pub name: String,
pub replacement: Ident,
}
impl Parse for FunctionOverrideField {
fn parse(input: ParseStream) -> Result<Self> {
let module = input.parse::<Ident>()?.to_string();
input.parse::<Token![:]>()?;
let field = input.parse::<Ident>()?.to_string();
let name = input.parse::<Ident>()?.to_string();
input.parse::<Token![=>]>()?;
let replacement = input.parse::<Ident>()?;
Ok(FunctionOverrideField {
module,
field,
replacement,
})
Ok(FunctionOverrideField { name, replacement })
}
}

View File

@@ -6,7 +6,7 @@ use wiggle_generate::Names;
mod config;
use config::{FunctionOverrideConf, InstanceConf, MissingMemoryConf, TargetConf};
use config::{MissingMemoryConf, ModuleConf, TargetConf};
#[proc_macro]
pub fn define_wasmtime_integration(args: TokenStream) -> TokenStream {
@@ -17,15 +17,19 @@ pub fn define_wasmtime_integration(args: TokenStream) -> TokenStream {
let doc = config.load_document();
let names = Names::new(&config.ctx.name, quote!(wasmtime_wiggle));
generate(
&doc,
&names,
&config.target,
&config.instance,
&config.missing_memory,
&config.function_override,
)
.into()
let modules = config.modules.iter().map(|(name, module_conf)| {
let module = doc
.module(&witx::Id::new(name))
.unwrap_or_else(|| panic!("witx document did not contain module named '{}'", name));
generate_module(
&module,
&module_conf,
&names,
&config.target,
&config.missing_memory,
)
});
quote!( #(#modules)* ).into()
}
enum Abi {
@@ -35,13 +39,12 @@ enum Abi {
F64,
}
fn generate(
doc: &witx::Document,
fn generate_module(
module: &witx::Module,
module_conf: &ModuleConf,
names: &Names,
target_conf: &TargetConf,
instance_conf: &InstanceConf,
missing_mem_conf: &MissingMemoryConf,
func_override_conf: &FunctionOverrideConf,
) -> TokenStream2 {
let mut fields = Vec::new();
let mut get_exports = Vec::new();
@@ -53,214 +56,208 @@ fn generate(
let target_path = &target_conf.path;
let missing_mem_err = &missing_mem_conf.err;
for module in doc.modules() {
let module_name = module.name.as_str();
let module_id = names.module(&module.name);
for func in module.funcs() {
let name = func.name.as_str();
let name_ident = names.func(&func.name);
fields.push(quote! { pub #name_ident: wasmtime::Func });
get_exports.push(quote! { #name => Some(&self.#name_ident) });
ctor_fields.push(name_ident.clone());
linker_add.push(quote! {
linker.define(#module_name, #name, self.#name_ident.clone())?;
});
if let Some(func_override) = func_override_conf.find(module_name, name) {
ctor_externs.push(quote! {
let #name_ident = wasmtime::Func::wrap(store, #func_override);
});
continue;
}
let mut shim_arg_decls = Vec::new();
let mut params = Vec::new();
let mut hostcall_args = Vec::new();
for param in func.params.iter() {
let name = names.func_param(&param.name);
// Registers a new parameter to the shim we're making with the
// given `name`, the `abi_ty` wasm type
//
// This will register a whole bunch of things:
//
// * The cranelift type for the parameter
// * Syntax to specify the actual function parameter
// * How to actually pass this argument to the host
// implementation, converting as necessary.
let mut add_param = |name: &Ident, abi_ty: Abi| {
match abi_ty {
Abi::I32 => {
params.push(quote! { types::I32 });
shim_arg_decls.push(quote! { #name: i32 });
}
Abi::I64 => {
params.push(quote! { types::I64 });
shim_arg_decls.push(quote! { #name: i64 });
}
Abi::F32 => {
params.push(quote! { types::F32 });
shim_arg_decls.push(quote! { #name: f32 });
}
Abi::F64 => {
params.push(quote! { types::F64 });
shim_arg_decls.push(quote! { #name: f64 });
}
}
hostcall_args.push(quote! { #name as _ });
};
match &*param.tref.type_() {
witx::Type::Int(e) => match e.repr {
witx::IntRepr::U64 => add_param(&name, Abi::I64),
_ => add_param(&name, Abi::I32),
},
witx::Type::Enum(e) => match e.repr {
witx::IntRepr::U64 => add_param(&name, Abi::I64),
_ => add_param(&name, Abi::I32),
},
witx::Type::Flags(f) => match f.repr {
witx::IntRepr::U64 => add_param(&name, Abi::I64),
_ => add_param(&name, Abi::I32),
},
witx::Type::Builtin(witx::BuiltinType::Char8)
| witx::Type::Builtin(witx::BuiltinType::S8)
| witx::Type::Builtin(witx::BuiltinType::U8)
| witx::Type::Builtin(witx::BuiltinType::S16)
| witx::Type::Builtin(witx::BuiltinType::U16)
| witx::Type::Builtin(witx::BuiltinType::S32)
| witx::Type::Builtin(witx::BuiltinType::U32)
| witx::Type::Builtin(witx::BuiltinType::USize) => {
add_param(&name, Abi::I32);
}
witx::Type::Builtin(witx::BuiltinType::S64)
| witx::Type::Builtin(witx::BuiltinType::U64) => {
add_param(&name, Abi::I64);
}
witx::Type::Builtin(witx::BuiltinType::F32) => {
add_param(&name, Abi::F32);
}
witx::Type::Builtin(witx::BuiltinType::F64) => {
add_param(&name, Abi::F64);
}
// strings/arrays have an extra ABI parameter for the length
// of the array passed.
witx::Type::Builtin(witx::BuiltinType::String) | witx::Type::Array(_) => {
add_param(&name, Abi::I32);
let len = format_ident!("{}_len", name);
add_param(&len, Abi::I32);
}
witx::Type::ConstPointer(_)
| witx::Type::Handle(_)
| witx::Type::Pointer(_) => {
add_param(&name, Abi::I32);
}
witx::Type::Struct(_) | witx::Type::Union(_) => {
panic!("unsupported argument type")
}
}
}
let mut results = func.results.iter();
let mut ret_ty = quote! { () };
let mut cvt_ret = quote! {};
let mut returns = Vec::new();
let mut handle_early_error = quote! { panic!("error: {:?}", e) };
// The first result is returned bare right now...
if let Some(ret) = results.next() {
handle_early_error = quote! { return e.into() };
match &*ret.tref.type_() {
// Eventually we'll want to add support for more returned
// types, but for now let's just conform to what `*.witx`
// definitions currently use.
witx::Type::Enum(e) => match e.repr {
witx::IntRepr::U16 => {
returns.push(quote! { types::I32 });
ret_ty = quote! { i32 };
cvt_ret = quote! { .into() }
}
other => panic!("unsupported ret enum repr {:?}", other),
},
other => panic!("unsupported first return {:?}", other),
}
}
// ... and all remaining results are returned via out-poiners
for result in results {
let name = format_ident!("{}", result.name.as_str());
params.push(quote! { types::I32 });
shim_arg_decls.push(quote! { #name: i32 });
hostcall_args.push(quote! { #name });
}
let module_name = module.name.as_str();
let module_id = names.module(&module.name);
for func in module.funcs() {
let func_name = func.name.as_str();
let name_ident = names.func(&func.name);
fields.push(quote! { pub #name_ident: wasmtime::Func });
get_exports.push(quote! { #func_name => Some(&self.#name_ident) });
ctor_fields.push(name_ident.clone());
linker_add.push(quote! {
linker.define(#module_name, #func_name, self.#name_ident.clone())?;
});
if let Some(func_override) = module_conf.function_override.find(func_name) {
ctor_externs.push(quote! {
let my_cx = cx.clone();
let #name_ident = wasmtime::Func::wrap(
store,
move |caller: wasmtime::Caller<'_> #(,#shim_arg_decls)*| -> #ret_ty {
unsafe {
let mem = match caller.get_export("memory") {
Some(wasmtime::Extern::Memory(m)) => m,
_ => {
log::warn!("callee does not export a memory as \"memory\"");
let e = { #missing_mem_err };
#handle_early_error
}
};
// Wiggle does not expose any methods for
// functions to re-enter the WebAssembly module,
// or expose the memory via non-wiggle mechanisms.
// Therefore, creating a new BorrowChecker at the
// root of each function invocation is correct.
let bc = #runtime::BorrowChecker::new();
let mem = #runtime::WasmtimeGuestMemory::new( mem, bc );
#target_path::#module_id::#name_ident(
&mut my_cx.borrow_mut(),
&mem,
#(#hostcall_args),*
) #cvt_ret
}
}
);
let #name_ident = wasmtime::Func::wrap(store, #func_override);
});
continue;
}
let mut shim_arg_decls = Vec::new();
let mut params = Vec::new();
let mut hostcall_args = Vec::new();
for param in func.params.iter() {
let name = names.func_param(&param.name);
// Registers a new parameter to the shim we're making with the
// given `name`, the `abi_ty` wasm type
//
// This will register a whole bunch of things:
//
// * The cranelift type for the parameter
// * Syntax to specify the actual function parameter
// * How to actually pass this argument to the host
// implementation, converting as necessary.
let mut add_param = |name: &Ident, abi_ty: Abi| {
match abi_ty {
Abi::I32 => {
params.push(quote! { types::I32 });
shim_arg_decls.push(quote! { #name: i32 });
}
Abi::I64 => {
params.push(quote! { types::I64 });
shim_arg_decls.push(quote! { #name: i64 });
}
Abi::F32 => {
params.push(quote! { types::F32 });
shim_arg_decls.push(quote! { #name: f32 });
}
Abi::F64 => {
params.push(quote! { types::F64 });
shim_arg_decls.push(quote! { #name: f64 });
}
}
hostcall_args.push(quote! { #name as _ });
};
match &*param.tref.type_() {
witx::Type::Int(e) => match e.repr {
witx::IntRepr::U64 => add_param(&name, Abi::I64),
_ => add_param(&name, Abi::I32),
},
witx::Type::Enum(e) => match e.repr {
witx::IntRepr::U64 => add_param(&name, Abi::I64),
_ => add_param(&name, Abi::I32),
},
witx::Type::Flags(f) => match f.repr {
witx::IntRepr::U64 => add_param(&name, Abi::I64),
_ => add_param(&name, Abi::I32),
},
witx::Type::Builtin(witx::BuiltinType::Char8)
| witx::Type::Builtin(witx::BuiltinType::S8)
| witx::Type::Builtin(witx::BuiltinType::U8)
| witx::Type::Builtin(witx::BuiltinType::S16)
| witx::Type::Builtin(witx::BuiltinType::U16)
| witx::Type::Builtin(witx::BuiltinType::S32)
| witx::Type::Builtin(witx::BuiltinType::U32)
| witx::Type::Builtin(witx::BuiltinType::USize) => {
add_param(&name, Abi::I32);
}
witx::Type::Builtin(witx::BuiltinType::S64)
| witx::Type::Builtin(witx::BuiltinType::U64) => {
add_param(&name, Abi::I64);
}
witx::Type::Builtin(witx::BuiltinType::F32) => {
add_param(&name, Abi::F32);
}
witx::Type::Builtin(witx::BuiltinType::F64) => {
add_param(&name, Abi::F64);
}
// strings/arrays have an extra ABI parameter for the length
// of the array passed.
witx::Type::Builtin(witx::BuiltinType::String) | witx::Type::Array(_) => {
add_param(&name, Abi::I32);
let len = format_ident!("{}_len", name);
add_param(&len, Abi::I32);
}
witx::Type::ConstPointer(_) | witx::Type::Handle(_) | witx::Type::Pointer(_) => {
add_param(&name, Abi::I32);
}
witx::Type::Struct(_) | witx::Type::Union(_) => panic!("unsupported argument type"),
}
}
let mut results = func.results.iter();
let mut ret_ty = quote! { () };
let mut cvt_ret = quote! {};
let mut returns = Vec::new();
let mut handle_early_error = quote! { panic!("error: {:?}", e) };
// The first result is returned bare right now...
if let Some(ret) = results.next() {
handle_early_error = quote! { return e.into() };
match &*ret.tref.type_() {
// Eventually we'll want to add support for more returned
// types, but for now let's just conform to what `*.witx`
// definitions currently use.
witx::Type::Enum(e) => match e.repr {
witx::IntRepr::U16 => {
returns.push(quote! { types::I32 });
ret_ty = quote! { i32 };
cvt_ret = quote! { .into() }
}
other => panic!("unsupported ret enum repr {:?}", other),
},
other => panic!("unsupported first return {:?}", other),
}
}
// ... and all remaining results are returned via out-poiners
for result in results {
let name = format_ident!("{}", result.name.as_str());
params.push(quote! { types::I32 });
shim_arg_decls.push(quote! { #name: i32 });
hostcall_args.push(quote! { #name });
}
ctor_externs.push(quote! {
let my_cx = cx.clone();
let #name_ident = wasmtime::Func::wrap(
store,
move |caller: wasmtime::Caller<'_> #(,#shim_arg_decls)*| -> #ret_ty {
unsafe {
let mem = match caller.get_export("memory") {
Some(wasmtime::Extern::Memory(m)) => m,
_ => {
log::warn!("callee does not export a memory as \"memory\"");
let e = { #missing_mem_err };
#handle_early_error
}
};
// Wiggle does not expose any methods for
// functions to re-enter the WebAssembly module,
// or expose the memory via non-wiggle mechanisms.
// Therefore, creating a new BorrowChecker at the
// root of each function invocation is correct.
let bc = #runtime::BorrowChecker::new();
let mem = #runtime::WasmtimeGuestMemory::new( mem, bc );
#target_path::#module_id::#name_ident(
&mut my_cx.borrow_mut(),
&mem,
#(#hostcall_args),*
) #cvt_ret
}
}
);
});
}
let inst_type = instance_conf.name.clone();
let inst_docs = if let Some(ref docs) = instance_conf.docs {
quote!( #[doc = #docs] )
} else {
quote!()
};
let type_name = module_conf.name.clone();
let type_docs = module_conf
.docs
.as_ref()
.map(|docs| quote!( #[doc = #docs] ))
.unwrap_or_default();
let constructor_docs = format!(
"Creates a new [`{}`] instance.
External values are allocated into the `store` provided and
configuration of the wasi instance itself should be all
contained in the `cx` parameter.",
instance_conf.name.to_string()
module_conf.name.to_string()
);
let ctx_type = names.ctx_type();
quote! {
#inst_docs
pub struct #inst_type {
#type_docs
pub struct #type_name {
#(#fields,)*
}
impl #inst_type {
impl #type_name {
#[doc = #constructor_docs]
pub fn new(store: &wasmtime::Store, cx: #ctx_type) -> Self {
let cx = std::rc::Rc::new(std::cell::RefCell::new(cx));