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(¬_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(¬_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(¬_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(¬_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}