| 1 | //! Support for "pseudo-dynamic", shared-everything linking of Wasm modules into a component. |
| 2 | //! |
| 3 | //! This implements [shared-everything |
| 4 | //! linking](https://github.com/WebAssembly/component-model/blob/main/design/mvp/examples/SharedEverythingDynamicLinking.md), |
| 5 | //! taking as input one or more [dynamic |
| 6 | //! library](https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md) modules and producing a |
| 7 | //! component whose type is the union of any `component-type*` custom sections found in the input modules. |
| 8 | //! |
| 9 | //! The entry point into this process is `Linker::encode`, which analyzes and topologically sorts the input |
| 10 | //! modules, then sythesizes two additional modules: |
| 11 | //! |
| 12 | //! - `main` AKA `env`: hosts the component's single memory and function table and exports any functions needed to |
| 13 | //! break dependency cycles discovered in the input modules. Those functions use `call.indirect` to invoke the real |
| 14 | //! functions, references to which are placed in the table by the `init` module. |
| 15 | //! |
| 16 | //! - `init`: populates the function table as described above, initializes global variables per the dynamic linking |
| 17 | //! tool convention, and calls any static constructors and/or link-time fixup functions |
| 18 | //! |
| 19 | //! `Linker` also supports synthesizing `dlopen`/`dlsym` lookup tables which allow symbols to be resolved at |
| 20 | //! runtime. Note that this is not true dynamic linking, since all the code is baked into the component ahead of |
| 21 | //! time -- we simply allow runtime resolution of already-resident definitions. This is sufficient to support |
| 22 | //! dynamic language FFI features such as Python native extensions, provided the required libraries are linked |
| 23 | //! ahead-of-time. |
| 24 | |
| 25 | use { |
| 26 | crate::encoding::{ComponentEncoder, Instance, Item, LibraryInfo, MainOrAdapter}, |
| 27 | anyhow::{anyhow, bail, Context, Result}, |
| 28 | indexmap::{map::Entry, IndexMap, IndexSet}, |
| 29 | metadata::{Export, ExportKey, FunctionType, GlobalType, Metadata, Type, ValueType}, |
| 30 | std::{ |
| 31 | collections::{BTreeMap, HashMap, HashSet}, |
| 32 | fmt::Debug, |
| 33 | hash::Hash, |
| 34 | iter, |
| 35 | }, |
| 36 | wasm_encoder::{ |
| 37 | CodeSection, ConstExpr, DataSection, ElementSection, Elements, EntityType, ExportKind, |
| 38 | ExportSection, Function, FunctionSection, GlobalSection, ImportSection, Instruction as Ins, |
| 39 | MemArg, MemorySection, MemoryType, Module, RawCustomSection, RefType, StartSection, |
| 40 | TableSection, TableType, TypeSection, ValType, |
| 41 | }, |
| 42 | wasmparser::SymbolFlags, |
| 43 | }; |
| 44 | |
| 45 | mod metadata; |
| 46 | |
| 47 | const PAGE_SIZE_BYTES: u32 = 65536; |
| 48 | // This matches the default stack size LLVM produces: |
| 49 | pub const DEFAULT_STACK_SIZE_BYTES: u32 = 16 * PAGE_SIZE_BYTES; |
| 50 | const HEAP_ALIGNMENT_BYTES: u32 = 16; |
| 51 | |
| 52 | enum Address<'a> { |
| 53 | Function(u32), |
| 54 | Global(&'a str), |
| 55 | } |
| 56 | |
| 57 | /// Represents a `dlopen`/`dlsym` lookup table enabling runtime symbol resolution |
| 58 | /// |
| 59 | /// The top level of this table is a sorted list of library names and offsets, each pointing to a sorted list of |
| 60 | /// symbol names and offsets. See ../dl/src/lib.rs for how this is used at runtime. |
| 61 | struct DlOpenables<'a> { |
| 62 | /// Offset into the main module's table where function references will be stored |
| 63 | table_base: u32, |
| 64 | |
| 65 | /// Offset into the main module's memory where the lookup table will be stored |
| 66 | memory_base: u32, |
| 67 | |
| 68 | /// The lookup table itself |
| 69 | buffer: Vec<u8>, |
| 70 | |
| 71 | /// Linear memory addresses where global variable addresses will live |
| 72 | /// |
| 73 | /// The init module will fill in the correct values at insantiation time. |
| 74 | global_addresses: Vec<(&'a str, &'a str, u32)>, |
| 75 | |
| 76 | /// Number of function references to be stored in the main module's table |
| 77 | function_count: u32, |
| 78 | |
| 79 | /// Linear memory address where the root of the lookup table will reside |
| 80 | /// |
| 81 | /// This can be different from `memory_base` depending on how the tree of libraries and symbols is laid out in |
| 82 | /// memory. |
| 83 | libraries_address: u32, |
| 84 | } |
| 85 | |
| 86 | impl<'a> DlOpenables<'a> { |
| 87 | /// Construct a lookup table containing all "dlopen-able" libraries and their symbols using the specified table |
| 88 | /// and memory offsets. |
| 89 | fn new(table_base: u32, memory_base: u32, metadata: &'a [Metadata<'a>]) -> Self { |
| 90 | let mut function_count = 0; |
| 91 | let mut buffer = Vec::new(); |
| 92 | let mut global_addresses = Vec::new(); |
| 93 | let mut libraries = metadata |
| 94 | .iter() |
| 95 | .filter(|metadata| metadata.dl_openable) |
| 96 | .map(|metadata| { |
| 97 | let name_address = memory_base + u32::try_from(buffer.len()).unwrap(); |
| 98 | write_bytes_padded(&mut buffer, metadata.name.as_bytes()); |
| 99 | |
| 100 | let mut symbols = metadata |
| 101 | .exports |
| 102 | .iter() |
| 103 | .map(|export| { |
| 104 | let name_address = memory_base + u32::try_from(buffer.len()).unwrap(); |
| 105 | write_bytes_padded(&mut buffer, export.key.name.as_bytes()); |
| 106 | |
| 107 | let address = match &export.key.ty { |
| 108 | Type::Function(_) => Address::Function( |
| 109 | table_base + get_and_increment(&mut function_count), |
| 110 | ), |
| 111 | Type::Global(_) => Address::Global(export.key.name), |
| 112 | }; |
| 113 | |
| 114 | (export.key.name, name_address, address) |
| 115 | }) |
| 116 | .collect::<Vec<_>>(); |
| 117 | |
| 118 | symbols.sort_by_key(|(name, ..)| *name); |
| 119 | |
| 120 | let start = buffer.len(); |
| 121 | for (name, name_address, address) in symbols { |
| 122 | write_u32(&mut buffer, u32::try_from(name.len()).unwrap()); |
| 123 | write_u32(&mut buffer, name_address); |
| 124 | match address { |
| 125 | Address::Function(address) => write_u32(&mut buffer, address), |
| 126 | Address::Global(name) => { |
| 127 | global_addresses.push(( |
| 128 | metadata.name, |
| 129 | name, |
| 130 | memory_base + u32::try_from(buffer.len()).unwrap(), |
| 131 | )); |
| 132 | |
| 133 | write_u32(&mut buffer, 0); |
| 134 | } |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | ( |
| 139 | metadata.name, |
| 140 | name_address, |
| 141 | metadata.exports.len(), |
| 142 | memory_base + u32::try_from(start).unwrap(), |
| 143 | ) |
| 144 | }) |
| 145 | .collect::<Vec<_>>(); |
| 146 | |
| 147 | libraries.sort_by_key(|(name, ..)| *name); |
| 148 | |
| 149 | let start = buffer.len(); |
| 150 | for (name, name_address, count, symbols) in &libraries { |
| 151 | write_u32(&mut buffer, u32::try_from(name.len()).unwrap()); |
| 152 | write_u32(&mut buffer, *name_address); |
| 153 | write_u32(&mut buffer, u32::try_from(*count).unwrap()); |
| 154 | write_u32(&mut buffer, *symbols); |
| 155 | } |
| 156 | |
| 157 | let libraries_address = memory_base + u32::try_from(buffer.len()).unwrap(); |
| 158 | write_u32(&mut buffer, u32::try_from(libraries.len()).unwrap()); |
| 159 | write_u32(&mut buffer, memory_base + u32::try_from(start).unwrap()); |
| 160 | |
| 161 | Self { |
| 162 | table_base, |
| 163 | memory_base, |
| 164 | buffer, |
| 165 | global_addresses, |
| 166 | function_count, |
| 167 | libraries_address, |
| 168 | } |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | fn write_u32(buffer: &mut Vec<u8>, value: u32) { |
| 173 | buffer.extend(iter:value.to_le_bytes()); |
| 174 | } |
| 175 | |
| 176 | fn write_bytes_padded(buffer: &mut Vec<u8>, bytes: &[u8]) { |
| 177 | buffer.extend(iter:bytes); |
| 178 | |
| 179 | let len: u32 = u32::try_from(bytes.len()).unwrap(); |
| 180 | for _ in len..align(a:len, b:4) { |
| 181 | buffer.push(0); |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | fn align(a: u32, b: u32) -> u32 { |
| 186 | assert!(b.is_power_of_two()); |
| 187 | (a + (b - 1)) & !(b - 1) |
| 188 | } |
| 189 | |
| 190 | fn get_and_increment(n: &mut u32) -> u32 { |
| 191 | let v: u32 = *n; |
| 192 | *n += 1; |
| 193 | v |
| 194 | } |
| 195 | |
| 196 | fn const_u32(a: u32) -> ConstExpr { |
| 197 | ConstExpr::i32_const(a as i32) |
| 198 | } |
| 199 | |
| 200 | /// Helper trait for determining the size of a set or map |
| 201 | trait Length { |
| 202 | fn len(&self) -> usize; |
| 203 | } |
| 204 | |
| 205 | impl<T> Length for HashSet<T> { |
| 206 | fn len(&self) -> usize { |
| 207 | HashSet::len(self) |
| 208 | } |
| 209 | } |
| 210 | |
| 211 | impl<K, V> Length for HashMap<K, V> { |
| 212 | fn len(&self) -> usize { |
| 213 | HashMap::len(self) |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | impl<T> Length for IndexSet<T> { |
| 218 | fn len(&self) -> usize { |
| 219 | IndexSet::len(self) |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | impl<K, V> Length for IndexMap<K, V> { |
| 224 | fn len(&self) -> usize { |
| 225 | IndexMap::len(self) |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | /// Extension trait for collecting into a set or map and asserting that there were no duplicate entries in the |
| 230 | /// source iterator. |
| 231 | trait CollectUnique: Iterator + Sized { |
| 232 | fn collect_unique<T: FromIterator<Self::Item> + Length>(self) -> T { |
| 233 | let tmp: Vec<::Item> = self.collect::<Vec<_>>(); |
| 234 | let len: usize = tmp.len(); |
| 235 | let result: T = tmp.into_iter().collect::<T>(); |
| 236 | assert!( |
| 237 | result.len() == len, |
| 238 | "one or more duplicate items detected when collecting into set or map" |
| 239 | ); |
| 240 | result |
| 241 | } |
| 242 | } |
| 243 | |
| 244 | impl<T: Iterator> CollectUnique for T {} |
| 245 | |
| 246 | /// Extension trait for inserting into a map and asserting that an entry did not already exist for the key |
| 247 | trait InsertUnique { |
| 248 | type Key; |
| 249 | type Value; |
| 250 | |
| 251 | fn insert_unique(&mut self, k: Self::Key, v: Self::Value); |
| 252 | } |
| 253 | |
| 254 | impl<K: Hash + Eq + PartialEq + Debug, V: Debug> InsertUnique for HashMap<K, V> { |
| 255 | type Key = K; |
| 256 | type Value = V; |
| 257 | |
| 258 | fn insert_unique(&mut self, k: Self::Key, v: Self::Value) { |
| 259 | if let Some(old_v: &V) = self.get(&k) { |
| 260 | panic!("duplicate item inserted into map for key {k:?} (old value: {old_v:?}; new value: {v:?})" ); |
| 261 | } |
| 262 | self.insert(k, v); |
| 263 | } |
| 264 | } |
| 265 | |
| 266 | /// Synthesize the "main" module for the component, responsible for exporting functions which break cyclic |
| 267 | /// dependencies, as well as hosting the memory and function table. |
| 268 | fn make_env_module<'a>( |
| 269 | metadata: &'a [Metadata<'a>], |
| 270 | function_exports: &[(&str, &FunctionType, usize)], |
| 271 | cabi_realloc_exporter: Option<&str>, |
| 272 | stack_size_bytes: u32, |
| 273 | ) -> (Vec<u8>, DlOpenables<'a>, u32) { |
| 274 | // TODO: deduplicate types |
| 275 | let mut types = TypeSection::new(); |
| 276 | let mut imports = ImportSection::new(); |
| 277 | let mut import_map = IndexMap::new(); |
| 278 | let mut function_count = 0; |
| 279 | let mut global_offset = 0; |
| 280 | let mut wasi_start = None; |
| 281 | |
| 282 | for metadata in metadata { |
| 283 | for import in &metadata.imports { |
| 284 | if let Entry::Vacant(entry) = import_map.entry(import) { |
| 285 | imports.import( |
| 286 | import.module, |
| 287 | import.name, |
| 288 | match &import.ty { |
| 289 | Type::Function(ty) => { |
| 290 | let index = get_and_increment(&mut function_count); |
| 291 | entry.insert(index); |
| 292 | types.ty().function( |
| 293 | ty.parameters.iter().copied().map(ValType::from), |
| 294 | ty.results.iter().copied().map(ValType::from), |
| 295 | ); |
| 296 | EntityType::Function(index) |
| 297 | } |
| 298 | Type::Global(ty) => { |
| 299 | entry.insert(get_and_increment(&mut global_offset)); |
| 300 | EntityType::Global(wasm_encoder::GlobalType { |
| 301 | val_type: ty.ty.into(), |
| 302 | mutable: ty.mutable, |
| 303 | shared: ty.shared, |
| 304 | }) |
| 305 | } |
| 306 | }, |
| 307 | ); |
| 308 | } |
| 309 | } |
| 310 | |
| 311 | if metadata.has_wasi_start { |
| 312 | if wasi_start.is_some() { |
| 313 | panic!("multiple libraries export _start" ); |
| 314 | } |
| 315 | let index = get_and_increment(&mut function_count); |
| 316 | |
| 317 | types.ty().function(vec![], vec![]); |
| 318 | imports.import(metadata.name, "_start" , EntityType::Function(index)); |
| 319 | |
| 320 | wasi_start = Some(index); |
| 321 | } |
| 322 | } |
| 323 | |
| 324 | let mut memory_offset = stack_size_bytes; |
| 325 | |
| 326 | // Table offset 0 is reserved for the null function pointer. |
| 327 | // This convention follows wasm-ld's table layout: |
| 328 | // https://github.com/llvm/llvm-project/blob/913622d012f72edb5ac3a501cef8639d0ebe471b/lld/wasm/Driver.cpp#L581-L584 |
| 329 | let mut table_offset = 1; |
| 330 | let mut globals = GlobalSection::new(); |
| 331 | let mut exports = ExportSection::new(); |
| 332 | |
| 333 | if let Some(exporter) = cabi_realloc_exporter { |
| 334 | let index = get_and_increment(&mut function_count); |
| 335 | types.ty().function([ValType::I32; 4], [ValType::I32]); |
| 336 | imports.import(exporter, "cabi_realloc" , EntityType::Function(index)); |
| 337 | exports.export("cabi_realloc" , ExportKind::Func, index); |
| 338 | } |
| 339 | |
| 340 | let dl_openables = DlOpenables::new(table_offset, memory_offset, metadata); |
| 341 | |
| 342 | table_offset += dl_openables.function_count; |
| 343 | memory_offset += u32::try_from(dl_openables.buffer.len()).unwrap(); |
| 344 | |
| 345 | let memory_size = { |
| 346 | let mut add_global_export = |name: &str, value, mutable| { |
| 347 | let index = globals.len(); |
| 348 | globals.global( |
| 349 | wasm_encoder::GlobalType { |
| 350 | val_type: ValType::I32, |
| 351 | mutable, |
| 352 | shared: false, |
| 353 | }, |
| 354 | &const_u32(value), |
| 355 | ); |
| 356 | exports.export(name, ExportKind::Global, index); |
| 357 | }; |
| 358 | |
| 359 | add_global_export("__stack_pointer" , stack_size_bytes, true); |
| 360 | |
| 361 | // Binaryen's Asyncify transform for shared everything linking requires these globals |
| 362 | // to be provided from env module |
| 363 | let has_asyncified_module = metadata.iter().any(|m| m.is_asyncified); |
| 364 | if has_asyncified_module { |
| 365 | add_global_export("__asyncify_state" , 0, true); |
| 366 | add_global_export("__asyncify_data" , 0, true); |
| 367 | } |
| 368 | |
| 369 | for metadata in metadata { |
| 370 | memory_offset = align(memory_offset, 1 << metadata.mem_info.memory_alignment); |
| 371 | table_offset = align(table_offset, 1 << metadata.mem_info.table_alignment); |
| 372 | |
| 373 | add_global_export( |
| 374 | &format!(" {}:memory_base" , metadata.name), |
| 375 | memory_offset, |
| 376 | false, |
| 377 | ); |
| 378 | add_global_export( |
| 379 | &format!(" {}:table_base" , metadata.name), |
| 380 | table_offset, |
| 381 | false, |
| 382 | ); |
| 383 | |
| 384 | memory_offset += metadata.mem_info.memory_size; |
| 385 | table_offset += metadata.mem_info.table_size; |
| 386 | |
| 387 | for import in &metadata.memory_address_imports { |
| 388 | // Note that we initialize this to zero and let the init module compute the real value at |
| 389 | // instantiation time. |
| 390 | add_global_export(&format!(" {}: {import}" , metadata.name), 0, true); |
| 391 | } |
| 392 | } |
| 393 | |
| 394 | { |
| 395 | let offsets = function_exports |
| 396 | .iter() |
| 397 | .enumerate() |
| 398 | .map(|(offset, (name, ..))| (*name, table_offset + u32::try_from(offset).unwrap())) |
| 399 | .collect_unique::<HashMap<_, _>>(); |
| 400 | |
| 401 | for metadata in metadata { |
| 402 | for import in &metadata.table_address_imports { |
| 403 | add_global_export( |
| 404 | &format!(" {}: {import}" , metadata.name), |
| 405 | *offsets.get(import).unwrap(), |
| 406 | true, |
| 407 | ); |
| 408 | } |
| 409 | } |
| 410 | } |
| 411 | |
| 412 | memory_offset = align(memory_offset, HEAP_ALIGNMENT_BYTES); |
| 413 | add_global_export("__heap_base" , memory_offset, true); |
| 414 | |
| 415 | let heap_end = align(memory_offset, PAGE_SIZE_BYTES); |
| 416 | add_global_export("__heap_end" , heap_end, true); |
| 417 | heap_end / PAGE_SIZE_BYTES |
| 418 | }; |
| 419 | |
| 420 | let indirection_table_base = table_offset; |
| 421 | |
| 422 | let mut functions = FunctionSection::new(); |
| 423 | let mut code = CodeSection::new(); |
| 424 | for (name, ty, _) in function_exports { |
| 425 | let index = get_and_increment(&mut function_count); |
| 426 | types.ty().function( |
| 427 | ty.parameters.iter().copied().map(ValType::from), |
| 428 | ty.results.iter().copied().map(ValType::from), |
| 429 | ); |
| 430 | functions.function(u32::try_from(index).unwrap()); |
| 431 | let mut function = Function::new([]); |
| 432 | for local in 0..ty.parameters.len() { |
| 433 | function.instruction(&Ins::LocalGet(u32::try_from(local).unwrap())); |
| 434 | } |
| 435 | function.instruction(&Ins::I32Const(i32::try_from(table_offset).unwrap())); |
| 436 | function.instruction(&Ins::CallIndirect { |
| 437 | type_index: u32::try_from(index).unwrap(), |
| 438 | table_index: 0, |
| 439 | }); |
| 440 | function.instruction(&Ins::End); |
| 441 | code.function(&function); |
| 442 | exports.export(name, ExportKind::Func, index); |
| 443 | |
| 444 | table_offset += 1; |
| 445 | } |
| 446 | |
| 447 | for (import, offset) in import_map { |
| 448 | exports.export( |
| 449 | &format!(" {}: {}" , import.module, import.name), |
| 450 | ExportKind::from(&import.ty), |
| 451 | offset, |
| 452 | ); |
| 453 | } |
| 454 | if let Some(index) = wasi_start { |
| 455 | exports.export("_start" , ExportKind::Func, index); |
| 456 | } |
| 457 | |
| 458 | let mut module = Module::new(); |
| 459 | |
| 460 | module.section(&types); |
| 461 | module.section(&imports); |
| 462 | module.section(&functions); |
| 463 | |
| 464 | { |
| 465 | let mut tables = TableSection::new(); |
| 466 | tables.table(TableType { |
| 467 | element_type: RefType::FUNCREF, |
| 468 | minimum: table_offset.into(), |
| 469 | maximum: None, |
| 470 | table64: false, |
| 471 | shared: false, |
| 472 | }); |
| 473 | exports.export("__indirect_function_table" , ExportKind::Table, 0); |
| 474 | module.section(&tables); |
| 475 | } |
| 476 | |
| 477 | { |
| 478 | let mut memories = MemorySection::new(); |
| 479 | memories.memory(MemoryType { |
| 480 | minimum: u64::from(memory_size), |
| 481 | maximum: None, |
| 482 | memory64: false, |
| 483 | shared: false, |
| 484 | page_size_log2: None, |
| 485 | }); |
| 486 | exports.export("memory" , ExportKind::Memory, 0); |
| 487 | module.section(&memories); |
| 488 | } |
| 489 | |
| 490 | module.section(&globals); |
| 491 | module.section(&exports); |
| 492 | module.section(&code); |
| 493 | module.section(&RawCustomSection( |
| 494 | &crate::base_producers().raw_custom_section(), |
| 495 | )); |
| 496 | |
| 497 | let module = module.finish(); |
| 498 | wasmparser::validate(&module).unwrap(); |
| 499 | |
| 500 | (module, dl_openables, indirection_table_base) |
| 501 | } |
| 502 | |
| 503 | /// Synthesize the "init" module, responsible for initializing global variables per the dynamic linking tool |
| 504 | /// convention and calling any static constructors and/or link-time fixup functions. |
| 505 | /// |
| 506 | /// This module also contains the data segment for the `dlopen`/`dlsym` lookup table. |
| 507 | fn make_init_module( |
| 508 | metadata: &[Metadata], |
| 509 | exporters: &IndexMap<&ExportKey, (&str, &Export)>, |
| 510 | function_exports: &[(&str, &FunctionType, usize)], |
| 511 | dl_openables: DlOpenables, |
| 512 | indirection_table_base: u32, |
| 513 | ) -> Result<Vec<u8>> { |
| 514 | let mut module = Module::new(); |
| 515 | |
| 516 | // TODO: deduplicate types |
| 517 | let mut types = TypeSection::new(); |
| 518 | types.ty().function([], []); |
| 519 | let thunk_ty = 0; |
| 520 | types.ty().function([ValType::I32], []); |
| 521 | let one_i32_param_ty = 1; |
| 522 | let mut type_offset = 2; |
| 523 | |
| 524 | for metadata in metadata { |
| 525 | if metadata.dl_openable { |
| 526 | for export in &metadata.exports { |
| 527 | if let Type::Function(ty) = &export.key.ty { |
| 528 | types.ty().function( |
| 529 | ty.parameters.iter().copied().map(ValType::from), |
| 530 | ty.results.iter().copied().map(ValType::from), |
| 531 | ); |
| 532 | } |
| 533 | } |
| 534 | } |
| 535 | } |
| 536 | for (_, ty, _) in function_exports { |
| 537 | types.ty().function( |
| 538 | ty.parameters.iter().copied().map(ValType::from), |
| 539 | ty.results.iter().copied().map(ValType::from), |
| 540 | ); |
| 541 | } |
| 542 | module.section(&types); |
| 543 | |
| 544 | let mut imports = ImportSection::new(); |
| 545 | imports.import( |
| 546 | "env" , |
| 547 | "memory" , |
| 548 | MemoryType { |
| 549 | minimum: 0, |
| 550 | maximum: None, |
| 551 | memory64: false, |
| 552 | shared: false, |
| 553 | page_size_log2: None, |
| 554 | }, |
| 555 | ); |
| 556 | imports.import( |
| 557 | "env" , |
| 558 | "__indirect_function_table" , |
| 559 | TableType { |
| 560 | element_type: RefType::FUNCREF, |
| 561 | minimum: 0, |
| 562 | maximum: None, |
| 563 | table64: false, |
| 564 | shared: false, |
| 565 | }, |
| 566 | ); |
| 567 | |
| 568 | let mut global_count = 0; |
| 569 | let mut global_map = HashMap::new(); |
| 570 | let mut add_global_import = |imports: &mut ImportSection, module: &str, name: &str, mutable| { |
| 571 | *global_map |
| 572 | .entry((module.to_owned(), name.to_owned())) |
| 573 | .or_insert_with(|| { |
| 574 | imports.import( |
| 575 | module, |
| 576 | name, |
| 577 | wasm_encoder::GlobalType { |
| 578 | val_type: ValType::I32, |
| 579 | mutable, |
| 580 | shared: false, |
| 581 | }, |
| 582 | ); |
| 583 | get_and_increment(&mut global_count) |
| 584 | }) |
| 585 | }; |
| 586 | |
| 587 | let mut function_count = 0; |
| 588 | let mut function_map = HashMap::new(); |
| 589 | let mut add_function_import = |imports: &mut ImportSection, module: &str, name: &str, ty| { |
| 590 | *function_map |
| 591 | .entry((module.to_owned(), name.to_owned())) |
| 592 | .or_insert_with(|| { |
| 593 | imports.import(module, name, EntityType::Function(ty)); |
| 594 | get_and_increment(&mut function_count) |
| 595 | }) |
| 596 | }; |
| 597 | |
| 598 | let mut memory_address_inits = Vec::new(); |
| 599 | let mut reloc_calls = Vec::new(); |
| 600 | let mut ctor_calls = Vec::new(); |
| 601 | let mut names = HashMap::new(); |
| 602 | |
| 603 | for (exporter, export, address) in dl_openables.global_addresses.iter() { |
| 604 | memory_address_inits.push(Ins::I32Const(i32::try_from(*address).unwrap())); |
| 605 | memory_address_inits.push(Ins::GlobalGet(add_global_import( |
| 606 | &mut imports, |
| 607 | "env" , |
| 608 | &format!(" {exporter}:memory_base" ), |
| 609 | false, |
| 610 | ))); |
| 611 | memory_address_inits.push(Ins::GlobalGet(add_global_import( |
| 612 | &mut imports, |
| 613 | exporter, |
| 614 | export, |
| 615 | false, |
| 616 | ))); |
| 617 | memory_address_inits.push(Ins::I32Add); |
| 618 | memory_address_inits.push(Ins::I32Store(MemArg { |
| 619 | offset: 0, |
| 620 | align: 2, |
| 621 | memory_index: 0, |
| 622 | })); |
| 623 | } |
| 624 | |
| 625 | for (index, metadata) in metadata.iter().enumerate() { |
| 626 | names.insert_unique(index, metadata.name); |
| 627 | |
| 628 | if metadata.has_data_relocs { |
| 629 | reloc_calls.push(Ins::Call(add_function_import( |
| 630 | &mut imports, |
| 631 | metadata.name, |
| 632 | "__wasm_apply_data_relocs" , |
| 633 | thunk_ty, |
| 634 | ))); |
| 635 | } |
| 636 | |
| 637 | if metadata.has_ctors && metadata.has_initialize { |
| 638 | bail!( |
| 639 | "library {} exports both `__wasm_call_ctors` and `_initialize`; \ |
| 640 | expected at most one of the two" , |
| 641 | metadata.name |
| 642 | ); |
| 643 | } |
| 644 | |
| 645 | if metadata.has_ctors { |
| 646 | ctor_calls.push(Ins::Call(add_function_import( |
| 647 | &mut imports, |
| 648 | metadata.name, |
| 649 | "__wasm_call_ctors" , |
| 650 | thunk_ty, |
| 651 | ))); |
| 652 | } |
| 653 | |
| 654 | if metadata.has_initialize { |
| 655 | ctor_calls.push(Ins::Call(add_function_import( |
| 656 | &mut imports, |
| 657 | metadata.name, |
| 658 | "_initialize" , |
| 659 | thunk_ty, |
| 660 | ))); |
| 661 | } |
| 662 | |
| 663 | if metadata.has_set_libraries { |
| 664 | ctor_calls.push(Ins::I32Const( |
| 665 | i32::try_from(dl_openables.libraries_address).unwrap(), |
| 666 | )); |
| 667 | ctor_calls.push(Ins::Call(add_function_import( |
| 668 | &mut imports, |
| 669 | metadata.name, |
| 670 | "__wasm_set_libraries" , |
| 671 | one_i32_param_ty, |
| 672 | ))); |
| 673 | } |
| 674 | |
| 675 | for import in &metadata.memory_address_imports { |
| 676 | let (exporter, _) = find_offset_exporter(import, exporters)?; |
| 677 | |
| 678 | memory_address_inits.push(Ins::GlobalGet(add_global_import( |
| 679 | &mut imports, |
| 680 | "env" , |
| 681 | &format!(" {exporter}:memory_base" ), |
| 682 | false, |
| 683 | ))); |
| 684 | memory_address_inits.push(Ins::GlobalGet(add_global_import( |
| 685 | &mut imports, |
| 686 | exporter, |
| 687 | import, |
| 688 | false, |
| 689 | ))); |
| 690 | memory_address_inits.push(Ins::I32Add); |
| 691 | memory_address_inits.push(Ins::GlobalSet(add_global_import( |
| 692 | &mut imports, |
| 693 | "env" , |
| 694 | &format!(" {}: {import}" , metadata.name), |
| 695 | true, |
| 696 | ))); |
| 697 | } |
| 698 | } |
| 699 | |
| 700 | let mut dl_openable_functions = Vec::new(); |
| 701 | for metadata in metadata { |
| 702 | if metadata.dl_openable { |
| 703 | for export in &metadata.exports { |
| 704 | if let Type::Function(_) = &export.key.ty { |
| 705 | dl_openable_functions.push(add_function_import( |
| 706 | &mut imports, |
| 707 | metadata.name, |
| 708 | export.key.name, |
| 709 | get_and_increment(&mut type_offset), |
| 710 | )); |
| 711 | } |
| 712 | } |
| 713 | } |
| 714 | } |
| 715 | |
| 716 | let indirections = function_exports |
| 717 | .iter() |
| 718 | .map(|(name, _, index)| { |
| 719 | add_function_import( |
| 720 | &mut imports, |
| 721 | names[index], |
| 722 | name, |
| 723 | get_and_increment(&mut type_offset), |
| 724 | ) |
| 725 | }) |
| 726 | .collect::<Vec<_>>(); |
| 727 | |
| 728 | module.section(&imports); |
| 729 | |
| 730 | { |
| 731 | let mut functions = FunctionSection::new(); |
| 732 | functions.function(thunk_ty); |
| 733 | module.section(&functions); |
| 734 | } |
| 735 | |
| 736 | module.section(&StartSection { |
| 737 | function_index: function_count, |
| 738 | }); |
| 739 | |
| 740 | { |
| 741 | let mut elements = ElementSection::new(); |
| 742 | elements.active( |
| 743 | None, |
| 744 | &const_u32(dl_openables.table_base), |
| 745 | Elements::Functions(dl_openable_functions.into()), |
| 746 | ); |
| 747 | elements.active( |
| 748 | None, |
| 749 | &const_u32(indirection_table_base), |
| 750 | Elements::Functions(indirections.into()), |
| 751 | ); |
| 752 | module.section(&elements); |
| 753 | } |
| 754 | |
| 755 | { |
| 756 | let mut code = CodeSection::new(); |
| 757 | let mut function = Function::new([]); |
| 758 | for ins in memory_address_inits |
| 759 | .iter() |
| 760 | .chain(&reloc_calls) |
| 761 | .chain(&ctor_calls) |
| 762 | { |
| 763 | function.instruction(ins); |
| 764 | } |
| 765 | function.instruction(&Ins::End); |
| 766 | code.function(&function); |
| 767 | module.section(&code); |
| 768 | } |
| 769 | |
| 770 | let mut data = DataSection::new(); |
| 771 | data.active(0, &const_u32(dl_openables.memory_base), dl_openables.buffer); |
| 772 | module.section(&data); |
| 773 | |
| 774 | module.section(&RawCustomSection( |
| 775 | &crate::base_producers().raw_custom_section(), |
| 776 | )); |
| 777 | |
| 778 | let module = module.finish(); |
| 779 | wasmparser::validate(&module)?; |
| 780 | |
| 781 | Ok(module) |
| 782 | } |
| 783 | |
| 784 | /// Find the library which exports the specified function or global address. |
| 785 | fn find_offset_exporter<'a>( |
| 786 | name: &str, |
| 787 | exporters: &IndexMap<&ExportKey, (&'a str, &'a Export<'a>)>, |
| 788 | ) -> Result<(&'a str, &'a Export<'a>)> { |
| 789 | let export: ExportKey<'_> = ExportKey { |
| 790 | name, |
| 791 | ty: Type::Global(GlobalType { |
| 792 | ty: ValueType::I32, |
| 793 | mutable: false, |
| 794 | shared: false, |
| 795 | }), |
| 796 | }; |
| 797 | |
| 798 | exporters |
| 799 | .get(&export) |
| 800 | .copied() |
| 801 | .ok_or_else(|| anyhow!("unable to find {export:?} in any library" )) |
| 802 | } |
| 803 | |
| 804 | /// Find the library which exports the specified function. |
| 805 | fn find_function_exporter<'a>( |
| 806 | name: &str, |
| 807 | ty: &FunctionType, |
| 808 | exporters: &IndexMap<&ExportKey, (&'a str, &'a Export<'a>)>, |
| 809 | ) -> Result<(&'a str, &'a Export<'a>)> { |
| 810 | let export: ExportKey<'_> = ExportKey { |
| 811 | name, |
| 812 | ty: Type::Function(ty.clone()), |
| 813 | }; |
| 814 | |
| 815 | exporters |
| 816 | .get(&export) |
| 817 | .copied() |
| 818 | .ok_or_else(|| anyhow!("unable to find {export:?} in any library" )) |
| 819 | } |
| 820 | |
| 821 | /// Analyze the specified library metadata, producing a symbol-to-library-name map of exports. |
| 822 | fn resolve_exporters<'a>( |
| 823 | metadata: &'a [Metadata<'a>], |
| 824 | ) -> Result<IndexMap<&'a ExportKey<'a>, Vec<(&'a str, &'a Export<'a>)>>> { |
| 825 | let mut exporters: IndexMap<&ExportKey<'_>, Vec<…>> = IndexMap::<_, Vec<_>>::new(); |
| 826 | for metadata: &'a Metadata<'_> in metadata { |
| 827 | for export: &Export<'_> in &metadata.exports { |
| 828 | exporters&mut Vec<(&'a str, &Export<'_>)> |
| 829 | .entry(&export.key) |
| 830 | .or_default() |
| 831 | .push((metadata.name, export)); |
| 832 | } |
| 833 | } |
| 834 | Ok(exporters) |
| 835 | } |
| 836 | |
| 837 | /// Match up all imported symbols to their corresponding exports, reporting any missing or duplicate symbols. |
| 838 | fn resolve_symbols<'a>( |
| 839 | metadata: &'a [Metadata<'a>], |
| 840 | exporters: &'a IndexMap<&'a ExportKey<'a>, Vec<(&'a str, &'a Export<'a>)>>, |
| 841 | ) -> ( |
| 842 | IndexMap<&'a ExportKey<'a>, (&'a str, &'a Export<'a>)>, |
| 843 | Vec<(&'a str, Export<'a>)>, |
| 844 | Vec<(&'a str, &'a ExportKey<'a>, &'a [(&'a str, &'a Export<'a>)])>, |
| 845 | ) { |
| 846 | let function_exporters = exporters |
| 847 | .iter() |
| 848 | .filter_map(|(export, exporters)| { |
| 849 | if let Type::Function(_) = &export.ty { |
| 850 | Some((export.name, (export, exporters))) |
| 851 | } else { |
| 852 | None |
| 853 | } |
| 854 | }) |
| 855 | .collect_unique::<IndexMap<_, _>>(); |
| 856 | |
| 857 | let mut resolved = IndexMap::new(); |
| 858 | let mut missing = Vec::new(); |
| 859 | let mut duplicates = Vec::new(); |
| 860 | |
| 861 | let mut triage = |metadata: &'a Metadata, export: Export<'a>| { |
| 862 | if let Some((key, value)) = exporters.get_key_value(&export.key) { |
| 863 | match value.as_slice() { |
| 864 | [] => unreachable!(), |
| 865 | [exporter] => { |
| 866 | // Note that we do not use `insert_unique` here since multiple libraries may import the same |
| 867 | // symbol, in which case we may redundantly insert the same value. |
| 868 | resolved.insert(*key, *exporter); |
| 869 | } |
| 870 | [exporter, ..] => { |
| 871 | resolved.insert(*key, *exporter); |
| 872 | duplicates.push((metadata.name, *key, value.as_slice())); |
| 873 | } |
| 874 | } |
| 875 | } else { |
| 876 | missing.push((metadata.name, export)); |
| 877 | } |
| 878 | }; |
| 879 | |
| 880 | for metadata in metadata { |
| 881 | for (name, (ty, flags)) in &metadata.env_imports { |
| 882 | triage( |
| 883 | metadata, |
| 884 | Export { |
| 885 | key: ExportKey { |
| 886 | name, |
| 887 | ty: Type::Function(ty.clone()), |
| 888 | }, |
| 889 | flags: *flags, |
| 890 | }, |
| 891 | ); |
| 892 | } |
| 893 | |
| 894 | for name in &metadata.memory_address_imports { |
| 895 | triage( |
| 896 | metadata, |
| 897 | Export { |
| 898 | key: ExportKey { |
| 899 | name, |
| 900 | ty: Type::Global(GlobalType { |
| 901 | ty: ValueType::I32, |
| 902 | mutable: false, |
| 903 | shared: false, |
| 904 | }), |
| 905 | }, |
| 906 | flags: SymbolFlags::empty(), |
| 907 | }, |
| 908 | ); |
| 909 | } |
| 910 | } |
| 911 | |
| 912 | for metadata in metadata { |
| 913 | for name in &metadata.table_address_imports { |
| 914 | if let Some((key, value)) = function_exporters.get(name) { |
| 915 | // Note that we do not use `insert_unique` here since multiple libraries may import the same |
| 916 | // symbol, in which case we may redundantly insert the same value. |
| 917 | match value.as_slice() { |
| 918 | [] => unreachable!(), |
| 919 | [exporter] => { |
| 920 | resolved.insert(key, *exporter); |
| 921 | } |
| 922 | [exporter, ..] => { |
| 923 | resolved.insert(key, *exporter); |
| 924 | duplicates.push((metadata.name, *key, value.as_slice())); |
| 925 | } |
| 926 | } |
| 927 | } else { |
| 928 | missing.push(( |
| 929 | metadata.name, |
| 930 | Export { |
| 931 | key: ExportKey { |
| 932 | name, |
| 933 | ty: Type::Function(FunctionType { |
| 934 | parameters: Vec::new(), |
| 935 | results: Vec::new(), |
| 936 | }), |
| 937 | }, |
| 938 | flags: SymbolFlags::empty(), |
| 939 | }, |
| 940 | )); |
| 941 | } |
| 942 | } |
| 943 | } |
| 944 | |
| 945 | (resolved, missing, duplicates) |
| 946 | } |
| 947 | |
| 948 | /// Recursively add a library (represented by its offset) and its dependency to the specified set, maintaining |
| 949 | /// topological order (modulo cycles). |
| 950 | fn topo_add<'a>( |
| 951 | sorted: &mut IndexSet<usize>, |
| 952 | dependencies: &IndexMap<usize, IndexSet<usize>>, |
| 953 | element: usize, |
| 954 | ) { |
| 955 | let empty: &IndexSet = &IndexSet::new(); |
| 956 | let deps: &IndexSet = dependencies.get(&element).unwrap_or(default:empty); |
| 957 | |
| 958 | // First, add any dependencies which do not depend on `element` |
| 959 | for &dep: usize in deps { |
| 960 | if !(sorted.contains(&dep) || dependencies.get(&dep).unwrap_or(default:empty).contains(&element)) { |
| 961 | topo_add(sorted, dependencies, element:dep); |
| 962 | } |
| 963 | } |
| 964 | |
| 965 | // Next, add the element |
| 966 | sorted.insert(element); |
| 967 | |
| 968 | // Finally, add any dependencies which depend on `element` |
| 969 | for &dep: usize in deps { |
| 970 | if !sorted.contains(&dep) && dependencies.get(&dep).unwrap_or(default:empty).contains(&element) { |
| 971 | topo_add(sorted, dependencies, element:dep); |
| 972 | } |
| 973 | } |
| 974 | } |
| 975 | |
| 976 | /// Topologically sort a set of libraries (represented by their offsets) according to their dependencies, modulo |
| 977 | /// cycles. |
| 978 | fn topo_sort(count: usize, dependencies: &IndexMap<usize, IndexSet<usize>>) -> Result<Vec<usize>> { |
| 979 | let mut sorted: IndexSet = IndexSet::new(); |
| 980 | for index: usize in 0..count { |
| 981 | topo_add(&mut sorted, &dependencies, element:index); |
| 982 | } |
| 983 | |
| 984 | Ok(sorted.into_iter().collect()) |
| 985 | } |
| 986 | |
| 987 | /// Analyze the specified library metadata, producing a map of transitive dependencies, where each library is |
| 988 | /// represented by its offset in the original metadata slice. |
| 989 | fn find_dependencies( |
| 990 | metadata: &[Metadata], |
| 991 | exporters: &IndexMap<&ExportKey, (&str, &Export)>, |
| 992 | ) -> Result<IndexMap<usize, IndexSet<usize>>> { |
| 993 | // First, generate a map of direct dependencies (i.e. depender to dependees) |
| 994 | let mut dependencies = IndexMap::<_, IndexSet<_>>::new(); |
| 995 | let mut indexes = HashMap::new(); |
| 996 | for (index, metadata) in metadata.iter().enumerate() { |
| 997 | indexes.insert_unique(metadata.name, index); |
| 998 | for &needed in &metadata.needed_libs { |
| 999 | dependencies |
| 1000 | .entry(metadata.name) |
| 1001 | .or_default() |
| 1002 | .insert(needed); |
| 1003 | } |
| 1004 | for (import_name, (ty, _)) in &metadata.env_imports { |
| 1005 | dependencies |
| 1006 | .entry(metadata.name) |
| 1007 | .or_default() |
| 1008 | .insert(find_function_exporter(import_name, ty, exporters)?.0); |
| 1009 | } |
| 1010 | } |
| 1011 | |
| 1012 | // Next, convert the map from names to offsets |
| 1013 | let mut dependencies = dependencies |
| 1014 | .into_iter() |
| 1015 | .map(|(k, v)| { |
| 1016 | ( |
| 1017 | indexes[k], |
| 1018 | v.into_iter() |
| 1019 | .map(|v| indexes[v]) |
| 1020 | .collect_unique::<IndexSet<_>>(), |
| 1021 | ) |
| 1022 | }) |
| 1023 | .collect_unique::<IndexMap<_, _>>(); |
| 1024 | |
| 1025 | // Finally, add all transitive dependencies to the map in a fixpoint loop, exiting when no new dependencies are |
| 1026 | // discovered. |
| 1027 | let empty = &IndexSet::new(); |
| 1028 | |
| 1029 | loop { |
| 1030 | let mut new = IndexMap::<_, IndexSet<_>>::new(); |
| 1031 | for (index, exporters) in &dependencies { |
| 1032 | for exporter in exporters { |
| 1033 | for exporter in dependencies.get(exporter).unwrap_or(empty) { |
| 1034 | if !exporters.contains(exporter) { |
| 1035 | new.entry(*index).or_default().insert(*exporter); |
| 1036 | } |
| 1037 | } |
| 1038 | } |
| 1039 | } |
| 1040 | |
| 1041 | if new.is_empty() { |
| 1042 | break Ok(dependencies); |
| 1043 | } else { |
| 1044 | for (index, exporters) in new { |
| 1045 | dependencies.entry(index).or_default().extend(exporters); |
| 1046 | } |
| 1047 | } |
| 1048 | } |
| 1049 | } |
| 1050 | |
| 1051 | struct EnvFunctionExports<'a> { |
| 1052 | exports: Vec<(&'a str, &'a FunctionType, usize)>, |
| 1053 | reexport_cabi_realloc: bool, |
| 1054 | } |
| 1055 | |
| 1056 | /// Analyze the specified metadata and generate a list of functions which should be re-exported as a |
| 1057 | /// `call.indirect`-based function by the main (AKA "env") module, including the offset of the library containing |
| 1058 | /// the original export. |
| 1059 | fn env_function_exports<'a>( |
| 1060 | metadata: &'a [Metadata<'a>], |
| 1061 | exporters: &'a IndexMap<&'a ExportKey, (&'a str, &Export)>, |
| 1062 | topo_sorted: &[usize], |
| 1063 | ) -> Result<EnvFunctionExports<'a>> { |
| 1064 | let function_exporters = exporters |
| 1065 | .iter() |
| 1066 | .filter_map(|(export, exporter)| { |
| 1067 | if let Type::Function(ty) = &export.ty { |
| 1068 | Some((export.name, (ty, *exporter))) |
| 1069 | } else { |
| 1070 | None |
| 1071 | } |
| 1072 | }) |
| 1073 | .collect_unique::<HashMap<_, _>>(); |
| 1074 | |
| 1075 | let indexes = metadata |
| 1076 | .iter() |
| 1077 | .enumerate() |
| 1078 | .map(|(index, metadata)| (metadata.name, index)) |
| 1079 | .collect_unique::<HashMap<_, _>>(); |
| 1080 | |
| 1081 | let mut result = Vec::new(); |
| 1082 | let mut exported = HashSet::new(); |
| 1083 | let mut seen = HashSet::new(); |
| 1084 | |
| 1085 | for &index in topo_sorted { |
| 1086 | let metadata = &metadata[index]; |
| 1087 | |
| 1088 | for name in &metadata.table_address_imports { |
| 1089 | if !exported.contains(name) { |
| 1090 | let (ty, (exporter, _)) = function_exporters |
| 1091 | .get(name) |
| 1092 | .ok_or_else(|| anyhow!("unable to find {name:?} in any library" ))?; |
| 1093 | |
| 1094 | result.push((*name, *ty, indexes[exporter])); |
| 1095 | exported.insert(*name); |
| 1096 | } |
| 1097 | } |
| 1098 | |
| 1099 | for (import_name, (ty, _)) in &metadata.env_imports { |
| 1100 | if !exported.contains(import_name) { |
| 1101 | let exporter = indexes[find_function_exporter(import_name, ty, exporters) |
| 1102 | .unwrap() |
| 1103 | .0]; |
| 1104 | if !seen.contains(&exporter) { |
| 1105 | result.push((*import_name, ty, exporter)); |
| 1106 | exported.insert(*import_name); |
| 1107 | } |
| 1108 | } |
| 1109 | } |
| 1110 | seen.insert(index); |
| 1111 | } |
| 1112 | |
| 1113 | let reexport_cabi_realloc = exported.contains("cabi_realloc" ); |
| 1114 | |
| 1115 | Ok(EnvFunctionExports { |
| 1116 | exports: result, |
| 1117 | reexport_cabi_realloc, |
| 1118 | }) |
| 1119 | } |
| 1120 | |
| 1121 | /// Synthesize a module which contains trapping stub exports for the specified functions. |
| 1122 | fn make_stubs_module(missing: &[(&str, Export)]) -> Vec<u8> { |
| 1123 | let mut types = TypeSection::new(); |
| 1124 | let mut exports = ExportSection::new(); |
| 1125 | let mut functions = FunctionSection::new(); |
| 1126 | let mut code = CodeSection::new(); |
| 1127 | for (offset, (_, export)) in missing.iter().enumerate() { |
| 1128 | let offset = u32::try_from(offset).unwrap(); |
| 1129 | |
| 1130 | let Export { |
| 1131 | key: |
| 1132 | ExportKey { |
| 1133 | name, |
| 1134 | ty: Type::Function(ty), |
| 1135 | }, |
| 1136 | .. |
| 1137 | } = export |
| 1138 | else { |
| 1139 | unreachable!(); |
| 1140 | }; |
| 1141 | |
| 1142 | types.ty().function( |
| 1143 | ty.parameters.iter().copied().map(ValType::from), |
| 1144 | ty.results.iter().copied().map(ValType::from), |
| 1145 | ); |
| 1146 | functions.function(offset); |
| 1147 | let mut function = Function::new([]); |
| 1148 | function.instruction(&Ins::Unreachable); |
| 1149 | function.instruction(&Ins::End); |
| 1150 | code.function(&function); |
| 1151 | exports.export(name, ExportKind::Func, offset); |
| 1152 | } |
| 1153 | |
| 1154 | let mut module = Module::new(); |
| 1155 | |
| 1156 | module.section(&types); |
| 1157 | module.section(&functions); |
| 1158 | module.section(&exports); |
| 1159 | module.section(&code); |
| 1160 | module.section(&RawCustomSection( |
| 1161 | &crate::base_producers().raw_custom_section(), |
| 1162 | )); |
| 1163 | |
| 1164 | let module = module.finish(); |
| 1165 | wasmparser::validate(&module).unwrap(); |
| 1166 | |
| 1167 | module |
| 1168 | } |
| 1169 | |
| 1170 | /// Determine which of the specified libraries are transitively reachable at runtime, i.e. reachable from a |
| 1171 | /// component export or via `dlopen`. |
| 1172 | fn find_reachable<'a>( |
| 1173 | metadata: &'a [Metadata<'a>], |
| 1174 | dependencies: &IndexMap<usize, IndexSet<usize>>, |
| 1175 | ) -> IndexSet<&'a str> { |
| 1176 | let reachable = metadata |
| 1177 | .iter() |
| 1178 | .enumerate() |
| 1179 | .filter_map(|(index, metadata)| { |
| 1180 | if metadata.has_component_exports || metadata.dl_openable || metadata.has_wasi_start { |
| 1181 | Some(index) |
| 1182 | } else { |
| 1183 | None |
| 1184 | } |
| 1185 | }) |
| 1186 | .collect_unique::<IndexSet<_>>(); |
| 1187 | |
| 1188 | let empty = &IndexSet::new(); |
| 1189 | |
| 1190 | reachable |
| 1191 | .iter() |
| 1192 | .chain( |
| 1193 | reachable |
| 1194 | .iter() |
| 1195 | .flat_map(|index| dependencies.get(index).unwrap_or(empty)), |
| 1196 | ) |
| 1197 | .map(|&index| metadata[index].name) |
| 1198 | .collect() |
| 1199 | } |
| 1200 | |
| 1201 | /// Builder type for composing dynamic library modules into a component |
| 1202 | #[derive (Default)] |
| 1203 | pub struct Linker { |
| 1204 | /// The `(name, module, dl_openable)` triple representing the libraries to be composed |
| 1205 | /// |
| 1206 | /// The order of this list determines priority in cases where more than one library exports the same symbol. |
| 1207 | libraries: Vec<(String, Vec<u8>, bool)>, |
| 1208 | |
| 1209 | /// The set of adapters to use when generating the component |
| 1210 | adapters: Vec<(String, Vec<u8>)>, |
| 1211 | |
| 1212 | /// Whether to validate the resulting component prior to returning it |
| 1213 | validate: bool, |
| 1214 | |
| 1215 | /// Whether to generate trapping stubs for any unresolved imports |
| 1216 | stub_missing_functions: bool, |
| 1217 | |
| 1218 | /// Whether to use a built-in implementation of `dlopen`/`dlsym`. |
| 1219 | use_built_in_libdl: bool, |
| 1220 | |
| 1221 | /// Size of stack (in bytes) to allocate in the synthesized main module |
| 1222 | /// |
| 1223 | /// If `None`, use `DEFAULT_STACK_SIZE_BYTES`. |
| 1224 | stack_size: Option<u32>, |
| 1225 | |
| 1226 | /// This affects how when to WIT worlds are merged together, for example |
| 1227 | /// from two different libraries, whether their imports are unified when the |
| 1228 | /// semver version ranges for interface allow it. |
| 1229 | merge_imports_based_on_semver: Option<bool>, |
| 1230 | } |
| 1231 | |
| 1232 | impl Linker { |
| 1233 | /// Add a dynamic library module to this linker. |
| 1234 | /// |
| 1235 | /// If `dl_openable` is true, all of the libraries exports will be added to the `dlopen`/`dlsym` lookup table |
| 1236 | /// for runtime resolution. |
| 1237 | pub fn library(mut self, name: &str, module: &[u8], dl_openable: bool) -> Result<Self> { |
| 1238 | self.libraries |
| 1239 | .push((name.to_owned(), module.to_vec(), dl_openable)); |
| 1240 | |
| 1241 | Ok(self) |
| 1242 | } |
| 1243 | |
| 1244 | /// Add an adapter to this linker. |
| 1245 | /// |
| 1246 | /// See [crate::encoding::ComponentEncoder::adapter] for details. |
| 1247 | pub fn adapter(mut self, name: &str, module: &[u8]) -> Result<Self> { |
| 1248 | self.adapters.push((name.to_owned(), module.to_vec())); |
| 1249 | |
| 1250 | Ok(self) |
| 1251 | } |
| 1252 | |
| 1253 | /// Specify whether to validate the resulting component prior to returning it |
| 1254 | pub fn validate(mut self, validate: bool) -> Self { |
| 1255 | self.validate = validate; |
| 1256 | self |
| 1257 | } |
| 1258 | |
| 1259 | /// Specify size of stack to allocate in the synthesized main module |
| 1260 | pub fn stack_size(mut self, stack_size: u32) -> Self { |
| 1261 | self.stack_size = Some(stack_size); |
| 1262 | self |
| 1263 | } |
| 1264 | |
| 1265 | /// Specify whether to generate trapping stubs for any unresolved imports |
| 1266 | pub fn stub_missing_functions(mut self, stub_missing_functions: bool) -> Self { |
| 1267 | self.stub_missing_functions = stub_missing_functions; |
| 1268 | self |
| 1269 | } |
| 1270 | |
| 1271 | /// Specify whether to use a built-in implementation of `dlopen`/`dlsym`. |
| 1272 | pub fn use_built_in_libdl(mut self, use_built_in_libdl: bool) -> Self { |
| 1273 | self.use_built_in_libdl = use_built_in_libdl; |
| 1274 | self |
| 1275 | } |
| 1276 | |
| 1277 | /// This affects how when to WIT worlds are merged together, for example |
| 1278 | /// from two different libraries, whether their imports are unified when the |
| 1279 | /// semver version ranges for interface allow it. |
| 1280 | /// |
| 1281 | /// This is enabled by default. |
| 1282 | pub fn merge_imports_based_on_semver(mut self, merge: bool) -> Self { |
| 1283 | self.merge_imports_based_on_semver = Some(merge); |
| 1284 | self |
| 1285 | } |
| 1286 | |
| 1287 | /// Encode the component and return the bytes |
| 1288 | pub fn encode(mut self) -> Result<Vec<u8>> { |
| 1289 | if self.use_built_in_libdl { |
| 1290 | self.use_built_in_libdl = false; |
| 1291 | self = self.library("libdl.so" , include_bytes!("../libdl.so" ), false)?; |
| 1292 | } |
| 1293 | |
| 1294 | let adapter_names = self |
| 1295 | .adapters |
| 1296 | .iter() |
| 1297 | .map(|(name, _)| name.as_str()) |
| 1298 | .collect_unique::<HashSet<_>>(); |
| 1299 | |
| 1300 | if adapter_names.len() != self.adapters.len() { |
| 1301 | bail!("duplicate adapter name" ); |
| 1302 | } |
| 1303 | |
| 1304 | let metadata = self |
| 1305 | .libraries |
| 1306 | .iter() |
| 1307 | .map(|(name, module, dl_openable)| { |
| 1308 | Metadata::try_new(name, *dl_openable, module, &adapter_names) |
| 1309 | .with_context(|| format!("failed to extract linking metadata from {name}" )) |
| 1310 | }) |
| 1311 | .collect::<Result<Vec<_>>>()?; |
| 1312 | |
| 1313 | { |
| 1314 | let names = self |
| 1315 | .libraries |
| 1316 | .iter() |
| 1317 | .map(|(name, ..)| name.as_str()) |
| 1318 | .collect_unique::<HashSet<_>>(); |
| 1319 | |
| 1320 | let missing = metadata |
| 1321 | .iter() |
| 1322 | .filter_map(|metadata| { |
| 1323 | let missing = metadata |
| 1324 | .needed_libs |
| 1325 | .iter() |
| 1326 | .copied() |
| 1327 | .filter(|name| !names.contains(*name)) |
| 1328 | .collect::<Vec<_>>(); |
| 1329 | |
| 1330 | if missing.is_empty() { |
| 1331 | None |
| 1332 | } else { |
| 1333 | Some((metadata.name, missing)) |
| 1334 | } |
| 1335 | }) |
| 1336 | .collect::<Vec<_>>(); |
| 1337 | |
| 1338 | if !missing.is_empty() { |
| 1339 | bail!( |
| 1340 | "missing libraries: \n{}" , |
| 1341 | missing |
| 1342 | .iter() |
| 1343 | .map(|(needed_by, missing)| format!( |
| 1344 | " \t{needed_by} needs {}" , |
| 1345 | missing.join(", " ) |
| 1346 | )) |
| 1347 | .collect::<Vec<_>>() |
| 1348 | .join(" \n" ) |
| 1349 | ); |
| 1350 | } |
| 1351 | } |
| 1352 | |
| 1353 | let exporters = resolve_exporters(&metadata)?; |
| 1354 | |
| 1355 | let cabi_realloc_exporter = exporters |
| 1356 | .get(&ExportKey { |
| 1357 | name: "cabi_realloc" , |
| 1358 | ty: Type::Function(FunctionType { |
| 1359 | parameters: vec![ValueType::I32; 4], |
| 1360 | results: vec![ValueType::I32], |
| 1361 | }), |
| 1362 | }) |
| 1363 | .map(|exporters| exporters.first().unwrap().0); |
| 1364 | |
| 1365 | let (exporters, missing, _) = resolve_symbols(&metadata, &exporters); |
| 1366 | |
| 1367 | if !missing.is_empty() { |
| 1368 | if missing |
| 1369 | .iter() |
| 1370 | .all(|(_, export)| matches!(&export.key.ty, Type::Function(_))) |
| 1371 | && (self.stub_missing_functions |
| 1372 | || missing |
| 1373 | .iter() |
| 1374 | .all(|(_, export)| export.flags.contains(SymbolFlags::BINDING_WEAK))) |
| 1375 | { |
| 1376 | self.stub_missing_functions = false; |
| 1377 | self.libraries.push(( |
| 1378 | "wit-component:stubs" .into(), |
| 1379 | make_stubs_module(&missing), |
| 1380 | false, |
| 1381 | )); |
| 1382 | return self.encode(); |
| 1383 | } else { |
| 1384 | bail!( |
| 1385 | "unresolved symbol(s): \n{}" , |
| 1386 | missing |
| 1387 | .iter() |
| 1388 | .filter(|(_, export)| !export.flags.contains(SymbolFlags::BINDING_WEAK)) |
| 1389 | .map(|(importer, export)| { format!(" \t{importer} needs {}" , export.key) }) |
| 1390 | .collect::<Vec<_>>() |
| 1391 | .join(" \n" ) |
| 1392 | ); |
| 1393 | } |
| 1394 | } |
| 1395 | |
| 1396 | let dependencies = find_dependencies(&metadata, &exporters)?; |
| 1397 | |
| 1398 | { |
| 1399 | let reachable = find_reachable(&metadata, &dependencies); |
| 1400 | let unreachable = self |
| 1401 | .libraries |
| 1402 | .iter() |
| 1403 | .filter_map(|(name, ..)| (!reachable.contains(name.as_str())).then(|| name.clone())) |
| 1404 | .collect_unique::<HashSet<_>>(); |
| 1405 | |
| 1406 | if !unreachable.is_empty() { |
| 1407 | self.libraries |
| 1408 | .retain(|(name, ..)| !unreachable.contains(name)); |
| 1409 | return self.encode(); |
| 1410 | } |
| 1411 | } |
| 1412 | |
| 1413 | let topo_sorted = topo_sort(metadata.len(), &dependencies)?; |
| 1414 | |
| 1415 | let EnvFunctionExports { |
| 1416 | exports: env_function_exports, |
| 1417 | reexport_cabi_realloc, |
| 1418 | } = env_function_exports(&metadata, &exporters, &topo_sorted)?; |
| 1419 | |
| 1420 | let (env_module, dl_openables, table_base) = make_env_module( |
| 1421 | &metadata, |
| 1422 | &env_function_exports, |
| 1423 | if reexport_cabi_realloc { |
| 1424 | // If "env" module already reexports "cabi_realloc", we don't need to |
| 1425 | // reexport it again. |
| 1426 | None |
| 1427 | } else { |
| 1428 | cabi_realloc_exporter |
| 1429 | }, |
| 1430 | self.stack_size.unwrap_or(DEFAULT_STACK_SIZE_BYTES), |
| 1431 | ); |
| 1432 | |
| 1433 | let mut encoder = ComponentEncoder::default().validate(self.validate); |
| 1434 | if let Some(merge) = self.merge_imports_based_on_semver { |
| 1435 | encoder = encoder.merge_imports_based_on_semver(merge); |
| 1436 | }; |
| 1437 | encoder = encoder.module(&env_module)?; |
| 1438 | |
| 1439 | for (name, module) in &self.adapters { |
| 1440 | encoder = encoder.adapter(name, module)?; |
| 1441 | } |
| 1442 | |
| 1443 | let default_env_items = [ |
| 1444 | Item { |
| 1445 | alias: "memory" .into(), |
| 1446 | kind: ExportKind::Memory, |
| 1447 | which: MainOrAdapter::Main, |
| 1448 | name: "memory" .into(), |
| 1449 | }, |
| 1450 | Item { |
| 1451 | alias: "__indirect_function_table" .into(), |
| 1452 | kind: ExportKind::Table, |
| 1453 | which: MainOrAdapter::Main, |
| 1454 | name: "__indirect_function_table" .into(), |
| 1455 | }, |
| 1456 | Item { |
| 1457 | alias: "__stack_pointer" .into(), |
| 1458 | kind: ExportKind::Global, |
| 1459 | which: MainOrAdapter::Main, |
| 1460 | name: "__stack_pointer" .into(), |
| 1461 | }, |
| 1462 | ]; |
| 1463 | |
| 1464 | let mut seen = HashSet::new(); |
| 1465 | for index in topo_sorted { |
| 1466 | let (name, module, _) = &self.libraries[index]; |
| 1467 | let metadata = &metadata[index]; |
| 1468 | |
| 1469 | let env_items = default_env_items |
| 1470 | .iter() |
| 1471 | .cloned() |
| 1472 | .chain([ |
| 1473 | Item { |
| 1474 | alias: "__memory_base" .into(), |
| 1475 | kind: ExportKind::Global, |
| 1476 | which: MainOrAdapter::Main, |
| 1477 | name: format!(" {name}:memory_base" ), |
| 1478 | }, |
| 1479 | Item { |
| 1480 | alias: "__table_base" .into(), |
| 1481 | kind: ExportKind::Global, |
| 1482 | which: MainOrAdapter::Main, |
| 1483 | name: format!(" {name}:table_base" ), |
| 1484 | }, |
| 1485 | ]) |
| 1486 | .chain(metadata.env_imports.iter().map(|(name, (ty, _))| { |
| 1487 | let (exporter, _) = find_function_exporter(name, ty, &exporters).unwrap(); |
| 1488 | |
| 1489 | Item { |
| 1490 | alias: (*name).into(), |
| 1491 | kind: ExportKind::Func, |
| 1492 | which: if seen.contains(exporter) { |
| 1493 | MainOrAdapter::Adapter(exporter.to_owned()) |
| 1494 | } else { |
| 1495 | MainOrAdapter::Main |
| 1496 | }, |
| 1497 | name: (*name).into(), |
| 1498 | } |
| 1499 | })) |
| 1500 | .chain(if metadata.is_asyncified { |
| 1501 | vec![ |
| 1502 | Item { |
| 1503 | alias: "__asyncify_state" .into(), |
| 1504 | kind: ExportKind::Global, |
| 1505 | which: MainOrAdapter::Main, |
| 1506 | name: "__asyncify_state" .into(), |
| 1507 | }, |
| 1508 | Item { |
| 1509 | alias: "__asyncify_data" .into(), |
| 1510 | kind: ExportKind::Global, |
| 1511 | which: MainOrAdapter::Main, |
| 1512 | name: "__asyncify_data" .into(), |
| 1513 | }, |
| 1514 | ] |
| 1515 | } else { |
| 1516 | vec![] |
| 1517 | }) |
| 1518 | .collect(); |
| 1519 | |
| 1520 | let global_item = |address_name: &str| Item { |
| 1521 | alias: address_name.into(), |
| 1522 | kind: ExportKind::Global, |
| 1523 | which: MainOrAdapter::Main, |
| 1524 | name: format!(" {name}: {address_name}" ), |
| 1525 | }; |
| 1526 | |
| 1527 | let mem_items = metadata |
| 1528 | .memory_address_imports |
| 1529 | .iter() |
| 1530 | .copied() |
| 1531 | .map(global_item) |
| 1532 | .chain(["__heap_base" , "__heap_end" ].into_iter().map(|name| Item { |
| 1533 | alias: name.into(), |
| 1534 | kind: ExportKind::Global, |
| 1535 | which: MainOrAdapter::Main, |
| 1536 | name: name.into(), |
| 1537 | })) |
| 1538 | .collect(); |
| 1539 | |
| 1540 | let func_items = metadata |
| 1541 | .table_address_imports |
| 1542 | .iter() |
| 1543 | .copied() |
| 1544 | .map(global_item) |
| 1545 | .collect(); |
| 1546 | |
| 1547 | let mut import_items = BTreeMap::<_, Vec<_>>::new(); |
| 1548 | for import in &metadata.imports { |
| 1549 | import_items.entry(import.module).or_default().push(Item { |
| 1550 | alias: import.name.into(), |
| 1551 | kind: ExportKind::from(&import.ty), |
| 1552 | which: MainOrAdapter::Main, |
| 1553 | name: format!(" {}: {}" , import.module, import.name), |
| 1554 | }); |
| 1555 | } |
| 1556 | |
| 1557 | encoder = encoder.library( |
| 1558 | name, |
| 1559 | module, |
| 1560 | LibraryInfo { |
| 1561 | instantiate_after_shims: false, |
| 1562 | arguments: [ |
| 1563 | ("GOT.mem" .into(), Instance::Items(mem_items)), |
| 1564 | ("GOT.func" .into(), Instance::Items(func_items)), |
| 1565 | ("env" .into(), Instance::Items(env_items)), |
| 1566 | ] |
| 1567 | .into_iter() |
| 1568 | .chain( |
| 1569 | import_items |
| 1570 | .into_iter() |
| 1571 | .map(|(k, v)| (k.into(), Instance::Items(v))), |
| 1572 | ) |
| 1573 | .collect(), |
| 1574 | }, |
| 1575 | )?; |
| 1576 | |
| 1577 | seen.insert(name.as_str()); |
| 1578 | } |
| 1579 | |
| 1580 | encoder |
| 1581 | .library( |
| 1582 | "__init" , |
| 1583 | &make_init_module( |
| 1584 | &metadata, |
| 1585 | &exporters, |
| 1586 | &env_function_exports, |
| 1587 | dl_openables, |
| 1588 | table_base, |
| 1589 | )?, |
| 1590 | LibraryInfo { |
| 1591 | instantiate_after_shims: true, |
| 1592 | arguments: iter::once(( |
| 1593 | "env" .into(), |
| 1594 | Instance::MainOrAdapter(MainOrAdapter::Main), |
| 1595 | )) |
| 1596 | .chain(self.libraries.iter().map(|(name, ..)| { |
| 1597 | ( |
| 1598 | name.clone(), |
| 1599 | Instance::MainOrAdapter(MainOrAdapter::Adapter(name.clone())), |
| 1600 | ) |
| 1601 | })) |
| 1602 | .collect(), |
| 1603 | }, |
| 1604 | )? |
| 1605 | .encode() |
| 1606 | } |
| 1607 | } |
| 1608 | |