#![cfg(feature = "wasm")]
use super::RunWasmGcHost;
use super::imports::{
host_http_response_make, host_map_string_list_string_empty, host_option_string_none,
host_option_string_some, host_result_err_list_string, host_result_err_string,
host_result_err_unit_string, host_result_http_response_err, host_result_http_response_ok,
host_result_ok_list_string, host_result_ok_string, host_result_ok_unit,
host_result_tcp_connection_err, host_result_tcp_connection_ok, host_tcp_connection_make,
host_terminal_size_make, lm_string_from_host,
};
pub(crate) fn decode_main_return_typed(
store: &mut wasmtime::Store<RunWasmGcHost>,
instance: &wasmtime::Instance,
out: &[wasmtime::Val],
ty: &aver::ast::Type,
) -> Result<aver::replay::JsonValue, String> {
use aver::ast::Type;
use aver::replay::JsonValue;
match (ty, out) {
(Type::Unit, []) | (Type::Unit, [_]) => Ok(JsonValue::Null),
(_, []) => Err(format!("main returns no values but type is {:?}", ty)),
(_, [single]) => decode_val_typed(store, instance, single, ty),
(Type::Tuple(types), many) if many.len() == types.len() => {
let mut arr = Vec::with_capacity(many.len());
for (v, t) in many.iter().zip(types.iter()) {
arr.push(decode_val_typed(store, instance, v, t)?);
}
Ok(wrap_marker("$tuple", JsonValue::Array(arr)))
}
(_, _) => Err(format!(
"main return shape {} values does not match type {:?}",
out.len(),
ty
)),
}
}
pub(super) fn wrap_marker(
marker: &str,
payload: aver::replay::JsonValue,
) -> aver::replay::JsonValue {
let mut obj = std::collections::BTreeMap::new();
obj.insert(marker.to_string(), payload);
aver::replay::JsonValue::Object(obj)
}
pub(crate) fn decode_val_typed(
store: &mut wasmtime::Store<RunWasmGcHost>,
instance: &wasmtime::Instance,
val: &wasmtime::Val,
ty: &aver::ast::Type,
) -> Result<aver::replay::JsonValue, String> {
use aver::ast::Type;
use aver::replay::JsonValue;
use wasmtime::Val;
match (ty, val) {
(Type::Unit, _) => Ok(JsonValue::Null),
(Type::Int, Val::I64(n)) => Ok(JsonValue::Int(*n)),
(Type::Int, Val::I32(n)) => Ok(JsonValue::Int(*n as i64)),
(Type::Float, Val::F64(b)) => Ok(JsonValue::Float(f64::from_bits(*b))),
(Type::Float, Val::F32(b)) => Ok(JsonValue::Float(f32::from_bits(*b) as f64)),
(Type::Bool, Val::I32(n)) => Ok(JsonValue::Bool(*n != 0)),
(Type::Str, Val::AnyRef(opt)) => match opt {
None => Ok(JsonValue::String(String::new())),
Some(_) => decode_string_via_export(store, instance, val),
},
(Type::Result(ok_ty, err_ty), Val::AnyRef(Some(_))) => {
decode_result_struct(store, instance, val, ok_ty, err_ty)
}
(Type::Option(inner), Val::AnyRef(Some(_))) => {
decode_option_struct(store, instance, val, inner)
}
(Type::Tuple(types), Val::AnyRef(Some(_))) => {
decode_tuple_struct(store, instance, val, types)
}
(Type::Tuple(_), Val::AnyRef(None)) => Err(
"main returned null tuple ref — wasm-gc tuples are non-nullable structs".to_string(),
),
(other, _) => Err(format!(
"wasm-gc replay: main return type {:?} not yet supported by the value decoder",
other
)),
}
}
pub(super) fn encode_entry_args_for_wasm_gc(
store: &mut wasmtime::Store<RunWasmGcHost>,
instance: &wasmtime::Instance,
args: &[aver::value::Value],
) -> Result<Vec<wasmtime::Val>, String> {
use aver::value::Value;
use wasmtime::Val;
let mut out = Vec::with_capacity(args.len());
for (idx, value) in args.iter().enumerate() {
let val = match value {
Value::Int(n) => Val::I64(*n),
Value::Float(f) => Val::F64(f.to_bits()),
Value::Bool(b) => Val::I32(if *b { 1 } else { 0 }),
Value::Unit => continue, Value::Str(s) => {
let any_ref = lm_string_from_host_via_store(store, instance, s)?;
Val::AnyRef(any_ref)
}
other => {
return Err(format!(
"wasm-gc entry arg #{}: unsupported shape `{}` (entry args support \
Int / Float / Bool / String / Unit; nest compound values inside a \
helper fn and point --expr at that)",
idx + 1,
aver::value::aver_repr(other)
));
}
};
out.push(val);
}
Ok(out)
}
pub(super) fn lm_string_from_host_via_store(
store: &mut wasmtime::Store<RunWasmGcHost>,
instance: &wasmtime::Instance,
text: &str,
) -> Result<Option<wasmtime::Rooted<wasmtime::AnyRef>>, String> {
use wasmtime::Val;
let from_lm = instance
.get_func(&mut *store, "__rt_string_from_lm")
.ok_or_else(|| "missing __rt_string_from_lm export".to_string())?;
let memory = instance
.get_memory(&mut *store, "memory")
.ok_or_else(|| "missing memory export".to_string())?;
let bytes = text.as_bytes();
let needed_pages = (bytes.len() + 65535) >> 16;
let cur_pages = memory.size(&store) as usize;
if needed_pages > cur_pages {
memory
.grow(&mut *store, (needed_pages - cur_pages) as u64)
.map_err(|e| format!("memory.grow for entry arg: {e:#}"))?;
}
memory
.write(&mut *store, 0, bytes)
.map_err(|e| format!("memory write for entry arg: {e:#}"))?;
let mut out = [Val::AnyRef(None)];
from_lm
.call(&mut *store, &[Val::I32(bytes.len() as i32)], &mut out)
.map_err(|e| format!("__rt_string_from_lm trap: {e:#}"))?;
Ok(match &out[0] {
Val::AnyRef(r) => *r,
_ => None,
})
}
pub(crate) fn decode_string_via_export(
store: &mut wasmtime::Store<RunWasmGcHost>,
instance: &wasmtime::Instance,
val: &wasmtime::Val,
) -> Result<aver::replay::JsonValue, String> {
use aver::replay::JsonValue;
use wasmtime::Val;
let to_lm = instance
.get_func(&mut *store, "__rt_string_to_lm")
.ok_or_else(|| "missing __rt_string_to_lm export".to_string())?;
let memory = instance
.get_memory(&mut *store, "memory")
.ok_or_else(|| "missing memory export".to_string())?;
let mut out = [Val::I32(0)];
to_lm
.call(&mut *store, std::slice::from_ref(val), &mut out)
.map_err(|e| format!("__rt_string_to_lm trap: {e:#}"))?;
let len = match out[0] {
Val::I32(n) => n.max(0) as usize,
_ => 0,
};
let mut buf = vec![0u8; len];
if len > 0 {
memory
.read(&store, 0, &mut buf)
.map_err(|e| format!("memory read for string decode: {e:#}"))?;
}
Ok(JsonValue::String(
String::from_utf8_lossy(&buf).into_owned(),
))
}
pub(crate) fn decode_result_struct(
store: &mut wasmtime::Store<RunWasmGcHost>,
instance: &wasmtime::Instance,
val: &wasmtime::Val,
ok_ty: &aver::ast::Type,
err_ty: &aver::ast::Type,
) -> Result<aver::replay::JsonValue, String> {
let (tag, fields) = read_struct(store, val)?;
if fields.len() < 3 {
return Err(format!(
"Result struct expected 3 fields, got {}",
fields.len()
));
}
if tag == 1 {
let ok = decode_val_typed(store, instance, &fields[1], ok_ty)?;
Ok(wrap_marker("$ok", ok))
} else {
let err = decode_val_typed(store, instance, &fields[2], err_ty)?;
Ok(wrap_marker("$err", err))
}
}
pub(crate) fn decode_option_struct(
store: &mut wasmtime::Store<RunWasmGcHost>,
instance: &wasmtime::Instance,
val: &wasmtime::Val,
inner_ty: &aver::ast::Type,
) -> Result<aver::replay::JsonValue, String> {
use aver::replay::JsonValue;
let (tag, fields) = read_struct(store, val)?;
if fields.len() < 2 {
return Err(format!(
"Option struct expected 2 fields, got {}",
fields.len()
));
}
if tag == 1 {
let inner = decode_val_typed(store, instance, &fields[1], inner_ty)?;
Ok(wrap_marker("$some", inner))
} else {
Ok(wrap_marker("$none", JsonValue::Bool(true)))
}
}
pub(crate) fn decode_tuple_struct(
store: &mut wasmtime::Store<RunWasmGcHost>,
instance: &wasmtime::Instance,
val: &wasmtime::Val,
types: &[aver::ast::Type],
) -> Result<aver::replay::JsonValue, String> {
use aver::replay::JsonValue;
let (_tag, fields) = read_struct(store, val)?;
if fields.len() < types.len() {
return Err(format!(
"Tuple struct expected {} fields, got {}",
types.len(),
fields.len()
));
}
let mut arr = Vec::with_capacity(types.len());
for (i, t) in types.iter().enumerate() {
arr.push(decode_val_typed(store, instance, &fields[i], t)?);
}
Ok(wrap_marker("$tuple", JsonValue::Array(arr)))
}
pub(super) fn read_struct(
store: &mut wasmtime::Store<RunWasmGcHost>,
val: &wasmtime::Val,
) -> Result<(i32, Vec<wasmtime::Val>), String> {
use wasmtime::Val;
let any_ref = match val {
Val::AnyRef(Some(r)) => *r,
Val::AnyRef(None) => return Err("expected struct ref, got null".to_string()),
other => return Err(format!("expected AnyRef, got {:?}", other)),
};
let struct_ref = any_ref
.as_struct(&*store)
.map_err(|e| format!("anyref → struct cast: {e:#}"))?
.ok_or_else(|| "anyref is not a struct".to_string())?;
let ty = struct_ref
.ty(&*store)
.map_err(|e| format!("struct type lookup: {e:#}"))?;
let n = ty.fields().len();
let mut fields = Vec::with_capacity(n);
for i in 0..n {
fields.push(
struct_ref
.field(&mut *store, i)
.map_err(|e| format!("struct field {i}: {e:#}"))?,
);
}
let tag = match fields.first() {
Some(Val::I32(n)) => *n,
_ => 0,
};
Ok((tag, fields))
}
pub(crate) fn decode_string<T: 'static>(
caller: &mut wasmtime::Caller<'_, T>,
json: &aver::replay::JsonValue,
) -> Result<Option<wasmtime::Rooted<wasmtime::AnyRef>>, wasmtime::Error> {
match json {
aver::replay::JsonValue::String(s) => lm_string_from_host(caller, s),
aver::replay::JsonValue::Null => Ok(None),
other => Err(wasmtime::Error::msg(format!(
"replay decode: expected String, got {:?}",
other
))),
}
}
pub(crate) fn decode_result_string(
caller: &mut wasmtime::Caller<'_, RunWasmGcHost>,
json: &aver::replay::JsonValue,
) -> Result<Option<wasmtime::Rooted<wasmtime::AnyRef>>, wasmtime::Error> {
let (marker, inner) = expect_marker(json, &["$ok", "$err"])?;
match (marker, inner) {
("$ok", aver::replay::JsonValue::String(s)) => host_result_ok_string(caller, s),
("$err", aver::replay::JsonValue::String(s)) => host_result_err_string(caller, s),
_ => Err(wasmtime::Error::msg(
"replay decode Result<String, String>: unexpected payload",
)),
}
}
pub(crate) fn decode_result_unit(
caller: &mut wasmtime::Caller<'_, RunWasmGcHost>,
json: &aver::replay::JsonValue,
) -> Result<Option<wasmtime::Rooted<wasmtime::AnyRef>>, wasmtime::Error> {
let (marker, inner) = expect_marker(json, &["$ok", "$err"])?;
match (marker, inner) {
("$ok", aver::replay::JsonValue::Null) => host_result_ok_unit(caller),
("$err", aver::replay::JsonValue::String(s)) => host_result_err_unit_string(caller, s),
_ => Err(wasmtime::Error::msg(
"replay decode Result<Unit, String>: unexpected payload",
)),
}
}
pub(crate) fn decode_result_list_string(
caller: &mut wasmtime::Caller<'_, RunWasmGcHost>,
json: &aver::replay::JsonValue,
) -> Result<Option<wasmtime::Rooted<wasmtime::AnyRef>>, wasmtime::Error> {
let (marker, inner) = expect_marker(json, &["$ok", "$err"])?;
match (marker, inner) {
("$ok", aver::replay::JsonValue::Array(items)) => {
let names: Vec<String> = items
.iter()
.map(|v| match v {
aver::replay::JsonValue::String(s) => Ok(s.clone()),
other => Err(wasmtime::Error::msg(format!(
"replay decode List<String>: element is {:?}",
other
))),
})
.collect::<Result<_, _>>()?;
host_result_ok_list_string(caller, &names)
}
("$err", aver::replay::JsonValue::String(s)) => host_result_err_list_string(caller, s),
_ => Err(wasmtime::Error::msg(
"replay decode Result<List<String>, String>: unexpected payload",
)),
}
}
pub(crate) fn decode_option_string(
caller: &mut wasmtime::Caller<'_, RunWasmGcHost>,
json: &aver::replay::JsonValue,
) -> Result<Option<wasmtime::Rooted<wasmtime::AnyRef>>, wasmtime::Error> {
let (marker, inner) = expect_marker(json, &["$some", "$none"])?;
match (marker, inner) {
("$some", aver::replay::JsonValue::String(s)) => host_option_string_some(caller, s),
("$none", _) => host_option_string_none(caller),
_ => Err(wasmtime::Error::msg(
"replay decode Option<String>: unexpected payload",
)),
}
}
pub(crate) fn decode_result_http_response(
caller: &mut wasmtime::Caller<'_, RunWasmGcHost>,
json: &aver::replay::JsonValue,
) -> Result<Option<wasmtime::Rooted<wasmtime::AnyRef>>, wasmtime::Error> {
let (marker, inner) = expect_marker(json, &["$ok", "$err"])?;
match marker {
"$err" => match inner {
aver::replay::JsonValue::String(s) => host_result_http_response_err(caller, s),
_ => Err(wasmtime::Error::msg(
"replay decode Result<HttpResponse, String>.Err: payload not a String",
)),
},
"$ok" => {
let fields = expect_record(inner, "HttpResponse")?;
let status = match fields.get("status") {
Some(aver::replay::JsonValue::Int(n)) => *n,
_ => 0,
};
let body = match fields.get("body") {
Some(aver::replay::JsonValue::String(s)) => s.clone(),
_ => String::new(),
};
let body_ref = lm_string_from_host(caller, &body)?;
let headers_ref = host_map_string_list_string_empty(caller)?;
let rec_ref = host_http_response_make(caller, status, body_ref, headers_ref)?;
host_result_http_response_ok(caller, rec_ref)
}
_ => unreachable!(),
}
}
pub(crate) fn decode_result_tcp_connection(
caller: &mut wasmtime::Caller<'_, RunWasmGcHost>,
json: &aver::replay::JsonValue,
) -> Result<Option<wasmtime::Rooted<wasmtime::AnyRef>>, wasmtime::Error> {
let (marker, inner) = expect_marker(json, &["$ok", "$err"])?;
match marker {
"$err" => match inner {
aver::replay::JsonValue::String(s) => host_result_tcp_connection_err(caller, s),
_ => Err(wasmtime::Error::msg(
"replay decode Result<Tcp.Connection, String>.Err: payload not a String",
)),
},
"$ok" => {
let fields = expect_record(inner, "Tcp.Connection")?;
let id = match fields.get("id") {
Some(aver::replay::JsonValue::String(s)) => s.clone(),
_ => String::new(),
};
let host = match fields.get("host") {
Some(aver::replay::JsonValue::String(s)) => s.clone(),
_ => String::new(),
};
let port = match fields.get("port") {
Some(aver::replay::JsonValue::Int(n)) => *n,
_ => 0,
};
let id_ref = lm_string_from_host(caller, &id)?;
let host_ref = lm_string_from_host(caller, &host)?;
let rec_ref = host_tcp_connection_make(caller, id_ref, host_ref, port)?;
host_result_tcp_connection_ok(caller, rec_ref)
}
_ => unreachable!(),
}
}
pub(crate) fn decode_terminal_size(
caller: &mut wasmtime::Caller<'_, RunWasmGcHost>,
json: &aver::replay::JsonValue,
) -> Result<Option<wasmtime::Rooted<wasmtime::AnyRef>>, wasmtime::Error> {
let fields = expect_record(json, "Terminal.Size")?;
let w = match fields.get("width") {
Some(aver::replay::JsonValue::Int(n)) => *n,
_ => 80,
};
let h = match fields.get("height") {
Some(aver::replay::JsonValue::Int(n)) => *n,
_ => 24,
};
host_terminal_size_make(caller, w, h)
}
pub(super) fn expect_marker<'a>(
json: &'a aver::replay::JsonValue,
allowed: &[&str],
) -> Result<(&'static str, &'a aver::replay::JsonValue), wasmtime::Error> {
let aver::replay::JsonValue::Object(map) = json else {
return Err(wasmtime::Error::msg(format!(
"replay decode: expected wrapper Object, got {:?}",
json
)));
};
if map.len() != 1 {
return Err(wasmtime::Error::msg(format!(
"replay decode: wrapper Object should have 1 key, got {}",
map.len()
)));
}
let (key, val) = map.iter().next().expect("checked above");
for tag in allowed {
if key == tag {
let static_tag: &'static str = match *tag {
"$ok" => "$ok",
"$err" => "$err",
"$some" => "$some",
"$none" => "$none",
"$record" => "$record",
"$tuple" => "$tuple",
"$map" => "$map",
_ => "$unknown",
};
return Ok((static_tag, val));
}
}
Err(wasmtime::Error::msg(format!(
"replay decode: unexpected marker {}, expected one of {:?}",
key, allowed
)))
}
pub(super) fn expect_record<'a>(
json: &'a aver::replay::JsonValue,
expected_type: &str,
) -> Result<&'a std::collections::BTreeMap<String, aver::replay::JsonValue>, wasmtime::Error> {
let (marker, payload) = expect_marker(json, &["$record"])?;
debug_assert_eq!(marker, "$record");
let aver::replay::JsonValue::Object(payload) = payload else {
return Err(wasmtime::Error::msg(
"replay decode $record: payload not an Object",
));
};
match payload.get("type") {
Some(aver::replay::JsonValue::String(s)) if s == expected_type => {}
Some(other) => {
return Err(wasmtime::Error::msg(format!(
"replay decode $record: type {:?} != expected {}",
other, expected_type
)));
}
None => return Err(wasmtime::Error::msg("replay decode $record: missing type")),
}
match payload.get("fields") {
Some(aver::replay::JsonValue::Object(fields)) => Ok(fields),
_ => Err(wasmtime::Error::msg(
"replay decode $record: missing fields object",
)),
}
}