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