1use std::{ptr::addr_eq, sync::Arc};
2
3use arc_gc::gc::GC;
4use unicode_width::UnicodeWidthStr;
5
6use crate::{
7 lambda::runnable::{Runnable, RuntimeError, StepResult},
8 types::{
9 lambda::vm_instructions::opcode::{
10 self, OpcodeArgument, build_operand_argument, decode_opcode, get_operand_arg,
11 get_processed_opcode,
12 },
13 object::{OnionObject, OnionObjectCell, OnionStaticObject},
14 },
15 utils::{fastmap::OnionFastMap, find_line_and_col_from_source},
16};
17
18use super::{
19 context::{Context, Frame},
20 vm_instructions::{
21 self,
22 instruction_set::{VMInstruction, VMInstructionPackage},
23 opcode::ProcessedOpcode,
24 },
25};
26
27type InstructionHandler =
28 fn(&mut OnionLambdaRunnable, &ProcessedOpcode, &mut GC<OnionObjectCell>) -> StepResult;
29
30static INSTRUCTION_TABLE: std::sync::LazyLock<Vec<InstructionHandler>> =
32 std::sync::LazyLock::new(|| {
33 let mut instruction_table: Vec<InstructionHandler> = vec![
34 |_, opcode, _| StepResult::Error(RuntimeError::DetailedError(format!("Invalid instruction: {:?}", opcode).into())); 256 ];
37
38 instruction_table[VMInstruction::LoadNull as usize] = vm_instructions::load_null;
41 instruction_table[VMInstruction::LoadInt32 as usize] = vm_instructions::load_int;
42 instruction_table[VMInstruction::LoadInt64 as usize] = vm_instructions::load_int;
43 instruction_table[VMInstruction::LoadFloat32 as usize] = vm_instructions::load_float;
44 instruction_table[VMInstruction::LoadFloat64 as usize] = vm_instructions::load_float;
45 instruction_table[VMInstruction::LoadString as usize] = vm_instructions::load_string;
46 instruction_table[VMInstruction::LoadBytes as usize] = vm_instructions::load_bytes;
47 instruction_table[VMInstruction::LoadBool as usize] = vm_instructions::load_bool;
48 instruction_table[VMInstruction::LoadLambda as usize] = vm_instructions::load_lambda;
49 instruction_table[VMInstruction::LoadUndefined as usize] = vm_instructions::load_undefined;
50
51 instruction_table[VMInstruction::BuildTuple as usize] = vm_instructions::build_tuple;
53 instruction_table[VMInstruction::BuildKeyValue as usize] = vm_instructions::build_keyval;
54 instruction_table[VMInstruction::BuildRange as usize] = vm_instructions::build_range;
55 instruction_table[VMInstruction::BuildSet as usize] = vm_instructions::build_set;
56 instruction_table[VMInstruction::BinaryIn as usize] = vm_instructions::is_in;
58 instruction_table[VMInstruction::BinaryIs as usize] = vm_instructions::check_is_same_object;
59
60 instruction_table[VMInstruction::BinaryAdd as usize] = vm_instructions::binary_add;
61 instruction_table[VMInstruction::BinarySub as usize] = vm_instructions::binary_subtract;
62 instruction_table[VMInstruction::BinaryMul as usize] = vm_instructions::binary_multiply;
63 instruction_table[VMInstruction::BinaryDiv as usize] = vm_instructions::binary_divide;
64 instruction_table[VMInstruction::BinaryMod as usize] = vm_instructions::binary_modulus;
65 instruction_table[VMInstruction::BinaryPow as usize] = vm_instructions::binary_power;
66 instruction_table[VMInstruction::BinaryBitAnd as usize] =
67 vm_instructions::binary_bitwise_and;
68 instruction_table[VMInstruction::BinaryBitOr as usize] = vm_instructions::binary_bitwise_or;
69 instruction_table[VMInstruction::BinaryBitXor as usize] =
70 vm_instructions::binary_bitwise_xor;
71 instruction_table[VMInstruction::BinaryShl as usize] = vm_instructions::binary_shift_left;
72 instruction_table[VMInstruction::BinaryShr as usize] = vm_instructions::binary_shift_right;
73 instruction_table[VMInstruction::BinaryEq as usize] = vm_instructions::binary_equal;
74 instruction_table[VMInstruction::BinaryNe as usize] = vm_instructions::binary_not_equal;
75 instruction_table[VMInstruction::BinaryGt as usize] = vm_instructions::binary_greater;
76 instruction_table[VMInstruction::BinaryLt as usize] = vm_instructions::binary_less;
77 instruction_table[VMInstruction::BinaryGe as usize] = vm_instructions::binary_greater_equal;
78 instruction_table[VMInstruction::BinaryLe as usize] = vm_instructions::binary_less_equal;
79 instruction_table[VMInstruction::MapTo as usize] = vm_instructions::map_to;
80
81 instruction_table[VMInstruction::UnaryBitNot as usize] = vm_instructions::unary_bitwise_not;
83 instruction_table[VMInstruction::UnaryAbs as usize] = vm_instructions::unary_plus;
84 instruction_table[VMInstruction::UnaryNeg as usize] = vm_instructions::unary_minus;
85
86 instruction_table[VMInstruction::StoreVar as usize] = vm_instructions::let_var;
88 instruction_table[VMInstruction::LoadVar as usize] = vm_instructions::get_var;
89 instruction_table[VMInstruction::SetValue as usize] = vm_instructions::set_var;
90 instruction_table[VMInstruction::GetAttr as usize] = vm_instructions::get_attr;
91 instruction_table[VMInstruction::KeyOf as usize] = vm_instructions::key_of;
92 instruction_table[VMInstruction::ValueOf as usize] = vm_instructions::value_of;
93 instruction_table[VMInstruction::TypeOf as usize] = vm_instructions::type_of;
94 instruction_table[VMInstruction::Swap as usize] = vm_instructions::swap;
95 instruction_table[VMInstruction::LengthOf as usize] = vm_instructions::get_length;
96 instruction_table[VMInstruction::Mut as usize] = vm_instructions::mutablize;
97 instruction_table[VMInstruction::Const as usize] = vm_instructions::immutablize;
98 instruction_table[VMInstruction::ForkInstruction as usize] =
99 vm_instructions::fork_instruction;
100 instruction_table[VMInstruction::Launch as usize] = vm_instructions::launch_thread;
101 instruction_table[VMInstruction::Spawn as usize] = vm_instructions::spawn_task;
102 instruction_table[VMInstruction::MakeAtomic as usize] = vm_instructions::make_atomic;
103 instruction_table[VMInstruction::MakeAsync as usize] = vm_instructions::make_async;
104 instruction_table[VMInstruction::MakeSync as usize] = vm_instructions::make_sync;
105
106 instruction_table[VMInstruction::Apply as usize] = vm_instructions::apply;
108 instruction_table[VMInstruction::Return as usize] = vm_instructions::return_value;
109 instruction_table[VMInstruction::Raise as usize] = vm_instructions::raise;
110 instruction_table[VMInstruction::Jump as usize] = vm_instructions::jump;
111 instruction_table[VMInstruction::JumpIfFalse as usize] = vm_instructions::jump_if_false;
112
113 instruction_table[VMInstruction::NewFrame as usize] = vm_instructions::new_frame;
115 instruction_table[VMInstruction::PopFrame as usize] = vm_instructions::pop_frame;
116 instruction_table[VMInstruction::ResetStack as usize] = vm_instructions::clear_stack;
117 instruction_table[VMInstruction::Pop as usize] = vm_instructions::discard_top;
118
119 instruction_table[VMInstruction::Import as usize] = vm_instructions::import;
121
122 instruction_table[VMInstruction::Assert as usize] = vm_instructions::assert;
123
124 instruction_table
125 });
126
127pub struct OnionLambdaRunnable {
128 pub(crate) context: Context,
129 pub(crate) ip: isize, pub(crate) ip_before_step: isize, pub(crate) instruction: Arc<VMInstructionPackage>,
132}
133
134impl OnionLambdaRunnable {
135 pub fn new(
136 argument: &OnionFastMap<Box<str>, OnionStaticObject>,
137 capture: &OnionFastMap<Box<str>, OnionObject>,
138 self_object: &OnionObject,
139 this_lambda: &OnionStaticObject,
140 instruction: Arc<VMInstructionPackage>,
141 ip: isize,
142 ) -> Result<Self, RuntimeError> {
143 if !addr_eq(argument.pool().keys(), instruction.get_string_pool()) {
144 panic!(
145 "Argument pool does not match instruction string pool: {:?} != {:?}",
146 argument.pool().keys(),
147 instruction.get_string_pool()
148 );
149 }
150 if !addr_eq(capture.pool().keys(), instruction.get_string_pool()) {
151 panic!(
152 "Capture pool does not match instruction string pool: {:?} != {:?}",
153 capture.pool().keys(),
154 instruction.get_string_pool()
155 );
156 }
157
158 let mut new_context = Context::new();
159 Context::push_frame(
160 &mut new_context,
161 Frame {
162 variables: rustc_hash::FxHashMap::default(),
163 stack: Vec::new(),
164 },
165 );
166
167 let index_this = instruction.get_string_index("this").ok_or_else(|| {
168 RuntimeError::InvalidOperation(
169 "Missing required variable 'this' in string pool"
170 .to_string()
171 .into(),
172 )
173 })?;
174 let index_self = instruction.get_string_index("self").ok_or_else(|| {
175 RuntimeError::InvalidOperation(
176 "Missing required variable 'self' in string pool"
177 .to_string()
178 .into(),
179 )
180 })?;
181 new_context
196 .let_variable(index_this, this_lambda.clone())
197 .map_err(|e| {
198 RuntimeError::InvalidOperation(
199 format!("Failed to initialize 'this' variable: {}", e).into(),
200 )
201 })?;
202
203 new_context
204 .let_variable(index_self, self_object.stabilize())
205 .map_err(|e| {
206 RuntimeError::InvalidOperation(
207 format!("Failed to initialize 'self' variable: {}", e).into(),
208 )
209 })?;
210
211 for (argument_index, value) in argument.pairs() {
220 new_context
221 .let_variable(*argument_index, value.clone())
222 .map_err(|e| {
223 RuntimeError::InvalidOperation(
224 format!("Failed to initialize argument '{}': {}", argument_index, e).into(),
225 )
226 })?;
227 }
228
229 for (capture_index, value) in capture.pairs() {
231 new_context
232 .let_variable(*capture_index, value.stabilize())
233 .map_err(|e| {
234 RuntimeError::InvalidOperation(
235 format!(
236 "Failed to initialize captured variable '{}': {}",
237 capture_index, e
238 )
239 .into(),
240 )
241 })?;
242 }
243
244 Ok(OnionLambdaRunnable {
245 context: new_context,
246 ip,
247 ip_before_step: ip,
248 instruction,
249 })
250 }
251}
252
253impl Runnable for OnionLambdaRunnable {
254 fn receive(
255 &mut self,
256 step_result: &StepResult,
257 _gc: &mut GC<OnionObjectCell>,
258 ) -> Result<(), RuntimeError> {
259 if let StepResult::Return(result) = step_result {
260 self.context.push_object(result.as_ref().clone())?;
261 Ok(())
262 } else {
263 Err(RuntimeError::DetailedError(
264 "receive not implemented for cases except 'Return'"
265 .to_string()
266 .into(),
267 ))
268 }
269 }
270 fn step(&mut self, gc: &mut GC<OnionObjectCell>) -> StepResult {
271 const MAX_INLINE_STEPS: usize = 1024;
272
273 let mut steps = 0;
274
275 let (code_ptr, code_len) = {
277 let code = self.instruction.get_code();
278 (code.as_ptr(), code.len())
279 };
280
281 loop {
282 if steps >= MAX_INLINE_STEPS {
283 break;
284 }
285 steps += 1;
286
287 let mut ip = self.ip as usize;
288 if ip >= code_len {
289 return StepResult::Error(RuntimeError::DetailedError(
290 "Instruction pointer out of bounds".into(),
291 ));
292 }
293
294 let code = unsafe { std::slice::from_raw_parts(code_ptr, code_len) };
296 let pending_ip = ip;
297 let opcode = get_processed_opcode(code, &mut ip);
298
299 let handler = unsafe { *INSTRUCTION_TABLE.get_unchecked(opcode.instruction as usize) };
300 self.ip = ip as isize;
301 self.ip_before_step = pending_ip as isize; match handler(self, &opcode, gc) {
304 StepResult::Continue => continue,
305 StepResult::Error(e) => {
306 self.ip = pending_ip as isize; return StepResult::Error(e);
308 }
309 v => return v,
310 }
311 }
312
313 StepResult::Continue
314 }
315
316 fn format_context(&self) -> String {
317 let ip = self.ip_before_step as usize;
318 let mut parts = Vec::new();
319
320 if let Some(location) = get_source_location_for_ip(&self.instruction, ip) {
322 parts.push(format!("-> at {}:{}", location.line, location.column));
324 parts.push(location.code_snippet);
325 } else {
326 let disassembly = disassemble_instruction(&self.instruction, ip); parts.push(format!("-> Executing VM Code at ip: {}", ip));
329 parts.push(format!(" - Current Instruction: {}", disassembly));
330 }
331
332 let context_state = self.context.format_context(self.instruction.as_ref());
334 parts.push("\n--- Lambda Execution State ---".to_string());
335 parts.push(context_state);
336
337 parts.join("\n")
338 }
339}
340
341pub fn disassemble_instruction(package: &VMInstructionPackage, ip: usize) -> String {
346 let code = package.get_code();
347 if ip >= code.len() {
348 return format!("<IP:{} out of bounds>", ip);
349 }
350
351 let mut temp_ip = ip;
354 let opcode_word = opcode::take_u32(code, &mut temp_ip);
355 let decoded_opcode = decode_opcode(opcode_word);
356
357 let mut next_ip = temp_ip; let raw_operand1 = get_operand_arg(code, &mut next_ip, decoded_opcode.operand1());
361 let raw_operand2 = get_operand_arg(code, &mut next_ip, decoded_opcode.operand2());
362 let raw_operand3 = get_operand_arg(code, &mut next_ip, decoded_opcode.operand3());
363
364 let format_operand = |flag: u8, raw_value: u64| -> Option<String> {
367 let arg = build_operand_argument(flag, raw_value);
369
370 match arg {
371 OpcodeArgument::None => None,
372 OpcodeArgument::Int32(v) => Some(format!("{}", v)),
373 OpcodeArgument::Int64(v) => Some(format!("{}L", v)), OpcodeArgument::Float32(v) => Some(format!("{}f", v)), OpcodeArgument::Float64(v) => Some(format!("{}", v)),
376 OpcodeArgument::String(idx) => {
377 let s = package
378 .get_string_pool()
379 .get(idx as usize)
380 .map(|s| format!("Str({}) -> \"{}\"", idx, s))
381 .unwrap_or_else(|| format!("Str({}) -> <Invalid>", idx));
382 Some(s)
383 }
384 OpcodeArgument::ByteArray(idx) => {
385 let b = package
386 .get_bytes_pool()
387 .get(idx as usize)
388 .map(|b| format!("Bytes({}) -> {:X?}", idx, b))
389 .unwrap_or_else(|| format!("Bytes({}) -> <Invalid>", idx));
390 Some(b)
391 }
392 }
393 };
394
395 let op1_str = format_operand(decoded_opcode.operand1(), raw_operand1);
396 let op2_str = format_operand(decoded_opcode.operand2(), raw_operand2);
397 let op3_str = format_operand(decoded_opcode.operand3(), raw_operand3);
398
399 let instruction_name = VMInstruction::from_opcode(decoded_opcode.instruction())
401 .map(|instr| format!("{:?}", instr))
402 .unwrap_or_else(|| format!("Invalid({})", decoded_opcode.instruction()));
403
404 let parts = vec![op1_str, op2_str, op3_str];
405 let operands_str = parts
407 .into_iter()
408 .filter_map(|p| p)
409 .collect::<Vec<String>>()
410 .join(", ");
411
412 if operands_str.is_empty() {
413 instruction_name
414 } else {
415 format!("{} {}", instruction_name, operands_str)
416 }
417}
418#[derive(Debug)]
419pub struct SourceLocation {
420 pub line: usize,
421 pub column: usize,
422 pub code_snippet: String,
426}
427
428pub fn get_source_location_for_ip(
429 package: &VMInstructionPackage,
430 ip: usize,
431) -> Option<SourceLocation> {
432 let source = package.get_source().as_ref()?;
433 let debug_info = package.get_debug_info().get(&ip)?;
434
435 let (span_start, span_end) = debug_info.token_span();
436
437 if span_start >= span_end {
438 return None;
439 }
440
441 let (line_idx, col_char_idx) = find_line_and_col_from_source(span_start, source);
442 let line_content = source.lines().nth(line_idx).unwrap_or("");
443
444 let error_token_text: String = source
445 .chars()
446 .skip(span_start)
447 .take(span_end - span_start)
448 .collect();
449
450 let display_offset = line_content
451 .chars()
452 .take(col_char_idx)
453 .collect::<String>()
454 .width();
455
456 let underline_width = error_token_text.width();
457
458 let line_num_width = 5;
459 let line_num = line_idx + 1;
460
461 let code_snippet = format!(
462 " {:>width$} | {}\n {empty:>width$} | {padding}{underline}",
463 line_num,
464 line_content,
465 empty = "",
466 padding = " ".repeat(display_offset),
467 underline = "^".repeat(underline_width),
468 width = line_num_width
469 );
470
471 Some(SourceLocation {
472 line: line_num,
473 column: col_char_idx + 1,
474 code_snippet,
475 })
476}
477#[cfg(test)]
478mod size_tests {
479 use super::*;
480
481 #[test]
482 fn print_sizes() {
483 println!("StepResult size: {}", std::mem::size_of::<StepResult>());
484 println!("RuntimeError size: {}", std::mem::size_of::<RuntimeError>());
485 println!("StepResult size: {}", std::mem::size_of::<StepResult>());
486 }
487}