Skip to main content

submilli_engine/codegen/
classes.rs

1//! WasmGC type emission and body lowering for user `class` declarations.
2//!
3//! Each class becomes one rec group holding a struct type
4//! `$Foo (sub $ObjectShape …)` and a vtable type `$Foo_vtable (sub $VTable …)`,
5//! with subclasses subtyping their parent's pair. Alongside those: the
6//! per-class singleton globals (vtable instance, field-names array), the
7//! allocating constructor and its self-first init counterpart, real method
8//! bodies, one closure-ABI adapter per non-generic vtable *slot* for
9//! interface-typed dispatch (inherited slots included — a subclass reuses its
10//! ancestor's adapter where one exists locally),
11//! and the four universal vtable bodies (`toString`/`toJson`/`equals`/`hash`).
12//!
13//! Data fields live in the shared object-fields payload rather than as named
14//! struct fields, so a class's struct only ever references its own vtable and
15//! the intrinsics — which is what lets a cross-package consumer rebuild just
16//! the classes it uses (see [`super::imported_classes`]).
17
18use std::collections::{BTreeMap, BTreeSet};
19
20use wasm_encoder::{
21    BlockType, CodeSection, CompositeInnerType, CompositeType, ConstExpr, FieldType, Function,
22    FunctionSection, GlobalSection, GlobalType, HeapType, Instruction, RefType, StorageType,
23    StructType, SubType, TypeSection, ValType,
24};
25
26use crate::codegen::CodegenCtx;
27use crate::codegen::closures::ClosureSig;
28use crate::codegen::function_emitter::FunctionEmitter;
29use crate::codegen::imported_classes::ImportedClassLayout;
30use crate::codegen::intrinsics::IntrinsicTypeIndices;
31use crate::codegen::symbol_table::{FieldSetup, MethodSlotAbi, SymbolTable};
32use crate::codegen::{GuardedBodies, internal_failure, next_index, parameter_local, wasm_u32};
33use crate::compiler_error::CompilerFailure;
34use crate::{MangledName, TypedAst, TypedClassDecl, TypedTypeDecl};
35
36/// The four universal vtable slots (toString/toJson/equals/hash) every vtable
37/// carries, each backed by a shared stub function.
38pub(crate) const UNIVERSAL_STUB_COUNT: usize = 4;
39/// `$ClassVTable` field index of the nominal-identity parent link.
40pub(crate) const VTABLE_PARENT_SLOT: u32 = UNIVERSAL_STUB_COUNT as u32;
41/// Whether JSON uses the runtime's default property serializer.
42pub(crate) const VTABLE_DEFAULT_JSON_SLOT: u32 = VTABLE_PARENT_SLOT + 1;
43/// Universal funcrefs, parent link and default-serializer marker precede methods.
44pub(crate) const VTABLE_METHOD_SLOT_BASE: u32 = VTABLE_DEFAULT_JSON_SLOT + 1;
45
46/// One method slot in a class vtable. `name` is the method name; `owner` is the
47/// most-derived class that supplies the body (the class itself for an override,
48/// else the nearest ancestor); `sig_idx` is the Wasm fn-type the slot holds
49/// (allocated by the base declarer and reused by overrides).
50struct MethodSlot {
51    name: String,
52    owner: MangledName,
53    sig_idx: u32,
54    /// Declared parameter/return types of the body backing this slot (the
55    /// *owner's*). Needed to pick the closure signature for the slot's payload
56    /// adapter; its body uses the recorded ABI for physical representations.
57    param_tys: Vec<crate::Type>,
58    argument_metadata: Option<String>,
59    ret_ty: crate::Type,
60    /// A generic method has no adapter and never enters the instance payload.
61    generic: bool,
62}
63
64struct ClassLayout {
65    private_members: BTreeSet<String>,
66    generics: Vec<String>,
67    mangled: MangledName,
68    name: String,
69    /// The class name's span, the declaration a constructor frame points at.
70    name_span: crate::Span,
71    parent: Option<MangledName>,
72    /// Full field list (inherited prefix then own), in object payload order.
73    fields: Vec<FieldLayout>,
74    /// Full method slot list (inherited prefix then own/new), in vtable order.
75    methods: Vec<MethodSlot>,
76    /// Methods declared on *this* class (own or override) — each gets a real body.
77    own_methods: Vec<OwnMethod>,
78    /// Indices into `methods` whose payload adapter *this* class emits. A slot
79    /// inherited from a local ancestor reuses that ancestor's adapter (topo
80    /// order guarantees it exists); an imported owner has no adapter in this
81    /// module, so one is emitted here.
82    adapter_slots: Vec<usize>,
83    /// `this`-class's own constructor params + body. `None` body = no declared
84    /// constructor (synthesize a default that just allocates + returns).
85    ctor_params: Vec<crate::TypedParam>,
86    ctor_body: Option<crate::StmtId>,
87    /// Parameter-property copies then own-field initializers, in execution order.
88    /// Emitted at the post-`super()` field-setup point — after the parent is
89    /// initialized, before the rest of the constructor body.
90    field_setup: Vec<FieldSetup>,
91    /// Accessor property names (own). These are not data fields, so they need a
92    /// per-name `$string` global for the runtime property scan even though they
93    /// never appear in any shape's field-names array.
94    accessor_names: Vec<String>,
95    struct_type_idx: u32,
96    vtable_type_idx: u32,
97    ctor_func_idx: u32,
98    ctor_sig_idx: u32,
99    /// Constructor *init* fn: self-first body that initializes fields on an
100    /// already-allocated instance (`super(...)` calls it; the entry constructor
101    /// delegates to it after allocation).
102    ctor_init_func_idx: u32,
103    ctor_init_sig_idx: u32,
104    getter_func_idx: u32,
105    setter_func_idx: u32,
106    /// Per-class `equals` body (vtable universal slot 2): nominal exact-class
107    /// guard + structural compare of the data-field payload slots.
108    equals_func_idx: u32,
109    /// Per-class universal slot 0 body: a user `toString(): string` method
110    /// thunk, else the `"[object Object]"` default.
111    to_string_func_idx: u32,
112    /// Per-class universal slot 1 body: a user `toJson(): string` method
113    /// thunk, else data-field JSON in canonical sorted-key order.
114    to_json_func_idx: u32,
115    /// Per-class universal slot 3 body: FNV-1a over the data-field payload
116    /// slots, consistent with the `equals` contract.
117    hash_func_idx: u32,
118    guarded_bodies: GuardedBodies,
119}
120
121impl ClassLayout {
122    /// The non-generic vtable slots that enter the instance payload, in slot
123    /// order — every method in the inheritance chain, not just the declared
124    /// ones. An interface-typed receiver resolves a method by scanning this
125    /// payload, and `implements` conformance may be satisfied by an inherited
126    /// member, so a subclass instance has to carry the whole chain.
127    fn payload_slots(&self) -> impl Iterator<Item = &MethodSlot> {
128        self.methods.iter().filter(|s| !s.generic)
129    }
130
131    /// Instance payload names: the full data fields (inherited prefix then own,
132    /// folded in by [`ClassPlan::collect`]) followed by the payload method
133    /// names. Single source of truth for the payload built in
134    /// [`ClassPlan::emit_ctor_body`] and the field-names global that indexes it.
135    fn payload_field_names(&self) -> Vec<super::field_names::FieldName> {
136        let mut names: Vec<_> = self
137            .fields
138            .iter()
139            .map(|f| super::field_names::FieldName {
140                name: f.name.clone(),
141                optional: f.optional,
142                is_accessor: false,
143                is_private: self.private_members.contains(&f.name),
144            })
145            .collect();
146        names.extend(self.payload_slots().map(|s| super::field_names::FieldName {
147            name: s.name.clone(),
148            optional: false,
149            is_accessor: s.name.starts_with("get ") || s.name.starts_with("set "),
150            is_private: self.private_members.contains(&s.name),
151        }));
152        names
153    }
154}
155
156#[derive(Clone)]
157struct FieldLayout {
158    name: String,
159    optional: bool,
160    /// Inherited along the layout prefix, so a subclass that does not redeclare
161    /// still reads at the narrowed type. See [`crate::FieldNarrowingCheck`].
162    narrowing_check: Option<Box<crate::FieldNarrowingCheck>>,
163}
164
165/// A method declared on the class itself (own or override) — gets a real Wasm body.
166#[derive(Clone)]
167struct OwnMethod {
168    name: String,
169    name_span: crate::Span,
170    /// The backtrace name: `C.m`, or `get C.x` / `set C.x` for an accessor.
171    frame_name: String,
172    params: Vec<crate::TypedParam>,
173    body: crate::StmtId,
174    return_type: crate::Type,
175    /// A method declaring its own type parameters is rejected at bind time; the
176    /// flag only exists so codegen can emit an unreachable stub for its slot.
177    generic: bool,
178}
179
180/// The class-emission plan, computed once and threaded through the codegen phases.
181pub struct ClassPlan {
182    package_name: String,
183    classes: Vec<ClassLayout>,
184    /// Shared `unreachable` stubs for the universal vtable slots. No vtable
185    /// references them anymore (every class emits its own four real bodies);
186    /// they're kept only so function numbering stays stable.
187    universal_stubs: [u32; UNIVERSAL_STUB_COUNT],
188}
189
190impl ClassPlan {
191    pub fn is_empty(&self) -> bool {
192        self.classes.is_empty()
193    }
194
195    /// Phase 0: gather class declarations from the typed AST, topologically
196    /// ordered by `extends` (parents before children), and resolve each class's
197    /// full field + method slot layout across the inheritance chain.
198    pub fn collect(
199        ta: &TypedAst,
200        imported: &BTreeMap<MangledName, ImportedClassLayout>,
201    ) -> Result<Self, crate::compiler_error::CompilerFailure> {
202        let by_mangled: BTreeMap<&MangledName, &TypedClassDecl> = ta
203            .types
204            .iter()
205            .filter_map(|d| match d {
206                TypedTypeDecl::Class(c) => Some((&c.mangled_name, c)),
207                _ => None,
208            })
209            .collect();
210
211        let order = topo_order(&by_mangled)?;
212        let mut private_members: BTreeMap<MangledName, BTreeSet<String>> = imported
213            .iter()
214            .map(|(name, layout)| (name.clone(), layout.private_members.clone()))
215            .collect();
216
217        // Build layouts in topo order so a parent's resolved layout is available
218        // when its child is processed.
219        let mut layouts: BTreeMap<MangledName, ResolvedLayout> = BTreeMap::new();
220        let mut ordered: Vec<MangledName> = Vec::new();
221        // Which of each class's data-field slots came from its parent prefix —
222        // read back below to decide which own fields need a construction-time
223        // reset (see [`field_setup_steps`]).
224        let mut inherited_fields: BTreeMap<MangledName, BTreeSet<String>> = BTreeMap::new();
225
226        for mangled in &order {
227            let decl = class_decl(&by_mangled, mangled)?;
228            let mut private = match &decl.extends {
229                Some(parent) => private_members
230                    .get(parent)
231                    .cloned()
232                    .ok_or_else(|| unavailable_parent(parent))?,
233                None => BTreeSet::new(),
234            };
235            for field in &decl.fields {
236                if field.visibility == crate::Visibility::Private {
237                    private.insert(field.name.name.clone());
238                }
239            }
240            for accessor in &decl.accessors {
241                if accessor.visibility() == crate::Visibility::Private {
242                    private.insert(accessor_getter_name(&accessor.name().name));
243                    private.insert(accessor_setter_name(&accessor.name().name));
244                }
245            }
246            private_members.insert(mangled.clone(), private);
247            // Parent prefix: a local parent (resolved earlier this pass) or an
248            // imported parent (its full layout reconstructed in `imported_classes`).
249            let (parent_fields, parent_methods) = match &decl.extends {
250                Some(parent) => parent_prefix(parent, &layouts, imported)?,
251                None => (Vec::new(), Vec::new()),
252            };
253
254            // Slot order is an internal ABI, not source order: own members sort
255            // by name and append after the inherited prefix. A cross-package
256            // consumer derives the identical order from the (sorted) public
257            // `PackageDeclaration` maps, so no order metadata crosses the boundary.
258            // Serialization sorts the full set itself (spec.md §JSON), so payload
259            // order stays free.
260            let mut own_fields: Vec<&crate::TypedClassField> = decl.fields.iter().collect();
261            own_fields.sort_by(|a, b| a.name.name.cmp(&b.name.name));
262            // Accessors lower to internal getter/setter methods here in codegen —
263            // they are not methods in the typed AST or `PackageDeclaration`.
264            let synth_methods = accessor_methods(decl);
265            let mut sorted_methods: Vec<(&crate::TypedClassMethod, String)> = decl
266                .methods
267                .iter()
268                .map(|m| (m, format!("{}.{}", decl.name.name, m.name.name)))
269                .chain(synth_methods.iter().map(|(m, frame)| (m, frame.clone())))
270                .collect();
271            sorted_methods.sort_by(|(a, _), (b, _)| a.name.name.cmp(&b.name.name));
272
273            inherited_fields.insert(
274                mangled.clone(),
275                parent_fields.iter().map(|f| f.name.clone()).collect(),
276            );
277            let mut fields: Vec<FieldLayout> = parent_fields;
278            for f in &own_fields {
279                // A redeclaration reuses the parent's slot. Its guard replaces the
280                // inherited one only if it has its own: redeclaring at the *same*
281                // type as the parent narrows nothing and records none, and the
282                // guard the ancestor installed still describes this slot.
283                if let Some(slot) = fields.iter_mut().find(|e| e.name == f.name.name) {
284                    slot.optional = f.optional;
285                    if let Some(check) = f.narrowing_check.clone() {
286                        slot.narrowing_check = Some(check);
287                    }
288                    continue;
289                }
290                fields.push(FieldLayout {
291                    name: f.name.name.clone(),
292                    optional: f.optional,
293                    narrowing_check: f.narrowing_check.clone(),
294                });
295            }
296
297            let mut methods: Vec<SlotDraft> = parent_methods;
298            for (m, _) in &sorted_methods {
299                let param_tys: Vec<crate::Type> = m.params.iter().map(|p| p.ty.clone()).collect();
300                let argument_metadata = super::call_arguments::typed_metadata(&m.params);
301                if let Some(slot) = methods.iter_mut().find(|s| s.name == m.name.name) {
302                    // Override: same slot, body now supplied by this class — so
303                    // the slot must describe *this* body, not the ancestor's.
304                    slot.owner = decl.mangled_name.clone();
305                    slot.param_tys = param_tys;
306                    slot.argument_metadata = argument_metadata;
307                    slot.ret_ty = m.return_type.clone();
308                    slot.generic = !m.generics.is_empty();
309                } else {
310                    methods.push(SlotDraft {
311                        name: m.name.name.clone(),
312                        owner: decl.mangled_name.clone(),
313                        param_tys,
314                        argument_metadata,
315                        ret_ty: m.return_type.clone(),
316                        generic: !m.generics.is_empty(),
317                    });
318                }
319            }
320            let own_methods: Vec<OwnMethod> = sorted_methods
321                .into_iter()
322                .map(|(m, frame_name)| OwnMethod {
323                    name: m.name.name.clone(),
324                    name_span: m.name.span,
325                    frame_name,
326                    params: m.params.clone(),
327                    body: m.body,
328                    return_type: m.return_type.clone(),
329                    generic: !m.generics.is_empty(),
330                })
331                .collect();
332            layouts.insert(mangled.clone(), (fields, methods, own_methods));
333            ordered.push(mangled.clone());
334        }
335
336        // Effective ctor params per class (topo order, parents first), so an
337        // implicit constructor (no body, has a parent) inherits the nearest
338        // ancestor's params — what the entry constructor takes and forwards.
339        // Seeded with imported parents so a local subclass of one inherits its
340        // params across the package boundary.
341        let mut ctor_params_by_mangled: BTreeMap<MangledName, Vec<crate::TypedParam>> = imported
342            .iter()
343            .map(|(m, l)| (m.clone(), l.ctor_params.clone()))
344            .collect();
345        let mut classes: Vec<ClassLayout> = Vec::with_capacity(ordered.len());
346        for mangled in ordered {
347            let decl = class_decl(&by_mangled, &mangled)?;
348            let (fields, method_drafts, own_methods) = layouts
349                .remove(&mangled)
350                .ok_or_else(|| internal_failure("a class layout was resolved out of order"))?;
351            // An inherited signature comes from the typed AST with the
352            // `extends` clause's type arguments substituted, matching what the
353            // declaration exports and a consumer imports; re-deriving the
354            // parent's unsubstituted parameters here would emit a module that
355            // cannot link.
356            let ctor_params = decl.effective_ctor_params().to_vec();
357            let ctor_body = decl.constructor.as_ref().map(|c| c.body);
358            ctor_params_by_mangled.insert(mangled.clone(), ctor_params.clone());
359            let inherited = inherited_fields.remove(&mangled).ok_or_else(|| {
360                internal_failure("a class's inherited field names were not recorded")
361            })?;
362            let field_setup = field_setup_steps(decl, &inherited)?;
363            // get + set share a property name; dedup for the per-name string globals.
364            let accessor_names: Vec<String> = decl
365                .accessors
366                .iter()
367                .map(|a| a.name().name.clone())
368                .collect::<std::collections::BTreeSet<_>>()
369                .into_iter()
370                .collect();
371            let private_members = private_members
372                .remove(&mangled)
373                .ok_or_else(|| internal_failure("a class's private members were not recorded"))?;
374            classes.push(ClassLayout {
375                private_members,
376                generics: ta
377                    .runtime_class_parameters
378                    .get(&mangled)
379                    .cloned()
380                    .unwrap_or_default(),
381                mangled: mangled.clone(),
382                name: decl.name.name.clone(),
383                name_span: decl.name.span,
384                parent: decl.extends.clone(),
385                fields,
386                field_setup,
387                accessor_names,
388                methods: method_drafts
389                    .into_iter()
390                    .map(|d| MethodSlot {
391                        name: d.name,
392                        owner: d.owner,
393                        sig_idx: 0, // filled in reserve_types
394                        param_tys: d.param_tys,
395                        argument_metadata: d.argument_metadata,
396                        ret_ty: d.ret_ty,
397                        generic: d.generic,
398                    })
399                    .collect(),
400                own_methods,
401                adapter_slots: Vec::new(),
402                ctor_params,
403                ctor_body,
404                struct_type_idx: 0,
405                vtable_type_idx: 0,
406                ctor_func_idx: 0,
407                ctor_sig_idx: 0,
408                ctor_init_func_idx: 0,
409                ctor_init_sig_idx: 0,
410                getter_func_idx: 0,
411                setter_func_idx: 0,
412                equals_func_idx: 0,
413                to_string_func_idx: 0,
414                to_json_func_idx: 0,
415                hash_func_idx: 0,
416                guarded_bodies: GuardedBodies::default(),
417            });
418        }
419
420        Ok(ClassPlan {
421            package_name: ta.package_name.to_string(),
422            classes,
423            universal_stubs: [0; UNIVERSAL_STUB_COUNT],
424        })
425    }
426
427    /// The class's instance `Type::ClassRef`, for constructor return types.
428    fn class_ref_ty(&self, class: &ClassLayout) -> crate::Type {
429        crate::Type::class_ref(
430            crate::Package(self.package_name.clone()),
431            class.name.clone(),
432            class.mangled.clone(),
433            Vec::new(),
434        )
435    }
436
437    /// The ordered list of class field-name vectors, for the shared
438    /// field-names-array global emission (`field_names::emit`). Built from the
439    /// same helper the instance payload is, so the two cannot drift.
440    pub fn field_name_lists(&self) -> Vec<Vec<super::field_names::FieldName>> {
441        self.classes
442            .iter()
443            .map(ClassLayout::payload_field_names)
444            .collect()
445    }
446
447    /// Accessor property names across all classes. They need per-name `$string`
448    /// globals for the runtime property scan, but are not data fields, so they're
449    /// excluded from `field_name_lists` (no shape stores them).
450    pub fn accessor_property_names(&self) -> Vec<String> {
451        self.classes
452            .iter()
453            .flat_map(|c| c.accessor_names.iter().cloned())
454            .collect()
455    }
456
457    /// Phase: emit standalone method fn-type sigs, then reserve struct/vtable type
458    /// indices, then emit the class rec group. `types`/`next_type_idx` advance in
459    /// lockstep with the wasm-encoder type section.
460    pub fn reserve_and_emit_types(
461        &mut self,
462        types: &mut TypeSection,
463        next_type_idx: &mut u32,
464        symbols: &mut SymbolTable,
465        intrinsics: IntrinsicTypeIndices,
466    ) -> Result<(), crate::compiler_error::CompilerFailure> {
467        if self.classes.is_empty() {
468            return Ok(());
469        }
470        // 1. A standalone fn-type sig per method slot first introduced (base
471        //    method or subclass-new method); overrides reuse the parent's sig.
472        //    Each sig carries its erasure shape (derived from the originating
473        //    declaration's types) — inherited/override slots copy it with the
474        //    sig, so a call through any class in the chain sees the physical
475        //    slot's boxing needs.
476        let mut sig_by_name: BTreeMap<(MangledName, String), (u32, MethodSlotAbi)> =
477            BTreeMap::new();
478        for class in &self.classes {
479            for slot in &class.methods {
480                // Only allocate when this class is where the slot's sig originates:
481                // i.e. the method is declared here AND the parent has no such slot.
482                let parent_has = match &class.parent {
483                    Some(p) => inherited_slot_sig(&sig_by_name, symbols, p, &slot.name)?,
484                    None => None,
485                };
486                if let Some(parent_entry) = parent_has {
487                    sig_by_name.insert((class.mangled.clone(), slot.name.clone()), parent_entry);
488                    continue;
489                }
490                // Slot originates here: build its sig from the declaring method.
491                // `own_methods` includes the synthetic accessor getter/setter.
492                let method = class
493                    .own_methods
494                    .iter()
495                    .find(|m| m.name == slot.name)
496                    .ok_or_else(|| {
497                        internal_failure("a class method slot originates in a class without it")
498                    })?;
499                let mut params: Vec<ValType> = vec![ref_to(intrinsics.object)];
500                // Overrides may widen parameters beyond the ancestor's representation.
501                // Every method argument therefore crosses a nullable boxed slot.
502                params.extend(
503                    method
504                        .params
505                        .iter()
506                        .map(|_| symbols.value_type(&crate::Type::Unknown))
507                        .collect::<Result<Vec<_>, _>>()?,
508                );
509                let results = symbols.slot_wasm_result(&method.return_type)?;
510                let abi = MethodSlotAbi {
511                    params: params.iter().skip(1).copied().collect(),
512                    ret: results.first().copied(),
513                };
514                let sig_idx = next_index(next_type_idx)?;
515                types.ty().function(params, results);
516                sig_by_name.insert((class.mangled.clone(), slot.name.clone()), (sig_idx, abi));
517            }
518        }
519        // Stamp each slot's sig_idx (resolving inherited slots to their origin sig).
520        for class in &mut self.classes {
521            for slot in &mut class.methods {
522                slot.sig_idx = sig_by_name
523                    .get(&(class.mangled.clone(), slot.name.clone()))
524                    .ok_or_else(|| internal_failure("a class method slot has no signature"))?
525                    .0;
526            }
527        }
528
529        // 2. Reserve struct + vtable type indices: one (vtable, struct) pair per
530        //    class, interleaved, in topo order (parents precede children).
531        for class in &mut self.classes {
532            class.vtable_type_idx = next_index(next_type_idx)?;
533            class.struct_type_idx = next_index(next_type_idx)?;
534            symbols.record_class_vtable_type(class.mangled.clone(), class.vtable_type_idx);
535            symbols.record_class_struct_type(class.mangled.clone(), class.struct_type_idx);
536            let vtable_super = vtable_supertype_idx(class, intrinsics, symbols)?;
537            let struct_super = struct_supertype_idx(class, intrinsics, symbols)?;
538            symbols.record_struct_supertype(class.vtable_type_idx, vtable_super);
539            symbols.record_struct_supertype(class.struct_type_idx, struct_super);
540        }
541
542        // 3. Emit one rec group per class: `(rec $Foo_vtable $Foo)`. A subclass's
543        //    group references its parent's types from an earlier rec group — valid
544        //    because there are no cross-class type cycles (single inheritance; a
545        //    class's fields live in the object-fields payload, not as named struct
546        //    fields, so the struct only references its own vtable + intrinsics).
547        //    Per-class groups let a cross-package consumer reconstruct just the
548        //    classes it uses, byte-identically.
549        for class in &self.classes {
550            let vtable = self.vtable_subtype(class, intrinsics, symbols)?;
551            let strukt = self.struct_subtype(class, intrinsics, symbols)?;
552            types.ty().rec([vtable, strukt]);
553        }
554
555        // Record each field's payload index inside ObjectShape.fields.
556        for class in &self.classes {
557            symbols
558                .class_type_parameters
559                .insert(class.mangled.clone(), class.generics.clone());
560            let parent_guarded = match &class.parent {
561                Some(parent) => {
562                    symbols
563                        .recorded_class_guard_layout(parent)?
564                        .has_instance_guards
565                }
566                None => false,
567            };
568            symbols.record_class_guard_layout(
569                class.mangled.clone(),
570                class.parent.as_ref(),
571                wasm_u32(class.payload_field_names().len())?,
572                !class.generics.is_empty()
573                    || parent_guarded
574                    || class
575                        .fields
576                        .iter()
577                        .any(|field| field.narrowing_check.is_some()),
578            )?;
579            for (i, f) in class.fields.iter().enumerate() {
580                symbols.record_class_field_slot(
581                    class.mangled.clone(),
582                    f.name.clone(),
583                    wasm_u32(i)?,
584                );
585                if let Some(check) = &f.narrowing_check {
586                    symbols.record_class_field_narrowing_check(
587                        class.mangled.clone(),
588                        f.name.clone(),
589                        (**check).clone(),
590                    );
591                }
592            }
593            // Method vtable slot (universal slots + parent link precede user
594            // methods) + its sig, for every slot in the chain so a parent-typed
595            // receiver resolves too.
596            for (i, slot) in class.methods.iter().enumerate() {
597                if !slot.generic && !slot.name.starts_with("get ") && !slot.name.starts_with("set ")
598                {
599                    symbols.record_class_payload_method(class.mangled.clone(), slot.name.clone());
600                }
601                symbols.record_class_method_slot(
602                    class.mangled.clone(),
603                    slot.name.clone(),
604                    method_vtable_slot(i)?,
605                );
606                symbols.record_class_method_sig(
607                    class.mangled.clone(),
608                    slot.name.clone(),
609                    slot.sig_idx,
610                );
611                if let Some((_, abi)) = sig_by_name.get(&(class.mangled.clone(), slot.name.clone()))
612                {
613                    symbols.record_class_method_abi(
614                        class.mangled.clone(),
615                        slot.name.clone(),
616                        abi.clone(),
617                    );
618                }
619            }
620        }
621
622        // 4. Constructor fn-type sigs — standalone, emitted *after* the rec group
623        //    so they can reference `(ref $Foo)` (a rec-group type) as the return.
624        //    Each class gets two: the allocating entry `(params) -> (ref $Foo)`,
625        //    and the self-first init `((ref $Object), params) -> ()`. Parameters
626        //    erase like method slots so a cross-package consumer reconstructs
627        //    the same signature whatever order it rebuilds the classes in.
628        let object_idx = intrinsics.object;
629        for class in &mut self.classes {
630            let params: Vec<ValType> = class
631                .ctor_params
632                .iter()
633                .map(|p| symbols.slot_value_type(&p.ty))
634                .collect::<Result<_, _>>()?;
635            symbols.record_class_ctor_abi(class.mangled.clone(), params.clone());
636            let ret = ref_to(class.struct_type_idx);
637            let mut entry_params = params.clone();
638            if symbols
639                .recorded_class_guard_layout(&class.mangled)?
640                .has_instance_guards
641            {
642                entry_params.push(ref_to(intrinsics.object_fields));
643            }
644            class.ctor_sig_idx = next_index(next_type_idx)?;
645            types.ty().function(entry_params, vec![ret]);
646
647            let mut init_params = vec![ref_to(object_idx)];
648            init_params.extend(params);
649            class.ctor_init_sig_idx = next_index(next_type_idx)?;
650            types.ty().function(init_params, Vec::new());
651        }
652        Ok(())
653    }
654
655    fn vtable_subtype(
656        &self,
657        class: &ClassLayout,
658        intrinsics: IntrinsicTypeIndices,
659        symbols: &SymbolTable,
660    ) -> Result<SubType, CompilerFailure> {
661        let supertype = vtable_supertype_idx(class, intrinsics, symbols)?;
662        let mut fields: Vec<FieldType> = vec![
663            fieldtype_ref(intrinsics.to_string_fn),
664            fieldtype_ref(intrinsics.to_json_fn),
665            fieldtype_ref(intrinsics.equals_fn),
666            fieldtype_ref(intrinsics.hash_fn),
667            fieldtype_ref_null(intrinsics.class_vtable),
668            FieldType {
669                element_type: StorageType::Val(ValType::I32),
670                mutable: false,
671            },
672        ];
673        for slot in &class.methods {
674            fields.push(fieldtype_ref(slot.sig_idx));
675        }
676        Ok(substruct(fields, Some(supertype)))
677    }
678
679    fn struct_subtype(
680        &self,
681        class: &ClassLayout,
682        intrinsics: IntrinsicTypeIndices,
683        symbols: &SymbolTable,
684    ) -> Result<SubType, CompilerFailure> {
685        let supertype = struct_supertype_idx(class, intrinsics, symbols)?;
686        // Header slots 0-2: vtable (refined to this class's vtable),
687        // field-names, object-fields. Wasm requires the full supertype prefix
688        // to be re-listed.
689        let fields: Vec<FieldType> = vec![
690            fieldtype_ref(class.vtable_type_idx),
691            FieldType {
692                mutable: true,
693                ..fieldtype_ref(intrinsics.field_names)
694            },
695            FieldType {
696                mutable: true,
697                ..fieldtype_ref(intrinsics.object_fields)
698            },
699            FieldType {
700                element_type: StorageType::Val(ValType::Ref(RefType::ANYREF)),
701                mutable: true,
702            },
703            FieldType {
704                element_type: StorageType::Val(ValType::I64),
705                mutable: true,
706            },
707        ];
708        Ok(substruct(fields, Some(supertype)))
709    }
710
711    /// Phase: reserve function indices — 4 shared universal stubs, then per-class
712    /// method stubs (one per own method) + getter + setter.
713    pub fn allocate_funcs(
714        &mut self,
715        next_func_idx: &mut u32,
716        symbols: &mut SymbolTable,
717    ) -> Result<(), CompilerFailure> {
718        if self.classes.is_empty() {
719            return Ok(());
720        }
721        self.universal_stubs = [
722            next_index(next_func_idx)?,
723            next_index(next_func_idx)?,
724            next_index(next_func_idx)?,
725            next_index(next_func_idx)?,
726        ];
727        // Borrow-checker: build the (mangled, ctor params, ret) tuples first so we
728        // can call the `&self` helper `class_ref_ty` before the `&mut` loop.
729        let ctor_infos: Vec<(MangledName, Vec<crate::Type>, crate::Type)> = self
730            .classes
731            .iter()
732            .map(|c| {
733                (
734                    crate::mangle::extend(&c.mangled, "constructor"),
735                    c.ctor_params.iter().map(|p| p.ty.clone()).collect(),
736                    self.class_ref_ty(c),
737                )
738            })
739            .collect();
740        for (class, (ctor_mangled, ctor_param_tys, ret)) in self.classes.iter_mut().zip(ctor_infos)
741        {
742            // Constructor first (its index backs the `new Foo(...)` direct call),
743            // then its init body (backs `super(...)` and the entry's delegation).
744            class.ctor_func_idx = next_index(next_func_idx)?;
745            symbols.record_local_fn(ctor_mangled, class.ctor_func_idx, ctor_param_tys, ret)?;
746            class.ctor_init_func_idx = next_index(next_func_idx)?;
747            symbols.record_class_ctor_init_func(class.mangled.clone(), class.ctor_init_func_idx);
748            symbols.record_class_field_setup(class.mangled.clone(), class.field_setup.clone());
749            for method in &class.own_methods {
750                let idx = next_index(next_func_idx)?;
751                symbols.record_class_method_func(class.mangled.clone(), method.name.clone(), idx);
752            }
753            // One closure-ABI adapter per non-generic *slot* — inherited ones
754            // included, since an interface-typed receiver scans the instance
755            // payload, which carries the whole chain. An override rewrites the
756            // slot's owner to this class, so a declared method still allocates
757            // here; a slot inherited from a local ancestor reuses that
758            // ancestor's adapter, which is valid because the adapter takes its
759            // receiver as `(ref $Object)` and casts.
760            let mut adapter_slots: Vec<usize> = Vec::new();
761            for (i, slot) in class.methods.iter().enumerate() {
762                if slot.generic {
763                    continue;
764                }
765                let reused = (slot.owner != class.mangled)
766                    .then(|| symbols.class_method_adapter_func_idx(&slot.owner, &slot.name))
767                    .flatten();
768                let idx = match reused {
769                    Some(idx) => idx,
770                    None => next_index(next_func_idx)?,
771                };
772                if reused.is_none() {
773                    adapter_slots.push(i);
774                    // Also key it by the owner, so descendants of this class
775                    // find it. Without this an import-owned slot re-emits an
776                    // identical adapter at every level of a local chain: the
777                    // owner is the imported class all the way down, and only
778                    // the emitting class was ever recorded.
779                    symbols.record_class_method_adapter_func(
780                        slot.owner.clone(),
781                        slot.name.clone(),
782                        idx,
783                    );
784                }
785                symbols.record_class_method_adapter_func(
786                    class.mangled.clone(),
787                    slot.name.clone(),
788                    idx,
789                );
790            }
791            class.adapter_slots = adapter_slots;
792            // Retained temporarily for stable function/global numbering while
793            // class field dispatch migrates to the object-fields payload.
794            class.getter_func_idx = next_index(next_func_idx)?;
795            class.setter_func_idx = next_index(next_func_idx)?;
796            class.equals_func_idx = next_index(next_func_idx)?;
797            class.to_string_func_idx = next_index(next_func_idx)?;
798            class.to_json_func_idx = next_index(next_func_idx)?;
799            class.hash_func_idx = next_index(next_func_idx)?;
800            // Field order is allocation order, which the body emission follows.
801            class.guarded_bodies = GuardedBodies {
802                equals: next_index(next_func_idx)?,
803                to_json: next_index(next_func_idx)?,
804                hash: next_index(next_func_idx)?,
805            };
806        }
807        Ok(())
808    }
809
810    /// Phase: function-section entries for every emitted function, in the same
811    /// order as `allocate_funcs` / `emit_bodies`.
812    pub fn emit_function_entries(
813        &self,
814        functions: &mut FunctionSection,
815        symbols: &SymbolTable,
816        intrinsics: IntrinsicTypeIndices,
817    ) -> Result<(), crate::compiler_error::CompilerFailure> {
818        if self.classes.is_empty() {
819            return Ok(());
820        }
821        functions.function(intrinsics.to_string_fn);
822        functions.function(intrinsics.to_json_fn);
823        functions.function(intrinsics.equals_fn);
824        functions.function(intrinsics.hash_fn);
825        for class in &self.classes {
826            functions.function(class.ctor_sig_idx);
827            functions.function(class.ctor_init_sig_idx);
828            for method in &class.own_methods {
829                let sig_idx = class
830                    .methods
831                    .iter()
832                    .find(|s| s.name == method.name)
833                    .ok_or_else(|| internal_failure("a class's own method has no vtable slot"))?
834                    .sig_idx;
835                functions.function(sig_idx);
836            }
837            for &i in &class.adapter_slots {
838                let ty = symbols
839                    .closure_func_type_idx(slot_closure_sig(class.methods.get(i).ok_or_else(
840                        || {
841                            crate::codegen::internal_failure(
842                                "class adapter refers to a missing method slot",
843                            )
844                        },
845                    )?)?)
846                    .ok_or_else(|| {
847                        crate::codegen::internal_failure(
848                            "class method closure sig registered (closures::class_member_sigs)",
849                        )
850                    })?;
851                functions.function(ty);
852            }
853            functions.function(intrinsics.field_getter);
854            functions.function(intrinsics.field_setter);
855            functions.function(intrinsics.equals_fn);
856            functions.function(intrinsics.to_string_fn);
857            functions.function(intrinsics.to_json_fn);
858            functions.function(intrinsics.hash_fn);
859            functions.function(intrinsics.equals_fn);
860            functions.function(intrinsics.to_json_fn);
861            functions.function(intrinsics.hash_fn);
862        }
863        Ok(())
864    }
865
866    /// Phase: vtable instance, getter, and setter globals (the field-names array
867    /// global is emitted separately via `field_names::emit`).
868    pub fn emit_globals(
869        &self,
870        globals: &mut GlobalSection,
871        symbols: &mut SymbolTable,
872        next_global_idx: &mut u32,
873        intrinsics: IntrinsicTypeIndices,
874    ) -> Result<(), CompilerFailure> {
875        for class in &self.classes {
876            // Vtable instance: the four per-class universal bodies + the
877            // parent-vtable link + a ref.func per method slot (resolved to the
878            // owning class's body), then struct.new $Foo_vtable. The parent
879            // link is a global.get of the parent's singleton — an earlier
880            // defined global (topo order, parents first) or an imported one
881            // (cross-package / `Error`); both are valid in a const expr.
882            let parent_link = match &class.parent {
883                Some(p) => {
884                    Instruction::GlobalGet(symbols.class_vtable_global_idx(p).ok_or_else(|| {
885                        internal_failure("a parent vtable global was not recorded before its child")
886                    })?)
887                }
888                None => Instruction::RefNull(HeapType::Concrete(intrinsics.class_vtable)),
889            };
890            let mut instrs: Vec<Instruction<'_>> = vec![
891                Instruction::RefFunc(class.to_string_func_idx),
892                Instruction::RefFunc(class.to_json_func_idx),
893                Instruction::RefFunc(class.equals_func_idx),
894                Instruction::RefFunc(class.hash_func_idx),
895                parent_link,
896                Instruction::I32Const(i32::from(
897                    !class.methods.iter().any(|slot| slot.name == "toJson"),
898                )),
899            ];
900            for slot in &class.methods {
901                let func = symbols
902                    .class_method_func_idx(&slot.owner, &slot.name)
903                    .ok_or_else(|| internal_failure("a class method body was not allocated"))?;
904                instrs.push(Instruction::RefFunc(func));
905            }
906            instrs.push(Instruction::StructNew(class.vtable_type_idx));
907            globals.global(
908                GlobalType {
909                    val_type: ref_to(class.vtable_type_idx),
910                    mutable: false,
911                    shared: false,
912                },
913                &ConstExpr::extended(instrs),
914            );
915            symbols.record_class_vtable_global(class.mangled.clone(), next_index(next_global_idx)?);
916
917            // Getter + setter globals are no longer part of ObjectShape, but keep
918            // emitting them for now so existing numbering remains stable.
919            globals.global(
920                GlobalType {
921                    val_type: ref_to(intrinsics.field_getter),
922                    mutable: false,
923                    shared: false,
924                },
925                &ConstExpr::extended([Instruction::RefFunc(class.getter_func_idx)]),
926            );
927            symbols.record_class_getter_global(class.mangled.clone(), next_index(next_global_idx)?);
928
929            globals.global(
930                GlobalType {
931                    val_type: ref_to(intrinsics.field_setter),
932                    mutable: false,
933                    shared: false,
934                },
935                &ConstExpr::extended([Instruction::RefFunc(class.setter_func_idx)]),
936            );
937            symbols.record_class_setter_global(class.mangled.clone(), next_index(next_global_idx)?);
938        }
939        Ok(())
940    }
941
942    /// Phase: function bodies — constructor + init, method bodies and their
943    /// closure adapters, then the retired getter/setter stubs (kept for stable
944    /// numbering) and the four per-class universal vtable bodies
945    /// (equals/toString/toJson/hash).
946    pub fn emit_bodies(
947        &self,
948        code: &mut CodeSection,
949        debug: &mut super::dwarf::DebugFunctions,
950        ctx: &crate::codegen::CodegenCtx<'_>,
951    ) -> Result<(), crate::compiler_error::CompilerFailure> {
952        if self.classes.is_empty() {
953            return Ok(());
954        }
955        // The slot-2 stub is unreferenced (each class emits its own equals
956        // body) but kept so function numbering stays in lockstep with
957        // `allocate_funcs` / `emit_function_entries`.
958        for _ in 0..UNIVERSAL_STUB_COUNT {
959            code.function(&stub_body());
960        }
961        for class in &self.classes {
962            code.function(&self.emit_ctor_body(class, ctx)?);
963            // The constructor body and field initializers run in the init fn, so
964            // that is the frame a failure in them names.
965            debug.write(
966                code,
967                self.emit_ctor_init(class, ctx)?,
968                format!("new {}", class.name),
969                class.name_span,
970            )?;
971            for method in &class.own_methods {
972                if method.generic {
973                    code.function(&stub_body());
974                    continue;
975                }
976                debug.write(
977                    code,
978                    self.emit_method_body(class, method, ctx)?,
979                    method.frame_name.clone(),
980                    method.name_span,
981                )?;
982            }
983            for &i in &class.adapter_slots {
984                code.function(&self.emit_method_adapter_body(
985                    class,
986                    class.methods.get(i).ok_or_else(|| {
987                        crate::codegen::internal_failure(
988                            "class adapter refers to a missing method slot",
989                        )
990                    })?,
991                    ctx,
992                )?);
993            }
994            // getter, then setter.
995            code.function(&stub_body());
996            code.function(&stub_body());
997            let intrinsics = ctx
998                .symbols
999                .intrinsic_type_indices()
1000                .ok_or_else(|| internal_failure("intrinsic types are not declared"))?;
1001            let bodies = class.guarded_bodies;
1002            code.function(&super::vtable_walk::guarded_body(
1003                bodies.equals,
1004                2,
1005                ValType::I32,
1006                ctx.symbols,
1007            )?);
1008            let string_vtable_global_idx = ctx
1009                .symbols
1010                .prelude_global_idx("string_vtable")
1011                .ok_or_else(|| {
1012                    internal_failure("string_vtable is not imported from the prelude")
1013                })?;
1014            // A user `toString`/`toJson` method fills the universal slot (the
1015            // universal slots require strings even when the authored method's
1016            // physical return has widened to preserve live values).
1017            let user_method_thunk =
1018                |name: &str| -> Result<Option<Function>, crate::compiler_error::CompilerFailure> {
1019                    class
1020                        .methods
1021                        .iter()
1022                        .find(|s| s.name == name)
1023                        .map(|slot| emit_universal_slot_thunk(ctx, &slot.name))
1024                        .transpose()
1025                };
1026            let extends_error = ctx
1027                .symbols
1028                .class_struct_type_idx(&crate::mangle::prelude("Error"))
1029                .is_some_and(|error_type| {
1030                    ctx.symbols.ref_fits_slot(
1031                        RefType {
1032                            nullable: false,
1033                            heap_type: HeapType::Concrete(class.struct_type_idx),
1034                        },
1035                        RefType {
1036                            nullable: false,
1037                            heap_type: HeapType::Concrete(error_type),
1038                        },
1039                    )
1040                });
1041            let to_string = match user_method_thunk("toString")? {
1042                Some(thunk) => thunk,
1043                None if class.fields.iter().any(|field| field.name == "toString") => {
1044                    emit_class_to_string_field_body(ctx.symbols, intrinsics)?
1045                }
1046                None if extends_error => {
1047                    emit_error_slot_body(ctx.symbols, intrinsics, 0, intrinsics.to_string_fn)?
1048                }
1049                None => emit_class_to_string_body(intrinsics, string_vtable_global_idx)?,
1050            };
1051            code.function(&to_string);
1052            code.function(&super::vtable_walk::guarded_body(
1053                bodies.to_json,
1054                1,
1055                ref_to(intrinsics.string),
1056                ctx.symbols,
1057            )?);
1058            code.function(&super::vtable_walk::guarded_body(
1059                bodies.hash,
1060                1,
1061                ValType::I32,
1062                ctx.symbols,
1063            )?);
1064            let field_count = wasm_u32(class.fields.len())?;
1065            if extends_error {
1066                let mut equals = Function::new([]);
1067                equals.instruction(&Instruction::LocalGet(0));
1068                equals.instruction(&Instruction::LocalGet(1));
1069                equals.instruction(&Instruction::RefEq);
1070                equals.instruction(&Instruction::End);
1071                code.function(&equals);
1072            } else {
1073                code.function(&emit_class_equals_body(field_count, intrinsics));
1074            }
1075            let to_json = match user_method_thunk("toJson")? {
1076                Some(thunk) => thunk,
1077                None => emit_class_to_json_body(ctx.symbols)?,
1078            };
1079            code.function(&to_json);
1080            if extends_error {
1081                code.function(&emit_error_slot_body(
1082                    ctx.symbols,
1083                    intrinsics,
1084                    3,
1085                    intrinsics.hash_fn,
1086                )?);
1087            } else {
1088                code.function(&emit_class_hash_body(field_count, intrinsics));
1089            }
1090        }
1091        Ok(())
1092    }
1093
1094    /// Closure-ABI adapter for one vtable slot: forward the boxed args, call the
1095    /// body backing the slot (self-first), and box a primitive slot return. `env` (slot 0) is the receiver — cast it to `(ref $Object)` for
1096    /// the body's self param, which is why one adapter serves every subclass.
1097    /// Mirrors [`function_adapters::emit_bodies`].
1098    ///
1099    /// `class` is used only for the ABI lookup; every other input comes from
1100    /// the slot. That is sound because a slot's `MethodSlotAbi` is
1101    /// chain-invariant — `reserve_types` propagates the declarer's verbatim,
1102    /// and the one place a slot's types are rewritten (an override) rewrites
1103    /// `owner` too, so an overridden slot never shares an adapter. Classes
1104    /// sharing an adapter therefore necessarily share its ABI. Make a slot's
1105    /// ABI class-dependent and that stops holding, with no validation error to
1106    /// catch it — reuse would box against the wrong shape.
1107    fn emit_method_adapter_body(
1108        &self,
1109        class: &ClassLayout,
1110        slot: &MethodSlot,
1111        ctx: &crate::codegen::CodegenCtx<'_>,
1112    ) -> Result<Function, crate::compiler_error::CompilerFailure> {
1113        use crate::codegen::function_emitter::{FunctionEmitter, cast};
1114
1115        slot_closure_sig(slot)?;
1116        let intrinsics = ctx.symbols.intrinsic_type_indices().ok_or_else(|| {
1117            crate::codegen::internal_failure("intrinsics declared by codegen entry")
1118        })?;
1119        let object_ref_null = ValType::Ref(RefType {
1120            nullable: true,
1121            heap_type: HeapType::Concrete(intrinsics.object),
1122        });
1123        let any_ref = ValType::Ref(RefType {
1124            nullable: false,
1125            heap_type: HeapType::ANY,
1126        });
1127        let mut wasm_params: Vec<(crate::Ident, ValType)> = vec![(
1128            crate::Ident {
1129                name: "$__env__".to_string(),
1130                span: crate::Span::at(ctx.file),
1131            },
1132            any_ref,
1133        )];
1134        for (i, _) in slot.param_tys.iter().enumerate() {
1135            wasm_params.push((
1136                crate::Ident {
1137                    name: format!("$__arg{i}__"),
1138                    span: crate::Span::at(ctx.file),
1139                },
1140                object_ref_null,
1141            ));
1142        }
1143        let mut emitter = FunctionEmitter::new(ctx, &wasm_params)?;
1144        // The owner supplies the body — a local ancestor's, or an imported
1145        // one's, both recorded under the owner's mangled name.
1146        let method_func = ctx
1147            .symbols
1148            .class_method_func_idx(&slot.owner, &slot.name)
1149            .ok_or_else(|| {
1150                crate::codegen::internal_failure("method body func allocated or imported")
1151            })?;
1152
1153        // self = env, cast to the method body's `(ref $Object)` self param.
1154        // The callee's physical signature is the slot's. Arguments are boxed;
1155        // reference results already carry the value the closure ABI wants.
1156        let abi = ctx
1157            .symbols
1158            .class_method_abi(&class.mangled, &slot.name)
1159            .cloned()
1160            .ok_or_else(|| {
1161                crate::codegen::internal_failure("class method slot ABI is not registered")
1162            })?;
1163        if abi.params.len() != slot.param_tys.len() {
1164            return Err(crate::codegen::internal_failure(
1165                "class method slot ABI has the wrong parameter count",
1166            ));
1167        }
1168        emitter.instruction(Instruction::LocalGet(0));
1169        if slot.argument_metadata.is_some() {
1170            super::call_arguments::unwrap(&mut emitter, ctx)?;
1171        }
1172        emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(
1173            intrinsics.object,
1174        )));
1175        for (i, ty) in slot.param_tys.iter().enumerate() {
1176            emitter.instruction(Instruction::LocalGet(parameter_local(i)?));
1177            if abi.params.get(i) != Some(&object_ref_null) {
1178                crate::codegen::cast_check::emit_checked_parameter_cast_on_stack(
1179                    &mut emitter,
1180                    ctx,
1181                    &crate::Type::Unknown,
1182                    ty,
1183                )?;
1184            }
1185        }
1186        emitter.instruction(Instruction::Call(method_func));
1187        match abi.ret {
1188            Some(ValType::F64) => cast::emit_box(&mut emitter, ctx, &crate::Type::Number)?,
1189            Some(ValType::I32) => cast::emit_box(&mut emitter, ctx, &crate::Type::Boolean)?,
1190            // A never-returning override can inherit a void slot. Its closure
1191            // convention has a result, but the inherited call cannot return.
1192            None if matches!(slot.ret_ty.peel(), crate::Type::Never) => {
1193                emitter.instruction(Instruction::Unreachable);
1194            }
1195            _ => {}
1196        }
1197        emitter.build()
1198    }
1199
1200    /// Method body: bind `this` (`local.get 0 ; ref.cast (ref $Foo)` — the vtable
1201    /// ABI's self param is `(ref $Object)`), then run the typed body. Returns the
1202    /// body's line rows with it. A method with its own type parameters is
1203    /// rejected by the typechecker, so [`Self::emit_bodies`] writes an
1204    /// unreachable stub in its place as defence rather than pending work.
1205    ///
1206    /// The function's Wasm signature is the slot's — the originating ancestor's,
1207    /// which an override's own annotations may differ from in either direction:
1208    /// wider, where a generic class's params/returns erase to
1209    /// `(ref null $Object)`, or narrower, where the ancestor spelled a
1210    /// structural type the override reaches through an interface. So each param
1211    /// binds through a shadow local cast to the body's own type, and each
1212    /// `return` coerces back into the slot. Byte-identical when own == origin.
1213    fn emit_method_body(
1214        &self,
1215        class: &ClassLayout,
1216        method: &OwnMethod,
1217        ctx: &crate::codegen::CodegenCtx<'_>,
1218    ) -> Result<(Function, Vec<(u64, crate::Span)>), crate::compiler_error::CompilerFailure> {
1219        use crate::codegen::function_emitter::{
1220            FunctionEmitter, ReturnTarget, emit_body_end, stmt,
1221        };
1222
1223        let object_idx = ctx
1224            .symbols
1225            .intrinsic_type_indices()
1226            .ok_or_else(|| internal_failure("intrinsic types are not declared"))?
1227            .object;
1228        let abi = ctx
1229            .symbols
1230            .class_method_abi(&class.mangled, &method.name)
1231            .cloned()
1232            .ok_or_else(|| internal_failure("class method slot ABI is not registered"))?;
1233        let mut wasm_params: Vec<(crate::Ident, ValType)> = vec![(
1234            crate::Ident {
1235                name: "this".to_string(),
1236                span: crate::Span::at(ctx.file),
1237            },
1238            ref_to(object_idx),
1239        )];
1240        wasm_params.extend(slot_params(ctx, &method.params, &abi.params)?);
1241        let mut emitter = FunctionEmitter::new(ctx, &wasm_params)?;
1242        let param_slots = rebind_erased_params(&mut emitter, ctx, &method.params, &abi.params)?;
1243        emitter.prepare_parameter_initialization(Some(method.body))?;
1244        // Param prologue boxes captured-mutated method params.
1245        emitter.emit_boxed_param_prologue(&method.params, &param_slots)?;
1246
1247        // `this` = ref.cast of the self param to the concrete class.
1248        let this_slot = emitter.add_anonymous_local(ref_to(class.struct_type_idx))?;
1249        emitter.instruction(Instruction::LocalGet(0));
1250        emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(
1251            class.struct_type_idx,
1252        )));
1253        emitter.instruction(Instruction::LocalSet(this_slot));
1254        emitter.set_this_local(this_slot)?;
1255        super::field_guards::bind_receiver(&mut emitter, ctx, this_slot, &class.mangled)?;
1256
1257        {
1258            emitter.set_return_target(ReturnTarget::Slot(match abi.ret {
1259                Some(ty) => ty,
1260                None => ctx.symbols.slot_value_type(&method.return_type)?,
1261            }));
1262        }
1263        stmt::emit_statement(&mut emitter, ctx, method.body)?;
1264        emit_body_end(&mut emitter, ctx, &method.return_type)?;
1265        emitter.build_with_lines()
1266    }
1267
1268    /// Entry constructor (`new Foo(...)`): `struct.new $Foo` (header globals +
1269    /// default field values + method closures) into a `this` local, delegate
1270    /// field initialization to the self-first init fn, then return the instance.
1271    /// Identical for base and derived classes — the struct is allocated once at
1272    /// the most-derived type, and `super(...)` chains run inside the init fns.
1273    fn emit_ctor_body(
1274        &self,
1275        class: &ClassLayout,
1276        ctx: &crate::codegen::CodegenCtx<'_>,
1277    ) -> Result<Function, crate::compiler_error::CompilerFailure> {
1278        use crate::codegen::function_emitter::FunctionEmitter;
1279
1280        // Parameters stay at their (possibly erased) slot types: this body only
1281        // forwards them to the init fn, which takes the same slots.
1282        let ctor_slots = ctor_slot_types(ctx, &class.mangled)?;
1283        let mut wasm_params = slot_params(ctx, &class.ctor_params, &ctor_slots)?;
1284        let guarded = ctx
1285            .symbols
1286            .recorded_class_guard_layout(&class.mangled)?
1287            .has_instance_guards;
1288        if guarded {
1289            let intr = ctx
1290                .symbols
1291                .intrinsic_type_indices()
1292                .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?;
1293            wasm_params.push((
1294                crate::Ident {
1295                    name: "$field_guards".into(),
1296                    span: crate::Span::at(ctx.file),
1297                },
1298                ref_to(intr.object_fields),
1299            ));
1300        }
1301        let mut emitter = FunctionEmitter::new(ctx, &wasm_params)?;
1302
1303        let this_slot = emitter.add_anonymous_local(ref_to(class.struct_type_idx))?;
1304        emitter.set_this_local(this_slot)?;
1305
1306        // struct.new $Foo: vtable + field-names + object-fields payload. The
1307        // payload holds the data fields (slots `0..N_fields`, written by the init
1308        // fn) then one closure per non-generic method (slots `N_fields..`,
1309        // populated by the prologue below). The field-names list must match
1310        // `class_field_names` so the dynamic field-name scan resolves both.
1311        let dynamic_methods: Vec<&MethodSlot> = class.payload_slots().collect();
1312        let n_fields = wasm_u32(class.fields.len())?;
1313        let field_names = class.payload_field_names();
1314        let push_global =
1315            |em: &mut FunctionEmitter, idx: u32| em.instruction(Instruction::GlobalGet(idx));
1316        push_global(
1317            &mut emitter,
1318            ctx.symbols
1319                .class_vtable_global_idx(&class.mangled)
1320                .ok_or_else(|| crate::codegen::internal_failure("vtable global"))?,
1321        );
1322        super::field_names::emit_instance_names(&mut emitter, ctx, &field_names, |_| false)?;
1323        let intrinsics = ctx.symbols.intrinsic_type_indices().ok_or_else(|| {
1324            crate::codegen::internal_failure("intrinsics declared by codegen entry")
1325        })?;
1326        // Instance validators live after the named payload, indexed by data
1327        // slot. Keeping them out of field_names preserves object enumeration.
1328        let layout = ctx.symbols.recorded_class_guard_layout(&class.mangled)?;
1329        let named_len = wasm_u32(field_names.len())?;
1330        let guard_slots = if layout.has_instance_guards {
1331            guard_slot_count(layout.inheritance_depth, named_len)?
1332        } else {
1333            0
1334        };
1335        let payload_len = named_len
1336            .checked_add(guard_slots)
1337            .ok_or_else(|| internal_failure("a class instance payload is too large"))?;
1338        for slot in 0..payload_len {
1339            if slot < named_len {
1340                super::function_emitter::expr::emit_undefined(&mut emitter, ctx)?;
1341            } else {
1342                emitter.instruction(Instruction::RefNull(HeapType::Concrete(intrinsics.object)));
1343            }
1344        }
1345        emitter.instruction(Instruction::ArrayNewFixed {
1346            array_type_index: intrinsics.object_fields,
1347            array_size: payload_len,
1348        });
1349        emitter.instruction(Instruction::RefNull(HeapType::ANY));
1350        emitter.instruction(Instruction::I64Const(0));
1351        emitter.instruction(Instruction::StructNew(class.struct_type_idx));
1352        emitter.instruction(Instruction::LocalSet(this_slot));
1353
1354        if layout.has_instance_guards {
1355            emitter.instruction(Instruction::LocalGet(this_slot));
1356            emitter.instruction(Instruction::StructGet {
1357                struct_type_index: class.struct_type_idx,
1358                field_index: 2,
1359            });
1360            emitter.instruction(Instruction::I32Const(named_len.cast_signed()));
1361            emitter.instruction(Instruction::LocalGet(wasm_u32(class.ctor_params.len())?));
1362            emitter.instruction(Instruction::I32Const(0));
1363            emitter.instruction(Instruction::I32Const(guard_slots.cast_signed()));
1364            emitter.instruction(Instruction::ArrayCopy {
1365                array_type_index_dst: intrinsics.object_fields,
1366                array_type_index_src: intrinsics.object_fields,
1367            });
1368        }
1369
1370        // Method-closure prologue: store `$closure_<sig>{ closure_vtable, adapter,
1371        // this }` into each method's payload slot, so an interface-typed receiver
1372        // dispatches it through the shared field-name scan.
1373        if !dynamic_methods.is_empty() {
1374            let closure_vtable = ctx.symbols.closure_vtable_global_idx().ok_or_else(|| {
1375                crate::codegen::internal_failure(
1376                    "closure vtable global emitted when class methods exist",
1377                )
1378            })?;
1379            for (j, method) in dynamic_methods.iter().enumerate() {
1380                let method_slot = n_fields
1381                    .checked_add(wasm_u32(j)?)
1382                    .ok_or_else(|| internal_failure("a class instance payload is too large"))?;
1383                let closure_struct = ctx
1384                    .symbols
1385                    .closure_struct_type_idx(slot_closure_sig(method)?)
1386                    .ok_or_else(|| {
1387                        crate::codegen::internal_failure("class method closure struct registered")
1388                    })?;
1389                let adapter = ctx
1390                    .symbols
1391                    .class_method_adapter_func_idx(&class.mangled, &method.name)
1392                    .ok_or_else(|| {
1393                        crate::codegen::internal_failure(
1394                            "class method adapter allocated or reused from the owner",
1395                        )
1396                    })?;
1397                emitter.instruction(Instruction::LocalGet(this_slot));
1398                emitter.instruction(Instruction::StructGet {
1399                    struct_type_index: class.struct_type_idx,
1400                    field_index: 2,
1401                });
1402                emitter.instruction(Instruction::I32Const(method_slot.cast_signed()));
1403                emitter.instruction(Instruction::GlobalGet(closure_vtable));
1404                emitter.instruction(Instruction::RefFunc(adapter));
1405                emitter.instruction(Instruction::LocalGet(this_slot));
1406                if let Some(metadata) = &method.argument_metadata {
1407                    super::call_arguments::wrap(&mut emitter, ctx, metadata)?;
1408                }
1409                emitter.instruction(Instruction::I64Const(0));
1410                emitter.instruction(Instruction::StructNew(closure_struct));
1411                emitter.instruction(Instruction::ArraySet(intrinsics.object_fields));
1412            }
1413        }
1414
1415        // Delegate to the self-first init fn: `Foo#ctor_init(this, params...)`.
1416        // It runs the constructor body (or forwards to the parent for an implicit
1417        // constructor), where `super(...)` chains up to the parent init.
1418        emitter.instruction(Instruction::LocalGet(this_slot));
1419        for i in 0..wasm_u32(class.ctor_params.len())? {
1420            emitter.instruction(Instruction::LocalGet(i));
1421        }
1422        emitter.instruction(Instruction::Call(class.ctor_init_func_idx));
1423
1424        // Implicit `return this`. (Explicit `return;` inside a constructor is not
1425        // supported in this slice — the fixtures don't use it.)
1426        emitter.instruction(Instruction::LocalGet(this_slot));
1427        emitter.build()
1428    }
1429
1430    /// Constructor init fn: self-first ABI `((ref $Object), params...) -> ()` that
1431    /// initializes fields on an already-allocated instance. `super(...)` lowers to
1432    /// a direct call of the parent's init fn (see [`expr`] codegen). A class with
1433    /// no declared constructor but a parent (implicit constructor) forwards all
1434    /// params to the parent init. Returns the body's line rows with it.
1435    fn emit_ctor_init(
1436        &self,
1437        class: &ClassLayout,
1438        ctx: &crate::codegen::CodegenCtx<'_>,
1439    ) -> Result<(Function, Vec<(u64, crate::Span)>), crate::compiler_error::CompilerFailure> {
1440        use crate::codegen::function_emitter::{FunctionEmitter, cast, stmt};
1441
1442        let object_idx = ctx
1443            .symbols
1444            .intrinsic_type_indices()
1445            .ok_or_else(|| internal_failure("intrinsic types are not declared"))?
1446            .object;
1447        let mut wasm_params: Vec<(crate::Ident, ValType)> = vec![(
1448            crate::Ident {
1449                name: "this".to_string(),
1450                span: crate::Span::at(ctx.file),
1451            },
1452            ref_to(object_idx),
1453        )];
1454        let ctor_slots = ctor_slot_types(ctx, &class.mangled)?;
1455        wasm_params.extend(slot_params(ctx, &class.ctor_params, &ctor_slots)?);
1456        let mut emitter = FunctionEmitter::new(ctx, &wasm_params)?;
1457        let param_slots = rebind_erased_params(&mut emitter, ctx, &class.ctor_params, &ctor_slots)?;
1458        emitter.prepare_parameter_initialization(class.ctor_body)?;
1459        // Box captured-mutated ctor params.
1460        emitter.emit_boxed_param_prologue(&class.ctor_params, &param_slots)?;
1461
1462        // `this` = ref.cast of the self param to the concrete class.
1463        let this_slot = emitter.add_anonymous_local(ref_to(class.struct_type_idx))?;
1464        emitter.instruction(Instruction::LocalGet(0));
1465        emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(
1466            class.struct_type_idx,
1467        )));
1468        emitter.instruction(Instruction::LocalSet(this_slot));
1469        emitter.set_this_local(this_slot)?;
1470        super::field_guards::bind_receiver(&mut emitter, ctx, this_slot, &class.mangled)?;
1471        emitter.set_ctor_class(class.mangled.clone());
1472
1473        if let Some(body) = class.ctor_body {
1474            if class.parent.is_none() {
1475                super::function_emitter::expr::emit_class_field_setup_phase(
1476                    &mut emitter,
1477                    ctx,
1478                    &class.mangled,
1479                    super::function_emitter::expr::FieldSetupPhase::Initializers,
1480                )?;
1481            }
1482            let defaults = ctx
1483                .ta
1484                .parameter_default_prologues
1485                .get(&body)
1486                .copied()
1487                .unwrap_or(0);
1488            if class.parent.is_none() && defaults > 0 {
1489                let body = ctx.ta.try_stmt(body).map_err(super::arena_failure)?;
1490                let crate::TypedStmtKind::Block(statements) = &body.kind else {
1491                    return Err(internal_failure(
1492                        "constructor default prologue is not a block",
1493                    ));
1494                };
1495                let prologue = statements.get(..defaults).ok_or_else(|| {
1496                    internal_failure("constructor default prologue is incomplete")
1497                })?;
1498                let body = statements.get(defaults..).ok_or_else(|| {
1499                    internal_failure("constructor body is missing after defaults")
1500                })?;
1501                emitter.push_scope();
1502                for &statement in prologue {
1503                    stmt::emit_statement(&mut emitter, ctx, statement)?;
1504                }
1505                super::function_emitter::expr::emit_class_field_setup_phase(
1506                    &mut emitter,
1507                    ctx,
1508                    &class.mangled,
1509                    super::function_emitter::expr::FieldSetupPhase::ParameterProperties,
1510                )?;
1511                for &statement in body {
1512                    stmt::emit_statement(&mut emitter, ctx, statement)?;
1513                }
1514                emitter.pop_scope()?;
1515            } else {
1516                if class.parent.is_none() {
1517                    super::function_emitter::expr::emit_class_field_setup_phase(
1518                        &mut emitter,
1519                        ctx,
1520                        &class.mangled,
1521                        super::function_emitter::expr::FieldSetupPhase::ParameterProperties,
1522                    )?;
1523                }
1524                stmt::emit_statement(&mut emitter, ctx, body)?;
1525            }
1526        } else {
1527            // Implicit constructor: forward all params to the parent init (if
1528            // any), then run this class's field setup.
1529            if let Some(parent) = &class.parent {
1530                let parent_init =
1531                    ctx.symbols
1532                        .class_ctor_init_func_idx(parent)
1533                        .ok_or_else(|| {
1534                            internal_failure("a parent constructor init was not allocated")
1535                        })?;
1536                // This class's parameters carry the `extends` clause's
1537                // substituted types, while the parent's own signature may
1538                // still erase them, so each one is coerced on the way through.
1539                // Forward from the rebound local, whose Wasm type is `p.ty`'s —
1540                // what the coercion takes as its source.
1541                let parent_slots = ctor_slot_types(ctx, parent)?;
1542                emitter.instruction(Instruction::LocalGet(this_slot));
1543                if param_slots.len() != class.ctor_params.len() {
1544                    return Err(internal_failure(
1545                        "constructor parameters and their locals differ in number",
1546                    ));
1547                }
1548                for (i, (p, local)) in class.ctor_params.iter().zip(&param_slots).enumerate() {
1549                    emitter.instruction(Instruction::LocalGet(*local));
1550                    if let Some(slot) = parent_slots.get(i).copied() {
1551                        cast::emit_coerce_to_wasm_slot(&mut emitter, ctx, &p.ty, slot)?;
1552                    }
1553                }
1554                emitter.instruction(Instruction::Call(parent_init));
1555            }
1556            crate::codegen::function_emitter::expr::emit_class_field_setup(
1557                &mut emitter,
1558                ctx,
1559                &class.mangled,
1560            )?;
1561        }
1562        emitter.build_with_lines()
1563    }
1564
1565    /// `(export name, func index)` pairs for every class function a cross-package
1566    /// consumer may import: the entry constructor, the self-first ctor-init, and
1567    /// every method body. Restricted to classes the package actually exports
1568    /// (`is_exported`). Method bodies include private methods — Wasm-level
1569    /// visibility is wider than source so a cross-package subclass can fill its
1570    /// inherited vtable slots (docs/classes.md §2, §6). Generic methods are
1571    /// excluded (still emitted as stubs this slice).
1572    pub fn exported_funcs(
1573        &self,
1574        symbols: &SymbolTable,
1575        is_exported: impl Fn(&MangledName) -> bool,
1576    ) -> Result<Vec<(MangledName, u32)>, CompilerFailure> {
1577        let mut out: Vec<(MangledName, u32)> = Vec::new();
1578        for class in &self.classes {
1579            if !is_exported(&class.mangled) {
1580                continue;
1581            }
1582            out.push((
1583                crate::mangle::extend(&class.mangled, "constructor"),
1584                class.ctor_func_idx,
1585            ));
1586            out.push((
1587                crate::mangle::extend(&class.mangled, "constructor_init"),
1588                class.ctor_init_func_idx,
1589            ));
1590            for method in &class.own_methods {
1591                if method.generic {
1592                    continue;
1593                }
1594                let idx = symbols
1595                    .class_method_func_idx(&class.mangled, &method.name)
1596                    .ok_or_else(|| internal_failure("a class method body was not allocated"))?;
1597                out.push((crate::mangle::extend(&class.mangled, &method.name), idx));
1598            }
1599        }
1600        Ok(out)
1601    }
1602
1603    /// Each exported class's vtable-singleton global, for the export section —
1604    /// the class's nominal identity, imported by consumers for `instanceof`'s
1605    /// ref.eq comparison and as a local subclass's parent link.
1606    pub fn exported_vtable_globals(
1607        &self,
1608        symbols: &SymbolTable,
1609        is_exported: impl Fn(&MangledName) -> bool,
1610    ) -> Result<Vec<(MangledName, u32)>, CompilerFailure> {
1611        self.classes
1612            .iter()
1613            .filter(|class| is_exported(&class.mangled))
1614            .map(|class| {
1615                let global_idx = symbols
1616                    .class_vtable_global_idx(&class.mangled)
1617                    .ok_or_else(|| internal_failure("a class vtable global was not emitted"))?;
1618                Ok((vtable_global_export_name(&class.mangled), global_idx))
1619            })
1620            .collect()
1621    }
1622
1623    /// Function indices that appear in a `ref.func` const-expr and so must be
1624    /// covered by the declarative element section.
1625    pub fn declared_funcs(&self, symbols: &SymbolTable) -> Vec<u32> {
1626        if self.classes.is_empty() {
1627            return Vec::new();
1628        }
1629        let mut out: Vec<u32> = self.universal_stubs.to_vec();
1630        for class in &self.classes {
1631            // Every vtable slot funcref the vtable global stores — own bodies,
1632            // overrides, and inherited slots whose body lives on an ancestor
1633            // (possibly imported from another package).
1634            for slot in &class.methods {
1635                if let Some(idx) = symbols.class_method_func_idx(&slot.owner, &slot.name) {
1636                    out.push(idx);
1637                }
1638            }
1639            // Method-closure adapters are `ref.func`'d in the constructor
1640            // prologue — one per payload slot, inherited ones included. The
1641            // dedup below absorbs the indices shared with an ancestor.
1642            for slot in class.payload_slots() {
1643                if let Some(idx) = symbols.class_method_adapter_func_idx(&class.mangled, &slot.name)
1644                {
1645                    out.push(idx);
1646                }
1647            }
1648            out.push(class.getter_func_idx);
1649            out.push(class.setter_func_idx);
1650            out.push(class.equals_func_idx);
1651            out.push(class.to_string_func_idx);
1652            out.push(class.to_json_func_idx);
1653            out.push(class.hash_func_idx);
1654        }
1655        out.sort_unstable();
1656        out.dedup();
1657        out
1658    }
1659}
1660
1661/// Constructor field-setup steps for a class: parameter-property copies (in
1662/// constructor-param order) followed by own-field initializers (in declaration
1663/// order). Parameter-property fields are the auto-assigned own fields; their copy
1664/// reads the matching constructor param (init-fn local `position + 1`; self is 0).
1665///
1666/// A field that only redeclares an inherited one contributes a
1667/// [`FieldSetup::Reset`] instead — see [`redeclares_uninitialized_slot`].
1668/// `inherited` names the slots the parent prefix already owns.
1669fn field_setup_steps(
1670    decl: &TypedClassDecl,
1671    inherited: &BTreeSet<String>,
1672) -> Result<Vec<FieldSetup>, CompilerFailure> {
1673    let mut steps = Vec::new();
1674    if let Some(ctor) = &decl.constructor {
1675        for field in decl.fields.iter().filter(|f| f.auto_assigned) {
1676            if let Some(param) = ctor.params.iter().find(|p| p.name.name == field.name.name) {
1677                steps.push(FieldSetup::ParamCopy {
1678                    field: field.name.name.clone(),
1679                    param: Box::new(param.clone()),
1680                });
1681            }
1682        }
1683    }
1684    for field in &decl.fields {
1685        if let Some(value) = field.initializer {
1686            steps.push(FieldSetup::Init {
1687                field: field.name.name.clone(),
1688                value,
1689            });
1690        } else if redeclares_uninitialized_slot(field, inherited) {
1691            steps.push(FieldSetup::Reset {
1692                field: field.name.name.clone(),
1693            });
1694        }
1695    }
1696    Ok(steps)
1697}
1698
1699/// Vtable field of the method in class slot `slot`, after the universal slots
1700/// and parent link.
1701pub(crate) fn method_vtable_slot(slot: usize) -> Result<u32, CompilerFailure> {
1702    VTABLE_METHOD_SLOT_BASE
1703        .checked_add(wasm_u32(slot)?)
1704        .ok_or_else(|| internal_failure("a class has too many vtable slots"))
1705}
1706
1707/// Guard payload slots: one guard per named slot plus its receiver marker, for
1708/// each class level from this one to the root.
1709pub(super) fn guard_slot_count(
1710    inheritance_depth: u32,
1711    named_len: u32,
1712) -> Result<u32, CompilerFailure> {
1713    inheritance_depth
1714        .checked_add(1)
1715        .zip(named_len.checked_add(1))
1716        .and_then(|(levels, per_level)| levels.checked_mul(per_level))
1717        .ok_or_else(|| internal_failure("a class guard payload is too large"))
1718}
1719
1720fn class_decl<'a>(
1721    by_mangled: &BTreeMap<&MangledName, &'a TypedClassDecl>,
1722    mangled: &MangledName,
1723) -> Result<&'a TypedClassDecl, CompilerFailure> {
1724    by_mangled
1725        .get(mangled)
1726        .copied()
1727        .ok_or_else(|| internal_failure("class order names an undeclared class"))
1728}
1729
1730/// Whether this own field shares an inherited slot without writing it. Such a
1731/// field would otherwise read back whatever the *parent's* initializer left
1732/// there — a value the redeclaration's (possibly narrower) type need not admit.
1733/// Only the optional case arises: a non-optional field with no initializer must
1734/// be assigned in the constructor (`definite_assignment`), which runs after this
1735/// setup.
1736fn redeclares_uninitialized_slot(
1737    field: &crate::TypedClassField,
1738    inherited: &BTreeSet<String>,
1739) -> bool {
1740    field.optional && !field.auto_assigned && inherited.contains(&field.name.name)
1741}
1742
1743/// Export/linker field name of a class's vtable-singleton global. The space in
1744/// the suffix keeps it out of the member namespace (same trick as
1745/// `get foo`/`set foo`), so no user method can collide with it.
1746pub(crate) fn vtable_global_export_name(mangled: &MangledName) -> MangledName {
1747    crate::mangle::extend(mangled, "vtable global")
1748}
1749
1750/// Internal name of the vtable method an accessor property's getter lowers to.
1751/// The space keeps it disjoint from any user identifier, so it never collides
1752/// with a real method; it is also the runtime key under which the getter closure
1753/// is stored in the object payload. Codegen-only — not a mangled symbol name.
1754pub(crate) fn accessor_getter_name(prop: &str) -> String {
1755    format!("get {prop}")
1756}
1757
1758/// Internal name of the vtable method an accessor property's setter lowers to.
1759pub(crate) fn accessor_setter_name(prop: &str) -> String {
1760    format!("set {prop}")
1761}
1762
1763/// The closure shape an accessor's payload slot holds: a getter takes no
1764/// argument and returns the property (never `void`), a setter takes one and
1765/// returns nothing. Producer (`analysis::note_shaped_property_access`, which
1766/// collects the sig) and consumer (`emit_accessor_slot_present`, which looks up
1767/// its struct index) must agree exactly, so they read it from here.
1768pub(crate) fn accessor_closure_sig(kind: crate::AccessorKind) -> ClosureSig {
1769    match kind {
1770        crate::AccessorKind::Get => ClosureSig {
1771            arity: 0,
1772            is_void: false,
1773        },
1774        crate::AccessorKind::Set => ClosureSig {
1775            arity: 1,
1776            is_void: true,
1777        },
1778    }
1779}
1780
1781/// Whether an own data field redeclares one already in the inherited prefix —
1782/// a shadow, so it reuses the parent's slot rather than appending a second one.
1783/// The slot has to stay at the *parent's* index: a child's data region is a
1784/// prefix-compatible extension of its parent's, which is what lets a
1785/// parent-typed receiver read a child instance at all.
1786///
1787/// Both layout builders must agree on this rule, or the same class gets
1788/// different slot numbers on the two sides of a package boundary. This is the
1789/// `imported_classes` reconstruction's copy; [`ClassPlan::collect`] answers the
1790/// same question with `fields.iter_mut().find(…)`, because it also needs the
1791/// slot itself to carry the redeclaration's narrowing guard across.
1792pub(crate) fn shadows_inherited_field<'a>(
1793    inherited: impl IntoIterator<Item = &'a str>,
1794    name: &str,
1795) -> bool {
1796    inherited.into_iter().any(|f| f == name)
1797}
1798
1799/// Internal getter/setter methods synthesized from a class's accessors. These
1800/// reuse the ordinary method machinery (vtable slots, closures, bodies, dynamic
1801/// dispatch) but exist only inside codegen — never in the typed AST or
1802/// `PackageDeclaration`. Their names (`get <p>` / `set <p>`) are the runtime ABI
1803/// key under which the getter/setter closure is stored in the object payload.
1804///
1805/// Each comes with its backtrace name, `get C.x` / `set C.x`. It is built here
1806/// rather than parsed back out of the internal name, which a method with a
1807/// string-literal key such as `"get x"()` can share.
1808fn accessor_methods(decl: &TypedClassDecl) -> Vec<(crate::TypedClassMethod, String)> {
1809    decl.accessors
1810        .iter()
1811        .map(|acc| {
1812            let (kind, name, params, return_type) = match acc {
1813                crate::TypedClassAccessor::Getter { name, ret_ty, .. } => (
1814                    "get",
1815                    accessor_getter_name(&name.name),
1816                    Vec::new(),
1817                    ret_ty.clone(),
1818                ),
1819                crate::TypedClassAccessor::Setter { name, param, .. } => (
1820                    "set",
1821                    accessor_setter_name(&name.name),
1822                    vec![param.clone()],
1823                    crate::Type::Void,
1824                ),
1825            };
1826            let frame_name = format!("{kind} {}.{}", decl.name.name, acc.name().name);
1827            let method = crate::TypedClassMethod {
1828                name: crate::Ident {
1829                    name,
1830                    span: acc.name().span,
1831                },
1832                generics: Vec::new(),
1833                params,
1834                return_type,
1835                body: acc.body(),
1836                visibility: acc.visibility(),
1837                doc: None,
1838            };
1839            (method, frame_name)
1840        })
1841        .collect()
1842}
1843
1844fn slot_closure_sig(
1845    slot: &MethodSlot,
1846) -> Result<crate::codegen::closures::ClosureSig, crate::compiler_error::CompilerFailure> {
1847    let sig = crate::Type::Function {
1848        params: slot.param_tys.clone(),
1849        ret: Box::new(slot.ret_ty.clone()),
1850        predicate: None,
1851        optional: 0,
1852        has_rest: false,
1853    };
1854    crate::codegen::closures::classify(&sig)
1855}
1856
1857#[derive(Clone)]
1858struct SlotDraft {
1859    name: String,
1860    owner: MangledName,
1861    param_tys: Vec<crate::Type>,
1862    argument_metadata: Option<String>,
1863    ret_ty: crate::Type,
1864    generic: bool,
1865}
1866
1867/// A class's resolved (fields, method slots, own-method names) during the
1868/// inheritance-folding pass in [`ClassPlan::collect`].
1869type ResolvedLayout = (Vec<FieldLayout>, Vec<SlotDraft>, Vec<OwnMethod>);
1870
1871/// The inherited field and method-slot prefix of a class whose parent is
1872/// `parent`: a local class laid out earlier in topological order, or an
1873/// imported one reconstructed before local classes.
1874fn parent_prefix(
1875    parent: &MangledName,
1876    layouts: &BTreeMap<MangledName, ResolvedLayout>,
1877    imported: &BTreeMap<MangledName, ImportedClassLayout>,
1878) -> Result<(Vec<FieldLayout>, Vec<SlotDraft>), CompilerFailure> {
1879    if let Some((fields, methods, _)) = layouts.get(parent) {
1880        return Ok((fields.clone(), methods.clone()));
1881    }
1882    let layout = imported
1883        .get(parent)
1884        .ok_or_else(|| unavailable_parent(parent))?;
1885    let fields = layout
1886        .fields
1887        .iter()
1888        .map(|name| FieldLayout {
1889            name: name.clone(),
1890            optional: layout.optional_fields.contains(name),
1891            narrowing_check: layout.narrowing_checks.get(name).cloned().map(Box::new),
1892        })
1893        .collect();
1894    let methods = layout
1895        .methods
1896        .iter()
1897        .map(|s| SlotDraft {
1898            name: s.name.clone(),
1899            owner: s.owner.clone(),
1900            param_tys: s.param_tys.clone(),
1901            argument_metadata: s.argument_metadata.clone(),
1902            ret_ty: s.ret_ty.clone(),
1903            generic: s.generic,
1904        })
1905        .collect();
1906    Ok((fields, methods))
1907}
1908
1909fn unavailable_parent(parent: &MangledName) -> CompilerFailure {
1910    internal_failure(format!(
1911        "parent class `{parent}` was not laid out before its subclass"
1912    ))
1913}
1914
1915/// The signature and ABI a slot inherits from `parent`, if the parent has the
1916/// slot. An imported parent's slots live in the symbol table (recorded by
1917/// `imported_classes`), not in `sig_by_name`.
1918fn inherited_slot_sig(
1919    sig_by_name: &BTreeMap<(MangledName, String), (u32, MethodSlotAbi)>,
1920    symbols: &SymbolTable,
1921    parent: &MangledName,
1922    slot: &str,
1923) -> Result<Option<(u32, MethodSlotAbi)>, CompilerFailure> {
1924    if let Some(entry) = sig_by_name.get(&(parent.clone(), slot.to_owned())) {
1925        return Ok(Some(entry.clone()));
1926    }
1927    let Some(sig) = symbols.class_method_sig(parent, slot) else {
1928        return Ok(None);
1929    };
1930    let abi = symbols
1931        .class_method_abi(parent, slot)
1932        .cloned()
1933        .ok_or_else(|| internal_failure("an inherited method slot has no recorded ABI"))?;
1934    Ok(Some((sig, abi)))
1935}
1936
1937/// Topological order of classes by `extends` (parents before children). Classes
1938/// whose parent is not in this module (cross-package) are treated as roots.
1939/// Chains are walked iteratively; a cycle is an internal failure because
1940/// inference rejects cyclic hierarchies.
1941fn topo_order(
1942    by_mangled: &BTreeMap<&MangledName, &TypedClassDecl>,
1943) -> Result<Vec<MangledName>, CompilerFailure> {
1944    let mut visited: BTreeSet<&MangledName> = BTreeSet::new();
1945    let mut out: Vec<MangledName> = Vec::new();
1946    for &start in by_mangled.keys() {
1947        let mut chain: Vec<&MangledName> = Vec::new();
1948        let mut next = Some(start);
1949        while let Some(mangled) = next {
1950            if visited.contains(mangled) {
1951                break;
1952            }
1953            let Some(decl) = by_mangled.get(mangled) else {
1954                break;
1955            };
1956            if chain.contains(&mangled) {
1957                return Err(internal_failure("class inheritance is cyclic"));
1958            }
1959            chain.push(mangled);
1960            next = decl.extends.as_ref();
1961        }
1962        for mangled in chain.into_iter().rev() {
1963            visited.insert(mangled);
1964            out.push(mangled.clone());
1965        }
1966    }
1967    Ok(out)
1968}
1969
1970/// The Wasm type of a body's parameter `i` — the recorded slot's, not the
1971/// declaration's own: an override shares the signature of the ancestor that
1972/// introduced the slot, and a constructor's signature erases class types so
1973/// producer and cross-package consumer agree.
1974fn slot_param_type(
1975    ctx: &CodegenCtx<'_>,
1976    slot_types: &[ValType],
1977    i: usize,
1978    p: &crate::TypedParam,
1979) -> Result<ValType, crate::compiler_error::CompilerFailure> {
1980    Ok(match slot_types.get(i) {
1981        Some(ty) => *ty,
1982        None => ctx.symbols.slot_value_type(&p.ty)?,
1983    })
1984}
1985
1986/// The constructor parameter slots recorded when the class's ctor fn-types were
1987/// emitted — the same signature the entry ctor, the init fn, and every call site
1988/// must agree on. Local and imported classes both record it before any body is
1989/// emitted.
1990fn ctor_slot_types(
1991    ctx: &CodegenCtx<'_>,
1992    class: &MangledName,
1993) -> Result<Vec<ValType>, CompilerFailure> {
1994    ctx.symbols
1995        .class_ctor_abi(class)
1996        .map(<[ValType]>::to_vec)
1997        .ok_or_else(|| {
1998            internal_failure(format!(
1999                "class `{class}` has no recorded constructor signature"
2000            ))
2001        })
2002}
2003
2004/// Named Wasm parameters for a method or constructor body, at their slot types.
2005fn slot_params(
2006    ctx: &CodegenCtx<'_>,
2007    params: &[crate::TypedParam],
2008    slot_types: &[ValType],
2009) -> Result<Vec<(crate::Ident, ValType)>, crate::compiler_error::CompilerFailure> {
2010    params
2011        .iter()
2012        .enumerate()
2013        .map(|(i, p)| Ok((p.name.clone(), slot_param_type(ctx, slot_types, i, p)?)))
2014        .collect::<Result<_, crate::compiler_error::CompilerFailure>>()
2015}
2016
2017/// Unbox every param whose slot is erased but whose declared type is concrete
2018/// (an override of a generic method, a class-typed constructor param) into a
2019/// shadow local, and rebind the name to it so the body reads its own type.
2020/// Returns the local index backing each parameter, for the boxed-param
2021/// prologue. Every body this serves is self-first, so parameter `i` is local
2022/// `i + 1`.
2023fn rebind_erased_params(
2024    emitter: &mut FunctionEmitter<'_>,
2025    ctx: &CodegenCtx<'_>,
2026    params: &[crate::TypedParam],
2027    slot_types: &[ValType],
2028) -> Result<Vec<u32>, crate::compiler_error::CompilerFailure> {
2029    let mut param_slots = Vec::with_capacity(params.len());
2030    for (i, p) in params.iter().enumerate() {
2031        let param_local = parameter_local(i)?;
2032        let own_vt = ctx.symbols.value_type(&p.ty)?;
2033        if slot_param_type(ctx, slot_types, i, p)? == own_vt {
2034            param_slots.push(param_local);
2035            continue;
2036        }
2037        let shadow = emitter.add_anonymous_local(own_vt)?;
2038        emitter.instruction(Instruction::LocalGet(param_local));
2039        crate::codegen::cast_check::emit_checked_parameter_cast_on_stack(
2040            emitter,
2041            ctx,
2042            &crate::Type::Unknown,
2043            &p.ty,
2044        )?;
2045        emitter.instruction(Instruction::LocalSet(shadow));
2046        emitter.rebind_in_innermost_scope(&p.name.name, shadow, own_vt)?;
2047        param_slots.push(shadow);
2048    }
2049    Ok(param_slots)
2050}
2051
2052fn stub_body() -> Function {
2053    let mut f = Function::new([]);
2054    f.instruction(&Instruction::Unreachable);
2055    f.instruction(&Instruction::End);
2056    f
2057}
2058
2059/// An Error subclass's universal slot that forwards to the prelude `Error`
2060/// vtable's body for the same slot: `toString` renders the `name` and
2061/// `message` every subclass inherits, and `hash` uses identity.
2062fn emit_error_slot_body(
2063    symbols: &SymbolTable,
2064    intrinsics: IntrinsicTypeIndices,
2065    slot: u32,
2066    slot_fn_type: u32,
2067) -> Result<Function, CompilerFailure> {
2068    let vtable = symbols
2069        .class_vtable_global_idx(&crate::mangle::prelude("Error"))
2070        .ok_or_else(|| internal_failure("Error vtable is not imported for its subclass"))?;
2071    let mut body = Function::new([]);
2072    body.instruction(&Instruction::LocalGet(0));
2073    body.instruction(&Instruction::GlobalGet(vtable));
2074    body.instruction(&Instruction::StructGet {
2075        struct_type_index: intrinsics.vtable,
2076        field_index: slot,
2077    });
2078    body.instruction(&Instruction::CallRef(slot_fn_type));
2079    body.instruction(&Instruction::End);
2080    Ok(body)
2081}
2082
2083/// The class's `equals` vtable body: nominal exact-class guard + structural
2084/// compare of the data-field payload slots.
2085///
2086/// The guard is a `ref.eq` of the two operands' vtable refs — self's vtable
2087/// *is* this class's singleton (only this class's vtable references this
2088/// body), so no `global.get` is needed, and any other operand (sibling,
2089/// subclass, structural shape, boxed primitive) fails it. Only slots
2090/// `0..n_fields` are compared: the payload's trailing slots hold per-instance
2091/// method closures, whose reference-identity equals would break field-equal
2092/// instances.
2093fn emit_class_equals_body(n_fields: u32, intrinsics: IntrinsicTypeIndices) -> Function {
2094    // Locals layout (after 2 params):
2095    //   2: $elem_a    (ref null $Object)
2096    //   3: $elem_b    (ref null $Object)
2097    //   4: $elem_a_nn (ref $Object)
2098    //   5: $eq_fn     (ref $equalsFn)
2099    //   6: $a_fields  (ref $objectFields)
2100    //   7: $b_fields  (ref $objectFields)
2101    let locals: Vec<(u32, ValType)> = if n_fields == 0 {
2102        Vec::new()
2103    } else {
2104        vec![
2105            (
2106                2,
2107                ValType::Ref(RefType {
2108                    nullable: true,
2109                    heap_type: HeapType::Concrete(intrinsics.object),
2110                }),
2111            ),
2112            (1, ref_to(intrinsics.object)),
2113            (1, ref_to(intrinsics.equals_fn)),
2114            (2, ref_to(intrinsics.object_fields)),
2115        ]
2116    };
2117    let mut f = Function::new(locals);
2118    let (a, b) = (0u32, 1u32);
2119    let (a_fields, b_fields) = (6u32, 7u32);
2120
2121    f.instruction(&Instruction::LocalGet(a));
2122    f.instruction(&Instruction::LocalGet(b));
2123    f.instruction(&Instruction::RefEq);
2124    f.instruction(&Instruction::If(BlockType::Empty));
2125    f.instruction(&Instruction::I32Const(1));
2126    f.instruction(&Instruction::Return);
2127    f.instruction(&Instruction::End);
2128
2129    for operand in [a, b] {
2130        f.instruction(&Instruction::LocalGet(operand));
2131        f.instruction(&Instruction::StructGet {
2132            struct_type_index: intrinsics.object,
2133            field_index: 0,
2134        });
2135    }
2136    f.instruction(&Instruction::RefEq);
2137    f.instruction(&Instruction::I32Eqz);
2138    f.instruction(&Instruction::If(BlockType::Empty));
2139    f.instruction(&Instruction::I32Const(0));
2140    f.instruction(&Instruction::Return);
2141    f.instruction(&Instruction::End);
2142
2143    if n_fields > 0 {
2144        // Casts are guaranteed: the guard passed, so both operands are
2145        // instances of this class.
2146        for (operand, dest) in [(a, a_fields), (b, b_fields)] {
2147            f.instruction(&Instruction::LocalGet(operand));
2148            f.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
2149                intrinsics.object_shape,
2150            )));
2151            f.instruction(&Instruction::StructGet {
2152                struct_type_index: intrinsics.object_shape,
2153                field_index: 2,
2154            });
2155            f.instruction(&Instruction::LocalSet(dest));
2156        }
2157        for slot in 0..n_fields {
2158            emit_payload_slot_equals(&mut f, slot, intrinsics);
2159        }
2160    }
2161
2162    f.instruction(&Instruction::I32Const(1));
2163    f.instruction(&Instruction::End);
2164    f
2165}
2166
2167/// One payload-slot comparison for [`emit_class_equals_body`], null-aware
2168/// (optional fields store `null`): both null → equal, one null → return 0,
2169/// else dispatch the element's own `vtable.equals`. Uses the fixed locals
2170/// documented there.
2171fn emit_payload_slot_equals(f: &mut Function, slot: u32, intrinsics: IntrinsicTypeIndices) {
2172    let (elem_a, elem_b, elem_a_nn, eq_fn) = (2u32, 3u32, 4u32, 5u32);
2173    let (a_fields, b_fields) = (6u32, 7u32);
2174
2175    for (fields, dest) in [(a_fields, elem_a), (b_fields, elem_b)] {
2176        f.instruction(&Instruction::LocalGet(fields));
2177        f.instruction(&Instruction::I32Const(slot.cast_signed()));
2178        f.instruction(&Instruction::ArrayGet(intrinsics.object_fields));
2179        f.instruction(&Instruction::LocalSet(dest));
2180    }
2181
2182    f.instruction(&Instruction::LocalGet(elem_a));
2183    f.instruction(&Instruction::RefIsNull);
2184    f.instruction(&Instruction::If(BlockType::Empty));
2185    f.instruction(&Instruction::LocalGet(elem_b));
2186    f.instruction(&Instruction::RefIsNull);
2187    f.instruction(&Instruction::I32Eqz);
2188    f.instruction(&Instruction::If(BlockType::Empty));
2189    f.instruction(&Instruction::I32Const(0));
2190    f.instruction(&Instruction::Return);
2191    f.instruction(&Instruction::End);
2192    f.instruction(&Instruction::Else);
2193    f.instruction(&Instruction::LocalGet(elem_b));
2194    f.instruction(&Instruction::RefIsNull);
2195    f.instruction(&Instruction::If(BlockType::Empty));
2196    f.instruction(&Instruction::I32Const(0));
2197    f.instruction(&Instruction::Return);
2198    f.instruction(&Instruction::End);
2199
2200    f.instruction(&Instruction::LocalGet(elem_a));
2201    f.instruction(&Instruction::RefAsNonNull);
2202    f.instruction(&Instruction::LocalTee(elem_a_nn));
2203    f.instruction(&Instruction::StructGet {
2204        struct_type_index: intrinsics.object,
2205        field_index: 0,
2206    });
2207    f.instruction(&Instruction::StructGet {
2208        struct_type_index: intrinsics.vtable,
2209        field_index: 2,
2210    });
2211    f.instruction(&Instruction::LocalSet(eq_fn));
2212
2213    f.instruction(&Instruction::LocalGet(elem_a_nn));
2214    f.instruction(&Instruction::LocalGet(elem_b));
2215    f.instruction(&Instruction::RefAsNonNull);
2216    f.instruction(&Instruction::LocalGet(eq_fn));
2217    f.instruction(&Instruction::CallRef(intrinsics.equals_fn));
2218    f.instruction(&Instruction::I32Eqz);
2219    f.instruction(&Instruction::If(BlockType::Empty));
2220    f.instruction(&Instruction::I32Const(0));
2221    f.instruction(&Instruction::Return);
2222    f.instruction(&Instruction::End);
2223    f.instruction(&Instruction::End);
2224}
2225
2226/// Bridge an authored conversion method to the universal string-returning slot.
2227/// Read its current payload so writes through structural aliases also affect
2228/// implicit conversions, including inherited methods with a different layout.
2229fn emit_universal_slot_thunk(
2230    ctx: &CodegenCtx,
2231    method: &str,
2232) -> Result<Function, crate::compiler_error::CompilerFailure> {
2233    let mut emitter = super::function_emitter::FunctionEmitter::new(
2234        ctx,
2235        &[(
2236            crate::Ident {
2237                name: "self".into(),
2238                span: crate::Span::at(crate::FileId(0)),
2239            },
2240            ctx.symbols.value_type(&crate::Type::Unknown)?,
2241        )],
2242    )?;
2243    emitter.instruction(Instruction::LocalGet(0));
2244    super::function_emitter::expr::emit_interface_method_via_shape_with_receiver_on_stack(
2245        &mut emitter,
2246        ctx,
2247        method,
2248        &[],
2249        &crate::Type::String,
2250        None,
2251    )?;
2252    emitter.build()
2253}
2254
2255fn emit_class_to_string_body(
2256    intrinsics: IntrinsicTypeIndices,
2257    string_vtable_global_idx: u32,
2258) -> Result<Function, CompilerFailure> {
2259    let mut f = Function::new([]);
2260    crate::codegen::intrinsics::push_string_literal(
2261        &mut f,
2262        intrinsics,
2263        string_vtable_global_idx,
2264        "[object Object]",
2265    )?;
2266    f.instruction(&Instruction::End);
2267    Ok(f)
2268}
2269
2270/// A class whose `toString` is a field converts through the host object vtable,
2271/// which calls the field when it holds a function and gives `[object Object]`
2272/// otherwise, as when an optional one is absent.
2273fn emit_class_to_string_field_body(
2274    symbols: &SymbolTable,
2275    intrinsics: IntrinsicTypeIndices,
2276) -> Result<Function, CompilerFailure> {
2277    let object_vtable = symbols
2278        .prelude_global_idx("object_vtable")
2279        .ok_or_else(|| internal_failure("object_vtable is not imported from the prelude"))?;
2280    let mut f = Function::new([]);
2281    f.instruction(&Instruction::LocalGet(0));
2282    f.instruction(&Instruction::GlobalGet(object_vtable));
2283    f.instruction(&Instruction::StructGet {
2284        struct_type_index: intrinsics.vtable,
2285        field_index: 0,
2286    });
2287    f.instruction(&Instruction::CallRef(intrinsics.to_string_fn));
2288    f.instruction(&Instruction::End);
2289    Ok(f)
2290}
2291
2292/// Class serialization reads the runtime property metadata, including visibility
2293/// and getters, so structural views and dynamically added fields use the same rules.
2294fn emit_class_to_json_body(symbols: &SymbolTable) -> Result<Function, CompilerFailure> {
2295    let serializer = symbols
2296        .prelude_func_idx("ObjectConstructor##toJson")
2297        .ok_or_else(|| internal_failure("the object serializer is not imported"))?;
2298    let mut f = Function::new([]);
2299    f.instruction(&Instruction::LocalGet(0));
2300    f.instruction(&Instruction::Call(serializer));
2301    f.instruction(&Instruction::End);
2302    Ok(f)
2303}
2304
2305/// Default class `hash`: FNV-1a over the data-field payload slots, each value
2306/// hashed through its own `vtable.hash` (null slot → 0) — the same recipe as
2307/// the structural `emit_subtype_hash_body`, so the `equals`-implies-equal-hash
2308/// contract holds: `equals` compares exactly these slots by vtable dispatch.
2309fn emit_class_hash_body(n_fields: u32, intrinsics: IntrinsicTypeIndices) -> Function {
2310    const FNV_BASIS: i32 = 0x811c9dc5_u32.cast_signed();
2311    const FNV_PRIME: i32 = 0x01000193;
2312
2313    if n_fields == 0 {
2314        let mut f = Function::new([]);
2315        f.instruction(&Instruction::I32Const(FNV_BASIS));
2316        f.instruction(&Instruction::End);
2317        return f;
2318    }
2319
2320    // Locals (after 1 param self=0):
2321    //   1: $fields  (ref $objectFields)
2322    //   2: $hash    (i32)
2323    //   3: $elem    (ref null $Object)
2324    //   4: $elem_nn (ref $Object)
2325    //   5: $hash_fn (ref $hashFn)
2326    let locals: Vec<(u32, ValType)> = vec![
2327        (1, ref_to(intrinsics.object_fields)),
2328        (1, ValType::I32),
2329        (
2330            1,
2331            ValType::Ref(RefType {
2332                nullable: true,
2333                heap_type: HeapType::Concrete(intrinsics.object),
2334            }),
2335        ),
2336        (1, ref_to(intrinsics.object)),
2337        (1, ref_to(intrinsics.hash_fn)),
2338    ];
2339    let mut f = Function::new(locals);
2340    let (fields_arr, hash, elem, elem_nn, hash_fn) = (1u32, 2u32, 3u32, 4u32, 5u32);
2341
2342    f.instruction(&Instruction::LocalGet(0));
2343    f.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
2344        intrinsics.object_shape,
2345    )));
2346    f.instruction(&Instruction::StructGet {
2347        struct_type_index: intrinsics.object_shape,
2348        field_index: 2,
2349    });
2350    f.instruction(&Instruction::LocalSet(fields_arr));
2351
2352    f.instruction(&Instruction::I32Const(FNV_BASIS));
2353    f.instruction(&Instruction::LocalSet(hash));
2354
2355    for slot in 0..n_fields {
2356        f.instruction(&Instruction::LocalGet(fields_arr));
2357        f.instruction(&Instruction::I32Const(slot.cast_signed()));
2358        f.instruction(&Instruction::ArrayGet(intrinsics.object_fields));
2359        f.instruction(&Instruction::LocalSet(elem));
2360
2361        f.instruction(&Instruction::LocalGet(elem));
2362        f.instruction(&Instruction::RefIsNull);
2363        f.instruction(&Instruction::If(BlockType::Result(ValType::I32)));
2364        f.instruction(&Instruction::I32Const(0));
2365        f.instruction(&Instruction::Else);
2366        f.instruction(&Instruction::LocalGet(elem));
2367        f.instruction(&Instruction::RefAsNonNull);
2368        f.instruction(&Instruction::LocalTee(elem_nn));
2369        f.instruction(&Instruction::StructGet {
2370            struct_type_index: intrinsics.object,
2371            field_index: 0,
2372        });
2373        f.instruction(&Instruction::StructGet {
2374            struct_type_index: intrinsics.vtable,
2375            field_index: 3,
2376        });
2377        f.instruction(&Instruction::LocalSet(hash_fn));
2378        f.instruction(&Instruction::LocalGet(elem_nn));
2379        f.instruction(&Instruction::LocalGet(hash_fn));
2380        f.instruction(&Instruction::CallRef(intrinsics.hash_fn));
2381        f.instruction(&Instruction::End);
2382
2383        // hash = (hash XOR slot_hash) * FNV prime
2384        f.instruction(&Instruction::LocalGet(hash));
2385        f.instruction(&Instruction::I32Xor);
2386        f.instruction(&Instruction::I32Const(FNV_PRIME));
2387        f.instruction(&Instruction::I32Mul);
2388        f.instruction(&Instruction::LocalSet(hash));
2389    }
2390
2391    f.instruction(&Instruction::LocalGet(hash));
2392    f.instruction(&Instruction::End);
2393    f
2394}
2395
2396/// Member names this module may write through a structural alias. A method is
2397/// fixed on its class, but a `{ m: () => T }` view of the instance, or a record
2398/// holding functions, writes the payload slot the method's closure lives in.
2399///
2400/// The rule is by name, so it covers imported classes too: this module's write
2401/// reaches an imported instance as easily as a local one.
2402#[derive(Clone, Debug)]
2403pub(crate) enum RewritableMembers {
2404    Named(std::collections::BTreeSet<String>),
2405    /// A computed-key write of a function-capable value may reach any member.
2406    Every,
2407}
2408
2409impl Default for RewritableMembers {
2410    fn default() -> Self {
2411        Self::Named(std::collections::BTreeSet::new())
2412    }
2413}
2414
2415impl RewritableMembers {
2416    pub(crate) fn of(ta: &TypedAst) -> Result<Self, crate::compiler_error::CompilerFailure> {
2417        let mut members = Self::default();
2418        for id in ta.stmt_ids().map_err(super::arena_failure)? {
2419            match &ta.try_stmt(id).map_err(super::arena_failure)?.kind {
2420                crate::TypedStmtKind::AssignField { name, .. } => {
2421                    members.note_name(&name.name);
2422                }
2423                crate::TypedStmtKind::AssignIndex { index, elem_ty, .. } => {
2424                    members.note_index(ta, *index, elem_ty)?;
2425                }
2426                _ => {}
2427            }
2428        }
2429        for id in ta.expr_ids().map_err(super::arena_failure)? {
2430            let crate::TypedExprKind::PostfixUnary { target, .. } =
2431                &ta.try_expr(id).map_err(super::arena_failure)?.kind
2432            else {
2433                continue;
2434            };
2435            match target {
2436                crate::PostfixTarget::Field { name, .. } => {
2437                    members.note_name(&name.name);
2438                }
2439                crate::PostfixTarget::Index { index, elem_ty, .. } => {
2440                    members.note_index(ta, *index, elem_ty)?;
2441                }
2442                _ => {}
2443            }
2444        }
2445        Ok(members)
2446    }
2447
2448    fn note_index(
2449        &mut self,
2450        ta: &TypedAst,
2451        index: crate::ExprId,
2452        elem_ty: &crate::Type,
2453    ) -> Result<(), crate::compiler_error::CompilerFailure> {
2454        if !may_hold_function(elem_ty) {
2455            return Ok(());
2456        }
2457        match &ta.try_expr(index).map_err(super::arena_failure)?.kind {
2458            crate::TypedExprKind::String(name) => self.note_name(name),
2459            _ => *self = Self::Every,
2460        }
2461        Ok(())
2462    }
2463
2464    fn note_name(&mut self, name: &str) {
2465        if let Self::Named(names) = self {
2466            names.insert(name.to_string());
2467        }
2468    }
2469
2470    pub(crate) fn includes(&self, name: &str) -> bool {
2471        match self {
2472            Self::Named(names) => names.contains(name),
2473            Self::Every => true,
2474        }
2475    }
2476}
2477
2478fn may_hold_function(ty: &crate::Type) -> bool {
2479    match ty.peel() {
2480        crate::Type::Union(members) => members.iter().any(may_hold_function),
2481        crate::Type::Function { .. }
2482        | crate::Type::Unknown
2483        | crate::Type::TypeVar(_)
2484        | crate::Type::GenericParam { .. }
2485        | crate::Type::Error => true,
2486        _ => false,
2487    }
2488}
2489
2490fn ref_to(idx: u32) -> ValType {
2491    ValType::Ref(RefType {
2492        nullable: false,
2493        heap_type: HeapType::Concrete(idx),
2494    })
2495}
2496
2497fn fieldtype_ref(idx: u32) -> FieldType {
2498    FieldType {
2499        element_type: StorageType::Val(ref_to(idx)),
2500        mutable: false,
2501    }
2502}
2503
2504fn fieldtype_ref_null(idx: u32) -> FieldType {
2505    FieldType {
2506        element_type: StorageType::Val(ValType::Ref(RefType {
2507            nullable: true,
2508            heap_type: HeapType::Concrete(idx),
2509        })),
2510        mutable: false,
2511    }
2512}
2513
2514/// The Wasm supertype of a class's vtable: the parent class's vtable, or
2515/// `$ClassVTable` at the root of the chain.
2516fn vtable_supertype_idx(
2517    class: &ClassLayout,
2518    intrinsics: IntrinsicTypeIndices,
2519    symbols: &SymbolTable,
2520) -> Result<u32, CompilerFailure> {
2521    match &class.parent {
2522        Some(p) => symbols.class_vtable_type_idx(p).ok_or_else(|| {
2523            internal_failure("a parent vtable type was not reserved before its child")
2524        }),
2525        None => Ok(intrinsics.class_vtable),
2526    }
2527}
2528
2529/// The Wasm supertype of a class's instance struct: the parent class's struct,
2530/// or `$ObjectShape` at the root of the chain.
2531fn struct_supertype_idx(
2532    class: &ClassLayout,
2533    intrinsics: IntrinsicTypeIndices,
2534    symbols: &SymbolTable,
2535) -> Result<u32, CompilerFailure> {
2536    match &class.parent {
2537        Some(p) => symbols.class_struct_type_idx(p).ok_or_else(|| {
2538            internal_failure("a parent struct type was not reserved before its child")
2539        }),
2540        None => Ok(intrinsics.object_shape),
2541    }
2542}
2543
2544fn substruct(fields: Vec<FieldType>, supertype: Option<u32>) -> SubType {
2545    SubType {
2546        is_final: false,
2547        supertype_idx: supertype,
2548        composite_type: CompositeType {
2549            inner: CompositeInnerType::Struct(StructType {
2550                fields: fields.into_boxed_slice(),
2551            }),
2552            shared: false,
2553            descriptor: None,
2554            describes: None,
2555        },
2556    }
2557}
2558
2559#[cfg(test)]
2560mod tests {
2561    use super::*;
2562    use crate::codegen::intrinsics::declare_intrinsic_types;
2563    use crate::runtime::intrinsic_types::build_intrinsic_types;
2564    use wasm_encoder::{
2565        ConstExpr, ExportKind, ExportSection, GlobalSection, GlobalType, HeapType, Module, RefType,
2566        TypeSection, ValType,
2567    };
2568    use wasmtime::Config;
2569
2570    /// Seam guard for the intrinsic `(rec $Error_vtable $Error)` pair: this
2571    /// emitter's output for a parentless, method-less `{message, name}` class must
2572    /// canonicalize to the intrinsic types, so `class MyError extends Error`
2573    /// reconstructs the parent through the ordinary class path and WasmGC
2574    /// canonicalization unifies them.
2575    #[test]
2576    fn field_only_class_rec_group_canonicalizes_to_intrinsic_error() {
2577        let mut types = TypeSection::new();
2578        let intrinsics = declare_intrinsic_types(&mut types);
2579        let vtable_type_idx = crate::codegen::intrinsics::INTRINSIC_TYPE_COUNT;
2580        let struct_type_idx = vtable_type_idx + 1;
2581        let layout = ClassLayout {
2582            private_members: BTreeSet::new(),
2583            generics: Vec::new(),
2584            mangled: crate::mangle::prelude("ErrorWitness"),
2585            name: "ErrorWitness".to_string(),
2586            name_span: crate::Span::at(crate::FileId(0)),
2587            parent: None,
2588            fields: vec![
2589                FieldLayout {
2590                    name: "message".to_string(),
2591                    optional: false,
2592                    narrowing_check: None,
2593                },
2594                FieldLayout {
2595                    name: "name".to_string(),
2596                    optional: false,
2597                    narrowing_check: None,
2598                },
2599            ],
2600            methods: Vec::new(),
2601            own_methods: Vec::new(),
2602            adapter_slots: Vec::new(),
2603            ctor_params: Vec::new(),
2604            ctor_body: None,
2605            field_setup: Vec::new(),
2606            accessor_names: Vec::new(),
2607            struct_type_idx,
2608            vtable_type_idx,
2609            ctor_func_idx: 0,
2610            ctor_sig_idx: 0,
2611            ctor_init_func_idx: 0,
2612            ctor_init_sig_idx: 0,
2613            getter_func_idx: 0,
2614            setter_func_idx: 0,
2615            equals_func_idx: 0,
2616            to_string_func_idx: 0,
2617            to_json_func_idx: 0,
2618            hash_func_idx: 0,
2619            guarded_bodies: GuardedBodies::default(),
2620        };
2621        let plan = ClassPlan {
2622            package_name: "witness".to_string(),
2623            classes: Vec::new(),
2624            universal_stubs: [0; UNIVERSAL_STUB_COUNT],
2625        };
2626        let symbols = SymbolTable::default();
2627        let vtable = plan.vtable_subtype(&layout, intrinsics, &symbols).unwrap();
2628        let strukt = plan.struct_subtype(&layout, intrinsics, &symbols).unwrap();
2629        types.ty().rec([vtable, strukt]);
2630
2631        let mut module = Module::new();
2632        module.section(&types);
2633        let mut globals = GlobalSection::new();
2634        let mut exports = ExportSection::new();
2635        for (name, idx) in [("vtable", vtable_type_idx), ("struct", struct_type_idx)] {
2636            globals.global(
2637                GlobalType {
2638                    val_type: ValType::Ref(RefType {
2639                        nullable: true,
2640                        heap_type: HeapType::Concrete(idx),
2641                    }),
2642                    mutable: false,
2643                    shared: false,
2644                },
2645                &ConstExpr::ref_null(HeapType::Concrete(idx)),
2646            );
2647            exports.export(name, ExportKind::Global, idx - vtable_type_idx);
2648        }
2649        module.section(&globals);
2650        module.section(&exports);
2651
2652        let mut config = Config::new();
2653        config.wasm_gc(true);
2654        config.wasm_function_references(true);
2655        let engine = wasmtime::Engine::new(&config).unwrap();
2656        let intr = build_intrinsic_types(&engine).unwrap();
2657        let module = wasmtime::Module::new(&engine, module.finish()).unwrap();
2658        let recovered = |name: &str| {
2659            module
2660                .get_export(name)
2661                .unwrap()
2662                .global()
2663                .unwrap()
2664                .content()
2665                .as_ref()
2666                .map(|r| r.heap_type().clone())
2667                .unwrap()
2668                .as_concrete_struct()
2669                .unwrap()
2670                .clone()
2671        };
2672        assert!(wasmtime::StructType::eq(
2673            &intr.error_vtable,
2674            &recovered("vtable")
2675        ));
2676        assert!(wasmtime::StructType::eq(&intr.error, &recovered("struct")));
2677    }
2678}
2679
2680#[cfg(test)]
2681mod closure_adapter_invariant_tests {
2682    use super::*;
2683    use crate::codegen::invariant_tests::{assert_internal, with_context};
2684
2685    #[test]
2686    fn class_closure_adapter_requires_registered_method_and_slot_abi() {
2687        let (ta, diagnostics) = crate::compile::typecheck_to_typed_ast(
2688            "class C { f(a: number): number { return a; } } function main(): number { return new C().f(1); }",
2689            crate::FileId(0),
2690        );
2691        assert!(diagnostics.is_empty(), "{diagnostics:?}");
2692        let plan = ClassPlan::collect(&ta, &BTreeMap::new()).unwrap();
2693        let class = &plan.classes[0];
2694        let slot = &class.methods[0];
2695        let mut symbols = crate::codegen::tests::mock_symbols_with_intrinsics();
2696        with_context(&ta, &symbols, |ctx| {
2697            assert_internal(plan.emit_method_adapter_body(class, slot, ctx).unwrap_err());
2698        });
2699        symbols.record_class_method_func(slot.owner.clone(), slot.name.clone(), 1);
2700        with_context(&ta, &symbols, |ctx| {
2701            assert_internal(plan.emit_method_adapter_body(class, slot, ctx).unwrap_err());
2702        });
2703        symbols.record_class_method_abi(
2704            class.mangled.clone(),
2705            slot.name.clone(),
2706            MethodSlotAbi {
2707                params: vec![],
2708                ret: Some(ValType::F64),
2709            },
2710        );
2711        with_context(&ta, &symbols, |ctx| {
2712            assert_internal(plan.emit_method_adapter_body(class, slot, ctx).unwrap_err());
2713        });
2714    }
2715}
2716
2717#[cfg(test)]
2718mod layout_invariant_tests {
2719    use super::*;
2720    use crate::compiler_error::CompilerFailure;
2721
2722    fn typed(source: &str) -> TypedAst {
2723        let (ta, diagnostics) = crate::compile::typecheck_to_typed_ast(source, crate::FileId(0));
2724        assert!(diagnostics.is_empty(), "{diagnostics:?}");
2725        ta
2726    }
2727
2728    fn class_mut<'a>(ta: &'a mut TypedAst, name: &str) -> &'a mut TypedClassDecl {
2729        ta.types
2730            .iter_mut()
2731            .find_map(|decl| match decl {
2732                TypedTypeDecl::Class(class) if class.name.name == name => Some(class),
2733                _ => None,
2734            })
2735            .unwrap()
2736    }
2737
2738    fn assert_internal(result: Result<ClassPlan, CompilerFailure>) {
2739        assert!(
2740            matches!(result, Err(CompilerFailure::Internal { .. })),
2741            "expected an internal failure"
2742        );
2743    }
2744
2745    #[test]
2746    fn missing_or_cyclic_parents_stop_layout_instead_of_dropping_the_prefix() {
2747        let source = "class A { x: number = 1 } class B extends A { y: number = 2 } \
2748                      function main(): number { return new B().x; }";
2749        let intact = typed(source);
2750        let plan = ClassPlan::collect(&intact, &BTreeMap::new()).unwrap();
2751        let b = plan.classes.iter().find(|class| class.name == "B").unwrap();
2752        assert_eq!(b.fields.len(), 2, "B inherits A's field prefix");
2753
2754        // An orphaned subclass must fail rather than lose its parent's prefix.
2755        let mut orphan = intact.clone();
2756        orphan
2757            .types
2758            .retain(|decl| !matches!(decl, TypedTypeDecl::Class(class) if class.name.name == "A"));
2759        assert_internal(ClassPlan::collect(&orphan, &BTreeMap::new()));
2760
2761        let mut cyclic = intact;
2762        let b_name = class_mut(&mut cyclic, "B").mangled_name.clone();
2763        class_mut(&mut cyclic, "A").extends = Some(b_name);
2764        assert_internal(ClassPlan::collect(&cyclic, &BTreeMap::new()));
2765    }
2766
2767    #[test]
2768    fn guard_payload_arithmetic_is_checked() {
2769        assert_eq!(guard_slot_count(0, 0).unwrap(), 1);
2770        assert_eq!(guard_slot_count(2, 3).unwrap(), 12);
2771        assert!(guard_slot_count(u32::MAX, 0).is_err());
2772        assert!(guard_slot_count(1, u32::MAX).is_err());
2773        assert!(guard_slot_count(u32::MAX / 2, 3).is_err());
2774    }
2775}