1use std::{
2 collections::{BTreeMap, BTreeSet},
3 fmt::Display,
4};
5
6use anyhow::Result;
7use wamex_types::{BumpVersion, ModuleId, map_vec::MiniSet};
8
9use crate::{
10 ModuleIdentifier, SplitModuleIdentifier, SplitPointExtractor,
11 analysis::{
12 self, StaticModuleInfo,
13 split_point::{OutputModuleInfo, SharedModuleIdentifier},
14 symbols::DiffEntry,
15 },
16 index::{IdMap, SymbolId},
17};
18
19pub type ModuleDeps = BTreeMap<ModuleId, Vec<ModuleId>>;
20pub struct ModuleUpdate {
21 pub module_id: ModuleId,
22 pub force_restart: bool,
23}
24pub enum IncrementalSplitResult {
25 Unchanged,
26 UpdatedModules(Vec<ModuleUpdate>),
27 FullResplit,
28}
29
30pub struct SplitResult {
31 pub deps: ModuleDeps,
32 pub incremental_result: IncrementalSplitResult,
33 _non_exhaustive: (),
34}
35
36#[derive(Clone)]
37pub struct IncrementalSplitState {
38 last_module_info: StaticModuleInfo,
40 last_module_structure: Vec<(SplitModuleIdentifier, OutputModuleInfo)>,
41 modules_versions: BTreeMap<SplitModuleIdentifier, BumpVersion>,
42 bump_version: BumpVersion,
43 }
45impl IncrementalSplitState {
46 pub fn new() -> Self {
47 Self {
48 last_module_info: StaticModuleInfo::empty(),
49 last_module_structure: Vec::new(),
50 modules_versions: BTreeMap::new(),
51 bump_version: BumpVersion::new(),
52 }
53 }
54 pub fn is_empty(&self) -> bool {
55 self.last_module_structure.is_empty()
56 }
57
58 pub fn split_incremental(
59 &mut self,
60 input_wasm: &[u8],
61 verbose: bool,
62 precise_modification: bool,
63 split_point_extractor: SplitPointExtractor,
64 mut emit_module_fn: impl FnMut(ModuleId, &[u8]) -> Result<()>,
65 ) -> Result<SplitResult> {
66 let module = crate::InputModule::parse(input_wasm)?;
68 let info = analysis::ModuleInfo::from_raw_module(module)?;
69 let dep_graph = analysis::dep_graph::get_dependencies(&info)?;
70 let split_points = analysis::split_point::find_split_points(&info, split_point_extractor)?;
71 let wbg_closures = analysis::split_point::SplitProgramInfo::wbg_closures(&info, &dep_graph);
72 let mut split_program_info = crate::SplitProgramInfo::compute_split_modules(
73 &info,
74 &dep_graph,
75 &split_points,
76 &wbg_closures,
77 )?;
78
79 crate::emit::merge_main_shared(&mut split_program_info);
82
83 if verbose {
84 println!("Split points: {split_points:?}");
85 println!("Split program info: {split_program_info:?}");
86 println!("Dependency graph: {dep_graph:?}");
87 println!("Module symbols:");
88 info.symbols.print_debug();
89
90 println!("Module split details:");
91 for (name, split_deps) in split_program_info.output_modules.iter() {
92 split_deps.print(format!("{:?}", name).as_str(), &info, &dep_graph);
93 }
94 }
95
96 let module_structure = split_program_info.output_modules.clone();
98 let structure_diff = StructureDiffResult::new(
99 &self.last_module_structure,
100 &self.last_module_info,
101 &module_structure,
102 &info,
103 );
104 log::info!("Incremental split analysis result: {}", structure_diff);
105 if structure_diff.not_changed() {
106 log::info!("No changes detected in split modules.");
107 return Ok(SplitResult {
108 deps: self.build_deps_map(),
109 incremental_result: IncrementalSplitResult::Unchanged,
110 _non_exhaustive: (),
111 });
112 }
113 self.last_module_structure = module_structure;
115 self.last_module_info = StaticModuleInfo::new(&info);
116 self.bump_version.bump();
117 let whitelist = structure_diff.whitelist();
118
119 let latest_version = self.bump_version.clone();
120 let emit_fn = |identifier: &SplitModuleIdentifier, data: &[u8]| -> Result<()> {
122 self.modules_versions
123 .insert(identifier.clone(), latest_version);
124
125 let module_id = self.last_module_id(identifier);
126 emit_module_fn(module_id, data)
127 };
128
129 crate::emit::emit_modules(
130 &info,
131 verbose,
132 &split_program_info,
133 &wbg_closures,
134 precise_modification,
135 whitelist.as_ref(),
136 latest_version,
137 emit_fn,
138 )?;
139
140 if let Some(changed_modules) = whitelist {
141 Ok(SplitResult {
142 deps: self.build_deps_map(),
143 incremental_result: IncrementalSplitResult::UpdatedModules(
144 self.build_update_list(changed_modules, structure_diff),
145 ),
146 _non_exhaustive: (),
147 })
148 } else {
149 Ok(SplitResult {
150 deps: self.build_deps_map(),
151 incremental_result: IncrementalSplitResult::FullResplit,
152 _non_exhaustive: (),
153 })
154 }
155 }
156
157 fn build_update_list(
160 &self,
161 changed_list: BTreeSet<SplitModuleIdentifier>,
162 structure_diff: StructureDiffResult,
163 ) -> Vec<ModuleUpdate> {
164 changed_list
165 .into_iter()
166 .map(|id| ModuleUpdate {
167 module_id: self.last_module_id(&id),
168 force_restart: !structure_diff.only_struct_dep_change(&id),
169 })
170 .collect()
171 }
172
173 fn last_module_id(&self, module: &SplitModuleIdentifier) -> ModuleId {
174 ModuleId::new_from_components(
175 module.to_string(),
176 Some(
177 self.modules_versions
178 .get(module)
179 .expect("Module version must be present for last emitted module")
180 .clone(),
181 ),
182 None,
183 )
184 }
185
186 fn build_deps_map(&self) -> ModuleDeps {
187 let shared_modules = self
188 .last_module_structure
189 .iter()
190 .filter_map(|(id, _)| id.as_shared().cloned())
191 .collect::<Vec<_>>();
192
193 let mut deps = BTreeMap::new();
194 for (id, _) in &self.last_module_structure {
195 if id.is_shared() {
196 continue;
197 }
198
199 let module_id = self.last_module_id(id);
200 let module_deps = id.collect_deps(&shared_modules);
201 let prev = deps.insert(
202 module_id,
203 module_deps
204 .into_iter()
205 .map(|m| self.last_module_id(&SplitModuleIdentifier::Shared(m)))
206 .collect(),
207 );
208 debug_assert!(prev.is_none());
209 }
210 deps
211 }
212}
213
214#[derive(Debug)]
215struct StructureDiffResult {
216 with_changed_symbols: BTreeSet<SplitModuleIdentifier>,
218 with_changed_exports: BTreeSet<SharedModuleIdentifier>,
220
221 changed_deps: BTreeMap<SplitModuleIdentifier, bool >,
223 structure_changed: bool,
225}
226impl StructureDiffResult {
227 fn main_changed(&self) -> bool {
228 self.with_changed_symbols
229 .contains(&SplitModuleIdentifier::Single(ModuleIdentifier::Main))
230 }
231
232 pub fn not_changed(&self) -> bool {
233 !self.structure_changed
234 && self.with_changed_symbols.is_empty()
235 && self.with_changed_exports.is_empty()
236 && self.changed_deps.is_empty()
237 }
238 pub fn need_full_rebuild(&self) -> bool {
239 self.structure_changed || self.main_changed()
240 }
241 fn entrypoint_list(
242 module_structure: &[(SplitModuleIdentifier, OutputModuleInfo)],
243 ) -> Vec<SplitModuleIdentifier> {
244 module_structure
245 .iter()
246 .filter(|(m, _)| !m.is_shared())
247 .map(|(m, _)| m.clone())
248 .collect()
249 }
250
251 pub fn only_struct_dep_change(&self, module: &SplitModuleIdentifier) -> bool {
253 self.changed_deps.get(module).copied().unwrap_or(false)
254 }
255 pub fn whitelist(&self) -> Option<BTreeSet<SplitModuleIdentifier>> {
257 if self.need_full_rebuild() {
258 return None;
259 }
260 let mut whitelist = self.with_changed_symbols.clone();
261 whitelist.extend(
262 self.with_changed_exports
263 .iter()
264 .map(|m| SplitModuleIdentifier::Shared(m.clone())),
265 );
266 whitelist.extend(self.changed_deps.keys().cloned());
267
268 Some(whitelist)
269 }
270
271 fn find_changed_sig<SymMapper>(
273 old_modules: &[(SplitModuleIdentifier, OutputModuleInfo)],
274 new_modules: &[(SplitModuleIdentifier, OutputModuleInfo)],
275 sym_mapper: SymMapper,
276 ) -> BTreeSet<SharedModuleIdentifier>
277 where
278 SymMapper: Fn(&SymbolId) -> Option<SymbolId>,
279 {
280 let old_set: BTreeMap<SplitModuleIdentifier, &OutputModuleInfo> = old_modules
281 .iter()
282 .map(|(m, info)| (m.clone(), info))
283 .collect();
284 let mut new_chunks = BTreeSet::new();
285 for (module, info) in new_modules {
286 let Some(shared) = module.as_shared() else {
287 continue;
288 };
289 if let Some(old_info) = old_set.get(module) {
290 let exports_list = old_info
291 .exports
292 .iter()
293 .map(|s| sym_mapper(s))
294 .collect::<Option<MiniSet<SymbolId>>>();
295 if let Some(exports_list) = exports_list {
296 if exports_list == info.exports {
297 continue;
298 }
299 }
300 }
301 new_chunks.insert(shared.clone());
307 }
308 new_chunks
309 }
310
311 fn shared_module_users(
314 shared: &SharedModuleIdentifier,
315 list: &[(SplitModuleIdentifier, OutputModuleInfo)],
316 ) -> Vec<SplitModuleIdentifier> {
317 list.iter()
318 .filter(|(m, _)| shared.includes(m) && shared != m)
319 .map(|(m, _)| m.clone())
320 .collect()
321 }
322
323 fn collect_changed_users<'a>(
325 changed_shared: impl Iterator<Item = &'a SharedModuleIdentifier>,
326 new_module_structure: &[(SplitModuleIdentifier, OutputModuleInfo)],
327 ) -> BTreeSet<SplitModuleIdentifier> {
328 let mut changed_deps = BTreeSet::new();
329 for shared in changed_shared {
330 let users = Self::shared_module_users(shared, new_module_structure);
331 for user in users {
332 changed_deps.insert(user);
333 }
334 }
335 changed_deps
336 }
337
338 pub fn new(
339 old_module_structure: &[(SplitModuleIdentifier, OutputModuleInfo)],
340 old_module_info: &StaticModuleInfo,
341 new_module_structure: &[(SplitModuleIdentifier, OutputModuleInfo)],
342 new_module_info: &analysis::ModuleInfo<'_>,
343 ) -> Self {
344 let old_entrypoits = Self::entrypoint_list(old_module_structure);
345 let new_entrypoints = Self::entrypoint_list(new_module_structure);
346 let rebuild_full = old_entrypoits != new_entrypoints;
349
350 let mut debug_module_changed_info = BTreeMap::new();
351
352 if rebuild_full {
354 log::info!("Split module structure changed, performing full rebuild.");
355 return Self {
356 with_changed_symbols: BTreeSet::new(),
357 with_changed_exports: BTreeSet::new(),
358 changed_deps: BTreeMap::new(),
359 structure_changed: true,
360 };
361 }
362
363 let mut symbol_map = IdMap::<SymbolId, SplitModuleIdentifier>::new();
365 for (module_id, split_module) in new_module_structure.iter() {
366 for symbol in &split_module.defined_symbols {
367 symbol_map.insert(*symbol, module_id.clone());
368 }
369 }
370
371 let differ = analysis::symbols::Differ::new(old_module_info, new_module_info);
373 let sym_map = differ.symbol_map();
374 let diff_result = differ.build_diff(&sym_map);
375 let changed_syms = diff_result
376 .all_changes()
377 .filter_map(|entry| match entry {
378 DiffEntry::Added { right } | DiffEntry::Replaced { right, .. } => Some(right),
379 _ => None,
380 })
381 .copied()
382 .collect::<Vec<_>>();
383
384 let mut with_changed_symbols = BTreeSet::new();
386 for sym in changed_syms {
387 if let Some(module_id) = symbol_map.get(sym) {
388 with_changed_symbols.insert(module_id.clone());
389 debug_module_changed_info
390 .entry(module_id.clone())
391 .or_insert_with(Vec::new)
392 .push(sym);
393 }
394 }
395
396 let mut with_changed_exports =
397 Self::find_changed_sig(old_module_structure, new_module_structure, |sym| {
398 sym_map.map(*sym)
399 });
400
401 let changed_symbols_users = Self::collect_changed_users(
402 with_changed_symbols.iter().filter_map(|m| match m {
403 SplitModuleIdentifier::Shared(shared) => Some(shared),
404 _ => None,
405 }),
406 new_module_structure,
407 )
408 .into_iter()
409 .map(|m| (m, true)); let changed_exports_users =
412 Self::collect_changed_users(with_changed_exports.iter(), new_module_structure)
413 .into_iter()
414 .map(|m| (m, false)); let mut changed_deps = changed_symbols_users
417 .chain(changed_exports_users)
418 .collect::<BTreeMap<_, _>>();
419
420 with_changed_exports
422 .retain(|m| !with_changed_symbols.contains(&SplitModuleIdentifier::Shared(m.clone())));
423
424 changed_deps.retain(|m, _| {
425 !with_changed_symbols.contains(m)
426 && m.as_shared()
427 .map(|s| !with_changed_exports.contains(s))
428 .unwrap_or(true)
429 });
430
431 let this = Self {
434 with_changed_symbols,
435 with_changed_exports,
436 structure_changed: rebuild_full,
437 changed_deps,
438 };
439 if this.main_changed() {
441 log::info!("Main module changed, performing full rebuild.");
442 }
443
444 if !debug_module_changed_info.is_empty() {
445 log::info!("Changed modules and symbols:");
446 for (module, symbols) in debug_module_changed_info.iter() {
447 log::info!(" Module {:?} changed symbols:", module);
448 for sym in symbols {
449 let symbol = new_module_info.symbols.get(*sym).unwrap();
450 log::info!(
451 " Symbol {:?} <{}>",
452 sym,
453 crate::helpers::demangle_full(&symbol.name)
454 );
455 }
456 }
457 }
458
459 this
460 }
461}
462
463impl Display for StructureDiffResult {
464 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
465 if self.not_changed() {
466 return write!(f, "No changes detected");
467 }
468 if !self.with_changed_symbols.is_empty() {
470 let with_changed_symbols = self
471 .with_changed_symbols
472 .iter()
473 .map(|m| m.to_string())
474 .collect::<Vec<_>>()
475 .join(", ");
476 write!(f, " Modules with changed symbols: {}", with_changed_symbols)?;
477 }
478 if !self.with_changed_exports.is_empty() {
479 let with_changed_exports = self
480 .with_changed_exports
481 .iter()
482 .map(|m| m.to_string())
483 .collect::<Vec<_>>()
484 .join(", ");
485 write!(f, " Modules with changed exports: {}", with_changed_exports)?;
486 }
487
488 if !self.changed_deps.is_empty() {
489 let changed_deps = self
490 .changed_deps
491 .iter()
492 .map(|(m, v)| format!("{m}, soft_reload: {v}"))
493 .collect::<Vec<_>>()
494 .join(", ");
495 write!(f, " Changed dependent modules: {}", changed_deps)?;
496 }
497 Ok(())
498 }
499}