use std::{collections::HashMap, fmt};
use super::Instruction;
use crate::{
TulispObject,
bytecode::compiler::VMDefunParams,
object::wrappers::generic::{Shared, SharedMut},
};
#[derive(Clone, Debug)]
pub(crate) struct TraceRange {
pub start_pc: usize,
pub end_pc: usize,
pub form: TulispObject,
}
#[doc(hidden)]
#[derive(Clone)]
pub struct CompiledDefun {
pub(crate) name: TulispObject,
pub(crate) instructions: SharedMut<Vec<Instruction>>,
pub(crate) trace_ranges: Shared<Vec<TraceRange>>,
pub(crate) params: VMDefunParams,
}
#[derive(Clone)]
pub(crate) struct Bytecode {
pub(crate) global: SharedMut<Vec<Instruction>>,
pub(crate) global_trace_ranges: Shared<Vec<TraceRange>>,
pub(crate) functions: HashMap<usize, CompiledDefun>, }
impl Default for Bytecode {
fn default() -> Self {
Self {
global: SharedMut::default(),
global_trace_ranges: Shared::new_sized(Vec::new()),
functions: HashMap::default(),
}
}
}
impl fmt::Display for Bytecode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, instr) in self.global.borrow().iter().enumerate() {
write!(f, "\n{:<40} # {}", instr.to_string(), i)?;
}
for (key, func) in &self.functions {
write!(f, "\n\n{} (#{}):", func.name, key)?;
for (i, instr) in func.instructions.borrow().iter().enumerate() {
write!(f, "\n{:<40} # {}", instr.to_string(), i)?;
}
}
Ok(())
}
}
impl Bytecode {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn import_functions(&mut self, other: &Bytecode) {
self.functions.extend(other.functions.clone());
}
}
pub(crate) fn strip_trace_markers(input: Vec<Instruction>) -> (Vec<Instruction>, Vec<TraceRange>) {
use crate::bytecode::instruction::Pos;
let mut shift: Vec<usize> = Vec::with_capacity(input.len() + 1);
let mut cumulative = 0usize;
for instr in &input {
shift.push(cumulative);
if matches!(instr, Instruction::PushTrace(_) | Instruction::PopTrace) {
cumulative += 1;
}
}
shift.push(cumulative);
let mut output: Vec<Instruction> = Vec::with_capacity(input.len() - cumulative);
let mut ranges: Vec<TraceRange> = Vec::new();
let mut stack: Vec<(usize, TulispObject)> = Vec::new();
for (orig_pc, mut instr) in input.into_iter().enumerate() {
match &instr {
Instruction::PushTrace(form) => {
stack.push((output.len(), form.clone()));
continue;
}
Instruction::PopTrace => {
let (start, form) = stack
.pop()
.expect("PopTrace without matching PushTrace in compiled bytecode");
ranges.push(TraceRange {
start_pc: start,
end_pc: output.len(),
form,
});
continue;
}
_ => {}
}
let pos_field: Option<&mut Pos> = match &mut instr {
Instruction::JumpIfNil(p)
| Instruction::JumpIfNotNil(p)
| Instruction::JumpIfNilElsePop(p)
| Instruction::JumpIfNotNilElsePop(p)
| Instruction::JumpIfNeq(p)
| Instruction::JumpIfLt(p)
| Instruction::JumpIfLtEq(p)
| Instruction::JumpIfGt(p)
| Instruction::JumpIfGtEq(p)
| Instruction::Jump(p) => Some(p),
_ => None,
};
if let Some(Pos::Rel(rel)) = pos_field {
let orig_target = (orig_pc as isize + *rel + 1) as usize;
let new_pc = orig_pc - shift[orig_pc];
let new_target = orig_target - shift[orig_target];
*rel = new_target as isize - new_pc as isize - 1;
}
output.push(instr);
}
debug_assert!(
stack.is_empty(),
"PushTrace without matching PopTrace in compiled bytecode"
);
(output, ranges)
}