1use std::{
2 collections::{BTreeMap, BTreeSet},
3 fmt::{Debug, Display},
4};
5
6use anyhow::{anyhow, bail};
7use wamex_types::map_vec::MiniSet;
8
9use super::dep_graph::DepGraph;
10use crate::{
11 SplitPointExtractor,
12 analysis::{
13 self,
14 dep_graph::{DepMiniSet, DepSet, NamedGraph, find_reachable_deps},
15 symbols::SymbolKind,
16 },
17 index::{ExportId, IdMap, ImportId, InputFuncId, SymbolId},
18};
19
20#[derive(Default, Clone)]
23pub struct OutputModuleInfo {
24 pub defined_symbols: DepSet,
25 pub imports: DepMiniSet,
27 pub exports: DepMiniSet,
28 pub split_points: Vec<SplitPoint>,
30}
31
32#[derive(Debug, PartialEq, Eq, Clone)]
33pub struct SplitPoint {
34 module_name: String,
36 unique_id: String,
38 import: ImportId,
40 import_func: InputFuncId,
42 export: ExportId,
44 export_func: InputFuncId,
46}
47
48impl SplitPoint {
49 pub fn import_func(&self) -> InputFuncId {
50 self.import_func
51 }
52 pub fn export_func(&self) -> InputFuncId {
53 self.export_func
54 }
55}
56
57pub(crate) fn parser<'a>(name: &'a str, prefix: &str, postfix: &str) -> Option<(&'a str, &'a str)> {
58 if !name.starts_with(prefix) {
59 return None;
60 }
61 let name = &name[prefix.len()..];
62 let postfix_index = name.find(postfix)?;
63 let module_name = &name[..postfix_index];
64 let fn_name = &name[postfix_index + postfix.len()..];
65
66 Some((module_name, fn_name))
67}
68
69pub(crate) const SPLIT_IMPORT_POSTFIX: &str = "00_import_";
70pub(crate) const SPLIT_EXPORT_POSTFIX: &str = "00_export_";
71fn find_split_points_with_prefix(
72 info: &analysis::ModuleInfo,
73 prefix: &str,
74) -> anyhow::Result<Vec<SplitPoint>> {
75 macro_rules! process_imports_or_exports {
76 ($postfix: expr, $map:ident, $member:ident, $id_ty:ty) => {
77 let $map = info
78 .wasm
79 .$member
80 .iter()
81 .filter_map(|(id, item)| {
82 if let Some((module_name, unique_id)) = parser(&item.name, prefix, $postfix) {
83 Some(((module_name.into(), unique_id.into()), id))
84 } else {
85 None
86 }
87 })
88 .collect::<BTreeMap<(String, String), $id_ty>>();
89 };
90 }
91
92 process_imports_or_exports!(SPLIT_IMPORT_POSTFIX, import_map, imports, ImportId);
93 process_imports_or_exports!(SPLIT_EXPORT_POSTFIX, export_map, exports, ExportId);
94 let mut export_map = export_map;
95
96 let split_points = import_map
97 .into_iter()
98 .map(|(key, import_id)| -> anyhow::Result<SplitPoint> {
99 let export_id = export_map.remove(&key).ok_or_else(|| {
100 anyhow::anyhow!("No corresponding export for split import {key:?}")
101 })?;
102 let export = info.wasm.exports[export_id];
103 let wasmparser::Export {
104 kind: wasmparser::ExternalKind::Func,
105 index,
106 ..
107 } = export
108 else {
109 bail!("Expected exported function but received: {export:?}");
110 };
111 let &import_func = info
112 .import_info
113 .imported_func_map
114 .get(import_id)
115 .ok_or_else(|| {
116 anyhow!(
117 "Expected imported function but received: {:?}",
118 &info.wasm.imports[import_id]
119 )
120 })?;
121 Ok(SplitPoint {
122 module_name: key.0,
123 unique_id: key.1,
124 import: import_id,
125 import_func,
126 export: export_id,
127 export_func: InputFuncId::from_index(index),
128 })
129 })
130 .collect::<anyhow::Result<Vec<SplitPoint>>>()?;
131
132 if let Some((key, _)) = export_map.iter().next() {
133 log::error!(
134 "No corresponding import for split export {key:?} hash {key_hash:?}. Maybe split module is defined but not used.",
135 key_hash = key.1,
136 key = key.0
137 );
138 }
139
140 Ok(split_points)
141}
142
143pub fn find_split_points_legacy(info: &analysis::ModuleInfo) -> anyhow::Result<Vec<SplitPoint>> {
147 find_split_points_with_prefix(info, "__wasm_split_00")
148}
149pub(crate) const WAMEX_ENTRY_PREFIX: &str = "__wamex_00";
150fn find_split_points_wamex(info: &analysis::ModuleInfo) -> anyhow::Result<Vec<SplitPoint>> {
154 find_split_points_with_prefix(info, WAMEX_ENTRY_PREFIX)
155}
156
157pub fn find_split_points(
158 info: &analysis::ModuleInfo,
159 split_point_type: SplitPointExtractor,
160) -> anyhow::Result<Vec<SplitPoint>> {
161 match split_point_type {
162 SplitPointExtractor::Legacy => find_split_points_legacy(info),
163 SplitPointExtractor::Wamex => find_split_points_wamex(info),
164 }
165}
166
167#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
168pub enum ModuleIdentifier {
169 Main,
170 Split(String),
171}
172
173#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
174pub struct SharedModuleIdentifier(pub Vec<ModuleIdentifier>);
175
176impl Display for SharedModuleIdentifier {
177 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178 let mut components = self.0.iter();
179 let Some(first) = components.next() else {
180 return Ok(());
181 };
182
183 write!(f, "{}", first)?;
184 components.try_for_each(|n| write!(f, "_{}", n))?;
185 Ok(())
186 }
187}
188
189impl SharedModuleIdentifier {
190 pub fn contains(&self, module: &ModuleIdentifier) -> bool {
191 self.0.iter().any(|m| m == module)
192 }
193 pub fn remove(&mut self, module: &ModuleIdentifier) -> bool {
195 let original_len = self.0.len();
196 self.0.retain(|m| m != module);
197 original_len != self.0.len()
198 }
199 pub fn includes(&self, other: &SplitModuleIdentifier) -> bool {
200 match other {
201 SplitModuleIdentifier::Single(name) => self.contains(name),
202 SplitModuleIdentifier::Shared(shared) => {
203 shared.0.iter().all(|name| self.contains(name))
204 }
205 }
206 }
207}
208
209impl PartialEq<SplitModuleIdentifier> for SharedModuleIdentifier {
210 fn eq(&self, other: &SplitModuleIdentifier) -> bool {
211 match other {
212 SplitModuleIdentifier::Single(_) => false,
213 SplitModuleIdentifier::Shared(shared) => shared == self,
214 }
215 }
216}
217
218impl<'a> IntoIterator for &'a SharedModuleIdentifier {
219 type Item = &'a ModuleIdentifier;
220 type IntoIter = std::slice::Iter<'a, ModuleIdentifier>;
221
222 fn into_iter(self) -> Self::IntoIter {
223 self.0.iter()
224 }
225}
226
227#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
228pub enum SplitModuleIdentifier {
229 Single(ModuleIdentifier),
230 Shared(SharedModuleIdentifier),
231}
232
233impl Display for ModuleIdentifier {
234 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235 match self {
236 Self::Main => write!(f, "main"),
237 Self::Split(name) => write!(f, "{}", name),
238 }
239 }
240}
241
242impl Display for SplitModuleIdentifier {
243 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244 match self {
245 Self::Single(name) => Display::fmt(name, f),
246 Self::Shared(name) => Display::fmt(name, f),
247 }
248 }
249}
250
251impl SplitModuleIdentifier {
252 pub fn as_single(&self) -> Option<&ModuleIdentifier> {
253 match self {
254 Self::Single(name) => Some(name),
255 Self::Shared(_) => None,
256 }
257 }
258 pub fn as_shared(&self) -> Option<&SharedModuleIdentifier> {
259 match self {
260 Self::Single(_) => None,
261 Self::Shared(name) => Some(name),
262 }
263 }
264 pub fn is_shared(&self) -> bool {
265 matches!(self, Self::Shared(_))
266 }
267 pub fn is_main(&self) -> bool {
268 matches!(self, Self::Single(ModuleIdentifier::Main))
269 }
270
271 pub fn is_part_of(&self, other: &SharedModuleIdentifier) -> bool {
273 match self {
274 Self::Single(name) => other.contains(name),
275 Self::Shared(shared) => shared.0.iter().all(|name| other.contains(name)),
276 }
277 }
278
279 pub fn collect_deps(
294 &self,
295 shared_modules: &[SharedModuleIdentifier],
296 ) -> Vec<SharedModuleIdentifier> {
297 let mut result = Vec::new();
298 for shared_module in shared_modules {
299 if matches!(&self, SplitModuleIdentifier::Shared(our_module) if shared_module == our_module)
300 {
301 continue; }
303 if self.is_part_of(shared_module) {
304 result.push(shared_module.clone());
305 }
306 }
307 result
308 }
309}
310
311#[derive(Debug, Default)]
312pub struct SplitProgramInfo {
313 pub output_modules: Vec<(SplitModuleIdentifier, OutputModuleInfo)>,
314 pub symbol_output_module: IdMap<SymbolId, usize>,
315}
316
317impl SplitProgramInfo {
318 fn get_main_module_roots(
321 info: &analysis::ModuleInfo,
322 split_points: &[SplitPoint],
323 wbg_descriptors: &MiniSet<SymbolId>,
324 ) -> DepSet {
325 let mut roots: DepSet = DepSet::new();
326 if let Some(id) = info.wasm.code.section_payload.start_func {
327 roots.insert(info.symbols.get_function_symbol(id).unwrap());
328 }
329 for (_id, export) in info.wasm.exports.iter() {
330 let wasmparser::Export {
331 index,
332 kind: wasmparser::ExternalKind::Func,
333 ..
334 } = export
335 else {
336 continue;
337 };
338 roots.insert(
339 info.symbols
340 .get_function_symbol(InputFuncId::from_index(*index))
341 .unwrap(),
342 );
343 }
344
345 for (index, (_, import)) in info.wasm.imports.iter().enumerate() {
347 let wasmparser::Import {
348 ty: wasmparser::TypeRef::Func(_),
349 ..
350 } = import
351 else {
352 continue;
353 };
354
355 roots.insert(
356 info.symbols
357 .get_function_symbol(InputFuncId::from_index(index))
358 .unwrap(),
359 );
360 }
361
362 for descriptor in wbg_descriptors {
363 roots.insert(*descriptor);
364 }
365
366 for split_point in split_points.iter() {
367 roots.remove(
368 &info
369 .symbols
370 .get_function_symbol(split_point.export_func)
371 .unwrap(),
372 );
373 roots.remove(
374 &info
375 .symbols
376 .get_function_symbol(split_point.import_func)
377 .unwrap(),
378 );
379 }
380 roots
381 }
382
383 fn is_wasm_bindgen_cast(name: &str) -> bool {
384 name == "__wbindgen_describe_closure" || name == "__wbindgen_describe_cast" }
387
388 pub fn wbg_closures(module: &analysis::ModuleInfo, graph: &DepGraph) -> MiniSet<SymbolId> {
398 let wbg_fns: BTreeSet<_> = module
399 .symbols
400 .iter()
401 .filter(|(_id, sym)| Self::is_wasm_bindgen_cast(&sym.name))
402 .map(|(id, _name)| id)
403 .collect();
404
405 let mut wbg_descriptors = BTreeSet::new();
406 for id in wbg_fns.iter().cloned() {
407 wbg_descriptors.insert(id);
408 if let Some(parents) = graph.get_parents(id) {
409 for parent in parents {
410 debug_assert!(matches!(
411 module.symbols.get(*parent).unwrap().kind,
412 SymbolKind::Func { .. }
413 ));
414 wbg_descriptors.insert(*parent);
415 }
416 }
417 }
418 wbg_descriptors.into_iter().collect()
419 }
420
421 pub fn merge_split_points_by_name(
422 split_points: &[SplitPoint],
423 ) -> BTreeMap<String, Vec<&SplitPoint>> {
424 let mut result = BTreeMap::<String, Vec<&SplitPoint>>::new();
425
426 for split_point in split_points {
427 result
428 .entry(split_point.module_name.clone())
429 .or_default()
430 .push(split_point);
431 }
432 for results in result.values_mut() {
433 results.sort_by_key(|sp| sp.unique_id.clone());
434 }
435 result
436 }
437
438 pub fn compute_split_modules(
439 info: &analysis::ModuleInfo,
440 dep_graph: &DepGraph,
441 split_points: &[SplitPoint],
442 wbg_descriptors: &MiniSet<SymbolId>,
443 ) -> anyhow::Result<SplitProgramInfo> {
444 let split_points_by_module = Self::merge_split_points_by_name(split_points);
445
446 let main_roots = Self::get_main_module_roots(info, split_points, wbg_descriptors);
447
448 let main_deps = find_reachable_deps(dep_graph, &main_roots);
450
451 let mut named_modules = vec![NamedGraph::new(ModuleIdentifier::Main, main_deps.clone())];
452
453 for (module_name, entry_points) in split_points_by_module.iter() {
457 let mut roots = DepSet::new();
458 for entry_point in entry_points.iter() {
459 roots.insert(
460 info.symbols
461 .get_function_symbol(entry_point.export_func)
462 .unwrap(),
463 );
464 }
465
466 let split_functions = find_reachable_deps(dep_graph, &roots);
467
468 named_modules.push(NamedGraph::new(
469 ModuleIdentifier::Split(module_name.clone()),
470 split_functions,
471 ));
472 }
473
474 let shared_deps = NamedGraph::calculate_shared_modules(&mut named_modules, dep_graph);
476
477 let mut split_module_contents = BTreeMap::<SplitModuleIdentifier, OutputModuleInfo>::new();
478
479 split_module_contents.extend(named_modules.into_iter().map(|named_graph| {
480 let imports = named_graph.imports().clone();
481 let split_points = split_points_by_module
483 .get(&named_graph.module.to_string())
484 .iter()
485 .copied()
486 .flatten()
487 .copied()
488 .cloned()
489 .collect();
490 (
491 SplitModuleIdentifier::Single(named_graph.module),
492 OutputModuleInfo {
493 defined_symbols: named_graph.reachable,
494 imports,
495 split_points,
496 exports: DepMiniSet::new(),
498 },
499 )
500 }));
501
502 for shared in shared_deps {
503 split_module_contents.insert(
504 SplitModuleIdentifier::Shared(SharedModuleIdentifier(shared.module_names.clone())),
505 OutputModuleInfo {
506 defined_symbols: shared.shared_deps,
507 exports: shared.exports,
508 imports: shared.imports,
509 split_points: vec![],
510 },
511 );
512 }
513
514 let symbol_output_module = split_module_contents
515 .iter()
516 .enumerate()
517 .flat_map(|(output_index, (_, info))| {
518 info.defined_symbols
519 .iter()
520 .map(move |symbol| (*symbol, output_index))
521 })
522 .collect();
523
524 Ok(SplitProgramInfo {
525 output_modules: split_module_contents.into_iter().collect(),
526 symbol_output_module,
527 })
528 }
529}
530
531#[cfg(test)]
532mod tests {
533 use super::*;
534
535 #[test]
536 fn test_is_part_of() {
537 let shared = SharedModuleIdentifier(vec![
538 ModuleIdentifier::Main,
539 ModuleIdentifier::Split("a".into()),
540 ModuleIdentifier::Split("c".into()),
541 ]);
542 let single_main = SplitModuleIdentifier::Single(ModuleIdentifier::Main);
543 let single_a = SplitModuleIdentifier::Single(ModuleIdentifier::Split("a".into()));
544 let single_b = SplitModuleIdentifier::Single(ModuleIdentifier::Split("b".into()));
545 let shared_ab = SplitModuleIdentifier::Shared(SharedModuleIdentifier(vec![
546 ModuleIdentifier::Split("a".into()),
547 ModuleIdentifier::Split("b".into()),
548 ]));
549 let single_c = SplitModuleIdentifier::Single(ModuleIdentifier::Split("c".into()));
550 let shared_ac = SplitModuleIdentifier::Shared(SharedModuleIdentifier(vec![
551 ModuleIdentifier::Split("a".into()),
552 ModuleIdentifier::Split("c".into()),
553 ]));
554
555 assert!(single_main.is_part_of(&shared));
556 assert!(single_a.is_part_of(&shared));
557 assert!(single_c.is_part_of(&shared));
558 assert!(shared_ac.is_part_of(&shared));
559 assert!(!single_b.is_part_of(&shared));
560 assert!(!shared_ab.is_part_of(&shared));
561 }
562
563 }