wit_component/linking/
metadata.rs

1//! Support for parsing and analyzing [dynamic
2//! library](https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md) modules.
3
4use {
5    anyhow::{bail, Context, Error, Result},
6    std::{
7        collections::{BTreeSet, HashMap, HashSet},
8        fmt,
9    },
10    wasmparser::{
11        Dylink0Subsection, ExternalKind, FuncType, KnownCustom, MemInfo, Parser, Payload, RefType,
12        SymbolFlags, TableType, TypeRef, ValType,
13    },
14};
15
16/// Represents a core Wasm value type (not including V128 or reference types, which are not yet supported)
17#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
18pub enum ValueType {
19    I32,
20    I64,
21    F32,
22    F64,
23}
24
25impl TryFrom<ValType> for ValueType {
26    type Error = Error;
27
28    fn try_from(value: ValType) -> Result<Self> {
29        Ok(match value {
30            ValType::I32 => Self::I32,
31            ValType::I64 => Self::I64,
32            ValType::F32 => Self::F32,
33            ValType::F64 => Self::F64,
34            _ => bail!("{value:?} not yet supported"),
35        })
36    }
37}
38
39impl From<ValueType> for wasm_encoder::ValType {
40    fn from(value: ValueType) -> Self {
41        match value {
42            ValueType::I32 => Self::I32,
43            ValueType::I64 => Self::I64,
44            ValueType::F32 => Self::F32,
45            ValueType::F64 => Self::F64,
46        }
47    }
48}
49
50/// Represents a core Wasm function type
51#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
52pub struct FunctionType {
53    pub parameters: Vec<ValueType>,
54    pub results: Vec<ValueType>,
55}
56
57impl fmt::Display for FunctionType {
58    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
59        write!(f, "{:?} -> {:?}", self.parameters, self.results)
60    }
61}
62
63impl TryFrom<&FuncType> for FunctionType {
64    type Error = Error;
65
66    fn try_from(value: &FuncType) -> Result<Self> {
67        Ok(Self {
68            parameters: value
69                .params()
70                .iter()
71                .map(|&v| ValueType::try_from(v))
72                .collect::<Result<_>>()?,
73            results: value
74                .results()
75                .iter()
76                .map(|&v| ValueType::try_from(v))
77                .collect::<Result<_>>()?,
78        })
79    }
80}
81
82/// Represents a core Wasm global variable type
83#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
84pub struct GlobalType {
85    pub ty: ValueType,
86    pub mutable: bool,
87    pub shared: bool,
88}
89
90impl fmt::Display for GlobalType {
91    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
92        if self.mutable {
93            write!(f, "mut ")?;
94        }
95        write!(f, "{:?}", self.ty)
96    }
97}
98
99/// Represents a core Wasm export or import type
100#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
101pub enum Type {
102    Function(FunctionType),
103    Global(GlobalType),
104}
105
106impl fmt::Display for Type {
107    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
108        match self {
109            Self::Function(ty) => write!(f, "function {ty}"),
110            Self::Global(ty) => write!(f, "global {ty}"),
111        }
112    }
113}
114
115impl From<&Type> for wasm_encoder::ExportKind {
116    fn from(value: &Type) -> Self {
117        match value {
118            Type::Function(_) => wasm_encoder::ExportKind::Func,
119            Type::Global(_) => wasm_encoder::ExportKind::Global,
120        }
121    }
122}
123
124/// Represents a core Wasm import
125#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
126pub struct Import<'a> {
127    pub module: &'a str,
128    pub name: &'a str,
129    pub ty: Type,
130    pub flags: SymbolFlags,
131}
132
133/// Represents a core Wasm export
134#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
135pub struct ExportKey<'a> {
136    pub name: &'a str,
137    pub ty: Type,
138}
139
140impl<'a> fmt::Display for ExportKey<'a> {
141    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
142        write!(f, "{} ({})", self.name, self.ty)
143    }
144}
145
146/// Represents a core Wasm export, including dylink.0 flags
147#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
148pub struct Export<'a> {
149    pub key: ExportKey<'a>,
150    pub flags: SymbolFlags,
151}
152
153/// Metadata extracted from a dynamic library module
154#[derive(Debug)]
155pub struct Metadata<'a> {
156    /// The name of the module
157    ///
158    /// This is currently not part of the file itself and must be provided separately, but the plan is to add
159    /// something like a `WASM_DYLINK_SO_NAME` field to the dynamic linking tool convention so we can parse it
160    /// along with everything else.
161    pub name: &'a str,
162
163    /// Whether this module should be resolvable via `dlopen`
164    pub dl_openable: bool,
165
166    /// The `WASM_DYLINK_MEM_INFO` value (or all zeros if not found)
167    pub mem_info: MemInfo,
168
169    /// The `WASM_DYLINK_NEEDED` values, if any
170    pub needed_libs: Vec<&'a str>,
171
172    /// Whether this module exports `__wasm_apply_data_relocs`
173    pub has_data_relocs: bool,
174
175    /// Whether this module exports `__wasm_call_ctors`
176    pub has_ctors: bool,
177
178    /// Whether this module exports `_initialize`
179    pub has_initialize: bool,
180
181    /// Whether this module exports `_start`
182    pub has_wasi_start: bool,
183
184    /// Whether this module exports `__wasm_set_libraries`
185    pub has_set_libraries: bool,
186
187    /// Whether this module includes any `component-type*` custom sections which include exports
188    pub has_component_exports: bool,
189
190    /// Whether this module imports `__asyncify_state` or `__asyncify_data`, indicating that it is
191    /// asyncified with `--pass-arg=asyncify-relocatable` option.
192    pub is_asyncified: bool,
193
194    /// The functions imported from the `env` module, if any
195    pub env_imports: BTreeSet<(&'a str, (FunctionType, SymbolFlags))>,
196
197    /// The memory addresses imported from `GOT.mem`, if any
198    pub memory_address_imports: BTreeSet<&'a str>,
199
200    /// The table addresses imported from `GOT.func`, if any
201    pub table_address_imports: BTreeSet<&'a str>,
202
203    /// The symbols exported by this module, if any
204    pub exports: BTreeSet<Export<'a>>,
205
206    /// The symbols imported by this module (and not accounted for in the above fields), if any
207    pub imports: BTreeSet<Import<'a>>,
208}
209
210impl<'a> Metadata<'a> {
211    /// Parse the specified module and extract its metadata.
212    pub fn try_new(
213        name: &'a str,
214        dl_openable: bool,
215        module: &'a [u8],
216        adapter_names: &HashSet<&str>,
217    ) -> Result<Self> {
218        let bindgen = crate::metadata::decode(module)?.1;
219        let has_component_exports = !bindgen.resolve.worlds[bindgen.world].exports.is_empty();
220
221        let mut result = Self {
222            name,
223            dl_openable,
224            mem_info: MemInfo {
225                memory_size: 0,
226                memory_alignment: 1,
227                table_size: 0,
228                table_alignment: 1,
229            },
230            needed_libs: Vec::new(),
231            has_data_relocs: false,
232            has_ctors: false,
233            has_initialize: false,
234            has_wasi_start: false,
235            has_set_libraries: false,
236            has_component_exports,
237            is_asyncified: false,
238            env_imports: BTreeSet::new(),
239            memory_address_imports: BTreeSet::new(),
240            table_address_imports: BTreeSet::new(),
241            exports: BTreeSet::new(),
242            imports: BTreeSet::new(),
243        };
244        let mut types = Vec::new();
245        let mut function_types = Vec::new();
246        let mut global_types = Vec::new();
247        let mut import_info = HashMap::new();
248        let mut export_info = HashMap::new();
249
250        for payload in Parser::new(0).parse_all(module) {
251            match payload? {
252                Payload::CustomSection(section) => {
253                    if let KnownCustom::Dylink0(reader) = section.as_known() {
254                        for subsection in reader {
255                            match subsection.context("failed to parse `dylink.0` subsection")? {
256                                Dylink0Subsection::MemInfo(info) => result.mem_info = info,
257                                Dylink0Subsection::Needed(needed) => {
258                                    result.needed_libs = needed.clone()
259                                }
260                                Dylink0Subsection::ExportInfo(info) => {
261                                    export_info
262                                        .extend(info.iter().map(|info| (info.name, info.flags)));
263                                }
264                                Dylink0Subsection::ImportInfo(info) => {
265                                    import_info.extend(
266                                        info.iter()
267                                            .map(|info| ((info.module, info.field), info.flags)),
268                                    );
269                                }
270                                Dylink0Subsection::Unknown { ty, .. } => {
271                                    bail!("unrecognized `dylink.0` subsection: {ty}")
272                                }
273                            }
274                        }
275                    }
276                }
277
278                Payload::TypeSection(reader) => {
279                    types = reader
280                        .into_iter_err_on_gc_types()
281                        .collect::<Result<Vec<_>, _>>()?;
282                }
283
284                Payload::ImportSection(reader) => {
285                    for import in reader {
286                        let import = import?;
287
288                        match import.ty {
289                            TypeRef::Func(ty) => function_types.push(usize::try_from(ty).unwrap()),
290                            TypeRef::Global(ty) => global_types.push(ty),
291                            _ => (),
292                        }
293
294                        let type_error = || {
295                            bail!(
296                                "unexpected type for {}:{}: {:?}",
297                                import.module,
298                                import.name,
299                                import.ty
300                            )
301                        };
302
303                        match (import.module, import.name) {
304                            ("env", "memory") => {
305                                if !matches!(import.ty, TypeRef::Memory(_)) {
306                                    return type_error();
307                                }
308                            }
309                            ("env", "__asyncify_data" | "__asyncify_state") => {
310                                result.is_asyncified = true;
311                                if !matches!(
312                                    import.ty,
313                                    TypeRef::Global(wasmparser::GlobalType {
314                                        content_type: ValType::I32,
315                                        ..
316                                    })
317                                ) {
318                                    return type_error();
319                                }
320                            }
321                            ("env", "__memory_base" | "__table_base" | "__stack_pointer") => {
322                                if !matches!(
323                                    import.ty,
324                                    TypeRef::Global(wasmparser::GlobalType {
325                                        content_type: ValType::I32,
326                                        ..
327                                    })
328                                ) {
329                                    return type_error();
330                                }
331                            }
332                            ("env", "__indirect_function_table") => {
333                                if let TypeRef::Table(TableType {
334                                    element_type,
335                                    maximum: None,
336                                    ..
337                                }) = import.ty
338                                {
339                                    if element_type != RefType::FUNCREF {
340                                        return type_error();
341                                    }
342                                } else {
343                                    return type_error();
344                                }
345                            }
346                            ("env", name) => {
347                                if let TypeRef::Func(ty) = import.ty {
348                                    result.env_imports.insert((
349                                        name,
350                                        (
351                                            FunctionType::try_from(
352                                                &types[usize::try_from(ty).unwrap()],
353                                            )?,
354                                            import_info
355                                                .get(&("env", name))
356                                                .copied()
357                                                .unwrap_or_default(),
358                                        ),
359                                    ));
360                                } else {
361                                    return type_error();
362                                }
363                            }
364                            ("GOT.mem", name) => {
365                                if let TypeRef::Global(wasmparser::GlobalType {
366                                    content_type: ValType::I32,
367                                    ..
368                                }) = import.ty
369                                {
370                                    match name {
371                                        "__heap_base" | "__heap_end" => (),
372                                        _ => {
373                                            result.memory_address_imports.insert(name);
374                                        }
375                                    }
376                                } else {
377                                    return type_error();
378                                }
379                            }
380                            ("GOT.func", name) => {
381                                if let TypeRef::Global(wasmparser::GlobalType {
382                                    content_type: ValType::I32,
383                                    ..
384                                }) = import.ty
385                                {
386                                    result.table_address_imports.insert(name);
387                                } else {
388                                    return type_error();
389                                }
390                            }
391                            (module, name) if adapter_names.contains(module) => {
392                                let ty = match import.ty {
393                                    TypeRef::Global(wasmparser::GlobalType {
394                                        content_type,
395                                        mutable,
396                                        shared,
397                                    }) => Type::Global(GlobalType {
398                                        ty: content_type.try_into()?,
399                                        mutable,
400                                        shared,
401                                    }),
402                                    TypeRef::Func(ty) => Type::Function(FunctionType::try_from(
403                                        &types[usize::try_from(ty).unwrap()],
404                                    )?),
405                                    ty => {
406                                        bail!("unsupported import kind for {module}.{name}: {ty:?}",)
407                                    }
408                                };
409                                let flags = import_info
410                                    .get(&(module, name))
411                                    .copied()
412                                    .unwrap_or_default();
413                                result.imports.insert(Import {
414                                    module,
415                                    name,
416                                    ty,
417                                    flags,
418                                });
419                            }
420                            _ => {
421                                if !matches!(import.ty, TypeRef::Func(_) | TypeRef::Global(_)) {
422                                    return type_error();
423                                }
424                            }
425                        }
426                    }
427                }
428
429                Payload::FunctionSection(reader) => {
430                    for function in reader {
431                        function_types.push(usize::try_from(function?).unwrap());
432                    }
433                }
434
435                Payload::GlobalSection(reader) => {
436                    for global in reader {
437                        global_types.push(global?.ty);
438                    }
439                }
440
441                Payload::ExportSection(reader) => {
442                    for export in reader {
443                        let export = export?;
444
445                        match export.name {
446                            "__wasm_apply_data_relocs" => result.has_data_relocs = true,
447                            "__wasm_call_ctors" => result.has_ctors = true,
448                            "_initialize" => result.has_initialize = true,
449                            "_start" => result.has_wasi_start = true,
450                            "__wasm_set_libraries" => result.has_set_libraries = true,
451                            _ => {
452                                let ty = match export.kind {
453                                    ExternalKind::Func => Type::Function(FunctionType::try_from(
454                                        &types[function_types
455                                            [usize::try_from(export.index).unwrap()]],
456                                    )?),
457                                    ExternalKind::Global => {
458                                        let ty =
459                                            global_types[usize::try_from(export.index).unwrap()];
460                                        Type::Global(GlobalType {
461                                            ty: ValueType::try_from(ty.content_type)?,
462                                            mutable: ty.mutable,
463                                            shared: ty.shared,
464                                        })
465                                    }
466                                    kind => {
467                                        bail!(
468                                            "unsupported export kind for {}: {kind:?}",
469                                            export.name
470                                        )
471                                    }
472                                };
473                                let flags =
474                                    export_info.get(&export.name).copied().unwrap_or_default();
475                                result.exports.insert(Export {
476                                    key: ExportKey {
477                                        name: export.name,
478                                        ty,
479                                    },
480                                    flags,
481                                });
482                            }
483                        }
484                    }
485                }
486
487                _ => {}
488            }
489        }
490
491        Ok(result)
492    }
493}