This change is the first in a series of changes to support shared memory in Wiggle. Since Wiggle was written under the assumption of single-threaded guest-side access, this change introduces a `shared` field to guest memories in order to flag when this assumption will not be the case. This change always sets `shared` to `false`; once a few more pieces are in place, `shared` will be set dynamically when a shared memory is detected, e.g., in a change like #5054. Using the `shared` field, we can now decide to load Wiggle values differently under the new assumptions. This change makes the guest `T::read` and `T::write` calls into `Relaxed` atomic loads and stores in order to maintain WebAssembly's expected memory consistency guarantees. We choose Rust's `Relaxed` here to match the `Unordered` memory consistency described in the [memory model] section of the ECMA spec. These relaxed accesses are done unconditionally, since we theorize that the performance benefit of an additional branch vs a relaxed load is not much. [memory model]: https://tc39.es/ecma262/multipage/memory-model.html#sec-memory-model Since 128-bit scalar types do not have `Atomic*` equivalents, we remove their `T::read` and `T::write` implementations here. They are unused by any WASI implementations in the project.
165 lines
4.8 KiB
Rust
165 lines
4.8 KiB
Rust
use crate::config::Asyncness;
|
|
use crate::funcs::func_bounds;
|
|
use crate::{CodegenSettings, Names};
|
|
use proc_macro2::{Ident, Span, TokenStream};
|
|
use quote::{format_ident, quote};
|
|
use std::collections::HashSet;
|
|
|
|
pub fn link_module(
|
|
module: &witx::Module,
|
|
names: &Names,
|
|
target_path: Option<&syn::Path>,
|
|
settings: &CodegenSettings,
|
|
) -> TokenStream {
|
|
let module_ident = names.module(&module.name);
|
|
|
|
let send_bound = if settings.async_.contains_async(module) {
|
|
quote! { + Send, T: Send }
|
|
} else {
|
|
quote! {}
|
|
};
|
|
|
|
let mut bodies = Vec::new();
|
|
let mut bounds = HashSet::new();
|
|
for f in module.funcs() {
|
|
let asyncness = settings.async_.get(module.name.as_str(), f.name.as_str());
|
|
bodies.push(generate_func(&module, &f, names, target_path, asyncness));
|
|
let bound = func_bounds(names, module, &f, settings);
|
|
for b in bound {
|
|
bounds.insert(b);
|
|
}
|
|
}
|
|
|
|
let ctx_bound = if let Some(target_path) = target_path {
|
|
let bounds = bounds
|
|
.into_iter()
|
|
.map(|b| quote!(#target_path::#module_ident::#b));
|
|
quote!( #(#bounds)+* #send_bound )
|
|
} else {
|
|
let bounds = bounds.into_iter();
|
|
quote!( #(#bounds)+* #send_bound )
|
|
};
|
|
|
|
let func_name = if target_path.is_none() {
|
|
format_ident!("add_to_linker")
|
|
} else {
|
|
format_ident!("add_{}_to_linker", module_ident)
|
|
};
|
|
|
|
let rt = names.runtime_mod();
|
|
|
|
quote! {
|
|
/// Adds all instance items to the specified `Linker`.
|
|
pub fn #func_name<T, U>(
|
|
linker: &mut #rt::wasmtime_crate::Linker<T>,
|
|
get_cx: impl Fn(&mut T) -> &mut U + Send + Sync + Copy + 'static,
|
|
) -> #rt::anyhow::Result<()>
|
|
where
|
|
U: #ctx_bound #send_bound
|
|
{
|
|
#(#bodies)*
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
fn generate_func(
|
|
module: &witx::Module,
|
|
func: &witx::InterfaceFunc,
|
|
names: &Names,
|
|
target_path: Option<&syn::Path>,
|
|
asyncness: Asyncness,
|
|
) -> TokenStream {
|
|
let rt = names.runtime_mod();
|
|
|
|
let module_str = module.name.as_str();
|
|
let module_ident = names.module(&module.name);
|
|
|
|
let field_str = func.name.as_str();
|
|
let field_ident = names.func(&func.name);
|
|
|
|
let (params, results) = func.wasm_signature();
|
|
|
|
let arg_names = (0..params.len())
|
|
.map(|i| Ident::new(&format!("arg{}", i), Span::call_site()))
|
|
.collect::<Vec<_>>();
|
|
let arg_decls = params
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, ty)| {
|
|
let name = &arg_names[i];
|
|
let wasm = names.wasm_type(*ty);
|
|
quote! { #name: #wasm }
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let ret_ty = match results.len() {
|
|
0 => quote!(()),
|
|
1 => names.wasm_type(results[0]),
|
|
_ => unimplemented!(),
|
|
};
|
|
|
|
let await_ = if asyncness.is_sync() {
|
|
quote!()
|
|
} else {
|
|
quote!(.await)
|
|
};
|
|
|
|
let abi_func = if let Some(target_path) = target_path {
|
|
quote!( #target_path::#module_ident::#field_ident )
|
|
} else {
|
|
quote!( #field_ident )
|
|
};
|
|
|
|
let body = quote! {
|
|
let mem = match caller.get_export("memory") {
|
|
Some(#rt::wasmtime_crate::Extern::Memory(m)) => m,
|
|
_ => #rt::anyhow::bail!("missing required memory export"),
|
|
};
|
|
let (mem , ctx) = mem.data_and_store_mut(&mut caller);
|
|
let ctx = get_cx(ctx);
|
|
let mem = #rt::wasmtime::WasmtimeGuestMemory::new(mem, false);
|
|
Ok(<#ret_ty>::from(#abi_func(ctx, &mem #(, #arg_names)*) #await_ ?))
|
|
};
|
|
|
|
match asyncness {
|
|
Asyncness::Async => {
|
|
let wrapper = format_ident!("func_wrap{}_async", params.len());
|
|
quote! {
|
|
linker.#wrapper(
|
|
#module_str,
|
|
#field_str,
|
|
move |mut caller: #rt::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| {
|
|
Box::new(async move { #body })
|
|
},
|
|
)?;
|
|
}
|
|
}
|
|
|
|
Asyncness::Blocking => {
|
|
quote! {
|
|
linker.func_wrap(
|
|
#module_str,
|
|
#field_str,
|
|
move |mut caller: #rt::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| -> #rt::anyhow::Result<#ret_ty> {
|
|
let result = async { #body };
|
|
#rt::run_in_dummy_executor(result)?
|
|
},
|
|
)?;
|
|
}
|
|
}
|
|
|
|
Asyncness::Sync => {
|
|
quote! {
|
|
linker.func_wrap(
|
|
#module_str,
|
|
#field_str,
|
|
move |mut caller: #rt::wasmtime_crate::Caller<'_, T> #(, #arg_decls)*| -> #rt::anyhow::Result<#ret_ty> {
|
|
#body
|
|
},
|
|
)?;
|
|
}
|
|
}
|
|
}
|
|
}
|