use crate::ir::contract::PackageInfo;
use crate::ir::definitions::StreamMode;
use crate::ir::ids::{
CallbackId, ClassId, EnumId, FieldName, FunctionId, MethodId, ParamName, RecordId, StreamId,
VariantName,
};
use crate::ir::ops::{ReadSeq, WriteSeq};
use crate::ir::plan::{AbiType, Mutability, Transport};
use crate::ir::types::TypeExpr;
use boltffi_ffi_rules::callable::ExecutionKind;
use boltffi_ffi_rules::naming::{
CreateFn, GlobalSymbol, Name, RegisterFn, VtableField, VtableType,
};
use boltffi_ffi_rules::transport::{
EnumTagStrategy, ErrorReturnStrategy, ParamContract, ReturnContract, ReturnInvocationContext,
ReturnPlatform, ValueReturnMethod, ValueReturnStrategy,
};
#[derive(Debug, Clone)]
pub struct AbiContract {
pub package: PackageInfo,
pub calls: Vec<AbiCall>,
pub callbacks: Vec<AbiCallbackInvocation>,
pub streams: Vec<AbiStream>,
pub records: Vec<AbiRecord>,
pub enums: Vec<AbiEnum>,
pub free_buf: Name<GlobalSymbol>,
pub atomic_cas: Name<GlobalSymbol>,
}
#[derive(Debug, Clone)]
pub struct AbiRecord {
pub id: RecordId,
pub decode_ops: ReadSeq,
pub encode_ops: WriteSeq,
pub is_blittable: bool,
pub size: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct AbiEnum {
pub id: EnumId,
pub decode_ops: ReadSeq,
pub encode_ops: WriteSeq,
pub is_c_style: bool,
pub codec_tag_strategy: EnumTagStrategy,
pub variants: Vec<AbiEnumVariant>,
}
impl AbiEnum {
pub fn resolve_codec_tag(&self, ordinal: usize, discriminant: i128) -> i128 {
self.codec_tag_strategy.resolve_tag(ordinal, discriminant)
}
}
#[derive(Debug, Clone)]
pub struct AbiEnumVariant {
pub name: VariantName,
pub discriminant: i128,
pub payload: AbiEnumPayload,
}
#[derive(Debug, Clone)]
pub enum AbiEnumPayload {
Unit,
Tuple(Vec<AbiEnumField>),
Struct(Vec<AbiEnumField>),
}
#[derive(Debug, Clone)]
pub struct AbiEnumField {
pub name: FieldName,
pub type_expr: TypeExpr,
pub decode: ReadSeq,
pub encode: WriteSeq,
}
#[derive(Debug, Clone)]
pub enum StreamItemTransport {
WireEncoded { decode_ops: ReadSeq },
}
#[derive(Debug, Clone)]
pub struct AbiStream {
pub class_id: ClassId,
pub stream_id: StreamId,
pub mode: StreamMode,
pub item: StreamItemTransport,
pub item_transport: Transport,
pub item_size: Option<usize>,
pub subscribe: Name<GlobalSymbol>,
pub poll: Name<GlobalSymbol>,
pub pop_batch: Name<GlobalSymbol>,
pub wait: Name<GlobalSymbol>,
pub unsubscribe: Name<GlobalSymbol>,
pub free: Name<GlobalSymbol>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum CallId {
Function(FunctionId),
Method {
class_id: ClassId,
method_id: MethodId,
},
Constructor {
class_id: ClassId,
index: usize,
},
RecordMethod {
record_id: RecordId,
method_id: MethodId,
},
RecordConstructor {
record_id: RecordId,
index: usize,
},
EnumMethod {
enum_id: EnumId,
method_id: MethodId,
},
EnumConstructor {
enum_id: EnumId,
index: usize,
},
}
#[derive(Debug, Clone)]
pub struct AbiCall {
pub id: CallId,
pub symbol: Name<GlobalSymbol>,
pub mode: CallMode,
pub params: Vec<AbiParam>,
pub returns: ReturnShape,
pub error: ErrorTransport,
}
impl AbiCall {
pub fn is_value_type_call(&self) -> bool {
matches!(
self.id,
CallId::RecordMethod { .. }
| CallId::RecordConstructor { .. }
| CallId::EnumMethod { .. }
| CallId::EnumConstructor { .. }
)
}
}
#[derive(Debug, Clone)]
pub enum CallMode {
Sync,
Async(Box<AsyncCall>),
}
#[derive(Debug, Clone)]
pub struct AsyncCall {
pub poll: Name<GlobalSymbol>,
pub complete: Name<GlobalSymbol>,
pub cancel: Name<GlobalSymbol>,
pub free: Name<GlobalSymbol>,
pub result: ReturnShape,
pub error: ErrorTransport,
}
#[derive(Debug, Clone)]
pub struct AbiParam {
pub name: ParamName,
pub abi_type: AbiType,
pub role: ParamRole,
}
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum ParamRole {
Input {
contract: ParamContract,
transport: Transport,
mutability: Mutability,
len_param: Option<ParamName>,
decode_ops: Option<ReadSeq>,
encode_ops: Option<WriteSeq>,
},
SyntheticLen {
for_param: ParamName,
},
CallbackContext {
for_param: ParamName,
},
OutLen {
for_param: ParamName,
},
OutDirect,
StatusOut,
}
#[derive(Debug, Clone)]
pub struct ReturnShape {
pub contract: ReturnContract,
pub transport: Option<Transport>,
pub decode_ops: Option<ReadSeq>,
pub encode_ops: Option<WriteSeq>,
}
impl ReturnShape {
pub fn void() -> Self {
Self {
contract: ReturnContract::infallible(ValueReturnStrategy::Void),
transport: None,
decode_ops: None,
encode_ops: None,
}
}
pub fn from_transport_with_ops(
transport: Transport,
decode_ops: ReadSeq,
encode_ops: WriteSeq,
) -> Self {
Self {
contract: ReturnContract::infallible(transport.value_return_strategy()),
transport: Some(transport),
decode_ops: Some(decode_ops),
encode_ops: Some(encode_ops),
}
}
pub fn with_error_strategy(mut self, error_strategy: ErrorReturnStrategy) -> Self {
self.contract = ReturnContract::new(self.contract.value_strategy(), error_strategy);
self
}
pub fn return_contract(&self) -> ReturnContract {
self.contract
}
pub fn value_return_strategy(&self) -> ValueReturnStrategy {
self.contract.value_strategy()
}
pub fn value_return_method(
&self,
context: ReturnInvocationContext,
platform: ReturnPlatform,
) -> ValueReturnMethod {
self.contract.value_return_method(context, platform)
}
pub fn error_return_strategy(&self) -> ErrorReturnStrategy {
self.contract.error_strategy()
}
}
#[derive(Debug, Clone)]
pub enum ErrorTransport {
None,
StatusCode,
Encoded {
decode_ops: ReadSeq,
encode_ops: Option<WriteSeq>,
},
}
impl ErrorTransport {
pub fn return_strategy(&self) -> ErrorReturnStrategy {
match self {
Self::None => ErrorReturnStrategy::None,
Self::StatusCode => ErrorReturnStrategy::StatusCode,
Self::Encoded { .. } => ErrorReturnStrategy::Encoded,
}
}
}
#[derive(Debug, Clone)]
pub struct AbiCallbackInvocation {
pub callback_id: CallbackId,
pub vtable_type: Name<VtableType>,
pub register_fn: Name<RegisterFn>,
pub create_fn: Name<CreateFn>,
pub methods: Vec<AbiCallbackMethod>,
}
#[derive(Debug, Clone)]
pub struct AbiCallbackMethod {
pub id: MethodId,
pub vtable_field: Name<VtableField>,
pub execution_kind: ExecutionKind,
pub params: Vec<AbiParam>,
pub returns: ReturnShape,
pub error: ErrorTransport,
}
impl AbiCallbackMethod {
pub fn is_async(&self) -> bool {
self.execution_kind == ExecutionKind::Async
}
pub fn execution_kind(&self) -> ExecutionKind {
self.execution_kind
}
}
impl AbiParam {
pub fn is_hidden(&self) -> bool {
matches!(
self.role,
ParamRole::SyntheticLen { .. }
| ParamRole::CallbackContext { .. }
| ParamRole::OutLen { .. }
| ParamRole::OutDirect
| ParamRole::StatusOut
)
}
pub fn transport(&self) -> Option<&Transport> {
match &self.role {
ParamRole::Input { transport, .. } => Some(transport),
_ => None,
}
}
pub fn param_contract(&self) -> Option<ParamContract> {
match &self.role {
ParamRole::Input { contract, .. } => Some(*contract),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::ids::FunctionId;
use crate::ir::plan::ScalarOrigin;
use crate::ir::types::PrimitiveType;
use boltffi_ffi_rules::naming;
use boltffi_ffi_rules::transport::{
ParamContract, ParamPassingStrategy, ParamValueStrategy, ReturnContract,
ScalarParamStrategy, ScalarReturnStrategy, ValueReturnStrategy,
};
fn scalar_param(name: &str, abi: AbiType) -> AbiParam {
AbiParam {
name: ParamName::new(name),
abi_type: abi,
role: ParamRole::Input {
contract: ParamContract::new(
ParamValueStrategy::Scalar(ScalarParamStrategy::PrimitiveValue),
ParamPassingStrategy::ByValue,
),
transport: Transport::Scalar(ScalarOrigin::Primitive(PrimitiveType::I32)),
mutability: Mutability::Shared,
len_param: None,
decode_ops: None,
encode_ops: None,
},
}
}
#[test]
fn scalar_param_exposes_transport() {
let param = scalar_param("v", AbiType::I32);
assert!(matches!(
param.transport(),
Some(Transport::Scalar(ScalarOrigin::Primitive(
PrimitiveType::I32
)))
));
}
#[test]
fn scalar_param_exposes_param_contract() {
let param = scalar_param("v", AbiType::I32);
assert_eq!(
param.param_contract(),
Some(ParamContract::new(
ParamValueStrategy::Scalar(ScalarParamStrategy::PrimitiveValue),
ParamPassingStrategy::ByValue,
))
);
}
#[test]
fn hidden_param_has_no_transport() {
let param = AbiParam {
name: ParamName::new("len"),
abi_type: AbiType::U64,
role: ParamRole::SyntheticLen {
for_param: ParamName::new("buf"),
},
};
assert!(param.is_hidden());
assert!(param.transport().is_none());
}
#[test]
fn return_shape_void() {
let ret = ReturnShape::void();
assert!(ret.transport.is_none());
assert!(ret.decode_ops.is_none());
assert!(ret.encode_ops.is_none());
}
#[test]
fn abi_call_uses_return_shape() {
let call = AbiCall {
id: CallId::Function(FunctionId::new("f")),
symbol: naming::function_ffi_name("f"),
mode: CallMode::Sync,
params: vec![scalar_param("x", AbiType::I32)],
returns: ReturnShape {
contract: ReturnContract::infallible(ValueReturnStrategy::Scalar(
ScalarReturnStrategy::PrimitiveValue,
)),
transport: Some(Transport::Scalar(ScalarOrigin::Primitive(
PrimitiveType::I32,
))),
decode_ops: None,
encode_ops: None,
},
error: ErrorTransport::None,
};
assert!(matches!(
call.returns.transport,
Some(Transport::Scalar(ScalarOrigin::Primitive(
PrimitiveType::I32
)))
));
}
}