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