use crate::{decode_world, ComponentEncoder, StringEncoding};
use anyhow::{bail, Context, Result};
use indexmap::IndexMap;
use wasm_encoder::Encode;
use wasmparser::BinaryReader;
use wit_parser::World;
const CURRENT_VERSION: u8 = 0x01;
#[derive(Default)]
pub struct BindgenMetadata {
pub world: World,
pub import_encodings: IndexMap<(String, String), StringEncoding>,
pub export_encodings: IndexMap<String, StringEncoding>,
}
pub fn decode(wasm: &[u8]) -> Result<(Vec<u8>, BindgenMetadata)> {
let mut ret = BindgenMetadata::default();
let mut new_module = wasm_encoder::Module::new();
for payload in wasmparser::Parser::new(0).parse_all(wasm) {
let payload = payload.context("decoding item in module")?;
match payload {
wasmparser::Payload::CustomSection(cs) if cs.name().starts_with("component-type") => {
let data = BindgenMetadata::decode(cs.data())
.with_context(|| format!("decoding custom section {}", cs.name()))?;
ret.merge(data)
.with_context(|| format!("updating metadata for section {}", cs.name()))?;
}
_ => {
if let Some((id, range)) = payload.as_section() {
new_module.section(&wasm_encoder::RawSection {
id,
data: &wasm[range],
});
}
}
}
}
Ok((new_module.finish(), ret))
}
pub fn encode(world: &World, encoding: StringEncoding) -> Vec<u8> {
let component = ComponentEncoder::default()
.types_only(true)
.world(world.clone(), encoding)
.unwrap()
.encode()
.unwrap();
let mut ret = Vec::new();
ret.push(CURRENT_VERSION);
ret.push(match encoding {
StringEncoding::UTF8 => 0x00,
StringEncoding::UTF16 => 0x01,
StringEncoding::CompactUTF16 => 0x02,
});
world.name.encode(&mut ret);
ret.extend(component);
ret
}
impl BindgenMetadata {
fn decode(data: &[u8]) -> Result<BindgenMetadata> {
let mut reader = BinaryReader::new(data);
let version = reader.read_u8()?;
if version != CURRENT_VERSION {
bail!("component-type version {version} does not match supported version {CURRENT_VERSION}");
}
let encoding = match reader.read_u8()? {
0x00 => StringEncoding::UTF8,
0x01 => StringEncoding::UTF16,
0x02 => StringEncoding::CompactUTF16,
byte => bail!("invalid string encoding {byte:#x}"),
};
let name = reader.read_string()?;
Ok(BindgenMetadata::new(
decode_world(name, &data[reader.original_position()..])?,
encoding,
))
}
pub fn new(world: World, encoding: StringEncoding) -> BindgenMetadata {
let mut ret = BindgenMetadata {
world,
import_encodings: Default::default(),
export_encodings: Default::default(),
};
if let Some(iface) = &ret.world.default {
for func in iface.functions.iter() {
let name = func.core_export_name(None);
let prev = ret.export_encodings.insert(name.to_string(), encoding);
assert!(prev.is_none());
}
}
for (name, import) in ret.world.imports.iter() {
for func in import.functions.iter() {
let key = (name.clone(), func.name.clone());
let prev = ret.import_encodings.insert(key, encoding);
assert!(prev.is_none());
}
}
for (name, export) in ret.world.exports.iter() {
for func in export.functions.iter() {
let name = func.core_export_name(Some(name));
let prev = ret.export_encodings.insert(name.to_string(), encoding);
assert!(prev.is_none());
}
}
ret
}
pub fn merge(&mut self, other: BindgenMetadata) -> Result<()> {
let BindgenMetadata {
world,
import_encodings,
export_encodings,
} = other;
for (name, import) in world.imports {
let prev = self.world.imports.insert(name.clone(), import);
if prev.is_some() {
bail!("import interface `{name}` specified twice");
}
}
for (name, export) in world.exports {
let prev = self.world.exports.insert(name.clone(), export);
if prev.is_some() {
bail!("export interface `{name}` specified twice");
}
}
if let Some(default) = world.default {
if self.world.default.is_some() {
bail!("default export interface specified twice");
}
self.world.default = Some(default);
}
for (name, encoding) in export_encodings {
let prev = self.export_encodings.insert(name.clone(), encoding);
if let Some(prev) = prev {
if prev != encoding {
bail!("conflicting string encodings specified for export `{name}`");
}
}
}
for ((module, name), encoding) in import_encodings {
let prev = self
.import_encodings
.insert((module.clone(), name.clone()), encoding);
if let Some(prev) = prev {
if prev != encoding {
bail!("conflicting string encodings specified for import `{module}::{name}`");
}
}
}
Ok(())
}
}