use std::{
collections::{HashMap, HashSet},
fmt::Write,
};
use heck::{ToSnakeCase, ToUpperCamelCase};
use wit_bindgen_core::{
Files, Source,
abi::{self, WasmSignature},
uwriteln,
wit_parser::{Function, Param, Resolve, Type, TypeDefKind, TypeId},
};
use crate::{
FFI, FFI_DIR, indent,
pkg::{MoonbitSignature, ToMoonBitIdent},
};
use super::{FunctionBindgen, InterfaceGenerator, PayloadFor};
const ASYNC_PRIMITIVE: &str = include_str!("./ffi/async_primitive.mbt");
const ASYNC_FUTURE: &str = include_str!("./ffi/future.mbt");
const ASYNC_WASM_PRIMITIVE: &str = include_str!("./ffi/wasm_primitive.mbt");
const ASYNC_WAITABLE_SET: &str = include_str!("./ffi/waitable_task.mbt");
const ASYNC_SUBTASK: &str = include_str!("./ffi/subtask.mbt");
const ASYNC_ABI: &str = include_str!("./async/async_abi.mbt");
const ASYNC_CORO: &str = include_str!("./async/coroutine.mbt");
const ASYNC_EV: &str = include_str!("./async/ev.mbt");
const ASYNC_SCHEDULER: &str = include_str!("./async/scheduler.mbt");
const ASYNC_TASK: &str = include_str!("./async/task.mbt");
const ASYNC_TASK_GROUP: &str = include_str!("./async/task_group.mbt");
const ASYNC_TRAIT: &str = include_str!("./async/trait.mbt");
const ASYNC_PKG_JSON: &str = include_str!("./async/moon.pkg.json");
const ASYNC_PRIM: &str = include_str!("./async/async_primitive.mbt");
struct Segment<'a> {
name: &'a str,
src: &'a str,
}
const ASYNC_UTILS: [&Segment; 5] = [
&Segment {
name: "async_primitive",
src: ASYNC_PRIMITIVE,
},
&Segment {
name: "async_future",
src: ASYNC_FUTURE,
},
&Segment {
name: "async_wasm_primitive",
src: ASYNC_WASM_PRIMITIVE,
},
&Segment {
name: "async_waitable_set",
src: ASYNC_WAITABLE_SET,
},
&Segment {
name: "async_subtask",
src: ASYNC_SUBTASK,
},
];
const ASYNC_IMPL: [&Segment; 8] = [
&Segment {
name: "async_abi",
src: ASYNC_ABI,
},
&Segment {
name: "async_coro",
src: ASYNC_CORO,
},
&Segment {
name: "async_ev",
src: ASYNC_EV,
},
&Segment {
name: "async_scheduler",
src: ASYNC_SCHEDULER,
},
&Segment {
name: "async_task",
src: ASYNC_TASK,
},
&Segment {
name: "async_task_group",
src: ASYNC_TASK_GROUP,
},
&Segment {
name: "async_trait",
src: ASYNC_TRAIT,
},
&Segment {
name: "async_primitive",
src: ASYNC_PRIM,
},
];
pub(crate) const ASYNC_DIR: &str = "async";
#[derive(Default)]
pub(crate) struct AsyncSupport {
is_async: bool,
futures: HashMap<String, HashSet<TypeId>>,
}
impl AsyncSupport {
pub(crate) fn mark_async(&mut self) {
self.is_async = true;
}
pub(crate) fn register_future_or_stream(&mut self, module: &str, ty: TypeId) -> bool {
self.futures
.entry(module.to_string())
.or_default()
.insert(ty)
}
pub(crate) fn emit_utils(&self, files: &mut Files, version: &str) {
if !self.is_async && self.futures.is_empty() {
return;
}
let mut body = Source::default();
wit_bindgen_core::generated_preamble(&mut body, version);
body.push_str(FFI);
files.push(&format!("{FFI_DIR}/top.mbt"), indent(&body).as_bytes());
ASYNC_UTILS.iter().for_each(|s| {
files.push(
&format!("{FFI_DIR}/{}.mbt", s.name),
indent(s.src).as_bytes(),
);
});
ASYNC_IMPL.iter().for_each(|s| {
files.push(
&format!("{ASYNC_DIR}/{}.mbt", s.name),
indent(s.src).as_bytes(),
);
});
files.push(
&format!("{ASYNC_DIR}/moon.pkg.json"),
indent(ASYNC_PKG_JSON).as_bytes(),
);
files.push(
&format!("{FFI_DIR}/moon.pkg.json"),
"{ \"warn-list\": \"-44\", \"supported-targets\": [\"wasm\"] }".as_bytes(),
);
}
}
impl<'a> InterfaceGenerator<'a> {
#[allow(dead_code, reason = "used by follow-up async generation work")]
pub(super) fn generate_async_import_function(
&mut self,
func: &Function,
mbt_sig: MoonbitSignature,
sig: &WasmSignature,
) -> String {
let mut body = String::default();
let mut lower_params = Vec::new();
let mut lower_results = Vec::new();
if sig.indirect_params {
match &func.params[..] {
[] => {}
[_] => {
lower_params.push("_lower_ptr".into());
}
multiple_params => {
let params = multiple_params.iter().map(|Param { ty, .. }| ty);
let offsets = self.world_gen.sizes.field_offsets(params.clone());
let elem_info = self.world_gen.sizes.params(params);
body.push_str(&format!(
r#"
let _lower_ptr : Int = {ffi}malloc({})
"#,
elem_info.size.size_wasm32(),
ffi = self
.world_gen
.pkg_resolver
.qualify_package(self.name, FFI_DIR)
));
for ((offset, ty), name) in offsets.iter().zip(
multiple_params
.iter()
.map(|Param { name, .. }| name.to_moonbit_ident()),
) {
let result = self.lower_to_memory(
&format!("_lower_ptr + {}", offset.size_wasm32()),
&name,
ty,
self.name,
);
body.push_str(&result);
}
lower_params.push("_lower_ptr".into());
}
}
} else {
let mut f = FunctionBindgen::new(self, Box::new([]));
for (name, ty) in mbt_sig.params.iter() {
lower_params.extend(abi::lower_flat(
f.interface_gen.resolve,
&mut f,
name.clone(),
ty,
));
}
lower_results.push(f.src.clone());
}
let func_name = func.name.to_upper_camel_case();
let ffi = self
.world_gen
.pkg_resolver
.qualify_package(self.name, FFI_DIR);
let call_import = |params: &Vec<String>| {
format!(
r#"
let _subtask_code = wasmImport{func_name}({})
let _subtask_status = {ffi}SubtaskStatus::decode(_subtask_code)
let _subtask = @ffi.Subtask::from_handle(_subtask_status.handle(), code=_subtask_code)
let task = @ffi.current_task()
task.add_waitable(_subtask, @ffi.current_coroutine())
defer task.remove_waitable(_subtask)
for {{
if _subtask.done() || _subtask_status is Returned(_) {{
break
}} else {{
@ffi.suspend()
}}
}}
"#,
params.join(", ")
)
};
match &func.result {
Some(ty) => {
lower_params.push("_result_ptr".into());
let call_import = call_import(&lower_params);
let (lift, lift_result) = &self.lift_from_memory("_result_ptr", ty, self.name);
body.push_str(&format!(
r#"
{}
{}
{call_import}
{lift}
{lift_result}
"#,
lower_results.join("\n"),
&self.malloc_memory("_result_ptr", "1", ty)
));
}
None => {
let call_import = call_import(&lower_params);
body.push_str(&call_import);
}
}
body.to_string()
}
pub(super) fn generation_futures_and_streams_import(
&mut self,
prefix: &str,
func: &Function,
module: &str,
) {
let module = format!("{prefix}{module}");
for (index, ty) in func
.find_futures_and_streams(self.resolve)
.into_iter()
.enumerate()
{
let func_name = &func.name;
match &self.resolve.types[ty].kind {
TypeDefKind::Future(payload_type) => {
self.r#generate_async_future_or_stream_import(
PayloadFor::Future,
&module,
index,
func_name,
ty,
payload_type.as_ref(),
);
}
TypeDefKind::Stream(payload_type) => {
self.r#generate_async_future_or_stream_import(
PayloadFor::Stream,
&module,
index,
func_name,
ty,
payload_type.as_ref(),
);
}
_ => unreachable!(),
}
}
}
fn generate_async_future_or_stream_import(
&mut self,
payload_for: PayloadFor,
module: &str,
index: usize,
func_name: &str,
ty: TypeId,
result_type: Option<&Type>,
) {
if !self
.world_gen
.async_support
.register_future_or_stream(module, ty)
{
return;
}
let result = match result_type {
Some(ty) => self.world_gen.pkg_resolver.type_name(self.name, ty),
None => "Unit".into(),
};
let type_name = self
.world_gen
.pkg_resolver
.type_name(self.name, &Type::Id(ty));
let name = result.to_upper_camel_case();
let kind = match payload_for {
PayloadFor::Future => "future",
PayloadFor::Stream => "stream",
};
let table_name = format!("{}_{}_table", type_name.to_snake_case(), kind);
let camel_kind = kind.to_upper_camel_case();
let payload_len_arg = match payload_for {
PayloadFor::Future => "",
PayloadFor::Stream => " ,length : Int",
};
let payload_lift_func = match payload_for {
PayloadFor::Future => "",
PayloadFor::Stream => "List",
};
let ffi = self
.world_gen
.pkg_resolver
.qualify_package(self.name, FFI_DIR);
let mut dealloc_list;
let malloc;
let lift;
let lower;
let lift_result;
let lift_list: String;
let lower_list: String;
if let Some(result_type) = result_type {
(lift, lift_result) = self.lift_from_memory("ptr", result_type, module);
lower = self.lower_to_memory("ptr", "value", result_type, module);
dealloc_list = self.deallocate_lists(
std::slice::from_ref(result_type),
&[String::from("ptr")],
true,
module,
);
lift_list = self.list_lift_from_memory(
"ptr",
"length",
&format!("wasm{name}{kind}Lift"),
result_type,
);
lower_list =
self.list_lower_to_memory(&format!("wasm{name}{kind}Lower"), "value", result_type);
malloc = self.malloc_memory("ptr", "length", result_type);
if dealloc_list.is_empty() {
dealloc_list = "let _ = ptr".to_string();
}
} else {
lift = "let _ = ptr".to_string();
lower = "let _ = (ptr, value)".to_string();
dealloc_list = "let _ = ptr".to_string();
malloc = "let ptr = 0;".into();
lift_result = "".into();
lift_list = "FixedArray::make(length, Unit::default())".into();
lower_list = "0".into();
}
let (mut lift_func, mut lower_func) = if result_type
.is_some_and(|ty| self.is_list_canonical(self.resolve, ty))
&& matches!(payload_for, PayloadFor::Stream)
{
("".into(), "".into())
} else {
(
format!(
r#"
fn wasm{name}{kind}Lift(ptr: Int) -> {result} {{
{lift}
{lift_result}
}}
"#
),
format!(
r#"
fn wasm{name}{kind}Lower(value: {result}, ptr: Int) -> Unit {{
{lower}
}}
"#
),
)
};
if matches!(payload_for, PayloadFor::Stream) {
lift_func.push_str(&format!(
r#"
fn wasm{name}{kind}ListLift(ptr: Int, length: Int) -> FixedArray[{result}] {{
{lift_list}
}}
"#
));
lower_func.push_str(&format!(
r#"
fn wasm{name}{kind}ListLower(value: FixedArray[{result}]) -> Int {{
{lower_list}
}}
"#
));
};
uwriteln!(
self.ffi,
r#"
fn wasmImport{name}{kind}New() -> UInt64 = "{module}" "[{kind}-new-{index}]{func_name}"
fn wasmImport{name}{kind}Read(handle : Int, buffer_ptr : Int{payload_len_arg}) -> Int = "{module}" "[async-lower][{kind}-read-{index}]{func_name}"
fn wasmImport{name}{kind}Write(handle : Int, buffer_ptr : Int{payload_len_arg}) -> Int = "{module}" "[async-lower][{kind}-write-{index}]{func_name}"
fn wasmImport{name}{kind}CancelRead(handle : Int) -> Int = "{module}" "[{kind}-cancel-read-{index}]{func_name}"
fn wasmImport{name}{kind}CancelWrite(handle : Int) -> Int = "{module}" "[{kind}-cancel-write-{index}]{func_name}"
fn wasmImport{name}{kind}DropReadable(handle : Int) = "{module}" "[{kind}-drop-readable-{index}]{func_name}"
fn wasmImport{name}{kind}DropWritable(handle : Int) = "{module}" "[{kind}-drop-writable-{index}]{func_name}"
fn wasm{name}{kind}Deallocate(ptr: Int) -> Unit {{
{dealloc_list}
}}
fn wasm{name}{kind}Malloc(length: Int) -> Int {{
{malloc}
ptr
}}
fn {table_name}() -> {ffi}{camel_kind}VTable[{result}] {{
{ffi}{camel_kind}VTable::new(
wasmImport{name}{kind}New,
wasmImport{name}{kind}Read,
wasmImport{name}{kind}Write,
wasmImport{name}{kind}CancelRead,
wasmImport{name}{kind}CancelWrite,
wasmImport{name}{kind}DropReadable,
wasmImport{name}{kind}DropWritable,
wasm{name}{kind}Malloc,
wasm{name}{kind}Deallocate,
wasm{name}{kind}{payload_lift_func}Lift,
wasm{name}{kind}{payload_lift_func}Lower,
)
}}
{lift_func}
{lower_func}
"#
);
}
fn deallocate_lists(
&mut self,
types: &[Type],
operands: &[String],
indirect: bool,
_module: &str,
) -> String {
let mut f = FunctionBindgen::new(self, Box::new([]));
abi::deallocate_lists_in_types(f.interface_gen.resolve, types, operands, indirect, &mut f);
f.src
}
fn lift_from_memory(&mut self, address: &str, ty: &Type, _module: &str) -> (String, String) {
let mut f = FunctionBindgen::new(self, Box::new([]));
let result = abi::lift_from_memory(f.interface_gen.resolve, &mut f, address.into(), ty);
(f.src, result)
}
fn lower_to_memory(&mut self, address: &str, value: &str, ty: &Type, _module: &str) -> String {
let mut f = FunctionBindgen::new(self, Box::new([]));
abi::lower_to_memory(
f.interface_gen.resolve,
&mut f,
address.into(),
value.into(),
ty,
);
f.src
}
fn malloc_memory(&mut self, address: &str, length: &str, ty: &Type) -> String {
let size = self.world_gen.sizes.size(ty).size_wasm32();
let ffi = self
.world_gen
.pkg_resolver
.qualify_package(self.name, FFI_DIR);
format!("let {address} = {ffi}malloc({size} * {length});")
}
fn is_list_canonical(&self, _resolve: &Resolve, element: &Type) -> bool {
matches!(
element,
Type::U8 | Type::U32 | Type::U64 | Type::S32 | Type::S64 | Type::F32 | Type::F64
)
}
fn list_lift_from_memory(
&mut self,
address: &str,
length: &str,
lift_func: &str,
ty: &Type,
) -> String {
let ffi = self
.world_gen
.pkg_resolver
.qualify_package(self.name, FFI_DIR);
if self.is_list_canonical(self.resolve, ty) {
if ty == &Type::U8 {
return format!("{ffi}ptr2bytes({address}, {length})");
}
let ty = match ty {
Type::U32 => "uint",
Type::U64 => "uint64",
Type::S32 => "int",
Type::S64 => "int64",
Type::F32 => "float",
Type::F64 => "double",
_ => unreachable!(),
};
return format!("{ffi}ptr2{ty}_array({address}, {length})");
}
let size = self.world_gen.sizes.size(ty).size_wasm32();
format!(
r#"
FixedArray::makei(
{length},
(index) => {{
let ptr = ({address}) + (index * {size});
{lift_func}(ptr)
}}
)
"#
)
}
fn list_lower_to_memory(&mut self, lower_func: &str, value: &str, ty: &Type) -> String {
let ffi = self
.world_gen
.pkg_resolver
.qualify_package(self.name, FFI_DIR);
if self.is_list_canonical(self.resolve, ty) {
if ty == &Type::U8 {
return format!("{ffi}bytes2ptr({value})");
}
let ty = match ty {
Type::U32 => "uint",
Type::U64 => "uint64",
Type::S32 => "int",
Type::S64 => "int64",
Type::F32 => "float",
Type::F64 => "double",
_ => unreachable!(),
};
return format!("{ffi}{ty}_array2ptr({value})");
}
let size = self.world_gen.sizes.size(ty).size_wasm32();
format!(
r#"
let address = {ffi}malloc(({value}).length() * {size});
for index = 0; index < ({value}).length(); index = index + 1 {{
let ptr = (address) + (index * {size});
let value = {value}[index];
{lower_func}(value, ptr);
}}
address
"#
)
}
}