Skip to main content

submilli_engine/codegen/function_emitter/
stmt.rs

1use crate::codegen::CodegenCtx;
2use crate::codegen::bounds::{
3    emit_checked_index, emit_checked_index_with_length, stash_array_length, stash_index_operand,
4};
5use crate::codegen::function_emitter::FunctionEmitter;
6use crate::codegen::init_guard;
7use crate::{
8    EnumVariantPayload, ExprId, Ident, MangledName, StmtId, Type, TypedStmtKind, TypedSwitchCase,
9    TypedSwitchValue,
10};
11use wasm_encoder::{BlockType, HeapType, Instruction, RefType, ValType};
12
13use super::cast;
14use super::expr::{
15    emit_expr, emit_method_call, emit_object_property_write, stash_receiver_as_object_shape,
16};
17
18/// Does this statement contain a body some execution can skip, or re-enter?
19///
20/// That is exactly where emission order stops implying execution order, which
21/// is the one thing a narrowing shadow depends on — see
22/// [`FunctionEmitter::clear_all_narrow_shadows`]. Clearing is conservative (a
23/// missing shadow only costs the narrowed read a cast at use), so a statement
24/// kind that gains a body opts *out* of the rule rather than having to
25/// remember to opt in.
26fn branches(kind: &TypedStmtKind) -> bool {
27    matches!(
28        kind,
29        TypedStmtKind::If { .. }
30            | TypedStmtKind::While { .. }
31            | TypedStmtKind::DoWhile { .. }
32            | TypedStmtKind::For { .. }
33            | TypedStmtKind::ForOf { .. }
34            | TypedStmtKind::Switch { .. }
35            | TypedStmtKind::Try { .. }
36    )
37}
38
39pub fn emit_statement(
40    emitter: &mut FunctionEmitter,
41    ctx: &CodegenCtx,
42    id: StmtId,
43) -> Result<(), crate::compiler_error::CompilerFailure> {
44    let stmt = ctx.ta.try_stmt(id).map_err(crate::codegen::arena_failure)?;
45    emitter.record_span(stmt.span);
46    // Before as well as after: a loop's back edge re-runs the reads above its
47    // body's writes, so a shadow taken before the loop is already unsound at
48    // the loop head.
49    if branches(&stmt.kind) {
50        emitter.clear_all_narrow_shadows();
51    }
52    match &stmt.kind {
53        TypedStmtKind::Block(stmts) => {
54            // Wasm locals are function-scoped, so a source-level block only
55            // needs a fresh name-resolution scope — no Wasm `block`.
56            emitter.push_scope();
57            for &child in stmts {
58                emit_statement(emitter, ctx, child)?;
59            }
60            emitter.pop_scope()?;
61        }
62        TypedStmtKind::Return(value) => {
63            if let Some(expr_id) = value {
64                emit_expr(emitter, ctx, *expr_id)?;
65                cast::emit_coerce_to_return_slot(
66                    emitter,
67                    ctx,
68                    &ctx.ta
69                        .try_expr(*expr_id)
70                        .map_err(crate::codegen::arena_failure)?
71                        .ty,
72                )?;
73            } else if emitter.wasm_result_type(ctx)?.is_some() {
74                super::expr::emit_undefined(emitter, ctx)?;
75                cast::emit_coerce_to_return_slot(emitter, ctx, &Type::Undefined)?;
76            }
77            super::finally::emit_transfer(emitter, super::finally::Transfer::Return)?;
78        }
79        TypedStmtKind::Expr(expr_id) => {
80            emit_expr(emitter, ctx, *expr_id)?;
81            emitter.instruction(Instruction::Drop);
82        }
83        // top-level decls become global.set in _start; these are function-local
84        TypedStmtKind::Let {
85            name,
86            ty,
87            value,
88            boxed,
89            ..
90        } => {
91            emit_expr(emitter, ctx, *value)?;
92            let value_ty = ctx
93                .ta
94                .try_expr(*value)
95                .map_err(crate::codegen::arena_failure)?
96                .ty
97                .clone();
98            cast::emit_coerce_to_slot(emitter, ctx, &value_ty, ty)?;
99            if *boxed {
100                let box_idx = ctx.symbols.box_type_idx(ty)?.ok_or_else(|| {
101                    crate::codegen::internal_failure("box type registered for every boxed Let")
102                })?;
103                if ctx.ta.parameter_initializations.contains(&id) {
104                    let slot = emitter
105                        .parameter_binding_cells
106                        .get(name)
107                        .copied()
108                        .ok_or_else(|| {
109                            crate::codegen::internal_failure(
110                                "parameter pattern cell was not reserved",
111                            )
112                        })?;
113                    let value_slot = emitter.add_anonymous_local(ctx.symbols.value_type(ty)?)?;
114                    emitter.instruction(Instruction::LocalSet(value_slot));
115                    emitter.instruction(Instruction::LocalGet(slot));
116                    emitter.instruction(Instruction::LocalGet(value_slot));
117                    emitter.instruction(Instruction::StructSet {
118                        struct_type_index: box_idx,
119                        field_index: 0,
120                    });
121                    super::binding_cells::mark_initialized(emitter, slot, box_idx);
122                    return Ok(());
123                }
124                emitter.instruction(Instruction::I32Const(1));
125                emitter.instruction(Instruction::StructNew(box_idx));
126                let box_val = ValType::Ref(RefType {
127                    nullable: false,
128                    heap_type: HeapType::Concrete(box_idx),
129                });
130                let slot = emitter.define_local(name, box_val)?;
131                emitter.instruction(Instruction::LocalSet(slot));
132            } else {
133                let slot = emitter.define_local(name, ctx.symbols.value_type(ty)?)?;
134                emitter.instruction(Instruction::LocalSet(slot));
135            }
136        }
137        TypedStmtKind::ReboxLocal { ident, ty } => {
138            let slot = emitter.write_slot(&ident.name)?;
139            let box_idx = ctx.symbols.box_type_idx(ty)?.ok_or_else(|| {
140                crate::codegen::internal_failure("box type registered for every boxed Let")
141            })?;
142            super::binding_cells::read(emitter, ctx, slot, box_idx)?;
143            emitter.instruction(Instruction::I32Const(1));
144            emitter.instruction(Instruction::StructNew(box_idx));
145            emitter.instruction(Instruction::LocalSet(slot));
146        }
147        TypedStmtKind::Const {
148            name, ty, value, ..
149        } => {
150            // captured const is copied into closure env, not boxed
151            emit_expr(emitter, ctx, *value)?;
152            let value_ty = ctx
153                .ta
154                .try_expr(*value)
155                .map_err(crate::codegen::arena_failure)?
156                .ty
157                .clone();
158            cast::emit_coerce_to_slot(emitter, ctx, &value_ty, ty)?;
159            let slot = emitter.define_local(name, ctx.symbols.value_type(ty)?)?;
160            emitter.instruction(Instruction::LocalSet(slot));
161        }
162        TypedStmtKind::AssignLocal {
163            ident,
164            target_ty,
165            value,
166            boxed,
167            narrowed_shadow_ty,
168        } => {
169            let slot = emitter.write_slot(&ident.name)?;
170            if *boxed {
171                // The box's inner field is keyed on the binding's
172                // declared type (`target_ty`), not the RHS type, so
173                // a primitive RHS into a wider boxed slot lands the
174                // boxed primitive in the right box variant.
175                let box_idx = ctx.symbols.box_type_idx(target_ty)?.ok_or_else(|| {
176                    crate::codegen::internal_failure(
177                        "box type registered for every boxed AssignLocal",
178                    )
179                })?;
180                let value_ty = ctx
181                    .ta
182                    .try_expr(*value)
183                    .map_err(crate::codegen::arena_failure)?
184                    .ty
185                    .clone();
186                emitter.instruction(Instruction::LocalGet(slot));
187                emit_expr(emitter, ctx, *value)?;
188                cast::emit_coerce_to_slot(emitter, ctx, &value_ty, target_ty)?;
189                let initialization = ctx.ta.parameter_initializations.contains(&id);
190                if !initialization {
191                    super::binding_cells::check_initialized(emitter, ctx, slot, box_idx)?;
192                }
193                emitter.instruction(Instruction::StructSet {
194                    struct_type_index: box_idx,
195                    field_index: 0,
196                });
197                if initialization {
198                    super::binding_cells::mark_initialized(emitter, slot, box_idx);
199                }
200            } else {
201                emit_expr(emitter, ctx, *value)?;
202                let value_ty = ctx
203                    .ta
204                    .try_expr(*value)
205                    .map_err(crate::codegen::arena_failure)?
206                    .ty
207                    .clone();
208                // If this assignment installs a narrowing, allocate a fresh
209                // shadow local of the narrowed type. `local.tee` keeps the RHS
210                // on the stack for the subsequent coerce+set while also storing
211                // into the shadow. Installing it makes `LocalNarrowRef` reads
212                // resolve there — no per-use cast needed. The narrowed type can
213                // differ from the RHS's own: a function narrows to the declared
214                // member it fits, which may take more parameters.
215                let stored_ty = match narrowed_shadow_ty {
216                    Some(narrowed_ty) => {
217                        cast::emit_coerce_to_slot(emitter, ctx, &value_ty, narrowed_ty)?;
218                        let narrowed_val = ctx.symbols.value_type(narrowed_ty)?;
219                        let shadow_idx = emitter.add_anonymous_local(narrowed_val)?;
220                        emitter.instruction(Instruction::LocalTee(shadow_idx));
221                        emitter.install_narrow_shadow(&ident.name, shadow_idx, narrowed_val)?;
222                        narrowed_ty
223                    }
224                    None => &value_ty,
225                };
226                cast::emit_coerce_to_slot(emitter, ctx, stored_ty, target_ty)?;
227                emitter.instruction(Instruction::LocalSet(slot));
228            }
229        }
230        TypedStmtKind::AssignGlobal {
231            mangled,
232            target_ty,
233            value,
234            ..
235        } => {
236            emit_assign_global(emitter, ctx, id, mangled, target_ty, *value)?;
237        }
238        TypedStmtKind::AssignField {
239            receiver,
240            name,
241            value,
242        } => {
243            emit_assign_field(emitter, ctx, *receiver, name, *value)?;
244        }
245        TypedStmtKind::AssignIndex {
246            receiver,
247            index,
248            value,
249            elem_ty,
250        } => {
251            emit_assign_index(emitter, ctx, *receiver, *index, *value, elem_ty)?;
252        }
253        TypedStmtKind::If {
254            condition,
255            then_block,
256            else_block,
257        } => {
258            let cond_ty = ctx
259                .ta
260                .try_expr(*condition)
261                .map_err(crate::codegen::arena_failure)?
262                .ty
263                .clone();
264            emit_expr(emitter, ctx, *condition)?;
265            cast::emit_condition_to_i32(emitter, ctx, &cond_ty)?;
266            emitter.emit_if(BlockType::Empty);
267            emit_statement(emitter, ctx, *then_block)?;
268            if let Some(else_id) = else_block {
269                emitter.emit_else();
270                emit_statement(emitter, ctx, *else_id)?;
271            }
272            emitter.emit_end();
273        }
274        TypedStmtKind::While { condition, body } => {
275            // `block { loop { <cond>; i32.eqz; br_if 1; <body>; br 0; } }`
276            // — break exits the outer block, continue restarts the loop.
277            emitter.emit_while_open();
278            let cond_ty = ctx
279                .ta
280                .try_expr(*condition)
281                .map_err(crate::codegen::arena_failure)?
282                .ty
283                .clone();
284            emit_expr(emitter, ctx, *condition)?;
285            cast::emit_condition_to_i32(emitter, ctx, &cond_ty)?;
286            emitter.instruction(Instruction::I32Eqz);
287            let break_label = emitter.break_label()?;
288            emitter.instruction(Instruction::BrIf(break_label));
289            emit_statement(emitter, ctx, *body)?;
290            let continue_label = emitter.continue_label()?;
291            emitter.instruction(Instruction::Br(continue_label));
292            emitter.emit_while_close();
293        }
294        TypedStmtKind::Break => {
295            let transfer = super::finally::Transfer::Branch {
296                depth: emitter.wasm_block_depth - emitter.break_label()? - 1,
297                finally_floor: emitter.break_finally_floor(),
298            };
299            super::finally::emit_transfer(emitter, transfer)?;
300        }
301        TypedStmtKind::Continue => {
302            let transfer = super::finally::Transfer::Branch {
303                depth: emitter.wasm_block_depth - emitter.continue_label()? - 1,
304                finally_floor: emitter.continue_finally_floor(),
305            };
306            super::finally::emit_transfer(emitter, transfer)?;
307        }
308        TypedStmtKind::For { .. } | TypedStmtKind::ForOf { .. } | TypedStmtKind::DoWhile { .. } => {
309            return Err(crate::codegen::internal_failure(
310                "for / for-of / do-while must be lowered by the desugar pass before codegen",
311            ));
312        }
313        TypedStmtKind::Switch {
314            discriminant,
315            discriminant_ty,
316            cases,
317            default,
318        } => {
319            emit_switch(
320                emitter,
321                ctx,
322                *discriminant,
323                discriminant_ty,
324                cases,
325                default.as_ref().copied(),
326            )?;
327        }
328        TypedStmtKind::NarrowRegion {
329            path,
330            source,
331            binding,
332            cast_info,
333            body,
334        } => {
335            emitter.push_scope();
336            if path.chain.is_empty() {
337                let shadow_val = ctx.symbols.value_type(&cast_info.to_ty)?;
338                let shadow = emitter.add_anonymous_local(shadow_val)?;
339                emit_expr(emitter, ctx, *source)?;
340                cast::emit_narrowing_cast(emitter, ctx, cast_info)?;
341                emitter.instruction(Instruction::LocalSet(shadow));
342                emitter.install_narrow_shadow(&binding.name, shadow, shadow_val)?;
343            } else {
344                // The predicate proved a field-path value that may already have
345                // changed while evaluating the rest of the condition. Defer its
346                // first re-read and checked cast until an actual use in the body.
347                emitter.register_narrow_source(&binding.name, *source)?;
348            }
349            emit_statement(emitter, ctx, *body)?;
350            emitter.pop_scope()?;
351        }
352        TypedStmtKind::Throw { value } => {
353            // cast is unconditional: typechecker guarantees Error type; Wasm-level nullability comes from (ref null $Object) lowering
354            emit_expr(emitter, ctx, *value)?;
355            crate::codegen::throw::emit_error_throw(emitter, ctx);
356        }
357        TypedStmtKind::Try {
358            body,
359            catches,
360            finally,
361        } => {
362            emit_try(emitter, ctx, *body, catches, *finally)?;
363        }
364    }
365    let _: () = if branches(&stmt.kind) {
366        emitter.clear_all_narrow_shadows();
367    };
368    Ok(())
369}
370
371/// Evaluate `expr` into an anonymous local and register the node as already
372/// evaluated, so a compound assignment's synthesized read — which shares the
373/// write's own receiver and index nodes — reads the local instead of running
374/// the expression a second time. Leaves the stack unchanged.
375fn emit_operand_once(
376    emitter: &mut FunctionEmitter,
377    ctx: &CodegenCtx,
378    expr: ExprId,
379) -> Result<u32, crate::compiler_error::CompilerFailure> {
380    let slot = emitter.add_anonymous_local(
381        ctx.symbols.value_type(
382            &ctx.ta
383                .try_expr(expr)
384                .map_err(crate::codegen::arena_failure)?
385                .ty,
386        )?,
387    )?;
388    emit_expr(emitter, ctx, expr)?;
389    emitter.instruction(Instruction::LocalSet(slot));
390    emitter.record_single_evaluation(expr, slot)?;
391    Ok(slot)
392}
393
394fn emit_assign_global(
395    emitter: &mut FunctionEmitter,
396    ctx: &CodegenCtx,
397    stmt: StmtId,
398    mangled: &MangledName,
399    target_ty: &Type,
400    value: ExprId,
401) -> Result<(), crate::compiler_error::CompilerFailure> {
402    emit_expr(emitter, ctx, value)?;
403    let value_ty = ctx
404        .ta
405        .try_expr(value)
406        .map_err(crate::codegen::arena_failure)?
407        .ty
408        .clone();
409    cast::emit_coerce_to_slot(emitter, ctx, &value_ty, target_ty)?;
410    let idx = ctx
411        .symbols
412        .global_idx(mangled)
413        .ok_or_else(|| crate::codegen::internal_failure("Inferer guarantees the binding exists"))?;
414    // JavaScript checks the binding only once the value is computed.
415    let declares = init_guard::is_declaration(ctx, mangled, stmt);
416    if !declares {
417        init_guard::emit_check(emitter, ctx, mangled);
418    }
419    emitter.instruction(Instruction::GlobalSet(idx));
420    if declares {
421        init_guard::emit_mark_initialized(emitter, ctx, mangled);
422    }
423    Ok(())
424}
425
426fn emit_assign_field(
427    emitter: &mut FunctionEmitter,
428    ctx: &CodegenCtx,
429    receiver: ExprId,
430    name: &Ident,
431    value: ExprId,
432) -> Result<(), crate::compiler_error::CompilerFailure> {
433    let mark = emitter.single_evaluation_mark();
434    let receiver_ty = ctx
435        .ta
436        .source_type(receiver)
437        .map_err(crate::codegen::arena_failure)?
438        .clone();
439    // The receiver runs before the value in both arms, which is both the
440    // left-to-right order the language guarantees and the order
441    // `emit_operand_once` needs: a read inside `value` can only reuse a slot
442    // that is already filled.
443    let recv = emit_operand_once(emitter, ctx, receiver)?;
444    emit_operand_once(emitter, ctx, value)?;
445    emitter.instruction(Instruction::LocalGet(recv));
446    if ctx
447        .ta
448        .try_expr(receiver)
449        .map_err(crate::codegen::arena_failure)?
450        .ty
451        != receiver_ty
452    {
453        crate::codegen::cast_check::emit_operation_cast_on_stack(
454            emitter,
455            ctx,
456            &ctx.ta
457                .try_expr(receiver)
458                .map_err(crate::codegen::arena_failure)?
459                .ty,
460            &receiver_ty,
461        )?;
462    }
463    let recv = emitter.add_anonymous_local(ctx.symbols.value_type(&receiver_ty)?)?;
464    emitter.instruction(Instruction::LocalSet(recv));
465    match receiver_ty.peel() {
466        // Class instance: nominal receiver, array-backed object payload.
467        Type::ClassRef { mangled, .. } => {
468            emit_class_field_store(emitter, ctx, receiver, recv, mangled, name, value)?;
469        }
470        _ => emit_object_field_store(emitter, ctx, &receiver_ty, recv, name, value)?,
471    }
472    emitter.end_single_evaluations(mark)?;
473    Ok(())
474}
475
476#[allow(clippy::too_many_arguments)]
477fn emit_class_field_store(
478    emitter: &mut FunctionEmitter,
479    ctx: &CodegenCtx,
480    receiver: ExprId,
481    recv: u32,
482    mangled: &crate::MangledName,
483    name: &Ident,
484    value: ExprId,
485) -> Result<(), crate::compiler_error::CompilerFailure> {
486    // Accessor property (no data slot): dispatch the synthetic setter. The
487    // call leaves its `undefined` result, which the store discards.
488    let Some(slot) = ctx.symbols.class_field_slot(mangled, &name.name) else {
489        let setter = crate::codegen::classes::accessor_setter_name(&name.name);
490        emit_method_call(
491            emitter,
492            ctx,
493            receiver,
494            mangled,
495            &setter,
496            std::slice::from_ref(&value),
497            None,
498            None,
499            &Type::Void,
500        )?;
501        emitter.instruction(Instruction::Drop);
502        return Ok(());
503    };
504    let struct_idx = ctx.symbols.class_struct_type_idx(mangled).ok_or_else(|| {
505        crate::codegen::internal_failure("class struct type recorded in classes::emit")
506    })?;
507    let intrinsics = ctx
508        .symbols
509        .intrinsic_type_indices()
510        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared by codegen entry"))?;
511    let value_ty = ctx
512        .ta
513        .try_expr(value)
514        .map_err(crate::codegen::arena_failure)?
515        .ty
516        .clone();
517    emit_expr(emitter, ctx, value)?;
518    cast::emit_box(emitter, ctx, &value_ty)?;
519    let stored = emitter.add_anonymous_local(ctx.symbols.value_type(&Type::Unknown)?)?;
520    emitter.instruction(Instruction::LocalSet(stored));
521    emitter.instruction(Instruction::LocalGet(recv));
522    emitter.instruction(Instruction::StructGet {
523        struct_type_index: struct_idx,
524        field_index: 2,
525    });
526    emitter.instruction(Instruction::I32Const(slot as i32));
527    emitter.instruction(Instruction::LocalGet(stored));
528    emitter.instruction(Instruction::ArraySet(intrinsics.object_fields));
529    let index = emitter.add_anonymous_local(ValType::I32)?;
530    emitter.instruction(Instruction::I32Const(slot as i32));
531    emitter.instruction(Instruction::LocalSet(index));
532    crate::codegen::field_names::emit_mark_present(emitter, ctx, recv, index)?;
533    Ok(())
534}
535
536fn emit_object_field_store(
537    emitter: &mut FunctionEmitter,
538    ctx: &CodegenCtx,
539    receiver_ty: &Type,
540    recv: u32,
541    name: &Ident,
542    value: ExprId,
543) -> Result<(), crate::compiler_error::CompilerFailure> {
544    let object_shape = ctx
545        .symbols
546        .intrinsic_type_indices()
547        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared by codegen entry"))?
548        .object_shape;
549    emitter.instruction(Instruction::LocalGet(recv));
550    let rcv_local = stash_receiver_as_object_shape(emitter, receiver_ty, object_shape)?;
551    // Accessor-aware: a data field writes its slot; an accessor property
552    // invokes its `set <prop>` method closure.
553    emit_object_property_write(emitter, ctx, rcv_local, name, value)?;
554    Ok(())
555}
556
557fn emit_assign_index(
558    emitter: &mut FunctionEmitter,
559    ctx: &CodegenCtx,
560    receiver: ExprId,
561    index: ExprId,
562    value: ExprId,
563    elem_ty: &Type,
564) -> Result<(), crate::compiler_error::CompilerFailure> {
565    let mark = emitter.single_evaluation_mark();
566    if ctx
567        .ta
568        .source_type(receiver)
569        .map_err(crate::codegen::arena_failure)?
570        .is_structural_object()
571    {
572        let object = emit_operand_once(emitter, ctx, receiver)?;
573        let key = emit_operand_once(emitter, ctx, index)?;
574        let stored = emit_operand_once(emitter, ctx, value)?;
575        emitter.instruction(Instruction::LocalGet(object));
576        cast::emit_box(
577            emitter,
578            ctx,
579            &ctx.ta
580                .try_expr(receiver)
581                .map_err(crate::codegen::arena_failure)?
582                .ty,
583        )?;
584        emitter.instruction(Instruction::LocalGet(key));
585        crate::codegen::cast_check::emit_operation_cast_on_stack(
586            emitter,
587            ctx,
588            &ctx.ta
589                .try_expr(index)
590                .map_err(crate::codegen::arena_failure)?
591                .ty,
592            &Type::String,
593        )?;
594        emitter.instruction(Instruction::LocalGet(stored));
595        cast::emit_box(
596            emitter,
597            ctx,
598            &ctx.ta
599                .try_expr(value)
600                .map_err(crate::codegen::arena_failure)?
601                .ty,
602        )?;
603        let Some(symbol) = ctx.require(
604            ctx.symbols.prelude_func_idx("ObjectConstructor##setField"),
605            "dynamic write imported",
606        ) else {
607            return Ok(());
608        };
609        emitter.instruction(Instruction::Call(symbol));
610    } else if matches!(
611        ctx.ta
612            .source_type(receiver)
613            .map_err(crate::codegen::arena_failure)?
614            .peel(),
615        Type::Uint8Array
616    ) {
617        emit_uint8_index_store(emitter, ctx, receiver, index, value)?;
618    } else {
619        emit_array_index_store(emitter, ctx, receiver, index, value, elem_ty)?;
620    }
621    emitter.end_single_evaluations(mark)?;
622    Ok(())
623}
624
625/// `u8[i] = v`. Packed `i8` storage, so the RHS is truncated to its low 8 bits.
626fn emit_uint8_index_store(
627    emitter: &mut FunctionEmitter,
628    ctx: &CodegenCtx,
629    receiver: ExprId,
630    index: ExprId,
631    value: ExprId,
632) -> Result<(), crate::compiler_error::CompilerFailure> {
633    let raw_uint8_idx = ctx.symbols.raw_uint8_array_type_idx().ok_or_else(|| {
634        crate::codegen::internal_failure("Type::Uint8Array requires intrinsic types declared")
635    })?;
636    let uint8_idx = ctx.symbols.uint8_array_type_idx().ok_or_else(|| {
637        crate::codegen::internal_failure("Type::Uint8Array requires intrinsic types declared")
638    })?;
639    let recv_local = emit_operand_once(emitter, ctx, receiver)?;
640    let key = emit_operand_once(emitter, ctx, index)?;
641    // RHS evaluates before the bounds check throws, preserving left-to-right
642    // evaluation order (`emit_array_index_store` does the same).
643    let original_value = emit_operand_once(emitter, ctx, value)?;
644
645    // `$Uint8Array`'s backing field is immutable — no `push` exists to swap the
646    // buffer out — so this read could sit anywhere after the receiver. It goes
647    // here to keep the two index stores the same shape.
648    let raw_local = emitter.add_anonymous_local(ValType::Ref(RefType {
649        nullable: false,
650        heap_type: HeapType::Concrete(raw_uint8_idx),
651    }))?;
652    emitter.instruction(Instruction::LocalGet(recv_local));
653    crate::codegen::cast_check::emit_operation_cast_on_stack(
654        emitter,
655        ctx,
656        &ctx.ta
657            .try_expr(receiver)
658            .map_err(crate::codegen::arena_failure)?
659            .ty,
660        &Type::Uint8Array,
661    )?;
662    emitter.instruction(Instruction::LocalGet(key));
663    super::expr::emit_index_number(
664        emitter,
665        ctx,
666        &ctx.ta
667            .try_expr(index)
668            .map_err(crate::codegen::arena_failure)?
669            .ty,
670    )?;
671    let idx_f64_local = stash_index_operand(emitter)?;
672    let value_local = emitter.add_anonymous_local(ValType::I32)?;
673    emitter.instruction(Instruction::LocalGet(original_value));
674    cast::emit_coerce_to_slot(
675        emitter,
676        ctx,
677        &ctx.ta
678            .try_expr(value)
679            .map_err(crate::codegen::arena_failure)?
680            .ty,
681        &Type::Number,
682    )?;
683    emit_to_uint8(emitter)?;
684    emitter.instruction(Instruction::LocalSet(value_local));
685    emitter.instruction(Instruction::StructGet {
686        struct_type_index: uint8_idx,
687        field_index: 1,
688    });
689    emitter.instruction(Instruction::LocalSet(raw_local));
690    let idx_local = emit_checked_index(emitter, ctx, raw_local, idx_f64_local)?;
691    emitter.instruction(Instruction::LocalGet(raw_local));
692    emitter.instruction(Instruction::LocalGet(idx_local));
693    emitter.instruction(Instruction::LocalGet(value_local));
694    emitter.instruction(Instruction::ArraySet(raw_uint8_idx));
695    Ok(())
696}
697
698/// JavaScript's `ToUint8` on the `f64` on the stack, leaving an `i32` byte:
699/// truncate, then reduce modulo 256 (so `-1` is 255). Every step is exact in
700/// `f64`; `NaN` and `±Infinity` become `NaN`, which `trunc_sat` maps to 0.
701fn emit_to_uint8(
702    emitter: &mut FunctionEmitter,
703) -> Result<(), crate::compiler_error::CompilerFailure> {
704    let truncated = emitter.add_anonymous_local(ValType::F64)?;
705    emitter.instruction(Instruction::F64Trunc);
706    emitter.instruction(Instruction::LocalTee(truncated));
707    emitter.instruction(Instruction::LocalGet(truncated));
708    emitter.instruction(Instruction::F64Const(256.0.into()));
709    emitter.instruction(Instruction::F64Div);
710    emitter.instruction(Instruction::F64Floor);
711    emitter.instruction(Instruction::F64Const(256.0.into()));
712    emitter.instruction(Instruction::F64Mul);
713    emitter.instruction(Instruction::F64Sub);
714    emitter.instruction(Instruction::I32TruncSatF64U);
715    Ok(())
716}
717
718/// `a[i] = v` on a `$Array`.
719fn emit_array_index_store(
720    emitter: &mut FunctionEmitter,
721    ctx: &CodegenCtx,
722    receiver: ExprId,
723    index: ExprId,
724    value: ExprId,
725    _elem_ty: &Type,
726) -> Result<(), crate::compiler_error::CompilerFailure> {
727    let raw_array_idx = ctx.symbols.raw_array_type_idx().ok_or_else(|| {
728        crate::codegen::internal_failure("Type::Array requires intrinsic types declared")
729    })?;
730    let array_idx = ctx.symbols.array_type_idx().ok_or_else(|| {
731        crate::codegen::internal_failure("Type::Array requires intrinsic types declared")
732    })?;
733    let object_idx = ctx
734        .symbols
735        .intrinsic_type_indices()
736        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?
737        .object;
738    let recv_local = emit_operand_once(emitter, ctx, receiver)?;
739    let key = emit_operand_once(emitter, ctx, index)?;
740    // RHS evaluates before the store (and so before the bounds check throws),
741    // preserving left-to-right evaluation order.
742    let value_local = emitter.add_anonymous_local(ValType::Ref(RefType {
743        nullable: true,
744        heap_type: HeapType::Concrete(object_idx),
745    }))?;
746    let value_ty = ctx
747        .ta
748        .try_expr(value)
749        .map_err(crate::codegen::arena_failure)?
750        .ty
751        .clone();
752    emit_expr(emitter, ctx, value)?;
753    // Preserve the actual RHS in the erased element slot.
754    cast::emit_box(emitter, ctx, &value_ty)?;
755    emitter.instruction(Instruction::LocalSet(value_local));
756
757    // Read the backing array *after* the RHS: `push` swaps in a fresh
758    // `$rawArray`, so a RHS that grows this array leaves any buffer read
759    // earlier detached — the store would land in one nobody holds, and the
760    // bounds check would test the stale length.
761    let raw_arr_local = emitter.add_anonymous_local(ValType::Ref(RefType {
762        nullable: false,
763        heap_type: HeapType::Concrete(raw_array_idx),
764    }))?;
765    emitter.instruction(Instruction::LocalGet(recv_local));
766    crate::codegen::cast_check::emit_operation_cast_on_stack(
767        emitter,
768        ctx,
769        &ctx.ta
770            .try_expr(receiver)
771            .map_err(crate::codegen::arena_failure)?
772            .ty,
773        &Type::Array(Box::new(Type::Unknown)),
774    )?;
775    emitter.instruction(Instruction::LocalGet(key));
776    super::expr::emit_index_number(
777        emitter,
778        ctx,
779        &ctx.ta
780            .try_expr(index)
781            .map_err(crate::codegen::arena_failure)?
782            .ty,
783    )?;
784    let idx_f64_local = stash_index_operand(emitter)?;
785    let length = stash_array_length(emitter, array_idx)?;
786    emitter.instruction(Instruction::StructGet {
787        struct_type_index: array_idx,
788        field_index: 1,
789    });
790    emitter.instruction(Instruction::LocalSet(raw_arr_local));
791    let idx_local = emit_checked_index_with_length(emitter, ctx, length, idx_f64_local)?;
792    emitter.instruction(Instruction::LocalGet(raw_arr_local));
793    emitter.instruction(Instruction::LocalGet(idx_local));
794    emitter.instruction(Instruction::LocalGet(value_local));
795    emitter.instruction(Instruction::ArraySet(raw_array_idx));
796    Ok(())
797}
798
799/// Catch handlers surround only the try body. A finally gets an outer
800/// completion target shared by normal, exceptional, and early exits.
801pub(super) fn emit_try(
802    emitter: &mut FunctionEmitter,
803    ctx: &CodegenCtx,
804    body: StmtId,
805    catches: &[crate::TypedCatchClause],
806    finally: Option<StmtId>,
807) -> Result<(), crate::compiler_error::CompilerFailure> {
808    if let Some(finally) = finally {
809        super::finally::emit_try_finally(emitter, ctx, body, catches, finally)?;
810        return Ok(());
811    }
812    if catches.is_empty() {
813        emit_statement(emitter, ctx, body)?;
814        return Ok(());
815    }
816    let error_idx = ctx
817        .symbols
818        .intrinsic_type_indices()
819        .ok_or_else(|| crate::codegen::internal_failure("error intrinsic registered"))?
820        .error;
821    let tag_idx = ctx
822        .symbols
823        .error_tag_idx()
824        .ok_or_else(|| crate::codegen::internal_failure("error tag registered"))?;
825    emitter.emit_block(BlockType::Empty);
826    emitter.emit_block(BlockType::Result(ValType::Ref(RefType {
827        nullable: false,
828        heap_type: HeapType::Concrete(error_idx),
829    })));
830    emitter.instruction(Instruction::TryTable(
831        BlockType::Empty,
832        std::borrow::Cow::Owned(vec![wasm_encoder::Catch::One {
833            tag: tag_idx,
834            label: 0,
835        }]),
836    ));
837    emitter.bump_block_depth();
838    emit_statement(emitter, ctx, body)?;
839    emitter.emit_end();
840    emitter.instruction(Instruction::Br(1));
841    emitter.emit_end();
842    let err_stash = emitter.add_anonymous_local(ValType::Ref(RefType {
843        nullable: true,
844        heap_type: HeapType::Concrete(error_idx),
845    }))?;
846    emitter.instruction(Instruction::LocalSet(err_stash));
847    emit_catch_dispatch(emitter, ctx, catches, err_stash)?;
848    emitter.emit_end();
849    Ok(())
850}
851
852/// A `catch (e: MyError)` clause annotated with a proper `Error` subclass
853/// filters by nominal identity — `(vtable global, struct type)` of the
854/// annotation class. The root `Error` annotation (or none) binds everything.
855fn catch_filter_class(
856    ctx: &CodegenCtx,
857    clause_ty: &Type,
858) -> Result<Option<(u32, u32)>, crate::compiler_error::CompilerFailure> {
859    let Type::ClassRef { mangled, .. } = clause_ty.peel() else {
860        return Ok(None);
861    };
862    if *mangled == crate::mangle::prelude("Error") {
863        return Ok(None);
864    }
865    let vtable_global = ctx
866        .symbols
867        .class_vtable_global_idx(mangled)
868        .ok_or_else(|| {
869            crate::codegen::internal_failure("catch-annotation class vtable global recorded")
870        })?;
871    let struct_idx = ctx.symbols.class_struct_type_idx(mangled).ok_or_else(|| {
872        crate::codegen::internal_failure("catch-annotation class struct type recorded")
873    })?;
874    Ok(Some((vtable_global, struct_idx)))
875}
876
877/// Ordered dispatch over the catch arms for the caught error stashed in
878/// `err_stash` (a nullable `(ref null $Error)` local, already set). Each
879/// subclass arm runs the nominal brand test; the first match binds, runs its
880/// body, and branches out. A root-`Error`/untyped arm is a catch-all and ends
881/// the chain; without one, the chain ends by re-raising with `throw` (not
882/// `throw_ref`) so an enclosing `catch_all_ref` wrapper still observes it and
883/// runs `finally` first.
884///
885/// At the emission site, relative label 0 is `$end_try`, the matched-arm exit.
886/// When cleanup is present, the whole try/catch sits inside its wrapper.
887fn emit_catch_dispatch(
888    emitter: &mut FunctionEmitter,
889    ctx: &CodegenCtx,
890    catches: &[crate::TypedCatchClause],
891    err_stash: u32,
892) -> Result<(), crate::compiler_error::CompilerFailure> {
893    for clause in catches {
894        let Some((vtable_global, struct_idx)) = catch_filter_class(ctx, &clause.ty)? else {
895            emitter.push_scope();
896            emitter.instruction(Instruction::LocalGet(err_stash));
897            emitter.instruction(Instruction::RefAsNonNull);
898            bind_catch_local(emitter, ctx, clause)?;
899            emit_statement(emitter, ctx, clause.body)?;
900            emitter.pop_scope()?;
901            // Falling through reaches the matched-arm exit in both topologies.
902            return Ok(());
903        };
904        emitter.instruction(Instruction::LocalGet(err_stash));
905        super::cast::emit_nominal_instance_test(emitter, ctx, vtable_global)?;
906        emitter.emit_if(BlockType::Empty);
907        emitter.push_scope();
908        emitter.instruction(Instruction::LocalGet(err_stash));
909        // Shape-only cast is sound here: the brand matched.
910        emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(struct_idx)));
911        bind_catch_local(emitter, ctx, clause)?;
912        emit_statement(emitter, ctx, clause.body)?;
913        emitter.pop_scope()?;
914        // Matched-arm exit is label 0 at chain level; +1 for the `if` frame.
915        emitter.instruction(Instruction::Br(1));
916        emitter.emit_end();
917    }
918    let tag_idx = ctx
919        .symbols
920        .error_tag_idx()
921        .ok_or_else(|| crate::codegen::internal_failure("error tag registered"))?;
922    emitter.instruction(Instruction::LocalGet(err_stash));
923    emitter.instruction(Instruction::RefAsNonNull);
924    emitter.instruction(Instruction::Throw(tag_idx));
925    Ok(())
926}
927
928/// Store the caught error on top of the stack in the clause's binding, boxed
929/// when a closure captures the binding and may reassign it.
930fn bind_catch_local(
931    emitter: &mut FunctionEmitter,
932    ctx: &CodegenCtx,
933    clause: &crate::TypedCatchClause,
934) -> Result<(), crate::compiler_error::CompilerFailure> {
935    let slot_ty = if clause.boxed {
936        let box_idx = ctx.symbols.box_type_idx(&clause.ty)?.ok_or_else(|| {
937            crate::codegen::internal_failure("box type registered for every boxed catch binding")
938        })?;
939        // The caught error initializes the cell.
940        emitter.instruction(Instruction::I32Const(1));
941        emitter.instruction(Instruction::StructNew(box_idx));
942        ValType::Ref(RefType {
943            nullable: false,
944            heap_type: HeapType::Concrete(box_idx),
945        })
946    } else {
947        ctx.symbols.value_type(&clause.ty)?
948    };
949    let slot = emitter.define_local(&clause.binding, slot_ty)?;
950    emitter.instruction(Instruction::LocalSet(slot));
951    Ok(())
952}
953
954/// Emit a `switch` statement as a block-stack of per-case targets.
955///
956/// Layout (with N cases and a `default`):
957///
958/// ```text
959/// (block $end
960///   (block $default
961///     (block $case_{N-1})
962///     …
963///     (block $case_0
964///       <dispatcher>            ;; br_if $case_i for each value-test
965///       br $default             ;; (or $end when no default)
966///     )                          ;; close $case_0
967///     <case_0 body>; br $end
968///     …                          ;; close + body for each case
969///     <case_{N-1} body>; br $end
970///   )                            ;; close $default
971///   <default body>               ;; falls through to $end naturally
972/// )
973/// ```
974///
975/// The discriminant is evaluated once into a fresh anonymous local
976/// so each per-case test reads it without re-running side effects.
977/// `break` from any case body targets `$end` via the loop-contexts
978/// stack (`emit_switch_open` pushed `is_switch: true`); `continue`
979/// inside a case body walks past this frame to the enclosing loop.
980fn emit_switch(
981    emitter: &mut FunctionEmitter,
982    ctx: &CodegenCtx,
983    discriminant: crate::ExprId,
984    discriminant_ty: &Type,
985    cases: &[TypedSwitchCase],
986    default: Option<StmtId>,
987) -> Result<(), crate::compiler_error::CompilerFailure> {
988    // Stash discriminant in an anonymous local; numeric enums unboxed to f64
989    // so per-case tests use f64.eq instead of a structural-equality call.
990    emit_expr(emitter, ctx, discriminant)?;
991    let disc_val = switch_disc_local_type(ctx, discriminant_ty)?;
992    if matches!(discriminant_ty.peel(), Type::NumberEnum { .. }) {
993        let boxed_idx = ctx.symbols.boxed_number_type_idx().ok_or_else(|| {
994            crate::codegen::internal_failure("boxed_number type registered with intrinsics")
995        })?;
996        emitter.instruction(Instruction::StructGet {
997            struct_type_index: boxed_idx,
998            field_index: 1,
999        });
1000    }
1001    let disc_local = emitter.add_anonymous_local(disc_val)?;
1002    emitter.instruction(Instruction::LocalSet(disc_local));
1003
1004    emitter.emit_switch_open();
1005    // Open $default first (outermost), then $case_{N-1} … $case_0 (innermost),
1006    // so the dispatcher's br_if(source_idx) directly addresses each case block.
1007    if default.is_some() {
1008        emitter.emit_block(BlockType::Empty);
1009    }
1010    for _ in 0..cases.len() {
1011        emitter.emit_block(BlockType::Empty);
1012    }
1013
1014    // Dispatcher inside $case_0: one br_if per case value, then br to $default (or $end).
1015    for (source_idx, case) in cases.iter().enumerate() {
1016        emitter.record_span(case.span);
1017        for value in &case.values {
1018            emit_case_comparison(emitter, ctx, disc_local, discriminant_ty, value)?;
1019            emitter.instruction(Instruction::BrIf(crate::codegen::wasm_u32(source_idx)?));
1020        }
1021    }
1022    // Both $default and $end sit at depth cases.len() from inside the dispatcher.
1023    emitter.instruction(Instruction::Br(crate::codegen::wasm_u32(cases.len())?));
1024
1025    // Close each $case_i and emit its body. The trailing br $end is dead when
1026    // the body terminates via break/return, but Wasm's stack-polymorphic typing
1027    // validates unreachable code, so it's harmless.
1028    for case in cases {
1029        emitter.emit_end();
1030        emitter.push_scope();
1031        emit_statement(emitter, ctx, case.body)?;
1032        emitter.pop_scope()?;
1033        let end_label = emitter.break_label()?;
1034        emitter.instruction(Instruction::Br(end_label));
1035    }
1036
1037    if let Some(default_body) = default {
1038        emitter.emit_end();
1039        emitter.push_scope();
1040        emit_statement(emitter, ctx, default_body)?;
1041        emitter.pop_scope()?;
1042        // Falls through to `$end` naturally — no explicit `br`.
1043    }
1044
1045    emitter.emit_switch_close();
1046    Ok(())
1047}
1048
1049/// Numeric-enum discriminants are unboxed to `f64` here so per-case tests use `f64.eq`
1050/// instead of a structural-equality call on every comparison.
1051fn switch_disc_local_type(
1052    ctx: &CodegenCtx,
1053    ty: &Type,
1054) -> Result<ValType, crate::compiler_error::CompilerFailure> {
1055    Ok(if matches!(ty.peel(), Type::NumberEnum { .. }) {
1056        ValType::F64
1057    } else {
1058        ctx.symbols.value_type(ty)?
1059    })
1060}
1061
1062/// Internal `if/else` uses `BlockType::Result(ValType::I32)` so the i32 result
1063/// flows out without adding a `br_if`-visible block depth.
1064fn emit_case_comparison(
1065    emitter: &mut FunctionEmitter,
1066    ctx: &CodegenCtx,
1067    disc_local: u32,
1068    disc_ty: &Type,
1069    value: &TypedSwitchValue,
1070) -> Result<(), crate::compiler_error::CompilerFailure> {
1071    let disc_val = switch_disc_local_type(ctx, disc_ty)?;
1072    if let Some(case_ty) = switch_case_primitive_type(value)
1073        && matches!(disc_val, ValType::Ref(_))
1074        && disc_val != ctx.symbols.value_type(&case_ty)?
1075    {
1076        emit_boxed_case_comparison(emitter, ctx, disc_local, &case_ty, value)?;
1077        return Ok(());
1078    }
1079    match value {
1080        TypedSwitchValue::Expr { comparison, .. } => {
1081            emit_expr(emitter, ctx, *comparison)?;
1082        }
1083        TypedSwitchValue::Undefined { .. } => {
1084            emitter.instruction(Instruction::LocalGet(disc_local));
1085            super::expr::emit_is_undefined(emitter, ctx)?;
1086        }
1087        TypedSwitchValue::Null { .. } => {
1088            // case null: accepted only when discriminant can hold null (nullable ref)
1089            emitter.instruction(Instruction::LocalGet(disc_local));
1090            emitter.instruction(Instruction::RefIsNull);
1091        }
1092        TypedSwitchValue::Number { value, .. } => {
1093            emitter.instruction(Instruction::LocalGet(disc_local));
1094            emitter.instruction(Instruction::F64Const((*value).into()));
1095            emitter.instruction(Instruction::F64Eq);
1096        }
1097        TypedSwitchValue::Boolean { value, .. } => {
1098            emitter.instruction(Instruction::LocalGet(disc_local));
1099            emitter.instruction(Instruction::I32Const(if *value { 1 } else { 0 }));
1100            emitter.instruction(Instruction::I32Eq);
1101        }
1102        TypedSwitchValue::String { value, .. } => {
1103            emit_string_compare(emitter, ctx, disc_local, disc_val, value)?;
1104        }
1105        TypedSwitchValue::Enum { value: payload, .. } => match payload {
1106            EnumVariantPayload::Number(n) => {
1107                emitter.instruction(Instruction::LocalGet(disc_local));
1108                emitter.instruction(Instruction::F64Const((*n).into()));
1109                emitter.instruction(Instruction::F64Eq);
1110            }
1111            EnumVariantPayload::String(s) => {
1112                emit_string_compare(emitter, ctx, disc_local, disc_val, s)?;
1113            }
1114        },
1115    };
1116    Ok(())
1117}
1118
1119/// A union discriminant may carry a different primitive kind or null. Test
1120/// its boxed shape before unboxing; a nonmatching kind simply skips this case.
1121fn emit_boxed_case_comparison(
1122    emitter: &mut FunctionEmitter,
1123    ctx: &CodegenCtx,
1124    disc_local: u32,
1125    case_ty: &Type,
1126    value: &TypedSwitchValue,
1127) -> Result<(), crate::compiler_error::CompilerFailure> {
1128    let heap_index = match case_ty {
1129        Type::Number => ctx.symbols.boxed_number_type_idx(),
1130        Type::Boolean => ctx.symbols.boxed_boolean_type_idx(),
1131        Type::String => ctx.symbols.string_type_idx(),
1132        _ => {
1133            return Err(crate::codegen::internal_failure(
1134                "switch case primitive classified above",
1135            ));
1136        }
1137    }
1138    .ok_or_else(|| {
1139        crate::codegen::internal_failure("primitive types registered before switch emission")
1140    })?;
1141    emitter.instruction(Instruction::LocalGet(disc_local));
1142    emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(heap_index)));
1143    emitter.emit_if(BlockType::Result(ValType::I32));
1144    emitter.instruction(Instruction::LocalGet(disc_local));
1145    cast::emit_cast_to(emitter, ctx, case_ty)?;
1146    let unboxed = emitter.add_anonymous_local(ctx.symbols.value_type(case_ty)?)?;
1147    emitter.instruction(Instruction::LocalSet(unboxed));
1148    emit_case_comparison(emitter, ctx, unboxed, case_ty, value)?;
1149    emitter.emit_else();
1150    emitter.instruction(Instruction::I32Const(0));
1151    emitter.emit_end();
1152
1153    Ok(())
1154}
1155
1156fn switch_case_primitive_type(value: &TypedSwitchValue) -> Option<Type> {
1157    match value {
1158        TypedSwitchValue::Number { .. }
1159        | TypedSwitchValue::Enum {
1160            value: EnumVariantPayload::Number(_),
1161            ..
1162        } => Some(Type::Number),
1163        TypedSwitchValue::String { .. }
1164        | TypedSwitchValue::Enum {
1165            value: EnumVariantPayload::String(_),
1166            ..
1167        } => Some(Type::String),
1168        TypedSwitchValue::Boolean { .. } => Some(Type::Boolean),
1169        TypedSwitchValue::Null { .. }
1170        | TypedSwitchValue::Undefined { .. }
1171        | TypedSwitchValue::Expr { .. } => None,
1172    }
1173}
1174
1175/// Wraps in a `ref.is_null` guard when discriminant is nullable so a null
1176/// discriminant compares as not-equal. The `if/else` uses explicit i32 result
1177/// so no extra block depth is added to the dispatcher.
1178fn emit_string_compare(
1179    emitter: &mut FunctionEmitter,
1180    ctx: &CodegenCtx,
1181    disc_local: u32,
1182    disc_val: ValType,
1183    literal: &str,
1184) -> Result<(), crate::compiler_error::CompilerFailure> {
1185    let string_eq_idx = ctx.symbols.prelude_func_idx("string_eq").ok_or_else(|| {
1186        crate::codegen::internal_failure("string_eq is not imported from the prelude")
1187    })?;
1188
1189    let disc_is_nullable = matches!(
1190        disc_val,
1191        ValType::Ref(RefType {
1192            nullable: true,
1193            heap_type: HeapType::Concrete(_),
1194        })
1195    );
1196    if disc_is_nullable {
1197        emitter.instruction(Instruction::LocalGet(disc_local));
1198        emitter.instruction(Instruction::RefIsNull);
1199        emitter.emit_if(BlockType::Result(ValType::I32));
1200        emitter.instruction(Instruction::I32Const(0));
1201        emitter.emit_else();
1202        emitter.instruction(Instruction::LocalGet(disc_local));
1203        emitter.instruction(Instruction::RefAsNonNull);
1204        super::emit_const_string_by_text(emitter, ctx, literal)?;
1205        emitter.instruction(Instruction::Call(string_eq_idx));
1206        emitter.emit_end();
1207    } else {
1208        emitter.instruction(Instruction::LocalGet(disc_local));
1209        super::emit_const_string_by_text(emitter, ctx, literal)?;
1210        emitter.instruction(Instruction::Call(string_eq_idx));
1211    }
1212    Ok(())
1213}