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