Skip to main content

vm/compiler/
codegen.rs

1use std::collections::HashMap;
2
3use crate::assembler::Assembler;
4use crate::builtins::BuiltinFunction;
5use crate::{
6    CallableKind, CallablePrototype, CallableTarget, ExportedCallable, FunctionRegion, Program,
7    RootCallableBinding, ScriptFunction, TypeMap, Value, ValueType,
8};
9
10use super::ir::{
11    ClosureExpr, Expr, FunctionDecl, FunctionImpl, LocalSlot, MatchPattern, MatchTypePattern, Stmt,
12    StructDecl, TypeSchema,
13};
14use super::{CompileError, TypingMode, typing};
15
16pub struct Compiler {
17    assembler: Assembler,
18    next_label_id: u32,
19    loop_stack: Vec<LoopContext>,
20    function_impls: HashMap<u16, FunctionImpl>,
21    function_decls: HashMap<u16, FunctionDecl>,
22    struct_schemas: HashMap<String, StructDecl>,
23    host_import_return_types: HashMap<u16, typing::BoundType>,
24    host_import_signatures: HashMap<u16, typing::HostCallableSignature>,
25    call_index_remap: HashMap<u16, u16>,
26
27    callable_bindings: HashMap<LocalSlot, CallableBinding>,
28    enable_local_move_semantics: bool,
29    typing_mode: TypingMode,
30    type_state: typing::LocalTypeState,
31    type_map: TypeMap,
32    root_local_count: usize,
33    frame_local_count: usize,
34    function_slots: HashMap<u16, LocalSlot>,
35    specialized_function_slots: Vec<(u16, Vec<TypeSchema>, LocalSlot)>,
36    function_prototype_ids: HashMap<u16, u32>,
37    script_functions: Vec<ScriptFunction>,
38    callable_prototypes: Vec<CallablePrototype>,
39    function_regions: Vec<FunctionRegion>,
40    root_callable_bindings: Vec<RootCallableBinding>,
41    pending_closures: Vec<(u32, ClosureExpr)>,
42    callable_prototype_bindings: HashMap<LocalSlot, u32>,
43    closure_param_hints: HashMap<u32, Vec<(typing::BoundType, Option<TypeSchema>)>>,
44}
45
46struct LoopContext {
47    continue_label: String,
48    break_label: String,
49}
50
51#[derive(Clone)]
52enum CallableBinding {
53    Closure(ClosureExpr),
54    Function(u16),
55}
56
57impl Default for Compiler {
58    fn default() -> Self {
59        Self::new()
60    }
61}
62
63impl Compiler {
64    pub fn new() -> Self {
65        Self {
66            assembler: Assembler::new(),
67            next_label_id: 0,
68            loop_stack: Vec::new(),
69            function_impls: HashMap::new(),
70            function_decls: HashMap::new(),
71            struct_schemas: HashMap::new(),
72            host_import_return_types: HashMap::new(),
73            host_import_signatures: HashMap::new(),
74            call_index_remap: HashMap::new(),
75
76            callable_bindings: HashMap::new(),
77            enable_local_move_semantics: false,
78            typing_mode: TypingMode::DynamicHints,
79            type_state: typing::LocalTypeState::default(),
80            type_map: TypeMap::default(),
81            root_local_count: 0,
82            frame_local_count: 0,
83            function_slots: HashMap::new(),
84            specialized_function_slots: Vec::new(),
85            function_prototype_ids: HashMap::new(),
86            script_functions: Vec::new(),
87            callable_prototypes: Vec::new(),
88            function_regions: Vec::new(),
89            root_callable_bindings: Vec::new(),
90            pending_closures: Vec::new(),
91            callable_prototype_bindings: HashMap::new(),
92            closure_param_hints: HashMap::new(),
93        }
94    }
95
96    pub fn set_source(&mut self, source: String) {
97        self.assembler.set_source(source);
98    }
99
100    pub fn add_function_debug(&mut self, func: &FunctionDecl) {
101        self.assembler
102            .add_function(func.name.clone(), func.args.clone());
103    }
104
105    pub fn add_local_debug(
106        &mut self,
107        name: String,
108        index: LocalSlot,
109        declared_line: Option<u32>,
110        last_line: Option<u32>,
111    ) -> Result<(), CompileError> {
112        self.assembler.add_local_with_range(
113            name,
114            local_slot_operand(index)?,
115            declared_line,
116            last_line,
117        );
118        Ok(())
119    }
120
121    pub fn set_root_local_count(&mut self, root_local_count: usize) {
122        self.root_local_count = root_local_count;
123    }
124
125    pub fn set_function_impls(&mut self, function_impls: HashMap<u16, FunctionImpl>) {
126        self.function_impls = function_impls;
127    }
128
129    pub fn set_function_decls(&mut self, function_decls: HashMap<u16, FunctionDecl>) {
130        self.function_decls = function_decls;
131    }
132
133    pub fn set_struct_schemas(&mut self, struct_schemas: HashMap<String, StructDecl>) {
134        self.struct_schemas = struct_schemas;
135    }
136
137    pub(crate) fn set_host_import_return_types(
138        &mut self,
139        host_import_return_types: HashMap<u16, typing::BoundType>,
140    ) {
141        self.host_import_return_types = host_import_return_types;
142    }
143
144    pub(crate) fn set_host_import_signatures(
145        &mut self,
146        host_import_signatures: HashMap<u16, typing::HostCallableSignature>,
147    ) {
148        self.host_import_signatures = host_import_signatures;
149    }
150
151    pub fn set_call_index_remap(&mut self, call_index_remap: HashMap<u16, u16>) {
152        self.call_index_remap = call_index_remap;
153    }
154
155    pub fn set_enable_local_move_semantics(&mut self, enable_local_move_semantics: bool) {
156        self.enable_local_move_semantics = enable_local_move_semantics;
157    }
158
159    pub(crate) fn set_typing_mode(&mut self, typing_mode: TypingMode) {
160        self.typing_mode = typing_mode;
161    }
162
163    pub(crate) fn set_type_inference(&mut self, type_info: typing::TypeInferenceResult) {
164        self.type_map.local_types = type_info.local_types;
165        self.type_map.local_schemas = type_info.local_schemas;
166        self.type_map.callable_slots = type_info.callable_slots;
167        self.type_map.optional_slots = type_info.optional_slots;
168    }
169
170    pub fn compile_program(mut self, stmts: &[Stmt]) -> Result<Program, CompileError> {
171        let named_functions = self.prepare_named_callables()?;
172        self.compile_stmts(stmts)?;
173        self.assembler.ret();
174        let root_end = self.assembler.position();
175        if root_end > 0 {
176            self.function_regions.push(FunctionRegion {
177                start_ip: 0,
178                end_ip: root_end,
179                prototype_id: None,
180            });
181        }
182
183        for function_index in named_functions {
184            self.compile_named_function_body(function_index)?;
185        }
186        let mut closure_index = 0usize;
187        while closure_index < self.pending_closures.len() {
188            let (prototype_id, closure) = self.pending_closures[closure_index].clone();
189            self.compile_closure_body(prototype_id, &closure)?;
190            closure_index += 1;
191        }
192        for prototype in &mut self.callable_prototypes {
193            prototype.frame_local_count = self.frame_local_count;
194        }
195        let mut exported_callables = self
196            .function_decls
197            .values()
198            .filter(|decl| decl.exported)
199            .filter_map(|decl| {
200                self.function_slots
201                    .get(&decl.index)
202                    .copied()
203                    .map(|local_slot| ExportedCallable {
204                        name: decl.name.clone(),
205                        local_slot,
206                    })
207            })
208            .collect::<Vec<_>>();
209        exported_callables.sort_unstable_by(|lhs, rhs| lhs.name.cmp(&rhs.name));
210
211        let mut program = self
212            .assembler
213            .finish_program()
214            .map_err(CompileError::Assembler)?;
215        self.type_map.strict_types = self.typing_mode.is_strict();
216        program.type_map = Some(self.type_map);
217        program.local_count = self.frame_local_count;
218        program.script_functions = self.script_functions;
219        program.callable_prototypes = self.callable_prototypes;
220        program.function_regions = self.function_regions;
221        program.root_callable_bindings = self.root_callable_bindings;
222        program.exported_callables = exported_callables;
223        Ok(program)
224    }
225
226    fn seed_frame_type_state_from_type_map(&mut self) {
227        for index in 0..self.frame_local_count.min(usize::from(u16::MAX) + 1) {
228            let Ok(slot) = LocalSlot::try_from(index) else {
229                break;
230            };
231            let schema = self.type_map.local_schemas.get(index).cloned().flatten();
232            let ty = schema
233                .as_ref()
234                .map(typing::bound_type_from_schema)
235                .unwrap_or_else(|| {
236                    self.type_map
237                        .local_types
238                        .get(index)
239                        .copied()
240                        .map(typing::BoundType::from)
241                        .unwrap_or(typing::BoundType::Unknown)
242                });
243            self.type_state
244                .set_with_schema_origin(slot, ty, schema, false);
245        }
246    }
247
248    fn prepare_named_callables(&mut self) -> Result<Vec<u16>, CompileError> {
249        let mut indices = self.function_impls.keys().copied().collect::<Vec<_>>();
250        indices.sort_unstable();
251        self.frame_local_count = self
252            .root_local_count
253            .checked_add(indices.len())
254            .ok_or(CompileError::LocalSlotOverflow(LocalSlot::MAX))?;
255        if self.frame_local_count > usize::from(u8::MAX) + 1 {
256            return Err(CompileError::LocalSlotOverflow(LocalSlot::MAX));
257        }
258
259        for (position, function_index) in indices.iter().copied().enumerate() {
260            let hidden_slot = LocalSlot::try_from(self.root_local_count + position)
261                .map_err(|_| CompileError::LocalSlotOverflow(LocalSlot::MAX))?;
262            let prototype_id = self.callable_prototypes.len() as u32;
263            let script_function_id = self.script_functions.len() as u32 + position as u32;
264            let function_impl = self
265                .function_impls
266                .get(&function_index)
267                .expect("function index came from implementation map");
268            let decl = self.function_decls.get(&function_index);
269            self.function_slots.insert(function_index, hidden_slot);
270            self.function_prototype_ids
271                .insert(function_index, prototype_id);
272            self.callable_prototypes.push(CallablePrototype {
273                kind: if function_impl.capture_copies.is_empty() {
274                    CallableKind::FunctionItem
275                } else {
276                    CallableKind::Closure
277                },
278                target: CallableTarget::ScriptFunction(script_function_id),
279                arity: function_impl.param_slots.len() as u8,
280                frame_local_count: self.frame_local_count,
281                parameter_slots: function_impl.param_slots.clone(),
282                capture_source_slots: function_impl
283                    .capture_copies
284                    .iter()
285                    .map(|(source, _)| *source)
286                    .collect(),
287                capture_slots: function_impl
288                    .capture_copies
289                    .iter()
290                    .map(|(_, target)| *target)
291                    .collect(),
292                capture_modes: function_impl
293                    .capture_copies
294                    .iter()
295                    .map(|(_, target)| {
296                        super::lifetime::function_capture_binding_mode(function_impl, *target)
297                    })
298                    .collect(),
299                self_slot: Some(hidden_slot),
300                schema: decl.map(|decl| TypeSchema::Callable {
301                    params: decl
302                        .arg_schemas
303                        .iter()
304                        .map(|schema| schema.clone().unwrap_or(TypeSchema::Unknown))
305                        .collect(),
306                    result: Box::new(decl.return_schema.clone().unwrap_or(TypeSchema::Unknown)),
307                }),
308            });
309            if function_impl.capture_copies.is_empty() {
310                self.root_callable_bindings.push(RootCallableBinding {
311                    local_slot: hidden_slot,
312                    prototype_id,
313                });
314            }
315        }
316        Ok(indices)
317    }
318
319    fn compile_named_function_body(&mut self, function_index: u16) -> Result<(), CompileError> {
320        let function_impl = self
321            .function_impls
322            .get(&function_index)
323            .cloned()
324            .expect("prepared function implementation must exist");
325        let prototype_id = self.function_prototype_ids[&function_index];
326        let entry_ip = self.assembler.position();
327        let callable_snapshot = self.callable_bindings.clone();
328        let type_snapshot = self.type_state.clone();
329        let loop_snapshot = std::mem::take(&mut self.loop_stack);
330        self.seed_frame_type_state_from_type_map();
331        if let Some(decl) = self.function_decls.get(&function_index) {
332            for (slot, schema) in function_impl.param_slots.iter().zip(&decl.arg_schemas) {
333                match schema {
334                    Some(schema) => self.type_state.set_with_schema_origin(
335                        *slot,
336                        typing::bound_type_from_schema(schema),
337                        Some(schema.clone()),
338                        true,
339                    ),
340                    None => self.type_state.set_with_schema_origin(
341                        *slot,
342                        typing::BoundType::Unknown,
343                        None,
344                        false,
345                    ),
346                }
347            }
348        }
349        self.compile_stmts(&function_impl.body_stmts)?;
350        self.compile_expr(&function_impl.body_expr)?;
351        self.assembler.ret();
352        self.loop_stack = loop_snapshot;
353        self.callable_bindings = callable_snapshot;
354        self.type_state = type_snapshot;
355        let end_ip = self.assembler.position();
356        self.script_functions
357            .push(ScriptFunction { entry_ip, end_ip });
358        self.function_regions.push(FunctionRegion {
359            start_ip: entry_ip,
360            end_ip,
361            prototype_id: Some(prototype_id),
362        });
363        Ok(())
364    }
365
366    fn compile_closure_body(
367        &mut self,
368        prototype_id: u32,
369        closure: &ClosureExpr,
370    ) -> Result<(), CompileError> {
371        let entry_ip = self.assembler.position();
372        let callable_snapshot = self.callable_bindings.clone();
373        let type_snapshot = self.type_state.clone();
374        let loop_snapshot = std::mem::take(&mut self.loop_stack);
375        self.seed_frame_type_state_from_type_map();
376        if let Some(hints) = self.closure_param_hints.get(&prototype_id).cloned() {
377            for (slot, (ty, schema)) in closure.param_slots.iter().zip(hints) {
378                self.type_state
379                    .set_with_schema_origin(*slot, ty, schema, false);
380            }
381        }
382        self.compile_expr(&closure.body)?;
383        self.assembler.ret();
384        self.loop_stack = loop_snapshot;
385        self.callable_bindings = callable_snapshot;
386        self.type_state = type_snapshot;
387        let end_ip = self.assembler.position();
388        let function_id = self.script_functions.len() as u32;
389        self.script_functions
390            .push(ScriptFunction { entry_ip, end_ip });
391        self.callable_prototypes[prototype_id as usize].target =
392            CallableTarget::ScriptFunction(function_id);
393        self.function_regions.push(FunctionRegion {
394            start_ip: entry_ip,
395            end_ip,
396            prototype_id: Some(prototype_id),
397        });
398        Ok(())
399    }
400
401    fn compile_stmts(&mut self, stmts: &[Stmt]) -> Result<(), CompileError> {
402        for stmt in stmts {
403            self.compile_stmt(stmt)?;
404        }
405        Ok(())
406    }
407
408    fn compile_stmt(&mut self, stmt: &Stmt) -> Result<(), CompileError> {
409        match stmt {
410            Stmt::Noop { line } => {
411                self.assembler.mark_line(*line);
412            }
413            Stmt::Let {
414                index,
415                declared_schema,
416                expr,
417                line,
418            } => {
419                self.assembler.mark_line(*line);
420                self.assign_expr_to_slot(*index, declared_schema.as_ref(), expr)?;
421            }
422            Stmt::Assign {
423                index, expr, line, ..
424            } => {
425                self.assembler.mark_line(*line);
426                self.assign_expr_to_slot(*index, None, expr)?;
427            }
428            Stmt::ClosureLet { line, .. } => {
429                self.assembler.mark_line(*line);
430            }
431            Stmt::FuncDecl {
432                index,
433                has_impl,
434                line,
435                ..
436            } => {
437                self.assembler.mark_line(*line);
438                if *has_impl {
439                    self.emit_named_callable_binding(*index)?;
440                }
441            }
442            Stmt::Expr { expr, line } => {
443                self.assembler.mark_line(*line);
444                self.compile_expr(expr)?;
445            }
446            Stmt::IfElse {
447                condition,
448                then_branch,
449                else_branch,
450                line,
451            } => {
452                self.assembler.mark_line(*line);
453                if matches!(condition, Expr::Bool(true)) {
454                    self.compile_stmts(then_branch)?;
455                    return Ok(());
456                }
457                if matches!(condition, Expr::Bool(false)) {
458                    self.compile_stmts(else_branch)?;
459                    return Ok(());
460                }
461                let callable_snapshot = self.callable_bindings.clone();
462                let type_state_snapshot = self.type_state.clone();
463                let then_refined_type_state =
464                    typing::refine_state_for_condition(&type_state_snapshot, condition, true);
465                let else_refined_type_state =
466                    typing::refine_state_for_condition(&type_state_snapshot, condition, false);
467                let else_label = self.fresh_label("else");
468                let end_label = self.fresh_label("endif");
469                self.compile_scalar_expr(condition)?;
470                self.assembler.brfalse_label(&else_label);
471                self.type_state = then_refined_type_state;
472                self.compile_stmts(then_branch)?;
473                let then_type_state = self.type_state.clone();
474                self.assembler.br_label(&end_label);
475                self.assembler
476                    .label(&else_label)
477                    .map_err(CompileError::Assembler)?;
478                self.callable_bindings = callable_snapshot.clone();
479                self.type_state = else_refined_type_state;
480                self.compile_stmts(else_branch)?;
481                let else_type_state = self.type_state.clone();
482                self.assembler
483                    .label(&end_label)
484                    .map_err(CompileError::Assembler)?;
485                self.callable_bindings = callable_snapshot;
486                self.type_state
487                    .merge_from_branches(&then_type_state, &else_type_state);
488            }
489            Stmt::For {
490                init,
491                condition,
492                post,
493                body,
494                line,
495            } => {
496                let callable_snapshot = self.callable_bindings.clone();
497                self.assembler.mark_line(*line);
498                self.compile_stmt(init)?;
499                let loop_entry_type_state = self.type_state.clone();
500                let stabilized_loop_type_state =
501                    self.stabilize_loop_type_state(&loop_entry_type_state, |iterated| {
502                        let _ = typing::infer_expr_type_with_function_impls_and_imports(
503                            condition,
504                            iterated,
505                            &self.function_impls,
506                            &self.function_decls,
507                            &self.struct_schemas,
508                            &self.host_import_return_types,
509                            &self.host_import_signatures,
510                        );
511                        typing::apply_stmts_with_function_impls_and_imports(
512                            body,
513                            iterated,
514                            &self.function_impls,
515                            &self.function_decls,
516                            &self.struct_schemas,
517                            &self.host_import_return_types,
518                            &self.host_import_signatures,
519                        );
520                        typing::apply_stmts_with_function_impls_and_imports(
521                            std::slice::from_ref(post),
522                            iterated,
523                            &self.function_impls,
524                            &self.function_decls,
525                            &self.struct_schemas,
526                            &self.host_import_return_types,
527                            &self.host_import_signatures,
528                        );
529                    });
530                self.type_state = stabilized_loop_type_state;
531                let start_label = self.fresh_label("for_start");
532                let continue_label = self.fresh_label("for_continue");
533                let end_label = self.fresh_label("for_end");
534                self.assembler
535                    .label(&start_label)
536                    .map_err(CompileError::Assembler)?;
537                self.compile_scalar_expr(condition)?;
538                self.assembler.brfalse_label(&end_label);
539                self.loop_stack.push(LoopContext {
540                    continue_label: continue_label.clone(),
541                    break_label: end_label.clone(),
542                });
543                self.compile_stmts(body)?;
544                self.loop_stack.pop();
545                self.assembler
546                    .label(&continue_label)
547                    .map_err(CompileError::Assembler)?;
548                self.compile_stmt(post)?;
549                self.assembler.br_label(&start_label);
550                self.assembler
551                    .label(&end_label)
552                    .map_err(CompileError::Assembler)?;
553                self.callable_bindings = callable_snapshot;
554                self.type_state = self
555                    .simulate_stmt_type_state(std::slice::from_ref(stmt), &loop_entry_type_state);
556            }
557            Stmt::While {
558                condition,
559                body,
560                line,
561            } => {
562                let callable_snapshot = self.callable_bindings.clone();
563                let loop_entry_type_state = self.type_state.clone();
564                let stabilized_loop_type_state =
565                    self.stabilize_loop_type_state(&loop_entry_type_state, |iterated| {
566                        let _ = typing::infer_expr_type_with_function_impls_and_imports(
567                            condition,
568                            iterated,
569                            &self.function_impls,
570                            &self.function_decls,
571                            &self.struct_schemas,
572                            &self.host_import_return_types,
573                            &self.host_import_signatures,
574                        );
575                        typing::apply_stmts_with_function_impls_and_imports(
576                            body,
577                            iterated,
578                            &self.function_impls,
579                            &self.function_decls,
580                            &self.struct_schemas,
581                            &self.host_import_return_types,
582                            &self.host_import_signatures,
583                        );
584                    });
585                self.assembler.mark_line(*line);
586                self.type_state = stabilized_loop_type_state;
587                let start_label = self.fresh_label("while_start");
588                let end_label = self.fresh_label("while_end");
589                self.assembler
590                    .label(&start_label)
591                    .map_err(CompileError::Assembler)?;
592                self.compile_scalar_expr(condition)?;
593                self.assembler.brfalse_label(&end_label);
594                self.loop_stack.push(LoopContext {
595                    continue_label: start_label.clone(),
596                    break_label: end_label.clone(),
597                });
598                self.compile_stmts(body)?;
599                self.loop_stack.pop();
600                self.assembler.br_label(&start_label);
601                self.assembler
602                    .label(&end_label)
603                    .map_err(CompileError::Assembler)?;
604                self.callable_bindings = callable_snapshot;
605                self.type_state = self
606                    .simulate_stmt_type_state(std::slice::from_ref(stmt), &loop_entry_type_state);
607            }
608            Stmt::Break { line } => {
609                self.assembler.mark_line(*line);
610                let loop_ctx = self
611                    .loop_stack
612                    .last()
613                    .ok_or(CompileError::BreakOutsideLoop)?;
614                self.assembler.br_label(&loop_ctx.break_label);
615            }
616            Stmt::Continue { line } => {
617                self.assembler.mark_line(*line);
618                let loop_ctx = self
619                    .loop_stack
620                    .last()
621                    .ok_or(CompileError::ContinueOutsideLoop)?;
622                self.assembler.br_label(&loop_ctx.continue_label);
623            }
624            Stmt::Drop { index, line } => {
625                self.assembler.mark_line(*line);
626                self.assign_expr_to_slot(*index, None, &Expr::Null)?;
627            }
628        }
629        Ok(())
630    }
631
632    fn compile_expr(&mut self, expr: &Expr) -> Result<(), CompileError> {
633        match expr {
634            Expr::Null => {
635                self.assembler.push_const(Value::Null);
636            }
637            Expr::Int(value) => {
638                self.assembler.push_const(Value::Int(*value));
639            }
640            Expr::Float(value) => {
641                self.assembler.push_const(Value::Float(*value));
642            }
643            Expr::Bool(value) => {
644                self.assembler.push_const(Value::Bool(*value));
645            }
646            Expr::String(value) => {
647                self.assembler.push_const(Value::string(value.clone()));
648            }
649            Expr::Bytes(value) => {
650                self.assembler.push_const(Value::bytes(value.clone()));
651            }
652            Expr::OptionalGet {
653                container,
654                key,
655                container_slot,
656                key_slot,
657            } => {
658                self.compile_optional_get_expr(container, key, *container_slot, *key_slot)?;
659            }
660            Expr::OptionUnwrapOr {
661                value,
662                value_slot,
663                fallback,
664            } => {
665                self.compile_option_unwrap_or_expr(value, *value_slot, fallback)?;
666            }
667            Expr::FunctionRef(index, type_args) => {
668                let slot = self.ensure_function_value_slot(*index, type_args)?;
669                self.emit_copy_ldloc(slot)?;
670            }
671            Expr::Call(index, _, args) => {
672                self.compile_function_call(*index, args)?;
673            }
674            Expr::Closure(closure) => {
675                let _ = self.emit_closure_callable(closure)?;
676            }
677            Expr::ClosureCall(closure, args) => {
678                let prototype_id = self.emit_closure_callable(closure)?;
679                self.record_closure_param_hints(prototype_id, args);
680                self.compile_callvalue_args(args)?;
681            }
682            Expr::LocalCall(index, _, args) => {
683                if let Some(prototype_id) = self.callable_prototype_bindings.get(index).copied() {
684                    self.record_closure_param_hints(prototype_id, args);
685                }
686                self.emit_copy_ldloc(*index)?;
687                self.compile_callvalue_args(args)?;
688            }
689            Expr::Add(lhs, rhs) => {
690                let lhs_ty = self.value_type_of_expr(lhs);
691                let rhs_ty = self.value_type_of_expr(rhs);
692                if is_definitely_string_expr(lhs) {
693                    self.compile_scalar_expr(lhs)?;
694                    self.compile_string_concat_operand(rhs)?;
695                    self.record_operand_types(ValueType::String, ValueType::String);
696                    self.assembler.add();
697                    return Ok(());
698                }
699                if is_definitely_string_expr(rhs) {
700                    self.compile_string_concat_operand(lhs)?;
701                    self.compile_scalar_expr(rhs)?;
702                    self.record_operand_types(ValueType::String, ValueType::String);
703                    self.assembler.add();
704                    return Ok(());
705                }
706                self.compile_scalar_expr(lhs)?;
707                self.compile_scalar_expr(rhs)?;
708                self.record_operand_types(lhs_ty, rhs_ty);
709                self.assembler.add();
710            }
711            Expr::Sub(lhs, rhs) => {
712                let lhs_ty = self.value_type_of_expr(lhs);
713                let rhs_ty = self.value_type_of_expr(rhs);
714                self.compile_scalar_expr(lhs)?;
715                self.compile_scalar_expr(rhs)?;
716                self.record_operand_types(lhs_ty, rhs_ty);
717                self.assembler.sub();
718            }
719            Expr::Mul(lhs, rhs) => {
720                if let Expr::Int(value) = rhs.as_ref()
721                    && let Some(shift) = shift_amount_for_power_of_two(*value)
722                {
723                    self.compile_scalar_expr(lhs)?;
724                    self.assembler.push_const(Value::Int(shift as i64));
725                    self.assembler.shl();
726                } else if let Expr::Int(value) = lhs.as_ref()
727                    && let Some(shift) = shift_amount_for_power_of_two(*value)
728                {
729                    self.compile_scalar_expr(rhs)?;
730                    self.assembler.push_const(Value::Int(shift as i64));
731                    self.assembler.shl();
732                } else {
733                    let lhs_ty = self.value_type_of_expr(lhs);
734                    let rhs_ty = self.value_type_of_expr(rhs);
735                    self.compile_scalar_expr(lhs)?;
736                    self.compile_scalar_expr(rhs)?;
737                    self.record_operand_types(lhs_ty, rhs_ty);
738                    self.assembler.mul();
739                }
740            }
741            Expr::Div(lhs, rhs) => {
742                let lhs_ty = self.value_type_of_expr(lhs);
743                let rhs_ty = self.value_type_of_expr(rhs);
744                self.compile_scalar_expr(lhs)?;
745                self.compile_scalar_expr(rhs)?;
746                self.record_operand_types(lhs_ty, rhs_ty);
747                self.assembler.div();
748            }
749            Expr::Mod(lhs, rhs) => {
750                let lhs_ty = self.value_type_of_expr(lhs);
751                let rhs_ty = self.value_type_of_expr(rhs);
752                self.compile_scalar_expr(lhs)?;
753                self.compile_scalar_expr(rhs)?;
754                self.record_operand_types(lhs_ty, rhs_ty);
755                self.assembler.modulo();
756            }
757            Expr::Neg(inner) => {
758                let inner_ty = self.value_type_of_expr(inner);
759                self.compile_scalar_expr(inner)?;
760                self.record_unary_operand_type(inner_ty);
761                self.assembler.neg();
762            }
763            Expr::Not(inner) => {
764                self.compile_scalar_expr(inner)?;
765                self.assembler.not();
766            }
767            Expr::ToOwned(inner) => {
768                self.compile_scalar_expr(inner)?;
769            }
770            Expr::Borrow(inner) | Expr::BorrowMut(inner) => {
771                self.compile_scalar_expr(inner)?;
772            }
773            Expr::And(lhs, rhs) => {
774                self.compile_short_circuit_and(lhs, rhs)?;
775            }
776            Expr::Or(lhs, rhs) => {
777                self.compile_short_circuit_or(lhs, rhs)?;
778            }
779            Expr::Eq(lhs, rhs) => {
780                let lhs_ty = self.value_type_of_expr(lhs);
781                let rhs_ty = self.value_type_of_expr(rhs);
782                self.compile_scalar_expr(lhs)?;
783                self.compile_scalar_expr(rhs)?;
784                self.record_operand_types(lhs_ty, rhs_ty);
785                self.assembler.ceq();
786            }
787            Expr::Lt(lhs, rhs) => {
788                let lhs_ty = self.value_type_of_expr(lhs);
789                let rhs_ty = self.value_type_of_expr(rhs);
790                self.compile_scalar_expr(lhs)?;
791                self.compile_scalar_expr(rhs)?;
792                self.record_operand_types(lhs_ty, rhs_ty);
793                self.assembler.clt();
794            }
795            Expr::Gt(lhs, rhs) => {
796                let lhs_ty = self.value_type_of_expr(lhs);
797                let rhs_ty = self.value_type_of_expr(rhs);
798                self.compile_scalar_expr(lhs)?;
799                self.compile_scalar_expr(rhs)?;
800                self.record_operand_types(lhs_ty, rhs_ty);
801                self.assembler.cgt();
802            }
803            Expr::Var(index) => {
804                self.emit_copy_ldloc(*index)?;
805            }
806            Expr::MoveVar(index) => {
807                self.emit_move_ldloc(*index)?;
808                self.type_state.set(*index, typing::BoundType::Null);
809            }
810            Expr::MoveField { root, key } => {
811                self.emit_copy_ldloc(*root)?;
812                self.assembler.push_const(Value::string(key.clone()));
813                self.assembler.call(BuiltinFunction::Get.call_index(), 2);
814
815                self.emit_copy_ldloc(*root)?;
816                self.assembler.push_const(Value::string(key.clone()));
817                self.assembler.push_const(Value::Null);
818                self.assembler.call(BuiltinFunction::Set.call_index(), 3);
819                self.emit_stloc(*root)?;
820            }
821            Expr::MoveIndex { root, index } => {
822                self.emit_copy_ldloc(*root)?;
823                self.assembler.push_const(Value::Int(*index));
824                self.assembler.call(BuiltinFunction::Get.call_index(), 2);
825
826                self.emit_copy_ldloc(*root)?;
827                self.assembler.push_const(Value::Int(*index));
828                self.assembler.push_const(Value::Null);
829                self.assembler.call(BuiltinFunction::Set.call_index(), 3);
830                self.emit_stloc(*root)?;
831            }
832            Expr::IfElse {
833                condition,
834                then_expr,
835                else_expr,
836            } => {
837                let callable_snapshot = self.callable_bindings.clone();
838                let type_state_snapshot = self.type_state.clone();
839                self.compile_scalar_expr(condition)?;
840                let else_label = self.fresh_label("if_else");
841                let end_label = self.fresh_label("if_end");
842                self.assembler.brfalse_label(&else_label);
843                self.type_state =
844                    typing::refine_state_for_condition(&type_state_snapshot, condition, true);
845                self.compile_expr(then_expr)?;
846                let then_type_state = self.type_state.clone();
847                self.assembler.br_label(&end_label);
848                self.assembler
849                    .label(&else_label)
850                    .map_err(CompileError::Assembler)?;
851                self.callable_bindings = callable_snapshot.clone();
852                self.type_state =
853                    typing::refine_state_for_condition(&type_state_snapshot, condition, false);
854                self.compile_expr(else_expr)?;
855                let else_type_state = self.type_state.clone();
856                self.assembler
857                    .label(&end_label)
858                    .map_err(CompileError::Assembler)?;
859                self.callable_bindings = callable_snapshot;
860                self.type_state
861                    .merge_from_branches(&then_type_state, &else_type_state);
862            }
863            Expr::Match {
864                value_slot,
865                result_slot,
866                value,
867                arms,
868                default,
869            } => {
870                self.compile_scalar_expr(value)?;
871                self.emit_stloc(*value_slot)?;
872                let callable_snapshot = self.callable_bindings.clone();
873                let match_entry_type_state = self.type_state.clone();
874                let end_label = self.fresh_label("match_end");
875                let mut merged_type_state: Option<typing::LocalTypeState> = None;
876                for (pattern, arm_expr) in arms {
877                    let next_label = self.fresh_label("match_next");
878                    self.callable_bindings = callable_snapshot.clone();
879                    self.type_state = match_entry_type_state.clone();
880                    self.compile_match_pattern_condition(*value_slot, pattern)?;
881                    self.assembler.brfalse_label(&next_label);
882                    self.bind_match_pattern_slot(
883                        pattern,
884                        value,
885                        *value_slot,
886                        &match_entry_type_state,
887                    )?;
888                    self.compile_scalar_expr(arm_expr)?;
889                    self.emit_stloc(*result_slot)?;
890                    let arm_type_state = self.type_state.clone();
891                    merged_type_state = Some(match merged_type_state {
892                        Some(existing) => {
893                            let mut merged = typing::LocalTypeState::default();
894                            merged.merge_from_branches(&existing, &arm_type_state);
895                            merged
896                        }
897                        None => arm_type_state,
898                    });
899                    self.assembler.br_label(&end_label);
900                    self.assembler
901                        .label(&next_label)
902                        .map_err(CompileError::Assembler)?;
903                }
904                self.callable_bindings = callable_snapshot.clone();
905                self.type_state = match_entry_type_state.clone();
906                self.compile_scalar_expr(default)?;
907                self.emit_stloc(*result_slot)?;
908                let default_type_state = self.type_state.clone();
909                self.assembler
910                    .label(&end_label)
911                    .map_err(CompileError::Assembler)?;
912                self.callable_bindings = callable_snapshot;
913                self.type_state = if let Some(existing) = merged_type_state {
914                    let mut merged = typing::LocalTypeState::default();
915                    merged.merge_from_branches(&existing, &default_type_state);
916                    merged
917                } else {
918                    default_type_state
919                };
920                self.emit_copy_ldloc(*result_slot)?;
921            }
922            Expr::Block { stmts, expr } => {
923                self.compile_stmts(stmts)?;
924                self.compile_expr(expr)?;
925            }
926        }
927        Ok(())
928    }
929
930    fn compile_optional_get_expr(
931        &mut self,
932        container: &Expr,
933        key: &Expr,
934        container_slot: LocalSlot,
935        key_slot: LocalSlot,
936    ) -> Result<(), CompileError> {
937        self.compile_scalar_expr(container)?;
938        self.emit_stloc(container_slot)?;
939        self.compile_scalar_expr(key)?;
940        self.emit_stloc(key_slot)?;
941
942        let map_lookup = Expr::IfElse {
943            condition: Box::new(Expr::Call(
944                BuiltinFunction::Has.call_index(),
945                Vec::new(),
946                vec![Expr::Var(container_slot), Expr::Var(key_slot)],
947            )),
948            then_expr: Box::new(Expr::Call(
949                BuiltinFunction::Get.call_index(),
950                Vec::new(),
951                vec![Expr::Var(container_slot), Expr::Var(key_slot)],
952            )),
953            else_expr: Box::new(Expr::Null),
954        };
955        let index_lookup = Expr::IfElse {
956            condition: Box::new(Expr::Eq(
957                Box::new(Expr::Call(
958                    BuiltinFunction::TypeOf.call_index(),
959                    Vec::new(),
960                    vec![Expr::Var(key_slot)],
961                )),
962                Box::new(Expr::String("int".to_string())),
963            )),
964            then_expr: Box::new(Expr::IfElse {
965                condition: Box::new(Expr::Lt(
966                    Box::new(Expr::Var(key_slot)),
967                    Box::new(Expr::Int(0)),
968                )),
969                then_expr: Box::new(Expr::Null),
970                else_expr: Box::new(Expr::IfElse {
971                    condition: Box::new(Expr::Lt(
972                        Box::new(Expr::Var(key_slot)),
973                        Box::new(Expr::Call(
974                            BuiltinFunction::Len.call_index(),
975                            Vec::new(),
976                            vec![Expr::Var(container_slot)],
977                        )),
978                    )),
979                    then_expr: Box::new(Expr::Call(
980                        BuiltinFunction::Get.call_index(),
981                        Vec::new(),
982                        vec![Expr::Var(container_slot), Expr::Var(key_slot)],
983                    )),
984                    else_expr: Box::new(Expr::Null),
985                }),
986            }),
987            else_expr: Box::new(Expr::Null),
988        };
989        let lowered = Expr::IfElse {
990            condition: Box::new(Expr::Eq(
991                Box::new(Expr::Call(
992                    BuiltinFunction::TypeOf.call_index(),
993                    Vec::new(),
994                    vec![Expr::Var(container_slot)],
995                )),
996                Box::new(Expr::String("null".to_string())),
997            )),
998            then_expr: Box::new(Expr::Null),
999            else_expr: Box::new(Expr::IfElse {
1000                condition: Box::new(Expr::Eq(
1001                    Box::new(Expr::Call(
1002                        BuiltinFunction::TypeOf.call_index(),
1003                        Vec::new(),
1004                        vec![Expr::Var(container_slot)],
1005                    )),
1006                    Box::new(Expr::String("map".to_string())),
1007                )),
1008                then_expr: Box::new(map_lookup),
1009                else_expr: Box::new(Expr::IfElse {
1010                    condition: Box::new(Expr::Eq(
1011                        Box::new(Expr::Call(
1012                            BuiltinFunction::TypeOf.call_index(),
1013                            Vec::new(),
1014                            vec![Expr::Var(container_slot)],
1015                        )),
1016                        Box::new(Expr::String("array".to_string())),
1017                    )),
1018                    then_expr: Box::new(index_lookup.clone()),
1019                    else_expr: Box::new(Expr::IfElse {
1020                        condition: Box::new(Expr::Eq(
1021                            Box::new(Expr::Call(
1022                                BuiltinFunction::TypeOf.call_index(),
1023                                Vec::new(),
1024                                vec![Expr::Var(container_slot)],
1025                            )),
1026                            Box::new(Expr::String("string".to_string())),
1027                        )),
1028                        then_expr: Box::new(index_lookup),
1029                        else_expr: Box::new(Expr::Null),
1030                    }),
1031                }),
1032            }),
1033        };
1034
1035        self.compile_expr(&lowered)
1036    }
1037
1038    fn compile_option_unwrap_or_expr(
1039        &mut self,
1040        value: &Expr,
1041        value_slot: LocalSlot,
1042        fallback: &Expr,
1043    ) -> Result<(), CompileError> {
1044        self.compile_scalar_expr(value)?;
1045        self.emit_stloc(value_slot)?;
1046        let lowered = Expr::IfElse {
1047            condition: Box::new(Expr::Eq(
1048                Box::new(Expr::Call(
1049                    BuiltinFunction::TypeOf.call_index(),
1050                    Vec::new(),
1051                    vec![Expr::Var(value_slot)],
1052                )),
1053                Box::new(Expr::String("null".to_string())),
1054            )),
1055            then_expr: Box::new(fallback.clone()),
1056            else_expr: Box::new(Expr::Var(value_slot)),
1057        };
1058        self.compile_expr(&lowered)
1059    }
1060
1061    fn emit_named_callable_binding(&mut self, index: u16) -> Result<(), CompileError> {
1062        let Some(function_impl) = self.function_impls.get(&index).cloned() else {
1063            return Ok(());
1064        };
1065        if function_impl.capture_copies.is_empty() {
1066            return Ok(());
1067        }
1068        let prototype_id = *self
1069            .function_prototype_ids
1070            .get(&index)
1071            .ok_or(CompileError::CallableUsedAsValue)?;
1072        let slot = *self
1073            .function_slots
1074            .get(&index)
1075            .ok_or(CompileError::CallableUsedAsValue)?;
1076        self.emit_bind_callable(
1077            prototype_id,
1078            function_impl
1079                .capture_copies
1080                .iter()
1081                .map(|(source, _)| *source),
1082        )?;
1083        self.emit_stloc(slot)?;
1084        Ok(())
1085    }
1086
1087    fn callable_binding_from_expr(
1088        &mut self,
1089        expr: &Expr,
1090    ) -> Result<Option<CallableBinding>, CompileError> {
1091        match expr {
1092            Expr::Closure(closure) => Ok(Some(CallableBinding::Closure(closure.clone()))),
1093            Expr::FunctionRef(index, _) => Ok(Some(CallableBinding::Function(*index))),
1094            Expr::Var(index) => Ok(self.callable_bindings.get(index).cloned()),
1095            _ => Ok(None),
1096        }
1097    }
1098
1099    fn assign_expr_to_slot(
1100        &mut self,
1101        slot: LocalSlot,
1102        declared_schema: Option<&TypeSchema>,
1103        expr: &Expr,
1104    ) -> Result<(), CompileError> {
1105        if let Some(callable) = self.callable_binding_from_expr(expr)? {
1106            self.callable_bindings.insert(slot, callable.clone());
1107            match callable {
1108                CallableBinding::Closure(closure) => self.type_state.bind_closure(slot, &closure),
1109                CallableBinding::Function(index) => self.type_state.bind_function(slot, index),
1110            }
1111            if let Expr::Closure(closure) = expr {
1112                let prototype_id = self.emit_closure_callable_with_self(closure, Some(slot))?;
1113                self.callable_prototype_bindings.insert(slot, prototype_id);
1114            } else {
1115                if let Expr::Var(source) | Expr::MoveVar(source) = expr
1116                    && let Some(prototype_id) =
1117                        self.callable_prototype_bindings.get(source).copied()
1118                {
1119                    self.callable_prototype_bindings.insert(slot, prototype_id);
1120                }
1121                self.compile_expr(expr)?;
1122            }
1123            self.emit_stloc(slot)?;
1124            return Ok(());
1125        }
1126        let declared_binding = declared_schema.map(TypeSchema::split_optional).or_else(|| {
1127            self.type_state
1128                .has_declared_schema(slot)
1129                .then(|| {
1130                    (
1131                        self.type_state.schema(slot).cloned(),
1132                        self.type_state.is_optional(slot),
1133                    )
1134                })
1135                .and_then(|(schema, optional)| schema.map(|schema| (schema, optional)))
1136        });
1137        let slot_declared_schema = declared_binding.as_ref().map(|(schema, _)| schema.clone());
1138        let declared_optional = declared_binding
1139            .as_ref()
1140            .map(|(_, optional)| *optional)
1141            .unwrap_or(false);
1142        let optional = typing::expr_is_optional_with_function_impls_and_imports(
1143            expr,
1144            &self.type_state,
1145            &self.function_impls,
1146            &self.function_decls,
1147            &self.struct_schemas,
1148            &self.host_import_return_types,
1149            &self.host_import_signatures,
1150        ) || declared_optional;
1151        let ty = if optional {
1152            typing::infer_optional_expr_inner_type_with_function_impls_and_imports(
1153                expr,
1154                &self.type_state,
1155                &self.function_impls,
1156                &self.function_decls,
1157                &self.struct_schemas,
1158                &self.host_import_return_types,
1159                &self.host_import_signatures,
1160            )
1161        } else {
1162            self.infer_bound_type(expr)
1163        };
1164        self.callable_bindings.remove(&slot);
1165        if !self.try_compile_same_local_collection_rebind(slot, expr)? {
1166            self.compile_scalar_expr(expr)?;
1167        }
1168        self.emit_stloc(slot)?;
1169        let schema = slot_declared_schema.clone().or_else(|| {
1170            if optional {
1171                typing::infer_optional_expr_inner_schema_with_function_impls_and_imports(
1172                    expr,
1173                    &self.type_state,
1174                    &self.function_impls,
1175                    &self.function_decls,
1176                    &self.struct_schemas,
1177                    &self.host_import_return_types,
1178                    &self.host_import_signatures,
1179                )
1180            } else {
1181                typing::infer_expr_schema_with_function_impls_and_imports(
1182                    expr,
1183                    &self.type_state,
1184                    &self.function_impls,
1185                    &self.function_decls,
1186                    &self.struct_schemas,
1187                    &self.host_import_return_types,
1188                    &self.host_import_signatures,
1189                )
1190            }
1191        });
1192        let from_declared_schema =
1193            slot_declared_schema.is_some() || self.type_state.has_declared_schema(slot);
1194        let ty = slot_declared_schema
1195            .as_ref()
1196            .map(typing::bound_type_from_schema)
1197            .unwrap_or(ty);
1198        self.type_state.set_with_optional_schema_origin(
1199            slot,
1200            ty,
1201            schema,
1202            from_declared_schema,
1203            optional,
1204        );
1205        Ok(())
1206    }
1207
1208    fn try_compile_same_local_collection_rebind(
1209        &mut self,
1210        target: LocalSlot,
1211        expr: &Expr,
1212    ) -> Result<bool, CompileError> {
1213        if !self.enable_local_move_semantics {
1214            return Ok(false);
1215        }
1216        let Expr::Call(index, _, args) = expr else {
1217            return Ok(false);
1218        };
1219        let Some(builtin) = BuiltinFunction::from_call_index(*index) else {
1220            return Ok(false);
1221        };
1222        let expected_arity = match builtin {
1223            BuiltinFunction::Set => 3,
1224            BuiltinFunction::ArrayPush => 2,
1225            _ => return Ok(false),
1226        };
1227        if args.len() != expected_arity
1228            || !matches!(args.first(), Some(Expr::Var(source)) if *source == target)
1229        {
1230            return Ok(false);
1231        }
1232
1233        for arg in args {
1234            self.compile_scalar_expr(arg)?;
1235        }
1236        self.assembler.push_const(Value::Null);
1237        self.emit_stloc(target)?;
1238        self.emit_direct_call(*index, args)?;
1239        Ok(true)
1240    }
1241
1242    fn compile_scalar_expr(&mut self, expr: &Expr) -> Result<(), CompileError> {
1243        self.compile_expr(expr)
1244    }
1245
1246    fn compile_short_circuit_and(&mut self, lhs: &Expr, rhs: &Expr) -> Result<(), CompileError> {
1247        let false_label = self.fresh_label("and_false");
1248        let end_label = self.fresh_label("and_end");
1249        self.compile_scalar_expr(lhs)?;
1250        self.assembler.brfalse_label(&false_label);
1251        self.compile_scalar_expr(rhs)?;
1252        self.assembler.br_label(&end_label);
1253        self.assembler
1254            .label(&false_label)
1255            .map_err(CompileError::Assembler)?;
1256        self.assembler.push_const(Value::Bool(false));
1257        self.assembler
1258            .label(&end_label)
1259            .map_err(CompileError::Assembler)?;
1260        Ok(())
1261    }
1262
1263    fn compile_short_circuit_or(&mut self, lhs: &Expr, rhs: &Expr) -> Result<(), CompileError> {
1264        let rhs_label = self.fresh_label("or_rhs");
1265        let end_label = self.fresh_label("or_end");
1266        self.compile_scalar_expr(lhs)?;
1267        self.assembler.brfalse_label(&rhs_label);
1268        self.assembler.push_const(Value::Bool(true));
1269        self.assembler.br_label(&end_label);
1270        self.assembler
1271            .label(&rhs_label)
1272            .map_err(CompileError::Assembler)?;
1273        self.compile_scalar_expr(rhs)?;
1274        self.assembler
1275            .label(&end_label)
1276            .map_err(CompileError::Assembler)?;
1277        Ok(())
1278    }
1279
1280    fn ensure_function_value_slot(
1281        &mut self,
1282        index: u16,
1283        type_args: &[TypeSchema],
1284    ) -> Result<LocalSlot, CompileError> {
1285        if type_args.is_empty()
1286            && self
1287                .function_decls
1288                .get(&index)
1289                .is_some_and(|decl| !decl.type_params.is_empty())
1290        {
1291            let name = self
1292                .function_decls
1293                .get(&index)
1294                .map(|decl| decl.name.as_str())
1295                .unwrap_or("<unknown>");
1296            return Err(CompileError::CallableArgumentTypeMismatch {
1297                line: None,
1298                source_name: None,
1299                detail: format!(
1300                    "generic function value '{name}' requires explicit type arguments or an unambiguous callable context"
1301                ),
1302            });
1303        }
1304        if !type_args.is_empty()
1305            && let Some((_, _, slot)) = self
1306                .specialized_function_slots
1307                .iter()
1308                .find(|(candidate, args, _)| *candidate == index && args == type_args)
1309        {
1310            return Ok(*slot);
1311        }
1312        if type_args.is_empty()
1313            && let Some(slot) = self.function_slots.get(&index)
1314        {
1315            return Ok(*slot);
1316        }
1317        if !type_args.is_empty() && self.function_slots.contains_key(&index) {
1318            return self.ensure_specialized_function_slot(index, type_args);
1319        }
1320
1321        let (target_index, arity) = if let Some(builtin) = BuiltinFunction::from_call_index(index) {
1322            (index, builtin.arity())
1323        } else if let Some(decl) = self.function_decls.get(&index) {
1324            (
1325                self.call_index_remap.get(&index).copied().unwrap_or(index),
1326                decl.args.len() as u8,
1327            )
1328        } else {
1329            return Err(CompileError::CallableUsedAsValue);
1330        };
1331        let slot = self.allocate_hidden_callable_slot()?;
1332        let prototype_id = self.callable_prototypes.len() as u32;
1333        self.callable_prototypes.push(CallablePrototype {
1334            kind: CallableKind::HostFunction,
1335            target: CallableTarget::HostImport(target_index),
1336            arity,
1337            frame_local_count: self.frame_local_count,
1338            parameter_slots: Vec::new(),
1339            capture_source_slots: Vec::new(),
1340            capture_slots: Vec::new(),
1341            capture_modes: Vec::new(),
1342            self_slot: None,
1343            schema: self.instantiated_callable_schema(index, type_args),
1344        });
1345        self.root_callable_bindings.push(RootCallableBinding {
1346            local_slot: slot,
1347            prototype_id,
1348        });
1349        self.function_slots.insert(index, slot);
1350        self.function_prototype_ids.insert(index, prototype_id);
1351        if type_args.is_empty() {
1352            Ok(slot)
1353        } else {
1354            self.ensure_specialized_function_slot(index, type_args)
1355        }
1356    }
1357
1358    fn ensure_specialized_function_slot(
1359        &mut self,
1360        index: u16,
1361        type_args: &[TypeSchema],
1362    ) -> Result<LocalSlot, CompileError> {
1363        let base_prototype_id = *self
1364            .function_prototype_ids
1365            .get(&index)
1366            .ok_or(CompileError::CallableUsedAsValue)?;
1367        if self
1368            .function_impls
1369            .get(&index)
1370            .is_some_and(|function| !function.capture_copies.is_empty())
1371        {
1372            return self
1373                .function_slots
1374                .get(&index)
1375                .copied()
1376                .ok_or(CompileError::CallableUsedAsValue);
1377        }
1378        let slot = self.allocate_hidden_callable_slot()?;
1379        let mut prototype = self.callable_prototypes[base_prototype_id as usize].clone();
1380        prototype.schema = self.instantiated_callable_schema(index, type_args);
1381        prototype.frame_local_count = self.frame_local_count;
1382        let prototype_id = self.callable_prototypes.len() as u32;
1383        self.callable_prototypes.push(prototype);
1384        self.root_callable_bindings.push(RootCallableBinding {
1385            local_slot: slot,
1386            prototype_id,
1387        });
1388        self.specialized_function_slots
1389            .push((index, type_args.to_vec(), slot));
1390        Ok(slot)
1391    }
1392
1393    fn allocate_hidden_callable_slot(&mut self) -> Result<LocalSlot, CompileError> {
1394        let slot = LocalSlot::try_from(self.frame_local_count)
1395            .map_err(|_| CompileError::LocalSlotOverflow(LocalSlot::MAX))?;
1396        let _ = local_slot_operand(slot)?;
1397        self.frame_local_count = self.frame_local_count.saturating_add(1);
1398        Ok(slot)
1399    }
1400
1401    fn instantiated_callable_schema(
1402        &self,
1403        index: u16,
1404        type_args: &[TypeSchema],
1405    ) -> Option<TypeSchema> {
1406        let decl = self.function_decls.get(&index)?;
1407        if decl.type_params.len() != type_args.len() {
1408            return None;
1409        }
1410        let bindings = decl
1411            .type_params
1412            .iter()
1413            .cloned()
1414            .zip(type_args.iter().cloned())
1415            .collect::<HashMap<_, _>>();
1416        Some(TypeSchema::Callable {
1417            params: decl
1418                .arg_schemas
1419                .iter()
1420                .map(|schema| {
1421                    schema
1422                        .as_ref()
1423                        .map(|schema| substitute_type_schema(schema, &bindings))
1424                        .unwrap_or(TypeSchema::Unknown)
1425                })
1426                .collect(),
1427            result: Box::new(
1428                decl.return_schema
1429                    .as_ref()
1430                    .map(|schema| substitute_type_schema(schema, &bindings))
1431                    .unwrap_or(TypeSchema::Unknown),
1432            ),
1433        })
1434    }
1435
1436    fn record_closure_param_hints(&mut self, prototype_id: u32, args: &[Expr]) {
1437        let hints = args
1438            .iter()
1439            .map(|arg| {
1440                let schema = typing::infer_expr_schema_with_function_impls_and_imports(
1441                    arg,
1442                    &self.type_state,
1443                    &self.function_impls,
1444                    &self.function_decls,
1445                    &self.struct_schemas,
1446                    &self.host_import_return_types,
1447                    &self.host_import_signatures,
1448                );
1449                let ty = schema
1450                    .as_ref()
1451                    .map(typing::bound_type_from_schema)
1452                    .unwrap_or_else(|| self.infer_bound_type(arg));
1453                (ty, schema)
1454            })
1455            .collect::<Vec<_>>();
1456
1457        self.closure_param_hints
1458            .entry(prototype_id)
1459            .and_modify(|existing| {
1460                for (index, hint) in hints.iter().enumerate() {
1461                    if let Some(existing) = existing.get_mut(index)
1462                        && existing.0 == typing::BoundType::Unknown
1463                    {
1464                        *existing = hint.clone();
1465                    }
1466                }
1467            })
1468            .or_insert(hints);
1469    }
1470
1471    fn compile_function_call(&mut self, index: u16, args: &[Expr]) -> Result<(), CompileError> {
1472        if self.function_impls.contains_key(&index) {
1473            let slot = *self
1474                .function_slots
1475                .get(&index)
1476                .ok_or(CompileError::CallableUsedAsValue)?;
1477            self.emit_copy_ldloc(slot)?;
1478            return self.compile_callvalue_args(args);
1479        }
1480        self.compile_direct_call(index, args)
1481    }
1482
1483    fn compile_callvalue_args(&mut self, args: &[Expr]) -> Result<(), CompileError> {
1484        for arg in args {
1485            self.compile_scalar_expr(arg)?;
1486        }
1487        let argc = u8::try_from(args.len()).map_err(|_| CompileError::CallArityOverflow)?;
1488        self.assembler.call_value(argc);
1489        Ok(())
1490    }
1491
1492    fn emit_closure_callable(&mut self, closure: &ClosureExpr) -> Result<u32, CompileError> {
1493        self.emit_closure_callable_with_self(closure, None)
1494    }
1495
1496    fn emit_closure_callable_with_self(
1497        &mut self,
1498        closure: &ClosureExpr,
1499        binding_slot: Option<LocalSlot>,
1500    ) -> Result<u32, CompileError> {
1501        let prototype_id = self.callable_prototypes.len() as u32;
1502        self.callable_prototypes.push(CallablePrototype {
1503            kind: CallableKind::Closure,
1504            target: CallableTarget::ScriptFunction(u32::MAX),
1505            arity: u8::try_from(closure.param_slots.len())
1506                .map_err(|_| CompileError::CallArityOverflow)?,
1507            frame_local_count: self.frame_local_count,
1508            parameter_slots: closure.param_slots.clone(),
1509            capture_source_slots: closure
1510                .capture_copies
1511                .iter()
1512                .map(|(source, _)| *source)
1513                .collect(),
1514            capture_slots: closure
1515                .capture_copies
1516                .iter()
1517                .map(|(_, target)| *target)
1518                .collect(),
1519            capture_modes: closure
1520                .capture_copies
1521                .iter()
1522                .map(|(_, target)| super::lifetime::closure_capture_binding_mode(closure, *target))
1523                .collect(),
1524            self_slot: binding_slot.and_then(|binding_slot| {
1525                closure
1526                    .capture_copies
1527                    .iter()
1528                    .find_map(|(source, target)| (*source == binding_slot).then_some(*target))
1529            }),
1530            schema: binding_slot.and_then(|slot| {
1531                self.type_map
1532                    .local_schemas
1533                    .get(slot as usize)
1534                    .cloned()
1535                    .flatten()
1536            }),
1537        });
1538        self.pending_closures.push((prototype_id, closure.clone()));
1539        self.emit_bind_callable(
1540            prototype_id,
1541            closure.capture_copies.iter().map(|(source, _)| *source),
1542        )?;
1543        Ok(prototype_id)
1544    }
1545
1546    fn emit_bind_callable(
1547        &mut self,
1548        prototype_id: u32,
1549        capture_slots: impl IntoIterator<Item = LocalSlot>,
1550    ) -> Result<(), CompileError> {
1551        self.assembler
1552            .push_const(Value::Int(i64::from(prototype_id)));
1553        self.assembler
1554            .call(BuiltinFunction::ArrayNew.call_index(), 0);
1555        for source_slot in capture_slots {
1556            self.emit_copy_ldloc(source_slot)?;
1557            self.assembler
1558                .call(BuiltinFunction::ArrayPush.call_index(), 2);
1559        }
1560        self.assembler
1561            .call(BuiltinFunction::BindCallable.call_index(), 2);
1562        Ok(())
1563    }
1564
1565    fn compile_direct_call(&mut self, index: u16, args: &[Expr]) -> Result<(), CompileError> {
1566        for arg in args {
1567            self.compile_scalar_expr(arg)?;
1568        }
1569        self.emit_direct_call(index, args)
1570    }
1571
1572    fn emit_direct_call(&mut self, index: u16, args: &[Expr]) -> Result<(), CompileError> {
1573        let argc = u8::try_from(args.len()).map_err(|_| CompileError::CallArityOverflow)?;
1574        if let Some(builtin) = BuiltinFunction::from_call_index(index) {
1575            debug_assert!(builtin.accepts_arity(argc));
1576            self.record_builtin_call_operand_types(args);
1577            self.assembler.call(index, argc);
1578            return Ok(());
1579        }
1580        let remapped_index = self.call_index_remap.get(&index).copied().unwrap_or(index);
1581        self.assembler.call(remapped_index, argc);
1582        Ok(())
1583    }
1584
1585    fn compile_match_pattern_condition(
1586        &mut self,
1587        value_slot: LocalSlot,
1588        pattern: &MatchPattern,
1589    ) -> Result<(), CompileError> {
1590        match pattern {
1591            MatchPattern::Int(v) => {
1592                self.emit_copy_ldloc(value_slot)?;
1593                self.assembler.push_const(Value::Int(*v));
1594                self.assembler.ceq();
1595            }
1596            MatchPattern::String(v) => {
1597                self.emit_copy_ldloc(value_slot)?;
1598                self.assembler.push_const(Value::string(v.clone()));
1599                self.assembler.ceq();
1600            }
1601            MatchPattern::Bytes(v) => {
1602                self.emit_copy_ldloc(value_slot)?;
1603                self.assembler.push_const(Value::bytes(v.clone()));
1604                self.assembler.ceq();
1605            }
1606            MatchPattern::Null => {
1607                self.emit_copy_ldloc(value_slot)?;
1608                self.assembler.push_const(Value::Null);
1609                self.assembler.ceq();
1610            }
1611            MatchPattern::None => {
1612                self.emit_copy_ldloc(value_slot)?;
1613                self.assembler.push_const(Value::Null);
1614                self.assembler.ceq();
1615            }
1616            MatchPattern::SomeBinding(_) => {
1617                self.emit_copy_ldloc(value_slot)?;
1618                self.assembler.push_const(Value::Null);
1619                self.assembler.ceq();
1620                self.assembler.not();
1621            }
1622            MatchPattern::Type(type_pattern) => {
1623                self.compile_match_type_pattern_condition(value_slot, type_pattern)?;
1624            }
1625        }
1626        Ok(())
1627    }
1628
1629    fn compile_match_type_pattern_condition(
1630        &mut self,
1631        value_slot: LocalSlot,
1632        type_pattern: &MatchTypePattern,
1633    ) -> Result<(), CompileError> {
1634        match type_pattern {
1635            MatchTypePattern::Int => self.compile_type_name_equals(value_slot, "int")?,
1636            MatchTypePattern::Float => self.compile_type_name_equals(value_slot, "float")?,
1637            MatchTypePattern::Bool => self.compile_type_name_equals(value_slot, "bool")?,
1638            MatchTypePattern::String => self.compile_type_name_equals(value_slot, "string")?,
1639            MatchTypePattern::Bytes => self.compile_type_name_equals(value_slot, "bytes")?,
1640            MatchTypePattern::Array => self.compile_type_name_equals(value_slot, "array")?,
1641            MatchTypePattern::Map => self.compile_type_name_equals(value_slot, "map")?,
1642            MatchTypePattern::Number => {
1643                let number_fallback_label = self.fresh_label("match_type_number_fallback");
1644                let number_end_label = self.fresh_label("match_type_number_end");
1645
1646                self.compile_type_name_equals(value_slot, "int")?;
1647                self.assembler.brfalse_label(&number_fallback_label);
1648                self.assembler.push_const(Value::Bool(true));
1649                self.assembler.br_label(&number_end_label);
1650                self.assembler
1651                    .label(&number_fallback_label)
1652                    .map_err(CompileError::Assembler)?;
1653                self.compile_type_name_equals(value_slot, "float")?;
1654                self.assembler
1655                    .label(&number_end_label)
1656                    .map_err(CompileError::Assembler)?;
1657            }
1658        }
1659        Ok(())
1660    }
1661
1662    fn compile_type_name_equals(
1663        &mut self,
1664        value_slot: LocalSlot,
1665        expected: &str,
1666    ) -> Result<(), CompileError> {
1667        self.emit_copy_ldloc(value_slot)?;
1668        self.assembler.call(BuiltinFunction::TypeOf.call_index(), 1);
1669        self.assembler
1670            .push_const(Value::string(expected.to_string()));
1671        self.assembler.ceq();
1672        Ok(())
1673    }
1674
1675    fn bind_match_pattern_slot(
1676        &mut self,
1677        pattern: &MatchPattern,
1678        value: &Expr,
1679        value_slot: LocalSlot,
1680        match_entry_type_state: &typing::LocalTypeState,
1681    ) -> Result<(), CompileError> {
1682        let Some(binding_slot) = pattern.binding_slot() else {
1683            return Ok(());
1684        };
1685        let ty = typing::infer_optional_expr_inner_type_with_function_impls_and_imports(
1686            value,
1687            match_entry_type_state,
1688            &self.function_impls,
1689            &self.function_decls,
1690            &self.struct_schemas,
1691            &self.host_import_return_types,
1692            &self.host_import_signatures,
1693        );
1694        let schema = typing::infer_optional_expr_inner_schema_with_function_impls_and_imports(
1695            value,
1696            match_entry_type_state,
1697            &self.function_impls,
1698            &self.function_decls,
1699            &self.struct_schemas,
1700            &self.host_import_return_types,
1701            &self.host_import_signatures,
1702        );
1703        self.emit_copy_ldloc(value_slot)?;
1704        self.emit_stloc(binding_slot)?;
1705        self.type_state
1706            .set_with_optional_schema_origin(binding_slot, ty, schema, false, false);
1707        Ok(())
1708    }
1709
1710    fn infer_bound_type(&self, expr: &Expr) -> typing::BoundType {
1711        typing::infer_expr_type_with_function_impls_and_imports(
1712            expr,
1713            &self.type_state,
1714            &self.function_impls,
1715            &self.function_decls,
1716            &self.struct_schemas,
1717            &self.host_import_return_types,
1718            &self.host_import_signatures,
1719        )
1720    }
1721
1722    fn simulate_stmt_type_state(
1723        &self,
1724        stmts: &[Stmt],
1725        initial_state: &typing::LocalTypeState,
1726    ) -> typing::LocalTypeState {
1727        let mut state = initial_state.clone();
1728        typing::apply_stmts_with_function_impls_and_imports(
1729            stmts,
1730            &mut state,
1731            &self.function_impls,
1732            &self.function_decls,
1733            &self.struct_schemas,
1734            &self.host_import_return_types,
1735            &self.host_import_signatures,
1736        );
1737        state
1738    }
1739
1740    fn stabilize_loop_type_state<F>(
1741        &self,
1742        initial_state: &typing::LocalTypeState,
1743        mut run_iteration: F,
1744    ) -> typing::LocalTypeState
1745    where
1746        F: FnMut(&mut typing::LocalTypeState),
1747    {
1748        let zero_iteration = initial_state.clone();
1749        let mut first_iteration = initial_state.clone();
1750        run_iteration(&mut first_iteration);
1751        let mut second_iteration = first_iteration.clone();
1752        run_iteration(&mut second_iteration);
1753
1754        let mut stable_iteration = typing::LocalTypeState::default();
1755        stable_iteration.merge_from_branches(&first_iteration, &second_iteration);
1756
1757        let mut stabilized = zero_iteration.clone();
1758        stabilized.merge_from_branches(&zero_iteration, &stable_iteration);
1759        stabilized
1760    }
1761
1762    fn value_type_of_expr(&self, expr: &Expr) -> ValueType {
1763        ValueType::from(self.infer_bound_type(expr))
1764    }
1765
1766    fn record_operand_types(&mut self, lhs: ValueType, rhs: ValueType) {
1767        if lhs == ValueType::Unknown || rhs == ValueType::Unknown {
1768            return;
1769        }
1770        self.type_map
1771            .operand_types
1772            .insert(self.assembler.position() as usize, (lhs, rhs));
1773    }
1774
1775    fn record_unary_operand_type(&mut self, operand: ValueType) {
1776        if operand == ValueType::Unknown {
1777            return;
1778        }
1779        self.type_map.operand_types.insert(
1780            self.assembler.position() as usize,
1781            (operand, ValueType::Unknown),
1782        );
1783    }
1784
1785    fn record_builtin_call_operand_types(&mut self, args: &[Expr]) {
1786        if args.is_empty() {
1787            return;
1788        }
1789        let lhs = self.value_type_of_expr(&args[0]);
1790        let rhs = args
1791            .get(1)
1792            .map(|expr| self.value_type_of_expr(expr))
1793            .unwrap_or(ValueType::Unknown);
1794        if lhs == ValueType::Unknown && rhs == ValueType::Unknown {
1795            return;
1796        }
1797        self.type_map
1798            .operand_types
1799            .insert(self.assembler.position() as usize, (lhs, rhs));
1800    }
1801
1802    fn fresh_label(&mut self, prefix: &str) -> String {
1803        let label = format!("{prefix}_{}", self.next_label_id);
1804        self.next_label_id += 1;
1805        label
1806    }
1807
1808    fn emit_move_ldloc(&mut self, slot: LocalSlot) -> Result<(), CompileError> {
1809        let operand = local_slot_operand(slot)?;
1810        self.assembler.ldloc(operand);
1811        self.assembler.push_const(Value::Int(i64::from(operand)));
1812        self.assembler
1813            .call(BuiltinFunction::DetachLocal.call_index(), 1);
1814        Ok(())
1815    }
1816
1817    fn emit_copy_ldloc(&mut self, slot: LocalSlot) -> Result<(), CompileError> {
1818        self.assembler.ldloc(local_slot_operand(slot)?);
1819        Ok(())
1820    }
1821
1822    fn emit_stloc(&mut self, slot: LocalSlot) -> Result<(), CompileError> {
1823        self.assembler.stloc(local_slot_operand(slot)?);
1824        Ok(())
1825    }
1826
1827    fn compile_string_concat_operand(&mut self, expr: &Expr) -> Result<(), CompileError> {
1828        if let Some(value) = eval_const_int_expr(expr) {
1829            self.assembler.push_const(Value::string(value.to_string()));
1830            return Ok(());
1831        }
1832
1833        self.compile_scalar_expr(expr)?;
1834        self.lower_number_to_string_for_concat_top();
1835        Ok(())
1836    }
1837
1838    fn lower_number_to_string_for_concat_top(&mut self) {
1839        let not_int_label = self.fresh_label("concat_not_int");
1840        let not_float_label = self.fresh_label("concat_not_float");
1841        let done_label = self.fresh_label("concat_value_done");
1842
1843        self.assembler.dup();
1844        self.assembler.call(BuiltinFunction::TypeOf.call_index(), 1);
1845        self.assembler.push_const(Value::string("int"));
1846        self.assembler.ceq();
1847        self.assembler.brfalse_label(&not_int_label);
1848        self.assembler
1849            .call(BuiltinFunction::ToString.call_index(), 1);
1850        self.assembler.br_label(&done_label);
1851
1852        self.assembler
1853            .label(&not_int_label)
1854            .expect("compiler-generated label should be valid");
1855        self.assembler.dup();
1856        self.assembler.call(BuiltinFunction::TypeOf.call_index(), 1);
1857        self.assembler.push_const(Value::string("float"));
1858        self.assembler.ceq();
1859        self.assembler.brfalse_label(&not_float_label);
1860        self.assembler
1861            .call(BuiltinFunction::ToString.call_index(), 1);
1862        self.assembler.br_label(&done_label);
1863
1864        self.assembler
1865            .label(&not_float_label)
1866            .expect("compiler-generated label should be valid");
1867        self.assembler
1868            .label(&done_label)
1869            .expect("compiler-generated label should be valid");
1870    }
1871}
1872
1873fn substitute_type_schema(
1874    schema: &TypeSchema,
1875    bindings: &HashMap<String, TypeSchema>,
1876) -> TypeSchema {
1877    match schema {
1878        TypeSchema::GenericParam(name) => bindings
1879            .get(name)
1880            .cloned()
1881            .unwrap_or_else(|| schema.clone()),
1882        TypeSchema::Optional(inner) => {
1883            TypeSchema::Optional(Box::new(substitute_type_schema(inner, bindings)))
1884        }
1885        TypeSchema::Named(name, args) => TypeSchema::Named(
1886            name.clone(),
1887            args.iter()
1888                .map(|schema| substitute_type_schema(schema, bindings))
1889                .collect(),
1890        ),
1891        TypeSchema::Array(inner) => {
1892            TypeSchema::Array(Box::new(substitute_type_schema(inner, bindings)))
1893        }
1894        TypeSchema::ArrayTuple(items) => TypeSchema::ArrayTuple(
1895            items
1896                .iter()
1897                .map(|schema| substitute_type_schema(schema, bindings))
1898                .collect(),
1899        ),
1900        TypeSchema::ArrayTupleRest { prefix, rest } => TypeSchema::ArrayTupleRest {
1901            prefix: prefix
1902                .iter()
1903                .map(|schema| substitute_type_schema(schema, bindings))
1904                .collect(),
1905            rest: Box::new(substitute_type_schema(rest, bindings)),
1906        },
1907        TypeSchema::Map(inner) => {
1908            TypeSchema::Map(Box::new(substitute_type_schema(inner, bindings)))
1909        }
1910        TypeSchema::Object(fields) => TypeSchema::Object(
1911            fields
1912                .iter()
1913                .map(|(name, schema)| (name.clone(), substitute_type_schema(schema, bindings)))
1914                .collect(),
1915        ),
1916        TypeSchema::Callable { params, result } => TypeSchema::Callable {
1917            params: params
1918                .iter()
1919                .map(|schema| substitute_type_schema(schema, bindings))
1920                .collect(),
1921            result: Box::new(substitute_type_schema(result, bindings)),
1922        },
1923        _ => schema.clone(),
1924    }
1925}
1926
1927fn local_slot_operand(index: LocalSlot) -> Result<u8, CompileError> {
1928    u8::try_from(index).map_err(|_| CompileError::LocalSlotOverflow(index))
1929}
1930
1931fn shift_amount_for_power_of_two(value: i64) -> Option<u32> {
1932    if value <= 0 {
1933        return None;
1934    }
1935    let as_u64 = value as u64;
1936    if !as_u64.is_power_of_two() {
1937        return None;
1938    }
1939    Some(as_u64.trailing_zeros())
1940}
1941
1942fn is_definitely_string_expr(expr: &Expr) -> bool {
1943    match expr {
1944        Expr::String(_) => true,
1945        Expr::ToOwned(inner) | Expr::Borrow(inner) | Expr::BorrowMut(inner) => {
1946            is_definitely_string_expr(inner)
1947        }
1948        Expr::Add(lhs, rhs) => {
1949            (is_definitely_string_expr(lhs) && is_definitely_string_expr(rhs))
1950                || (is_definitely_string_expr(lhs) && eval_const_int_expr(rhs).is_some())
1951                || (eval_const_int_expr(lhs).is_some() && is_definitely_string_expr(rhs))
1952        }
1953        _ => false,
1954    }
1955}
1956
1957fn eval_const_int_expr(expr: &Expr) -> Option<i64> {
1958    match expr {
1959        Expr::Int(value) => Some(*value),
1960        Expr::ToOwned(inner) | Expr::Borrow(inner) | Expr::BorrowMut(inner) => {
1961            eval_const_int_expr(inner)
1962        }
1963        Expr::Neg(inner) => eval_const_int_expr(inner)?.checked_neg(),
1964        Expr::Add(lhs, rhs) => eval_const_int_expr(lhs)?.checked_add(eval_const_int_expr(rhs)?),
1965        Expr::Sub(lhs, rhs) => eval_const_int_expr(lhs)?.checked_sub(eval_const_int_expr(rhs)?),
1966        Expr::Mul(lhs, rhs) => eval_const_int_expr(lhs)?.checked_mul(eval_const_int_expr(rhs)?),
1967        Expr::Div(lhs, rhs) => {
1968            let rhs = eval_const_int_expr(rhs)?;
1969            if rhs == 0 {
1970                return None;
1971            }
1972            eval_const_int_expr(lhs)?.checked_div(rhs)
1973        }
1974        _ => None,
1975    }
1976}