1use crate::{DecodedWasm, StringEncoding};
45use anyhow::{Context, Result, bail};
46use indexmap::{IndexMap, IndexSet};
47use std::borrow::Cow;
48use wasm_encoder::{
49 ComponentBuilder, ComponentExportKind, ComponentType, ComponentTypeRef, CustomSection,
50};
51use wasm_metadata::Producers;
52use wasmparser::{BinaryReader, Encoding, Parser, Payload};
53use wit_parser::{CloneMaps, Package, PackageName, Resolve, World, WorldId, WorldItem, WorldKey};
54
55const CURRENT_VERSION: u8 = 0x04;
56const CUSTOM_SECTION_NAME: &str = "wit-component-encoding";
57
58pub struct Bindgen {
63 pub resolve: Resolve,
65 pub world: WorldId,
67 pub metadata: ModuleMetadata,
69 pub producers: Option<Producers>,
71}
72
73impl Default for Bindgen {
74 fn default() -> Bindgen {
75 let mut resolve = Resolve::default();
76 let package = resolve.packages.alloc(Package {
77 name: PackageName {
78 namespace: "root".to_string(),
79 name: "root".to_string(),
80 version: None,
81 },
82 docs: Default::default(),
83 interfaces: Default::default(),
84 worlds: Default::default(),
85 });
86 let world = resolve.worlds.alloc(World {
87 name: "root".to_string(),
88 docs: Default::default(),
89 imports: Default::default(),
90 exports: Default::default(),
91 includes: Default::default(),
92 package: Some(package),
93 stability: Default::default(),
94 span: Default::default(),
95 });
96 resolve.packages[package]
97 .worlds
98 .insert("root".to_string(), world);
99 Bindgen {
100 resolve,
101 world,
102 metadata: ModuleMetadata::default(),
103 producers: None,
104 }
105 }
106}
107
108#[derive(Default)]
111pub struct ModuleMetadata {
112 pub import_encodings: EncodingMap,
115
116 pub export_encodings: EncodingMap,
119}
120
121#[derive(Default)]
126pub struct EncodingMap {
127 encodings: IndexMap<String, StringEncoding>,
151}
152
153impl EncodingMap {
154 fn insert_all(
155 &mut self,
156 resolve: &Resolve,
157 set: &IndexMap<WorldKey, WorldItem>,
158 encoding: StringEncoding,
159 ) {
160 for (name, item) in set {
161 match item {
162 WorldItem::Function(func) => {
163 let key = self.key(resolve, name, &func.name);
164 self.encodings.insert(key, encoding);
165 }
166 WorldItem::Interface { id, .. } => {
167 for (func, _) in resolve.interfaces[*id].functions.iter() {
168 let key = self.key(resolve, name, func);
169 self.encodings.insert(key, encoding);
170 }
171 }
172 WorldItem::Type { .. } => {}
173 }
174 }
175 }
176
177 pub fn get(&self, resolve: &Resolve, key: &WorldKey, func: &str) -> Option<StringEncoding> {
180 let key = self.key(resolve, key, func);
181 self.encodings.get(&key).copied()
182 }
183
184 fn key(&self, resolve: &Resolve, key: &WorldKey, func: &str) -> String {
185 format!(
186 "{}/{func}",
187 match key {
188 WorldKey::Name(name) => name.to_string(),
189 WorldKey::Interface(id) => {
190 let iface = &resolve.interfaces[*id];
191 let pkg = &resolve.packages[iface.package.unwrap()];
192 format!(
193 "{}:{}/{}",
194 pkg.name.namespace,
195 pkg.name.name,
196 iface.name.as_ref().unwrap()
197 )
198 }
199 }
200 )
201 }
202
203 fn merge(&mut self, other: EncodingMap) -> Result<()> {
204 for (key, encoding) in other.encodings {
205 if let Some(prev) = self.encodings.insert(key.clone(), encoding) {
206 if prev != encoding {
207 bail!("conflicting string encodings specified for `{key}`");
208 }
209 }
210 }
211 Ok(())
212 }
213}
214
215pub fn decode(wasm: &[u8]) -> Result<(Option<Vec<u8>>, Bindgen)> {
230 let mut ret = Bindgen::default();
231 let mut new_module = wasm_encoder::Module::new();
232
233 let mut found_custom = false;
234 for payload in wasmparser::Parser::new(0).parse_all(&wasm) {
235 let payload = payload.context("decoding item in module")?;
236 match payload {
237 wasmparser::Payload::CustomSection(cs) if cs.name().starts_with("component-type") => {
238 let data = Bindgen::decode_custom_section(cs.data())
239 .with_context(|| format!("decoding custom section {}", cs.name()))?;
240 ret.merge(data)
241 .with_context(|| format!("updating metadata for section {}", cs.name()))?;
242 found_custom = true;
243 }
244 wasmparser::Payload::Version { encoding, .. } if encoding != Encoding::Module => {
245 bail!("decoding a component is not supported")
246 }
247 _ => {
248 if let Some((id, range)) = payload.as_section() {
249 new_module.section(&wasm_encoder::RawSection {
250 id,
251 data: &wasm[range.start as usize..range.end as usize],
252 });
253 }
254 }
255 }
256 }
257
258 if found_custom {
259 Ok((Some(new_module.finish()), ret))
260 } else {
261 Ok((None, ret))
262 }
263}
264
265pub fn encode(
272 resolve: &Resolve,
273 world: WorldId,
274 string_encoding: StringEncoding,
275 extra_producers: Option<&Producers>,
276 canonical_names: bool,
277) -> Result<Vec<u8>> {
278 if canonical_names {
279 crate::encoding::check_duplicate_canonical_names(resolve, world)?;
280 }
281
282 let ty = crate::encoding::encode_world(resolve, world, canonical_names)?;
283
284 let world = &resolve.worlds[world];
285 let mut outer_ty = ComponentType::new();
286 outer_ty.ty().component(&ty);
287 outer_ty.export(
288 resolve.id_of_name(world.package.unwrap(), &world.name),
289 ComponentTypeRef::Component(0),
290 );
291
292 let mut builder = ComponentBuilder::default();
293
294 let string_encoding = encode_string_encoding(string_encoding);
295 builder.custom_section(&CustomSection {
296 name: CUSTOM_SECTION_NAME.into(),
297 data: Cow::Borrowed(&[CURRENT_VERSION, string_encoding]),
298 });
299
300 let ty = builder.type_component(None, &outer_ty);
301 builder.export(&world.name, ComponentExportKind::Type, ty, None);
302
303 let mut producers = crate::base_producers();
304 if let Some(p) = extra_producers {
305 producers.merge(&p);
306 }
307 builder.raw_custom_section(&producers.raw_custom_section());
308 Ok(builder.finish())
309}
310
311fn decode_custom_section(wasm: &[u8]) -> Result<(Resolve, WorldId, StringEncoding)> {
312 let (resolve, world) = wit_parser::decoding::decode_world(wasm)?;
313 let mut custom_section = None;
314
315 for payload in Parser::new(0).parse_all(wasm) {
316 match payload? {
317 Payload::CustomSection(s) if s.name() == CUSTOM_SECTION_NAME => {
318 custom_section = Some(s.data());
319 }
320 _ => {}
321 }
322 }
323 let string_encoding = match custom_section {
324 None => bail!("missing custom section of name `{CUSTOM_SECTION_NAME}`"),
325 Some([CURRENT_VERSION, byte]) => decode_string_encoding(*byte)?,
326 Some([]) => bail!("custom section `{CUSTOM_SECTION_NAME}` in unknown format"),
327 Some([version, ..]) => bail!(
328 "custom section `{CUSTOM_SECTION_NAME}` uses format {version} but only {CURRENT_VERSION} is supported"
329 ),
330 };
331 Ok((resolve, world, string_encoding))
332}
333
334fn encode_string_encoding(e: StringEncoding) -> u8 {
335 match e {
336 StringEncoding::UTF8 => 0x00,
337 StringEncoding::UTF16 => 0x01,
338 StringEncoding::CompactUTF16 => 0x02,
339 }
340}
341
342fn decode_string_encoding(byte: u8) -> Result<StringEncoding> {
343 match byte {
344 0x00 => Ok(StringEncoding::UTF8),
345 0x01 => Ok(StringEncoding::UTF16),
346 0x02 => Ok(StringEncoding::CompactUTF16),
347 byte => bail!("invalid string encoding {byte:#x}"),
348 }
349}
350
351impl Bindgen {
352 fn decode_custom_section(data: &[u8]) -> Result<Bindgen> {
353 let wasm;
354 let world;
355 let resolve;
356 let encoding;
357
358 let mut reader = BinaryReader::new(&data, 0);
359 match reader.read_u8()? {
360 0x03 => {
363 encoding = decode_string_encoding(reader.read_u8()?)?;
364 let world_name = reader.read_string()?;
365 let data_offset = reader.original_position() as usize;
366 wasm = &data[data_offset..];
367
368 let (r, pkg) = match crate::decode(wasm)? {
369 DecodedWasm::WitPackage(resolve, pkgs) => (resolve, pkgs),
370 DecodedWasm::Component(..) => bail!("expected encoded wit package(s)"),
371 };
372 resolve = r;
373 world = resolve.select_world(&[pkg], Some(world_name.into()))?;
374 }
375
376 _ => {
378 wasm = data;
379 (resolve, world, encoding) = decode_custom_section(wasm)?;
380 }
381 }
382
383 Ok(Bindgen {
384 metadata: ModuleMetadata::new(&resolve, world, encoding),
385 producers: wasm_metadata::Producers::from_wasm(wasm)?,
386 resolve,
387 world,
388 })
389 }
390
391 pub fn merge(&mut self, other: Bindgen) -> Result<IndexSet<WorldKey>> {
404 let Bindgen {
405 resolve,
406 world,
407 metadata:
408 ModuleMetadata {
409 import_encodings,
410 export_encodings,
411 },
412 producers,
413 } = other;
414
415 let remap = self
416 .resolve
417 .merge(resolve)
418 .context("failed to merge WIT package sets together")?;
419 let world = remap.map_world(world, Default::default())?;
420 let exports = self.resolve.worlds[world].exports.keys().cloned().collect();
421 self.resolve
422 .merge_worlds(world, self.world, &mut CloneMaps::default())
423 .context("failed to merge worlds from two documents")?;
424
425 self.metadata.import_encodings.merge(import_encodings)?;
426 self.metadata.export_encodings.merge(export_encodings)?;
427 if let Some(producers) = producers {
428 if let Some(mine) = &mut self.producers {
429 mine.merge(&producers);
430 } else {
431 self.producers = Some(producers);
432 }
433 }
434
435 Ok(exports)
436 }
437}
438
439impl ModuleMetadata {
440 pub fn new(resolve: &Resolve, world: WorldId, encoding: StringEncoding) -> ModuleMetadata {
443 let mut ret = ModuleMetadata::default();
444
445 let world = &resolve.worlds[world];
446 ret.export_encodings
447 .insert_all(resolve, &world.exports, encoding);
448 ret.import_encodings
449 .insert_all(resolve, &world.imports, encoding);
450
451 ret
452 }
453}