Skip to main content

submilli_engine/codegen/function_emitter/
mod.rs

1//! `FunctionEmitter` — single-pass builder for the body of one Wasm function.
2
3pub(super) mod binding_cells;
4pub mod cast;
5pub mod expr;
6mod finally;
7pub mod json;
8pub mod stmt;
9pub mod virtual_call;
10
11use std::collections::{BTreeMap, HashMap, HashSet};
12
13use wasm_encoder::{BlockType, Encode, Function, Instruction, ValType};
14
15use crate::codegen::CodegenCtx;
16use crate::compiler_error::CompilerFailure;
17use crate::{ExprId, Ident, Span, Type};
18
19#[derive(Copy, Clone, Debug, PartialEq, Eq)]
20pub enum ExprContext {
21    Value,
22    Statement,
23}
24
25/// Where a `return` in a body coerces to. Exactly one applies to any body.
26#[derive(Clone, Debug, Default)]
27pub enum ReturnTarget {
28    /// A constructor initializer or compiler-generated body with no result.
29    #[default]
30    NoResult,
31    /// A *recorded* Wasm result: a closure's funcref result, or a class
32    /// method's vtable slot, which this body's own annotation may lower either
33    /// wider or narrower than.
34    Slot(ValType),
35    /// A top-level function, whose declared return type *is* its signature.
36    Declared(Type),
37}
38
39pub struct FunctionEmitter<'a> {
40    pub(super) cast_diagnostic: Option<super::cast_diagnostics::Locals>,
41    pub runtime_type_params: BTreeMap<String, (u32, u32)>,
42    ctx: &'a CodegenCtx<'a>,
43
44    next_local_index: u32,
45    parameter_types: Vec<ValType>,
46    locals: Vec<(u32, ValType)>,
47    instructions: Vec<Instruction<'static>>,
48    /// How many leading `instructions` [`Self::body_size`] has
49    /// encoded so far.
50    sized_instructions: usize,
51    /// The encoded size of those instructions.
52    instruction_bytes: usize,
53    // Construction seeds a root; pop_scope rejects removing it.
54    scopes: Vec<Scope>,
55
56    wasm_block_depth: u32,
57    loop_contexts: Vec<LoopLabels>,
58    #[allow(dead_code)]
59    return_block_depth: u32,
60
61    return_target: ReturnTarget,
62
63    /// Pending completion targets, innermost last. Bodies are emitted once.
64    finally_stack: Vec<finally::FinallyFrame>,
65
66    /// Local receiver for a class member, ordinary function expression, or
67    /// arrow capturing an enclosing receiver.
68    this_local: Option<u32>,
69    pub(super) dynamic_this: bool,
70    pub(super) call_receiver: Option<u32>,
71
72    /// Set while emitting a constructor *init* fn: the class being constructed.
73    /// `super(...)` reads it to emit the field-setup sequence (own initializers
74    /// and parameter-property copies) right after the parent is initialized.
75    ctor_class: Option<crate::MangledName>,
76
77    source_mappings: Vec<(usize, Span)>,
78    /// The source being emitted when the function first needed more locals
79    /// than the Wasm engine accepts.
80    locals_limit_span: Option<Span>,
81    /// The encoded size of `locals`' entries.
82    local_entry_bytes: usize,
83    /// The first instruction [`Self::body_size`] found past the Wasm
84    /// engine's body-size limit.
85    body_limit_instruction: Option<usize>,
86
87    /// Expression nodes already evaluated into a local, so re-emitting one
88    /// reads the local instead of running it again. A compound assignment is
89    /// the one place the typed AST reuses an `ExprId` to mean a *single*
90    /// evaluation: `a[i] += v` synthesizes its read from the very receiver and
91    /// index nodes the write uses.
92    single_evaluations: Vec<(ExprId, u32)>,
93    parameter_input_slots: BTreeMap<String, (u32, Type)>,
94    uninitialized_parameters: HashSet<Ident>,
95    parameter_binding_cells: HashMap<Ident, u32>,
96    /// Binding cells a read can reach before their initializer has run: a
97    /// parameter or pattern cell a default can observe, or a capture that may
98    /// be one. Every other cell is initialized when it is created.
99    pub(super) uninitialized_cells: HashSet<u32>,
100    /// Slots of the locals that hold a read which may have found no value.
101    /// See `typechecker::infer::missing_reads`.
102    pub(super) maybe_missing_slots: HashMap<u32, MaybeMissingSlot>,
103    /// While the value of a local with a present flag is emitted: that value's
104    /// node and the flag, which that read is asked to set (see
105    /// `emit_value_with_present_flag`).
106    pub(super) present_flag_request: Option<(ExprId, u32)>,
107}
108
109/// A local holding a read that may have found no value.
110#[derive(Clone)]
111pub(super) struct MaybeMissingSlot {
112    /// The message a read of it at `T` throws when the value is missing.
113    pub(super) message: String,
114    /// Its storage, `T | undefined`, which also keys its box when boxed.
115    pub(super) storage: Type,
116    /// For a local typed with a type parameter, whose instantiation may hold
117    /// `undefined`, an i32 local that is 1 when the value was found: the
118    /// value alone can't tell a missing read from a present `undefined`.
119    pub(super) present_flag: Option<u32>,
120}
121
122/// A name's two slots. `declared` is the binding's storage — the slot every
123/// write goes to. `shadow` is a snapshot of that storage taken at a narrowing,
124/// held in a local of the narrowed value type so a narrowed read needs no cast.
125/// Keeping them apart is what stops a widening write from landing in the
126/// narrowed slot (`let x: string | null; x = "ab"; x = null`), and what lets a
127/// read after such a write go back to the storage instead of a stale shadow.
128#[derive(Clone, Copy, Default)]
129struct Binding {
130    /// `None` only while a narrowing snapshot sits in a scope inner to the one
131    /// that declares the name — the shadow is visible here, the storage is not.
132    declared: Option<(u32, ValType)>,
133    shadow: Option<(u32, ValType)>,
134}
135
136#[derive(Default)]
137struct Scope {
138    bindings: BTreeMap<String, Binding>,
139    narrow_sources: BTreeMap<String, crate::ExprId>,
140}
141
142impl Scope {
143    fn new() -> Self {
144        Self::default()
145    }
146    /// Declare (or re-declare) `name`'s storage. Re-declaring drops any
147    /// snapshot: the storage moved, so the old one describes nothing.
148    fn define(&mut self, name: String, index: u32, ty: ValType) {
149        self.bindings.insert(
150            name,
151            Binding {
152                declared: Some((index, ty)),
153                shadow: None,
154            },
155        );
156    }
157    fn set_shadow(&mut self, name: String, index: u32, ty: ValType) {
158        self.bindings.entry(name).or_default().shadow = Some((index, ty));
159    }
160    fn clear_shadow(&mut self, name: &str) {
161        if let Some(binding) = self.bindings.get_mut(name) {
162            binding.shadow = None;
163        }
164    }
165    fn clear_all_shadows(&mut self) {
166        for binding in self.bindings.values_mut() {
167            binding.shadow = None;
168        }
169    }
170    /// The slot a narrowed read resolves to: the shadow when one is live.
171    fn lookup(&self, name: &str) -> Option<(u32, ValType)> {
172        self.bindings
173            .get(name)
174            .and_then(|b| b.shadow.or(b.declared))
175    }
176    fn lookup_declared(&self, name: &str) -> Option<(u32, ValType)> {
177        self.bindings.get(name).and_then(|b| b.declared)
178    }
179}
180
181struct LoopLabels {
182    break_depth: u32,
183    continue_depth: u32,
184    /// `true` for switch frames; `continue` skips these to find the enclosing loop.
185    is_switch: bool,
186    /// Finallys pushed after this loop opened must run before any break/continue out of it fires.
187    finally_count_at_push: usize,
188}
189
190impl<'a> FunctionEmitter<'a> {
191    /// Parameters are implicit Wasm locals (0..N) — not in the `locals` declaration section.
192    pub fn new(
193        ctx: &'a CodegenCtx<'a>,
194        params: &[(Ident, ValType)],
195    ) -> Result<Self, CompilerFailure> {
196        let parameter_count = crate::codegen::wasm_u32(params.len())?;
197        super::FunctionLimit::exceeded(0, parameter_count).map_or(Ok(()), |limit| {
198            Err(limit.failure(params.last().map(|(name, _)| name.span)))
199        })?;
200        let mut emitter = Self {
201            ctx,
202            runtime_type_params: BTreeMap::new(),
203            next_local_index: parameter_count,
204            parameter_types: params.iter().map(|(_, ty)| *ty).collect(),
205            locals: Vec::new(),
206            instructions: Vec::new(),
207            sized_instructions: 0,
208            instruction_bytes: 0,
209            scopes: vec![Scope::new()],
210            wasm_block_depth: 0,
211            loop_contexts: Vec::new(),
212            return_block_depth: 0,
213            return_target: ReturnTarget::NoResult,
214            finally_stack: Vec::new(),
215            this_local: None,
216            dynamic_this: false,
217            call_receiver: None,
218            ctor_class: None,
219            source_mappings: Vec::new(),
220            locals_limit_span: None,
221            body_limit_instruction: None,
222            local_entry_bytes: 0,
223            single_evaluations: Vec::new(),
224            parameter_input_slots: BTreeMap::new(),
225            uninitialized_parameters: HashSet::new(),
226            parameter_binding_cells: HashMap::new(),
227            maybe_missing_slots: HashMap::new(),
228            present_flag_request: None,
229            uninitialized_cells: HashSet::new(),
230            cast_diagnostic: None,
231        };
232        let mut scope = Scope::new();
233        for (index, (name, ty)) in params.iter().enumerate() {
234            scope.define(name.name.clone(), crate::codegen::wasm_u32(index)?, *ty);
235        }
236        emitter.scopes = vec![scope];
237        Ok(emitter)
238    }
239
240    pub fn define_local(&mut self, name: &Ident, ty: ValType) -> Result<u32, CompilerFailure> {
241        let index = self.add_anonymous_local(ty)?;
242        self.rebind_in_innermost_scope(&name.name, index, ty)?;
243        Ok(index)
244    }
245
246    /// The local holding `id`'s already-computed value, if the statement being
247    /// emitted registered one.
248    pub fn evaluated_slot(&self, id: ExprId) -> Option<u32> {
249        self.single_evaluations
250            .iter()
251            .find(|(recorded, _)| *recorded == id)
252            .map(|(_, slot)| *slot)
253    }
254
255    /// Register `slot` as `id`'s value. Call *after* emitting `id`, so the
256    /// emission that fills the slot isn't itself short-circuited.
257    pub fn record_single_evaluation(
258        &mut self,
259        id: ExprId,
260        slot: u32,
261    ) -> Result<(), CompilerFailure> {
262        self.local_type(slot)?;
263        self.single_evaluations.push((id, slot));
264        Ok(())
265    }
266
267    /// Registrations are statement-scoped: later emission of a shared expression
268    /// must compute its value again. Take a mark before emitting a statement
269    /// that registers, and [`Self::end_single_evaluations`] with it afterwards.
270    pub fn single_evaluation_mark(&self) -> usize {
271        self.single_evaluations.len()
272    }
273
274    /// Drop every registration taken since `mark`. See
275    /// [`Self::single_evaluation_mark`].
276    pub fn end_single_evaluations(&mut self, mark: usize) -> Result<(), CompilerFailure> {
277        if mark > self.single_evaluations.len() {
278            return Err(self.state_failure("invalid single evaluation mark"));
279        }
280        self.single_evaluations.truncate(mark);
281        Ok(())
282    }
283
284    pub fn add_anonymous_local(&mut self, ty: ValType) -> Result<u32, CompilerFailure> {
285        let index = self.next_local_index;
286        let next = index
287            .checked_add(1)
288            .ok_or_else(|| self.state_failure("local index overflow"))?;
289        if let Some(limit) = super::FunctionLimit::exceeded(0, next) {
290            return Err(limit.failure(self.last_source_span()));
291        }
292        // Each local is its own entry: a count of one (one byte) and its type.
293        let mut entry = vec![1u8];
294        ty.encode(&mut entry);
295        self.local_entry_bytes = self.local_entry_bytes.saturating_add(entry.len());
296        self.locals.push((1, ty));
297        self.next_local_index = next;
298        Ok(index)
299    }
300
301    pub fn require_local_slot(&self, name: &str) -> Result<u32, CompilerFailure> {
302        let index = self
303            .local_slot(name)
304            .ok_or_else(|| self.state_failure(format!("local `{name}` is not defined")))?;
305        self.local_type(index)?;
306        Ok(index)
307    }
308
309    /// The slot a narrowed read of `name` resolves to, and what it holds: the
310    /// innermost shadow when one is live, otherwise the binding's storage,
311    /// which the caller then casts down to the narrowed type. Only
312    /// `LocalNarrowRef` wants this — an ordinary read wants
313    /// [`Self::local_slot`], whose value is never stale.
314    pub fn narrowed_read_slot(&self, name: &str) -> Option<(u32, ValType)> {
315        self.scopes
316            .iter()
317            .rev()
318            .find_map(|scope| scope.lookup(name))
319    }
320
321    /// The binding's storage — what every read at the declared type sees.
322    pub fn local_slot(&self, name: &str) -> Option<u32> {
323        self.scopes
324            .iter()
325            .rev()
326            .find_map(|scope| scope.lookup_declared(name))
327            .map(|(index, _)| index)
328    }
329
330    /// The storage slot to write `name` through, dropping every shadow of it on
331    /// the way. Both halves are the point: writing through a shadow instead
332    /// loses the write (the storage keeps its old value) and rejects any value
333    /// wider than the narrowing — `x = null` into a `(ref $string)` slot is
334    /// invalid Wasm — and leaving a shadow behind hands the next narrowed read
335    /// the value from before the write.
336    pub fn write_slot(&mut self, name: &str) -> Result<u32, CompilerFailure> {
337        let index = self.require_local_slot(name)?;
338        self.clear_narrow_shadows(name);
339        Ok(index)
340    }
341
342    pub fn set_return_target(&mut self, target: ReturnTarget) {
343        self.return_target = target;
344    }
345
346    pub fn return_target(&self) -> &ReturnTarget {
347        &self.return_target
348    }
349
350    pub fn set_this_local(&mut self, index: u32) -> Result<(), CompilerFailure> {
351        self.local_type(index)?;
352        self.this_local = Some(index);
353        Ok(())
354    }
355
356    pub fn this_local(&self) -> Option<u32> {
357        self.this_local
358    }
359
360    pub fn set_ctor_class(&mut self, mangled: crate::MangledName) {
361        self.ctor_class = Some(mangled);
362    }
363
364    pub fn ctor_class(&self) -> Option<&crate::MangledName> {
365        self.ctor_class.as_ref()
366    }
367
368    /// Move `name`'s storage to `index` — for a param whose real home is the box
369    /// or typed local the prologue builds. This is a re-declaration, not a
370    /// narrowing: writes follow it.
371    pub fn rebind_in_innermost_scope(
372        &mut self,
373        name: &str,
374        index: u32,
375        ty: ValType,
376    ) -> Result<(), CompilerFailure> {
377        self.check_local_type(index, ty)?;
378        let scope = self
379            .scopes
380            .last_mut()
381            .expect("emitter retains its root scope");
382        scope.define(name.to_string(), index, ty);
383        Ok(())
384    }
385
386    /// The present flag of the local in `slot`, when it holds a maybe-missing
387    /// read typed with a type parameter.
388    pub(super) fn present_flag(&self, slot: u32) -> Option<u32> {
389        self.maybe_missing_slots
390            .get(&slot)
391            .and_then(|local| local.present_flag)
392    }
393
394    /// Record the shadow an assignment's narrowing just took. Narrowed reads
395    /// resolve here until the next write clears it; writes never do.
396    pub fn install_narrow_shadow(
397        &mut self,
398        name: &str,
399        index: u32,
400        ty: ValType,
401    ) -> Result<(), CompilerFailure> {
402        self.check_local_type(index, ty)?;
403        let scope = self
404            .scopes
405            .last_mut()
406            .expect("emitter retains its root scope");
407        scope.set_shadow(name.to_string(), index, ty);
408        Ok(())
409    }
410
411    /// Register the deferred live-read source for a field/index narrowing.
412    /// Every narrowed use reloads this source and checks the current value.
413    pub fn register_narrow_source(
414        &mut self,
415        name: &str,
416        source: crate::ExprId,
417    ) -> Result<(), CompilerFailure> {
418        self.ctx
419            .ta
420            .try_expr(source)
421            .map_err(crate::codegen::arena_failure)?;
422        let scope = self
423            .scopes
424            .last_mut()
425            .expect("emitter retains its root scope");
426        scope.narrow_sources.insert(name.to_string(), source);
427        Ok(())
428    }
429
430    pub fn narrow_source(&self, name: &str) -> Option<crate::ExprId> {
431        self.scopes
432            .iter()
433            .rev()
434            .find_map(|scope| scope.narrow_sources.get(name).copied())
435    }
436
437    /// Drop every shadow of `name` visible from here — a write to the storage
438    /// slot makes all of them stale. Stops at the scope that declares the name;
439    /// an outer binding of the same name is a different one.
440    pub fn clear_narrow_shadows(&mut self, name: &str) {
441        for scope in self.scopes.iter_mut().rev() {
442            scope.clear_shadow(name);
443            if scope.lookup_declared(name).is_some() {
444                return;
445            }
446        }
447    }
448
449    /// Drop every narrowing shadow in scope. A shadow is sound only for reads
450    /// that follow its assignment both in the emitted code and at run time, and
451    /// a branching statement is where those two stop agreeing — see
452    /// `stmt::branches`, which is what calls this.
453    pub fn clear_all_narrow_shadows(&mut self) {
454        for scope in &mut self.scopes {
455            scope.clear_all_shadows();
456        }
457    }
458
459    pub fn push_scope(&mut self) {
460        self.scopes.push(Scope::new());
461    }
462
463    /// Wasm local indices remain reserved after pop — locals are function-scoped, not block-scoped.
464    pub fn pop_scope(&mut self) -> Result<(), CompilerFailure> {
465        if self.scopes.len() <= 1 {
466            return Err(self.state_failure("cannot pop the root emitter scope"));
467        }
468        self.scopes.pop();
469        Ok(())
470    }
471
472    /// `typed_slots[i]` is the Wasm slot holding the typed-form value of `params[i]` —
473    /// index `i` for top-level functions, a prologue-allocated local for closure bodies.
474    /// Boxing is body-internal; the Wasm signature is unchanged.
475    pub fn emit_boxed_param_prologue(
476        &mut self,
477        params: &[crate::TypedParam],
478        typed_slots: &[u32],
479    ) -> Result<(), crate::compiler_error::CompilerFailure> {
480        if params.len() != typed_slots.len() {
481            return Err(self.state_failure("typed slots must have one entry per parameter"));
482        }
483        // Resolve every registration before mutating bindings or emitting a prologue.
484        let mut boxes = Vec::new();
485        for (p, &typed_slot) in params.iter().zip(typed_slots) {
486            self.check_local_type(typed_slot, self.ctx.symbols.value_type(&p.ty)?)?;
487            if self.require_local_slot(&p.name.name)? != typed_slot {
488                return Err(self.state_failure("typed parameter slot does not match its binding"));
489            }
490            self.parameter_input_slots
491                .insert(p.name.name.clone(), (typed_slot, p.ty.clone()));
492            if !p.boxed {
493                continue;
494            }
495            let box_idx =
496                self.ctx.symbols.box_type_idx(&p.ty)?.ok_or_else(|| {
497                    self.state_failure("box type registered for every boxed param")
498                })?;
499            boxes.push((p, typed_slot, box_idx));
500        }
501        let count = self
502            .next_local_index
503            .checked_add(crate::codegen::wasm_u32(boxes.len())?)
504            .ok_or_else(|| self.state_failure("boxed parameter local count overflow"))?;
505        if let Some(limit) = super::FunctionLimit::exceeded(0, count) {
506            return Err(limit.failure(self.last_source_span()));
507        }
508        for (p, typed_slot, box_idx) in boxes {
509            let box_val = ValType::Ref(wasm_encoder::RefType {
510                nullable: false,
511                heap_type: wasm_encoder::HeapType::Concrete(box_idx),
512            });
513            let shadow_idx = self.add_anonymous_local(box_val)?;
514            let uninitialized = self.uninitialized_parameters.contains(&p.name);
515            if uninitialized {
516                self.uninitialized_cells.insert(shadow_idx);
517            }
518            self.instruction(Instruction::LocalGet(typed_slot));
519            self.instruction(Instruction::I32Const(i32::from(!uninitialized)));
520            self.instruction(Instruction::StructNew(box_idx));
521            self.instruction(Instruction::LocalSet(shadow_idx));
522            self.rebind_in_innermost_scope(&p.name.name, shadow_idx, box_val)?;
523        }
524        Ok(())
525    }
526
527    /// Prefer the structured helpers (emit_block/if/loop) to keep depth tracking consistent.
528    pub fn instruction(&mut self, inst: Instruction<'static>) {
529        self.instructions.push(inst);
530    }
531
532    pub fn instructions(&mut self, insts: impl IntoIterator<Item = Instruction<'static>>) {
533        self.instructions.extend(insts);
534    }
535
536    /// The encoded size the function body would have if it ended here: its
537    /// local declarations, its instructions and the closing `end`, as
538    /// [`Self::build`] encodes them. Instructions are only ever appended, so
539    /// each is encoded once, the first time it is measured.
540    pub fn body_size(&mut self) -> usize {
541        let locals_count = u32::try_from(self.locals.len()).unwrap_or(u32::MAX);
542        // The declarations and the one-byte `end`, around the instructions.
543        let declarations_and_end_bytes = super::leb128_u32_size(locals_count)
544            .saturating_add(self.local_entry_bytes)
545            .saturating_add(1);
546        let mut encoded = Vec::new();
547        for (index, instruction) in self
548            .instructions
549            .iter()
550            .enumerate()
551            .skip(self.sized_instructions)
552        {
553            instruction.encode(&mut encoded);
554            let body_bytes = self
555                .instruction_bytes
556                .saturating_add(encoded.len())
557                .saturating_add(declarations_and_end_bytes);
558            if self.body_limit_instruction.is_none() && !super::FunctionLimit::body_fits(body_bytes)
559            {
560                self.body_limit_instruction = Some(index);
561            }
562        }
563        self.sized_instructions = self.instructions.len();
564        self.instruction_bytes = self.instruction_bytes.saturating_add(encoded.len());
565        self.instruction_bytes
566            .saturating_add(declarations_and_end_bytes)
567    }
568
569    /// The source whose code took [`Self::body_size`] past the Wasm
570    /// engine's body-size limit.
571    pub fn body_limit_span(&self) -> Option<Span> {
572        self.body_limit_instruction
573            .and_then(|instruction| self.source_span_before(instruction))
574    }
575
576    /// The source being emitted when the function first needed more locals
577    /// than the Wasm engine accepts.
578    pub fn locals_limit_span(&self) -> Option<Span> {
579        self.locals_limit_span
580    }
581
582    /// Locals the function has so far, parameters included.
583    pub fn local_count(&self) -> u32 {
584        self.next_local_index
585    }
586
587    /// The source span most recently mapped to emitted code.
588    pub fn last_mapped_span(&self) -> Option<Span> {
589        self.source_mappings.last().map(|(_, span)| *span)
590    }
591
592    /// The most recently mapped span that locates source, skipping the
593    /// placeholders of desugared code.
594    pub fn last_source_span(&self) -> Option<Span> {
595        self.source_span_before(self.instructions.len())
596    }
597
598    /// The last span mapped at or before `instruction` that locates source.
599    fn source_span_before(&self, instruction: usize) -> Option<Span> {
600        let mapped = self
601            .source_mappings
602            .partition_point(|(index, _)| *index <= instruction);
603        self.source_mappings
604            .iter()
605            .take(mapped)
606            .rev()
607            .map(|(_, span)| *span)
608            .find(|span| !span.is_placeholder())
609    }
610
611    pub fn record_span(&mut self, span: Span) {
612        self.source_mappings.push((self.instructions.len(), span));
613    }
614
615    pub fn emit_block(&mut self, ty: BlockType) {
616        self.instructions.push(Instruction::Block(ty));
617        if let Some(depth) = self.ctx.latch(
618            self.wasm_block_depth
619                .checked_add(1)
620                .ok_or_else(|| self.state_failure("block depth overflow")),
621        ) {
622            self.wasm_block_depth = depth;
623        }
624    }
625
626    pub fn emit_if(&mut self, ty: BlockType) {
627        self.instructions.push(Instruction::If(ty));
628        if let Some(depth) = self.ctx.latch(
629            self.wasm_block_depth
630                .checked_add(1)
631                .ok_or_else(|| self.state_failure("block depth overflow")),
632        ) {
633            self.wasm_block_depth = depth;
634        }
635    }
636
637    pub fn emit_loop(&mut self, ty: BlockType) {
638        self.instructions.push(Instruction::Loop(ty));
639        if let Some(depth) = self.ctx.latch(
640            self.wasm_block_depth
641                .checked_add(1)
642                .ok_or_else(|| self.state_failure("block depth overflow")),
643        ) {
644            self.wasm_block_depth = depth;
645        }
646    }
647
648    /// `else` doesn't change block depth — it's a continuation of the surrounding `if`.
649    pub fn emit_else(&mut self) {
650        self.instructions.push(Instruction::Else);
651    }
652
653    pub fn emit_end(&mut self) {
654        let Some(depth) = self.ctx.latch(
655            self.wasm_block_depth
656                .checked_sub(1)
657                .ok_or_else(|| self.state_failure("end without an active block")),
658        ) else {
659            return;
660        };
661        self.wasm_block_depth = depth;
662        self.instructions.push(Instruction::End);
663    }
664
665    /// For instructions like `try_table` that open a block frame but are emitted via
666    /// `instruction()` (which skips depth tracking); the matching `emit_end()` decrements.
667    pub fn bump_block_depth(&mut self) {
668        if let Some(depth) = self.ctx.latch(
669            self.wasm_block_depth
670                .checked_add(1)
671                .ok_or_else(|| self.state_failure("block depth overflow")),
672        ) {
673            self.wasm_block_depth = depth;
674        }
675    }
676
677    /// The Wasm result this body's signature declares, or `None` when it has none.
678    pub fn wasm_result_type(
679        &self,
680        ctx: &CodegenCtx,
681    ) -> Result<Option<ValType>, crate::compiler_error::CompilerFailure> {
682        Ok(match &self.return_target {
683            ReturnTarget::Slot(slot) => Some(*slot),
684            ReturnTarget::Declared(ret) => Some(ctx.symbols.value_type(ret)?),
685            ReturnTarget::NoResult => None,
686        })
687    }
688
689    pub fn break_finally_floor(&self) -> usize {
690        self.loop_contexts
691            .last()
692            .map_or(0, |c| c.finally_count_at_push)
693    }
694
695    /// Like `break_finally_floor` but for `continue`; skips switch frames.
696    pub fn continue_finally_floor(&self) -> usize {
697        self.loop_contexts
698            .iter()
699            .rev()
700            .find(|c| !c.is_switch)
701            .map_or(0, |c| c.finally_count_at_push)
702    }
703
704    pub fn emit_while_open(&mut self) {
705        if self.wasm_block_depth > u32::MAX - 2 {
706            self.ctx
707                .record_failure(self.state_failure("loop block depth overflow"));
708            return;
709        }
710        self.emit_block(BlockType::Empty);
711        self.emit_loop(BlockType::Empty);
712        self.loop_contexts.push(LoopLabels {
713            break_depth: self.wasm_block_depth - 2,
714            continue_depth: self.wasm_block_depth - 1,
715            is_switch: false,
716            finally_count_at_push: self.finally_stack.len(),
717        });
718    }
719
720    /// Pushes an `is_switch: true` break frame so `continue_label()` skips to the enclosing loop.
721    pub fn emit_switch_open(&mut self) {
722        if self.wasm_block_depth == u32::MAX {
723            self.ctx
724                .record_failure(self.state_failure("switch block depth overflow"));
725            return;
726        }
727        self.emit_block(BlockType::Empty);
728        self.loop_contexts.push(LoopLabels {
729            break_depth: self.wasm_block_depth - 1,
730            // unread for switch frames; 0 so any accidental read gives a deterministically bad label
731            continue_depth: 0,
732            is_switch: true,
733            finally_count_at_push: self.finally_stack.len(),
734        });
735    }
736
737    pub fn emit_switch_close(&mut self) {
738        if !self.loop_contexts.last().is_some_and(|frame| {
739            frame.is_switch && frame.break_depth.checked_add(1) == Some(self.wasm_block_depth)
740        }) {
741            self.ctx
742                .record_failure(self.state_failure("mismatched switch close"));
743            return;
744        }
745        self.loop_contexts.pop();
746        self.emit_end();
747    }
748
749    pub fn emit_while_close(&mut self) {
750        if !self.loop_contexts.last().is_some_and(|frame| {
751            !frame.is_switch && frame.break_depth.checked_add(2) == Some(self.wasm_block_depth)
752        }) {
753            self.ctx
754                .record_failure(self.state_failure("mismatched loop close"));
755            return;
756        }
757        self.loop_contexts.pop();
758        self.emit_end(); // end loop
759        self.emit_end(); // end block
760    }
761
762    pub fn break_label(&self) -> Result<u32, CompilerFailure> {
763        let ctx = self
764            .loop_contexts
765            .last()
766            .ok_or_else(|| self.state_failure("break outside loop or switch"))?;
767        self.branch_depth(ctx.break_depth)
768    }
769
770    /// Walks past switch frames — `continue` inside a switch targets the enclosing loop.
771    pub fn continue_label(&self) -> Result<u32, CompilerFailure> {
772        let ctx = self
773            .loop_contexts
774            .iter()
775            .rev()
776            .find(|c| !c.is_switch)
777            .ok_or_else(|| self.state_failure("continue outside loop"))?;
778        self.branch_depth(ctx.continue_depth)
779    }
780
781    pub fn return_label(&self) -> Result<u32, CompilerFailure> {
782        self.branch_depth(self.return_block_depth)
783    }
784
785    pub fn branch_depth(&self, target_depth: u32) -> Result<u32, CompilerFailure> {
786        self.wasm_block_depth
787            .checked_sub(target_depth)
788            .and_then(|depth| depth.checked_sub(1))
789            .ok_or_else(|| self.state_failure("branch target is outside the active blocks"))
790    }
791
792    /// Segment offset is always 0 — one data segment per literal.
793    pub fn emit_const_string(
794        &mut self,
795        string_type_idx: u32,
796        raw_string_type_idx: u32,
797        string_vtable_global_idx: u32,
798        consts_data_idx: u32,
799        code_units: u32,
800    ) {
801        self.instruction(Instruction::GlobalGet(string_vtable_global_idx));
802        self.instruction(Instruction::I32Const(0));
803        // Wasm takes the unsigned array length as an i32 bit pattern.
804        self.instruction(Instruction::I32Const(code_units as i32));
805        self.instruction(Instruction::ArrayNewData {
806            array_type_index: raw_string_type_idx,
807            array_data_index: consts_data_idx,
808        });
809        self.instruction(Instruction::I64Const(0));
810        self.instruction(Instruction::StructNew(string_type_idx));
811    }
812
813    /// Consumes the emitter, returning bytes only after state and size checks pass.
814    /// Any failure discards the owned instructions, scopes and completion frames.
815    pub fn build(self) -> Result<Function, CompilerFailure> {
816        Ok(self.build_with_lines()?.0)
817    }
818
819    /// Like `build`, but also returns DWARF byte offsets. Offsets are measured
820    /// before each instruction is pushed, so they never decrease.
821    pub fn build_with_lines(self) -> Result<(Function, Vec<(u64, Span)>), CompilerFailure> {
822        self.ctx.check_failure()?;
823        self.validate_state()?;
824        let mut func = Function::new(self.locals.iter().copied());
825        let mut byte_offsets: Vec<(u64, Span)> = Vec::with_capacity(self.source_mappings.len());
826        let mut next_mapping = 0usize;
827        let mut body_limit_instruction = None;
828
829        for (i, inst) in self.instructions.iter().enumerate() {
830            while next_mapping < self.source_mappings.len()
831                && self.source_mappings[next_mapping].0 == i
832            {
833                let offset = func.byte_len() as u64;
834                byte_offsets.push((offset, self.source_mappings[next_mapping].1));
835                next_mapping += 1;
836            }
837            func.instruction(inst);
838            // The closing `end` is one byte.
839            if body_limit_instruction.is_none()
840                && !super::FunctionLimit::body_fits(func.byte_len().saturating_add(1))
841            {
842                body_limit_instruction = Some(i);
843            }
844        }
845        // Drain spans recorded past the last instruction; their offset is the function's end byte.
846        let end_offset = func.byte_len() as u64;
847        while next_mapping < self.source_mappings.len() {
848            byte_offsets.push((end_offset, self.source_mappings[next_mapping].1));
849            next_mapping += 1;
850        }
851        func.instruction(&Instruction::End);
852        self.ctx.check_finished_function(super::FinishedFunction {
853            body_bytes: func.byte_len(),
854            body_limit_span: body_limit_instruction
855                .and_then(|instruction| self.source_span_before(instruction)),
856            locals: self.next_local_index,
857            locals_limit_span: self.locals_limit_span,
858        });
859        self.ctx.check_failure()?;
860        Ok((func, byte_offsets))
861    }
862    /// Validate recorded state before handing any bytes to a module builder.
863    fn validate_state(&self) -> Result<(), CompilerFailure> {
864        if self.scopes.len() != 1
865            || self.wasm_block_depth != 0
866            || !self.loop_contexts.is_empty()
867            || !self.finally_stack.is_empty()
868            || !self.single_evaluations.is_empty()
869        {
870            return Err(self.state_failure("unfinished emitter scope or control-flow state"));
871        }
872        let count = self
873            .parameter_types
874            .len()
875            .checked_add(self.locals.len())
876            .ok_or_else(|| self.state_failure("local count overflow"))?;
877        if crate::codegen::wasm_u32(count)? != self.next_local_index
878            || self.locals.iter().any(|(count, _)| *count != 1)
879        {
880            return Err(
881                self.state_failure("local declaration count does not match allocated slots")
882            );
883        }
884        for scope in &self.scopes {
885            for binding in scope.bindings.values() {
886                for (index, ty) in binding.declared.iter().chain(binding.shadow.iter()) {
887                    self.check_local_type(*index, *ty)?;
888                }
889            }
890        }
891        if let Some(index) = self.this_local {
892            self.local_type(index)?;
893        }
894        if let Some(index) = self.call_receiver {
895            self.local_type(index)?;
896        }
897        for (index, _) in self.runtime_type_params.values() {
898            self.local_type(*index)?;
899        }
900        self.validate_instructions()
901    }
902
903    fn validate_instructions(&self) -> Result<(), CompilerFailure> {
904        let mut blocks = Vec::new();
905        for (position, instruction) in self.instructions.iter().enumerate() {
906            let failure = |message| {
907                let error = crate::codegen::internal_failure(message);
908                self.source_span_before(position)
909                    .map_or(error.clone(), |span| error.with_span(span))
910            };
911            match instruction {
912                Instruction::LocalGet(index)
913                | Instruction::LocalSet(index)
914                | Instruction::LocalTee(index) => {
915                    self.local_type(*index)
916                        .map_err(|_| failure("instruction uses an unallocated local"))?;
917                }
918                Instruction::Block(_) | Instruction::Loop(_) => blocks.push(false),
919                Instruction::TryTable(_, catches) => {
920                    for catch in catches.iter() {
921                        let (wasm_encoder::Catch::One { label, .. }
922                        | wasm_encoder::Catch::OneRef { label, .. }
923                        | wasm_encoder::Catch::All { label }
924                        | wasm_encoder::Catch::AllRef { label }) = catch;
925                        if u64::from(*label) > blocks.len() as u64 {
926                            return Err(failure("catch branch depth exceeds active blocks"));
927                        }
928                    }
929                    blocks.push(false);
930                }
931                Instruction::If(_) => blocks.push(true),
932                Instruction::Else => {
933                    let block = blocks
934                        .last_mut()
935                        .ok_or_else(|| failure("else without an if block"))?;
936                    if !*block {
937                        return Err(failure("else without an unmatched if block"));
938                    }
939                    *block = false;
940                }
941                Instruction::End => {
942                    blocks
943                        .pop()
944                        .ok_or_else(|| failure("end without an active block"))?;
945                }
946                Instruction::Br(depth)
947                | Instruction::BrIf(depth)
948                | Instruction::BrOnNull(depth)
949                | Instruction::BrOnNonNull(depth)
950                | Instruction::BrOnCast {
951                    relative_depth: depth,
952                    ..
953                }
954                | Instruction::BrOnCastFail {
955                    relative_depth: depth,
956                    ..
957                }
958                | Instruction::BrOnCastDescEq {
959                    relative_depth: depth,
960                    ..
961                }
962                | Instruction::BrOnCastDescEqFail {
963                    relative_depth: depth,
964                    ..
965                } => {
966                    // The function's implicit block is also a valid branch target.
967                    if u64::from(*depth) > blocks.len() as u64 {
968                        return Err(failure("branch depth exceeds active blocks"));
969                    }
970                }
971                Instruction::BrTable(depths, default)
972                    if depths
973                        .iter()
974                        .chain(std::iter::once(default))
975                        .any(|depth| u64::from(*depth) > blocks.len() as u64) =>
976                {
977                    return Err(failure("branch table depth exceeds active blocks"));
978                }
979                _ => {}
980            }
981        }
982        if !blocks.is_empty() {
983            return Err(self.state_failure("unclosed Wasm blocks"));
984        }
985        Ok(())
986    }
987
988    fn state_failure(&self, message: impl Into<String>) -> CompilerFailure {
989        let failure = crate::codegen::internal_failure(message);
990        self.last_source_span()
991            .map_or(failure.clone(), |span| failure.with_span(span))
992    }
993
994    fn local_type(&self, index: u32) -> Result<ValType, CompilerFailure> {
995        let index = usize::try_from(index)
996            .map_err(|_| self.state_failure("local index does not fit usize"))?;
997        if let Some(ty) = self.parameter_types.get(index) {
998            return Ok(*ty);
999        }
1000        index
1001            .checked_sub(self.parameter_types.len())
1002            .and_then(|offset| self.locals.get(offset))
1003            .map(|(_, ty)| *ty)
1004            .ok_or_else(|| self.state_failure("local index is not allocated"))
1005    }
1006
1007    fn check_local_type(&self, index: u32, ty: ValType) -> Result<(), CompilerFailure> {
1008        if self.local_type(index)? != ty {
1009            return Err(self.state_failure("local slot type does not match its metadata"));
1010        }
1011        Ok(())
1012    }
1013}
1014
1015/// Push a constant `(ref $string)` for `text`, which must already be interned in
1016/// the [`StringPool`](crate::codegen::string_pool::StringPool). The ctx-aware
1017/// companion to [`FunctionEmitter::emit_const_string`]: it resolves the pool
1018/// slot and `$string`/`$rawString`/vtable indices, then emits the literal.
1019pub(crate) fn emit_const_string_by_text(
1020    emitter: &mut FunctionEmitter,
1021    ctx: &CodegenCtx,
1022    text: &str,
1023) -> Result<(), crate::compiler_error::CompilerFailure> {
1024    let pool_idx = ctx.strings.lookup_text(text).ok_or_else(|| {
1025        crate::codegen::internal_failure(format!("`{text}` was not interned by the string pool"))
1026    })?;
1027    emit_pooled_string(emitter, ctx, pool_idx)
1028}
1029
1030/// Push the constant `(ref $string)` backed by string-pool entry `pool_idx`.
1031pub(crate) fn emit_pooled_string(
1032    emitter: &mut FunctionEmitter,
1033    ctx: &CodegenCtx,
1034    pool_idx: usize,
1035) -> Result<(), crate::compiler_error::CompilerFailure> {
1036    use crate::codegen::internal_failure;
1037    let code_units = ctx.strings.code_units(pool_idx)?;
1038    let string_type_idx = ctx
1039        .symbols
1040        .string_type_idx()
1041        .ok_or_else(|| internal_failure("the $string intrinsic is not registered"))?;
1042    let raw_string_type_idx = ctx
1043        .symbols
1044        .raw_string_type_idx()
1045        .ok_or_else(|| internal_failure("the $rawString intrinsic is not registered"))?;
1046    let vtable_global_idx = ctx
1047        .symbols
1048        .prelude_global_idx("string_vtable")
1049        .ok_or_else(|| internal_failure("string_vtable is not imported from the prelude"))?;
1050    emitter.emit_const_string(
1051        string_type_idx,
1052        raw_string_type_idx,
1053        vtable_global_idx,
1054        crate::codegen::wasm_u32(pool_idx)?,
1055        code_units,
1056    );
1057    Ok(())
1058}
1059
1060/// Push a fresh `(ref $string)` for a short codegen-owned literal (`"null"`,
1061/// `"[object Object]"`, …) — not pool-interned, built via `array.new_fixed`.
1062pub(crate) fn emit_inline_string_literal(
1063    emitter: &mut FunctionEmitter,
1064    ctx: &CodegenCtx,
1065    text: &str,
1066) -> Result<(), crate::compiler_error::CompilerFailure> {
1067    use crate::codegen::internal_failure;
1068    let string_type_idx = ctx
1069        .symbols
1070        .string_type_idx()
1071        .ok_or_else(|| internal_failure("the $string intrinsic is not registered"))?;
1072    let raw_string_type_idx = ctx
1073        .symbols
1074        .raw_string_type_idx()
1075        .ok_or_else(|| internal_failure("the $rawString intrinsic is not registered"))?;
1076    let vtable_global_idx = ctx
1077        .symbols
1078        .prelude_global_idx("string_vtable")
1079        .ok_or_else(|| internal_failure("string_vtable is not imported from the prelude"))?;
1080    emitter.instruction(Instruction::GlobalGet(vtable_global_idx));
1081    let mut len = 0usize;
1082    for unit in text.encode_utf16() {
1083        emitter.instruction(Instruction::I32Const(i32::from(unit)));
1084        len = len.saturating_add(1);
1085    }
1086    emitter.instruction(Instruction::ArrayNewFixed {
1087        array_type_index: raw_string_type_idx,
1088        array_size: crate::codegen::wasm_u32(len)?,
1089    });
1090    emitter.instruction(Instruction::I64Const(0));
1091    emitter.instruction(Instruction::StructNew(string_type_idx));
1092    Ok(())
1093}
1094
1095pub fn emit_function(
1096    generics: &[String],
1097    ctx: &CodegenCtx<'_>,
1098    params: &[crate::TypedParam],
1099    body: crate::StmtId,
1100    return_type: &crate::Type,
1101) -> Result<(Function, Vec<(u64, Span)>), crate::compiler_error::CompilerFailure> {
1102    let mut wasm_params: Vec<(Ident, ValType)> = params
1103        .iter()
1104        .map(|p| Ok((p.name.clone(), ctx.symbols.value_type(&p.ty)?)))
1105        .collect::<Result<_, crate::compiler_error::CompilerFailure>>()?;
1106    if !generics.is_empty() {
1107        wasm_params.push((
1108            Ident {
1109                name: "$types".into(),
1110                span: Span::at(ctx.file),
1111            },
1112            crate::codegen::runtime_descriptors::environment_type(ctx.symbols)?,
1113        ));
1114    }
1115    let mut emitter = FunctionEmitter::new(ctx, &wasm_params)?;
1116    crate::codegen::runtime_descriptors::bind(
1117        &mut emitter,
1118        generics,
1119        crate::codegen::wasm_u32(params.len())?,
1120    )?;
1121
1122    emitter.set_return_target(ReturnTarget::Declared(return_type.clone()));
1123    let typed_slots: Vec<u32> = (0..crate::codegen::wasm_u32(params.len())?).collect();
1124    emitter.prepare_parameter_initialization(Some(body))?;
1125    emitter.emit_boxed_param_prologue(params, &typed_slots)?;
1126    stmt::emit_statement(&mut emitter, ctx, body)?;
1127    emit_body_end(&mut emitter, ctx, return_type)?;
1128    emitter.build_with_lines()
1129}
1130
1131/// ABI: `(func (param (ref any) N×(ref $Object)) (result (ref $Object)?))` —
1132/// env at slot 0, every user param erased to `(ref $Object)`, result erased.
1133pub fn emit_closure_function(
1134    ctx: &CodegenCtx<'_>,
1135    meta: &crate::codegen::closures::ClosureMeta,
1136) -> Result<(Function, Vec<(u64, Span)>), crate::compiler_error::CompilerFailure> {
1137    let signature = crate::codegen::closures::classify(&meta.signature)?;
1138    if usize::from(signature.arity) != meta.params.len() {
1139        return Err(crate::codegen::internal_failure(
1140            "closure body disagrees with its registered signature",
1141        ));
1142    }
1143    let intrinsics = ctx
1144        .symbols
1145        .intrinsic_type_indices()
1146        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared by codegen entry"))?;
1147    let object_ref = ValType::Ref(wasm_encoder::RefType {
1148        nullable: true,
1149        heap_type: wasm_encoder::HeapType::Concrete(intrinsics.object),
1150    });
1151    let any_ref = ValType::Ref(wasm_encoder::RefType {
1152        nullable: false,
1153        heap_type: wasm_encoder::HeapType::ANY,
1154    });
1155
1156    let env_name = Ident {
1157        name: "$__env__".to_string(),
1158        span: Span::at(ctx.file),
1159    };
1160    let mut wasm_params: Vec<(Ident, ValType)> = vec![(env_name, any_ref)];
1161    for p in &meta.params {
1162        wasm_params.push((p.name.clone(), object_ref));
1163    }
1164    let mut emitter = FunctionEmitter::new(ctx, &wasm_params)?;
1165
1166    let env_type_idx = ctx.symbols.env_type_idx(meta.expr_id).ok_or_else(|| {
1167        crate::codegen::internal_failure("env type registered for every closure expression")
1168    })?;
1169    let env_typed_val = ValType::Ref(wasm_encoder::RefType {
1170        nullable: false,
1171        heap_type: wasm_encoder::HeapType::Concrete(env_type_idx),
1172    });
1173    let env_typed_local = emitter.add_anonymous_local(env_typed_val)?;
1174    if meta.this_type.is_some() {
1175        crate::codegen::this_binding::load_receiver(&mut emitter, ctx)?;
1176    }
1177    emitter.instructions.push(Instruction::LocalGet(0));
1178    if meta.this_type.is_some() {
1179        emitter.instruction(Instruction::RefCastNonNull(
1180            wasm_encoder::HeapType::Concrete(
1181                ctx.symbols
1182                    .this_environment_type
1183                    .ok_or_else(|| crate::codegen::internal_failure("this environment"))?,
1184            ),
1185        ));
1186        emitter.instruction(Instruction::StructGet {
1187            struct_type_index: ctx
1188                .symbols
1189                .this_environment_type
1190                .ok_or_else(|| crate::codegen::internal_failure("this environment"))?,
1191            field_index: 0,
1192        });
1193    }
1194    if crate::codegen::call_arguments::typed_metadata(&meta.params).is_some() {
1195        crate::codegen::call_arguments::unwrap(&mut emitter, ctx)?;
1196    }
1197    emitter.instructions.push(Instruction::RefCastNonNull(
1198        wasm_encoder::HeapType::Concrete(env_type_idx),
1199    ));
1200    emitter
1201        .instructions
1202        .push(Instruction::LocalSet(env_typed_local));
1203
1204    if let Some(name) = &meta.self_name {
1205        let slot = emitter.define_local(name, ctx.symbols.value_type(&meta.signature)?)?;
1206        emitter.instruction(Instruction::LocalGet(env_typed_local));
1207        emitter.instruction(Instruction::StructGet {
1208            struct_type_index: env_type_idx,
1209            field_index: crate::codegen::wasm_u32(
1210                meta.captured
1211                    .len()
1212                    .checked_add(usize::from(!meta.runtime_generics.is_empty()))
1213                    .ok_or_else(|| {
1214                        crate::codegen::internal_failure("capture field index overflow")
1215                    })?,
1216            )?,
1217        });
1218        cast::emit_cast_to(&mut emitter, ctx, &meta.signature)?;
1219        emitter.instruction(Instruction::LocalSet(slot));
1220    }
1221    if !meta.runtime_generics.is_empty() {
1222        let types = emitter.add_anonymous_local(
1223            crate::codegen::runtime_descriptors::environment_type(ctx.symbols)?,
1224        )?;
1225        emitter.instruction(Instruction::LocalGet(env_typed_local));
1226        emitter.instruction(Instruction::StructGet {
1227            struct_type_index: env_type_idx,
1228            field_index: crate::codegen::wasm_u32(meta.captured.len())?,
1229        });
1230        emitter.instruction(Instruction::LocalSet(types));
1231        crate::codegen::runtime_descriptors::bind(&mut emitter, &meta.runtime_generics, types)?;
1232    }
1233    let mut typed_slots: Vec<u32> = Vec::with_capacity(meta.params.len());
1234    for (i, p) in meta.params.iter().enumerate() {
1235        let wasm_slot = crate::codegen::wasm_u32(i)?
1236            .checked_add(1)
1237            .ok_or_else(|| crate::codegen::internal_failure("parameter index overflow"))?;
1238        let typed_ty = ctx.symbols.value_type(&p.ty)?;
1239        let typed_local = emitter.add_anonymous_local(typed_ty)?;
1240        emitter.instructions.push(Instruction::LocalGet(wasm_slot));
1241        crate::codegen::cast_check::emit_checked_parameter_cast_on_stack(
1242            &mut emitter,
1243            ctx,
1244            &crate::Type::Unknown,
1245            &p.ty,
1246        )?;
1247        emitter
1248            .instructions
1249            .push(Instruction::LocalSet(typed_local));
1250        emitter.rebind_in_innermost_scope(&p.name.name, typed_local, typed_ty)?;
1251        typed_slots.push(typed_local);
1252    }
1253
1254    // Boxed captures share the outer scope's box cell; unboxed captures copy the value directly.
1255    for (i, c) in meta.captured.iter().enumerate() {
1256        let local_ty = if c.boxed {
1257            let box_idx = ctx.symbols.box_type_idx(&c.ty)?.ok_or_else(|| {
1258                crate::codegen::internal_failure("box type registered for every boxed capture")
1259            })?;
1260            ValType::Ref(wasm_encoder::RefType {
1261                nullable: false,
1262                heap_type: wasm_encoder::HeapType::Concrete(box_idx),
1263            })
1264        } else {
1265            ctx.symbols.value_type(&c.ty)?
1266        };
1267        let captured_local = emitter.define_local(&c.name, local_ty)?;
1268        // A closure a default creates can run before a later parameter is set.
1269        if c.boxed && !ctx.ta.parameter_default_prologues.is_empty() {
1270            emitter.uninitialized_cells.insert(captured_local);
1271        }
1272        emitter
1273            .instructions
1274            .push(Instruction::LocalGet(env_typed_local));
1275        emitter.instructions.push(Instruction::StructGet {
1276            struct_type_index: env_type_idx,
1277            field_index: crate::codegen::wasm_u32(i)?,
1278        });
1279        // The env field may hold the erased `(ref null $Object)` form: env struct
1280        // types are emitted before class type indices are recorded, so a
1281        // class-typed capture's field widens while the local resolves concrete.
1282        if !c.boxed && matches!(c.ty.lowered(), crate::Type::ClassRef { .. }) {
1283            cast::emit_cast_to(&mut emitter, ctx, &c.ty)?;
1284        } else if !c.boxed
1285            && let ValType::Ref(local_ref) = local_ty
1286            && !local_ref.nullable
1287        {
1288            // Same widening, one level up: a union of class types lowers to a
1289            // *nullable* `$Object` while the class indices are still unknown,
1290            // but to a non-null one once they are. `ref.as_non_null` closes the
1291            // repr gap and is a no-op when the field was already non-null.
1292            emitter.instructions.push(Instruction::RefAsNonNull);
1293        }
1294        emitter
1295            .instructions
1296            .push(Instruction::LocalSet(captured_local));
1297        // A closure that mentions `this` captures the enclosing member's
1298        // receiver; point the emitter's receiver slot at the materialized
1299        // local so `TypedExprKind::This` reads it like any method body.
1300        if c.name.name == crate::typechecker::capture::THIS_BINDING {
1301            emitter.set_this_local(captured_local)?;
1302            emitter.dynamic_this = c.ty == crate::Type::Unknown;
1303        }
1304    }
1305
1306    emitter.prepare_parameter_initialization(match meta.body {
1307        crate::ClosureBody::Block(body) => Some(body),
1308        crate::ClosureBody::Expr(_) => None,
1309    })?;
1310    emitter.emit_boxed_param_prologue(&meta.params, &typed_slots)?;
1311
1312    emitter.set_return_target(closure_return_target(ctx, &meta.return_type)?);
1313
1314    match meta.body {
1315        crate::ClosureBody::Expr(e) => {
1316            expr::emit_expr(&mut emitter, ctx, e)?;
1317            cast::emit_coerce_to_return_slot(
1318                &mut emitter,
1319                ctx,
1320                &ctx.ta
1321                    .try_expr(e)
1322                    .map_err(crate::codegen::arena_failure)?
1323                    .ty,
1324            )?;
1325        }
1326        crate::ClosureBody::Block(b) => {
1327            stmt::emit_statement(&mut emitter, ctx, b)?;
1328            emit_body_end(&mut emitter, ctx, &meta.return_type)?;
1329        }
1330    }
1331
1332    emitter.build_with_lines()
1333}
1334
1335/// A closure's result is fixed by its funcref signature: the erased
1336/// `(ref null $Object)`, or no slot at all when it returns void.
1337///
1338/// Must answer `void` the same way `closures::classify` does when it picks that
1339/// signature, or the target names a result slot the signature lacks.
1340fn closure_return_target(
1341    ctx: &CodegenCtx<'_>,
1342    ret: &Type,
1343) -> Result<ReturnTarget, CompilerFailure> {
1344    let _ = ret;
1345    let intrinsics = ctx
1346        .symbols
1347        .intrinsic_type_indices()
1348        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?;
1349    Ok(ReturnTarget::Slot(ValType::Ref(wasm_encoder::RefType {
1350        nullable: true,
1351        heap_type: wasm_encoder::HeapType::Concrete(intrinsics.object),
1352    })))
1353}
1354
1355/// Ends a block body that may produce a value. A body falling off its end
1356/// completes with `undefined` when its return type permits it. For every other
1357/// type the missing-return rule rejects such a body unless the fall-through
1358/// runs through an exhaustive `switch`. A narrowing that a call left stale can
1359/// still bring a value no case matches, so a module with such a switch throws
1360/// a `TypeError` there. Elsewhere the end is unreachable; the trap keeps the
1361/// function statically total for Wasm validation, which cannot prove that.
1362pub(crate) fn emit_body_end(
1363    emitter: &mut FunctionEmitter<'_>,
1364    ctx: &CodegenCtx<'_>,
1365    return_type: &Type,
1366) -> Result<(), crate::compiler_error::CompilerFailure> {
1367    let accepts_undefined = return_type.is_void()
1368        || matches!(return_type.lowered(), Type::Unknown | Type::Undefined)
1369        || matches!(return_type.lowered(), Type::Union(members) if members.iter().any(|member| matches!(member.lowered(), Type::Undefined | Type::Void)));
1370    if !accepts_undefined {
1371        use crate::codegen::throw;
1372        if throw::body_end_may_be_reached(ctx.ta) {
1373            throw::emit_type_error_throw(emitter, ctx, throw::MISSING_RETURN_VALUE_MESSAGE);
1374        }
1375        emitter.instruction(Instruction::Unreachable);
1376        return Ok(());
1377    }
1378    expr::emit_undefined(emitter, ctx)?;
1379    cast::emit_coerce_to_return_slot(emitter, ctx, &Type::Undefined)?;
1380    emitter.instruction(Instruction::Return);
1381    Ok(())
1382}
1383
1384#[cfg(test)]
1385mod tests {
1386    use super::{ExprContext, FunctionEmitter};
1387    use crate::codegen::{
1388        CodegenCtx, SymbolTable, bigint_pool::BigIntPool, string_pool::StringPool,
1389    };
1390    use crate::{Ident, LineIndex, Span, TypedAst};
1391    use wasm_encoder::{
1392        BlockType, ExportKind, ExportSection, Function, FunctionSection, Instruction, Module,
1393        TypeSection, ValType,
1394    };
1395
1396    struct Fixture {
1397        ta: TypedAst,
1398        strings: StringPool,
1399        bigints: BigIntPool,
1400        symbols: SymbolTable,
1401        line_index: LineIndex,
1402        validator_bodies: crate::codegen::recursive_validators::ValidatorBodies,
1403        type_info: crate::TypeInfoTable,
1404    }
1405
1406    fn fixture() -> Fixture {
1407        Fixture {
1408            ta: TypedAst::new(),
1409            strings: StringPool::default(),
1410            bigints: BigIntPool::default(),
1411            symbols: SymbolTable::default(),
1412            line_index: LineIndex::new("").unwrap(),
1413            validator_bodies: crate::codegen::recursive_validators::ValidatorBodies::collect(
1414                &TypedAst::new(),
1415                &[],
1416            ),
1417            type_info: crate::TypeInfoTable::default(),
1418        }
1419    }
1420
1421    fn cx_of<'a>(f: &'a Fixture) -> CodegenCtx<'a> {
1422        CodegenCtx {
1423            ta: &f.ta,
1424            strings: &f.strings,
1425            bigints: &f.bigints,
1426            symbols: &f.symbols,
1427            source: "",
1428            line_index: &f.line_index,
1429            file: crate::FileId(0),
1430            validator_bodies: &f.validator_bodies,
1431            type_info: &f.type_info,
1432            package_string_global_idx: None,
1433            failure: std::cell::Cell::new(None),
1434            validator_steps_left: std::cell::Cell::new(
1435                crate::compiler_limits::MAX_INLINE_VALIDATOR_STEPS,
1436            ),
1437            validator_root: std::cell::Cell::new(None),
1438            check_is_standalone: std::cell::Cell::new(false),
1439        }
1440    }
1441
1442    fn ident(name: &str) -> Ident {
1443        Ident {
1444            name: name.to_string(),
1445            span: Span::at(crate::FileId(0)),
1446        }
1447    }
1448
1449    fn ident_param(name: &str, ty: ValType) -> (Ident, ValType) {
1450        (ident(name), ty)
1451    }
1452
1453    fn validate_in_module(f: wasm_encoder::Function, params: Vec<ValType>, results: Vec<ValType>) {
1454        let mut module = Module::new();
1455        let mut types = TypeSection::new();
1456        types.ty().function(params, results);
1457        module.section(&types);
1458        let mut functions = FunctionSection::new();
1459        functions.function(0);
1460        module.section(&functions);
1461        let mut exports = ExportSection::new();
1462        exports.export("f", ExportKind::Func, 0);
1463        module.section(&exports);
1464        let mut code = wasm_encoder::CodeSection::new();
1465        code.function(&f);
1466        module.section(&code);
1467        wasmparser::Validator::new()
1468            .validate_all(&module.finish())
1469            .expect("module validates");
1470    }
1471
1472    #[test]
1473    fn body_size_is_the_encoded_body_size() {
1474        let f = fixture();
1475        let cx = cx_of(&f);
1476        let mut emitter = FunctionEmitter::new(&cx, &[ident_param("p", ValType::I32)]).unwrap();
1477        assert_eq!(emitter.body_size(), 2, "no locals, then `end`");
1478        let any = ValType::Ref(wasm_encoder::RefType {
1479            nullable: true,
1480            heap_type: wasm_encoder::HeapType::ANY,
1481        });
1482        let concrete = ValType::Ref(wasm_encoder::RefType {
1483            nullable: false,
1484            heap_type: wasm_encoder::HeapType::Concrete(300),
1485        });
1486        for i in 0..200 {
1487            emitter
1488                .add_anonymous_local([ValType::F64, any, concrete][i % 3])
1489                .unwrap();
1490            emitter.instruction(Instruction::I32Const(i as i32 * 1_000));
1491            emitter.instruction(Instruction::Drop);
1492            if i % 50 == 0 {
1493                let _ = emitter.body_size();
1494            }
1495        }
1496        let size = emitter.body_size();
1497        assert_eq!(size, emitter.build().unwrap().byte_len());
1498    }
1499
1500    #[test]
1501    fn empty_function_validates() {
1502        let f = fixture();
1503        let cx = cx_of(&f);
1504        let emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1505        let func = emitter.build().unwrap();
1506        validate_in_module(func, vec![], vec![]);
1507    }
1508
1509    #[test]
1510    fn params_become_named_locals_at_low_indices() {
1511        let f = fixture();
1512        let cx = cx_of(&f);
1513        let emitter = FunctionEmitter::new(
1514            &cx,
1515            &[
1516                ident_param("a", ValType::F64),
1517                ident_param("b", ValType::F64),
1518            ],
1519        )
1520        .unwrap();
1521        assert_eq!(emitter.local_slot("a"), Some(0));
1522        assert_eq!(emitter.local_slot("b"), Some(1));
1523    }
1524
1525    #[test]
1526    fn define_local_returns_next_index_after_params() {
1527        let f = fixture();
1528        let cx = cx_of(&f);
1529        let mut emitter = FunctionEmitter::new(
1530            &cx,
1531            &[
1532                ident_param("a", ValType::F64),
1533                ident_param("b", ValType::F64),
1534            ],
1535        )
1536        .unwrap();
1537        let c = emitter.define_local(&ident("c"), ValType::I32).unwrap();
1538        assert_eq!(c, 2);
1539        assert_eq!(emitter.local_slot("c"), Some(2));
1540    }
1541
1542    #[test]
1543    fn anonymous_local_is_unnamed() {
1544        let f = fixture();
1545        let cx = cx_of(&f);
1546        let mut emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1547        let idx = emitter.add_anonymous_local(ValType::I32).unwrap();
1548        assert_eq!(idx, 0);
1549        assert!(emitter.local_slot("anything").is_none());
1550    }
1551
1552    #[test]
1553    fn nested_scopes_resolve_innermost_first() {
1554        let f = fixture();
1555        let cx = cx_of(&f);
1556        let mut emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1557        let outer = emitter.define_local(&ident("x"), ValType::I32).unwrap();
1558        emitter.push_scope();
1559        let inner = emitter.define_local(&ident("x"), ValType::I32).unwrap();
1560        assert_eq!(emitter.local_slot("x"), Some(inner));
1561        emitter.pop_scope().unwrap();
1562        assert_eq!(emitter.local_slot("x"), Some(outer));
1563    }
1564
1565    #[test]
1566    fn pop_scope_drops_inner_locals() {
1567        let f = fixture();
1568        let cx = cx_of(&f);
1569        let mut emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1570        emitter.push_scope();
1571        emitter.define_local(&ident("temp"), ValType::I32).unwrap();
1572        assert!(emitter.local_slot("temp").is_some());
1573        emitter.pop_scope().unwrap();
1574        assert!(emitter.local_slot("temp").is_none());
1575    }
1576
1577    #[test]
1578    fn block_depth_tracks_emit_end() {
1579        let f = fixture();
1580        let cx = cx_of(&f);
1581        let mut emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1582        assert_eq!(emitter.wasm_block_depth, 0);
1583        emitter.emit_block(BlockType::Empty);
1584        assert_eq!(emitter.wasm_block_depth, 1);
1585        emitter.emit_block(BlockType::Empty);
1586        assert_eq!(emitter.wasm_block_depth, 2);
1587        emitter.emit_end();
1588        assert_eq!(emitter.wasm_block_depth, 1);
1589        emitter.emit_end();
1590        assert_eq!(emitter.wasm_block_depth, 0);
1591    }
1592
1593    #[test]
1594    fn break_label_for_innermost_loop() {
1595        let f = fixture();
1596        let cx = cx_of(&f);
1597        let mut emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1598        emitter.emit_while_open();
1599        assert_eq!(emitter.break_label().unwrap(), 1);
1600    }
1601
1602    #[test]
1603    fn continue_label_for_innermost_loop() {
1604        let f = fixture();
1605        let cx = cx_of(&f);
1606        let mut emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1607        emitter.emit_while_open();
1608        assert_eq!(emitter.continue_label().unwrap(), 0);
1609    }
1610
1611    #[test]
1612    fn nested_loops_break_label_targets_inner() {
1613        let f = fixture();
1614        let cx = cx_of(&f);
1615        let mut emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1616        emitter.emit_while_open(); // outer
1617        emitter.emit_while_open(); // inner
1618        assert_eq!(emitter.break_label().unwrap(), 1);
1619        assert_eq!(emitter.continue_label().unwrap(), 0);
1620        emitter.emit_while_close();
1621        assert_eq!(emitter.break_label().unwrap(), 1);
1622        assert_eq!(emitter.continue_label().unwrap(), 0);
1623        emitter.emit_while_close();
1624    }
1625
1626    #[test]
1627    fn build_produces_valid_function() {
1628        let f = fixture();
1629        let cx = cx_of(&f);
1630        let mut emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1631        emitter.instruction(Instruction::Unreachable);
1632        let func = emitter.build().unwrap();
1633        validate_in_module(func, vec![], vec![ValType::F64]);
1634    }
1635
1636    #[test]
1637    fn build_emits_declared_locals_in_order() {
1638        let f = fixture();
1639        let cx = cx_of(&f);
1640        let mut emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1641        emitter.add_anonymous_local(ValType::I32).unwrap();
1642        emitter.add_anonymous_local(ValType::F64).unwrap();
1643        let func = emitter.build().unwrap();
1644        validate_in_module(func, vec![], vec![]);
1645    }
1646
1647    #[test]
1648    fn emit_const_string_emits_vtable_loaded_struct_new() {
1649        use wasm_encoder::Encode;
1650        let f = fixture();
1651        let cx = cx_of(&f);
1652        let mut emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1653        emitter.emit_const_string(
1654            /* string_type */ 3, /* raw_string_type */ 0,
1655            /* string_vtable_global */ 5, /* consts_data */ 3, /* code_units */ 5,
1656        );
1657        let actual = emitter.build().unwrap();
1658        let mut actual_bytes = Vec::new();
1659        actual.encode(&mut actual_bytes);
1660
1661        let mut expected = Function::new(std::iter::empty());
1662        expected.instruction(&Instruction::GlobalGet(5));
1663        expected.instruction(&Instruction::I32Const(0));
1664        expected.instruction(&Instruction::I32Const(5));
1665        expected.instruction(&Instruction::ArrayNewData {
1666            array_type_index: 0,
1667            array_data_index: 3,
1668        });
1669        expected.instruction(&Instruction::I64Const(0));
1670        expected.instruction(&Instruction::StructNew(3));
1671        expected.instruction(&Instruction::End);
1672        let mut expected_bytes = Vec::new();
1673        expected.encode(&mut expected_bytes);
1674
1675        assert_eq!(actual_bytes, expected_bytes);
1676    }
1677
1678    #[test]
1679    fn record_span_keeps_mappings_in_order() {
1680        let f = fixture();
1681        let cx = cx_of(&f);
1682        let mut emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1683
1684        emitter.record_span(Span::new(crate::FileId(0), 0, 5).unwrap());
1685        emitter.instruction(Instruction::I32Const(1));
1686        emitter.record_span(Span::new(crate::FileId(0), 7, 9).unwrap());
1687        emitter.instruction(Instruction::I32Const(2));
1688        emitter.record_span(Span::new(crate::FileId(0), 11, 15).unwrap());
1689        emitter.instruction(Instruction::I32Add);
1690
1691        assert_eq!(
1692            emitter.source_mappings,
1693            vec![
1694                (0, Span::new(crate::FileId(0), 0, 5).unwrap()),
1695                (1, Span::new(crate::FileId(0), 7, 9).unwrap()),
1696                (2, Span::new(crate::FileId(0), 11, 15).unwrap()),
1697            ]
1698        );
1699    }
1700
1701    #[test]
1702    fn expr_context_distinct_variants() {
1703        assert_ne!(ExprContext::Value, ExprContext::Statement);
1704    }
1705    fn param(name: &str, ty: crate::Type, boxed: bool) -> crate::TypedParam {
1706        crate::TypedParam {
1707            name: ident(name),
1708            ty,
1709            optional: false,
1710            boxed,
1711            rest: false,
1712            default: None,
1713        }
1714    }
1715
1716    fn assert_internal<T>(
1717        result: Result<T, crate::compiler_error::CompilerFailure>,
1718        message: &str,
1719    ) {
1720        match result {
1721            Err(crate::compiler_error::CompilerFailure::Internal {
1722                stage,
1723                message: actual,
1724                ..
1725            }) => {
1726                assert_eq!(stage, crate::compiler_error::CompilerStage::Codegen);
1727                assert!(actual.contains(message), "{actual}");
1728            }
1729            _ => panic!("expected internal failure containing {message}"),
1730        }
1731    }
1732
1733    #[test]
1734    fn root_scope_and_missing_locals_are_checked() {
1735        let f = fixture();
1736        let cx = cx_of(&f);
1737        let mut emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1738        assert_internal(emitter.require_local_slot("missing"), "not defined");
1739        assert_internal(emitter.write_slot("missing"), "not defined");
1740        assert_internal(emitter.pop_scope(), "root");
1741        assert_eq!(emitter.scopes.len(), 1);
1742        validate_in_module(
1743            FunctionEmitter::new(&cx, &[]).unwrap().build().unwrap(),
1744            vec![],
1745            vec![],
1746        );
1747    }
1748
1749    #[test]
1750    fn local_metadata_and_evaluation_marks_are_checked() {
1751        let f = fixture();
1752        let cx = cx_of(&f);
1753        let mut emitter = FunctionEmitter::new(&cx, &[ident_param("p", ValType::I32)]).unwrap();
1754        assert_internal(
1755            emitter.rebind_in_innermost_scope("p", 1, ValType::I32),
1756            "not allocated",
1757        );
1758        assert_internal(emitter.install_narrow_shadow("p", 0, ValType::F64), "type");
1759        assert_internal(emitter.set_this_local(1), "not allocated");
1760        assert_internal(emitter.end_single_evaluations(1), "mark");
1761        assert_eq!(emitter.local_slot("p"), Some(0));
1762        assert_eq!(emitter.narrowed_read_slot("p"), Some((0, ValType::I32)));
1763        emitter.scopes[0].define("bad".into(), 8, ValType::I32);
1764        assert_internal(emitter.build(), "not allocated");
1765    }
1766
1767    #[test]
1768    fn parameter_prologue_rejects_lengths_types_and_wrong_binding_slots() {
1769        let f = fixture();
1770        let cx = cx_of(&f);
1771        let mut emitter = FunctionEmitter::new(
1772            &cx,
1773            &[
1774                ident_param("p", ValType::F64),
1775                ident_param("q", ValType::I32),
1776            ],
1777        )
1778        .unwrap();
1779        let params = [param("p", crate::Type::Number, false)];
1780        assert_internal(emitter.emit_boxed_param_prologue(&params, &[]), "one entry");
1781        assert_internal(
1782            emitter.emit_boxed_param_prologue(&params, &[9]),
1783            "not allocated",
1784        );
1785        assert_internal(emitter.emit_boxed_param_prologue(&params, &[1]), "type");
1786        let wrong_binding = [param("q", crate::Type::Number, false)];
1787        assert_internal(
1788            emitter.emit_boxed_param_prologue(&wrong_binding, &[0]),
1789            "binding",
1790        );
1791        emitter.emit_boxed_param_prologue(&params, &[0]).unwrap();
1792        assert!(emitter.instructions.is_empty());
1793        assert_eq!(emitter.local_count(), 2);
1794    }
1795
1796    #[test]
1797    fn boxed_parameter_registration_failure_emits_no_prologue() {
1798        let f = fixture();
1799        let cx = cx_of(&f);
1800        let mut emitter = FunctionEmitter::new(&cx, &[ident_param("p", ValType::F64)]).unwrap();
1801        let span = Span::new(crate::FileId(0), 1, 3).unwrap();
1802        emitter.record_span(span);
1803        let error = emitter
1804            .emit_boxed_param_prologue(&[param("p", crate::Type::Number, true)], &[0])
1805            .unwrap_err();
1806        assert!(
1807            matches!(error, crate::compiler_error::CompilerFailure::Internal { span: Some(actual), .. } if actual == span)
1808        );
1809        assert!(emitter.instructions.is_empty());
1810        assert_eq!(emitter.local_count(), 1);
1811        assert_eq!(emitter.local_slot("p"), Some(0));
1812    }
1813
1814    #[test]
1815    fn local_capacity_and_counter_overflow_fail_before_allocation() {
1816        let f = fixture();
1817        let cx = cx_of(&f);
1818        let params = vec![
1819            ident_param("p", ValType::I32);
1820            crate::compiler_limits::MAX_FUNCTION_LOCALS as usize + 1
1821        ];
1822        assert!(matches!(
1823            FunctionEmitter::new(&cx, &params),
1824            Err(crate::compiler_error::CompilerFailure::Limit { .. })
1825        ));
1826        let mut emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1827        emitter.next_local_index = crate::compiler_limits::MAX_FUNCTION_LOCALS;
1828        assert!(matches!(
1829            emitter.add_anonymous_local(ValType::I32),
1830            Err(crate::compiler_error::CompilerFailure::Limit { .. })
1831        ));
1832        assert!(emitter.locals.is_empty());
1833        emitter.next_local_index = u32::MAX;
1834        assert_internal(emitter.add_anonymous_local(ValType::I32), "overflow");
1835        assert!(emitter.locals.is_empty());
1836    }
1837
1838    #[test]
1839    fn invalid_block_operations_discard_the_body() {
1840        let f = fixture();
1841        let cx = cx_of(&f);
1842        let mut emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1843        emitter.emit_end();
1844        assert_eq!(emitter.wasm_block_depth, 0);
1845        assert_internal(emitter.build(), "end without");
1846        let mut emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1847        emitter.wasm_block_depth = u32::MAX;
1848        emitter.emit_while_open();
1849        assert_internal(emitter.build(), "overflow");
1850        let mut emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1851        emitter.emit_switch_open();
1852        emitter.emit_while_close();
1853        assert_internal(emitter.build(), "mismatched loop");
1854        let mut emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1855        emitter.emit_if(BlockType::Empty);
1856        assert_internal(emitter.build(), "unfinished");
1857        validate_in_module(
1858            FunctionEmitter::new(&cx, &[]).unwrap().build().unwrap(),
1859            vec![],
1860            vec![],
1861        );
1862    }
1863
1864    #[test]
1865    fn labels_reject_missing_or_stale_targets_and_continue_skips_switch() {
1866        let f = fixture();
1867        let cx = cx_of(&f);
1868        let mut emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1869        assert_internal(emitter.break_label(), "outside loop");
1870        assert_internal(emitter.continue_label(), "outside loop");
1871        assert_internal(emitter.return_label(), "outside the active");
1872        emitter.emit_while_open();
1873        emitter.emit_switch_open();
1874        assert_eq!(emitter.break_label().unwrap(), 0);
1875        assert_eq!(emitter.continue_label().unwrap(), 1);
1876        emitter.emit_switch_close();
1877        emitter.loop_contexts.last_mut().unwrap().continue_depth = 20;
1878        assert_internal(emitter.continue_label(), "outside the active");
1879    }
1880
1881    #[test]
1882    fn malformed_raw_instructions_never_return_function_bytes() {
1883        let f = fixture();
1884        let cx = cx_of(&f);
1885        for instruction in [
1886            Instruction::LocalGet(0),
1887            Instruction::Br(1),
1888            Instruction::Else,
1889            Instruction::End,
1890            Instruction::BrTable(std::borrow::Cow::Owned(vec![1]), 0),
1891            Instruction::TryTable(
1892                BlockType::Empty,
1893                std::borrow::Cow::Owned(vec![wasm_encoder::Catch::AllRef { label: 1 }]),
1894            ),
1895        ] {
1896            let mut emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1897            emitter.instruction(instruction);
1898            assert!(matches!(
1899                emitter.build(),
1900                Err(crate::compiler_error::CompilerFailure::Internal { .. })
1901            ));
1902        }
1903        let mut emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1904        emitter.instruction(Instruction::Block(BlockType::Empty));
1905        assert_internal(emitter.build(), "unclosed");
1906    }
1907
1908    #[test]
1909    fn cast_branch_depths_are_checked_before_bytes_are_returned() {
1910        let f = fixture();
1911        let cx = cx_of(&f);
1912        let from_ref_type = wasm_encoder::RefType::ANYREF;
1913        let to_ref_type = wasm_encoder::RefType::EQREF;
1914        for instruction in [
1915            Instruction::BrOnCast {
1916                relative_depth: 1,
1917                from_ref_type,
1918                to_ref_type,
1919            },
1920            Instruction::BrOnCastFail {
1921                relative_depth: 1,
1922                from_ref_type,
1923                to_ref_type,
1924            },
1925            Instruction::BrOnCastDescEq {
1926                relative_depth: 1,
1927                from_ref_type,
1928                to_ref_type,
1929            },
1930            Instruction::BrOnCastDescEqFail {
1931                relative_depth: 1,
1932                from_ref_type,
1933                to_ref_type,
1934            },
1935        ] {
1936            let mut emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1937            emitter.instruction(instruction);
1938            assert_internal(emitter.build(), "branch depth");
1939        }
1940    }
1941
1942    #[test]
1943    fn interned_string_and_closure_result_metadata_are_fallible() {
1944        let f = fixture();
1945        let cx = cx_of(&f);
1946        let mut emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1947        assert_internal(
1948            super::emit_const_string_by_text(&mut emitter, &cx, "absent"),
1949            "not interned",
1950        );
1951        assert_internal(
1952            super::emit_pooled_string(&mut emitter, &cx, usize::MAX),
1953            "pool",
1954        );
1955        assert!(emitter.instructions.is_empty());
1956        assert_internal(
1957            super::closure_return_target(&cx, &crate::Type::Number),
1958            "intrinsics",
1959        );
1960        assert_internal(
1961            super::closure_return_target(&cx, &crate::Type::Void),
1962            "intrinsics",
1963        );
1964    }
1965
1966    #[test]
1967    fn latched_failure_prevents_finalization_and_retains_source() {
1968        let f = fixture();
1969        let cx = cx_of(&f);
1970        let emitter = FunctionEmitter::new(&cx, &[]).unwrap();
1971        let span = Span::new(crate::FileId(0), 2, 4).unwrap();
1972        cx.record_failure(
1973            crate::codegen::internal_failure("injected emitter failure").with_span(span),
1974        );
1975        let error = emitter.build_with_lines().unwrap_err();
1976        assert!(
1977            matches!(error, crate::compiler_error::CompilerFailure::Internal { span: Some(actual), .. } if actual == span)
1978        );
1979        validate_in_module(
1980            FunctionEmitter::new(&cx, &[]).unwrap().build().unwrap(),
1981            vec![],
1982            vec![],
1983        );
1984    }
1985}