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