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::TemplateLiteral { substitutions, .. } => {
356            for inner in substitutions {
357                walk_type(inner, out)?;
358            }
359        }
360        Type::Refined { ty, .. } | Type::Readonly(ty) => walk_type(ty, out)?,
361        Type::Function { params, ret, .. } => {
362            out.push(classify(ty)?);
363            for p in params {
364                walk_type(p, out)?;
365            }
366            walk_type(ret, out)?;
367        }
368        Type::Array(elem) => walk_type(elem, out)?,
369        Type::Tuple(elements) => {
370            for e in elements {
371                walk_type(e, out)?;
372            }
373        }
374        Type::Object { fields, index } => {
375            if let Some(index) = index {
376                walk_type(&index.value, out)?;
377            }
378            for f in fields.values() {
379                walk_type(&f.ty, out)?;
380            }
381        }
382        Type::InterfaceRef { args, .. } | Type::ClassRef { args, .. } => {
383            for a in args {
384                walk_type(a, out)?;
385            }
386        }
387        // A recursion back-edge has no inline body to walk — its
388        // function-typed positions were collected from the carrying
389        // `Alias`'s body. Args only (like `InterfaceRef`), which also
390        // stops the otherwise-infinite recursion.
391        Type::AliasRef { args, .. } => {
392            for a in args {
393                walk_type(a, out)?;
394            }
395        }
396        Type::Union(members) => {
397            for m in members {
398                walk_type(m, out)?;
399            }
400        }
401        Type::Alias { ty: inner, .. } => walk_type(inner, out)?,
402        Type::Number
403        | Type::NumberLiteral(_)
404        | Type::BigInt
405        | Type::BigIntLiteral(_)
406        | Type::Boolean
407        | Type::BooleanLiteral(_)
408        | Type::String
409        | Type::StringLiteral(_)
410        | Type::Uint8Array
411        | Type::Void
412        | Type::Never
413        | Type::Null
414        | Type::Undefined
415        | Type::Error
416        | Type::TypeVar(_)
417        | Type::GenericParam { .. }
418        | Type::KeyOf(_)
419        | Type::IndexedAccess { .. }
420        | Type::Unknown
421        | Type::NonPrimitive
422        | Type::NumberEnum { .. }
423        | Type::StringEnum { .. } => {}
424    };
425    Ok(())
426}
427
428pub fn emit_func_and_struct_types<I>(
429    signatures: I,
430    types: &mut TypeSection,
431    symbols: &mut SymbolTable,
432    next_type_idx: &mut u32,
433) -> Result<(), crate::compiler_error::CompilerFailure>
434where
435    I: IntoIterator<Item = ClosureSig>,
436{
437    let intrinsics = symbols
438        .intrinsic_type_indices()
439        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared by codegen entry"))?;
440    let signatures = signatures.into_iter().flat_map(|sig| {
441        [
442            sig,
443            ClosureSig {
444                is_void: !sig.is_void,
445                ..sig
446            },
447        ]
448    });
449    let assigned = emit_arity_closures(signatures, types, intrinsics, next_type_idx)?;
450    super::closure_coercions::emit_vtable_type(types, symbols, next_type_idx)?;
451    for (sig, (fn_idx, struct_idx)) in assigned {
452        symbols.record_closure_func_type(sig, fn_idx);
453        symbols.record_closure_struct_type(sig, struct_idx);
454        symbols.record_struct_supertype(struct_idx, closure_struct_supertype(intrinsics));
455    }
456    Ok(())
457}
458
459/// Structural canonicalization unifies prelude and consumer copies at instantiation time.
460pub fn emit_arity_closures<I>(
461    signatures: I,
462    types: &mut TypeSection,
463    intrinsics: IntrinsicTypeIndices,
464    next_type_idx: &mut u32,
465) -> Result<BTreeMap<ClosureSig, (u32, u32)>, crate::compiler_error::CompilerFailure>
466where
467    I: IntoIterator<Item = ClosureSig>,
468{
469    let mut seen: BTreeSet<ClosureSig> = BTreeSet::new();
470    let unique: Vec<ClosureSig> = signatures
471        .into_iter()
472        .filter(|sig| seen.insert(*sig))
473        .collect();
474
475    ensure_index_capacity(*next_type_idx, unique.len().checked_mul(2), 0)?;
476    let mut assigned: BTreeMap<ClosureSig, (u32, u32)> = BTreeMap::new();
477
478    let mut fn_indices: Vec<u32> = Vec::with_capacity(unique.len());
479    for sig in &unique {
480        let func_ty = closure_func_type(*sig, intrinsics);
481        types.ty().subtype(&SubType {
482            is_final: false,
483            supertype_idx: None,
484            composite_type: CompositeType {
485                inner: CompositeInnerType::Func(func_ty),
486                shared: false,
487                descriptor: None,
488                describes: None,
489            },
490        });
491        fn_indices.push(*next_type_idx);
492        *next_type_idx += 1;
493    }
494
495    for (sig, &fn_idx) in unique.iter().zip(fn_indices.iter()) {
496        let fields = vec![
497            FieldType {
498                element_type: StorageType::Val(ValType::Ref(RefType {
499                    nullable: false,
500                    heap_type: HeapType::Concrete(intrinsics.vtable),
501                })),
502                mutable: false,
503            },
504            FieldType {
505                element_type: StorageType::Val(ValType::Ref(RefType {
506                    nullable: false,
507                    heap_type: HeapType::Concrete(fn_idx),
508                })),
509                mutable: false,
510            },
511            FieldType {
512                element_type: StorageType::Val(ValType::Ref(RefType {
513                    nullable: false,
514                    heap_type: HeapType::ANY,
515                })),
516                mutable: false,
517            },
518            FieldType {
519                element_type: StorageType::Val(ValType::I64),
520                mutable: true,
521            },
522        ];
523        types.ty().subtype(&SubType {
524            is_final: false,
525            supertype_idx: Some(closure_struct_supertype(intrinsics)),
526            composite_type: CompositeType {
527                inner: CompositeInnerType::Struct(StructType {
528                    fields: fields.into_boxed_slice(),
529                }),
530                shared: false,
531                descriptor: None,
532                describes: None,
533            },
534        });
535        let struct_idx = *next_type_idx;
536        *next_type_idx += 1;
537        assigned.insert(*sig, (fn_idx, struct_idx));
538    }
539
540    Ok(assigned)
541}
542
543/// Must run after `box_types::emit` so env-field lookups for boxed captures resolve.
544pub fn emit_env_types(
545    metas: &[ClosureMeta],
546    types: &mut TypeSection,
547    symbols: &mut SymbolTable,
548    next_type_idx: &mut u32,
549) -> Result<(), crate::compiler_error::CompilerFailure> {
550    ensure_index_capacity(*next_type_idx, Some(metas.len()), 2)?;
551    let string = symbols
552        .string_type_idx()
553        .ok_or_else(|| crate::codegen::internal_failure("string intrinsic"))?;
554    types.ty().struct_([
555        FieldType {
556            element_type: StorageType::Val(ValType::Ref(RefType {
557                nullable: true,
558                heap_type: HeapType::ANY,
559            })),
560            mutable: false,
561        },
562        FieldType {
563            element_type: StorageType::Val(ValType::Ref(RefType {
564                nullable: false,
565                heap_type: HeapType::Concrete(string),
566            })),
567            mutable: false,
568        },
569        FieldType {
570            element_type: StorageType::Val(ValType::I64),
571            mutable: true,
572        },
573    ]);
574    symbols.call_metadata_type = Some(*next_type_idx);
575    *next_type_idx += 1;
576    let object = symbols
577        .intrinsic_type_indices()
578        .ok_or_else(|| crate::codegen::internal_failure("intrinsics"))?
579        .object;
580    types.ty().struct_([
581        FieldType {
582            element_type: StorageType::Val(ValType::Ref(RefType {
583                nullable: false,
584                heap_type: HeapType::ANY,
585            })),
586            mutable: false,
587        },
588        FieldType {
589            element_type: StorageType::Val(ValType::Ref(RefType {
590                nullable: true,
591                heap_type: HeapType::Concrete(object),
592            })),
593            mutable: false,
594        },
595    ]);
596    symbols.this_environment_type = Some(*next_type_idx);
597    *next_type_idx += 1;
598    for meta in metas {
599        let mut fields: Vec<FieldType> = meta
600            .captured
601            .iter()
602            .map(|c| {
603                Ok(FieldType {
604                    element_type: StorageType::Val(env_field_type(c, symbols)?),
605                    mutable: false,
606                })
607            })
608            .collect::<Result<_, crate::compiler_error::CompilerFailure>>()?;
609        if !meta.runtime_generics.is_empty() {
610            fields.push(FieldType {
611                element_type: StorageType::Val(super::runtime_descriptors::environment_type(
612                    symbols,
613                )?),
614                mutable: false,
615            });
616        }
617        if meta.self_name.is_some() {
618            fields.push(FieldType {
619                element_type: StorageType::Val(ValType::Ref(RefType {
620                    nullable: true,
621                    heap_type: HeapType::Concrete(
622                        symbols
623                            .intrinsic_type_indices()
624                            .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?
625                            .object,
626                    ),
627                })),
628                mutable: true,
629            });
630        }
631        types.ty().subtype(&SubType {
632            is_final: false,
633            supertype_idx: None,
634            composite_type: CompositeType {
635                inner: CompositeInnerType::Struct(StructType {
636                    fields: fields.into_boxed_slice(),
637                }),
638                shared: false,
639                descriptor: None,
640                describes: None,
641            },
642        });
643        symbols.record_env_type(meta.expr_id, *next_type_idx);
644        *next_type_idx += 1;
645    }
646    Ok(())
647}
648
649/// Read by both the declaration and the recorded coercion edge, so the two
650/// can't drift.
651fn closure_struct_supertype(intrinsics: IntrinsicTypeIndices) -> u32 {
652    intrinsics.closure
653}
654
655fn closure_func_type(sig: ClosureSig, intrinsics: IntrinsicTypeIndices) -> FuncType {
656    let object_ref = ValType::Ref(RefType {
657        nullable: true,
658        heap_type: HeapType::Concrete(intrinsics.object),
659    });
660    let mut wasm_params = vec![ValType::Ref(RefType {
661        nullable: false,
662        heap_type: HeapType::ANY,
663    })];
664    for _ in 0..sig.arity {
665        wasm_params.push(object_ref);
666    }
667    let results = vec![object_ref];
668    FuncType::new(wasm_params, results)
669}
670
671fn env_field_type(
672    c: &CapturedVar,
673    symbols: &SymbolTable,
674) -> Result<ValType, crate::compiler_error::CompilerFailure> {
675    Ok(if c.boxed {
676        let box_idx = symbols.box_type_idx(&c.ty)?.ok_or_else(|| {
677            crate::codegen::internal_failure("box type registered for every boxed capture")
678        })?;
679        ValType::Ref(RefType {
680            nullable: false,
681            heap_type: HeapType::Concrete(box_idx),
682        })
683    } else {
684        symbols.value_type(&c.ty)?
685    })
686}
687
688#[derive(Clone, Copy, Debug)]
689pub struct ClosureMethods {
690    pub to_string_func: u32,
691    pub to_json_func: u32,
692    pub equals_func: u32,
693    pub hash_func: u32,
694    /// Filled in by [`emit_vtable_global`].
695    pub vtable_global_idx: u32,
696}
697
698pub fn allocate_methods(
699    next_func_idx: &mut u32,
700) -> Result<ClosureMethods, crate::compiler_error::CompilerFailure> {
701    ensure_index_capacity(*next_func_idx, Some(4), 0)?;
702    let to_string_func = take(next_func_idx);
703    let to_json_func = take(next_func_idx);
704    let equals_func = take(next_func_idx);
705    let hash_func = take(next_func_idx);
706    Ok(ClosureMethods {
707        to_string_func,
708        to_json_func,
709        equals_func,
710        hash_func,
711        vtable_global_idx: 0,
712    })
713}
714
715fn take(counter: &mut u32) -> u32 {
716    let v = *counter;
717    *counter += 1;
718    v
719}
720
721/// Must be called after existing function-section entries so pre-allocated indices match.
722pub fn emit_method_function_entries(
723    functions: &mut wasm_encoder::FunctionSection,
724    intrinsics: crate::codegen::intrinsics::IntrinsicTypeIndices,
725) {
726    functions.function(intrinsics.to_string_fn);
727    functions.function(intrinsics.to_json_fn);
728    functions.function(intrinsics.equals_fn);
729    functions.function(intrinsics.hash_fn);
730}
731
732pub fn emit_vtable_global(
733    globals: &mut wasm_encoder::GlobalSection,
734    methods: &mut ClosureMethods,
735    symbols: &mut SymbolTable,
736    next_global_idx: &mut u32,
737    intrinsics: crate::codegen::intrinsics::IntrinsicTypeIndices,
738) -> Result<(), crate::compiler_error::CompilerFailure> {
739    ensure_index_capacity(*next_global_idx, Some(1), 0)?;
740    let init = wasm_encoder::ConstExpr::extended([
741        wasm_encoder::Instruction::RefFunc(methods.to_string_func),
742        wasm_encoder::Instruction::RefFunc(methods.to_json_func),
743        wasm_encoder::Instruction::RefFunc(methods.equals_func),
744        wasm_encoder::Instruction::RefFunc(methods.hash_func),
745        wasm_encoder::Instruction::StructNew(intrinsics.vtable),
746    ]);
747    globals.global(
748        wasm_encoder::GlobalType {
749            val_type: ValType::Ref(RefType {
750                nullable: false,
751                heap_type: HeapType::Concrete(intrinsics.vtable),
752            }),
753            mutable: false,
754            shared: false,
755        },
756        &init,
757    );
758    let idx = take(next_global_idx);
759    methods.vtable_global_idx = idx;
760    symbols.set_closure_vtable_global(idx);
761
762    Ok(())
763}
764
765pub fn emit_method_bodies(
766    code: &mut wasm_encoder::CodeSection,
767    _methods: ClosureMethods,
768    symbols: &SymbolTable,
769) -> Result<(), crate::compiler_error::CompilerFailure> {
770    let intrinsics = symbols
771        .intrinsic_type_indices()
772        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared by codegen entry"))?;
773    let string_vtable_global_idx = symbols
774        .prelude_global_idx("string_vtable")
775        .ok_or_else(|| crate::codegen::internal_failure("string_vtable imported from prelude"))?;
776
777    let mut to_string = wasm_encoder::Function::new(std::iter::empty());
778    crate::codegen::intrinsics::push_string_literal(
779        &mut to_string,
780        intrinsics,
781        string_vtable_global_idx,
782        "[object Function]",
783    )?;
784    to_string.instruction(&wasm_encoder::Instruction::End);
785    code.function(&to_string);
786
787    let mut to_json = wasm_encoder::Function::new(std::iter::empty());
788    crate::codegen::intrinsics::push_string_literal(
789        &mut to_json,
790        intrinsics,
791        string_vtable_global_idx,
792        "null",
793    )?;
794    to_json.instruction(&wasm_encoder::Instruction::End);
795    code.function(&to_json);
796
797    code.function(&super::closure_coercions::emit_equals(symbols)?);
798
799    let mut hash = wasm_encoder::Function::new(std::iter::empty());
800    let host_vtable = symbols
801        .prelude_global_idx("closure_vtable")
802        .ok_or_else(|| crate::codegen::internal_failure("closure_vtable imported from prelude"))?;
803    hash.instruction(&wasm_encoder::Instruction::LocalGet(0));
804    hash.instruction(&wasm_encoder::Instruction::GlobalGet(host_vtable));
805    hash.instruction(&wasm_encoder::Instruction::StructGet {
806        struct_type_index: intrinsics.vtable,
807        field_index: 3,
808    });
809    hash.instruction(&wasm_encoder::Instruction::CallRef(intrinsics.hash_fn));
810    hash.instruction(&wasm_encoder::Instruction::End);
811    code.function(&hash);
812
813    Ok(())
814}
815
816/// `ref.func` in a const-expr requires declarative element coverage; these are the target indices.
817pub fn declared_funcs(methods: ClosureMethods) -> Vec<u32> {
818    vec![
819        methods.to_string_func,
820        methods.to_json_func,
821        methods.equals_func,
822        methods.hash_func,
823    ]
824}
825
826/// Validate the whole index range before emitting any of its declarations.
827pub(super) fn ensure_index_capacity(
828    next: u32,
829    count: Option<usize>,
830    fixed: u32,
831) -> Result<(), crate::compiler_error::CompilerFailure> {
832    let end = count
833        .and_then(|count| u32::try_from(count).ok())
834        .and_then(|count| count.checked_add(fixed))
835        .and_then(|count| next.checked_add(count));
836    if end.is_none() {
837        return Err(crate::compiler_error::CompilerFailure::Limit {
838            stage: crate::compiler_error::CompilerStage::Codegen,
839            span: None,
840            message: "closure declarations exceed the Wasm index space".into(),
841            help: vec!["reduce the number of declarations".into()],
842        });
843    }
844    Ok(())
845}
846
847#[cfg(test)]
848mod tests {
849    use super::*;
850
851    #[test]
852    fn dependency_collection_checks_nested_callable_types() {
853        let signature = Type::Function {
854            params: vec![Type::Number; 256],
855            ret: Box::new(Type::Void),
856            predicate: None,
857            optional: 0,
858            has_rest: false,
859        };
860        let shapes = [Shape::Array(Box::new(Type::Array(Box::new(signature))))];
861        let error = collect_from_dependencies([], shapes.iter(), [], &DependencyUsage::empty())
862            .unwrap_err();
863        assert!(matches!(
864            error,
865            crate::compiler_error::CompilerFailure::Limit {
866                stage: crate::compiler_error::CompilerStage::Codegen,
867                span: None,
868                ..
869            }
870        ));
871        assert!(error.to_string().contains("256"));
872    }
873}