Skip to main content

submilli_engine/codegen/
box_types.rs

1//! Per-program GC box wrapper types for captured-binding storage.
2
3use std::collections::HashSet;
4
5use wasm_encoder::{
6    CompositeInnerType, CompositeType, FieldType, StorageType, StructType, SubType, TypeSection,
7    ValType,
8};
9
10use crate::codegen::symbol_table::SymbolTable;
11use crate::{
12    ClosureBody, ExprId, StmtId, Type, TypedAst, TypedExprKind, TypedParam, TypedStmtKind,
13};
14
15pub fn collect(
16    ta: &TypedAst,
17    symbols: &SymbolTable,
18) -> Result<Vec<ValType>, crate::compiler_error::CompilerFailure> {
19    let mut state = Collector {
20        ta,
21        symbols,
22        seen: HashSet::new(),
23        order: Vec::new(),
24    };
25    let function_bodies: Vec<(Vec<TypedParam>, StmtId)> = ta
26        .functions
27        .iter()
28        .map(|f| (f.params.clone(), f.body))
29        .collect();
30    for (params, body) in function_bodies {
31        for p in &params {
32            if p.boxed {
33                state.note(&p.ty)?;
34            }
35        }
36        state.walk_stmt(body)?;
37    }
38    let stmt_ids: Vec<StmtId> = ta.top_level_statements.clone();
39    for sid in stmt_ids {
40        state.walk_stmt(sid)?;
41    }
42    // Class member bodies and field initializers: a closure declared in either
43    // can capture a mutable binding, and its box cell needs a type just like a
44    // top-level function's.
45    for member in class_member_params(ta) {
46        for p in member {
47            if p.boxed {
48                state.note(&p.ty)?;
49            }
50        }
51    }
52    for body in ta.class_body_roots() {
53        state.walk_stmt(body)?;
54    }
55    for init in ta.class_field_initializers() {
56        state.walk_expr(init)?;
57    }
58    Ok(state.order)
59}
60
61/// Parameter lists of every class member that has one, so a captured-and-boxed
62/// member parameter registers its box type the way a function's does.
63fn class_member_params(ta: &TypedAst) -> Vec<Vec<TypedParam>> {
64    let mut out = Vec::new();
65    for ty_decl in &ta.types {
66        let crate::TypedTypeDecl::Class(c) = ty_decl else {
67            continue;
68        };
69        if let Some(ctor) = &c.constructor {
70            out.push(ctor.params.clone());
71        }
72        out.extend(c.methods.iter().map(|m| m.params.clone()));
73        for a in &c.accessors {
74            if let crate::TypedClassAccessor::Setter { param, .. } = a {
75                out.push(vec![param.clone()]);
76            }
77        }
78    }
79    out
80}
81
82struct Collector<'a> {
83    ta: &'a TypedAst,
84    symbols: &'a SymbolTable,
85    seen: HashSet<ValType>,
86    order: Vec<ValType>,
87}
88
89impl Collector<'_> {
90    /// Keyed on `slot_value_type`; see [`SymbolTable::box_type_idx`].
91    fn note(&mut self, ty: &Type) -> Result<(), crate::compiler_error::CompilerFailure> {
92        let valtype = self.symbols.slot_value_type(ty)?;
93        if self.seen.insert(valtype) {
94            self.order.push(valtype);
95        };
96        Ok(())
97    }
98
99    fn walk_stmt(&mut self, id: StmtId) -> Result<(), crate::compiler_error::CompilerFailure> {
100        let _: () = match &self
101            .ta
102            .try_stmt(id)
103            .map_err(crate::codegen::arena_failure)?
104            .kind
105        {
106            TypedStmtKind::Block(stmts) => {
107                for &s in stmts {
108                    self.walk_stmt(s)?;
109                }
110            }
111            TypedStmtKind::Let {
112                ty, value, boxed, ..
113            } => {
114                if *boxed {
115                    self.note(ty)?;
116                }
117                self.walk_expr(*value)?;
118            }
119            TypedStmtKind::Const { value, .. } => self.walk_expr(*value)?,
120            TypedStmtKind::If {
121                condition,
122                then_block,
123                else_block,
124            } => {
125                self.walk_expr(*condition)?;
126                self.walk_stmt(*then_block)?;
127                if let Some(eb) = else_block {
128                    self.walk_stmt(*eb)?;
129                }
130            }
131            TypedStmtKind::While { condition, body } => {
132                self.walk_expr(*condition)?;
133                self.walk_stmt(*body)?;
134            }
135            TypedStmtKind::For {
136                init,
137                condition,
138                update,
139                body,
140            } => {
141                if let Some(i) = init {
142                    self.walk_stmt(*i)?;
143                }
144                if let Some(c) = condition {
145                    self.walk_expr(*c)?;
146                }
147                if let Some(u) = update {
148                    self.walk_stmt(*u)?;
149                }
150                self.walk_stmt(*body)?;
151            }
152            TypedStmtKind::ForOf { iter, body, .. } => {
153                self.walk_expr(*iter)?;
154                self.walk_stmt(*body)?;
155            }
156            TypedStmtKind::DoWhile { body, condition } => {
157                self.walk_stmt(*body)?;
158                self.walk_expr(*condition)?;
159            }
160            TypedStmtKind::Switch {
161                discriminant,
162                cases,
163                default,
164                ..
165            } => {
166                self.walk_expr(*discriminant)?;
167                for comparison in cases
168                    .iter()
169                    .flat_map(crate::TypedSwitchCase::label_comparisons)
170                {
171                    self.walk_expr(comparison)?;
172                }
173                for case in cases {
174                    self.walk_stmt(case.body)?;
175                }
176                if let Some(d) = default {
177                    self.walk_stmt(*d)?;
178                }
179            }
180            TypedStmtKind::Break | TypedStmtKind::Continue | TypedStmtKind::ReboxLocal { .. } => {}
181            TypedStmtKind::Return(value) => {
182                if let Some(v) = value {
183                    self.walk_expr(*v)?;
184                }
185            }
186            TypedStmtKind::Expr(e) => self.walk_expr(*e)?,
187            TypedStmtKind::AssignLocal { value, .. }
188            | TypedStmtKind::AssignGlobal { value, .. } => self.walk_expr(*value)?,
189            TypedStmtKind::AssignField {
190                receiver, value, ..
191            } => {
192                self.walk_expr(*receiver)?;
193                self.walk_expr(*value)?;
194            }
195            TypedStmtKind::AssignIndex {
196                receiver,
197                index,
198                value,
199                ..
200            } => {
201                self.walk_expr(*receiver)?;
202                self.walk_expr(*index)?;
203                self.walk_expr(*value)?;
204            }
205            TypedStmtKind::NarrowRegion { source, body, .. } => {
206                self.walk_expr(*source)?;
207                self.walk_stmt(*body)?;
208            }
209            TypedStmtKind::Throw { value } => self.walk_expr(*value)?,
210            TypedStmtKind::Try {
211                body,
212                catches,
213                finally,
214            } => {
215                self.walk_stmt(*body)?;
216                for c in catches {
217                    if c.boxed {
218                        self.note(&c.ty)?;
219                    }
220                    self.walk_stmt(c.body)?;
221                }
222                if let Some(f) = finally {
223                    self.walk_stmt(*f)?;
224                }
225            }
226        };
227        Ok(())
228    }
229
230    fn walk_expr(&mut self, id: ExprId) -> Result<(), crate::compiler_error::CompilerFailure> {
231        let _: () = match &self
232            .ta
233            .try_expr(id)
234            .map_err(crate::codegen::arena_failure)?
235            .kind
236        {
237            TypedExprKind::Closure {
238                params,
239                captured,
240                body,
241                ..
242            } => {
243                for p in params {
244                    if p.boxed {
245                        self.note(&p.ty)?;
246                    }
247                }
248                for c in captured {
249                    if c.boxed {
250                        self.note(&c.ty)?;
251                    }
252                }
253                match *body {
254                    ClosureBody::Expr(e) => self.walk_expr(e)?,
255                    ClosureBody::Block(b) => self.walk_stmt(b)?,
256                }
257            }
258            TypedExprKind::Binary { lhs, rhs, .. } => {
259                self.walk_expr(*lhs)?;
260                self.walk_expr(*rhs)?;
261            }
262            TypedExprKind::EffectThen { effect, result } => {
263                self.walk_expr(*effect)?;
264                self.walk_expr(*result)?;
265            }
266            TypedExprKind::Sequence { stmts, result } => {
267                for &stmt in stmts {
268                    self.walk_stmt(stmt)?;
269                }
270                self.walk_expr(*result)?;
271            }
272            TypedExprKind::Unary { operand, .. } => self.walk_expr(*operand)?,
273            TypedExprKind::TypeofTag { value, .. } | TypedExprKind::InstanceOf { value, .. } => {
274                self.walk_expr(*value)?;
275            }
276            TypedExprKind::Call { args, .. }
277            | TypedExprKind::McpCall { args, .. }
278            | TypedExprKind::SuperCtorCall { args, .. }
279            | TypedExprKind::SuperMethodCall { args, .. } => {
280                for &a in args {
281                    self.walk_expr(a)?;
282                }
283            }
284            TypedExprKind::CallClosure { callee, args } => {
285                self.walk_expr(*callee)?;
286                for &a in args {
287                    self.walk_expr(a)?;
288                }
289            }
290            TypedExprKind::GenericCall { args, .. } => {
291                for a in args {
292                    self.walk_expr(a.expr)?;
293                }
294            }
295            TypedExprKind::MethodCall { receiver, args, .. } => {
296                self.walk_expr(*receiver)?;
297                for &a in args {
298                    self.walk_expr(a)?;
299                }
300            }
301            TypedExprKind::GenericMethodCall { receiver, args, .. } => {
302                self.walk_expr(*receiver)?;
303                for a in args {
304                    self.walk_expr(a.expr)?;
305                }
306            }
307            TypedExprKind::IntrinsicCall { args, .. } => {
308                for &a in args {
309                    self.walk_expr(a)?;
310                }
311            }
312            TypedExprKind::ObjectLiteral { members, .. } => {
313                for member in members {
314                    for expression in member.expressions() {
315                        self.walk_expr(expression)?;
316                    }
317                }
318            }
319            TypedExprKind::ArrayLiteral { elements, .. } => {
320                for e in elements {
321                    self.walk_expr(e.expr_id())?;
322                }
323            }
324            TypedExprKind::TupleLiteral { elements, .. } => {
325                for &e in elements {
326                    self.walk_expr(e)?;
327                }
328            }
329            TypedExprKind::FieldAccess { receiver, .. }
330            | TypedExprKind::InterfacePropertyAccess { receiver, .. } => {
331                self.walk_expr(*receiver)?;
332            }
333            TypedExprKind::IndexAccess { receiver, index } => {
334                self.walk_expr(*receiver)?;
335                self.walk_expr(*index)?;
336            }
337            TypedExprKind::Narrowed { source, inner, .. } => {
338                self.walk_expr(*source)?;
339                self.walk_expr(*inner)?;
340            }
341            TypedExprKind::Ternary { cond, then_, else_ } => {
342                self.walk_expr(*cond)?;
343                self.walk_expr(*then_)?;
344                self.walk_expr(*else_)?;
345            }
346            TypedExprKind::NullishCoalesce { lhs, rhs } => {
347                self.walk_expr(*lhs)?;
348                self.walk_expr(*rhs)?;
349            }
350            TypedExprKind::OptionalChain { base, parts } => {
351                self.walk_expr(*base)?;
352                for part in parts {
353                    match part {
354                        crate::TypedChainPart::Index { idx, .. } => self.walk_expr(*idx)?,
355                        crate::TypedChainPart::Call { args, .. }
356                        | crate::TypedChainPart::MethodCall { args, .. } => {
357                            for a in args {
358                                self.walk_expr(*a)?;
359                            }
360                        }
361                        crate::TypedChainPart::Field { .. }
362                        | crate::TypedChainPart::InterfaceProperty { .. }
363                        | crate::TypedChainPart::NonNull { .. } => {}
364                    }
365                }
366            }
367            TypedExprKind::PostfixUnary { target, .. } => match target {
368                crate::PostfixTarget::Local {
369                    boxed, target_ty, ..
370                } => {
371                    if *boxed {
372                        self.note(target_ty)?;
373                    }
374                }
375                crate::PostfixTarget::Global { .. } => {}
376                crate::PostfixTarget::Field { receiver, .. } => self.walk_expr(*receiver)?,
377                crate::PostfixTarget::Index {
378                    receiver, index, ..
379                } => {
380                    self.walk_expr(*receiver)?;
381                    self.walk_expr(*index)?;
382                }
383            },
384            TypedExprKind::NonNullAssert { value } | TypedExprKind::Cast { value, .. } => {
385                self.walk_expr(*value)?;
386            }
387            TypedExprKind::Number(_)
388            | TypedExprKind::BigInt(_)
389            | TypedExprKind::String(_)
390            | TypedExprKind::Boolean(_)
391            | TypedExprKind::Null
392            | TypedExprKind::Undefined
393            | TypedExprKind::This
394            | TypedExprKind::Regex { .. }
395            | TypedExprKind::LocalRef { .. }
396            | TypedExprKind::LocalNarrowRef { .. }
397            | TypedExprKind::GlobalRef { .. }
398            | TypedExprKind::FunctionRef { .. }
399            | TypedExprKind::NumberEnumMember { .. }
400            | TypedExprKind::StringEnumMember { .. } => {}
401        };
402        Ok(())
403    }
404}
405
406/// `is_final: false` so boxes can gain subtypes; no `supertype_idx` because they don't participate in `$Object` dispatch.
407pub fn emit(
408    value_types_to_emit: &[ValType],
409    types: &mut TypeSection,
410    symbols: &mut SymbolTable,
411    next_type_idx: &mut u32,
412) {
413    for val in value_types_to_emit {
414        let field = FieldType {
415            element_type: StorageType::Val(match *val {
416                ValType::Ref(mut reference) => {
417                    reference.nullable = true;
418                    ValType::Ref(reference)
419                }
420                value => value,
421            }),
422            mutable: true,
423        };
424        types.ty().subtype(&SubType {
425            is_final: false,
426            supertype_idx: None,
427            composite_type: CompositeType {
428                inner: CompositeInnerType::Struct(StructType {
429                    fields: vec![
430                        field,
431                        FieldType {
432                            element_type: StorageType::Val(ValType::I32),
433                            mutable: true,
434                        },
435                    ]
436                    .into_boxed_slice(),
437                }),
438                shared: false,
439                descriptor: None,
440                describes: None,
441            },
442        });
443        symbols.record_box_type(*val, *next_type_idx);
444        *next_type_idx += 1;
445    }
446}