use crate::{encode_section, Encode, Section, SectionId};
#[derive(Debug, Clone)]
pub struct SubType {
pub is_final: bool,
pub supertype_idx: Option<u32>,
pub composite_type: CompositeType,
}
impl Encode for SubType {
fn encode(&self, sink: &mut Vec<u8>) {
if self.supertype_idx.is_some() || !self.is_final {
sink.push(if self.is_final { 0x4f } else { 0x50 });
self.supertype_idx.encode(sink);
}
self.composite_type.encode(sink);
}
}
#[cfg(feature = "wasmparser")]
impl TryFrom<wasmparser::SubType> for SubType {
type Error = ();
fn try_from(sub_ty: wasmparser::SubType) -> Result<Self, Self::Error> {
Ok(SubType {
is_final: sub_ty.is_final,
supertype_idx: sub_ty
.supertype_idx
.map(|i| i.as_module_index().ok_or(()))
.transpose()?,
composite_type: sub_ty.composite_type.try_into()?,
})
}
}
#[derive(Debug, Clone)]
pub enum CompositeType {
Func(FuncType),
Array(ArrayType),
Struct(StructType),
}
impl Encode for CompositeType {
fn encode(&self, sink: &mut Vec<u8>) {
match self {
CompositeType::Func(ty) => TypeSection::encode_function(
sink,
ty.params().iter().copied(),
ty.results().iter().copied(),
),
CompositeType::Array(ArrayType(ty)) => {
TypeSection::encode_array(sink, &ty.element_type, ty.mutable)
}
CompositeType::Struct(ty) => {
TypeSection::encode_struct(sink, ty.fields.iter().cloned())
}
}
}
}
#[cfg(feature = "wasmparser")]
impl TryFrom<wasmparser::CompositeType> for CompositeType {
type Error = ();
fn try_from(composite_ty: wasmparser::CompositeType) -> Result<Self, Self::Error> {
Ok(match composite_ty {
wasmparser::CompositeType::Func(f) => CompositeType::Func(f.try_into()?),
wasmparser::CompositeType::Array(a) => CompositeType::Array(a.try_into()?),
wasmparser::CompositeType::Struct(s) => CompositeType::Struct(s.try_into()?),
})
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct FuncType {
params_results: Box<[ValType]>,
len_params: usize,
}
#[cfg(feature = "wasmparser")]
impl TryFrom<wasmparser::FuncType> for FuncType {
type Error = ();
fn try_from(func_ty: wasmparser::FuncType) -> Result<Self, Self::Error> {
let mut buf = Vec::with_capacity(func_ty.params().len() + func_ty.results().len());
for ty in func_ty.params().iter().chain(func_ty.results()).copied() {
buf.push(ty.try_into()?);
}
Ok(FuncType::from_parts(buf.into(), func_ty.params().len()))
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct ArrayType(pub FieldType);
#[cfg(feature = "wasmparser")]
impl TryFrom<wasmparser::ArrayType> for ArrayType {
type Error = ();
fn try_from(array_ty: wasmparser::ArrayType) -> Result<Self, Self::Error> {
Ok(ArrayType(array_ty.0.try_into()?))
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct StructType {
pub fields: Box<[FieldType]>,
}
#[cfg(feature = "wasmparser")]
impl TryFrom<wasmparser::StructType> for StructType {
type Error = ();
fn try_from(struct_ty: wasmparser::StructType) -> Result<Self, Self::Error> {
Ok(StructType {
fields: struct_ty
.fields
.iter()
.cloned()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub struct FieldType {
pub element_type: StorageType,
pub mutable: bool,
}
#[cfg(feature = "wasmparser")]
impl TryFrom<wasmparser::FieldType> for FieldType {
type Error = ();
fn try_from(field_ty: wasmparser::FieldType) -> Result<Self, Self::Error> {
Ok(FieldType {
element_type: field_ty.element_type.try_into()?,
mutable: field_ty.mutable,
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum StorageType {
I8,
I16,
Val(ValType),
}
#[cfg(feature = "wasmparser")]
impl TryFrom<wasmparser::StorageType> for StorageType {
type Error = ();
fn try_from(storage_ty: wasmparser::StorageType) -> Result<Self, Self::Error> {
Ok(match storage_ty {
wasmparser::StorageType::I8 => StorageType::I8,
wasmparser::StorageType::I16 => StorageType::I16,
wasmparser::StorageType::Val(v) => StorageType::Val(v.try_into()?),
})
}
}
impl StorageType {
pub fn is_defaultable(&self) -> bool {
self.unpack().is_defaultable()
}
pub fn unpack(&self) -> ValType {
match self {
StorageType::I8 | StorageType::I16 => ValType::I32,
StorageType::Val(v) => *v,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum ValType {
I32,
I64,
F32,
F64,
V128,
Ref(RefType),
}
#[cfg(feature = "wasmparser")]
impl TryFrom<wasmparser::ValType> for ValType {
type Error = ();
fn try_from(val_ty: wasmparser::ValType) -> Result<Self, Self::Error> {
Ok(match val_ty {
wasmparser::ValType::I32 => ValType::I32,
wasmparser::ValType::I64 => ValType::I64,
wasmparser::ValType::F32 => ValType::F32,
wasmparser::ValType::F64 => ValType::F64,
wasmparser::ValType::V128 => ValType::V128,
wasmparser::ValType::Ref(r) => ValType::Ref(r.try_into()?),
})
}
}
impl ValType {
pub fn is_numeric(&self) -> bool {
match self {
ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 => true,
ValType::V128 | ValType::Ref(_) => false,
}
}
pub fn is_vector(&self) -> bool {
match self {
ValType::V128 => true,
ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::Ref(_) => false,
}
}
pub fn is_reference(&self) -> bool {
match self {
ValType::Ref(_) => true,
ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => false,
}
}
}
impl FuncType {
pub fn new<P, R>(params: P, results: R) -> Self
where
P: IntoIterator<Item = ValType>,
R: IntoIterator<Item = ValType>,
{
let mut buffer = params.into_iter().collect::<Vec<_>>();
let len_params = buffer.len();
buffer.extend(results);
Self::from_parts(buffer.into(), len_params)
}
#[inline]
pub(crate) fn from_parts(params_results: Box<[ValType]>, len_params: usize) -> Self {
Self {
params_results,
len_params,
}
}
#[inline]
pub fn params(&self) -> &[ValType] {
&self.params_results[..self.len_params]
}
#[inline]
pub fn results(&self) -> &[ValType] {
&self.params_results[self.len_params..]
}
}
impl ValType {
pub const FUNCREF: ValType = ValType::Ref(RefType::FUNCREF);
pub const EXTERNREF: ValType = ValType::Ref(RefType::EXTERNREF);
pub const EXNREF: ValType = ValType::Ref(RefType::EXNREF);
pub fn is_defaultable(&self) -> bool {
match self {
ValType::Ref(r) => r.nullable,
ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => true,
}
}
}
impl Encode for StorageType {
fn encode(&self, sink: &mut Vec<u8>) {
match self {
StorageType::I8 => sink.push(0x78),
StorageType::I16 => sink.push(0x77),
StorageType::Val(vt) => vt.encode(sink),
}
}
}
impl Encode for ValType {
fn encode(&self, sink: &mut Vec<u8>) {
match self {
ValType::I32 => sink.push(0x7F),
ValType::I64 => sink.push(0x7E),
ValType::F32 => sink.push(0x7D),
ValType::F64 => sink.push(0x7C),
ValType::V128 => sink.push(0x7B),
ValType::Ref(rt) => rt.encode(sink),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
#[allow(missing_docs)]
pub struct RefType {
pub nullable: bool,
pub heap_type: HeapType,
}
impl RefType {
pub const FUNCREF: RefType = RefType {
nullable: true,
heap_type: HeapType::Func,
};
pub const EXTERNREF: RefType = RefType {
nullable: true,
heap_type: HeapType::Extern,
};
pub const EXNREF: RefType = RefType {
nullable: true,
heap_type: HeapType::Exn,
};
}
impl Encode for RefType {
fn encode(&self, sink: &mut Vec<u8>) {
if self.nullable {
match self.heap_type {
HeapType::Func => return sink.push(0x70),
HeapType::Extern => return sink.push(0x6f),
_ => {}
}
}
if self.nullable {
sink.push(0x63);
} else {
sink.push(0x64);
}
self.heap_type.encode(sink);
}
}
#[cfg(feature = "wasmparser")]
impl TryFrom<wasmparser::RefType> for RefType {
type Error = ();
fn try_from(ref_type: wasmparser::RefType) -> Result<Self, Self::Error> {
Ok(RefType {
nullable: ref_type.is_nullable(),
heap_type: ref_type.heap_type().try_into()?,
})
}
}
impl From<RefType> for ValType {
fn from(ty: RefType) -> ValType {
ValType::Ref(ty)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
pub enum HeapType {
Func,
Extern,
Any,
None,
NoExtern,
NoFunc,
Eq,
Struct,
Array,
I31,
Exn,
Concrete(u32),
}
impl Encode for HeapType {
fn encode(&self, sink: &mut Vec<u8>) {
match self {
HeapType::Func => sink.push(0x70),
HeapType::Extern => sink.push(0x6F),
HeapType::Any => sink.push(0x6E),
HeapType::None => sink.push(0x71),
HeapType::NoExtern => sink.push(0x72),
HeapType::NoFunc => sink.push(0x73),
HeapType::Eq => sink.push(0x6D),
HeapType::Struct => sink.push(0x6B),
HeapType::Array => sink.push(0x6A),
HeapType::I31 => sink.push(0x6C),
HeapType::Exn => sink.push(0x69),
HeapType::Concrete(i) => i64::from(*i).encode(sink),
}
}
}
#[cfg(feature = "wasmparser")]
impl TryFrom<wasmparser::HeapType> for HeapType {
type Error = ();
fn try_from(heap_type: wasmparser::HeapType) -> Result<Self, Self::Error> {
Ok(match heap_type {
wasmparser::HeapType::Concrete(i) => HeapType::Concrete(i.as_module_index().ok_or(())?),
wasmparser::HeapType::Func => HeapType::Func,
wasmparser::HeapType::Extern => HeapType::Extern,
wasmparser::HeapType::Any => HeapType::Any,
wasmparser::HeapType::None => HeapType::None,
wasmparser::HeapType::NoExtern => HeapType::NoExtern,
wasmparser::HeapType::NoFunc => HeapType::NoFunc,
wasmparser::HeapType::Eq => HeapType::Eq,
wasmparser::HeapType::Struct => HeapType::Struct,
wasmparser::HeapType::Array => HeapType::Array,
wasmparser::HeapType::I31 => HeapType::I31,
wasmparser::HeapType::Exn => HeapType::Exn,
})
}
}
#[derive(Clone, Debug, Default)]
pub struct TypeSection {
bytes: Vec<u8>,
num_added: u32,
}
impl TypeSection {
pub fn new() -> Self {
Self::default()
}
pub fn len(&self) -> u32 {
self.num_added
}
pub fn is_empty(&self) -> bool {
self.num_added == 0
}
pub fn function<P, R>(&mut self, params: P, results: R) -> &mut Self
where
P: IntoIterator<Item = ValType>,
P::IntoIter: ExactSizeIterator,
R: IntoIterator<Item = ValType>,
R::IntoIter: ExactSizeIterator,
{
Self::encode_function(&mut self.bytes, params, results);
self.num_added += 1;
self
}
fn encode_function<P, R>(sink: &mut Vec<u8>, params: P, results: R)
where
P: IntoIterator<Item = ValType>,
P::IntoIter: ExactSizeIterator,
R: IntoIterator<Item = ValType>,
R::IntoIter: ExactSizeIterator,
{
let params = params.into_iter();
let results = results.into_iter();
sink.push(0x60);
params.len().encode(sink);
params.for_each(|p| p.encode(sink));
results.len().encode(sink);
results.for_each(|p| p.encode(sink));
}
pub fn array(&mut self, ty: &StorageType, mutable: bool) -> &mut Self {
Self::encode_array(&mut self.bytes, ty, mutable);
self.num_added += 1;
self
}
fn encode_array(sink: &mut Vec<u8>, ty: &StorageType, mutable: bool) {
sink.push(0x5e);
Self::encode_field(sink, ty, mutable);
}
fn encode_field(sink: &mut Vec<u8>, ty: &StorageType, mutable: bool) {
ty.encode(sink);
sink.push(mutable as u8);
}
pub fn struct_<F>(&mut self, fields: F) -> &mut Self
where
F: IntoIterator<Item = FieldType>,
F::IntoIter: ExactSizeIterator,
{
Self::encode_struct(&mut self.bytes, fields);
self.num_added += 1;
self
}
fn encode_struct<F>(sink: &mut Vec<u8>, fields: F)
where
F: IntoIterator<Item = FieldType>,
F::IntoIter: ExactSizeIterator,
{
let fields = fields.into_iter();
sink.push(0x5f);
fields.len().encode(sink);
for f in fields {
Self::encode_field(sink, &f.element_type, f.mutable);
}
}
pub fn subtype(&mut self, ty: &SubType) -> &mut Self {
ty.encode(&mut self.bytes);
self.num_added += 1;
self
}
pub fn rec<T>(&mut self, types: T) -> &mut Self
where
T: IntoIterator<Item = SubType>,
T::IntoIter: ExactSizeIterator,
{
let types = types.into_iter();
self.bytes.push(0x4e);
types.len().encode(&mut self.bytes);
types.for_each(|t| t.encode(&mut self.bytes));
self.num_added += 1;
self
}
}
impl Encode for TypeSection {
fn encode(&self, sink: &mut Vec<u8>) {
encode_section(sink, self.num_added, &self.bytes);
}
}
impl Section for TypeSection {
fn id(&self) -> u8 {
SectionId::Type.into()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Module;
#[test]
fn func_types_dont_require_wasm_gc() {
let mut types = TypeSection::new();
types.subtype(&SubType {
is_final: true,
supertype_idx: None,
composite_type: CompositeType::Func(FuncType::new([], [])),
});
let mut module = Module::new();
module.section(&types);
let wasm_bytes = module.finish();
let mut validator = wasmparser::Validator::new_with_features(wasmparser::WasmFeatures {
gc: false,
..Default::default()
});
validator.validate_all(&wasm_bytes).expect(
"Encoding pre Wasm GC type should not accidentally use Wasm GC specific encoding",
);
}
}