use std::char;
use wasm_bindgen_shared::identifier::is_valid_ident;
use wasm_bindgen_shared::tys::*;
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum GenericImportKey {
Cast,
Shim(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Descriptor {
I8,
U8,
ClampedU8,
I16,
U16,
I32,
U32,
I64,
U64,
I64AsF64,
U64AsF64,
I128,
U128,
F32,
F64,
Boolean,
Function(Box<Function>),
Closure(Box<Closure>),
Ref(Box<Descriptor>),
RefMut(Box<Descriptor>),
Slice(Box<Descriptor>),
Vector(Box<Descriptor>),
CachedString,
String,
Externref,
NamedExternref(String),
Enum {
name: String,
hole: u32,
unique_crate_identifier: String,
module_hash: u32,
},
StringEnum {
name: String,
invalid: u32,
hole: u32,
unique_crate_identifier: String,
module_hash: u32,
},
DynamicUnion {
name: String,
variant_types: Vec<Descriptor>,
unique_crate_identifier: String,
module_hash: u32,
},
RustStruct {
name: String,
unique_crate_identifier: String,
},
Char,
Option(Box<Descriptor>),
Nullable(Box<Descriptor>),
Result(Box<Descriptor>),
Unit,
NonNull,
RawPointer,
SeqMode(SeqMode, Box<Descriptor>),
}
#[derive(Debug, PartialEq, Eq)]
pub enum SequenceCell<'a> {
InPlace(VectorKind),
Encoded(&'a Descriptor),
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum SeqValue {
#[default]
View,
Array,
Copy,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SeqMode {
pub vec: bool,
pub value: SeqValue,
pub explicit: bool,
}
impl std::fmt::Debug for SeqMode {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let key = if self.vec { "vec" } else { "slice" };
let value = match self.value {
SeqValue::View => "view",
SeqValue::Array => "array",
SeqValue::Copy => "copy",
};
write!(f, "{key} = {value}")
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum ItemKind {
Value,
StringEnum,
DynamicUnion,
}
#[derive(Clone, Copy)]
pub struct ItemOrigin<'a> {
pub kind: ItemKind,
pub unique_crate_identifier: &'a str,
pub module_hash: Option<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Function {
pub arguments: Vec<Descriptor>,
pub shim_idx: u32,
pub ret: Descriptor,
pub inner_ret: Option<Descriptor>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Closure {
pub owned: bool,
pub function: Function,
pub mutable: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum VectorKind {
I8,
U8,
ClampedU8,
I16,
U16,
I32,
U32,
I64,
U64,
F32,
F64,
String,
Externref,
NamedExternref(String),
}
impl Descriptor {
pub fn decode(mut data: &[u32]) -> Descriptor {
let descriptor = Descriptor::_decode(&mut data, false);
assert!(data.is_empty(), "remaining data {data:?}");
descriptor
}
pub fn decode_generic_import(mut data: &[u32]) -> (GenericImportKey, Descriptor) {
let key = get_string(&mut data);
let descriptor = Descriptor::_decode(&mut data, false);
assert!(data.is_empty(), "remaining data {data:?}");
let key = if key.is_empty() {
GenericImportKey::Cast
} else {
GenericImportKey::Shim(key)
};
(key, descriptor)
}
fn _decode(data: &mut &[u32], clamped: bool) -> Descriptor {
match get(data) {
I8 => Descriptor::I8,
I16 => Descriptor::I16,
I32 => Descriptor::I32,
I64 => Descriptor::I64,
I64_AS_F64 => Descriptor::I64AsF64,
I128 => Descriptor::I128,
U8 if clamped => Descriptor::ClampedU8,
U8 => Descriptor::U8,
U16 => Descriptor::U16,
U32 => Descriptor::U32,
U64 => Descriptor::U64,
U64_AS_F64 => Descriptor::U64AsF64,
U128 => Descriptor::U128,
F32 => Descriptor::F32,
F64 => Descriptor::F64,
BOOLEAN => Descriptor::Boolean,
FUNCTION => Descriptor::Function(Box::new(Function::decode(data))),
CLOSURE => Descriptor::Closure(Box::new(Closure::decode(data))),
REF => Descriptor::Ref(Box::new(Descriptor::_decode(data, clamped))),
REFMUT => Descriptor::RefMut(Box::new(Descriptor::_decode(data, clamped))),
LONGREF => {
let contents = Descriptor::_decode(data, clamped);
match contents {
Descriptor::Externref | Descriptor::NamedExternref(_) => contents,
_ => Descriptor::Ref(Box::new(contents)),
}
}
SLICE => Descriptor::Slice(Box::new(Descriptor::_decode(data, clamped))),
VECTOR => Descriptor::Vector(Box::new(Descriptor::_decode(data, clamped))),
OPTIONAL => Descriptor::Option(Box::new(Descriptor::_decode(data, clamped))),
NULLABLE => Descriptor::Nullable(Box::new(Descriptor::_decode(data, clamped))),
RESULT => Descriptor::Result(Box::new(Descriptor::_decode(data, clamped))),
CACHED_STRING => Descriptor::CachedString,
STRING => Descriptor::String,
EXTERNREF => Descriptor::Externref,
ENUM => {
let name = get_string(data);
let hole = get(data);
let unique_crate_identifier = get_string(data);
let module_hash = get(data);
Descriptor::Enum {
name,
hole,
unique_crate_identifier,
module_hash,
}
}
STRING_ENUM => {
let name = get_string(data);
let variant_count = get(data);
let invalid = variant_count;
let hole = variant_count + 1;
let unique_crate_identifier = get_string(data);
let module_hash = get(data);
Descriptor::StringEnum {
name,
invalid,
hole,
unique_crate_identifier,
module_hash,
}
}
DYNAMIC_UNION => {
let name = get_string(data);
let unique_crate_identifier = get_string(data);
let module_hash = get(data);
let type_count = get(data);
let mut variant_types = Vec::new();
for _ in 0..type_count {
variant_types.push(Descriptor::_decode(data, clamped));
}
Descriptor::DynamicUnion {
name,
variant_types,
unique_crate_identifier,
module_hash,
}
}
RUST_STRUCT => {
let name = get_string(data);
let unique_crate_identifier = get_string(data);
Descriptor::RustStruct {
name,
unique_crate_identifier,
}
}
NAMED_EXTERNREF => {
let name = get_string(data);
Descriptor::NamedExternref(name)
}
CHAR => Descriptor::Char,
UNIT => Descriptor::Unit,
CLAMPED => Descriptor::_decode(data, true),
NONNULL => Descriptor::NonNull,
RAW_POINTER => Descriptor::RawPointer,
SEQ_MODE => {
let word = get(data);
let mode = SeqMode {
vec: word & SEQ_MODE_VEC != 0,
value: match word & !(SEQ_MODE_VEC | SEQ_MODE_EXPLICIT) {
SEQ_MODE_VIEW => SeqValue::View,
SEQ_MODE_COPY => SeqValue::Copy,
SEQ_MODE_ARRAY => SeqValue::Array,
other => panic!("unknown slice mode: {other}"),
},
explicit: word & SEQ_MODE_EXPLICIT != 0,
};
let contents = Descriptor::_decode(data, clamped);
if contents.seq_mode_fit(mode) == Ok(false) {
contents
} else {
Descriptor::SeqMode(mode, Box::new(contents))
}
}
other => panic!("unknown descriptor: {other}"),
}
}
pub fn visit_named_types_mut<E>(
&mut self,
f: &mut impl FnMut(&mut String, Option<ItemOrigin<'_>>) -> Result<(), E>,
) -> Result<(), E> {
match self {
Descriptor::Function(function) => function.visit_named_types_mut(f),
Descriptor::Closure(closure) => closure.function.visit_named_types_mut(f),
Descriptor::Ref(d)
| Descriptor::RefMut(d)
| Descriptor::Slice(d)
| Descriptor::Vector(d)
| Descriptor::Result(d) => d.visit_named_types_mut(f),
Descriptor::Option(inner) | Descriptor::Nullable(inner) => {
inner.visit_named_types_mut(f)
}
Descriptor::NamedExternref(name) => f(name, None),
Descriptor::RustStruct {
name,
unique_crate_identifier,
} => f(
name,
Some(ItemOrigin {
kind: ItemKind::Value,
unique_crate_identifier,
module_hash: None,
}),
),
Descriptor::Enum {
name,
unique_crate_identifier,
module_hash,
..
} => f(
name,
Some(ItemOrigin {
kind: ItemKind::Value,
unique_crate_identifier,
module_hash: Some(*module_hash),
}),
),
Descriptor::StringEnum {
name,
unique_crate_identifier,
module_hash,
..
} => f(
name,
Some(ItemOrigin {
kind: ItemKind::StringEnum,
unique_crate_identifier,
module_hash: Some(*module_hash),
}),
),
Descriptor::DynamicUnion {
name,
variant_types,
unique_crate_identifier,
module_hash,
} => {
f(
name,
Some(ItemOrigin {
kind: ItemKind::DynamicUnion,
unique_crate_identifier,
module_hash: Some(*module_hash),
}),
)?;
for d in variant_types {
d.visit_named_types_mut(f)?;
}
Ok(())
}
_ => Ok(()),
}
}
pub fn unwrap_function(self) -> Function {
match self {
Descriptor::Function(f) => *f,
_ => panic!("not a function"),
}
}
pub fn seq_mode_fit(&self, mode: SeqMode) -> Result<bool, &'static str> {
let mut d = self;
while let Descriptor::SeqMode(_, inner) = d {
d = inner;
}
let d = match d {
Descriptor::Option(inner) | Descriptor::Nullable(inner) => inner,
d => d,
};
let d = match d {
Descriptor::Nullable(inner) => inner,
d => d,
};
let (borrowed, mutable) = match d {
Descriptor::Ref(s) => (matches!(**s, Descriptor::Slice(_)), false),
Descriptor::RefMut(s) => (matches!(**s, Descriptor::Slice(_)), true),
_ => (false, false),
};
let typed = d.vector_kind().is_some_and(|kind| {
!matches!(
kind,
VectorKind::String | VectorKind::Externref | VectorKind::NamedExternref(_)
)
});
let owned = matches!(d, Descriptor::Vector(_));
let misfit = if mode.vec && borrowed {
"`vec` applies to owned vectors; for a borrowed `&[T]` use `slice = view | copy | array`"
} else if mode.vec && !owned {
"`vec` applies to owned vector arguments (`Vec<T>`, `Box<[T]>`)"
} else if !mode.vec && owned {
"`slice` applies to borrowed slices; for an owned `Vec<T>` use `vec = copy | array`"
} else if !mode.vec && !borrowed {
"`slice` applies to borrowed slice arguments (`&[T]`)"
} else if mutable && mode.value != SeqValue::View {
return Err("JS would receive a copy, whose writes never reach the `&mut [T]`; only `slice = view` applies");
} else if !typed && mode.value != SeqValue::Array {
"it has no typed-array form, so it always reaches JS as a fresh `Array`"
} else {
return Ok(typed);
};
if mode.explicit {
Err(misfit)
} else {
Ok(false)
}
}
pub fn vector_kind(&self) -> Option<VectorKind> {
let inner = match *self {
Descriptor::String | Descriptor::CachedString => return Some(VectorKind::String),
Descriptor::Vector(ref d) => &**d,
Descriptor::Slice(ref d) => &**d,
Descriptor::Ref(ref d) => match **d {
Descriptor::Slice(ref d) => &**d,
Descriptor::String | Descriptor::CachedString => return Some(VectorKind::String),
_ => return None,
},
Descriptor::RefMut(ref d) => match **d {
Descriptor::Slice(ref d) => &**d,
_ => return None,
},
_ => return None,
};
inner.in_place_kind()
}
pub fn sequence_cell(&self) -> SequenceCell<'_> {
match self.in_place_kind() {
Some(kind) => SequenceCell::InPlace(kind),
None => SequenceCell::Encoded(self),
}
}
fn in_place_kind(&self) -> Option<VectorKind> {
match *self {
Descriptor::I8 => Some(VectorKind::I8),
Descriptor::I16 => Some(VectorKind::I16),
Descriptor::I32 => Some(VectorKind::I32),
Descriptor::I64 | Descriptor::I64AsF64 => Some(VectorKind::I64),
Descriptor::U8 => Some(VectorKind::U8),
Descriptor::ClampedU8 => Some(VectorKind::ClampedU8),
Descriptor::U16 => Some(VectorKind::U16),
Descriptor::U32 => Some(VectorKind::U32),
Descriptor::U64 | Descriptor::U64AsF64 => Some(VectorKind::U64),
Descriptor::F32 => Some(VectorKind::F32),
Descriptor::F64 => Some(VectorKind::F64),
Descriptor::Externref => Some(VectorKind::Externref),
Descriptor::NamedExternref(ref name) | Descriptor::RustStruct { ref name, .. } => {
Some(VectorKind::NamedExternref(name.clone()))
}
_ => None,
}
}
}
fn get(a: &mut &[u32]) -> u32 {
let ret = a[0];
*a = &a[1..];
ret
}
fn get_string(data: &mut &[u32]) -> String {
(0..get(data))
.map(|_| char::from_u32(get(data)).unwrap())
.collect()
}
impl Closure {
fn decode(data: &mut &[u32]) -> Closure {
let [owned, mutable] = std::array::from_fn(|_| match get(data) {
0 => false,
1 => true,
other => panic!("expected bool value, got {other}"),
});
assert_eq!(get(data), FUNCTION);
Closure {
owned,
mutable,
function: Function::decode(data),
}
}
}
#[test]
fn vector_kind_accepts_memory64_scalar_descriptors() {
assert_eq!(
Descriptor::Vector(Box::new(Descriptor::U64AsF64)).vector_kind(),
Some(VectorKind::U64)
);
assert_eq!(
Descriptor::Ref(Box::new(Descriptor::Slice(Box::new(Descriptor::I64AsF64)))).vector_kind(),
Some(VectorKind::I64)
);
}
#[test]
fn sequence_cells() {
assert_eq!(
Descriptor::U8.sequence_cell(),
SequenceCell::InPlace(VectorKind::U8)
);
assert_eq!(
Descriptor::Char.sequence_cell(),
SequenceCell::Encoded(&Descriptor::Char)
);
let level = Descriptor::Enum {
name: "Level".to_string(),
hole: 0,
unique_crate_identifier: String::new(),
module_hash: 0,
};
assert_eq!(level.sequence_cell(), SequenceCell::Encoded(&level));
assert_eq!(Descriptor::decode(&[VECTOR, BOOLEAN]).vector_kind(), None);
assert_eq!(
Descriptor::decode(&[REF, SLICE, BOOLEAN]).vector_kind(),
None
);
}
#[test]
fn seq_modes_apply_by_shape() {
let decode = |word, rest: &[u32]| Descriptor::decode(&[&[SEQ_MODE, word], rest].concat());
let fit = |word, rest: &[u32]| match decode(word, rest) {
Descriptor::SeqMode(mode, d) => d.seq_mode_fit(mode),
_ => Ok(false),
};
let (copy, array, explicit) = (SEQ_MODE_COPY, SEQ_MODE_ARRAY, SEQ_MODE_EXPLICIT);
let vec_array = SEQ_MODE_VEC | SEQ_MODE_ARRAY;
assert_eq!(fit(copy, &[REF, SLICE, U8]), Ok(true));
assert_eq!(fit(array, &[OPTIONAL, REF, SLICE, F64]), Ok(true));
assert_eq!(fit(array, &[CLAMPED, REF, SLICE, U8]), Ok(true));
assert_eq!(fit(vec_array, &[OPTIONAL, VECTOR, U8]), Ok(true));
assert_eq!(fit(explicit, &[REFMUT, SLICE, U8]), Ok(true));
assert_eq!(fit(array, &[VECTOR, U8]), Ok(false));
assert_eq!(fit(vec_array, &[REF, SLICE, U8]), Ok(false));
assert_eq!(fit(copy, &[REF, SLICE, EXTERNREF]), Ok(false));
assert_eq!(fit(copy, &[REF, SLICE, BOOLEAN]), Ok(false));
assert_eq!(fit(copy, &[REF, STRING]), Ok(false));
assert_eq!(fit(array, &[U32]), Ok(false));
assert_eq!(fit(explicit | array, &[REF, SLICE, REF, STRING]), Ok(false));
assert!(fit(explicit | SEQ_MODE_VEC | copy, &[REF, SLICE, U8]).is_err());
assert!(fit(explicit | array, &[VECTOR, U8]).is_err());
assert!(fit(explicit, &[REF, SLICE, REF, STRING]).is_err());
assert!(fit(copy, &[REFMUT, SLICE, U8]).is_err());
assert!(fit(copy, &[OPTIONAL, REFMUT, SLICE, U8]).is_err());
}
impl Function {
pub fn visit_named_types_mut<E>(
&mut self,
f: &mut impl FnMut(&mut String, Option<ItemOrigin<'_>>) -> Result<(), E>,
) -> Result<(), E> {
for d in &mut self.arguments {
d.visit_named_types_mut(f)?;
}
self.ret.visit_named_types_mut(f)?;
if let Some(d) = &mut self.inner_ret {
d.visit_named_types_mut(f)?;
}
Ok(())
}
fn decode(data: &mut &[u32]) -> Function {
let shim_idx = get(data);
let arguments = (0..get(data))
.map(|_| Descriptor::_decode(data, false))
.collect::<Vec<_>>();
Function {
arguments,
shim_idx,
ret: Descriptor::_decode(data, false),
inner_ret: Some(Descriptor::_decode(data, false)),
}
}
}
impl VectorKind {
pub fn js_ty(&self) -> String {
match *self {
VectorKind::String => "string".to_string(),
VectorKind::I8 => "Int8Array".to_string(),
VectorKind::U8 => "Uint8Array".to_string(),
VectorKind::ClampedU8 => "Uint8ClampedArray".to_string(),
VectorKind::I16 => "Int16Array".to_string(),
VectorKind::U16 => "Uint16Array".to_string(),
VectorKind::I32 => "Int32Array".to_string(),
VectorKind::U32 => "Uint32Array".to_string(),
VectorKind::I64 => "BigInt64Array".to_string(),
VectorKind::U64 => "BigUint64Array".to_string(),
VectorKind::F32 => "Float32Array".to_string(),
VectorKind::F64 => "Float64Array".to_string(),
VectorKind::Externref => "any[]".to_string(),
VectorKind::NamedExternref(ref name) => {
if is_valid_ident(name.as_str()) {
format!("{name}[]")
} else {
format!("({name})[]")
}
}
}
}
pub fn plain_array_element_ty(&self) -> Option<&'static str> {
match *self {
VectorKind::I8
| VectorKind::U8
| VectorKind::I16
| VectorKind::U16
| VectorKind::I32
| VectorKind::U32
| VectorKind::F32
| VectorKind::F64 => Some("number"),
VectorKind::I64 | VectorKind::U64 => Some("bigint"),
VectorKind::ClampedU8 => None,
VectorKind::String | VectorKind::Externref | VectorKind::NamedExternref(_) => None,
}
}
pub fn size(&self) -> usize {
match *self {
VectorKind::String => 1,
VectorKind::I8 => 1,
VectorKind::U8 => 1,
VectorKind::ClampedU8 => 1,
VectorKind::I16 => 2,
VectorKind::U16 => 2,
VectorKind::I32 => 4,
VectorKind::U32 => 4,
VectorKind::I64 => 8,
VectorKind::U64 => 8,
VectorKind::F32 => 4,
VectorKind::F64 => 8,
VectorKind::Externref => 4,
VectorKind::NamedExternref(_) => 4,
}
}
}