Skip to main content

yulang_runtime/monomorphize/pipeline/
mod.rs

1//! Specialize runtime IR until it is executable by the VM.
2//!
3//! This stage may clone bindings at concrete use sites, refine runtime types,
4//! resolve residual role calls, and remove unreachable helpers.  It should not
5//! invent new source semantics.  In particular, thunk shape and effect hygiene
6//! introduced by runtime lowering must be preserved rather than reconstructed
7//! from erased value types.
8
9use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
10use std::time::Duration;
11
12use yulang_typed_ir as typed_ir;
13
14use crate::diagnostic::{RuntimeError, RuntimeResult};
15use crate::invariant::{RuntimeStage, check_runtime_invariants, check_strict_runtime_value_types};
16use crate::ir::{
17    Binding, Expr, ExprKind, HandleArm, HandleEffect, JoinEvidence, MatchArm, Module, Pattern,
18    RecordExprField, RecordPatternField, RecordSpreadExpr, RecordSpreadPattern, ResumeBinding,
19    Root, Stmt, Type as RuntimeType, TypeInstantiation,
20};
21use crate::monomorphize::{
22    DemandEvidenceProfile, DemandQueueProfile, DemandSpecialization, demand_monomorphize_module,
23    reset_demand_evidence_profile, snapshot_demand_evidence_profile,
24};
25use crate::refine::refine_module_types_with_report;
26use crate::types::{
27    collect_expr_type_vars, collect_hir_type_vars, collect_type_vars as collect_core_type_vars,
28    core_type_has_vars, effect_is_empty, effect_paths_match, hir_type_has_vars,
29    project_runtime_effect, project_runtime_type_with_vars, runtime_core_type, should_thunk_effect,
30    substitute_apply_evidence, substitute_join_evidence, substitute_scheme, substitute_type,
31};
32use crate::validate::validate_module;
33
34mod audit;
35mod canonicalize;
36mod handler_boundary;
37mod local_refresh;
38mod locals;
39mod metadata;
40mod normalize;
41mod paths;
42mod principal_elaborate;
43mod principal_unify;
44mod reachability;
45mod shape;
46mod substitute;
47mod type_projection_metrics;
48mod type_surface;
49
50use audit::*;
51use canonicalize::*;
52use handler_boundary::*;
53use local_refresh::*;
54use locals::*;
55use metadata::*;
56use normalize::*;
57use paths::*;
58use principal_elaborate::*;
59use principal_unify::*;
60use reachability::*;
61use shape::*;
62use substitute::*;
63use type_surface::*;
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum MonomorphizeMode {
67    PrincipalElaborate,
68    LegacyDemandFixpoint,
69}
70
71impl MonomorphizeMode {
72    fn detect() -> Self {
73        if std::env::var_os("YULANG_LEGACY_MONO_FIXPOINT").is_some() {
74            Self::LegacyDemandFixpoint
75        } else {
76            Self::PrincipalElaborate
77        }
78    }
79
80    pub fn name(self) -> &'static str {
81        match self {
82            Self::PrincipalElaborate => "principal-elaborate",
83            Self::LegacyDemandFixpoint => "legacy-demand-fixpoint",
84        }
85    }
86}
87
88impl Default for MonomorphizeMode {
89    fn default() -> Self {
90        Self::PrincipalElaborate
91    }
92}
93
94pub fn monomorphize_module(module: Module) -> RuntimeResult<Module> {
95    crate::monomorphize::effect_hole_metrics::reset();
96    type_projection_metrics::reset();
97    let lowered = run_mono_pipeline_unprofiled(module)?;
98    let lowered = normalize_monomorphized_metadata(lowered);
99    audit_monomorphized_module(&lowered)?;
100    check_runtime_invariants(&lowered, RuntimeStage::Monomorphized)?;
101    check_strict_monomorphized_runtime_types_if_requested(&lowered)?;
102    validate_module(&lowered)?;
103    crate::monomorphize::effect_hole_metrics::report_if_requested("monomorphize_module");
104    type_projection_metrics::report_if_requested("monomorphize_module");
105    Ok(lowered)
106}
107
108pub fn monomorphize_module_profiled(
109    module: Module,
110) -> RuntimeResult<(Module, MonomorphizeProfile)> {
111    crate::monomorphize::effect_hole_metrics::reset();
112    type_projection_metrics::reset();
113    let (lowered, profile) = run_mono_pipeline(module)?;
114    let lowered = normalize_monomorphized_metadata(lowered);
115    audit_monomorphized_module(&lowered)?;
116    check_runtime_invariants(&lowered, RuntimeStage::Monomorphized)?;
117    check_strict_monomorphized_runtime_types_if_requested(&lowered)?;
118    validate_module(&lowered)?;
119    crate::monomorphize::effect_hole_metrics::report_if_requested("monomorphize_module_profiled");
120    type_projection_metrics::report_if_requested("monomorphize_module_profiled");
121    Ok((lowered, profile))
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
125pub struct MonomorphizeProfile {
126    pub mode: MonomorphizeMode,
127    pub passes: Vec<MonomorphizePassProfile>,
128    pub demand_evidence: DemandEvidenceProfile,
129}
130
131impl Default for MonomorphizeProfile {
132    fn default() -> Self {
133        Self {
134            mode: MonomorphizeMode::default(),
135            passes: Vec::new(),
136            demand_evidence: DemandEvidenceProfile::default(),
137        }
138    }
139}
140
141impl MonomorphizeProfile {
142    pub fn pass_count(&self) -> usize {
143        self.passes.len()
144    }
145
146    pub fn effective_pass_count(&self) -> usize {
147        self.passes
148            .iter()
149            .filter(|pass| pass.progress.changed())
150            .count()
151    }
152
153    pub fn added_specializations(&self) -> usize {
154        self.passes
155            .iter()
156            .map(|pass| pass.progress.added_specializations)
157            .sum()
158    }
159
160    pub fn demand_queue_profile(&self) -> DemandQueueProfile {
161        self.passes
162            .iter()
163            .fold(DemandQueueProfile::default(), |mut profile, pass| {
164                profile.merge(pass.demand_queue);
165                profile
166            })
167    }
168}
169
170#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct MonomorphizePassProfile {
172    pub name: &'static str,
173    pub duration: Duration,
174    pub bindings_before: usize,
175    pub bindings_after: usize,
176    pub roots_before: usize,
177    pub roots_after: usize,
178    pub progress: MonomorphizeProgress,
179    pub demand_queue: DemandQueueProfile,
180    pub principal_elaborate: SubstitutionSpecializeProfile,
181    pub added_binding_paths: Vec<typed_ir::Path>,
182    pub added_specializations: Vec<DemandSpecialization>,
183}
184
185#[derive(Debug, Default, Clone, PartialEq, Eq)]
186pub struct SubstitutionSpecializeProfile {
187    pub stats: HashMap<&'static str, usize>,
188    pub timings: HashMap<&'static str, Duration>,
189    pub target_skips: Vec<SubstitutionSpecializeTargetSkips>,
190    pub target_inferences: Vec<SubstitutionSpecializeTargetInferences>,
191    pub target_rewrites: Vec<SubstitutionSpecializeTargetRewrites>,
192}
193
194#[derive(Debug, Clone, PartialEq, Eq)]
195pub struct SubstitutionSpecializeTargetSkips {
196    pub target: typed_ir::Path,
197    pub survives_final_prune: Option<bool>,
198    pub actionable: bool,
199    pub reasons: Vec<SubstitutionSpecializeSkipCount>,
200    pub missing_vars: Vec<SubstitutionSpecializeMissingVarCount>,
201    pub no_complete_causes: Vec<SubstitutionSpecializeSkipCount>,
202}
203
204#[derive(Debug, Clone, PartialEq, Eq)]
205pub struct SubstitutionSpecializeSkipCount {
206    pub reason: &'static str,
207    pub count: usize,
208}
209
210#[derive(Debug, Clone, PartialEq, Eq)]
211pub struct SubstitutionSpecializeMissingVarCount {
212    pub var: typed_ir::TypeVar,
213    pub count: usize,
214}
215
216#[derive(Debug, Clone, PartialEq, Eq)]
217pub struct SubstitutionSpecializeTargetRewrites {
218    pub target: typed_ir::Path,
219    pub total_apply_visits: usize,
220    pub rewrites: usize,
221    pub cached_incomplete: usize,
222    pub incomplete: usize,
223    pub max_specialization_depth: usize,
224    pub contexts: Vec<SubstitutionSpecializeRewriteContextCount>,
225    pub phases: Vec<SubstitutionSpecializeRewritePhaseTiming>,
226    pub expr_kinds: Vec<SubstitutionSpecializeRewriteExprKindTiming>,
227}
228
229#[derive(Debug, Clone, PartialEq, Eq)]
230pub struct SubstitutionSpecializeRewriteContextCount {
231    pub context: &'static str,
232    pub count: usize,
233}
234
235#[derive(Debug, Clone, PartialEq, Eq)]
236pub struct SubstitutionSpecializeRewritePhaseTiming {
237    pub phase: &'static str,
238    pub duration: Duration,
239}
240
241#[derive(Debug, Clone, PartialEq, Eq)]
242pub struct SubstitutionSpecializeRewriteExprKindTiming {
243    pub kind: &'static str,
244    pub count: usize,
245    pub duration: Duration,
246}
247
248#[derive(Debug, Clone, PartialEq, Eq)]
249pub struct SubstitutionSpecializeTargetInferences {
250    pub target: typed_ir::Path,
251    pub sources: Vec<SubstitutionSpecializeInferenceCount>,
252}
253
254#[derive(Debug, Clone, PartialEq, Eq)]
255pub struct SubstitutionSpecializeInferenceCount {
256    pub source: &'static str,
257    pub count: usize,
258}
259
260#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
261pub struct MonomorphizeProgress {
262    pub changed_bindings: usize,
263    pub changed_roots: usize,
264    pub added_specializations: usize,
265}
266
267impl MonomorphizeProgress {
268    pub fn changed(self) -> bool {
269        self.changed_bindings > 0 || self.changed_roots > 0 || self.added_specializations > 0
270    }
271}
272
273#[derive(Debug, Clone, Copy, PartialEq, Eq)]
274enum MonoPass {
275    PrincipalElaborate,
276    DemandSpecialize,
277    RefineTypes,
278    RefreshClosedSchemes,
279    CanonicalizeSpecializations,
280    InlinePolymorphicWrappers,
281    PruneUnreachableSpecializations,
282    PruneUnreachable,
283}
284
285impl MonoPass {
286    fn name(self) -> &'static str {
287        match self {
288            MonoPass::PrincipalElaborate => "principal-elaborate",
289            MonoPass::DemandSpecialize => "demand-specialize",
290            MonoPass::RefineTypes => "refine-types",
291            MonoPass::RefreshClosedSchemes => "refresh-closed-schemes",
292            MonoPass::CanonicalizeSpecializations => "canonicalize-specializations",
293            MonoPass::InlinePolymorphicWrappers => "inline-polymorphic-wrappers",
294            MonoPass::PruneUnreachableSpecializations => "prune-unreachable-specializations",
295            MonoPass::PruneUnreachable => "prune-unreachable",
296        }
297    }
298}
299
300#[derive(Debug, Clone, Copy, PartialEq, Eq)]
301enum MonoStage {
302    Pass(MonoPass),
303    Repeat {
304        name: &'static str,
305        passes: &'static [MonoPass],
306        times: usize,
307    },
308    Fixpoint {
309        name: &'static str,
310        passes: &'static [MonoPass],
311        limit: usize,
312    },
313}
314
315const INITIAL_FIXPOINT: &[MonoPass] = &[MonoPass::DemandSpecialize, MonoPass::RefineTypes];
316
317const SPECIALIZATION_FIXPOINT: &[MonoPass] = &[
318    MonoPass::DemandSpecialize,
319    MonoPass::RefineTypes,
320    MonoPass::RefreshClosedSchemes,
321    MonoPass::PruneUnreachableSpecializations,
322];
323
324const MONO_PIPELINE: &[MonoStage] = &[
325    MonoStage::Repeat {
326        name: "initial-specialization",
327        passes: INITIAL_FIXPOINT,
328        times: 1,
329    },
330    MonoStage::Pass(MonoPass::CanonicalizeSpecializations),
331    MonoStage::Pass(MonoPass::InlinePolymorphicWrappers),
332    MonoStage::Fixpoint {
333        name: "role-specialization",
334        passes: SPECIALIZATION_FIXPOINT,
335        limit: 8,
336    },
337    MonoStage::Pass(MonoPass::PruneUnreachable),
338];
339
340fn run_mono_pipeline(module: Module) -> RuntimeResult<(Module, MonomorphizeProfile)> {
341    let mode = MonomorphizeMode::detect();
342    reset_demand_evidence_profile();
343    let (module, mut profile) = match mode {
344        MonomorphizeMode::PrincipalElaborate => run_principal_elaborate_pipeline(module)?,
345        MonomorphizeMode::LegacyDemandFixpoint => run_legacy_demand_fixpoint_pipeline(module)?,
346    };
347    profile.mode = mode;
348    profile.demand_evidence = snapshot_demand_evidence_profile();
349    annotate_substitution_skip_reachability(&mut profile, &module);
350    Ok((module, profile))
351}
352
353fn run_principal_elaborate_pipeline(
354    module: Module,
355) -> RuntimeResult<(Module, MonomorphizeProfile)> {
356    let debug = std::env::var_os("YULANG_DEBUG_MONO_PIPELINE").is_some();
357    let mut module = module;
358    let mut profile = MonomorphizeProfile::default();
359    let step = run_profiled_mono_pass(
360        module,
361        MonoPass::InlinePolymorphicWrappers,
362        &mut profile,
363        debug,
364    )?;
365    module = step.module;
366    let step = run_profiled_mono_pass(module, MonoPass::PrincipalElaborate, &mut profile, debug)?;
367    module = step.module;
368    let step = run_profiled_mono_pass(module, MonoPass::RefreshClosedSchemes, &mut profile, debug)?;
369    module = step.module;
370    let step = run_profiled_mono_pass(module, MonoPass::PruneUnreachable, &mut profile, debug)?;
371    module = step.module;
372    if std::env::var_os("YULANG_PRINCIPAL_ELABORATE_STRICT").is_some()
373        && let Some(context) = principal_elaborate_strict_failure(&module)
374    {
375        return Err(RuntimeError::InvariantViolation {
376            stage: "principal-elaborate",
377            context,
378            message: "principal elaboration plan incomplete",
379        });
380    }
381    Ok((module, profile))
382}
383
384fn check_strict_monomorphized_runtime_types_if_requested(module: &Module) -> RuntimeResult<()> {
385    if std::env::var_os("YULANG_STRICT_MONO_RUNTIME_TYPES").is_some() {
386        check_strict_runtime_value_types(module, RuntimeStage::Monomorphized)?;
387    }
388    Ok(())
389}
390
391fn run_legacy_demand_fixpoint_pipeline(
392    module: Module,
393) -> RuntimeResult<(Module, MonomorphizeProfile)> {
394    let debug = std::env::var_os("YULANG_DEBUG_MONO_PIPELINE").is_some();
395    let mut module = module;
396    let mut profile = MonomorphizeProfile::default();
397    for stage in MONO_PIPELINE {
398        match *stage {
399            MonoStage::Pass(pass) => {
400                let step = run_profiled_mono_pass(module, pass, &mut profile, debug)?;
401                module = step.module;
402            }
403            MonoStage::Repeat {
404                name,
405                passes,
406                times,
407            } => {
408                module = run_mono_repeat(module, name, passes, times, &mut profile, debug)?;
409            }
410            MonoStage::Fixpoint {
411                name,
412                passes,
413                limit,
414            } => {
415                module = run_mono_fixpoint(module, name, passes, limit, &mut profile, debug)?;
416            }
417        }
418    }
419    Ok((module, profile))
420}
421
422fn run_mono_pipeline_unprofiled(module: Module) -> RuntimeResult<Module> {
423    match MonomorphizeMode::detect() {
424        MonomorphizeMode::PrincipalElaborate => run_principal_elaborate_pipeline_unprofiled(module),
425        MonomorphizeMode::LegacyDemandFixpoint => {
426            run_mono_pipeline(module).map(|(module, _profile)| module)
427        }
428    }
429}
430
431fn run_principal_elaborate_pipeline_unprofiled(module: Module) -> RuntimeResult<Module> {
432    let mut module = inline_polymorphic_wrappers(module);
433    module = principal_elaborate_module(module);
434    module = refresh_closed_specialized_schemes(module);
435    module = prune_unreachable_bindings(module);
436    if std::env::var_os("YULANG_PRINCIPAL_ELABORATE_STRICT").is_some()
437        && let Some(context) = principal_elaborate_strict_failure(&module)
438    {
439        return Err(RuntimeError::InvariantViolation {
440            stage: "principal-elaborate",
441            context,
442            message: "principal elaboration plan incomplete",
443        });
444    }
445    Ok(module)
446}
447
448fn run_mono_repeat(
449    mut module: Module,
450    name: &'static str,
451    passes: &'static [MonoPass],
452    times: usize,
453    profile: &mut MonomorphizeProfile,
454    debug: bool,
455) -> RuntimeResult<Module> {
456    for round in 0..times {
457        let mut round_progress = MonoProgress::default();
458        for pass in passes {
459            let step = run_profiled_mono_pass(module, *pass, profile, debug)?;
460            module = step.module;
461            round_progress.merge(step.progress);
462        }
463        if debug {
464            eprintln!(
465                "mono repeat {name} round {round}: progress {}",
466                round_progress.format()
467            );
468        }
469        if !round_progress.changed() {
470            break;
471        }
472    }
473    Ok(module)
474}
475
476fn run_profiled_mono_pass(
477    module: Module,
478    pass: MonoPass,
479    profile: &mut MonomorphizeProfile,
480    debug: bool,
481) -> RuntimeResult<MonoStep> {
482    let before_for_changed_debug = std::env::var_os("YULANG_DEBUG_MONO_CHANGED")
483        .is_some()
484        .then(|| module.clone());
485    let before = MonoStats::from_module(&module);
486    let started = std::time::Instant::now();
487    let step = apply_mono_pass(module, pass)?;
488    let duration = started.elapsed();
489    let after = MonoStats::from_module(&step.module);
490    let progress = step.progress.to_public();
491    profile.passes.push(MonomorphizePassProfile {
492        name: pass.name(),
493        duration,
494        bindings_before: before.bindings,
495        bindings_after: after.bindings,
496        roots_before: before.roots,
497        roots_after: after.roots,
498        progress,
499        demand_queue: step.demand_queue,
500        principal_elaborate: step.principal_elaborate.clone(),
501        added_binding_paths: step.added_binding_paths.clone(),
502        added_specializations: step.added_specializations.clone(),
503    });
504    if debug {
505        eprintln!(
506            "mono pass {:>38}: bindings {} -> {}, roots {} -> {}, progress {}",
507            pass.name(),
508            before.bindings,
509            after.bindings,
510            before.roots,
511            after.roots,
512            step.progress.format()
513        );
514    }
515    if let Some(before) = before_for_changed_debug {
516        debug_mono_changed_items(pass.name(), &before, &step.module);
517    }
518    Ok(step)
519}
520
521fn run_mono_fixpoint(
522    mut module: Module,
523    name: &'static str,
524    passes: &'static [MonoPass],
525    limit: usize,
526    profile: &mut MonomorphizeProfile,
527    debug: bool,
528) -> RuntimeResult<Module> {
529    for round in 0..limit {
530        let mut round_progress = MonoProgress::default();
531        for pass in passes {
532            let step = run_profiled_mono_pass(module, *pass, profile, debug)?;
533            module = step.module;
534            round_progress.merge(step.progress);
535        }
536        if !round_progress.changed() {
537            if debug {
538                eprintln!("mono fixpoint {name} converged after {round} rounds");
539            }
540            return Ok(module);
541        } else if debug {
542            eprintln!(
543                "mono fixpoint {name} round {round}: progress {}",
544                round_progress.format()
545            );
546        }
547    }
548    if debug {
549        eprintln!("mono fixpoint {name} reached round limit");
550    }
551    Ok(module)
552}
553
554fn apply_mono_pass(module: Module, pass: MonoPass) -> RuntimeResult<MonoStep> {
555    match pass {
556        MonoPass::PrincipalElaborate => {
557            let bindings_before = module.bindings.len();
558            let roots_before = module.root_exprs.len();
559            let (module, principal_elaborate) = principal_elaborate_module_profiled(module);
560            let added_binding_paths = module
561                .bindings
562                .iter()
563                .skip(bindings_before)
564                .map(|binding| binding.name.clone())
565                .collect::<Vec<_>>();
566            let changed_bindings = principal_elaborate
567                .stats
568                .get("principal-unify-rewrite")
569                .copied()
570                .unwrap_or_default()
571                + principal_elaborate
572                    .stats
573                    .get("principal-unify-rewrite-surviving-template-binding")
574                    .copied()
575                    .unwrap_or_default();
576            let progress = MonoProgress {
577                changed_bindings,
578                changed_roots: module.root_exprs.len().abs_diff(roots_before),
579                added_specializations: module.bindings.len().saturating_sub(bindings_before),
580            };
581            Ok(MonoStep {
582                module,
583                progress,
584                demand_queue: DemandQueueProfile::default(),
585                principal_elaborate,
586                added_binding_paths,
587                added_specializations: Vec::new(),
588            })
589        }
590        MonoPass::DemandSpecialize => demand_specialize_module(module),
591        MonoPass::RefineTypes => refine_module_types_for_mono(module),
592        MonoPass::RefreshClosedSchemes => {
593            run_tracked_infallible_pass(module, refresh_closed_specialized_schemes)
594        }
595        MonoPass::CanonicalizeSpecializations => {
596            run_tracked_infallible_pass(module, canonicalize_equivalent_specializations)
597        }
598        MonoPass::InlinePolymorphicWrappers => {
599            run_tracked_infallible_pass(module, inline_polymorphic_wrappers)
600        }
601        MonoPass::PruneUnreachableSpecializations => {
602            run_tracked_infallible_pass(module, prune_unreachable_specializations)
603        }
604        MonoPass::PruneUnreachable => {
605            run_tracked_infallible_pass(module, prune_unreachable_bindings)
606        }
607    }
608}
609
610fn demand_specialize_module(module: Module) -> RuntimeResult<MonoStep> {
611    let before = module.clone();
612    let output =
613        demand_monomorphize_module(module).map_err(|error| RuntimeError::InvariantViolation {
614            stage: "monomorphization",
615            context: format!("{error:?}"),
616            message: "demand-driven specialization failed",
617        })?;
618    validate_module(&output.module)?;
619    let progress = MonoProgress::from_modules(&before, &output.module);
620    let added_binding_paths = added_binding_paths(&before, &output.module);
621    let added_specializations = output.profile.emitted_specializations;
622    Ok(MonoStep {
623        module: output.module,
624        progress,
625        demand_queue: output.profile.queue,
626        principal_elaborate: SubstitutionSpecializeProfile::default(),
627        added_binding_paths,
628        added_specializations,
629    })
630}
631
632struct MonoStats {
633    bindings: usize,
634    roots: usize,
635}
636
637impl MonoStats {
638    fn from_module(module: &Module) -> Self {
639        Self {
640            bindings: module.bindings.len(),
641            roots: module.root_exprs.len(),
642        }
643    }
644}
645
646fn debug_mono_changed_items(pass: &str, before: &Module, after: &Module) {
647    if std::env::var_os("YULANG_DEBUG_MONO_CHANGED").is_none() {
648        return;
649    }
650    let changed_bindings = changed_binding_names(before, after);
651    if !changed_bindings.is_empty() {
652        eprintln!("mono changed {pass} bindings: {changed_bindings:?}");
653    }
654    let changed_roots = changed_root_indexes(before, after);
655    if !changed_roots.is_empty() {
656        eprintln!("mono changed {pass} roots: {changed_roots:?}");
657    }
658}
659
660fn changed_binding_names(before: &Module, after: &Module) -> Vec<typed_ir::Path> {
661    let before_by_name = before
662        .bindings
663        .iter()
664        .map(|binding| (&binding.name, binding))
665        .collect::<HashMap<_, _>>();
666    let mut names = Vec::new();
667    for binding in &after.bindings {
668        if before_by_name
669            .get(&binding.name)
670            .is_none_or(|before| *before != binding)
671        {
672            names.push(binding.name.clone());
673        }
674    }
675    for binding in &before.bindings {
676        if !after
677            .bindings
678            .iter()
679            .any(|after| after.name == binding.name)
680        {
681            names.push(binding.name.clone());
682        }
683    }
684    names
685}
686
687fn changed_root_indexes(before: &Module, after: &Module) -> Vec<usize> {
688    let len = before.root_exprs.len().max(after.root_exprs.len());
689    (0..len)
690        .filter(|index| before.root_exprs.get(*index) != after.root_exprs.get(*index))
691        .collect()
692}
693
694struct MonoStep {
695    module: Module,
696    progress: MonoProgress,
697    demand_queue: DemandQueueProfile,
698    principal_elaborate: SubstitutionSpecializeProfile,
699    added_binding_paths: Vec<typed_ir::Path>,
700    added_specializations: Vec<DemandSpecialization>,
701}
702
703#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
704struct MonoProgress {
705    changed_bindings: usize,
706    changed_roots: usize,
707    added_specializations: usize,
708}
709
710impl MonoProgress {
711    fn changed(self) -> bool {
712        self.changed_bindings > 0 || self.changed_roots > 0 || self.added_specializations > 0
713    }
714
715    fn merge(&mut self, other: Self) {
716        self.changed_bindings += other.changed_bindings;
717        self.changed_roots += other.changed_roots;
718        self.added_specializations += other.added_specializations;
719    }
720
721    fn from_modules(before: &Module, after: &Module) -> Self {
722        let changed_roots = changed_item_count(&before.root_exprs, &after.root_exprs);
723        let changed_bindings = changed_item_count(&before.bindings, &after.bindings);
724        Self {
725            changed_bindings,
726            changed_roots,
727            added_specializations: after.bindings.len().saturating_sub(before.bindings.len()),
728        }
729    }
730
731    fn from_stats(before: MonoStats, after: MonoStats) -> Self {
732        Self {
733            changed_bindings: before.bindings.abs_diff(after.bindings),
734            changed_roots: before.roots.abs_diff(after.roots),
735            added_specializations: after.bindings.saturating_sub(before.bindings),
736        }
737    }
738
739    fn format(self) -> String {
740        if !self.changed() {
741            return "none".to_string();
742        }
743        format!(
744            "bindings={}, roots={}, new-specializations={}",
745            self.changed_bindings, self.changed_roots, self.added_specializations
746        )
747    }
748
749    fn to_public(self) -> MonomorphizeProgress {
750        MonomorphizeProgress {
751            changed_bindings: self.changed_bindings,
752            changed_roots: self.changed_roots,
753            added_specializations: self.added_specializations,
754        }
755    }
756}
757
758fn changed_item_count<T: PartialEq>(before: &[T], after: &[T]) -> usize {
759    let pair_changes = before
760        .iter()
761        .zip(after.iter())
762        .filter(|(before, after)| before != after)
763        .count();
764    let length_delta = before.len().abs_diff(after.len());
765    pair_changes + length_delta
766}
767
768fn run_tracked_pass<F>(module: Module, f: F) -> RuntimeResult<MonoStep>
769where
770    F: FnOnce(Module) -> RuntimeResult<Module>,
771{
772    if std::env::var_os("YULANG_LEGACY_MONO_FIXPOINT").is_some()
773        || std::env::var_os("YULANG_MONO_ACCURATE_PROGRESS").is_some()
774    {
775        let before = module.clone();
776        let module = f(module)?;
777        let progress = MonoProgress::from_modules(&before, &module);
778        let added_binding_paths = added_binding_paths(&before, &module);
779        return Ok(MonoStep {
780            module,
781            progress,
782            demand_queue: DemandQueueProfile::default(),
783            principal_elaborate: SubstitutionSpecializeProfile::default(),
784            added_binding_paths,
785            added_specializations: Vec::new(),
786        });
787    }
788
789    let before = MonoStats::from_module(&module);
790    let before_paths = module
791        .bindings
792        .iter()
793        .map(|binding| binding.name.clone())
794        .collect::<HashSet<_>>();
795    let module = f(module)?;
796    let progress = MonoProgress::from_stats(before, MonoStats::from_module(&module));
797    let added_binding_paths = module
798        .bindings
799        .iter()
800        .filter(|binding| !before_paths.contains(&binding.name))
801        .map(|binding| binding.name.clone())
802        .collect();
803    Ok(MonoStep {
804        module,
805        progress,
806        demand_queue: DemandQueueProfile::default(),
807        principal_elaborate: SubstitutionSpecializeProfile::default(),
808        added_binding_paths,
809        added_specializations: Vec::new(),
810    })
811}
812
813fn run_tracked_infallible_pass<F>(module: Module, f: F) -> RuntimeResult<MonoStep>
814where
815    F: FnOnce(Module) -> Module,
816{
817    run_tracked_pass(module, |module| Ok(f(module)))
818}
819
820fn refine_module_types_for_mono(module: Module) -> RuntimeResult<MonoStep> {
821    let output = refine_module_types_with_report(module)?;
822    let progress = MonoProgress {
823        changed_bindings: output.report.changed_bindings,
824        changed_roots: output.report.changed_roots,
825        added_specializations: 0,
826    };
827    Ok(MonoStep {
828        module: output.module,
829        progress,
830        demand_queue: DemandQueueProfile::default(),
831        principal_elaborate: SubstitutionSpecializeProfile::default(),
832        added_binding_paths: Vec::new(),
833        added_specializations: Vec::new(),
834    })
835}
836
837fn added_binding_paths(before: &Module, after: &Module) -> Vec<typed_ir::Path> {
838    let before_paths = before
839        .bindings
840        .iter()
841        .map(|binding| binding.name.clone())
842        .collect::<HashSet<_>>();
843    after
844        .bindings
845        .iter()
846        .filter(|binding| !before_paths.contains(&binding.name))
847        .map(|binding| binding.name.clone())
848        .collect()
849}
850
851fn annotate_substitution_skip_reachability(
852    profile: &mut MonomorphizeProfile,
853    final_module: &Module,
854) {
855    let surviving_bindings = final_reachable_binding_paths(final_module);
856    for pass in &mut profile.passes {
857        for target in &mut pass.principal_elaborate.target_skips {
858            target.survives_final_prune = Some(surviving_bindings.contains(&target.target));
859        }
860    }
861}