1use std::{ptr::addr_eq, sync::Arc};
7
8use arc_gc::gc::GC;
9use unicode_width::UnicodeWidthStr;
10
11use crate::{
12 lambda::runnable::{Runnable, RuntimeError, StepResult},
13 types::{
14 lambda::vm_instructions::opcode::{
15 self, OpcodeArgument, build_operand_argument, decode_opcode, get_operand_arg,
16 get_processed_opcode,
17 },
18 object::{OnionObject, OnionObjectCell, OnionStaticObject},
19 },
20 utils::{fastmap::OnionFastMap, find_line_and_col_from_source},
21};
22
23use super::{
24 context::{Context, Frame},
25 vm_instructions::{
26 self,
27 instruction_set::{VMInstruction, VMInstructionPackage},
28 opcode::ProcessedOpcode,
29 },
30};
31
32type InstructionHandler =
44 fn(&mut OnionLambdaRunnable, &ProcessedOpcode, &mut GC<OnionObjectCell>) -> StepResult;
45
46static INSTRUCTION_TABLE: std::sync::LazyLock<Vec<InstructionHandler>> =
54 std::sync::LazyLock::new(|| {
55 let mut instruction_table: Vec<InstructionHandler> = vec![
56 |_, opcode, _| StepResult::Error(RuntimeError::DetailedError(format!("Invalid instruction: {:?}", opcode).into())); 256 ];
59
60 instruction_table[VMInstruction::LoadNull as usize] = vm_instructions::load_null;
63 instruction_table[VMInstruction::LoadInt32 as usize] = vm_instructions::load_int;
64 instruction_table[VMInstruction::LoadInt64 as usize] = vm_instructions::load_int;
65 instruction_table[VMInstruction::LoadFloat32 as usize] = vm_instructions::load_float;
66 instruction_table[VMInstruction::LoadFloat64 as usize] = vm_instructions::load_float;
67 instruction_table[VMInstruction::LoadString as usize] = vm_instructions::load_string;
68 instruction_table[VMInstruction::LoadBytes as usize] = vm_instructions::load_bytes;
69 instruction_table[VMInstruction::LoadBool as usize] = vm_instructions::load_bool;
70 instruction_table[VMInstruction::LoadLambda as usize] = vm_instructions::load_lambda;
71 instruction_table[VMInstruction::LoadUndefined as usize] = vm_instructions::load_undefined;
72
73 instruction_table[VMInstruction::BuildTuple as usize] = vm_instructions::build_tuple;
75 instruction_table[VMInstruction::BuildKeyValue as usize] = vm_instructions::build_pair;
76 instruction_table[VMInstruction::BuildRange as usize] = vm_instructions::build_range;
77 instruction_table[VMInstruction::BuildSet as usize] = vm_instructions::build_set;
78 instruction_table[VMInstruction::BinaryIn as usize] = vm_instructions::is_in;
80 instruction_table[VMInstruction::BinaryIs as usize] = vm_instructions::check_is_same_object;
81
82 instruction_table[VMInstruction::BinaryAdd as usize] = vm_instructions::binary_add;
83 instruction_table[VMInstruction::BinarySub as usize] = vm_instructions::binary_subtract;
84 instruction_table[VMInstruction::BinaryMul as usize] = vm_instructions::binary_multiply;
85 instruction_table[VMInstruction::BinaryDiv as usize] = vm_instructions::binary_divide;
86 instruction_table[VMInstruction::BinaryMod as usize] = vm_instructions::binary_modulus;
87 instruction_table[VMInstruction::BinaryPow as usize] = vm_instructions::binary_power;
88 instruction_table[VMInstruction::BinaryBitAnd as usize] =
89 vm_instructions::binary_bitwise_and;
90 instruction_table[VMInstruction::BinaryBitOr as usize] = vm_instructions::binary_bitwise_or;
91 instruction_table[VMInstruction::BinaryBitXor as usize] =
92 vm_instructions::binary_bitwise_xor;
93 instruction_table[VMInstruction::BinaryShl as usize] = vm_instructions::binary_shift_left;
94 instruction_table[VMInstruction::BinaryShr as usize] = vm_instructions::binary_shift_right;
95 instruction_table[VMInstruction::BinaryEq as usize] = vm_instructions::binary_equal;
96 instruction_table[VMInstruction::BinaryNe as usize] = vm_instructions::binary_not_equal;
97 instruction_table[VMInstruction::BinaryGt as usize] = vm_instructions::binary_greater;
98 instruction_table[VMInstruction::BinaryLt as usize] = vm_instructions::binary_less;
99 instruction_table[VMInstruction::BinaryGe as usize] = vm_instructions::binary_greater_equal;
100 instruction_table[VMInstruction::BinaryLe as usize] = vm_instructions::binary_less_equal;
101 instruction_table[VMInstruction::MapTo as usize] = vm_instructions::map_to;
102
103 instruction_table[VMInstruction::UnaryBitNot as usize] = vm_instructions::unary_bitwise_not;
105 instruction_table[VMInstruction::UnaryAbs as usize] = vm_instructions::unary_plus;
106 instruction_table[VMInstruction::UnaryNeg as usize] = vm_instructions::unary_minus;
107
108 instruction_table[VMInstruction::StoreVar as usize] = vm_instructions::let_var;
110 instruction_table[VMInstruction::LoadVar as usize] = vm_instructions::get_var;
111 instruction_table[VMInstruction::SetValue as usize] = vm_instructions::set_var;
112 instruction_table[VMInstruction::GetAttr as usize] = vm_instructions::get_attr;
113 instruction_table[VMInstruction::KeyOf as usize] = vm_instructions::key_of;
114 instruction_table[VMInstruction::ValueOf as usize] = vm_instructions::value_of;
115 instruction_table[VMInstruction::TypeOf as usize] = vm_instructions::type_of;
116 instruction_table[VMInstruction::Swap as usize] = vm_instructions::swap;
117 instruction_table[VMInstruction::LengthOf as usize] = vm_instructions::get_length;
118 instruction_table[VMInstruction::Mut as usize] = vm_instructions::mutablize;
119 instruction_table[VMInstruction::Const as usize] = vm_instructions::immutablize;
120 instruction_table[VMInstruction::ForkInstruction as usize] =
121 vm_instructions::fork_instruction;
122 instruction_table[VMInstruction::Launch as usize] = vm_instructions::launch_thread;
123 instruction_table[VMInstruction::Spawn as usize] = vm_instructions::spawn_task;
124 instruction_table[VMInstruction::MakeAtomic as usize] = vm_instructions::make_atomic;
125 instruction_table[VMInstruction::MakeAsync as usize] = vm_instructions::make_async;
126 instruction_table[VMInstruction::MakeSync as usize] = vm_instructions::make_sync;
127
128 instruction_table[VMInstruction::Apply as usize] = vm_instructions::apply;
130 instruction_table[VMInstruction::Return as usize] = vm_instructions::return_value;
131 instruction_table[VMInstruction::Raise as usize] = vm_instructions::raise;
132 instruction_table[VMInstruction::Jump as usize] = vm_instructions::jump;
133 instruction_table[VMInstruction::JumpIfFalse as usize] = vm_instructions::jump_if_false;
134
135 instruction_table[VMInstruction::NewFrame as usize] = vm_instructions::new_frame;
137 instruction_table[VMInstruction::PopFrame as usize] = vm_instructions::pop_frame;
138 instruction_table[VMInstruction::ResetStack as usize] = vm_instructions::clear_stack;
139 instruction_table[VMInstruction::Pop as usize] = vm_instructions::discard_top;
140
141 instruction_table[VMInstruction::Import as usize] = vm_instructions::import;
143
144 instruction_table[VMInstruction::Assert as usize] = vm_instructions::assert;
145
146 instruction_table
147 });
148
149pub struct OnionLambdaRunnable {
173 pub(super) context: Context,
175 pub(super) ip: isize,
177 pub(super) ip_before_step: isize,
179 pub(super) instruction: Arc<VMInstructionPackage>,
181}
182
183impl OnionLambdaRunnable {
184 pub fn new(
211 argument: &OnionFastMap<Box<str>, OnionStaticObject>,
212 capture: &OnionFastMap<Box<str>, OnionObject>,
213 self_object: &OnionObject,
214 this_lambda: &OnionStaticObject,
215 instruction: Arc<VMInstructionPackage>,
216 ip: isize,
217 ) -> Result<Self, RuntimeError> {
218 if !addr_eq(argument.pool().keys(), instruction.get_string_pool()) {
219 panic!(
220 "Argument pool does not match instruction string pool: {:?} != {:?}",
221 argument.pool().keys(),
222 instruction.get_string_pool()
223 );
224 }
225 if !addr_eq(capture.pool().keys(), instruction.get_string_pool()) {
226 panic!(
227 "Capture pool does not match instruction string pool: {:?} != {:?}",
228 capture.pool().keys(),
229 instruction.get_string_pool()
230 );
231 }
232
233 let mut new_context = Context::new();
234 Context::push_frame(&mut new_context, Frame::new());
235
236 let index_this = instruction.get_string_index("this").ok_or_else(|| {
237 RuntimeError::InvalidOperation(
238 "Missing required variable 'this' in string pool"
239 .to_string()
240 .into(),
241 )
242 })?;
243 let index_self = instruction.get_string_index("self").ok_or_else(|| {
244 RuntimeError::InvalidOperation(
245 "Missing required variable 'self' in string pool"
246 .to_string()
247 .into(),
248 )
249 })?;
250 new_context
265 .let_variable(index_this, this_lambda.clone())
266 .map_err(|e| {
267 RuntimeError::InvalidOperation(
268 format!("Failed to initialize 'this' variable: {}", e).into(),
269 )
270 })?;
271
272 new_context
273 .let_variable(index_self, self_object.stabilize())
274 .map_err(|e| {
275 RuntimeError::InvalidOperation(
276 format!("Failed to initialize 'self' variable: {}", e).into(),
277 )
278 })?;
279
280 for (argument_index, value) in argument.pairs() {
289 new_context
290 .let_variable(*argument_index, value.clone())
291 .map_err(|e| {
292 RuntimeError::InvalidOperation(
293 format!("Failed to initialize argument '{}': {}", argument_index, e).into(),
294 )
295 })?;
296 }
297
298 for (capture_index, value) in capture.pairs() {
300 new_context
301 .let_variable(*capture_index, value.stabilize())
302 .map_err(|e| {
303 RuntimeError::InvalidOperation(
304 format!(
305 "Failed to initialize captured variable '{}': {}",
306 capture_index, e
307 )
308 .into(),
309 )
310 })?;
311 }
312
313 Ok(OnionLambdaRunnable {
314 context: new_context,
315 ip,
316 ip_before_step: ip,
317 instruction,
318 })
319 }
320}
321
322impl Runnable for OnionLambdaRunnable {
323 fn receive(
339 &mut self,
340 step_result: &StepResult,
341 _gc: &mut GC<OnionObjectCell>,
342 ) -> Result<(), RuntimeError> {
343 if let StepResult::Return(result) = step_result {
344 self.context.push_object(result.as_ref().clone())?;
345 Ok(())
346 } else {
347 Err(RuntimeError::DetailedError(
348 "receive not implemented for cases except 'Return'"
349 .to_string()
350 .into(),
351 ))
352 }
353 }
354
355 fn step(&mut self, gc: &mut GC<OnionObjectCell>) -> StepResult {
379 const MAX_INLINE_STEPS: usize = 1024;
380
381 let mut steps = 0;
382
383 let (code_ptr, code_len) = {
385 let code = self.instruction.get_code();
386 (code.as_ptr(), code.len())
387 };
388
389 loop {
390 if steps >= MAX_INLINE_STEPS {
391 break;
392 }
393 steps += 1;
394
395 let mut ip = self.ip as usize;
396 if ip >= code_len {
397 return StepResult::Error(RuntimeError::DetailedError(
398 "Instruction pointer out of bounds".into(),
399 ));
400 }
401
402 let code = unsafe { std::slice::from_raw_parts(code_ptr, code_len) };
404 let pending_ip = ip;
405 let opcode = get_processed_opcode(code, &mut ip);
406
407 let handler = unsafe { *INSTRUCTION_TABLE.get_unchecked(opcode.instruction as usize) };
408 self.ip = ip as isize;
409 self.ip_before_step = pending_ip as isize; match handler(self, &opcode, gc) {
412 StepResult::Continue => continue,
413 StepResult::Error(e) => {
414 self.ip = pending_ip as isize; return StepResult::Error(e);
416 }
417 v => return v,
418 }
419 }
420
421 StepResult::Continue
422 }
423
424 fn format_context(&self) -> String {
456 let ip = self.ip_before_step as usize;
457 let mut parts = Vec::new();
458
459 if let Some(location) = get_source_location_for_ip(&self.instruction, ip) {
461 parts.push(format!("-> at {}:{}", location.line, location.column));
463 parts.push(location.code_snippet);
464 } else {
465 let disassembly = disassemble_instruction(&self.instruction, ip); parts.push(format!("-> Executing VM Code at ip: {}", ip));
468 parts.push(format!(" - Current Instruction: {}", disassembly));
469 }
470
471 let context_state = self.context.format_context(self.instruction.as_ref());
473 parts.push("\n--- Lambda Execution State ---".to_string());
474 parts.push(context_state);
475
476 parts.join("\n")
477 }
478}
479
480pub fn disassemble_instruction(package: &VMInstructionPackage, ip: usize) -> String {
510 let code = package.get_code();
511 if ip >= code.len() {
512 return format!("<IP:{} out of bounds>", ip);
513 }
514
515 let mut temp_ip = ip;
518 let opcode_word = opcode::take_u32(code, &mut temp_ip);
519 let decoded_opcode = decode_opcode(opcode_word);
520
521 let mut next_ip = temp_ip; let raw_operand1 = get_operand_arg(code, &mut next_ip, decoded_opcode.operand1());
525 let raw_operand2 = get_operand_arg(code, &mut next_ip, decoded_opcode.operand2());
526 let raw_operand3 = get_operand_arg(code, &mut next_ip, decoded_opcode.operand3());
527
528 let format_operand = |flag: u8, raw_value: u64| -> Option<String> {
531 let arg = build_operand_argument(flag, raw_value);
533
534 match arg {
535 OpcodeArgument::None => None,
536 OpcodeArgument::Int32(v) => Some(format!("{}", v)),
537 OpcodeArgument::Int64(v) => Some(format!("{}L", v)), OpcodeArgument::Float32(v) => Some(format!("{}f", v)), OpcodeArgument::Float64(v) => Some(format!("{}", v)),
540 OpcodeArgument::String(idx) => {
541 let s = package
542 .get_string_pool()
543 .get(idx as usize)
544 .map(|s| format!("Str({}) -> \"{}\"", idx, s))
545 .unwrap_or_else(|| format!("Str({}) -> <Invalid>", idx));
546 Some(s)
547 }
548 OpcodeArgument::ByteArray(idx) => {
549 let b = package
550 .get_bytes_pool()
551 .get(idx as usize)
552 .map(|b| format!("Bytes({}) -> {:X?}", idx, b))
553 .unwrap_or_else(|| format!("Bytes({}) -> <Invalid>", idx));
554 Some(b)
555 }
556 }
557 };
558
559 let op1_str = format_operand(decoded_opcode.operand1(), raw_operand1);
560 let op2_str = format_operand(decoded_opcode.operand2(), raw_operand2);
561 let op3_str = format_operand(decoded_opcode.operand3(), raw_operand3);
562
563 let instruction_name = VMInstruction::from_opcode(decoded_opcode.instruction())
565 .map(|instr| format!("{:?}", instr))
566 .unwrap_or_else(|| format!("Invalid({})", decoded_opcode.instruction()));
567
568 let parts = vec![op1_str, op2_str, op3_str];
569 let operands_str = parts
571 .into_iter()
572 .filter_map(|p| p)
573 .collect::<Vec<String>>()
574 .join(", ");
575
576 if operands_str.is_empty() {
577 instruction_name
578 } else {
579 format!("{} {}", instruction_name, operands_str)
580 }
581}
582
583#[derive(Debug)]
600struct SourceLocation {
601 pub line: usize,
603 pub column: usize,
605 pub code_snippet: String,
613}
614
615fn get_source_location_for_ip(package: &VMInstructionPackage, ip: usize) -> Option<SourceLocation> {
637 let source = package.get_source().as_ref()?;
638 let debug_info = package.get_debug_info().get(&ip)?;
639
640 let (span_start, span_end) = debug_info.token_span();
641
642 if span_start >= span_end {
643 return None;
644 }
645
646 let (line_idx, col_char_idx) = find_line_and_col_from_source(span_start, source);
647 let line_content = source.lines().nth(line_idx).unwrap_or("");
648
649 let error_token_text: String = source
650 .chars()
651 .skip(span_start)
652 .take(span_end - span_start)
653 .collect();
654
655 let display_offset = line_content
656 .chars()
657 .take(col_char_idx)
658 .collect::<String>()
659 .width();
660
661 let underline_width = error_token_text.width();
662
663 let line_num_width = 5;
664 let line_num = line_idx + 1;
665
666 let code_snippet = format!(
667 " {:>width$} | {}\n {empty:>width$} | {padding}{underline}",
668 line_num,
669 line_content,
670 empty = "",
671 padding = " ".repeat(display_offset),
672 underline = "^".repeat(underline_width),
673 width = line_num_width
674 );
675
676 Some(SourceLocation {
677 line: line_num,
678 column: col_char_idx + 1,
679 code_snippet,
680 })
681}
682
683#[cfg(test)]
684mod size_tests {
685 use super::*;
686
687 #[test]
695 fn print_sizes() {
696 println!("StepResult size: {}", std::mem::size_of::<StepResult>());
697 println!("RuntimeError size: {}", std::mem::size_of::<RuntimeError>());
698 println!("StepResult size: {}", std::mem::size_of::<StepResult>());
699 }
700}