use anyhow::{anyhow, Result};
use wit_parser::abi::WasmType;
use wit_parser::{Resolve, Type};
use super::super::super::abi::emit::{wasm_type_to_val, BlobSlice};
use super::super::super::abi::flat_types;
use super::super::blob::NameInterner;
const ISSUES_URL: &str = "https://github.com/ejrgilbert/splicer/issues";
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum Cell {
Bool { flat_slot: u32 },
IntegerSignExt { flat_slot: u32 },
IntegerZeroExt { flat_slot: u32 },
Integer64 { flat_slot: u32 },
FloatingF32 { flat_slot: u32 },
FloatingF64 { flat_slot: u32 },
Text { ptr_slot: u32, len_slot: u32 },
Bytes { ptr_slot: u32, len_slot: u32 },
Char { flat_slot: u32 },
EnumCase {
flat_slot: u32,
type_name: BlobSlice,
case_names: Vec<BlobSlice>,
},
RecordOf {
type_name: BlobSlice,
fields: Vec<(BlobSlice, u32)>,
},
TupleOf { children: Vec<u32> },
Option { disc_slot: u32, child_idx: u32 },
Result {
disc_slot: u32,
ok_idx: Option<u32>,
err_idx: Option<u32>,
},
Flags {
flat_slot: u32,
type_name: BlobSlice,
flag_names: Vec<BlobSlice>,
},
Variant {
disc_slot: u32,
per_case_payload: Vec<Option<u32>>,
type_name: BlobSlice,
case_names: Vec<BlobSlice>,
},
Handle {
flat_slot: u32,
type_name: BlobSlice,
kind: HandleKind,
},
ListOf {
list_idx: u32,
ptr_slot: u32,
len_slot: u32,
element_plan: Box<LiftPlan>,
arm_guards: Vec<ArmGuard>,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct ArmGuard {
pub(crate) disc_slot: u32,
pub(crate) expected_disc: u32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum HandleKind {
Resource,
Stream,
Future,
ErrorContext,
}
impl HandleKind {
pub(crate) fn cell_disc_case(self) -> &'static str {
match self {
HandleKind::Resource => "resource-handle",
HandleKind::Stream => "stream-handle",
HandleKind::Future => "future-handle",
HandleKind::ErrorContext => "error-context-handle",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ListElementClass {
Scalar,
PrestagedChar,
PrestagedChildIdx,
PrestagedTupleIndices,
PrestagedHandle,
PrestagedFlags,
PrestagedRecord,
PrestagedVariant,
}
impl Cell {
pub(crate) fn list_element_class(&self) -> Option<ListElementClass> {
match self {
Cell::Char { .. } => Some(ListElementClass::PrestagedChar),
Cell::Option { .. } | Cell::Result { .. } => Some(ListElementClass::PrestagedChildIdx),
Cell::TupleOf { .. } => Some(ListElementClass::PrestagedTupleIndices),
Cell::Handle { .. } => Some(ListElementClass::PrestagedHandle),
Cell::Flags { .. } => Some(ListElementClass::PrestagedFlags),
Cell::RecordOf { .. } => Some(ListElementClass::PrestagedRecord),
Cell::Variant { .. } => Some(ListElementClass::PrestagedVariant),
Cell::Bool { .. }
| Cell::IntegerSignExt { .. }
| Cell::IntegerZeroExt { .. }
| Cell::Integer64 { .. }
| Cell::FloatingF32 { .. }
| Cell::FloatingF64 { .. }
| Cell::Text { .. }
| Cell::Bytes { .. }
| Cell::EnumCase { .. } => Some(ListElementClass::Scalar),
Cell::ListOf { .. } => None,
}
}
pub(crate) fn allowed_as_list_element(&self) -> bool {
self.list_element_class().is_some()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct LiftPlan {
pub(super) cells: Vec<Cell>,
pub flat_slot_count: u32,
slot_widening: Vec<Option<WasmType>>,
root: u32,
pub source_ty: Type,
}
impl LiftPlan {
pub(super) fn for_type(ty: &Type, resolve: &Resolve, names: &mut NameInterner) -> Result<Self> {
let mut builder = LiftPlanBuilder::new();
let root = builder.push(ty, resolve, names);
if let Some(err) = builder.error {
return Err(err);
}
Ok(builder.into_plan(root, *ty))
}
pub(crate) fn cell_count(&self) -> u32 {
self.cells.len() as u32
}
pub(crate) fn root(&self) -> u32 {
self.root
}
pub(crate) fn widening_for(&self, flat_slot: u32) -> Option<WasmType> {
self.slot_widening
.get(flat_slot as usize)
.copied()
.flatten()
}
fn walk_cells_recursive(&self) -> Vec<&Cell> {
let mut out = Vec::with_capacity(self.cells.len());
for cell in &self.cells {
out.push(cell);
if let Cell::ListOf { element_plan, .. } = cell {
out.extend(element_plan.walk_cells_recursive());
}
}
out
}
pub(crate) fn contains_char(&self) -> bool {
self.walk_cells_recursive()
.iter()
.any(|c| matches!(c, Cell::Char { .. }))
}
pub(crate) fn has_list_elem_handle(&self) -> bool {
self.list_specs().any(|spec| {
spec.element_plan
.cells
.iter()
.any(|c| matches!(c, Cell::Handle { .. }))
})
}
pub(crate) fn has_list_elem_flags(&self) -> bool {
self.list_specs().any(|spec| {
spec.element_plan
.cells
.iter()
.any(|c| matches!(c, Cell::Flags { .. }))
})
}
pub(crate) fn has_list_elem_record(&self) -> bool {
self.list_specs().any(|spec| {
spec.element_plan
.cells
.iter()
.any(|c| matches!(c, Cell::RecordOf { .. }))
})
}
pub(crate) fn has_list_elem_variant(&self) -> bool {
self.list_specs().any(|spec| {
spec.element_plan
.cells
.iter()
.any(|c| matches!(c, Cell::Variant { .. }))
})
}
pub(super) fn stub_for(source_ty: Type) -> Self {
Self {
cells: vec![Cell::Bool { flat_slot: 0 }],
flat_slot_count: 1,
slot_widening: vec![None],
root: 0,
source_ty,
}
}
pub(crate) fn list_specs(&self) -> impl Iterator<Item = ListSpec<'_>> + '_ {
self.cells.iter().filter_map(|op| match op {
Cell::ListOf {
list_idx,
len_slot,
element_plan,
arm_guards,
..
} => Some(ListSpec {
list_idx: *list_idx,
len_slot: *len_slot,
element_plan,
arm_guards,
}),
_ => None,
})
}
}
#[derive(Clone, Copy)]
pub(crate) struct ListSpec<'a> {
pub list_idx: u32,
pub len_slot: u32,
pub element_plan: &'a LiftPlan,
pub arm_guards: &'a [ArmGuard],
}
pub(super) struct LiftPlanBuilder {
cells: Vec<Cell>,
next_flat_slot: u32,
slot_widening: Vec<Option<WasmType>>,
next_list_idx: u32,
arm_guard_stack: Vec<ArmGuard>,
error: Option<anyhow::Error>,
}
impl LiftPlanBuilder {
pub(super) fn new() -> Self {
Self {
cells: Vec::new(),
slot_widening: Vec::new(),
next_flat_slot: 0,
next_list_idx: 0,
arm_guard_stack: Vec::new(),
error: None,
}
}
fn record_error(&mut self, err: anyhow::Error) {
if self.error.is_none() {
self.error = Some(err);
}
}
pub(super) fn push(&mut self, ty: &Type, resolve: &Resolve, names: &mut NameInterner) -> u32 {
match ty {
Type::Bool => {
let flat_slot = self.bump_flat_slot();
self.push_cell(Cell::Bool { flat_slot })
}
Type::S8 | Type::S16 | Type::S32 => {
let flat_slot = self.bump_flat_slot();
self.push_cell(Cell::IntegerSignExt { flat_slot })
}
Type::U8 | Type::U16 | Type::U32 => {
let flat_slot = self.bump_flat_slot();
self.push_cell(Cell::IntegerZeroExt { flat_slot })
}
Type::S64 | Type::U64 => {
let flat_slot = self.bump_flat_slot();
self.push_cell(Cell::Integer64 { flat_slot })
}
Type::F32 => {
let flat_slot = self.bump_flat_slot();
self.push_cell(Cell::FloatingF32 { flat_slot })
}
Type::F64 => {
let flat_slot = self.bump_flat_slot();
self.push_cell(Cell::FloatingF64 { flat_slot })
}
Type::String => {
let ptr_slot = self.bump_flat_slot();
let len_slot = self.bump_flat_slot();
self.push_cell(Cell::Text { ptr_slot, len_slot })
}
Type::Char => {
let flat_slot = self.bump_flat_slot();
self.push_cell(Cell::Char { flat_slot })
}
Type::ErrorContext => {
let type_name = names.intern("");
let flat_slot = self.bump_flat_slot();
self.push_cell(Cell::Handle {
flat_slot,
type_name,
kind: HandleKind::ErrorContext,
})
}
Type::Id(id) => match &resolve.types[*id].kind {
wit_parser::TypeDefKind::List(Type::U8) => {
let ptr_slot = self.bump_flat_slot();
let len_slot = self.bump_flat_slot();
self.push_cell(Cell::Bytes { ptr_slot, len_slot })
}
wit_parser::TypeDefKind::Enum(_) => {
let info = enum_lift_info_for_type(ty, resolve)
.expect("Enum kind implies enum-info available");
let type_name = names.intern(&info.type_name);
let case_names = info.item_names.iter().map(|n| names.intern(n)).collect();
let flat_slot = self.bump_flat_slot();
self.push_cell(Cell::EnumCase {
flat_slot,
type_name,
case_names,
})
}
wit_parser::TypeDefKind::Record(_) => self.push_record(ty, resolve, names),
wit_parser::TypeDefKind::Tuple(_) => self.push_tuple(ty, resolve, names),
wit_parser::TypeDefKind::Type(t) => self.push(t, resolve, names),
wit_parser::TypeDefKind::List(elem) => self.push_list_of(elem, resolve, names),
wit_parser::TypeDefKind::Variant(_) => self.push_variant(ty, resolve, names),
wit_parser::TypeDefKind::Flags(_) => {
let info = flags_lift_info_for_type(ty, resolve)
.expect("Flags kind implies flags-info available");
let type_name = names.intern(&info.type_name);
let flag_names = info.item_names.iter().map(|n| names.intern(n)).collect();
let flat_slot = self.bump_flat_slot();
self.push_cell(Cell::Flags {
flat_slot,
type_name,
flag_names,
})
}
wit_parser::TypeDefKind::Option(inner) => self.push_option(inner, resolve, names),
wit_parser::TypeDefKind::Result(_) => self.push_result(ty, resolve, names),
wit_parser::TypeDefKind::Handle(h) => self.push_handle(h, resolve, names),
wit_parser::TypeDefKind::Stream(elem) => {
self.push_stream_or_future(elem.as_ref(), HandleKind::Stream, resolve, names)
}
wit_parser::TypeDefKind::Future(elem) => {
self.push_stream_or_future(elem.as_ref(), HandleKind::Future, resolve, names)
}
wit_parser::TypeDefKind::Resource => {
unreachable!(
"tier-2 lift: bare `Resource` at payload position is \
forbidden by canonical ABI"
)
}
wit_parser::TypeDefKind::Unknown => {
unreachable!("tier-2 lift: unresolved `Unknown` typedef")
}
wit_parser::TypeDefKind::FixedLengthList(_, _)
| wit_parser::TypeDefKind::Map(_, _) => {
todo!(
"tier-2 lift: unsupported TypeDefKind {:?}",
&resolve.types[*id].kind
)
}
},
}
}
fn bump_flat_slot(&mut self) -> u32 {
let r = self.next_flat_slot;
self.next_flat_slot = self
.next_flat_slot
.checked_add(1)
.expect("LiftPlanBuilder flat-slot counter overflowed u32");
if self.slot_widening.len() < self.next_flat_slot as usize {
self.slot_widening.push(None);
}
r
}
fn set_widening(&mut self, flat_slot: u32, joined_ty: WasmType) {
debug_assert!(
(flat_slot as usize) < self.slot_widening.len(),
"set_widening called for flat_slot {flat_slot} before bump_flat_slot reached it \
(slot_widening len = {})",
self.slot_widening.len(),
);
if let Some(prev) = self.slot_widening[flat_slot as usize] {
debug_assert_eq!(
wasm_type_to_val(prev),
wasm_type_to_val(joined_ty),
"set_widening overwriting slot {flat_slot} with a different joined type \
({prev:?} vs {joined_ty:?}) — joined should be structural"
);
}
self.slot_widening[flat_slot as usize] = Some(joined_ty);
}
fn push_cell(&mut self, cell: Cell) -> u32 {
let idx = self.cells.len() as u32;
self.cells.push(cell);
idx
}
fn push_record(&mut self, ty: &Type, resolve: &Resolve, names: &mut NameInterner) -> u32 {
let Type::Id(id) = ty else {
unreachable!("Record kind came from non-Id type")
};
let typedef = &resolve.types[*id];
let wit_parser::TypeDefKind::Record(r) = &typedef.kind else {
unreachable!("Record kind came from non-Record TypeDefKind")
};
let type_name = names.intern(typedef.name.as_deref().unwrap_or(""));
let mut fields = Vec::with_capacity(r.fields.len());
for field in &r.fields {
let name_slice = names.intern(&field.name);
let child_idx = self.push(&field.ty, resolve, names);
fields.push((name_slice, child_idx));
}
self.push_cell(Cell::RecordOf { type_name, fields })
}
fn push_tuple(&mut self, ty: &Type, resolve: &Resolve, names: &mut NameInterner) -> u32 {
let Type::Id(id) = ty else {
unreachable!("Tuple kind came from non-Id type")
};
let typedef = &resolve.types[*id];
let wit_parser::TypeDefKind::Tuple(t) = &typedef.kind else {
unreachable!("Tuple kind came from non-Tuple TypeDefKind")
};
let mut children = Vec::with_capacity(t.types.len());
for elem_ty in &t.types {
children.push(self.push(elem_ty, resolve, names));
}
debug_assert!(
!children.is_empty(),
"Cell::TupleOf must have ≥1 child — WIT forbids 0-tuples",
);
self.push_cell(Cell::TupleOf { children })
}
fn push_option(&mut self, inner: &Type, resolve: &Resolve, names: &mut NameInterner) -> u32 {
let disc_slot = self.bump_flat_slot();
let child_idx = self.push(inner, resolve, names);
self.push_cell(Cell::Option {
disc_slot,
child_idx,
})
}
fn push_result(&mut self, ty: &Type, resolve: &Resolve, names: &mut NameInterner) -> u32 {
let Type::Id(id) = ty else {
unreachable!("Result kind came from non-Id type")
};
let wit_parser::TypeDefKind::Result(r) = &resolve.types[*id].kind else {
unreachable!("Result kind came from non-Result TypeDefKind")
};
let r = r.clone();
let joined = flat_types(resolve, ty, None)
.expect("result<T, E> must flatten within MAX_FLAT_PARAMS");
let disc_slot = self.bump_flat_slot();
let arms_base = self.next_flat_slot;
let [ok_idx, err_idx]: [Option<u32>; 2] = self
.push_disc_arms(disc_slot, arms_base, &joined, [r.ok, r.err], resolve, names)
.try_into()
.expect("push_disc_arms with 2-element input returns 2-element output");
self.push_cell(Cell::Result {
disc_slot,
ok_idx,
err_idx,
})
}
fn push_arm<R>(
&mut self,
disc_slot: u32,
expected_disc: u32,
walk: impl FnOnce(&mut Self) -> R,
) -> R {
self.arm_guard_stack.push(ArmGuard {
disc_slot,
expected_disc,
});
let r = walk(self);
self.arm_guard_stack.pop();
r
}
fn record_arm_widening(
&mut self,
arm: Option<&Type>,
arms_base: u32,
joined: &[WasmType],
resolve: &Resolve,
) {
let Some(t) = arm else { return };
let arm_flat =
flat_types(resolve, t, None).expect("arm flat fits — joined fit, so arm fits");
for (i, &arm_ty) in arm_flat.iter().enumerate() {
let joined_ty = joined[1 + i];
if wasm_type_to_val(arm_ty) != wasm_type_to_val(joined_ty) {
self.set_widening(arms_base + i as u32, joined_ty);
}
}
}
fn push_variant(&mut self, ty: &Type, resolve: &Resolve, names: &mut NameInterner) -> u32 {
let Type::Id(id) = ty else {
unreachable!("Variant kind came from non-Id type")
};
let typedef = &resolve.types[*id];
let wit_parser::TypeDefKind::Variant(v) = &typedef.kind else {
unreachable!("Variant kind came from non-Variant TypeDefKind")
};
let v = v.clone();
let info = variant_lift_info_for_type(ty, resolve)
.expect("Variant kind implies variant-info available");
let type_name = names.intern(&info.type_name);
let case_names = info.item_names.iter().map(|n| names.intern(n)).collect();
let joined =
flat_types(resolve, ty, None).expect("variant must flatten within MAX_FLAT_PARAMS");
let disc_slot = self.bump_flat_slot();
let arms_base = self.next_flat_slot;
let per_case_payload = self.push_disc_arms(
disc_slot,
arms_base,
&joined,
v.cases.iter().map(|c| c.ty),
resolve,
names,
);
self.push_cell(Cell::Variant {
disc_slot,
per_case_payload,
type_name,
case_names,
})
}
fn push_disc_arms<I>(
&mut self,
disc_slot: u32,
arms_base: u32,
joined: &[WasmType],
arms: I,
resolve: &Resolve,
names: &mut NameInterner,
) -> Vec<Option<u32>>
where
I: IntoIterator<Item = Option<Type>>,
{
let mut max_after = arms_base;
let mut indices: Vec<Option<u32>> = Vec::new();
for (disc, arm) in arms.into_iter().enumerate() {
self.next_flat_slot = arms_base;
let child_idx = self.push_arm(disc_slot, disc as u32, |b| {
arm.map(|t| b.push(&t, resolve, names))
});
max_after = max_after.max(self.next_flat_slot);
self.record_arm_widening(arm.as_ref(), arms_base, joined, resolve);
indices.push(child_idx);
}
self.next_flat_slot = max_after;
indices
}
fn push_handle(
&mut self,
h: &wit_parser::Handle,
resolve: &Resolve,
names: &mut NameInterner,
) -> u32 {
let resource_id = match h {
wit_parser::Handle::Own(id) | wit_parser::Handle::Borrow(id) => *id,
};
let type_name = names.intern(resolve.types[resource_id].name.as_deref().unwrap_or(""));
let flat_slot = self.bump_flat_slot();
self.push_cell(Cell::Handle {
flat_slot,
type_name,
kind: HandleKind::Resource,
})
}
fn push_stream_or_future(
&mut self,
elem: Option<&Type>,
kind: HandleKind,
resolve: &Resolve,
names: &mut NameInterner,
) -> u32 {
let elem_name = elem
.and_then(|t| match t {
Type::Id(id) => Some(*id),
_ => None,
})
.map(|id| {
let mut tid = id;
loop {
let td = &resolve.types[tid];
if let Some(name) = td.name.as_deref() {
return name;
}
match &td.kind {
wit_parser::TypeDefKind::Type(Type::Id(next)) => tid = *next,
wit_parser::TypeDefKind::Handle(
wit_parser::Handle::Own(next) | wit_parser::Handle::Borrow(next),
) => tid = *next,
_ => return "",
}
}
})
.unwrap_or("");
let type_name = names.intern(elem_name);
let flat_slot = self.bump_flat_slot();
self.push_cell(Cell::Handle {
flat_slot,
type_name,
kind,
})
}
fn push_list_of(&mut self, elem: &Type, resolve: &Resolve, names: &mut NameInterner) -> u32 {
let list_idx = self.next_list_idx;
self.next_list_idx += 1;
let ptr_slot = self.bump_flat_slot();
let len_slot = self.bump_flat_slot();
let element_plan = match LiftPlan::for_type(elem, resolve, names) {
Ok(plan) => plan,
Err(err) => {
self.record_error(err);
LiftPlan::stub_for(*elem)
}
};
if !element_plan
.cells
.iter()
.all(|c| c.allowed_as_list_element())
{
self.record_error(anyhow!(
"`list<T>` element type {elem:?} contains a cell shape that \
isn't yet supported as a list element (allowed today: bool, \
integers, floats, string, list<u8>, enum, char, option, \
result, tuple, flags, record, variant, \
own/borrow/stream/future/error-context handles — with \
allowed inner cells throughout). Still gated: nested list. \
File a request at {ISSUES_URL} to bump priority."
));
}
let arm_guards = self.arm_guard_stack.clone();
self.push_cell(Cell::ListOf {
list_idx,
ptr_slot,
len_slot,
element_plan: Box::new(element_plan),
arm_guards,
})
}
pub(super) fn into_plan(self, root: u32, source_ty: Type) -> LiftPlan {
debug_assert_eq!(
self.slot_widening.len() as u32,
self.next_flat_slot,
"slot_widening must mirror flat_slot_count (one entry per bump_flat_slot)",
);
LiftPlan {
cells: self.cells,
flat_slot_count: self.next_flat_slot,
slot_widening: self.slot_widening,
root,
source_ty,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct NamedListInfo {
pub(super) type_name: String,
pub(super) item_names: Vec<String>,
}
fn lift_info_for_type<F>(ty: &Type, resolve: &Resolve, kind_extract: F) -> Option<NamedListInfo>
where
F: FnOnce(&wit_parser::TypeDefKind) -> Option<Vec<String>>,
{
let Type::Id(id) = ty else {
return None;
};
let typedef = &resolve.types[*id];
let item_names = kind_extract(&typedef.kind)?;
let type_name = typedef.name.as_ref()?.clone();
Some(NamedListInfo {
type_name,
item_names,
})
}
fn enum_lift_info_for_type(ty: &Type, resolve: &Resolve) -> Option<NamedListInfo> {
lift_info_for_type(ty, resolve, |k| match k {
wit_parser::TypeDefKind::Enum(e) => Some(e.cases.iter().map(|c| c.name.clone()).collect()),
_ => None,
})
}
fn variant_lift_info_for_type(ty: &Type, resolve: &Resolve) -> Option<NamedListInfo> {
lift_info_for_type(ty, resolve, |k| match k {
wit_parser::TypeDefKind::Variant(v) => {
Some(v.cases.iter().map(|c| c.name.clone()).collect())
}
_ => None,
})
}
fn flags_lift_info_for_type(ty: &Type, resolve: &Resolve) -> Option<NamedListInfo> {
lift_info_for_type(ty, resolve, |k| match k {
wit_parser::TypeDefKind::Flags(fl) => {
Some(fl.flags.iter().map(|f| f.name.clone()).collect())
}
_ => None,
})
}