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