1use anyhow::{Context, Result};
2use itertools::Itertools;
3use rayon::prelude::{IntoParallelIterator, ParallelIterator};
4use std::{
5 collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque},
6 hash::Hash,
7 ops::Range,
8 sync::{Arc, RwLock},
9};
10use walrus::{
11 ConstExpr, DataKind, ElementItems, ElementKind, ExportId, ExportItem, FunctionBuilder,
12 FunctionId, FunctionKind, GlobalKind, ImportId, ImportKind, Module, ModuleConfig, RefType,
13 TableId, TypeId,
14 ir::{self, Visitor, dfs_in_order},
15};
16use wasmparser::{
17 BinaryReader, Linking, LinkingSectionReader, Payload, RelocSectionReader, RelocationEntry,
18 SymbolInfo,
19};
20
21pub const MAKE_LOAD_JS: &str = include_str!("./__wasm_split.js");
22
23pub struct Splitter<'a> {
28 source_module: Module,
30
31 original: &'a [u8],
34 bindgened: &'a [u8],
35
36 fns_to_ids: HashMap<FunctionId, usize>,
42 _ids_to_fns: Vec<FunctionId>,
43
44 shared_symbols: BTreeSet<Node>,
45 split_points: Vec<SplitPoint>,
46 chunks: Vec<HashSet<Node>>,
47 data_symbols: BTreeMap<usize, DataSymbol>,
48 main_graph: HashSet<Node>,
49 call_graph: HashMap<Node, HashSet<Node>>,
50 parent_graph: HashMap<Node, HashSet<Node>>,
51}
52
53pub struct OutputModules {
55 pub main: SplitModule,
57
58 pub modules: Vec<SplitModule>,
60
61 pub chunks: Vec<SplitModule>,
63}
64
65pub struct SplitModule {
69 pub module_name: String,
70 pub hash_id: Option<String>,
71 pub component_name: Option<String>,
72 pub bytes: Vec<u8>,
73 pub relies_on_chunks: HashSet<usize>,
74}
75
76impl<'a> Splitter<'a> {
77 pub fn new(original: &'a [u8], bindgened: &'a [u8]) -> Result<Self> {
85 let (module, ids, fns_to_ids) = parse_module_with_ids(bindgened)?;
86
87 let split_points = accumulate_split_points(&module);
88
89 let raw_data = parse_bytes_to_data_segment(bindgened)?;
92
93 let mut module = Self {
94 source_module: module,
95 original,
96 bindgened,
97 split_points,
98 data_symbols: raw_data.data_symbols,
99 _ids_to_fns: ids,
100 fns_to_ids,
101 main_graph: Default::default(),
102 chunks: Default::default(),
103 call_graph: Default::default(),
104 parent_graph: Default::default(),
105 shared_symbols: Default::default(),
106 };
107
108 module.build_call_graph()?;
109 module.build_split_chunks();
110
111 Ok(module)
112 }
113
114 pub fn emit(self) -> Result<OutputModules> {
123 tracing::info!("Emitting split modules.");
124
125 let chunks = (0..self.chunks.len())
126 .into_par_iter()
127 .map(|idx| self.emit_split_chunk(idx))
128 .collect::<Result<Vec<SplitModule>>>()?;
129
130 let modules = (0..self.split_points.len())
131 .into_par_iter()
132 .map(|idx| self.emit_split_module(idx))
133 .collect::<Result<Vec<SplitModule>>>()?;
134
135 let main = self.emit_main_module()?;
137
138 Ok(OutputModules {
139 modules,
140 chunks,
141 main,
142 })
143 }
144
145 fn emit_main_module(mut self) -> Result<SplitModule> {
158 tracing::info!("Emitting main bundle split module");
159
160 let unused_symbols = self.unused_main_symbols();
162
163 let mut out = std::mem::take(&mut self.source_module);
165
166 self.replace_segments_with_holes(&mut out, &unused_symbols);
169
170 self.prune_main_symbols(&mut out, &unused_symbols)?;
172
173 self.create_ifunc_table(&mut out);
175
176 self.re_export_items(&mut out);
178
179 self.remove_custom_sections(&mut out);
181
182 walrus::passes::gc::run(&mut out);
184
185 Ok(SplitModule {
186 module_name: "main".to_string(),
187 component_name: None,
188 bytes: out.emit_wasm(),
189 relies_on_chunks: Default::default(),
190 hash_id: None,
191 })
192 }
193
194 fn emit_split_module(&self, split_idx: usize) -> Result<SplitModule> {
196 let split = self.split_points[split_idx].clone();
197
198 let mut unique_symbols = split
200 .reachable_graph
201 .difference(&self.main_graph)
202 .cloned()
203 .collect::<HashSet<_>>();
204
205 let mut symbols_to_import: HashSet<_> = split
207 .reachable_graph
208 .intersection(&self.main_graph)
209 .cloned()
210 .collect();
211
212 let symbols_to_delete: HashSet<_> = self
214 .main_graph
215 .difference(&split.reachable_graph)
216 .cloned()
217 .collect();
218
219 let mut relies_on_chunks = HashSet::new();
221 for (idx, chunk) in self.chunks.iter().enumerate() {
222 let nodes_to_extract = unique_symbols
223 .intersection(chunk)
224 .cloned()
225 .collect::<Vec<_>>();
226 for node in nodes_to_extract {
227 if !self.main_graph.contains(&node) {
228 unique_symbols.remove(&node);
229 symbols_to_import.insert(node);
230 relies_on_chunks.insert(idx);
231 }
232 }
233 }
234
235 tracing::info!(
236 "Emitting module {}/{} {}: {:?}",
237 split_idx,
238 self.split_points.len(),
239 split.module_name,
240 relies_on_chunks
241 );
242
243 let (mut out, ids_to_fns, _fns_to_ids) = parse_module_with_ids(self.bindgened)?;
244
245 let shared_funcs = self
247 .shared_symbols
248 .iter()
249 .map(|f| self.remap_id(&ids_to_fns, f))
250 .collect::<Vec<_>>();
251
252 let unique_symbols = self.remap_ids(&unique_symbols, &ids_to_fns);
253 let symbols_to_delete = self.remap_ids(&symbols_to_delete, &ids_to_fns);
254 let symbols_to_import = self.remap_ids(&symbols_to_import, &ids_to_fns);
255 let split_export_func = ids_to_fns[self.fns_to_ids[&split.export_func]];
256
257 self.prune_split_module(&mut out);
260
261 self.clear_data_segments(&mut out, &unique_symbols);
263
264 self.create_ifunc_initializers(&mut out, &unique_symbols);
266
267 self.add_split_imports(
269 &mut out,
270 split.index,
271 split_export_func,
272 split.export_name,
273 &symbols_to_import,
274 &shared_funcs,
275 );
276
277 self.delete_main_funcs_from_split(&mut out, &symbols_to_delete);
279
280 self.remove_custom_sections(&mut out);
282
283 walrus::passes::gc::run(&mut out);
286
287 Ok(SplitModule {
288 bytes: out.emit_wasm(),
289 module_name: split.module_name.clone(),
290 component_name: Some(split.component_name.clone()),
291 relies_on_chunks,
292 hash_id: Some(split.hash_name.clone()),
293 })
294 }
295
296 fn emit_split_chunk(&self, idx: usize) -> Result<SplitModule> {
298 tracing::info!("emitting chunk {}", idx);
299
300 let unique_symbols = &self.chunks[idx];
301
302 let symbols_to_import: HashSet<_> = unique_symbols
304 .intersection(&self.main_graph)
305 .cloned()
306 .collect();
307
308 let symbols_to_delete: HashSet<_> = self
310 .main_graph
311 .difference(unique_symbols)
312 .cloned()
313 .collect();
314
315 let (mut out, ids_to_fns, _fns_to_ids) = parse_module_with_ids(self.bindgened)?;
317
318 let shared_funcs = self
320 .shared_symbols
321 .iter()
322 .map(|f| self.remap_id(&ids_to_fns, f))
323 .collect::<Vec<_>>();
324
325 let unique_symbols = self.remap_ids(unique_symbols, &ids_to_fns);
326 let symbols_to_import = self.remap_ids(&symbols_to_import, &ids_to_fns);
327 let symbols_to_delete = self.remap_ids(&symbols_to_delete, &ids_to_fns);
328
329 self.prune_split_module(&mut out);
330
331 self.clear_data_segments(&mut out, &unique_symbols);
333
334 self.create_ifunc_initializers(&mut out, &unique_symbols);
336
337 let ifunc_table_id = self.load_funcref_table(&mut out);
339 let segment_start = self
340 .expand_ifunc_table_max(
341 &mut out,
342 ifunc_table_id,
343 self.split_points.len() + shared_funcs.len(),
344 )
345 .unwrap();
346
347 self.convert_shared_to_imports(&mut out, segment_start, &shared_funcs, &symbols_to_import);
348
349 self.delete_main_funcs_from_split(&mut out, &symbols_to_delete);
351
352 self.remove_custom_sections(&mut out);
354
355 walrus::passes::gc::run(&mut out);
357
358 Ok(SplitModule {
359 bytes: out.emit_wasm(),
360 module_name: "split".to_string(),
361 component_name: None,
362 relies_on_chunks: Default::default(),
363 hash_id: None,
364 })
365 }
366
367 fn convert_shared_to_imports(
369 &self,
370 out: &mut Module,
371 segment_start: usize,
372 ifuncs: &Vec<Node>,
373 symbols_to_import: &HashSet<Node>,
374 ) {
375 let ifunc_table_id = self.load_funcref_table(out);
376
377 let mut idx = self.split_points.len();
378 for node in ifuncs {
379 if let Node::Function(ifunc) = node {
380 if symbols_to_import.contains(node) {
381 let ty_id = out.funcs.get(*ifunc).ty();
382 let stub = (idx + segment_start) as _;
383 out.funcs.get_mut(*ifunc).kind =
384 self.make_stub_funcs(out, ifunc_table_id, ty_id, stub);
385 }
386
387 idx += 1;
388 }
389 }
390 }
391
392 fn create_ifunc_table(&self, out: &mut Module) {
399 let ifunc_table = self.load_funcref_table(out);
400 let dummy_func = self.make_dummy_func(out);
401
402 out.exports.add("__indirect_function_table", ifunc_table);
403
404 let segment_start = self
406 .expand_ifunc_table_max(
407 out,
408 ifunc_table,
409 self.split_points.len() + self.shared_symbols.len(),
410 )
411 .expect("failed to expand ifunc table");
412
413 let mut ifuncs = vec![];
418
419 for idx in 0..self.split_points.len() {
421 let import_func = self.split_points[idx].import_func;
423 let import_id = self.split_points[idx].import_id;
424 let ty_id = out.funcs.get(import_func).ty();
425 let stub_idx = segment_start + ifuncs.len();
426
427 out.funcs.get_mut(import_func).kind =
429 self.make_stub_funcs(out, ifunc_table, ty_id, stub_idx as _);
430
431 out.imports.delete(import_id);
433
434 ifuncs.push(dummy_func);
437 }
438
439 let mut _idx = 0;
442 for func in self.shared_symbols.iter() {
443 if let Node::Function(id) = func {
444 ifuncs.push(*id);
445 _idx += 1;
446 }
447 }
448
449 out.tables
451 .get_mut(ifunc_table)
452 .elem_segments
453 .insert(out.elements.add(
454 ElementKind::Active {
455 table: ifunc_table,
456 offset: ConstExpr::Value(ir::Value::I32(segment_start as _)),
457 },
458 ElementItems::Functions(ifuncs),
459 ));
460 }
461
462 fn re_export_items(&self, out: &mut Module) {
464 for (idx, memory) in out.memories.iter().enumerate() {
466 let name = memory
467 .name
468 .clone()
469 .unwrap_or_else(|| format!("__memory_{}", idx));
470 out.exports.add(&name, memory.id());
471 }
472
473 for (idx, global) in out.globals.iter().enumerate() {
475 let global_name = format!("__global__{idx}");
476 out.exports.add(&global_name, global.id());
477 }
478
479 for (idx, table) in out.tables.iter().enumerate() {
481 if table.element_ty != RefType::FUNCREF {
482 let table_name = format!("__imported_table_{}", idx);
483 out.exports.add(&table_name, table.id());
484 }
485 }
486 }
487
488 fn prune_main_symbols(&self, out: &mut Module, unused_symbols: &HashSet<Node>) -> Result<()> {
489 for split in self.split_points.iter() {
491 out.exports.delete(split.export_id);
493 }
494
495 for symbol in unused_symbols.iter().cloned() {
497 match symbol {
498 Node::Function(id) => {
500 out.funcs.delete(id);
501 }
502
503 Node::DataSymbol(id) => {
505 let symbol = self
506 .data_symbols
507 .get(&id)
508 .context("Failed to find data symbol")?;
509
510 if symbol.which_data_segment == 0 {
517 let data_id = out.data.iter().nth(symbol.which_data_segment).unwrap().id();
518 let data = out.data.get_mut(data_id);
519 for i in symbol.segment_offset..symbol.segment_offset + symbol.symbol_size {
520 data.value[i] = 0;
521 }
522 }
523 }
524 }
525 }
526
527 Ok(())
528 }
529
530 fn replace_segments_with_holes(&self, out: &mut Module, unused_symbols: &HashSet<Node>) {
533 let dummy_func = self.make_dummy_func(out);
534 for element in out.elements.iter_mut() {
535 match &mut element.items {
536 ElementItems::Functions(vec) => {
537 for item in vec.iter_mut() {
538 if unused_symbols.contains(&Node::Function(*item)) {
539 *item = dummy_func;
540 }
541 }
542 }
543 ElementItems::Expressions(_ref_type, const_exprs) => {
544 for item in const_exprs.iter_mut() {
545 if let &mut ConstExpr::RefFunc(id) = item {
546 if unused_symbols.contains(&Node::Function(id)) {
547 *item = ConstExpr::RefFunc(dummy_func);
548 }
549 }
550 }
551 }
552 }
553 }
554 }
555
556 fn create_ifunc_initializers(&self, out: &mut Module, unique_symbols: &HashSet<Node>) {
558 let ifunc_table = self.load_funcref_table(out);
559
560 let mut initializers = HashMap::new();
561 for segment in out.elements.iter_mut() {
562 let ElementKind::Active { offset, .. } = &mut segment.kind else {
563 continue;
564 };
565
566 let ConstExpr::Value(ir::Value::I32(offset)) = offset else {
567 continue;
568 };
569
570 match &segment.items {
571 ElementItems::Functions(vec) => {
572 for (idx, id) in vec.iter().enumerate() {
573 if unique_symbols.contains(&Node::Function(*id)) {
574 initializers
575 .insert(*offset + idx as i32, ElementItems::Functions(vec![*id]));
576 }
577 }
578 }
579
580 ElementItems::Expressions(ref_type, const_exprs) => {
581 for (idx, expr) in const_exprs.iter().enumerate() {
582 if let ConstExpr::RefFunc(id) = expr {
583 if unique_symbols.contains(&Node::Function(*id)) {
584 initializers.insert(
585 *offset + idx as i32,
586 ElementItems::Expressions(
587 *ref_type,
588 vec![ConstExpr::RefFunc(*id)],
589 ),
590 );
591 }
592 }
593 }
594 }
595 }
596 }
597
598 for table in out.tables.iter_mut() {
600 table.elem_segments.clear();
601 }
602
603 let segments_to_delete: Vec<_> = out.elements.iter().map(|e| e.id()).collect();
605 for id in segments_to_delete {
606 out.elements.delete(id);
607 }
608
609 let ifunc_table_ = out.tables.get_mut(ifunc_table);
611 for (offset, items) in initializers {
612 let kind = ElementKind::Active {
613 table: ifunc_table,
614 offset: ConstExpr::Value(ir::Value::I32(offset)),
615 };
616
617 ifunc_table_
618 .elem_segments
619 .insert(out.elements.add(kind, items));
620 }
621 }
622
623 fn add_split_imports(
624 &self,
625 out: &mut Module,
626 split_idx: usize,
627 split_export_func: FunctionId,
628 split_export_name: String,
629 symbols_to_import: &HashSet<Node>,
630 ifuncs: &Vec<Node>,
631 ) {
632 let ifunc_table_id = self.load_funcref_table(out);
633 let segment_start = self
634 .expand_ifunc_table_max(out, ifunc_table_id, self.split_points.len() + ifuncs.len())
635 .unwrap();
636
637 out.exports.add(&split_export_name, split_export_func);
639
640 out.tables
642 .get_mut(ifunc_table_id)
643 .elem_segments
644 .insert(out.elements.add(
645 ElementKind::Active {
646 table: ifunc_table_id,
647 offset: ConstExpr::Value(ir::Value::I32((segment_start + split_idx) as i32)),
648 },
649 ElementItems::Functions(vec![split_export_func]),
650 ));
651
652 self.convert_shared_to_imports(out, segment_start, ifuncs, symbols_to_import);
653 }
654
655 fn delete_main_funcs_from_split(&self, out: &mut Module, symbols_to_delete: &HashSet<Node>) {
656 for node in symbols_to_delete {
657 if let Node::Function(id) = *node {
658 out.funcs.delete(id);
660 }
662 }
663 }
664
665 fn prune_split_module(&self, out: &mut Module) {
667 if let Some(start) = out.start.take() {
669 if let Some(export) = out.exports.get_exported_func(start) {
670 out.exports.delete(export.id());
671 }
672 }
673
674 for table in out.tables.iter_mut() {
676 table.elem_segments.clear();
677 }
678
679 let all_imports: HashSet<_> = out.imports.iter().map(|i| i.id()).collect();
681 for import_id in all_imports {
682 out.imports.delete(import_id);
683 }
684
685 let all_memories: Vec<_> = out.memories.iter().map(|m| m.id()).collect();
687 for memory_id in all_memories {
688 out.memories.get_mut(memory_id).data_segments.clear();
689 }
690
691 let exports = out.exports.iter().map(|e| e.id()).collect::<Vec<_>>();
693 for export_id in exports {
694 out.exports.delete(export_id);
695 }
696
697 for (idx, table) in out.tables.iter_mut().enumerate() {
700 let name = table.name.clone().unwrap_or_else(|| {
701 if table.element_ty == RefType::FUNCREF {
702 "__indirect_function_table".to_string()
703 } else {
704 format!("__imported_table_{}", idx)
705 }
706 });
707 let import = out.imports.add("__wasm_split", &name, table.id());
708 table.import = Some(import);
709 }
710
711 for (idx, memory) in out.memories.iter_mut().enumerate() {
714 let name = memory
715 .name
716 .clone()
717 .unwrap_or_else(|| format!("__memory_{}", idx));
718 let import = out.imports.add("__wasm_split", &name, memory.id());
719 memory.import = Some(import);
720 }
721
722 let global_ids: Vec<_> = out.globals.iter().map(|t| t.id()).collect();
725 for (idx, global_id) in global_ids.into_iter().enumerate() {
726 let global = out.globals.get_mut(global_id);
727 let global_name = format!("__global__{idx}");
728 let import = out.imports.add("__wasm_split", &global_name, global.id());
729 global.kind = GlobalKind::Import(import);
730 }
731 }
732
733 fn make_dummy_func(&self, out: &mut Module) -> FunctionId {
734 let mut b = FunctionBuilder::new(&mut out.types, &[], &[]);
735 b.name("dummy".into()).func_body().unreachable();
736 b.finish(vec![], &mut out.funcs)
737 }
738
739 fn clear_data_segments(&self, out: &mut Module, unique_symbols: &HashSet<Node>) {
740 let data_ids: Vec<_> = out.data.iter().map(|t| t.id()).collect();
742 for (idx, data_id) in data_ids.into_iter().enumerate() {
743 let data = out.data.get_mut(data_id);
744
745 let contents = data.value.split_off(0);
747
748 if idx != 0 {
750 continue;
751 }
752
753 let DataKind::Active { memory, offset } = &data.kind else {
754 continue;
755 };
756 let memory = *memory;
757
758 let ConstExpr::Value(ir::Value::I32(data_offset)) = *offset else {
759 continue;
760 };
761
762 for unique in unique_symbols {
764 if let Node::DataSymbol(id) = unique {
765 if let Some(symbol) = self.data_symbols.get(id) {
766 if symbol.which_data_segment == idx {
767 let range =
768 symbol.segment_offset..symbol.segment_offset + symbol.symbol_size;
769 let offset = ConstExpr::Value(ir::Value::I32(
770 data_offset + symbol.segment_offset as i32,
771 ));
772 out.data.add(
773 DataKind::Active { memory, offset },
774 contents[range].to_vec(),
775 );
776 }
777 }
778 }
779 }
780 }
781 }
782
783 fn load_funcref_table(&self, out: &mut Module) -> TableId {
786 let ifunc_table = out
787 .tables
788 .iter()
789 .find(|t| t.element_ty == RefType::FUNCREF)
790 .map(|t| t.id());
791
792 if let Some(table) = ifunc_table {
793 table
794 } else {
795 out.tables.add_local(false, 0, None, RefType::FUNCREF)
796 }
797 }
798
799 fn make_stub_funcs(
805 &self,
806 out: &mut Module,
807 table: TableId,
808 ty_id: TypeId,
809 table_idx: i32,
810 ) -> FunctionKind {
811 let ty = out.types.get(ty_id);
813
814 let params = ty.params().to_vec();
815 let results = ty.results().to_vec();
816 let args: Vec<_> = params.iter().map(|ty| out.locals.add(*ty)).collect();
817
818 let mut builder = FunctionBuilder::new(&mut out.types, ¶ms, &results);
820 let mut body = builder.name("stub".into()).func_body();
821
822 for arg in args.iter() {
824 body.local_get(*arg);
825 }
826
827 body.instr(ir::Instr::Const(ir::Const {
829 value: ir::Value::I32(table_idx),
830 }));
831
832 body.instr(ir::Instr::CallIndirect(ir::CallIndirect {
834 ty: ty_id,
835 table,
836 }));
837
838 FunctionKind::Local(builder.local_func(args))
839 }
840
841 fn expand_ifunc_table_max(
845 &self,
846 out: &mut Module,
847 table: TableId,
848 num_ifuncs: usize,
849 ) -> Option<usize> {
850 let ifunc_table_ = out.tables.get_mut(table);
851
852 if let Some(max) = ifunc_table_.maximum {
853 ifunc_table_.maximum = Some(max + num_ifuncs as u64);
854 ifunc_table_.initial += num_ifuncs as u64;
855 return Some(max as usize);
856 }
857
858 None
859 }
860
861 fn remove_custom_sections(&self, out: &mut Module) {
863 let sections_to_delete = out
864 .customs
865 .iter()
866 .filter_map(|(id, section)| {
867 if section.name() == "target_features" {
868 None
869 } else {
870 Some(id)
871 }
872 })
873 .collect::<Vec<_>>();
874
875 for id in sections_to_delete {
876 out.customs.delete(id);
877 }
878 }
879
880 fn build_split_chunks(&mut self) {
886 let mut funcs_used_by_chunks: HashMap<Node, HashSet<usize>> = HashMap::new();
888 for split in self.split_points.iter() {
889 for item in split.reachable_graph.iter() {
890 if self.main_graph.contains(item) {
891 continue;
892 }
893 }
894 }
895
896 funcs_used_by_chunks.retain(|_, v| v.len() > 1);
898
899 self.chunks
902 .push(funcs_used_by_chunks.keys().cloned().collect());
903 }
904
905 fn unused_main_symbols(&self) -> HashSet<Node> {
906 self.split_points
907 .iter()
908 .flat_map(|split| split.reachable_graph.iter())
909 .filter(|sym| {
910 if self.main_graph.contains(sym) {
912 return false;
913 }
914
915 match sym {
917 Node::Function(u) => self.source_module.exports.get_exported_func(*u).is_none(),
918 _ => true,
919 }
920 })
921 .cloned()
922 .collect()
923 }
924
925 fn build_call_graph(&mut self) -> Result<()> {
928 let original = ModuleWithRelocations::new(self.original)?;
929
930 let old_names: HashMap<String, FunctionId> = original
931 .module
932 .funcs
933 .iter()
934 .flat_map(|f| Some((f.name.clone()?, f.id())))
935 .collect();
936
937 let new_names: HashMap<String, FunctionId> = self
938 .source_module
939 .funcs
940 .iter()
941 .flat_map(|f| Some((f.name.clone()?, f.id())))
942 .collect();
943
944 let mut old_to_new = HashMap::new();
945 let mut new_call_graph: HashMap<Node, HashSet<Node>> = HashMap::new();
946
947 for (new_name, new_func) in new_names.iter() {
948 if let Some(old_func) = old_names.get(new_name) {
949 old_to_new.insert(*old_func, new_func);
950 } else {
951 new_call_graph.insert(Node::Function(*new_func), HashSet::new());
952 }
953 }
954
955 let get_old = |old: &Node| -> Option<Node> {
956 match old {
957 Node::Function(id) => old_to_new.get(id).map(|new_id| Node::Function(**new_id)),
958 Node::DataSymbol(id) => Some(Node::DataSymbol(*id)),
959 }
960 };
961
962 let mut lost_children = HashSet::new();
971 self.call_graph = original
972 .call_graph
973 .iter()
974 .flat_map(|(old, children)| {
975 let Some(new) = get_old(old) else {
977 for child in children {
978 fn descend(
979 lost_children: &mut HashSet<Node>,
980 old_graph: &HashMap<Node, HashSet<Node>>,
981 node: Node,
982 ) {
983 if !lost_children.insert(node) {
984 return;
985 }
986
987 if let Some(children) = old_graph.get(&node) {
988 for child in children {
989 descend(lost_children, old_graph, *child);
990 }
991 }
992 }
993
994 descend(&mut lost_children, &original.call_graph, *child);
995 }
996 return None;
997 };
998
999 let mut new_children = HashSet::new();
1000 for child in children {
1001 if let Some(new) = get_old(child) {
1002 new_children.insert(new);
1003 }
1004 }
1005
1006 Some((new, new_children))
1007 })
1008 .collect();
1009
1010 let mut recovered_children = HashSet::new();
1011 for lost in lost_children {
1012 match lost {
1013 Node::Function(id) => {
1015 let func = original.module.funcs.get(id);
1016 let name = func.name.as_ref().unwrap();
1017 if let Some(entry) = new_names.get(name) {
1018 recovered_children.insert(Node::Function(*entry));
1019 }
1020 }
1021
1022 Node::DataSymbol(id) => {
1024 recovered_children.insert(Node::DataSymbol(id));
1025 }
1026 }
1027 }
1028
1029 let main_fn = self.source_module.funcs.by_name("main").context("Failed to find `main` function - was this built with LTO, --emit-relocs, and debug symbols?")?;
1031 let main_fn_entry = new_call_graph.entry(Node::Function(main_fn)).or_default();
1032 main_fn_entry.extend(recovered_children);
1033
1034 for (name, new) in new_names.iter() {
1036 if !old_names.contains_key(name) {
1037 main_fn_entry.insert(Node::Function(*new));
1038 }
1039 }
1040
1041 for func in self.source_module.funcs.iter() {
1044 struct CallGrapher<'a> {
1045 cur: FunctionId,
1046 call_graph: &'a mut HashMap<Node, HashSet<Node>>,
1047 }
1048 impl<'a> Visitor<'a> for CallGrapher<'a> {
1049 fn visit_function_id(&mut self, function: &walrus::FunctionId) {
1050 self.call_graph
1051 .entry(Node::Function(self.cur))
1052 .or_default()
1053 .insert(Node::Function(*function));
1054 }
1055 }
1056 if let FunctionKind::Local(local) = &func.kind {
1057 let mut call_grapher = CallGrapher {
1058 cur: func.id(),
1059 call_graph: &mut self.call_graph,
1060 };
1061 dfs_in_order(&mut call_grapher, local, local.entry_block());
1062 }
1063 }
1064
1065 for (parnet, children) in self.call_graph.iter() {
1067 for child in children {
1068 self.parent_graph.entry(*child).or_default().insert(*parnet);
1069 }
1070 }
1071
1072 self.split_points.iter_mut().for_each(|split| {
1075 let roots: HashSet<_> = [Node::Function(split.export_func)].into();
1076 split.reachable_graph = reachable_graph(&self.call_graph, &roots);
1077 });
1078
1079 self.main_graph = reachable_graph(&self.call_graph, &self.main_roots());
1081
1082 self.shared_symbols = {
1084 let mut shared_funcs = HashSet::new();
1085
1086 for split in self.split_points.iter() {
1088 shared_funcs.extend(self.main_graph.intersection(&split.reachable_graph));
1089 }
1090
1091 for import in self.source_module.imports.iter() {
1093 if let ImportKind::Function(id) = import.kind {
1094 shared_funcs.insert(Node::Function(id));
1095 }
1096 }
1097
1098 shared_funcs.into_iter().collect()
1100 };
1101
1102 Ok(())
1103 }
1104
1105 fn main_roots(&self) -> HashSet<Node> {
1106 let exported_splits = self
1109 .split_points
1110 .iter()
1111 .map(|f| f.export_func)
1112 .collect::<HashSet<_>>();
1113
1114 let mut roots = self
1116 .source_module
1117 .exports
1118 .iter()
1119 .filter_map(|e| match e.item {
1120 ExportItem::Function(id) if !exported_splits.contains(&id) => {
1121 Some(Node::Function(id))
1122 }
1123 _ => None,
1124 })
1125 .chain(self.source_module.start.map(Node::Function))
1126 .collect::<HashSet<Node>>();
1127
1128 for import in self.source_module.imports.iter() {
1130 if let ImportKind::Function(id) = import.kind {
1131 roots.insert(Node::Function(id));
1132 }
1133 }
1134
1135 roots
1136 }
1137
1138 fn remap_ids(&self, set: &HashSet<Node>, ids_to_fns: &[FunctionId]) -> HashSet<Node> {
1140 let mut out = HashSet::with_capacity(set.len());
1141 for node in set {
1142 out.insert(self.remap_id(ids_to_fns, node));
1143 }
1144 out
1145 }
1146
1147 fn remap_id(&self, ids_to_fns: &[id_arena::Id<walrus::Function>], node: &Node) -> Node {
1148 match node {
1149 Node::Function(id) => Node::Function(ids_to_fns[self.fns_to_ids[id]]),
1151 Node::DataSymbol(id) => Node::DataSymbol(*id),
1153 }
1154 }
1155}
1156
1157fn parse_module_with_ids(
1160 bindgened: &[u8],
1161) -> Result<(Module, Vec<FunctionId>, HashMap<FunctionId, usize>)> {
1162 let ids = Arc::new(RwLock::new(Vec::new()));
1163 let ids_ = ids.clone();
1164 let module = Module::from_buffer_with_config(
1165 bindgened,
1166 ModuleConfig::new().on_parse(move |_m, our_ids| {
1167 let mut ids = ids_.write().expect("No shared writers");
1168 let mut idx = 0;
1169 while let Ok(entry) = our_ids.get_func(idx) {
1170 ids.push(entry);
1171 idx += 1;
1172 }
1173
1174 Ok(())
1175 }),
1176 )?;
1177 let mut ids_ = ids.write().expect("No shared writers");
1178 let mut ids = vec![];
1179 std::mem::swap(&mut ids, &mut *ids_);
1180
1181 let mut fns_to_ids = HashMap::new();
1182 for (idx, id) in ids.iter().enumerate() {
1183 fns_to_ids.insert(*id, idx);
1184 }
1185
1186 Ok((module, ids, fns_to_ids))
1187}
1188
1189struct ModuleWithRelocations<'a> {
1190 module: Module,
1191 symbols: Vec<SymbolInfo<'a>>,
1192 names_to_funcs: HashMap<String, FunctionId>,
1193 call_graph: HashMap<Node, HashSet<Node>>,
1194 parents: HashMap<Node, HashSet<Node>>,
1195 relocation_map: HashMap<Node, Vec<RelocationEntry>>,
1196 data_symbols: BTreeMap<usize, DataSymbol>,
1197 data_section_range: Range<usize>,
1198}
1199
1200impl<'a> ModuleWithRelocations<'a> {
1201 fn new(bytes: &'a [u8]) -> Result<Self> {
1202 let module = Module::from_buffer(bytes)?;
1203 let raw_data = parse_bytes_to_data_segment(bytes)?;
1204 let names_to_funcs = module
1205 .funcs
1206 .iter()
1207 .flat_map(|f| Some((f.name.clone()?, f.id())))
1208 .collect();
1209
1210 let mut module = Self {
1211 module,
1212 data_symbols: raw_data.data_symbols,
1213 data_section_range: raw_data.data_range,
1214 symbols: raw_data.symbols,
1215 names_to_funcs,
1216 call_graph: Default::default(),
1217 relocation_map: Default::default(),
1218 parents: Default::default(),
1219 };
1220
1221 module.build_code_call_graph()?;
1222 module.build_data_call_graph()?;
1223
1224 for (func, children) in module.call_graph.iter() {
1225 for child in children {
1226 module.parents.entry(*child).or_default().insert(*func);
1227 }
1228 }
1229
1230 Ok(module)
1231 }
1232
1233 fn build_code_call_graph(&mut self) -> Result<()> {
1234 let codes_relocations = self.collect_relocations_from_section("reloc.CODE")?;
1235 let mut relocations = codes_relocations.iter().peekable();
1236
1237 for (func_id, local) in self.module.funcs.iter_local() {
1238 let range = local
1239 .original_range
1240 .clone()
1241 .context("local function has no range")?;
1242
1243 while let Some(entry) =
1245 relocations.next_if(|entry| entry.relocation_range().start < range.end)
1246 {
1247 let reloc_range = entry.relocation_range();
1248 assert!(reloc_range.start >= range.start);
1249 assert!(reloc_range.end <= range.end);
1250
1251 if let Some(target) = self.get_symbol_dep_node(entry.index as usize)? {
1252 let us = Node::Function(func_id);
1253 self.call_graph.entry(us).or_default().insert(target);
1254 self.relocation_map.entry(us).or_default().push(*entry);
1255 }
1256 }
1257 }
1258
1259 assert!(relocations.next().is_none());
1260
1261 Ok(())
1262 }
1263
1264 fn build_data_call_graph(&mut self) -> Result<()> {
1265 let data_relocations = self.collect_relocations_from_section("reloc.DATA")?;
1266 let mut relocations = data_relocations.iter().peekable();
1267
1268 let symbols_sorted = self
1269 .data_symbols
1270 .values()
1271 .sorted_by(|a, b| a.range.start.cmp(&b.range.start));
1272
1273 for symbol in symbols_sorted {
1274 let start = symbol.range.start - self.data_section_range.start;
1275 let end = symbol.range.end - self.data_section_range.start;
1276 let range = start..end;
1277
1278 while let Some(entry) =
1279 relocations.next_if(|entry| entry.relocation_range().start < range.end)
1280 {
1281 let reloc_range = entry.relocation_range();
1282 assert!(reloc_range.start >= range.start);
1283 assert!(reloc_range.end <= range.end);
1284
1285 if let Some(target) = self.get_symbol_dep_node(entry.index as usize)? {
1286 let dep = Node::DataSymbol(symbol.index);
1287 self.call_graph.entry(dep).or_default().insert(target);
1288 self.relocation_map.entry(dep).or_default().push(*entry);
1289 }
1290 }
1291 }
1292
1293 assert!(relocations.next().is_none());
1294
1295 Ok(())
1296 }
1297
1298 fn collect_relocations_from_section(&self, name: &str) -> Result<Vec<RelocationEntry>> {
1302 let (_reloc_id, code_reloc) = self
1303 .module
1304 .customs
1305 .iter()
1306 .find(|(_, c)| c.name() == name)
1307 .context("Module does not contain the reloc section")?;
1308
1309 let code_reloc_data = code_reloc.data(&Default::default());
1310 let reader = BinaryReader::new(&code_reloc_data, 0);
1311 let relocations = RelocSectionReader::new(reader)
1312 .context("failed to parse reloc section")?
1313 .entries()
1314 .into_iter()
1315 .flatten()
1316 .collect();
1317
1318 Ok(relocations)
1319 }
1320
1321 fn get_symbol_dep_node(&self, index: usize) -> Result<Option<Node>> {
1326 let res = match self.symbols[index] {
1327 SymbolInfo::Data { .. } => Some(Node::DataSymbol(index)),
1328 SymbolInfo::Func { name, .. } => Some(Node::Function({
1329 let name = name.context(
1330 "Function symbol has no name - did you forget to enable debug symbols",
1331 )?;
1332
1333 let func_id = self.names_to_funcs.get(name);
1334
1335 let Some(res) = func_id else {
1338 if !name.contains("__wbindgen_") {
1339 tracing::error!(
1340 "Could not find function symbol {name:?} in module - was this built with LTO, --emit-relocs, and debug symbols? Ignoring."
1341 );
1342 }
1343 return Ok(None);
1344 };
1345
1346 *res
1347 })),
1348
1349 _ => None,
1350 };
1351
1352 Ok(res)
1353 }
1354}
1355
1356#[derive(Debug, Clone)]
1357pub struct SplitPoint {
1358 module_name: String,
1359 import_id: ImportId,
1360 export_id: ExportId,
1361 import_func: FunctionId,
1362 export_func: FunctionId,
1363 component_name: String,
1364 index: usize,
1365 reachable_graph: HashSet<Node>,
1366 hash_name: String,
1367
1368 #[allow(unused)]
1369 import_name: String,
1370
1371 #[allow(unused)]
1372 export_name: String,
1373}
1374
1375fn accumulate_split_points(module: &Module) -> Vec<SplitPoint> {
1387 let mut index = 0;
1388
1389 module
1390 .imports
1391 .iter()
1392 .sorted_by(|a, b| a.name.cmp(&b.name))
1393 .flat_map(|import| {
1394 if !import.name.starts_with("__wasm_split_00") {
1395 return None;
1396 }
1397
1398 let ImportKind::Function(import_func) = import.kind else {
1399 return None;
1400 };
1401
1402 let remain = import.name.trim_start_matches("__wasm_split_00___");
1404 let (module_name, rest) = remain.split_once("___00").unwrap();
1405 let (hash, fn_name) = rest.trim_start_matches("_import_").split_once("_").unwrap();
1406
1407 let export_name =
1409 format!("__wasm_split_00___{module_name}___00_export_{hash}_{fn_name}");
1410 let export_func = module
1411 .exports
1412 .get_func(&export_name)
1413 .expect("Could not find export");
1414 let export = module.exports.get_exported_func(export_func).unwrap();
1415
1416 let our_index = index;
1417 index += 1;
1418
1419 Some(SplitPoint {
1420 export_id: export.id(),
1421 import_id: import.id(),
1422 module_name: module_name.to_string(),
1423 import_name: import.name.clone(),
1424 import_func,
1425 export_func,
1426 export_name,
1427 hash_name: hash.to_string(),
1428 component_name: fn_name.to_string(),
1429 index: our_index,
1430 reachable_graph: Default::default(),
1431 })
1432 })
1433 .collect()
1434}
1435
1436#[derive(Debug, PartialEq, Eq, Hash, Copy, PartialOrd, Ord, Clone)]
1437pub enum Node {
1438 Function(FunctionId),
1439 DataSymbol(usize),
1440}
1441
1442fn reachable_graph(deps: &HashMap<Node, HashSet<Node>>, roots: &HashSet<Node>) -> HashSet<Node> {
1443 let mut queue: VecDeque<Node> = roots.iter().copied().collect();
1444 let mut reachable = HashSet::<Node>::new();
1445 let mut parents = HashMap::<Node, Node>::new();
1446
1447 while let Some(node) = queue.pop_front() {
1448 reachable.insert(node);
1449 let Some(children) = deps.get(&node) else {
1450 continue;
1451 };
1452 for child in children {
1453 if reachable.contains(child) {
1454 continue;
1455 }
1456 parents.entry(*child).or_insert(node);
1457 queue.push_back(*child);
1458 }
1459 }
1460
1461 reachable
1462}
1463
1464struct RawDataSection<'a> {
1465 data_range: Range<usize>,
1466 symbols: Vec<SymbolInfo<'a>>,
1467 data_symbols: BTreeMap<usize, DataSymbol>,
1468}
1469
1470#[derive(Debug)]
1471struct DataSymbol {
1472 index: usize,
1473 range: Range<usize>,
1474 segment_offset: usize,
1475 symbol_size: usize,
1476 which_data_segment: usize,
1477}
1478
1479fn parse_bytes_to_data_segment(bytes: &[u8]) -> Result<RawDataSection<'_>> {
1485 let parser = wasmparser::Parser::new(0);
1486 let mut parser = parser.parse_all(bytes);
1487 let mut segments = vec![];
1488 let mut data_range = 0..0;
1489 let mut symbols = vec![];
1490
1491 while let Some(Ok(payload)) = parser.next() {
1493 match payload {
1494 Payload::DataSection(section) => {
1495 data_range = section.range();
1496 segments = section.into_iter().collect::<Result<Vec<_>, _>>()?
1497 }
1498 Payload::CustomSection(section) if section.name() == "linking" => {
1499 let reader = BinaryReader::new(section.data(), 0);
1500 let reader = LinkingSectionReader::new(reader)?;
1501 for subsection in reader.subsections() {
1502 if let Linking::SymbolTable(map) = subsection? {
1503 symbols = map.into_iter().collect::<Result<Vec<_>, _>>()?;
1504 }
1505 }
1506 }
1507 _ => {}
1508 }
1509 }
1510
1511 let mut data_symbols = BTreeMap::new();
1513 for (index, symbol) in symbols.iter().enumerate() {
1514 let SymbolInfo::Data {
1515 symbol: Some(symbol),
1516 ..
1517 } = symbol
1518 else {
1519 continue;
1520 };
1521
1522 if symbol.size == 0 {
1523 continue;
1524 }
1525
1526 let data_segment = segments
1527 .get(symbol.index as usize)
1528 .context("Failed to find data segment")?;
1529 let offset: usize =
1530 data_segment.range.end - data_segment.data.len() + (symbol.offset as usize);
1531 let range = offset..(offset + symbol.size as usize);
1532
1533 data_symbols.insert(
1534 index,
1535 DataSymbol {
1536 index,
1537 range,
1538 segment_offset: symbol.offset as usize,
1539 symbol_size: symbol.size as usize,
1540 which_data_segment: symbol.index as usize,
1541 },
1542 );
1543 }
1544
1545 Ok(RawDataSection {
1546 data_range,
1547 symbols,
1548 data_symbols,
1549 })
1550}