use crate::ty::{Family, Prim, Ty, TypeIndex};
use super::ctype::{CType, ConstPos};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AbiParam {
pub name: String,
pub ty: CType,
}
impl AbiParam {
pub fn new(name: impl Into<String>, ty: CType) -> Self {
Self {
name: name.into(),
ty,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AbiReturn {
pub ret: CType,
pub out_params: Vec<AbiParam>,
}
fn owner_path(name: &str, types: &TypeIndex) -> String {
types
.module_path(name)
.unwrap_or_else(|| panic!("type '{name}' is not declared"))
.to_string()
}
fn struct_tag(name: &str, types: &TypeIndex) -> CType {
CType::StructTag {
module: owner_path(name, types),
name: name.to_string(),
}
}
fn vtable_tag(name: &str, types: &TypeIndex) -> CType {
CType::VtableTag {
module: owner_path(name, types),
name: name.to_string(),
}
}
fn direct_ctype(ty: &Ty, types: &TypeIndex) -> CType {
match ty {
Ty::Prim(p) => match p {
Prim::I8 => CType::Int8,
Prim::I16 => CType::Int16,
Prim::I32 => CType::Int32,
Prim::I64 => CType::Int64,
Prim::U8 => CType::Uint8,
Prim::U16 => CType::Uint16,
Prim::U32 => CType::Uint32,
Prim::U64 => CType::Uint64,
Prim::F32 => CType::Float,
Prim::F64 => CType::Double,
Prim::Bool => CType::Bool,
Prim::String | Prim::Bytes => unreachable!("{p} is not a direct type"),
},
Ty::Enum(e) => CType::Enum {
module: owner_path(e, types),
name: e.clone(),
},
other => unreachable!("{other} is not a direct type"),
}
}
fn ptr_len_slots(name: &str) -> Vec<AbiParam> {
vec![
AbiParam::new(format!("{name}_ptr"), CType::const_ptr(CType::Uint8)),
AbiParam::new(format!("{name}_len"), CType::Size),
]
}
pub fn lower_param(name: &str, ty: &Ty, types: &TypeIndex) -> Vec<AbiParam> {
match ty.family() {
Family::Direct => vec![AbiParam::new(name, direct_ctype(ty, types))],
Family::String | Family::Bytes | Family::Buffer => ptr_len_slots(name),
Family::Object { .. } => {
let iface = ty
.interface_name()
.expect("object family names an interface");
vec![AbiParam::new(
name,
CType::Ptr {
konst: ConstPos::West,
pointee: Box::new(struct_tag(iface, types)),
},
)]
}
Family::Callback { .. } => {
let cb = ty
.callback_interface_name()
.expect("callback family names a callback interface");
vec![
AbiParam::new(format!("{name}_ctx"), CType::ptr(CType::Void)),
AbiParam::new(
format!("{name}_vtable"),
CType::const_ptr(vtable_tag(cb, types)),
),
]
}
Family::Iterator => unreachable!("iterator not valid as parameter"),
}
}
pub fn lower_return(ty: &Ty, types: &TypeIndex) -> AbiReturn {
let no_out = |ret| AbiReturn {
ret,
out_params: vec![],
};
match ty.family() {
Family::Direct => no_out(direct_ctype(ty, types)),
Family::String | Family::Bytes | Family::Buffer => AbiReturn {
ret: CType::const_ptr(CType::Uint8),
out_params: vec![AbiParam::new("out_len", CType::ptr(CType::Size))],
},
Family::Object { .. } => {
let iface = ty
.interface_name()
.expect("object family names an interface");
no_out(CType::ptr(struct_tag(iface, types)))
}
Family::Callback { .. } => unreachable!("callback interfaces are never returned"),
Family::Iterator => {
unreachable!("iterator return handled specially by the function lowering")
}
}
}
pub fn lower_callback_return(ty: &Ty, types: &TypeIndex) -> AbiReturn {
match ty.family() {
Family::String | Family::Bytes | Family::Buffer => AbiReturn {
ret: CType::Void,
out_params: vec![
AbiParam::new("out_ptr", CType::ptr(CType::ptr(CType::Uint8))),
AbiParam::new("out_len", CType::ptr(CType::Size)),
],
},
Family::Callback { .. } | Family::Iterator => {
unreachable!("{ty} is never a callback method return")
}
_ => lower_return(ty, types),
}
}
pub fn callback_result_params(ty: &Ty, types: &TypeIndex) -> Vec<AbiParam> {
match ty.family() {
Family::String | Family::Bytes | Family::Buffer => vec![
AbiParam::new("result_ptr", CType::const_ptr(CType::Uint8)),
AbiParam::new("result_len", CType::Size),
],
_ => vec![AbiParam::new("result", lower_return(ty, types).ret)],
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ty::{TypeDecl, TypeKind};
fn render(params: &[AbiParam]) -> Vec<String> {
params
.iter()
.map(|p| format!("{} {}", p.ty.render_c("weaveffi"), p.name))
.collect()
}
fn types() -> TypeIndex {
let mut types = TypeIndex::default();
for (module, name, kind) in [
("kv", "Store", TypeKind::Interface),
("shared", "Status", TypeKind::Enum),
("events", "Listener", TypeKind::CallbackInterface),
] {
let module = types.push_module(module.into());
types.declare(
name,
TypeDecl {
kind,
module,
index: 0,
},
);
}
types
}
const I32: Ty = Ty::Prim(Prim::I32);
#[test]
fn params_lower_by_family() {
let t = types();
assert_eq!(render(&lower_param("x", &I32, &t)), ["int32_t x"]);
assert_eq!(
render(&lower_param("s", &Ty::Prim(Prim::String), &t)),
["const uint8_t* s_ptr", "size_t s_len"]
);
assert_eq!(
render(&lower_param("data", &Ty::Prim(Prim::Bytes), &t)),
["const uint8_t* data_ptr", "size_t data_len"]
);
assert_eq!(
render(&lower_param("xs", &Ty::List(Box::new(I32)), &t)),
["const uint8_t* xs_ptr", "size_t xs_len"]
);
assert_eq!(
render(&lower_param("c", &Ty::Record("Contact".into()), &t)),
["const uint8_t* c_ptr", "size_t c_len"]
);
assert_eq!(
render(&lower_param(
"s",
&Ty::Optional(Box::new(Ty::Interface("Store".into()))),
&t
)),
["const weaveffi_kv_Store* s"]
);
assert_eq!(
render(&lower_param("s", &Ty::Enum("Status".into()), &t)),
["weaveffi_shared_Status s"]
);
assert_eq!(
render(&lower_param(
"listener",
&Ty::CallbackInterface("Listener".into()),
&t
)),
[
"void* listener_ctx",
"const weaveffi_events_Listener_vtable* listener_vtable"
]
);
}
#[test]
fn returns_lower_by_family() {
let t = types();
let r = lower_return(&Ty::Prim(Prim::Bytes), &t);
assert_eq!(r.ret.render_c("weaveffi"), "const uint8_t*");
assert_eq!(render(&r.out_params), ["size_t* out_len"]);
for ty in [
Ty::Record("Contact".into()),
Ty::RichEnum("Shape".into()),
Ty::List(Box::new(Ty::Record("Contact".into()))),
Ty::List(Box::new(Ty::Interface("Store".into()))),
Ty::Map(Box::new(Ty::Prim(Prim::String)), Box::new(I32)),
Ty::Optional(Box::new(Ty::Prim(Prim::I64))),
] {
let r = lower_return(&ty, &t);
assert_eq!(r.ret.render_c("weaveffi"), "const uint8_t*", "{ty}");
assert_eq!(render(&r.out_params), ["size_t* out_len"], "{ty}");
}
let r = lower_return(&Ty::Optional(Box::new(Ty::Interface("Store".into()))), &t);
assert_eq!(r.ret.render_c("weaveffi"), "weaveffi_kv_Store*");
assert!(r.out_params.is_empty());
assert_eq!(
lower_return(&Ty::Enum("Status".into()), &t)
.ret
.render_c("weaveffi"),
"weaveffi_shared_Status"
);
}
#[test]
fn callback_method_returns_use_consumer_allocated_runs() {
let t = types();
let r = lower_callback_return(&Ty::Prim(Prim::String), &t);
assert_eq!(r.ret, CType::Void);
assert_eq!(
render(&r.out_params),
["uint8_t** out_ptr", "size_t* out_len"]
);
let r = lower_callback_return(&Ty::Interface("Store".into()), &t);
assert_eq!(r.ret.render_c("weaveffi"), "weaveffi_kv_Store*");
assert!(r.out_params.is_empty());
assert_eq!(lower_callback_return(&I32, &t).ret, CType::Int32);
assert_eq!(
render(&lower_param(
"l",
&Ty::Optional(Box::new(Ty::CallbackInterface("Listener".into()))),
&t
)),
[
"void* l_ctx",
"const weaveffi_events_Listener_vtable* l_vtable"
]
);
}
#[test]
fn callback_results() {
let t = types();
assert_eq!(
render(&callback_result_params(
&Ty::List(Box::new(Ty::Prim(Prim::String))),
&t
)),
["const uint8_t* result_ptr", "size_t result_len"]
);
assert_eq!(
render(&callback_result_params(&Ty::Prim(Prim::Bytes), &t)),
["const uint8_t* result_ptr", "size_t result_len"]
);
assert_eq!(
render(&callback_result_params(&I32, &t)),
["int32_t result"]
);
}
}