Skip to main content

submilli_engine/codegen/
closures.rs

1//! Per-program closure type collection + emission.
2//!
3//! A `$closure_<sig>` rec-group type is needed wherever a function-typed value
4//! occupies a slot — which is wider than the set of closures the module builds,
5//! since the closure may be built by a caller in another package. The module's
6//! own closure literals and adapters cover what it constructs; three collectors
7//! cover the shapes it only names:
8//!
9//! * `CodegenAnalysis`'s type funnel — every type the module *mentions*, after
10//!   substitution: expression types, slot annotations, cast targets, plus the
11//!   sigs the shape-dispatch paths build for themselves — a method call's, from
12//!   its own arity, and a property access's `() -> R` / `(W) -> void` accessor
13//!   branch, which is emitted whether or not an accessor is declared.
14//! * [`class_member_sigs`] — local class members, whose ABI is fixed by the
15//!   declaration whether or not the module mentions them.
16//! * [`collect_from_dependencies`] — the imported surface of other packages.
17//!
18//! Local `interface` and `type` declarations need no collector of their own: an
19//! object shape's payload is a uniform `(ref null $Object)` array and casts test
20//! function fields signature-blind, so a member's type never becomes a Wasm
21//! type on its own. The one thing a member *does* fix is the sig its dispatch
22//! goes through, and the funnel takes that from the call site — where the
23//! generics are already substituted, which the declaration's spelling isn't.
24//!
25//! Missing a sig is an internal compiler failure, so these over-collect
26//! freely: `emit_arity_closures` dedups, and an unused shape costs one func
27//! type plus one struct type.
28
29use std::collections::{BTreeMap, BTreeSet};
30
31use wasm_encoder::{
32    CompositeInnerType, CompositeType, FieldType, FuncType, HeapType, RefType, StorageType,
33    StructType, SubType, TypeSection, ValType,
34};
35
36use crate::codegen::dependency_usage::{DependencyType, DependencyUsage};
37use crate::codegen::intrinsics::IntrinsicTypeIndices;
38use crate::codegen::symbol_table::SymbolTable;
39use crate::{
40    CapturedVar, ClosureBody, Dispatch, ExprId, Shape, Type, TypeKind, TypedAst, TypedParam,
41    ValueKind, ValueSymbol,
42};
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
45pub struct ClosureSig {
46    pub arity: u8,
47    pub is_void: bool,
48}
49
50impl ClosureSig {
51    /// The closure ABI reduces a signature to these two facts, so callers that
52    /// know an arity and a return type need not build a `Type::Function`.
53    pub fn of(arity: usize, ret: &Type) -> Result<Self, crate::compiler_error::CompilerFailure> {
54        let arity = crate::compiler_limits::checked_closure_arity(arity).map_err(|error| {
55            crate::compiler_error::CompilerFailure::Limit {
56                stage: crate::compiler_error::CompilerStage::Codegen,
57                span: None,
58                message: error.to_string(),
59                help: vec!["group arguments into an object or a rest parameter".into()],
60            }
61        })?;
62        Ok(Self {
63            arity,
64            is_void: ret.is_void(),
65        })
66    }
67}
68
69pub fn classify(sig: &Type) -> Result<ClosureSig, crate::compiler_error::CompilerFailure> {
70    let Type::Function { params, ret, .. } = sig.lowered() else {
71        return Err(super::internal_failure(
72            "closure classification requires a function type",
73        ));
74    };
75    ClosureSig::of(params.len(), ret)
76}
77
78#[derive(Clone, Debug)]
79pub struct ClosureMeta {
80    pub this_type: Option<Type>,
81    pub self_name: Option<crate::Ident>,
82    pub runtime_generics: Vec<String>,
83    pub expr_id: ExprId,
84    pub signature: Type,
85    pub captured: Vec<CapturedVar>,
86    pub params: Vec<TypedParam>,
87    pub body: ClosureBody,
88    pub return_type: Type,
89}
90
91/// Consumer modules must re-declare these as a local closure rec group so
92/// import signatures and cast sites using `(ref $closure_<sig>)` resolve
93/// under WasmGC structural canonicalization.
94pub fn collect_from_dependencies<'a>(
95    values: impl IntoIterator<Item = &'a ValueSymbol>,
96    shapes: impl IntoIterator<Item = &'a Shape>,
97    types: impl IntoIterator<Item = &'a DependencyType<'a>>,
98    usage: &DependencyUsage,
99) -> Result<Vec<ClosureSig>, crate::compiler_error::CompilerFailure> {
100    let mut out: Vec<ClosureSig> = Vec::new();
101    for value_sym in values {
102        let ValueKind::Function { params, ret, .. } = &value_sym.kind else {
103            continue;
104        };
105        for p in params {
106            walk_type(&p.ty, &mut out)?;
107        }
108        walk_type(ret, &mut out)?;
109    }
110    for shape in shapes {
111        match shape {
112            crate::Shape::Object { fields, index } => {
113                if let Some(index) = index {
114                    walk_type(&index.value, &mut out)?;
115                }
116                for f in fields.values() {
117                    walk_type(&f.ty, &mut out)?;
118                }
119            }
120            crate::Shape::Array(elem) => walk_type(elem, &mut out)?,
121            crate::Shape::Tuple(elements) => {
122                for e in elements {
123                    walk_type(e, &mut out)?;
124                }
125            }
126            crate::Shape::Union(members) => {
127                for m in members {
128                    walk_type(m, &mut out)?;
129                }
130            }
131        }
132    }
133    for dependency_type in types {
134        let ty_sym = dependency_type.symbol;
135        if let TypeKind::Interface {
136            dispatch,
137            methods,
138            properties,
139            ..
140        } = &ty_sym.kind
141        {
142            if *dispatch == Dispatch::VTable {
143                for (name, sig) in methods {
144                    if usage.is_interface_member_used(ty_sym, name) {
145                        dispatched_member_sigs(
146                            sig.params.iter().map(|p| &p.ty),
147                            &sig.ret,
148                            &mut out,
149                        )?;
150                    }
151                }
152                for (name, prop) in properties {
153                    if usage.is_interface_member_used(ty_sym, name) {
154                        walk_type(&prop.ty, &mut out)?;
155                    }
156                }
157            } else {
158                for (name, sig) in methods {
159                    if usage.is_interface_member_used(ty_sym, name) {
160                        walk_signature_types(sig.params.iter().map(|p| &p.ty), &sig.ret, &mut out)?;
161                    }
162                }
163                for (name, prop) in properties {
164                    if usage.is_interface_member_used(ty_sym, name) {
165                        walk_type(&prop.ty, &mut out)?;
166                    }
167                }
168            }
169        } else if let TypeKind::Class {
170            accessors,
171            methods,
172            constructor,
173            fields,
174            statics,
175            static_fields,
176            ..
177        } = &ty_sym.kind
178        {
179            // An imported class's members carry their own closure sigs: each
180            // method becomes a payload closure for interface-typed dispatch,
181            // and any closure-typed parameter or return needs its rec-group
182            // type to exist here even though the closure itself is built in
183            // the producing package.
184            for sig in methods.values() {
185                if sig.generics.is_empty() {
186                    dispatched_member_sigs(sig.params.iter().map(|p| &p.ty), &sig.ret, &mut out)?;
187                }
188            }
189            for p in constructor {
190                walk_type(&p.ty, &mut out)?;
191            }
192            for f in fields.values() {
193                walk_type(&f.ty, &mut out)?;
194            }
195            for sig in statics.values() {
196                walk_signature_types(sig.params.iter().map(|p| &p.ty), &sig.ret, &mut out)?;
197            }
198            for f in static_fields.values() {
199                walk_type(&f.ty, &mut out)?;
200            }
201            accessor_sigs(accessors, &mut out)?;
202        }
203    }
204    Ok(out)
205}
206
207/// Closure signatures for each local class's members — its own, and the ones it
208/// inherits from a *dependency* class. An instance flowing through an
209/// interface-typed receiver carries every method in its chain as a
210/// `$closure_<sig>` value in the object-fields payload.
211pub fn class_member_sigs(
212    ta: &TypedAst,
213    dependencies: &[&crate::PackageDeclaration],
214) -> Result<Vec<ClosureSig>, crate::compiler_error::CompilerFailure> {
215    let mut out: Vec<ClosureSig> = Vec::new();
216    for decl in &ta.types {
217        let crate::TypedTypeDecl::Class(class) = decl else {
218            continue;
219        };
220        for f in &class.fields {
221            walk_type(&f.ty, &mut out)?;
222        }
223        for p in class.effective_ctor_params() {
224            walk_type(&p.ty, &mut out)?;
225        }
226        for method in &class.methods {
227            if method.generics.is_empty() {
228                dispatched_member_sigs(
229                    method.params.iter().map(|p| &p.ty),
230                    &method.return_type,
231                    &mut out,
232                )
233                .map_err(|error| error.with_span(method.name.span))?;
234            }
235        }
236        class_accessor_sigs(&class.accessors, &mut out)?;
237        inherited_dependency_sigs(class.extends.clone(), dependencies, &mut out)?;
238    }
239    Ok(out)
240}
241
242/// Every function-typed position nested inside a member's own signature.
243fn walk_signature_types<'a>(
244    params: impl IntoIterator<Item = &'a Type>,
245    ret: &Type,
246    out: &mut Vec<ClosureSig>,
247) -> Result<(), crate::compiler_error::CompilerFailure> {
248    for ty in params {
249        walk_type(ty, out)?;
250    }
251    walk_type(ret, out)?;
252
253    Ok(())
254}
255
256/// The sig the member itself dispatches through, plus everything
257/// [`walk_signature_types`] finds. A member carried in an object-fields payload
258/// needs both.
259fn dispatched_member_sigs<'a>(
260    params: impl ExactSizeIterator<Item = &'a Type>,
261    ret: &Type,
262    out: &mut Vec<ClosureSig>,
263) -> Result<(), crate::compiler_error::CompilerFailure> {
264    out.push(ClosureSig::of(params.len(), ret)?);
265    walk_signature_types(params, ret, out)?;
266
267    Ok(())
268}
269
270/// An accessor lowers to a getter `() -> R` or setter `(W) -> void` method,
271/// which dispatches like any other member.
272fn accessor_sigs(
273    accessors: &[crate::AccessorSig],
274    out: &mut Vec<ClosureSig>,
275) -> Result<(), crate::compiler_error::CompilerFailure> {
276    for acc in accessors {
277        match acc {
278            crate::AccessorSig::Getter { ret_ty, .. } => {
279                dispatched_member_sigs(std::iter::empty(), ret_ty, out)?;
280            }
281            crate::AccessorSig::Setter { param, .. } => {
282                dispatched_member_sigs(std::iter::once(&param.ty), &Type::Void, out)?;
283            }
284        }
285    }
286    Ok(())
287}
288
289/// The local-class mirror of [`accessor_sigs`] — same lowering, different enum.
290fn class_accessor_sigs(
291    accessors: &[crate::TypedClassAccessor],
292    out: &mut Vec<ClosureSig>,
293) -> Result<(), crate::compiler_error::CompilerFailure> {
294    for acc in accessors {
295        match acc {
296            crate::TypedClassAccessor::Getter { ret_ty, .. } => {
297                dispatched_member_sigs(std::iter::empty(), ret_ty, out)?;
298            }
299            crate::TypedClassAccessor::Setter { param, .. } => {
300                dispatched_member_sigs(std::iter::once(&param.ty), &Type::Void, out)?;
301            }
302        }
303    }
304    Ok(())
305}
306
307/// Closure sigs for the methods a local class inherits from *dependency*
308/// classes. Its instances carry those in their payload too, and neither
309/// [`collect_from_dependencies`] nor the local walk above reaches them: a class
310/// named solely in an `extends` clause is not in `dependency_types`.
311fn inherited_dependency_sigs(
312    mut parent: Option<crate::MangledName>,
313    dependencies: &[&crate::PackageDeclaration],
314    out: &mut Vec<ClosureSig>,
315) -> Result<(), crate::compiler_error::CompilerFailure> {
316    let mut seen: std::collections::BTreeSet<crate::MangledName> =
317        std::collections::BTreeSet::new();
318    while let Some(mangled) = parent {
319        if !seen.insert(mangled.clone()) {
320            return Ok(()); // malformed cyclic chain; the typechecker reports it
321        }
322        let Some(sym) = dependencies
323            .iter()
324            .flat_map(|d| d.types.values())
325            .find(|t| t.mangled_name == mangled)
326        else {
327            return Ok(());
328        };
329        let crate::TypeKind::Class {
330            methods,
331            accessors,
332            extends,
333            ..
334        } = &sym.kind
335        else {
336            return Ok(());
337        };
338        for sig in methods.values() {
339            if sig.generics.is_empty() {
340                dispatched_member_sigs(sig.params.iter().map(|p| &p.ty), &sig.ret, out)?;
341            }
342        }
343        accessor_sigs(accessors, out)?;
344        parent = extends.as_ref().map(|e| e.parent.clone());
345    }
346    Ok(())
347}
348
349/// Every function-typed position reachable from `ty`.
350pub(crate) fn walk_type(
351    ty: &Type,
352    out: &mut Vec<ClosureSig>,
353) -> Result<(), crate::compiler_error::CompilerFailure> {
354    match ty {
355        Type::Refined { ty, .. } | Type::Readonly(ty) => walk_type(ty, out)?,
356        Type::Function { params, ret, .. } => {
357            out.push(classify(ty)?);
358            for p in params {
359                walk_type(p, out)?;
360            }
361            walk_type(ret, out)?;
362        }
363        Type::Array(elem) => walk_type(elem, out)?,
364        Type::Tuple(elements) => {
365            for e in elements {
366                walk_type(e, out)?;
367            }
368        }
369        Type::Object { fields, index } => {
370            if let Some(index) = index {
371                walk_type(&index.value, out)?;
372            }
373            for f in fields.values() {
374                walk_type(&f.ty, out)?;
375            }
376        }
377        Type::InterfaceRef { args, .. } | Type::ClassRef { args, .. } => {
378            for a in args {
379                walk_type(a, out)?;
380            }
381        }
382        // A recursion back-edge has no inline body to walk — its
383        // function-typed positions were collected from the carrying
384        // `Alias`'s body. Args only (like `InterfaceRef`), which also
385        // stops the otherwise-infinite recursion.
386        Type::AliasRef { args, .. } => {
387            for a in args {
388                walk_type(a, out)?;
389            }
390        }
391        Type::Union(members) => {
392            for m in members {
393                walk_type(m, out)?;
394            }
395        }
396        Type::Alias { ty: inner, .. } => walk_type(inner, out)?,
397        Type::Number
398        | Type::NumberLiteral(_)
399        | Type::BigInt
400        | Type::BigIntLiteral(_)
401        | Type::Boolean
402        | Type::BooleanLiteral(_)
403        | Type::String
404        | Type::StringLiteral(_)
405        | Type::Uint8Array
406        | Type::Void
407        | Type::Never
408        | Type::Null
409        | Type::Undefined
410        | Type::Error
411        | Type::TypeVar(_)
412        | Type::GenericParam { .. }
413        | Type::Unknown
414        | Type::NonPrimitive
415        | Type::NumberEnum { .. }
416        | Type::StringEnum { .. } => {}
417    };
418    Ok(())
419}
420
421pub fn emit_func_and_struct_types<I>(
422    signatures: I,
423    types: &mut TypeSection,
424    symbols: &mut SymbolTable,
425    next_type_idx: &mut u32,
426) -> Result<(), crate::compiler_error::CompilerFailure>
427where
428    I: IntoIterator<Item = ClosureSig>,
429{
430    let intrinsics = symbols
431        .intrinsic_type_indices()
432        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared by codegen entry"))?;
433    let signatures = signatures.into_iter().flat_map(|sig| {
434        [
435            sig,
436            ClosureSig {
437                is_void: !sig.is_void,
438                ..sig
439            },
440        ]
441    });
442    let assigned = emit_arity_closures(signatures, types, intrinsics, next_type_idx)?;
443    super::closure_coercions::emit_vtable_type(types, symbols, next_type_idx)?;
444    for (sig, (fn_idx, struct_idx)) in assigned {
445        symbols.record_closure_func_type(sig, fn_idx);
446        symbols.record_closure_struct_type(sig, struct_idx);
447        symbols.record_struct_supertype(struct_idx, closure_struct_supertype(intrinsics));
448    }
449    Ok(())
450}
451
452/// Structural canonicalization unifies prelude and consumer copies at instantiation time.
453pub fn emit_arity_closures<I>(
454    signatures: I,
455    types: &mut TypeSection,
456    intrinsics: IntrinsicTypeIndices,
457    next_type_idx: &mut u32,
458) -> Result<BTreeMap<ClosureSig, (u32, u32)>, crate::compiler_error::CompilerFailure>
459where
460    I: IntoIterator<Item = ClosureSig>,
461{
462    let mut seen: BTreeSet<ClosureSig> = BTreeSet::new();
463    let unique: Vec<ClosureSig> = signatures
464        .into_iter()
465        .filter(|sig| seen.insert(*sig))
466        .collect();
467
468    ensure_index_capacity(*next_type_idx, unique.len().checked_mul(2), 0)?;
469    let mut assigned: BTreeMap<ClosureSig, (u32, u32)> = BTreeMap::new();
470
471    let mut fn_indices: Vec<u32> = Vec::with_capacity(unique.len());
472    for sig in &unique {
473        let func_ty = closure_func_type(*sig, intrinsics);
474        types.ty().subtype(&SubType {
475            is_final: false,
476            supertype_idx: None,
477            composite_type: CompositeType {
478                inner: CompositeInnerType::Func(func_ty),
479                shared: false,
480                descriptor: None,
481                describes: None,
482            },
483        });
484        fn_indices.push(*next_type_idx);
485        *next_type_idx += 1;
486    }
487
488    for (sig, &fn_idx) in unique.iter().zip(fn_indices.iter()) {
489        let fields = vec![
490            FieldType {
491                element_type: StorageType::Val(ValType::Ref(RefType {
492                    nullable: false,
493                    heap_type: HeapType::Concrete(intrinsics.vtable),
494                })),
495                mutable: false,
496            },
497            FieldType {
498                element_type: StorageType::Val(ValType::Ref(RefType {
499                    nullable: false,
500                    heap_type: HeapType::Concrete(fn_idx),
501                })),
502                mutable: false,
503            },
504            FieldType {
505                element_type: StorageType::Val(ValType::Ref(RefType {
506                    nullable: false,
507                    heap_type: HeapType::ANY,
508                })),
509                mutable: false,
510            },
511            FieldType {
512                element_type: StorageType::Val(ValType::I64),
513                mutable: true,
514            },
515        ];
516        types.ty().subtype(&SubType {
517            is_final: false,
518            supertype_idx: Some(closure_struct_supertype(intrinsics)),
519            composite_type: CompositeType {
520                inner: CompositeInnerType::Struct(StructType {
521                    fields: fields.into_boxed_slice(),
522                }),
523                shared: false,
524                descriptor: None,
525                describes: None,
526            },
527        });
528        let struct_idx = *next_type_idx;
529        *next_type_idx += 1;
530        assigned.insert(*sig, (fn_idx, struct_idx));
531    }
532
533    Ok(assigned)
534}
535
536/// Must run after `box_types::emit` so env-field lookups for boxed captures resolve.
537pub fn emit_env_types(
538    metas: &[ClosureMeta],
539    types: &mut TypeSection,
540    symbols: &mut SymbolTable,
541    next_type_idx: &mut u32,
542) -> Result<(), crate::compiler_error::CompilerFailure> {
543    ensure_index_capacity(*next_type_idx, Some(metas.len()), 2)?;
544    let string = symbols
545        .string_type_idx()
546        .ok_or_else(|| crate::codegen::internal_failure("string intrinsic"))?;
547    types.ty().struct_([
548        FieldType {
549            element_type: StorageType::Val(ValType::Ref(RefType {
550                nullable: true,
551                heap_type: HeapType::ANY,
552            })),
553            mutable: false,
554        },
555        FieldType {
556            element_type: StorageType::Val(ValType::Ref(RefType {
557                nullable: false,
558                heap_type: HeapType::Concrete(string),
559            })),
560            mutable: false,
561        },
562        FieldType {
563            element_type: StorageType::Val(ValType::I64),
564            mutable: true,
565        },
566    ]);
567    symbols.call_metadata_type = Some(*next_type_idx);
568    *next_type_idx += 1;
569    let object = symbols
570        .intrinsic_type_indices()
571        .ok_or_else(|| crate::codegen::internal_failure("intrinsics"))?
572        .object;
573    types.ty().struct_([
574        FieldType {
575            element_type: StorageType::Val(ValType::Ref(RefType {
576                nullable: false,
577                heap_type: HeapType::ANY,
578            })),
579            mutable: false,
580        },
581        FieldType {
582            element_type: StorageType::Val(ValType::Ref(RefType {
583                nullable: true,
584                heap_type: HeapType::Concrete(object),
585            })),
586            mutable: false,
587        },
588    ]);
589    symbols.this_environment_type = Some(*next_type_idx);
590    *next_type_idx += 1;
591    for meta in metas {
592        let mut fields: Vec<FieldType> = meta
593            .captured
594            .iter()
595            .map(|c| {
596                Ok(FieldType {
597                    element_type: StorageType::Val(env_field_type(c, symbols)?),
598                    mutable: false,
599                })
600            })
601            .collect::<Result<_, crate::compiler_error::CompilerFailure>>()?;
602        if !meta.runtime_generics.is_empty() {
603            fields.push(FieldType {
604                element_type: StorageType::Val(super::runtime_descriptors::environment_type(
605                    symbols,
606                )?),
607                mutable: false,
608            });
609        }
610        if meta.self_name.is_some() {
611            fields.push(FieldType {
612                element_type: StorageType::Val(ValType::Ref(RefType {
613                    nullable: true,
614                    heap_type: HeapType::Concrete(
615                        symbols
616                            .intrinsic_type_indices()
617                            .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?
618                            .object,
619                    ),
620                })),
621                mutable: true,
622            });
623        }
624        types.ty().subtype(&SubType {
625            is_final: false,
626            supertype_idx: None,
627            composite_type: CompositeType {
628                inner: CompositeInnerType::Struct(StructType {
629                    fields: fields.into_boxed_slice(),
630                }),
631                shared: false,
632                descriptor: None,
633                describes: None,
634            },
635        });
636        symbols.record_env_type(meta.expr_id, *next_type_idx);
637        *next_type_idx += 1;
638    }
639    Ok(())
640}
641
642/// Read by both the declaration and the recorded coercion edge, so the two
643/// can't drift.
644fn closure_struct_supertype(intrinsics: IntrinsicTypeIndices) -> u32 {
645    intrinsics.closure
646}
647
648fn closure_func_type(sig: ClosureSig, intrinsics: IntrinsicTypeIndices) -> FuncType {
649    let object_ref = ValType::Ref(RefType {
650        nullable: true,
651        heap_type: HeapType::Concrete(intrinsics.object),
652    });
653    let mut wasm_params = vec![ValType::Ref(RefType {
654        nullable: false,
655        heap_type: HeapType::ANY,
656    })];
657    for _ in 0..sig.arity {
658        wasm_params.push(object_ref);
659    }
660    let results = vec![object_ref];
661    FuncType::new(wasm_params, results)
662}
663
664fn env_field_type(
665    c: &CapturedVar,
666    symbols: &SymbolTable,
667) -> Result<ValType, crate::compiler_error::CompilerFailure> {
668    Ok(if c.boxed {
669        let box_idx = symbols.box_type_idx(&c.ty)?.ok_or_else(|| {
670            crate::codegen::internal_failure("box type registered for every boxed capture")
671        })?;
672        ValType::Ref(RefType {
673            nullable: false,
674            heap_type: HeapType::Concrete(box_idx),
675        })
676    } else {
677        symbols.value_type(&c.ty)?
678    })
679}
680
681#[derive(Clone, Copy, Debug)]
682pub struct ClosureMethods {
683    pub to_string_func: u32,
684    pub to_json_func: u32,
685    pub equals_func: u32,
686    pub hash_func: u32,
687    /// Filled in by [`emit_vtable_global`].
688    pub vtable_global_idx: u32,
689}
690
691pub fn allocate_methods(
692    next_func_idx: &mut u32,
693) -> Result<ClosureMethods, crate::compiler_error::CompilerFailure> {
694    ensure_index_capacity(*next_func_idx, Some(4), 0)?;
695    let to_string_func = take(next_func_idx);
696    let to_json_func = take(next_func_idx);
697    let equals_func = take(next_func_idx);
698    let hash_func = take(next_func_idx);
699    Ok(ClosureMethods {
700        to_string_func,
701        to_json_func,
702        equals_func,
703        hash_func,
704        vtable_global_idx: 0,
705    })
706}
707
708fn take(counter: &mut u32) -> u32 {
709    let v = *counter;
710    *counter += 1;
711    v
712}
713
714/// Must be called after existing function-section entries so pre-allocated indices match.
715pub fn emit_method_function_entries(
716    functions: &mut wasm_encoder::FunctionSection,
717    intrinsics: crate::codegen::intrinsics::IntrinsicTypeIndices,
718) {
719    functions.function(intrinsics.to_string_fn);
720    functions.function(intrinsics.to_json_fn);
721    functions.function(intrinsics.equals_fn);
722    functions.function(intrinsics.hash_fn);
723}
724
725pub fn emit_vtable_global(
726    globals: &mut wasm_encoder::GlobalSection,
727    methods: &mut ClosureMethods,
728    symbols: &mut SymbolTable,
729    next_global_idx: &mut u32,
730    intrinsics: crate::codegen::intrinsics::IntrinsicTypeIndices,
731) -> Result<(), crate::compiler_error::CompilerFailure> {
732    ensure_index_capacity(*next_global_idx, Some(1), 0)?;
733    let init = wasm_encoder::ConstExpr::extended([
734        wasm_encoder::Instruction::RefFunc(methods.to_string_func),
735        wasm_encoder::Instruction::RefFunc(methods.to_json_func),
736        wasm_encoder::Instruction::RefFunc(methods.equals_func),
737        wasm_encoder::Instruction::RefFunc(methods.hash_func),
738        wasm_encoder::Instruction::StructNew(intrinsics.vtable),
739    ]);
740    globals.global(
741        wasm_encoder::GlobalType {
742            val_type: ValType::Ref(RefType {
743                nullable: false,
744                heap_type: HeapType::Concrete(intrinsics.vtable),
745            }),
746            mutable: false,
747            shared: false,
748        },
749        &init,
750    );
751    let idx = take(next_global_idx);
752    methods.vtable_global_idx = idx;
753    symbols.set_closure_vtable_global(idx);
754
755    Ok(())
756}
757
758pub fn emit_method_bodies(
759    code: &mut wasm_encoder::CodeSection,
760    _methods: ClosureMethods,
761    symbols: &SymbolTable,
762) -> Result<(), crate::compiler_error::CompilerFailure> {
763    let intrinsics = symbols
764        .intrinsic_type_indices()
765        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared by codegen entry"))?;
766    let string_vtable_global_idx = symbols
767        .prelude_global_idx("string_vtable")
768        .ok_or_else(|| crate::codegen::internal_failure("string_vtable imported from prelude"))?;
769
770    let mut to_string = wasm_encoder::Function::new(std::iter::empty());
771    crate::codegen::intrinsics::push_string_literal(
772        &mut to_string,
773        intrinsics,
774        string_vtable_global_idx,
775        "[object Function]",
776    )?;
777    to_string.instruction(&wasm_encoder::Instruction::End);
778    code.function(&to_string);
779
780    let mut to_json = wasm_encoder::Function::new(std::iter::empty());
781    crate::codegen::intrinsics::push_string_literal(
782        &mut to_json,
783        intrinsics,
784        string_vtable_global_idx,
785        "null",
786    )?;
787    to_json.instruction(&wasm_encoder::Instruction::End);
788    code.function(&to_json);
789
790    code.function(&super::closure_coercions::emit_equals(symbols)?);
791
792    let mut hash = wasm_encoder::Function::new(std::iter::empty());
793    let host_vtable = symbols
794        .prelude_global_idx("closure_vtable")
795        .ok_or_else(|| crate::codegen::internal_failure("closure_vtable imported from prelude"))?;
796    hash.instruction(&wasm_encoder::Instruction::LocalGet(0));
797    hash.instruction(&wasm_encoder::Instruction::GlobalGet(host_vtable));
798    hash.instruction(&wasm_encoder::Instruction::StructGet {
799        struct_type_index: intrinsics.vtable,
800        field_index: 3,
801    });
802    hash.instruction(&wasm_encoder::Instruction::CallRef(intrinsics.hash_fn));
803    hash.instruction(&wasm_encoder::Instruction::End);
804    code.function(&hash);
805
806    Ok(())
807}
808
809/// `ref.func` in a const-expr requires declarative element coverage; these are the target indices.
810pub fn declared_funcs(methods: ClosureMethods) -> Vec<u32> {
811    vec![
812        methods.to_string_func,
813        methods.to_json_func,
814        methods.equals_func,
815        methods.hash_func,
816    ]
817}
818
819/// Validate the whole index range before emitting any of its declarations.
820pub(super) fn ensure_index_capacity(
821    next: u32,
822    count: Option<usize>,
823    fixed: u32,
824) -> Result<(), crate::compiler_error::CompilerFailure> {
825    let end = count
826        .and_then(|count| u32::try_from(count).ok())
827        .and_then(|count| count.checked_add(fixed))
828        .and_then(|count| next.checked_add(count));
829    if end.is_none() {
830        return Err(crate::compiler_error::CompilerFailure::Limit {
831            stage: crate::compiler_error::CompilerStage::Codegen,
832            span: None,
833            message: "closure declarations exceed the Wasm index space".into(),
834            help: vec!["reduce the number of declarations".into()],
835        });
836    }
837    Ok(())
838}
839
840#[cfg(test)]
841mod tests {
842    use super::*;
843
844    #[test]
845    fn dependency_collection_checks_nested_callable_types() {
846        let signature = Type::Function {
847            params: vec![Type::Number; 256],
848            ret: Box::new(Type::Void),
849            predicate: None,
850            optional: 0,
851            has_rest: false,
852        };
853        let shapes = [Shape::Array(Box::new(Type::Array(Box::new(signature))))];
854        let error = collect_from_dependencies([], shapes.iter(), [], &DependencyUsage::empty())
855            .unwrap_err();
856        assert!(matches!(
857            error,
858            crate::compiler_error::CompilerFailure::Limit {
859                stage: crate::compiler_error::CompilerStage::Codegen,
860                span: None,
861                ..
862            }
863        ));
864        assert!(error.to_string().contains("256"));
865    }
866}