Skip to main content

submilli_engine/codegen/
dependency_usage.rs

1use std::borrow::Cow;
2use std::collections::{BTreeMap, BTreeSet};
3
4use crate::{
5    MangledName, NamespaceSymbol, PackageDeclaration, Shape, Type, TypeKind, TypeSymbol, ValueKind,
6    ValueSymbol,
7};
8
9pub struct DependencyValue<'a> {
10    pub package: &'a PackageDeclaration,
11    pub export_name: Cow<'a, str>,
12    pub symbol: &'a ValueSymbol,
13}
14
15pub struct DependencyType<'a> {
16    pub package: &'a PackageDeclaration,
17    pub name: &'a str,
18    pub symbol: &'a TypeSymbol,
19    pub full: bool,
20}
21
22#[derive(Debug)]
23pub struct DependencyUsage {
24    values: BTreeSet<MangledName>,
25    members: BTreeSet<MangledName>,
26    types: BTreeSet<MangledName>,
27    member_types: BTreeSet<MangledName>,
28    shapes: BTreeSet<Shape>,
29    /// Shapes whose whole subtree `collect_type` has walked, unlike `shapes`,
30    /// which `note_shape` also fills.
31    collected_shapes: BTreeSet<Shape>,
32    uses_bigint: bool,
33}
34
35impl DependencyUsage {
36    pub(crate) fn empty() -> Self {
37        Self {
38            values: BTreeSet::new(),
39            members: BTreeSet::new(),
40            types: BTreeSet::new(),
41            member_types: BTreeSet::new(),
42            shapes: BTreeSet::new(),
43            collected_shapes: BTreeSet::new(),
44            uses_bigint: false,
45        }
46    }
47
48    pub(crate) fn finish(mut self, dependencies: &[&PackageDeclaration]) -> Self {
49        self.collect_codegen_prelude_values();
50        for package in dependencies {
51            for symbol in package.runtime_types.values() {
52                if let TypeKind::Class {
53                    narrowing_checks, ..
54                } = &symbol.kind
55                    && !narrowing_checks.is_empty()
56                {
57                    self.note_type(symbol.mangled_name.clone());
58                }
59            }
60        }
61        self.resolve_dependency_shapes(dependencies);
62        self
63    }
64
65    pub fn is_value_used(&self, mangled: &MangledName) -> bool {
66        self.values.contains(mangled)
67    }
68
69    pub fn is_member_used(&self, mangled: &MangledName) -> bool {
70        self.members.contains(mangled)
71    }
72
73    pub fn is_type_used(&self, mangled: &MangledName) -> bool {
74        self.types.contains(mangled)
75    }
76
77    pub fn is_shape_used(&self, shape: &Shape) -> bool {
78        self.shapes.contains(shape)
79    }
80
81    pub fn uses_bigint(&self) -> bool {
82        self.uses_bigint
83    }
84
85    pub(crate) fn note_value(&mut self, mangled: MangledName) {
86        self.values.insert(mangled);
87    }
88
89    pub(crate) fn note_member(&mut self, mangled: MangledName) {
90        self.members.insert(mangled);
91    }
92
93    pub(crate) fn note_type(&mut self, mangled: MangledName) {
94        self.types.insert(mangled);
95    }
96
97    pub(crate) fn note_shape(&mut self, shape: Shape) {
98        self.shapes.insert(shape);
99    }
100
101    pub fn is_host_value_used(&self, module: &str, name: &str) -> bool {
102        self.values.contains(&crate::mangle::host(module, name))
103    }
104
105    pub fn dependency_values<'a>(
106        &self,
107        dependencies: &'a [&'a PackageDeclaration],
108    ) -> Vec<DependencyValue<'a>> {
109        let mut values = Vec::new();
110        for package in dependencies {
111            values.extend(package.values.iter().filter_map(|(name, symbol)| {
112                self.is_value_used(&symbol.mangled_name)
113                    .then_some(DependencyValue {
114                        package,
115                        export_name: Cow::Borrowed(name.as_str()),
116                        symbol,
117                    })
118            }));
119            collect_used_namespace_values(self, package, "", &package.namespaces, &mut values);
120        }
121        values
122    }
123
124    pub fn dependency_types<'a>(
125        &self,
126        dependencies: &'a [&'a PackageDeclaration],
127    ) -> Vec<DependencyType<'a>> {
128        let mut types = Vec::new();
129        for package in dependencies {
130            collect_used_types(self, package, &package.types, &mut types);
131            for (name, symbol) in &package.runtime_types {
132                if self.is_type_reachable(symbol)
133                    && !package
134                        .types
135                        .values()
136                        .any(|public| public.mangled_name == symbol.mangled_name)
137                {
138                    types.push(DependencyType {
139                        package,
140                        name,
141                        symbol,
142                        full: self.is_type_fully_used(symbol),
143                    });
144                }
145            }
146            collect_used_namespace_types(self, package, &package.namespaces, &mut types);
147        }
148        types
149    }
150
151    pub fn dependency_shapes<'a>(
152        &self,
153        dependencies: &'a [&'a PackageDeclaration],
154    ) -> Vec<&'a Shape> {
155        dependencies
156            .iter()
157            .flat_map(|package| package.shapes.iter())
158            .filter(|shape| self.is_shape_used(shape))
159            .collect()
160    }
161
162    pub fn is_type_fully_used(&self, ty: &TypeSymbol) -> bool {
163        self.is_type_used(&ty.mangled_name)
164    }
165
166    pub fn is_type_reachable(&self, ty: &TypeSymbol) -> bool {
167        self.is_type_used(&ty.mangled_name) || self.member_types.contains(&ty.mangled_name)
168    }
169
170    /// Reachability by mangled name alone — a class used *only* through its
171    /// statics (`Calc.add(…)`, never constructed) lands in `member_types`, and
172    /// still needs its WasmGC types reconstructed on the consumer side.
173    pub fn is_type_reachable_by_name(&self, mangled: &MangledName) -> bool {
174        self.types.contains(mangled) || self.member_types.contains(mangled)
175    }
176
177    pub fn is_interface_member_used(&self, ty: &TypeSymbol, member: &str) -> bool {
178        self.is_type_used(&ty.mangled_name)
179            || self.is_member_used(&crate::mangle::extend(&ty.mangled_name, member))
180    }
181
182    fn resolve_dependency_shapes(&mut self, dependencies: &[&PackageDeclaration]) {
183        let mut values: BTreeMap<MangledName, &crate::ValueSymbol> = BTreeMap::new();
184        let mut types: BTreeMap<MangledName, &crate::TypeSymbol> = BTreeMap::new();
185        let mut members: BTreeMap<MangledName, (&crate::TypeSymbol, &str)> = BTreeMap::new();
186        // Class statics keyed `Class#static#name`. Their call sites lower to
187        // plain `Call`/`GlobalRef`, so they surface in `self.values` — resolved
188        // in the values loop below to mark the owning class reachable.
189        let mut statics: BTreeMap<MangledName, (&crate::TypeSymbol, &str)> = BTreeMap::new();
190
191        for defs in dependencies {
192            collect_values(&defs.values, &mut values);
193            collect_namespace_values(&defs.namespaces, &mut values);
194            for ty_sym in defs.runtime_types.values().chain(defs.types.values()) {
195                types.insert(ty_sym.mangled_name.clone(), ty_sym);
196                if let TypeKind::Interface {
197                    methods,
198                    properties,
199                    ..
200                } = &ty_sym.kind
201                {
202                    for name in methods.keys() {
203                        members.insert(
204                            crate::mangle::extend(&ty_sym.mangled_name, name),
205                            (ty_sym, name),
206                        );
207                    }
208                    for name in properties.keys() {
209                        members.insert(
210                            crate::mangle::extend(&ty_sym.mangled_name, name),
211                            (ty_sym, name),
212                        );
213                    }
214                }
215                if let TypeKind::Class {
216                    statics: static_methods,
217                    static_fields,
218                    ..
219                } = &ty_sym.kind
220                {
221                    for name in static_methods.keys().chain(static_fields.keys()) {
222                        statics.insert(
223                            crate::mangle::static_member(&ty_sym.mangled_name, name),
224                            (ty_sym, name),
225                        );
226                    }
227                }
228            }
229        }
230
231        let mut resolved_values = BTreeSet::new();
232        let mut resolved_members = BTreeSet::new();
233        let mut resolved_types = BTreeSet::new();
234        loop {
235            let mut changed = false;
236
237            for mangled in self.values.clone() {
238                if !resolved_values.insert(mangled.clone()) {
239                    continue;
240                }
241                if let Some(sym) = values.get(&mangled) {
242                    changed = true;
243                    self.collect_value_symbol(sym);
244                }
245                if let Some((ty_sym, member_name)) = statics.get(&mangled) {
246                    changed = true;
247                    self.member_types.insert(ty_sym.mangled_name.clone());
248                    self.collect_type_symbol(ty_sym);
249                    if let TypeKind::Class {
250                        statics: static_methods,
251                        static_fields,
252                        ..
253                    } = &ty_sym.kind
254                    {
255                        if let Some(sig) = static_methods.get(*member_name) {
256                            for p in &sig.params {
257                                self.collect_type(&p.ty);
258                            }
259                            self.collect_type(&sig.ret);
260                        }
261                        if let Some(field) = static_fields.get(*member_name) {
262                            self.collect_type(&field.ty);
263                        }
264                    }
265                }
266            }
267
268            for mangled in self.members.clone() {
269                if !resolved_members.insert(mangled.clone()) {
270                    continue;
271                }
272                if let Some(sym) = values.get(&mangled) {
273                    changed = true;
274                    self.values.insert(mangled.clone());
275                    self.collect_value_symbol(sym);
276                }
277                if let Some((ty_sym, member_name)) = members.get(&mangled) {
278                    changed = true;
279                    self.member_types.insert(ty_sym.mangled_name.clone());
280                    self.collect_type_symbol(ty_sym);
281                    if let TypeKind::Interface {
282                        methods,
283                        properties,
284                        ..
285                    } = &ty_sym.kind
286                    {
287                        if let Some(sig) = methods.get(*member_name) {
288                            for p in &sig.params {
289                                self.collect_type(&p.ty);
290                            }
291                            self.collect_type(&sig.ret);
292                        }
293                        if let Some(sig) = properties.get(*member_name) {
294                            self.collect_type(&sig.ty);
295                        }
296                    }
297                }
298            }
299
300            for mangled in self.types.clone() {
301                if !resolved_types.insert(mangled.clone()) {
302                    continue;
303                }
304                if let Some(sym) = types.get(&mangled) {
305                    changed = true;
306                    self.collect_type_symbol(sym);
307                    // A used interface imports every member (see
308                    // `is_interface_member_used`), so promote each member key too:
309                    // members ported to a prelude-host value symbol must resolve to
310                    // the Rust impl, not the deleted Wasm wrapper.
311                    if let TypeKind::Interface {
312                        methods,
313                        properties,
314                        ..
315                    } = &sym.kind
316                    {
317                        for name in methods.keys().chain(properties.keys()) {
318                            self.members
319                                .insert(crate::mangle::extend(&sym.mangled_name, name));
320                        }
321                    }
322                }
323            }
324
325            if !changed {
326                break;
327            }
328        }
329    }
330
331    fn collect_value_symbol(&mut self, value: &crate::ValueSymbol) {
332        match &value.kind {
333            ValueKind::Function { params, ret, .. } => {
334                for p in params {
335                    self.collect_type(&p.ty);
336                }
337                self.collect_type(ret);
338            }
339            ValueKind::Let { ty, .. } | ValueKind::Const { ty, .. } => {
340                self.collect_type(ty);
341            }
342        }
343    }
344
345    fn collect_type_symbol(&mut self, ty_sym: &crate::TypeSymbol) {
346        match &ty_sym.kind {
347            TypeKind::Alias { ty, .. } => self.collect_type(ty),
348            TypeKind::Interface {
349                properties,
350                methods,
351                ..
352            } => {
353                for sig in properties.values() {
354                    self.collect_type(&sig.ty);
355                }
356                for sig in methods.values() {
357                    for p in &sig.params {
358                        self.collect_type(&p.ty);
359                    }
360                    self.collect_type(&sig.ret);
361                }
362            }
363            TypeKind::Class {
364                fields,
365                methods,
366                statics,
367                static_fields,
368                constructor,
369                ..
370            } => {
371                for sig in fields.values() {
372                    self.collect_type(&sig.ty);
373                }
374                for sig in methods.values().chain(statics.values()) {
375                    for p in &sig.params {
376                        self.collect_type(&p.ty);
377                    }
378                    self.collect_type(&sig.ret);
379                }
380                for sig in static_fields.values() {
381                    self.collect_type(&sig.ty);
382                }
383                for p in constructor {
384                    self.collect_type(&p.ty);
385                }
386            }
387            TypeKind::NumberEnum { .. } | TypeKind::StringEnum { .. } => {}
388        }
389    }
390
391    /// The import half only. `CodegenAnalysis` calls this through its own
392    /// `visit_type` funnel, which also collects the type's closure shapes.
393    pub(crate) fn collect_type(&mut self, ty: &Type) {
394        if let Some(shape) = Shape::from_type(ty) {
395            // A shape carries its member types, so its subtree was collected
396            // with it. Walking it again for every expression that has the type
397            // is quadratic in the type's depth.
398            if !self.collected_shapes.insert(shape.clone()) {
399                return;
400            }
401            if matches!(shape, Shape::Object { .. }) {
402                self.collect_typed_object_stringify_host_value();
403            }
404            self.shapes.insert(shape);
405        }
406        match ty.peel() {
407            Type::Object { fields, index } => {
408                if let Some(index) = index {
409                    self.collect_type(&index.value);
410                    for helper in ["#getField", "#setField", "#recordValues", "#hasField"] {
411                        self.note_member(crate::mangle::extend(
412                            &crate::mangle::prelude("ObjectConstructor"),
413                            helper,
414                        ));
415                    }
416                }
417                for field in fields.values() {
418                    self.collect_type(&field.ty);
419                }
420            }
421            Type::Array(elem) => self.collect_type(elem),
422            Type::Tuple(elements) => {
423                for elem in elements {
424                    self.collect_type(elem);
425                }
426            }
427            Type::Union(members) => {
428                for member in members {
429                    self.collect_type(member);
430                }
431            }
432            Type::Function { params, ret, .. } => {
433                for p in params {
434                    self.collect_type(p);
435                }
436                self.collect_type(ret);
437            }
438            Type::InterfaceRef { mangled, args, .. }
439            | Type::ClassRef { mangled, args, .. }
440            | Type::AliasRef { mangled, args, .. } => {
441                self.types.insert(mangled.clone());
442                for arg in args {
443                    self.collect_type(arg);
444                }
445            }
446            Type::NumberEnum { mangled, .. } | Type::StringEnum { mangled, .. } => {
447                self.types.insert(mangled.clone());
448            }
449            Type::Alias { ty, .. } | Type::Refined { ty, .. } | Type::Readonly(ty) => {
450                self.collect_type(ty);
451            }
452            Type::BigInt | Type::BigIntLiteral(_) => {
453                self.uses_bigint = true;
454            }
455            Type::Number
456            | Type::NumberLiteral(_)
457            | Type::String
458            | Type::StringLiteral(_)
459            | Type::Uint8Array
460            | Type::Boolean
461            | Type::BooleanLiteral(_)
462            | Type::Null
463            | Type::Undefined
464            | Type::Void
465            | Type::Never
466            | Type::Unknown
467            | Type::Error
468            | Type::TypeVar(_)
469            | Type::GenericParam { .. } => {}
470        }
471    }
472
473    pub(crate) fn collect_json_host_values(&mut self) {
474        for name in [
475            "parse",
476            "parseTypedObject",
477            "parseTypedValue",
478            "stringify",
479            "stringifyPrettyNumber",
480            "stringifyPrettyString",
481            "diagnosticPathField",
482            "diagnosticPathIndex",
483            "diagnosticMismatch",
484            "diagnosticMissingFields",
485            "stringifyTypedObject",
486        ] {
487            self.values
488                .insert(crate::mangle::host(crate::runtime::JSON_MODULE_NAME, name));
489        }
490    }
491
492    pub(crate) fn collect_typed_object_stringify_host_value(&mut self) {
493        self.values.insert(crate::mangle::host(
494            crate::runtime::JSON_MODULE_NAME,
495            "stringifyTypedObject",
496        ));
497    }
498
499    pub(crate) fn collect_bigint_host_value(&mut self, name: &str) {
500        self.uses_bigint = true;
501        self.values.insert(crate::mangle::host(
502            crate::runtime::BIGINT_MODULE_NAME,
503            name,
504        ));
505    }
506
507    /// The string→number parse `+s` lowers to — the same host fn `Number(s)`
508    /// calls.
509    pub(crate) fn collect_number_coercion_host_value(&mut self) {
510        self.values.insert(crate::mangle::host(
511            crate::runtime::NUMBER_MODULE_NAME,
512            "toNumber",
513        ));
514    }
515
516    /// The bigint operator imports a `++`/`--` on a bigint target lowers to.
517    /// The target's own type goes through the caller's type funnel.
518    pub(crate) fn collect_postfix_bigint_ops(&mut self, ty: &Type) {
519        if ty.is_bigint() {
520            self.collect_bigint_host_value("fromNumber");
521            self.collect_bigint_host_value("add");
522            self.collect_bigint_host_value("sub");
523        }
524    }
525
526    fn collect_codegen_prelude_values(&mut self) {
527        for name in [
528            "isFinite",
529            "isNaN",
530            "string_cmp",
531            "string_concat",
532            "string_eq",
533            "vtable_walk_enter",
534            "vtable_walk_leave",
535        ] {
536            self.values.insert(crate::mangle::prelude(name));
537        }
538        for (iface, member) in [
539            ("Boolean", "toJson"),
540            ("Boolean", "toString"),
541            ("Number", "toJson"),
542            ("Number", "toString"),
543            ("RegExpConstructor", "new"),
544            ("String", "toJson"),
545            ("String", "toString"),
546        ] {
547            self.members.insert(crate::mangle::extend(
548                &crate::mangle::prelude(iface),
549                member,
550            ));
551        }
552        // The internal throw sites (bounds, casts, parameter TDZ, assertions)
553        // call the reconstructed class constructors, so these classes must
554        // reconstruct in every module.
555        self.note_type(crate::mangle::prelude("Error"));
556        self.note_type(crate::mangle::prelude("RangeError"));
557        self.note_type(crate::mangle::prelude("ReferenceError"));
558        self.note_type(crate::mangle::prelude("TypeError"));
559    }
560}
561
562fn collect_values<'a>(
563    values: &'a BTreeMap<String, crate::ValueSymbol>,
564    out: &mut BTreeMap<MangledName, &'a crate::ValueSymbol>,
565) {
566    for value in values.values() {
567        out.insert(value.mangled_name.clone(), value);
568    }
569}
570
571fn collect_namespace_values<'a>(
572    namespaces: &'a BTreeMap<String, crate::NamespaceSymbol>,
573    out: &mut BTreeMap<MangledName, &'a crate::ValueSymbol>,
574) {
575    for namespace in namespaces.values() {
576        collect_values(&namespace.values, out);
577        collect_namespace_values(&namespace.namespaces, out);
578    }
579}
580
581fn collect_used_namespace_values<'a>(
582    usage: &DependencyUsage,
583    package: &'a PackageDeclaration,
584    parent_path: &str,
585    namespaces: &'a BTreeMap<String, NamespaceSymbol>,
586    out: &mut Vec<DependencyValue<'a>>,
587) {
588    for (ns_name, namespace) in namespaces {
589        let path = if parent_path.is_empty() {
590            ns_name.clone()
591        } else {
592            format!("{parent_path}#{ns_name}")
593        };
594        for (local_name, symbol) in &namespace.values {
595            if usage.is_value_used(&symbol.mangled_name) {
596                out.push(DependencyValue {
597                    package,
598                    export_name: Cow::Owned(format!("{path}#{local_name}")),
599                    symbol,
600                });
601            }
602        }
603        collect_used_namespace_values(usage, package, &path, &namespace.namespaces, out);
604    }
605}
606
607fn collect_used_types<'a>(
608    usage: &DependencyUsage,
609    package: &'a PackageDeclaration,
610    types: &'a BTreeMap<String, TypeSymbol>,
611    out: &mut Vec<DependencyType<'a>>,
612) {
613    for (name, symbol) in types {
614        if usage.is_type_reachable(symbol) {
615            out.push(DependencyType {
616                package,
617                name,
618                symbol,
619                full: usage.is_type_fully_used(symbol),
620            });
621        }
622    }
623}
624
625fn collect_used_namespace_types<'a>(
626    usage: &DependencyUsage,
627    package: &'a PackageDeclaration,
628    namespaces: &'a BTreeMap<String, NamespaceSymbol>,
629    out: &mut Vec<DependencyType<'a>>,
630) {
631    for namespace in namespaces.values() {
632        collect_used_types(usage, package, &namespace.types, out);
633        collect_used_namespace_types(usage, package, &namespace.namespaces, out);
634    }
635}