use crate::validation::BARE_FUNC_MODULE_NAME;
use crate::{DecodedWasm, StringEncoding};
use anyhow::{bail, Context, Result};
use indexmap::IndexMap;
use std::borrow::Cow;
use wasm_encoder::{ComponentBuilder, ComponentExportKind, CustomSection, Encode};
use wasm_metadata::Producers;
use wasmparser::types::ComponentAnyTypeId;
use wasmparser::{
BinaryReader, ComponentExternalKind, Parser, Payload, ValidPayload, Validator, WasmFeatures,
};
use wit_parser::{Package, PackageName, Resolve, World, WorldId, WorldItem};
const CURRENT_VERSION: u8 = 0x04;
const CUSTOM_SECTION_NAME: &str = "wit-component-encoding";
pub struct Bindgen {
pub resolve: Resolve,
pub world: WorldId,
pub metadata: ModuleMetadata,
pub producers: Option<Producers>,
}
impl Default for Bindgen {
fn default() -> Bindgen {
let mut resolve = Resolve::default();
let package = resolve.packages.alloc(Package {
name: PackageName {
namespace: "root".to_string(),
name: "root".to_string(),
version: None,
},
docs: Default::default(),
interfaces: Default::default(),
worlds: Default::default(),
});
let world = resolve.worlds.alloc(World {
name: "root".to_string(),
docs: Default::default(),
imports: Default::default(),
exports: Default::default(),
includes: Default::default(),
include_names: Default::default(),
package: Some(package),
});
resolve.packages[package]
.worlds
.insert("root".to_string(), world);
Bindgen {
resolve,
world,
metadata: ModuleMetadata::default(),
producers: None,
}
}
}
#[derive(Default)]
pub struct ModuleMetadata {
pub import_encodings: IndexMap<(String, String), StringEncoding>,
pub export_encodings: IndexMap<String, StringEncoding>,
}
pub fn decode(wasm: &[u8]) -> Result<(Vec<u8>, Bindgen)> {
let mut ret = Bindgen::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 = Bindgen::decode_custom_section(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(
resolve: &Resolve,
world: WorldId,
string_encoding: StringEncoding,
extra_producers: Option<&Producers>,
use_next_encoding: Option<bool>,
) -> Result<Vec<u8>> {
enum EncodingFormat {
Previous,
Next,
}
let format = match use_next_encoding {
Some(true) => EncodingFormat::Next,
Some(false) => EncodingFormat::Previous,
None => match std::env::var("WIT_COMPONENT_NEW_ENCODE") {
Ok(s) if s == "1" => EncodingFormat::Next,
_ => EncodingFormat::Previous,
},
};
let ret = match format {
EncodingFormat::Previous => {
let world = &resolve.worlds[world];
let pkg = &resolve.packages[world.package.unwrap()];
assert!(
resolve
.packages
.iter()
.filter(|(_, p)| p.name == pkg.name)
.count()
== 1
);
let mut ret = Vec::new();
ret.push(0x03);
ret.push(encode_string_encoding(string_encoding));
world.name.encode(&mut ret);
let mut component_builder =
crate::encoding::encode_component(None, resolve, world.package.unwrap())?;
let mut producers = crate::base_producers();
if let Some(p) = extra_producers {
producers.merge(&p);
}
component_builder.raw_custom_section(&producers.raw_custom_section());
ret.extend(component_builder.finish());
ret
}
EncodingFormat::Next => {
let ty = crate::encoding::encode_world(None, resolve, world)?;
let mut builder = ComponentBuilder::default();
let string_encoding = encode_string_encoding(string_encoding);
builder.custom_section(&CustomSection {
name: CUSTOM_SECTION_NAME.into(),
data: Cow::Borrowed(&[CURRENT_VERSION, string_encoding]),
});
let ty = builder.type_component(&ty);
let world = &resolve.worlds[world];
builder.export(
&resolve.id_of_name(world.package.unwrap(), &world.name),
ComponentExportKind::Type,
ty,
None,
);
let mut producers = crate::base_producers();
if let Some(p) = extra_producers {
producers.merge(&p);
}
builder.raw_custom_section(&producers.raw_custom_section());
builder.finish()
}
};
Ok(ret)
}
fn decode_custom_section(wasm: &[u8]) -> Result<(Resolve, WorldId, StringEncoding)> {
let mut validator = Validator::new_with_features(WasmFeatures::all());
let mut exports = Vec::new();
let mut depth = 1;
let mut types = None;
let mut custom_section = None;
for payload in Parser::new(0).parse_all(wasm) {
let payload = payload?;
match validator.payload(&payload)? {
ValidPayload::Ok => {}
ValidPayload::Parser(_) => depth += 1,
ValidPayload::End(t) => {
depth -= 1;
if depth == 0 {
types = Some(t);
}
}
ValidPayload::Func(..) => {}
}
match payload {
Payload::ComponentExportSection(s) if depth == 1 => {
for export in s {
exports.push(export?);
}
}
Payload::CustomSection(s) if s.name() == CUSTOM_SECTION_NAME => {
custom_section = Some(s.data());
}
_ => {}
}
}
let string_encoding = match custom_section {
None => bail!("missing custom section of name `{CUSTOM_SECTION_NAME}`"),
Some([CURRENT_VERSION, byte]) => decode_string_encoding(*byte)?,
Some([]) => bail!("custom section `{CUSTOM_SECTION_NAME}` in unknown format"),
Some([version, ..]) => bail!(
"custom section `{CUSTOM_SECTION_NAME}` uses format {version} but only {CURRENT_VERSION} is supported"
),
};
if exports.len() != 1 {
bail!("expected one export in component");
}
if exports[0].kind != ComponentExternalKind::Type {
bail!("expected an export of a type");
}
if exports[0].ty.is_some() {
bail!("expected an un-ascribed exported type");
}
let types = types.as_ref().unwrap();
let ty = match types.component_any_type_at(exports[0].index) {
ComponentAnyTypeId::Component(c) => c,
_ => bail!("expected an exported component type"),
};
let (resolve, world) = crate::decoding::decode_world(types, exports[0].name.0, ty)?;
Ok((resolve, world, string_encoding))
}
fn encode_string_encoding(e: StringEncoding) -> u8 {
match e {
StringEncoding::UTF8 => 0x00,
StringEncoding::UTF16 => 0x01,
StringEncoding::CompactUTF16 => 0x02,
}
}
fn decode_string_encoding(byte: u8) -> Result<StringEncoding> {
match byte {
0x00 => Ok(StringEncoding::UTF8),
0x01 => Ok(StringEncoding::UTF16),
0x02 => Ok(StringEncoding::CompactUTF16),
byte => bail!("invalid string encoding {byte:#x}"),
}
}
impl Bindgen {
fn decode_custom_section(data: &[u8]) -> Result<Bindgen> {
let wasm;
let world;
let resolve;
let encoding;
let mut reader = BinaryReader::new(data);
match reader.read_u8()? {
0x03 => {
encoding = decode_string_encoding(reader.read_u8()?)?;
let world_name = reader.read_string()?;
wasm = &data[reader.original_position()..];
let (r, pkg) = match crate::decode(wasm)? {
DecodedWasm::WitPackage(resolve, pkg) => (resolve, pkg),
DecodedWasm::Component(..) => bail!("expected an encoded wit package"),
};
resolve = r;
world = resolve.packages[pkg].worlds[world_name];
}
_ => {
wasm = data;
(resolve, world, encoding) = decode_custom_section(wasm)?;
}
}
Ok(Bindgen {
metadata: ModuleMetadata::new(&resolve, world, encoding),
producers: wasm_metadata::Producers::from_wasm(wasm)?,
resolve,
world,
})
}
pub fn merge(&mut self, other: Bindgen) -> Result<WorldId> {
let Bindgen {
resolve,
world,
metadata:
ModuleMetadata {
import_encodings,
export_encodings,
},
producers,
} = other;
let world = self
.resolve
.merge(resolve)
.context("failed to merge WIT package sets together")?
.worlds[world.index()];
self.resolve
.merge_worlds(world, self.world)
.context("failed to merge worlds from two documents")?;
for (name, encoding) in export_encodings {
let prev = self
.metadata
.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
.metadata
.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}`");
}
}
}
if let Some(producers) = producers {
if let Some(mine) = &mut self.producers {
mine.merge(&producers);
} else {
self.producers = Some(producers);
}
}
Ok(world)
}
}
impl ModuleMetadata {
pub fn new(resolve: &Resolve, world: WorldId, encoding: StringEncoding) -> ModuleMetadata {
let mut ret = ModuleMetadata::default();
let world = &resolve.worlds[world];
for (name, item) in world.imports.iter() {
let name = resolve.name_world_key(name);
match item {
WorldItem::Function(_) => {
let prev = ret
.import_encodings
.insert((BARE_FUNC_MODULE_NAME.to_string(), name.clone()), encoding);
assert!(prev.is_none());
}
WorldItem::Interface(i) => {
for (func, _) in resolve.interfaces[*i].functions.iter() {
let prev = ret
.import_encodings
.insert((name.clone(), func.clone()), encoding);
assert!(prev.is_none());
}
}
WorldItem::Type(_) => {}
}
}
for (name, item) in world.exports.iter() {
let name = resolve.name_world_key(name);
match item {
WorldItem::Function(func) => {
let name = func.core_export_name(None).into_owned();
let prev = ret.export_encodings.insert(name.clone(), encoding);
assert!(prev.is_none());
}
WorldItem::Interface(i) => {
for (_, func) in resolve.interfaces[*i].functions.iter() {
let name = func.core_export_name(Some(&name)).into_owned();
let prev = ret.export_encodings.insert(name, encoding);
assert!(prev.is_none());
}
}
WorldItem::Type(_) => {}
}
}
ret
}
}