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

@@ -15,23 +15,24 @@ wasmtime_wiggle::define_wasmtime_integration!({
ctx: WasiCtx,
// This macro will emit a struct to represent the instance,
// with this name and docs:
instance: {
name: Wasi,
docs: "An instantiated instance of the wasi exports.
modules: { wasi_snapshot_preview1 =>
{ name: Wasi,
docs: "An instantiated instance of the wasi exports.
This represents a wasi module which can be used to instantiate other
wasm modules. This structure exports all that various fields of the
wasi instance as fields which can be used to implement your own
instantiation logic, if necessary. Additionally [`Wasi::get_export`]
can be used to do name-based resolution."
This represents a wasi module which can be used to instantiate other wasm
modules. This structure exports all that various fields of the wasi instance
as fields which can be used to implement your own instantiation logic, if
necessary. Additionally [`Wasi::get_export`] can be used to do name-based
resolution.",
// Don't use the wiggle generated code to implement proc_exit, we need
// to hook directly into the runtime there:
function_override: {
proc_exit => wasi_proc_exit
}
},
},
// Error to return when caller module is missing memory export:
missing_memory: { wasi_common::wasi::Errno::Inval },
// Don't use the wiggle generated code to implement proc_exit, we need to hook directly into
// the runtime there:
function_override: {
wasi_snapshot_preview1:proc_exit => wasi_proc_exit
}
});
pub fn is_wasi_module(name: &str) -> bool {

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));