use crate::runtime::host::{abi_arg, abi_result};
use wasmtime::{
Caller, FieldType, Finality, FuncType, HeapType, Linker, Mutability, RefType, Rooted,
StorageType, StructRef, StructRefPre, StructType, Val, ValType,
};
use crate::runtime::StoreData;
use crate::runtime::gc_singleton::singleton_struct;
use crate::runtime::host::{fatal_host_error, host_opaque_vtable, register_host_fn};
use crate::runtime::intrinsic_types::IntrinsicTypes;
use crate::runtime::prelude::closure::Closure;
use crate::runtime::prelude::iterator::as_struct;
pub(crate) fn string_field(intr: &IntrinsicTypes) -> FieldType {
ref_field(intr.string.clone().into(), false)
}
pub(crate) fn nullable_object_field(intr: &IntrinsicTypes) -> FieldType {
ref_field(intr.object.clone().into(), true)
}
pub(crate) fn array_field(intr: &IntrinsicTypes) -> FieldType {
ref_field(intr.array.clone().into(), false)
}
pub(crate) fn f64_field() -> FieldType {
FieldType::new(Mutability::Const, StorageType::ValType(ValType::F64))
}
pub(crate) fn i32_field() -> FieldType {
FieldType::new(Mutability::Const, StorageType::ValType(ValType::I32))
}
pub(crate) fn raw_bytes_field(intr: &IntrinsicTypes) -> FieldType {
ref_field(intr.raw_uint8_array.clone().into(), false)
}
pub(crate) fn externref_field() -> FieldType {
FieldType::new(Mutability::Const, StorageType::ValType(ValType::EXTERNREF))
}
fn ref_field(heap: HeapType, nullable: bool) -> FieldType {
FieldType::new(
Mutability::Const,
StorageType::ValType(ValType::Ref(RefType::new(nullable, heap))),
)
}
pub(crate) fn backing_struct(
engine: &wasmtime::Engine,
intr: &IntrinsicTypes,
data_fields: Vec<FieldType>,
) -> wasmtime::Result<StructType> {
let mut fields = vec![ref_field(intr.vtable.clone().into(), false)];
fields.extend(data_fields);
singleton_struct(engine, Finality::Final, Some(intr.object.clone()), fields)
}
pub(crate) fn new_backing(
caller: &mut Caller<'_, StoreData>,
ty: StructType,
data_values: &[Val],
) -> wasmtime::Result<Val> {
let vtable = host_opaque_vtable(caller)?;
let mut values = vec![vtable];
values.extend_from_slice(data_values);
let pre = StructRefPre::new(&mut *caller, ty);
let st = StructRef::new(&mut *caller, &pre, &values)?;
Ok(Val::AnyRef(Some(st.to_anyref())))
}
pub(crate) fn new_array(
caller: &mut Caller<'_, StoreData>,
elements: &[Val],
) -> wasmtime::Result<Val> {
let st = crate::runtime::host::write_submilli_array_struct(caller, elements)?;
Ok(Val::AnyRef(Some(st.to_anyref())))
}
pub(crate) type HostProperties = &'static [(&'static str, usize)];
pub(crate) struct HostView {
pub(crate) module: &'static str,
pub(crate) interface: &'static str,
pub(crate) backing: fn(&wasmtime::Engine) -> wasmtime::Result<StructType>,
pub(crate) properties: HostProperties,
}
pub(crate) fn install_field_getters(
linker: &mut Linker<StoreData>,
engine: &wasmtime::Engine,
receiver: &ValType,
view: &HostView,
) -> wasmtime::Result<()> {
let backing = (view.backing)(engine)?;
let iface_key = crate::mangle::package_symbol(view.module, view.interface);
for &(prop, field) in view.properties {
let Some(StorageType::ValType(result)) = backing
.field(field)
.map(|field| field.element_type().clone())
else {
return Err(fatal_host_error(format!(
"{}.{prop}: backing field {field} holds no value",
view.interface
)));
};
register_host_fn(
linker,
view.module,
crate::mangle::extend(&iface_key, prop),
FuncType::new(engine, [receiver.clone()], [result]),
move |caller, params, results| {
let st = backing_receiver(caller, abi_arg(params, 0)?)?;
*abi_result(results, 0)? = st.field(&mut *caller, field)?;
Ok(())
},
)?;
}
Ok(())
}
pub(crate) fn json_properties(
caller: &mut Caller<'_, StoreData>,
value: &Rooted<StructRef>,
) -> wasmtime::Result<HostProperties> {
let ty = value.ty(&*caller)?;
if caller.data().host_views.is_none() {
let mut views = Vec::new();
for view in crate::stdlib::host_views() {
crate::runtime::fuel::charge(&mut *caller, crate::runtime::fuel::ELEM, 1)?;
views.push(((view.backing)(caller.engine())?, view.properties));
}
caller.data_mut().host_views = Some(views);
}
Ok(caller
.data()
.host_views
.iter()
.flatten()
.find(|(backing, _)| *backing == ty)
.map_or(&[], |&(_, properties)| properties))
}
pub(crate) fn backing_receiver(
caller: &mut Caller<'_, StoreData>,
val: &Val,
) -> wasmtime::Result<Rooted<StructRef>> {
as_struct(caller, val, "stdlib property receiver")
}
pub(crate) fn new_object(
caller: &mut Caller<'_, StoreData>,
fields: std::collections::BTreeMap<String, Val>,
) -> wasmtime::Result<Val> {
use crate::runtime::{fuel, host};
use wasmtime::{ArrayRef, ArrayRefPre};
let intr = crate::runtime::intrinsic_types::intrinsic_types(&mut *caller)?;
fuel::charge(&mut *caller, fuel::ELEM, fields.len() as u64)?;
let mut names = Vec::new();
let mut values = Vec::new();
names
.try_reserve_exact(fields.len())
.map_err(host::fatal_host_error)?;
values
.try_reserve_exact(fields.len())
.map_err(host::fatal_host_error)?;
for (name, value) in fields {
names.push(Val::AnyRef(Some(
host::write_submilli_string_struct(caller, &name)?.to_anyref(),
)));
values.push(value);
}
let names_pre = ArrayRefPre::new(&mut *caller, intr.field_names.clone());
let values_pre = ArrayRefPre::new(&mut *caller, intr.object_fields.clone());
let names = ArrayRef::new_fixed(&mut *caller, &names_pre, &names)?;
let values = ArrayRef::new_fixed(&mut *caller, &values_pre, &values)?;
let vtable = host::host_object_vtable(caller)?;
let pre = StructRefPre::new(&mut *caller, intr.object_shape.clone());
let object = StructRef::new(
&mut *caller,
&pre,
&[
vtable,
Val::AnyRef(Some(names.to_anyref())),
Val::AnyRef(Some(values.to_anyref())),
Val::AnyRef(None),
],
)?;
Ok(Val::AnyRef(Some(object.to_anyref())))
}
pub(crate) struct TypeDescriptor {
test: Closure,
pub name: String,
pub schema: Option<String>,
}
impl TypeDescriptor {
pub(crate) fn schema_for(&self, api: &str) -> wasmtime::Result<&str> {
self.schema.as_deref().ok_or_else(|| {
crate::runtime::host::type_error(format!(
"{api}<{name}>: `{name}` has no JSON Schema to send — it is not a JSON \
data shape (a function, `bigint`, `Uint8Array`, `unknown`, a class, a \
`Map`, an enum, a recursive or method-bearing type), or its schema is \
larger than a request may carry; name an object, array, tuple, union or \
primitive shape instead",
name = self.name
))
})
}
pub(crate) async fn accepts(
&self,
caller: &mut Caller<'_, StoreData>,
value: Val,
) -> wasmtime::Result<bool> {
crate::runtime::fuel::charge_call(&mut *caller)?;
let verdict = self.test.call(caller, &[value]).await?;
let boxed =
as_struct(caller, &verdict, "type descriptor verdict").map_err(fatal_host_error)?;
match boxed.field(&mut *caller, 1)? {
Val::I32(bit) => Ok(bit != 0),
other => Err(fatal_host_error(format!(
"type descriptor verdict is not a boxed boolean: {other:?}"
))),
}
}
}
pub(crate) fn read_type_argument(
caller: &mut Caller<'_, StoreData>,
env: &Val,
slot: usize,
) -> wasmtime::Result<TypeDescriptor> {
decode_type_argument(caller, env, slot).map_err(fatal_host_error)
}
fn decode_type_argument(
caller: &mut Caller<'_, StoreData>,
env: &Val,
slot: usize,
) -> wasmtime::Result<TypeDescriptor> {
let Val::AnyRef(Some(env)) = env else {
return Err(wasmtime::Error::msg(
"type descriptor environment is not an array",
));
};
let env = env.unwrap_array(&mut *caller)?;
let slot = u32::try_from(slot).map_err(|_| wasmtime::Error::msg("type descriptor slot"))?;
let descriptor = env.get(&mut *caller, slot)?;
let descriptor = as_struct(caller, &descriptor, "type descriptor")?;
let test = descriptor.field(&mut *caller, 1)?;
let test = crate::runtime::prelude::closure::read(caller, &test, "type descriptor test")?;
let name = descriptor.field(&mut *caller, 2)?;
let name = crate::runtime::host::read_string_arg(caller, &name, "type descriptor name")?;
let schema = match descriptor.field(&mut *caller, 3)? {
Val::AnyRef(None) => None,
schema => Some(crate::runtime::host::read_string_arg(
caller,
&schema,
"type descriptor schema",
)?),
};
Ok(TypeDescriptor { test, name, schema })
}