Skip to main content

submilli_engine/codegen/
symbol_table.rs

1//! Maps [`MangledName`] to Wasm indices for types, functions, and globals —
2//! both imported symbols and consumer-local declarations.
3
4use std::collections::{BTreeMap, BTreeSet, HashMap};
5
6use wasm_encoder::{HeapType, RefType, ValType};
7
8use crate::codegen::closures::ClosureSig;
9use crate::codegen::internal_failure;
10use crate::codegen::intrinsics::{IntrinsicTypeIndices, intrinsic_supertypes};
11use crate::compiler_error::CompilerFailure;
12use crate::{Dispatch, ExprId, MangledName, Type};
13
14/// One step of a constructor's field-setup sequence, run after `super(...)`
15/// (or at the top of a base class's init fn): own-field initializers and
16/// parameter-property copies, in execution order.
17#[derive(Clone, Debug)]
18pub enum FieldSetup {
19    /// `this.field = <initializer expr>`.
20    Init { field: String, value: ExprId },
21    /// Read the current parameter storage after defaults, including captured cells.
22    ParamCopy {
23        field: String,
24        param: Box<crate::TypedParam>,
25    },
26    /// `this.field = undefined` for an optional field that redeclares an inherited
27    /// one with no initializer of its own. The slot is the parent's, so without
28    /// this it would keep the parent's initialized value.
29    Reset { field: String },
30}
31
32#[derive(Default, Clone, Copy, Debug)]
33pub struct ClassGuardLayout {
34    pub inheritance_depth: u32,
35    pub named_payload_len: u32,
36    pub has_instance_guards: bool,
37}
38
39#[derive(Default, Clone, Debug)]
40pub struct SymbolTable {
41    pub(crate) field_lookup_function: Option<u32>,
42    pub(crate) resultless_functions: BTreeSet<u32>,
43    /// Declared defaults of a host function's Wasm parameters, by position.
44    host_parameter_defaults: BTreeMap<u32, Vec<Option<crate::DefaultValue>>>,
45    pub class_type_parameters: BTreeMap<MangledName, Vec<String>>,
46    pub runtime_generic_functions: BTreeSet<MangledName>,
47    pub field_guard_targets: BTreeMap<u32, Type>,
48    pub type_descriptor_functions: BTreeMap<Type, u32>,
49    pub type_descriptor_globals: BTreeMap<Type, u32>,
50    /// The JSON Schema each descriptor type carries, for the types that have
51    /// one (`codegen::descriptor_schema`).
52    pub descriptor_schemas: BTreeMap<Type, String>,
53    types: BTreeMap<MangledName, u32>,
54    funcs: BTreeMap<MangledName, u32>,
55    globals: BTreeMap<MangledName, u32>,
56    global_types: BTreeMap<MangledName, Type>,
57    init_guards: BTreeMap<MangledName, crate::codegen::init_guard::InitFlag>,
58    /// The flags of guarded classes, by the index of the top-level statement
59    /// their declaration precedes.
60    class_declaration_flags: BTreeMap<usize, Vec<u32>>,
61    intrinsic_type_indices: Option<IntrinsicTypeIndices>,
62    pub(crate) this_environment_type: Option<u32>,
63    pub(crate) call_metadata_type: Option<u32>,
64    pub(crate) function_argument_metadata: BTreeMap<MangledName, String>,
65    // Per-class WasmGC artifacts, keyed by the class's mangled name. Classes are
66    // nominal (one struct type each), unlike arity-shared object subtypes.
67    class_struct_type_idx: BTreeMap<MangledName, u32>,
68    class_vtable_type_idx: BTreeMap<MangledName, u32>,
69    class_vtable_global_idx: BTreeMap<MangledName, u32>,
70    class_getter_global_idx: BTreeMap<MangledName, u32>,
71    class_setter_global_idx: BTreeMap<MangledName, u32>,
72    class_method_func_idx: BTreeMap<(MangledName, String), u32>,
73    /// Wasm fn index of a class's constructor *init* function — the body that
74    /// initializes fields on an already-allocated instance (self-first ABI), as
75    /// distinct from the allocating entry constructor. `super(...)` calls it.
76    class_ctor_init_func_idx: BTreeMap<MangledName, u32>,
77    /// Wasm fn index of a class method's closure-ABI adapter, for the per-instance
78    /// method closures placed in the object-fields payload (interface-typed dispatch).
79    class_method_adapter_func_idx: BTreeMap<(MangledName, String), u32>,
80    /// Vtable slot index (4 + declaration position) of a class method, for static
81    /// `call_ref` dispatch; keyed for every class in the method's inheritance chain.
82    class_method_slot: BTreeMap<(MangledName, String), u32>,
83    /// Methods whose current callable value lives in the mutable instance payload.
84    class_payload_methods: BTreeSet<(MangledName, String)>,
85    /// Member names this module may write through a structural alias.
86    rewritable_members: crate::codegen::classes::RewritableMembers,
87    /// Wasm fn-type index of a class method's signature, for the `call_ref`.
88    class_method_sig: BTreeMap<(MangledName, String), u32>,
89    /// Physical Wasm signature of a class method's vtable slot, keyed for every
90    /// class in the chain like `class_method_sig`.
91    class_method_abi: BTreeMap<(MangledName, String), MethodSlotAbi>,
92    /// Wasm parameter types of a class's constructor (implicit subclass ctors
93    /// inherit the parent's).
94    class_ctor_abi: BTreeMap<MangledName, Vec<ValType>>,
95    /// Struct slot index (4 + declaration position) of a class field, for
96    /// `struct.get`/`struct.set` on a class-typed receiver.
97    class_field_slot: BTreeMap<(MangledName, String), u32>,
98    /// Read guards, keyed by `(class, field)`. See [`crate::FieldNarrowingCheck`].
99    class_field_narrowing_check: BTreeMap<(MangledName, String), crate::FieldNarrowingCheck>,
100    /// Constructor field-setup steps (parameter-property copies then own-field
101    /// initializers) emitted at the post-`super()` field-setup point.
102    class_field_setup: BTreeMap<MangledName, Vec<FieldSetup>>,
103    vtable_global_idx: BTreeMap<Type, u32>,
104    field_names_global_idx: BTreeMap<Vec<super::field_names::FieldName>, u32>,
105    optional_field_name_type: Option<u32>,
106    field_name_string_global_idx: BTreeMap<String, u32>,
107    // Keyed by ValType so language types sharing a Wasm representation share one box.
108    box_type_idx: HashMap<ValType, u32>,
109    /// Declared Wasm supertype of each struct type the module emits, child →
110    /// parent, for [`ref_fits_slot`](Self::ref_fits_slot).
111    struct_supertype: BTreeMap<u32, u32>,
112    closure_func_type_idx: BTreeMap<ClosureSig, u32>,
113    closure_struct_type_idx: BTreeMap<ClosureSig, u32>,
114    closure_coercions: BTreeMap<ClosureSig, u32>,
115    closure_coercion_vtable_type: Option<u32>,
116    env_type_idx: BTreeMap<ExprId, u32>,
117    closure_func_idx: BTreeMap<ExprId, u32>,
118    closure_vtable_global_idx: Option<u32>,
119    /// The sole exception tag's index, recorded at import emission.
120    error_tag_idx: Option<u32>,
121    adapter_func_idx: BTreeMap<MangledName, u32>,
122    adapter_closure_global_idx: BTreeMap<MangledName, u32>,
123    /// The closure caches typed `anyref` because the function's package exports
124    /// them, so its consumers share one closure; see `function_adapters`.
125    shared_closure_globals: BTreeSet<MangledName>,
126    /// Runtime validator helpers keyed by recursive alias or interface back-edges.
127    runtime_validator_idx: BTreeMap<Type, u32>,
128    generic_runtime_validators: BTreeMap<MangledName, (Vec<String>, u32)>,
129    instance_field_guards: BTreeMap<(Type, MangledName, String), u32>,
130    class_guard_layout: BTreeMap<MangledName, ClassGuardLayout>,
131    // Closures, adapters, and direct-dispatch wrappers are NOT in this map.
132    top_level_fns: BTreeMap<MangledName, TopLevelFn>,
133    iface_dispatch: BTreeMap<MangledName, Dispatch>,
134    /// The JavaScript string form of each static-dispatch interface's binding,
135    /// which names the object the binding reads as when used as a value.
136    static_value_tags: BTreeMap<MangledName, String>,
137    /// Physical signature of each Direct/Static-dispatch interface wrapper,
138    /// keyed by its `iface#method` dispatch key.
139    iface_method_abi: BTreeMap<MangledName, MethodSlotAbi>,
140    // Members declared `intrinsic` — codegen emits them inline; nothing imports.
141    intrinsic_members: BTreeSet<MangledName>,
142}
143
144#[derive(Clone, Debug)]
145pub struct TopLevelFn {
146    pub wasm_idx: u32,
147    pub params: Vec<Type>,
148    pub ret: Type,
149    /// `true` for host modules whose Wasm ABI uses raw `(ref $rawString)` instead of
150    /// `(ref $string)`; drives per-arg extract and post-call rewrap in `emit_direct_call`.
151    pub is_host: bool,
152}
153
154/// The physical Wasm signature of a dispatched method — a class-method vtable
155/// slot, or a Direct/Static-dispatch interface wrapper — captured where the
156/// signature is built rather than re-derived at each call site.
157///
158/// A class slot's signature belongs to the ancestor that introduced it, and
159/// erases class types and type variables (see
160/// [`slot_value_type`](SymbolTable::slot_value_type)), so it can differ from
161/// what an overriding method declares; an interface wrapper's signature erases
162/// the interface's type parameters the same way. Call sites coerce arguments
163/// into [`params`](Self::params) and cast [`ret`](Self::ret) back to the
164/// call-site type; an overriding body binds `params` and returns `ret`
165/// whatever its own annotations say.
166#[derive(Clone, Debug, Default, PartialEq)]
167pub struct MethodSlotAbi {
168    /// User parameters; the leading `self` is excluded.
169    pub params: Vec<ValType>,
170    /// `None` for a void slot.
171    pub ret: Option<ValType>,
172}
173
174/// Whether a type occupies the erased (boxed) object slot rather than its own
175/// concrete lowering. Class types are included: an erased slot must lower the
176/// same way regardless of whether the class's struct type has been recorded yet
177/// — imported classes are reconstructed before local slot signatures are built,
178/// local classes after, and box cells are collected before either — and
179/// regardless of which package is compiling it. A union erases whole if any
180/// member does; a fully concrete union already lowers identically everywhere.
181pub fn is_erased(ty: &Type) -> bool {
182    match ty.lowered() {
183        Type::TypeVar(_) | Type::GenericParam { .. } | Type::ClassRef { .. } => true,
184        Type::Union(members) => members.iter().any(is_erased),
185        _ => false,
186    }
187}
188
189/// The *lowering* question — whether a Wasm slot of this type can hold null.
190/// See [`may_hold_null`] for the language-level one.
191pub(crate) fn is_nullable_ref(val: ValType) -> bool {
192    matches!(val, ValType::Ref(RefType { nullable: true, .. }))
193}
194
195/// True iff `ty`'s value space contains `null`.
196///
197/// The *semantic* question, deliberately not `is_nullable_ref(value_type(ty))`:
198/// an `InterfaceRef` and an unrecorded `ClassRef` lower to a nullable
199/// `(ref null $Object)` without admitting `null` at the language level, and the
200/// `ClassRef` lowering is phase-dependent besides. Codegen reads this wherever
201/// it has to decide "can this value be null at runtime" — the null-aware `===`
202/// dispatch, `JSON.stringify`, and the object-shape `toJson` emitter.
203pub(crate) fn may_hold_null(ty: &Type) -> bool {
204    match ty.lowered() {
205        // A recursion back-edge is a name codegen has no body for, and the alias
206        // it names often does list `null` (`type J = number | null | Wrap[]`).
207        Type::Null
208        | Type::Void
209        | Type::Unknown
210        | Type::TypeVar(_)
211        | Type::GenericParam { .. }
212        | Type::AliasRef { .. } => true,
213        Type::Union(members) => members.iter().any(may_hold_null),
214        _ => false,
215    }
216}
217
218impl SymbolTable {
219    pub fn new() -> Self {
220        Self::default()
221    }
222
223    pub fn type_idx(&self, mangled: &MangledName) -> Option<u32> {
224        self.types.get(mangled).copied()
225    }
226
227    pub fn func_idx(&self, mangled: &MangledName) -> Option<u32> {
228        self.funcs.get(mangled).copied()
229    }
230
231    pub fn global_idx(&self, mangled: &MangledName) -> Option<u32> {
232        self.globals.get(mangled).copied()
233    }
234
235    pub fn global_type(&self, mangled: &MangledName) -> Option<&Type> {
236        self.global_types.get(mangled)
237    }
238
239    pub fn init_guard(
240        &self,
241        global: &MangledName,
242    ) -> Option<&crate::codegen::init_guard::InitFlag> {
243        self.init_guards.get(global)
244    }
245
246    pub fn class_declaration_flags(&self, index: usize) -> &[u32] {
247        self.class_declaration_flags
248            .get(&index)
249            .map_or(&[], Vec::as_slice)
250    }
251
252    pub fn prelude_func_idx(&self, symbol: &str) -> Option<u32> {
253        self.func_idx(&crate::mangle::prelude(symbol))
254    }
255
256    pub fn prelude_global_idx(&self, symbol: &str) -> Option<u32> {
257        self.global_idx(&crate::mangle::prelude(symbol))
258    }
259
260    pub fn string_type_idx(&self) -> Option<u32> {
261        self.intrinsic_type_indices.map(|i| i.string)
262    }
263
264    pub fn raw_string_type_idx(&self) -> Option<u32> {
265        self.intrinsic_type_indices.map(|i| i.raw_string)
266    }
267
268    pub fn bigint_type_idx(&self) -> Option<u32> {
269        self.intrinsic_type_indices.map(|i| i.bigint)
270    }
271
272    pub fn raw_bigint_type_idx(&self) -> Option<u32> {
273        self.intrinsic_type_indices.map(|i| i.raw_bigint)
274    }
275
276    pub fn regex_type_idx(&self) -> Option<u32> {
277        self.intrinsic_type_indices.map(|i| i.regex)
278    }
279
280    pub fn regex_match_type_idx(&self) -> Option<u32> {
281        self.intrinsic_type_indices.map(|i| i.regex_match)
282    }
283
284    pub fn regex_capture_array_type_idx(&self) -> Option<u32> {
285        self.intrinsic_type_indices.map(|i| i.regex_capture_array)
286    }
287
288    pub fn regex_match_box_type_idx(&self) -> Option<u32> {
289        self.intrinsic_type_indices.map(|i| i.regex_match_box)
290    }
291
292    pub fn boxed_number_type_idx(&self) -> Option<u32> {
293        self.intrinsic_type_indices.map(|i| i.boxed_number)
294    }
295
296    pub fn boxed_boolean_type_idx(&self) -> Option<u32> {
297        self.intrinsic_type_indices.map(|i| i.boxed_boolean)
298    }
299
300    pub fn raw_array_type_idx(&self) -> Option<u32> {
301        self.intrinsic_type_indices.map(|i| i.raw_array)
302    }
303
304    pub fn field_names_type_idx(&self) -> Option<u32> {
305        self.intrinsic_type_indices.map(|i| i.field_names)
306    }
307
308    pub fn object_shape_type_idx(&self) -> Option<u32> {
309        self.intrinsic_type_indices.map(|i| i.object_shape)
310    }
311
312    pub fn array_type_idx(&self) -> Option<u32> {
313        self.intrinsic_type_indices.map(|i| i.array)
314    }
315
316    pub fn raw_uint8_array_type_idx(&self) -> Option<u32> {
317        self.intrinsic_type_indices.map(|i| i.raw_uint8_array)
318    }
319
320    pub fn uint8_array_type_idx(&self) -> Option<u32> {
321        self.intrinsic_type_indices.map(|i| i.uint8_array)
322    }
323
324    pub fn closure_type_idx(&self) -> Option<u32> {
325        self.intrinsic_type_indices.map(|i| i.closure)
326    }
327
328    pub fn intrinsic_type_indices(&self) -> Option<IntrinsicTypeIndices> {
329        self.intrinsic_type_indices
330    }
331
332    pub fn object_subtype_idx(&self, ty: &Type) -> Option<u32> {
333        match ty {
334            Type::Object { .. } => self.object_shape_type_idx(),
335            _ => None,
336        }
337    }
338
339    pub fn class_struct_type_idx(&self, mangled: &MangledName) -> Option<u32> {
340        self.class_struct_type_idx.get(mangled).copied()
341    }
342
343    pub fn class_vtable_type_idx(&self, mangled: &MangledName) -> Option<u32> {
344        self.class_vtable_type_idx.get(mangled).copied()
345    }
346
347    pub fn class_vtable_global_idx(&self, mangled: &MangledName) -> Option<u32> {
348        self.class_vtable_global_idx.get(mangled).copied()
349    }
350
351    pub fn class_getter_global_idx(&self, mangled: &MangledName) -> Option<u32> {
352        self.class_getter_global_idx.get(mangled).copied()
353    }
354
355    pub fn class_setter_global_idx(&self, mangled: &MangledName) -> Option<u32> {
356        self.class_setter_global_idx.get(mangled).copied()
357    }
358
359    pub fn class_method_func_idx(&self, class: &MangledName, method: &str) -> Option<u32> {
360        self.class_method_func_idx
361            .get(&(class.clone(), method.to_string()))
362            .copied()
363    }
364
365    pub fn class_ctor_init_func_idx(&self, class: &MangledName) -> Option<u32> {
366        self.class_ctor_init_func_idx.get(class).copied()
367    }
368
369    pub fn class_method_adapter_func_idx(&self, class: &MangledName, method: &str) -> Option<u32> {
370        self.class_method_adapter_func_idx
371            .get(&(class.clone(), method.to_string()))
372            .copied()
373    }
374
375    pub fn class_method_slot(&self, class: &MangledName, method: &str) -> Option<u32> {
376        self.class_method_slot
377            .get(&(class.clone(), method.to_string()))
378            .copied()
379    }
380
381    pub fn is_class_payload_method(&self, class: &MangledName, method: &str) -> bool {
382        self.class_payload_methods
383            .contains(&(class.clone(), method.to_string()))
384    }
385
386    pub fn class_method_sig(&self, class: &MangledName, method: &str) -> Option<u32> {
387        self.class_method_sig
388            .get(&(class.clone(), method.to_string()))
389            .copied()
390    }
391
392    pub fn class_method_abi(&self, class: &MangledName, method: &str) -> Option<&MethodSlotAbi> {
393        self.class_method_abi
394            .get(&(class.clone(), method.to_string()))
395    }
396
397    pub fn class_ctor_abi(&self, class: &MangledName) -> Option<&[ValType]> {
398        self.class_ctor_abi.get(class).map(Vec::as_slice)
399    }
400
401    pub fn class_field_slot(&self, class: &MangledName, field: &str) -> Option<u32> {
402        self.class_field_slot
403            .get(&(class.clone(), field.to_string()))
404            .copied()
405    }
406
407    /// `None` for every ordinary field. See [`crate::FieldNarrowingCheck`].
408    pub fn class_field_narrowing_check(
409        &self,
410        class: &MangledName,
411        field: &str,
412    ) -> Option<&crate::FieldNarrowingCheck> {
413        self.class_field_narrowing_check
414            .get(&(class.clone(), field.to_string()))
415    }
416
417    pub fn class_field_narrowing_checks<'a>(
418        &'a self,
419        field: &'a str,
420    ) -> impl Iterator<Item = (&'a MangledName, &'a crate::FieldNarrowingCheck)> + 'a {
421        self.class_field_narrowing_check
422            .iter()
423            .filter(move |((_, name), _)| name == field)
424            .map(|((class, _), check)| (class, check))
425    }
426
427    pub fn class_field_setup(&self, class: &MangledName) -> Option<&[FieldSetup]> {
428        self.class_field_setup.get(class).map(Vec::as_slice)
429    }
430
431    pub fn vtable_global_idx(&self, ty: &Type) -> Option<u32> {
432        self.vtable_global_idx.get(ty).copied()
433    }
434
435    pub fn record_optional_field_name_type(&mut self, index: u32) {
436        self.optional_field_name_type = Some(index);
437    }
438
439    pub fn optional_field_name_type(&self) -> Result<u32, CompilerFailure> {
440        self.optional_field_name_type
441            .ok_or_else(|| internal_failure("optional field-name type was not declared"))
442    }
443
444    pub fn field_names_global_idx(
445        &self,
446        field_names: &[super::field_names::FieldName],
447    ) -> Option<u32> {
448        self.field_names_global_idx.get(field_names).copied()
449    }
450
451    pub fn field_name_string_global_idx(&self, name: &str) -> Option<u32> {
452        self.field_name_string_global_idx.get(name).copied()
453    }
454
455    /// Box cells are keyed on [`slot_value_type`](Self::slot_value_type) so the
456    /// key a binding registers during collection still resolves once class
457    /// struct types are recorded. A cell for an erased type therefore holds
458    /// `(ref null $Object)`; reads out of it need `emit_unerase`.
459    pub fn box_type_idx(
460        &self,
461        ty: &Type,
462    ) -> Result<Option<u32>, crate::compiler_error::CompilerFailure> {
463        Ok(self.box_type_idx.get(&self.slot_value_type(ty)?).copied())
464    }
465
466    /// A box struct is the `(ref $box_T)` wrapper a captured-and-mutated binding
467    /// is stored behind. Returns the payload type of `idx`, or `None` if `idx`
468    /// is not one — so codegen can recognise a slot that holds a box rather than
469    /// the value, and know what a `struct.get` off it leaves on the stack.
470    ///
471    /// The reverse scan is unambiguous because `box_types::collect` dedups by
472    /// payload before recording, so one index is registered per payload type.
473    pub fn box_payload_type(&self, idx: u32) -> Option<ValType> {
474        self.box_type_idx
475            .iter()
476            .find(|&(_, &v)| v == idx)
477            .map(|(payload, _)| *payload)
478    }
479
480    pub fn closure_func_type_idx(&self, sig: ClosureSig) -> Option<u32> {
481        self.closure_func_type_idx.get(&sig).copied()
482    }
483
484    pub fn record_closure_coercion_vtable_type(&mut self, index: u32) {
485        self.closure_coercion_vtable_type = Some(index);
486    }
487
488    pub fn closure_coercion_vtable_type(
489        &self,
490    ) -> Result<u32, crate::compiler_error::CompilerFailure> {
491        self.closure_coercion_vtable_type.ok_or_else(|| {
492            crate::codegen::internal_failure("closure coercion vtable is not registered")
493        })
494    }
495
496    pub fn closure_signatures(&self) -> impl Iterator<Item = ClosureSig> + '_ {
497        self.closure_struct_type_idx.keys().copied()
498    }
499
500    pub fn record_closure_coercion(&mut self, target: ClosureSig, index: u32) {
501        self.closure_coercions.insert(target, index);
502    }
503
504    pub fn closure_coercion(&self, target: ClosureSig) -> Option<u32> {
505        self.closure_coercions.get(&target).copied()
506    }
507
508    pub fn closure_struct_type_idx(&self, sig: ClosureSig) -> Option<u32> {
509        self.closure_struct_type_idx.get(&sig).copied()
510    }
511
512    pub fn env_type_idx(&self, expr_id: ExprId) -> Option<u32> {
513        self.env_type_idx.get(&expr_id).copied()
514    }
515
516    pub fn closure_func_idx(&self, expr_id: ExprId) -> Option<u32> {
517        self.closure_func_idx.get(&expr_id).copied()
518    }
519
520    pub fn closure_vtable_global_idx(&self) -> Option<u32> {
521        self.closure_vtable_global_idx
522    }
523
524    pub fn adapter_func_idx(&self, mangled: &MangledName) -> Option<u32> {
525        self.adapter_func_idx.get(mangled).copied()
526    }
527
528    pub fn adapter_closure_global_idx(&self, mangled: &MangledName) -> Option<u32> {
529        self.adapter_closure_global_idx.get(mangled).copied()
530    }
531
532    pub fn is_shared_closure_global(&self, mangled: &MangledName) -> bool {
533        self.shared_closure_globals.contains(mangled)
534    }
535
536    /// Whether a `value`-typed ref already satisfies a `slot` by WasmGC
537    /// subtyping, so a coercion needs no instruction.
538    ///
539    /// Answers `false` whenever the relation isn't known — an unrecorded
540    /// supertype edge, an abstract heap type — leaving the caller to emit a
541    /// `ref.cast`, which is valid for any two types sharing a hierarchy. Wrong
542    /// in that direction costs an always-succeeding cast; wrong in the other
543    /// direction is a module the validator rejects.
544    pub fn ref_fits_slot(&self, value: RefType, slot: RefType) -> bool {
545        if value.nullable && !slot.nullable {
546            return false;
547        }
548        let (HeapType::Concrete(value_idx), HeapType::Concrete(slot_idx)) =
549            (value.heap_type, slot.heap_type)
550        else {
551            return value.heap_type == slot.heap_type;
552        };
553        let mut current = value_idx;
554        while current != slot_idx {
555            // Wasm requires a supertype to be declared before its subtypes, so
556            // the walk strictly descends and terminates whatever the map holds.
557            match self.struct_supertype.get(&current) {
558                Some(&parent) if parent < current => current = parent,
559                _ => return false,
560            }
561        }
562        true
563    }
564
565    pub fn record_type(&mut self, mangled: MangledName, idx: u32) {
566        self.types.insert(mangled, idx);
567    }
568
569    /// Also seeds the intrinsic hierarchy: those edges are fixed by the indices,
570    /// so they can neither be known before this call nor go stale after it.
571    pub fn set_intrinsic_type_indices(&mut self, indices: IntrinsicTypeIndices) {
572        self.intrinsic_type_indices = Some(indices);
573        for (child, parent) in intrinsic_supertypes(indices) {
574            self.record_struct_supertype(child, parent);
575        }
576    }
577
578    pub fn record_struct_supertype(&mut self, child: u32, parent: u32) {
579        self.struct_supertype.insert(child, parent);
580    }
581
582    pub fn record_class_struct_type(&mut self, mangled: MangledName, idx: u32) {
583        self.class_struct_type_idx.insert(mangled, idx);
584    }
585
586    pub fn record_class_vtable_type(&mut self, mangled: MangledName, idx: u32) {
587        self.class_vtable_type_idx.insert(mangled, idx);
588    }
589
590    pub fn record_class_vtable_global(&mut self, mangled: MangledName, idx: u32) {
591        self.class_vtable_global_idx.insert(mangled, idx);
592    }
593
594    pub fn record_class_getter_global(&mut self, mangled: MangledName, idx: u32) {
595        self.class_getter_global_idx.insert(mangled, idx);
596    }
597
598    pub fn record_class_setter_global(&mut self, mangled: MangledName, idx: u32) {
599        self.class_setter_global_idx.insert(mangled, idx);
600    }
601
602    pub fn record_class_method_func(&mut self, class: MangledName, method: String, idx: u32) {
603        self.class_method_func_idx.insert((class, method), idx);
604    }
605
606    pub fn record_class_ctor_init_func(&mut self, class: MangledName, idx: u32) {
607        self.class_ctor_init_func_idx.insert(class, idx);
608    }
609
610    pub fn record_class_method_adapter_func(
611        &mut self,
612        class: MangledName,
613        method: String,
614        idx: u32,
615    ) {
616        self.class_method_adapter_func_idx
617            .insert((class, method), idx);
618    }
619
620    pub fn record_class_method_slot(&mut self, class: MangledName, method: String, slot: u32) {
621        self.class_method_slot.insert((class, method), slot);
622    }
623
624    pub(crate) fn record_host_parameter_defaults(
625        &mut self,
626        func_idx: u32,
627        defaults: Vec<Option<crate::DefaultValue>>,
628    ) {
629        self.host_parameter_defaults.insert(func_idx, defaults);
630    }
631
632    /// The declared defaults for a call to host function `func_idx`, aligned
633    /// to its `arg_count` arguments: entry `i` belongs to argument `i`.
634    ///
635    /// The arguments fill the trailing parameters, so any leading parameter
636    /// without an argument (a method's receiver, already on the stack) is
637    /// skipped. A function without recorded defaults has none.
638    pub(crate) fn host_call_defaults(
639        &self,
640        func_idx: u32,
641        arg_count: usize,
642    ) -> &[Option<crate::DefaultValue>] {
643        let defaults = self
644            .host_parameter_defaults
645            .get(&func_idx)
646            .map_or(&[][..], Vec::as_slice);
647        let receivers = defaults.len().saturating_sub(arg_count);
648        defaults.get(receivers..).unwrap_or_default()
649    }
650
651    pub fn record_class_payload_method(&mut self, class: MangledName, method: String) {
652        self.class_payload_methods.insert((class, method));
653    }
654
655    pub(crate) fn record_rewritable_members(
656        &mut self,
657        members: crate::codegen::classes::RewritableMembers,
658    ) {
659        self.rewritable_members = members;
660    }
661
662    /// A payload method, local or imported, that this module may overwrite
663    /// through a structural alias, so a direct call must read the payload rather
664    /// than the class vtable. Every other call keeps the cheaper vtable dispatch.
665    pub fn is_rewritable_class_method(&self, class: &MangledName, method: &str) -> bool {
666        self.rewritable_members.includes(method) && self.is_class_payload_method(class, method)
667    }
668
669    pub fn record_class_method_sig(&mut self, class: MangledName, method: String, sig: u32) {
670        self.class_method_sig.insert((class, method), sig);
671    }
672
673    pub fn record_class_method_abi(
674        &mut self,
675        class: MangledName,
676        method: String,
677        abi: MethodSlotAbi,
678    ) {
679        self.class_method_abi.insert((class, method), abi);
680    }
681
682    pub fn record_class_ctor_abi(&mut self, class: MangledName, params: Vec<ValType>) {
683        self.class_ctor_abi.insert(class, params);
684    }
685
686    pub fn record_class_field_narrowing_check(
687        &mut self,
688        class: MangledName,
689        field: String,
690        check: crate::FieldNarrowingCheck,
691    ) {
692        self.class_field_narrowing_check
693            .insert((class, field), check);
694    }
695
696    pub fn record_class_field_slot(&mut self, class: MangledName, field: String, slot: u32) {
697        self.class_field_slot.insert((class, field), slot);
698    }
699
700    pub fn record_class_field_setup(&mut self, class: MangledName, setup: Vec<FieldSetup>) {
701        self.class_field_setup.insert(class, setup);
702    }
703
704    pub fn record_vtable_global(&mut self, ty: Type, idx: u32) {
705        self.vtable_global_idx.insert(ty, idx);
706    }
707
708    pub fn record_field_names_global(
709        &mut self,
710        field_names: Vec<super::field_names::FieldName>,
711        idx: u32,
712    ) {
713        self.field_names_global_idx.insert(field_names, idx);
714    }
715
716    pub fn record_field_name_string_global(&mut self, name: String, idx: u32) {
717        self.field_name_string_global_idx.insert(name, idx);
718    }
719
720    pub fn record_box_type(&mut self, value_type: ValType, idx: u32) {
721        self.box_type_idx.insert(value_type, idx);
722    }
723
724    pub fn record_closure_func_type(&mut self, sig: ClosureSig, idx: u32) {
725        self.closure_func_type_idx.insert(sig, idx);
726    }
727
728    pub fn record_closure_struct_type(&mut self, sig: ClosureSig, idx: u32) {
729        self.closure_struct_type_idx.insert(sig, idx);
730    }
731
732    pub fn record_env_type(&mut self, expr_id: ExprId, idx: u32) {
733        self.env_type_idx.insert(expr_id, idx);
734    }
735
736    pub fn record_closure_func_idx(&mut self, expr_id: ExprId, idx: u32) {
737        self.closure_func_idx.insert(expr_id, idx);
738    }
739
740    pub fn set_closure_vtable_global(&mut self, idx: u32) {
741        self.closure_vtable_global_idx = Some(idx);
742    }
743
744    // Kept as Option so call sites pattern-match consistently with other optional resources.
745    pub fn error_tag_idx(&self) -> Option<u32> {
746        self.error_tag_idx
747    }
748
749    pub fn set_error_tag_idx(&mut self, idx: u32) {
750        self.error_tag_idx = Some(idx);
751    }
752
753    pub fn record_adapter_func_idx(&mut self, mangled: MangledName, idx: u32) {
754        self.adapter_func_idx.insert(mangled, idx);
755    }
756
757    pub fn record_adapter_closure_global_idx(&mut self, mangled: MangledName, idx: u32) {
758        self.adapter_closure_global_idx.insert(mangled, idx);
759    }
760
761    pub fn record_shared_closure_global_idx(&mut self, mangled: MangledName, idx: u32) {
762        self.shared_closure_globals.insert(mangled.clone());
763        self.adapter_closure_global_idx.insert(mangled, idx);
764    }
765
766    pub fn record_class_guard_layout(
767        &mut self,
768        class: MangledName,
769        parent: Option<&MangledName>,
770        named_len: u32,
771        guarded: bool,
772    ) -> Result<(), CompilerFailure> {
773        let depth = match parent {
774            Some(parent) => self
775                .recorded_class_guard_layout(parent)?
776                .inheritance_depth
777                .checked_add(1)
778                .ok_or_else(|| internal_failure("class inheritance depth overflowed"))?,
779            None => 0,
780        };
781        self.class_guard_layout.insert(
782            class,
783            ClassGuardLayout {
784                inheritance_depth: depth,
785                named_payload_len: named_len,
786                has_instance_guards: guarded,
787            },
788        );
789        Ok(())
790    }
791
792    /// Defaults an unrecorded class to no guards. Only the field-guard emitters
793    /// still rely on this until SUB-633 item 17; everything else uses
794    /// [`Self::recorded_class_guard_layout`].
795    pub fn class_guard_layout(&self, class: &MangledName) -> ClassGuardLayout {
796        self.class_guard_layout
797            .get(class)
798            .copied()
799            .unwrap_or_default()
800    }
801
802    /// Guard layout of a class this module lays out or reconstructs, which is
803    /// recorded before any use.
804    pub fn recorded_class_guard_layout(
805        &self,
806        class: &MangledName,
807    ) -> Result<ClassGuardLayout, CompilerFailure> {
808        self.class_guard_layout.get(class).copied().ok_or_else(|| {
809            internal_failure(format!(
810                "class `{}` has no recorded guard layout",
811                class.as_str()
812            ))
813        })
814    }
815
816    pub fn record_instance_field_guard(
817        &mut self,
818        class: Type,
819        declaration: MangledName,
820        field: String,
821        function: u32,
822    ) {
823        self.instance_field_guards
824            .insert((class, declaration, field), function);
825    }
826
827    pub fn instance_field_guards<'a>(
828        &'a self,
829        class: &'a Type,
830    ) -> impl Iterator<Item = (&'a MangledName, &'a str, u32)> + 'a {
831        self.instance_field_guards
832            .iter()
833            .filter(move |((ty, _, _), _)| ty == class)
834            .map(|((_, declaration, field), function)| (declaration, field.as_str(), *function))
835    }
836
837    pub fn record_runtime_validator(&mut self, key: Type, idx: u32) {
838        if let Type::AliasRef { mangled, args, .. } | Type::InterfaceRef { mangled, args, .. } =
839            &key
840            && let Some(names) = generic_parameter_names(args)
841        {
842            self.generic_runtime_validators
843                .insert(mangled.clone(), (names, idx));
844        }
845        self.runtime_validator_idx.insert(key, idx);
846    }
847
848    /// Function index of a recursive runtime validator keyed by an alias or
849    /// interface back-edge.
850    pub fn runtime_validator_idx(&self, key: &Type) -> Option<u32> {
851        self.runtime_validator_idx
852            .get(key)
853            .copied()
854            .or_else(|| self.generic_runtime_validator(key).map(|(_, idx)| idx))
855    }
856
857    pub fn generic_runtime_validator(&self, key: &Type) -> Option<(&[String], u32)> {
858        let (Type::AliasRef {
859            mangled: identity, ..
860        }
861        | Type::InterfaceRef {
862            mangled: identity, ..
863        }) = key.lowered()
864        else {
865            return None;
866        };
867        let (names, index) = self.generic_runtime_validators.get(identity)?;
868        if self
869            .runtime_validator_idx
870            .get(key)
871            .is_some_and(|exact| exact != index)
872        {
873            return None;
874        }
875        Some((names.as_slice(), *index))
876    }
877
878    pub fn record_func(&mut self, mangled: MangledName, idx: u32) {
879        self.funcs.insert(mangled, idx);
880    }
881
882    pub fn record_global(&mut self, mangled: MangledName, idx: u32) {
883        self.globals.insert(mangled, idx);
884    }
885
886    /// A guarded module global's initialization flag; see `init_guard`.
887    pub fn record_init_guard(
888        &mut self,
889        global: MangledName,
890        flag: crate::codegen::init_guard::InitFlag,
891    ) {
892        self.init_guards.insert(global, flag);
893    }
894
895    pub fn record_class_declaration_flag(&mut self, index: usize, flag_idx: u32) {
896        self.class_declaration_flags
897            .entry(index)
898            .or_default()
899            .push(flag_idx);
900    }
901
902    /// Language globals retain their declared type for checked narrowed reads.
903    /// Runtime metadata globals only need an index.
904    pub fn record_typed_global(&mut self, mangled: MangledName, idx: u32, ty: Type) {
905        self.global_types.insert(mangled.clone(), ty);
906        self.record_global(mangled, idx);
907    }
908
909    /// Writes both the wasm function index and the static-call registry entry;
910    /// splitting them invited callers to drop one half by accident.
911    pub fn record_imported_fn(
912        &mut self,
913        mangled: MangledName,
914        idx: u32,
915        params: Vec<Type>,
916        ret: Type,
917        is_host: bool,
918    ) {
919        self.funcs.insert(mangled.clone(), idx);
920        self.top_level_fns.insert(
921            mangled,
922            TopLevelFn {
923                wasm_idx: idx,
924                params,
925                ret,
926                is_host,
927            },
928        );
929    }
930
931    pub fn record_local_fn(
932        &mut self,
933        mangled: MangledName,
934        idx: u32,
935        params: Vec<Type>,
936        ret: Type,
937    ) -> Result<(), CompilerFailure> {
938        // Guard against an accidental mangle/source-name swap.
939        if !mangled.as_str().contains(crate::mangle::SEP) {
940            return Err(internal_failure(format!(
941                "local function `{}` has no mangled package prefix",
942                mangled.as_str()
943            )));
944        }
945        self.funcs.insert(mangled.clone(), idx);
946        self.top_level_fns.insert(
947            mangled,
948            TopLevelFn {
949                wasm_idx: idx,
950                params,
951                ret,
952                is_host: false,
953            },
954        );
955        Ok(())
956    }
957
958    pub fn top_level_fn(&self, mangled: &MangledName) -> Option<&TopLevelFn> {
959        self.top_level_fns.get(mangled)
960    }
961
962    pub fn record_iface_dispatch(&mut self, iface: MangledName, dispatch: Dispatch) {
963        self.iface_dispatch.insert(iface, dispatch);
964    }
965
966    pub fn record_static_value_tag(&mut self, iface: MangledName, tag: String) {
967        self.static_value_tags.insert(iface, tag);
968    }
969
970    pub fn static_value_tag(&self, iface: &MangledName) -> Option<&str> {
971        self.static_value_tags.get(iface).map(String::as_str)
972    }
973
974    pub fn iface_dispatch(&self, iface: &MangledName) -> Option<Dispatch> {
975        self.iface_dispatch.get(iface).copied()
976    }
977
978    pub fn record_iface_method_abi(&mut self, method_key: MangledName, abi: MethodSlotAbi) {
979        self.iface_method_abi.insert(method_key, abi);
980    }
981
982    pub fn iface_method_abi(&self, method_key: &MangledName) -> Option<&MethodSlotAbi> {
983        self.iface_method_abi.get(method_key)
984    }
985
986    pub fn record_intrinsic_member(&mut self, mangled: MangledName) {
987        self.intrinsic_members.insert(mangled);
988    }
989
990    pub fn is_intrinsic_member(&self, mangled: &MangledName) -> bool {
991        self.intrinsic_members.contains(mangled)
992    }
993
994    pub fn value_type(&self, ty: &Type) -> Result<ValType, crate::compiler_error::CompilerFailure> {
995        let ty = ty.lowered();
996        Ok(match ty {
997            Type::Number | Type::NumberLiteral(_) => ValType::F64,
998            Type::Boolean | Type::BooleanLiteral(_) => ValType::I32,
999            Type::String | Type::StringLiteral(_) => {
1000                let idx = self.string_type_idx().ok_or_else(|| crate::codegen::internal_failure("Type::String requires the intrinsic types to be declared (declare_intrinsic_types)"))?;
1001                ValType::Ref(RefType {
1002                    nullable: false,
1003                    heap_type: HeapType::Concrete(idx),
1004                })
1005            }
1006            Type::BigInt | Type::BigIntLiteral(_) => {
1007                let idx = self.bigint_type_idx().ok_or_else(|| crate::codegen::internal_failure("Type::BigInt requires the intrinsic types to be declared (declare_intrinsic_types)"))?;
1008                ValType::Ref(RefType {
1009                    nullable: false,
1010                    heap_type: HeapType::Concrete(idx),
1011                })
1012            }
1013            Type::Object { .. } => {
1014                // All objects lower to `$ObjectShape` (not the arity-specific subtype):
1015                // sibling per-arity subtypes are not Wasm subtypes of each other, so
1016                // width subtyping and object-only unions require `$ObjectShape` as the
1017                // common supertype.
1018                let idx = self
1019                    .intrinsic_type_indices()
1020                    .ok_or_else(|| {
1021                        crate::codegen::internal_failure("intrinsics declared by codegen entry")
1022                    })?
1023                    .object_shape;
1024                ValType::Ref(RefType {
1025                    nullable: false,
1026                    heap_type: HeapType::Concrete(idx),
1027                })
1028            }
1029            Type::Array(_) => {
1030                let idx = self.array_type_idx().ok_or_else(|| crate::codegen::internal_failure("Type::Array requires the intrinsic types to be declared (declare_intrinsic_types)"))?;
1031                ValType::Ref(RefType {
1032                    nullable: false,
1033                    heap_type: HeapType::Concrete(idx),
1034                })
1035            }
1036            Type::Tuple(_) => {
1037                // Tuples lower to `$Array` — they are arrays at runtime; positional
1038                // element types are tracked by the typechecker only.
1039                let idx = self.array_type_idx().ok_or_else(|| crate::codegen::internal_failure("Type::Tuple requires the intrinsic types to be declared (declare_intrinsic_types)"))?;
1040                ValType::Ref(RefType {
1041                    nullable: false,
1042                    heap_type: HeapType::Concrete(idx),
1043                })
1044            }
1045            Type::Uint8Array => {
1046                let idx = self.uint8_array_type_idx().ok_or_else(|| crate::codegen::internal_failure("Type::Uint8Array requires the intrinsic types to be declared (declare_intrinsic_types)"))?;
1047                ValType::Ref(RefType {
1048                    nullable: false,
1049                    heap_type: HeapType::Concrete(idx),
1050                })
1051            }
1052            Type::TypeVar(_)
1053            | Type::GenericParam { .. }
1054            | Type::Unknown
1055            | Type::NonPrimitive
1056            | Type::Void => {
1057                // Erased-generic and dynamic slots lower to nullable `(ref null $Object)`;
1058                // nullable so `null` literals can flow in via WasmGC subtyping.
1059                let object_idx = self
1060                    .intrinsic_type_indices()
1061                    .ok_or_else(|| {
1062                        crate::codegen::internal_failure("intrinsics declared by codegen entry")
1063                    })?
1064                    .object;
1065                ValType::Ref(RefType {
1066                    nullable: true,
1067                    heap_type: HeapType::Concrete(object_idx),
1068                })
1069            }
1070            Type::Function { .. } => {
1071                let sig = crate::codegen::closures::classify(ty)?;
1072                let idx = self.closure_struct_type_idx(sig).ok_or_else(|| {
1073                    crate::codegen::internal_failure(format!(
1074                        "closure signature {sig:?} is not registered"
1075                    ))
1076                })?;
1077                ValType::Ref(RefType {
1078                    nullable: false,
1079                    heap_type: HeapType::Concrete(idx),
1080                })
1081            }
1082            Type::Null => {
1083                let object_idx = self
1084                    .intrinsic_type_indices()
1085                    .ok_or_else(|| {
1086                        crate::codegen::internal_failure("intrinsics declared by codegen entry")
1087                    })?
1088                    .object;
1089                ValType::Ref(RefType {
1090                    nullable: true,
1091                    heap_type: HeapType::Concrete(object_idx),
1092                })
1093            }
1094            Type::Undefined => {
1095                let intrinsic = self.intrinsic_type_indices().ok_or_else(|| {
1096                    crate::codegen::internal_failure("undefined intrinsic is not declared")
1097                })?;
1098                ValType::Ref(RefType {
1099                    nullable: false,
1100                    heap_type: HeapType::Concrete(intrinsic.undefined),
1101                })
1102            }
1103            Type::Error => {
1104                // `Error` only follows a reported typecheck failure.
1105                return Err(crate::codegen::internal_failure(
1106                    "error cannot occupy a value slot",
1107                ));
1108            }
1109            Type::Never => {
1110                // The surrounding expression always diverges, but a `: never` function's
1111                // result slot still needs a concrete Wasm type for the validator. Share
1112                // the `Unknown` lowering — the call always throws before any value reaches
1113                // the slot.
1114                let object_idx = self
1115                    .intrinsic_type_indices()
1116                    .ok_or_else(|| {
1117                        crate::codegen::internal_failure("intrinsics declared by codegen entry")
1118                    })?
1119                    .object;
1120                ValType::Ref(RefType {
1121                    nullable: true,
1122                    heap_type: HeapType::Concrete(object_idx),
1123                })
1124            }
1125            Type::Union(members) => {
1126                let lowered: Vec<ValType> = members
1127                    .iter()
1128                    .map(|m| self.value_type(m))
1129                    .collect::<Result<_, _>>()?;
1130                let first_lowering = *lowered.first().ok_or_else(|| {
1131                    crate::codegen::internal_failure("Type::Union holds ≥2 members")
1132                })?;
1133                if lowered.iter().all(|&lowering| lowering == first_lowering) {
1134                    return Ok(first_lowering);
1135                }
1136                // Object-only union → `$ObjectShape`; mixed union → `$Object`.
1137                let all_object_or_null = members
1138                    .iter()
1139                    .all(|m| matches!(m.lowered(), Type::Object { .. } | Type::Null));
1140                let heap_idx = if all_object_or_null {
1141                    self.intrinsic_type_indices()
1142                        .ok_or_else(|| {
1143                            crate::codegen::internal_failure("intrinsics declared by codegen entry")
1144                        })?
1145                        .object_shape
1146                } else {
1147                    self.intrinsic_type_indices()
1148                        .ok_or_else(|| {
1149                            crate::codegen::internal_failure("intrinsics declared by codegen entry")
1150                        })?
1151                        .object
1152                };
1153                // Nullable iff some member *lowers* nullable, not iff one is
1154                // spelled `null`: a `TypeVar`, `GenericParam`, `unknown`, or an
1155                // unrecorded `ClassRef` can hold null after instantiation
1156                // without naming it, and a non-null slot would trap the write.
1157                let nullable = lowered.iter().copied().any(is_nullable_ref)
1158                    || members
1159                        .iter()
1160                        .any(|member| matches!(member.lowered(), Type::Undefined));
1161                ValType::Ref(RefType {
1162                    nullable,
1163                    heap_type: HeapType::Concrete(heap_idx),
1164                })
1165            }
1166            // ClassRef lowers to the universal `(ref null $Object)` in this
1167            // typecheck-only slice; the concrete `(ref $Foo)` lands with class
1168            // codegen (SUB-480).
1169            // A locally-known class lowers to its concrete `(ref $Foo)` (non-null);
1170            // `struct.new`/`struct.get`/`struct.set` need the concrete type. A class
1171            // not in this module's rec group (cross-package, SUB-488) falls back to
1172            // the universal `(ref null $Object)`.
1173            Type::ClassRef { mangled, .. } => match self.class_struct_type_idx(mangled) {
1174                Some(idx) => ValType::Ref(RefType {
1175                    nullable: false,
1176                    heap_type: HeapType::Concrete(idx),
1177                }),
1178                None => ValType::Ref(RefType {
1179                    nullable: true,
1180                    heap_type: HeapType::Concrete(
1181                        self.intrinsic_type_indices()
1182                            .ok_or_else(|| {
1183                                crate::codegen::internal_failure(
1184                                    "intrinsics declared by codegen entry",
1185                                )
1186                            })?
1187                            .object,
1188                    ),
1189                }),
1190            },
1191            Type::InterfaceRef { .. } => {
1192                let object_idx = self
1193                    .intrinsic_type_indices()
1194                    .ok_or_else(|| {
1195                        crate::codegen::internal_failure("intrinsics declared by codegen entry")
1196                    })?
1197                    .object;
1198                ValType::Ref(RefType {
1199                    nullable: true,
1200                    heap_type: HeapType::Concrete(object_idx),
1201                })
1202            }
1203            Type::AliasRef { .. } => {
1204                // A recursion back-edge's runtime value is a heap object
1205                // (object-shaped alias) or a boxed/erased union member —
1206                // both lower to the universal `(ref null $Object)`, the
1207                // same as `InterfaceRef`. The structural type is tracked
1208                // only at the typechecker layer.
1209                let object_idx = self
1210                    .intrinsic_type_indices()
1211                    .ok_or_else(|| {
1212                        crate::codegen::internal_failure("intrinsics declared by codegen entry")
1213                    })?
1214                    .object;
1215                ValType::Ref(RefType {
1216                    nullable: true,
1217                    heap_type: HeapType::Concrete(object_idx),
1218                })
1219            }
1220            Type::NumberEnum { .. } => {
1221                let idx = self.boxed_number_type_idx().ok_or_else(|| {
1222                    crate::codegen::internal_failure(
1223                        "Type::NumberEnum requires the intrinsic types to be declared",
1224                    )
1225                })?;
1226                ValType::Ref(RefType {
1227                    nullable: false,
1228                    heap_type: HeapType::Concrete(idx),
1229                })
1230            }
1231            Type::StringEnum { .. } => {
1232                let idx = self.string_type_idx().ok_or_else(|| {
1233                    crate::codegen::internal_failure(
1234                        "Type::StringEnum requires the intrinsic types to be declared",
1235                    )
1236                })?;
1237                ValType::Ref(RefType {
1238                    nullable: false,
1239                    heap_type: HeapType::Concrete(idx),
1240                })
1241            }
1242            Type::Alias { .. } | Type::Refined { .. } | Type::Readonly(_) => {
1243                return Err(crate::codegen::internal_failure(
1244                    "unpeeled alias in value type lowering",
1245                ));
1246            }
1247        })
1248    }
1249
1250    pub fn wasm_result(
1251        &self,
1252        ty: &Type,
1253    ) -> Result<Vec<ValType>, crate::compiler_error::CompilerFailure> {
1254        Ok(vec![self.value_type(ty)?])
1255    }
1256
1257    /// Wasm value type of a class-method **vtable-slot** parameter or result,
1258    /// of a **constructor** parameter, or of a **box cell**'s payload.
1259    ///
1260    /// These slots erase every class type and type variable to the boxed
1261    /// object slot. [`value_type`](Self::value_type) cannot serve here because
1262    /// its `ClassRef` lowering is phase-dependent: it yields the concrete
1263    /// struct once that struct type is recorded, and imported classes are
1264    /// reconstructed *before* local slot signatures are built while local
1265    /// classes are reserved *after* — so one declaration would lower two ways.
1266    /// Erasing unconditionally keeps a slot's ABI stable across phases and
1267    /// identical between a producer and a cross-package consumer, and keeps it
1268    /// in step with the types recorded for each slot ([`MethodSlotAbi`] for a
1269    /// method, `class_ctor_abi` for a constructor).
1270    pub fn slot_value_type(
1271        &self,
1272        ty: &Type,
1273    ) -> Result<ValType, crate::compiler_error::CompilerFailure> {
1274        if is_erased(ty) {
1275            let object = self
1276                .intrinsic_type_indices()
1277                .ok_or_else(|| {
1278                    crate::codegen::internal_failure(
1279                        "intrinsics declared before class slot signatures",
1280                    )
1281                })?
1282                .object;
1283            return Ok(ValType::Ref(RefType {
1284                nullable: true,
1285                heap_type: HeapType::Concrete(object),
1286            }));
1287        }
1288        self.value_type(ty)
1289    }
1290
1291    pub fn slot_wasm_result(
1292        &self,
1293        ty: &Type,
1294    ) -> Result<Vec<ValType>, crate::compiler_error::CompilerFailure> {
1295        Ok(vec![self.slot_value_type(ty)?])
1296    }
1297
1298    pub fn host_value_type(
1299        &self,
1300        ty: &Type,
1301    ) -> Result<ValType, crate::compiler_error::CompilerFailure> {
1302        if matches!(ty, Type::String | Type::StringLiteral(_)) {
1303            let idx = self.raw_string_type_idx().ok_or_else(|| crate::codegen::internal_failure("Type::String requires the intrinsic types to be declared (declare_intrinsic_types)"))?;
1304            return Ok(ValType::Ref(RefType {
1305                nullable: false,
1306                heap_type: HeapType::Concrete(idx),
1307            }));
1308        }
1309        if matches!(ty, Type::Uint8Array) {
1310            let idx = self.raw_uint8_array_type_idx().ok_or_else(|| crate::codegen::internal_failure("Type::Uint8Array requires the intrinsic types to be declared (declare_intrinsic_types)"))?;
1311            return Ok(ValType::Ref(RefType {
1312                nullable: false,
1313                heap_type: HeapType::Concrete(idx),
1314            }));
1315        }
1316        self.value_type(ty)
1317    }
1318}
1319
1320/// Names of a validator key's type arguments when every one is a generic
1321/// parameter, so one validator serves each instantiation.
1322fn generic_parameter_names(args: &[Type]) -> Option<Vec<String>> {
1323    if args.is_empty() {
1324        return None;
1325    }
1326    args.iter()
1327        .map(|arg| match arg {
1328            Type::TypeVar(name) => Some(name.clone()),
1329            _ => None,
1330        })
1331        .collect()
1332}
1333
1334#[cfg(test)]
1335mod tests {
1336    use super::*;
1337
1338    #[test]
1339    fn func_idx_round_trip_across_name_shapes() {
1340        let mut map = SymbolTable::default();
1341        map.record_local_fn(
1342            crate::mangle::package_symbol("main", "main"),
1343            10,
1344            Vec::new(),
1345            Type::Void,
1346        )
1347        .unwrap();
1348        let unmangled: MangledName = serde_json::from_str("\"main\"").unwrap();
1349        assert!(
1350            map.record_local_fn(unmangled.clone(), 13, Vec::new(), Type::Void)
1351                .is_err()
1352        );
1353        assert_eq!(map.func_idx(&unmangled), None);
1354        map.record_imported_fn(
1355            crate::mangle::prelude("string_concat"),
1356            11,
1357            vec![Type::String, Type::String],
1358            Type::String,
1359            false,
1360        );
1361        map.record_func(
1362            crate::mangle::extend(&crate::mangle::prelude("Number"), "toString"),
1363            12,
1364        );
1365
1366        assert_eq!(
1367            map.func_idx(&crate::mangle::package_symbol("main", "main")),
1368            Some(10)
1369        );
1370        assert_eq!(map.prelude_func_idx("string_concat"), Some(11));
1371        assert_eq!(
1372            map.func_idx(&crate::mangle::extend(
1373                &crate::mangle::prelude("Number"),
1374                "toString",
1375            )),
1376            Some(12),
1377        );
1378
1379        assert_eq!(
1380            map.func_idx(&crate::mangle::package_symbol("main", "string_concat")),
1381            None,
1382        );
1383        assert_eq!(map.prelude_func_idx("toString"), None);
1384    }
1385}