use crate::vm::lambda::Lambda;
use crate::vm::vcell::VCell;
use crate::vm::Error::InvalidBytecode;
use crate::vm::{Error, Vm};
use lazy_static::lazy_static;
use std::collections::HashMap;
use std::fmt::{Display, Formatter};
use std::iter::Peekable;
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
pub enum OpCode {
Jmp,
Jnt,
Mov,
MovImmediate,
Push,
PushAcc,
PushImmediate,
Halt,
CallAcc,
ClosureAcc,
Enter,
Ret,
TCallAcc,
VarArg,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Operand {
Immediate,
StoreReference,
LoadReference,
Acc,
}
struct Schema {
name: &'static str,
operands: Vec<Operand>,
}
impl Schema {
fn new(name: &'static str, operands: Vec<Operand>) -> Schema {
Schema { name, operands }
}
}
#[rustfmt::skip]
fn schema() -> &'static HashMap<OpCode, Schema> {
lazy_static! {
static ref SCHEMA: HashMap<OpCode, Schema> = HashMap::from([
(OpCode::CallAcc, Schema::new("CALL", vec![Operand::Acc])),
(OpCode::ClosureAcc, Schema::new("CLOSURE", vec![Operand::Acc])),
(OpCode::Enter, Schema::new("ENTER", vec![])),
(OpCode::Halt, Schema::new("HALT", vec![])),
(OpCode::Jmp, Schema::new("JMP", vec![Operand::Immediate])),
(OpCode::Jnt, Schema::new("JNT", vec![Operand::Immediate])),
(OpCode::Mov, Schema::new("MOV", vec![Operand::LoadReference, Operand::StoreReference])),
(OpCode::MovImmediate, Schema::new("MOV", vec![Operand::Immediate, Operand::StoreReference])),
(OpCode::Push, Schema::new("PUSH", vec![Operand::LoadReference])),
(OpCode::PushImmediate, Schema::new("PUSH", vec![Operand::Immediate])),
(OpCode::PushAcc, Schema::new("PUSH", vec![Operand::Acc])),
(OpCode::Ret, Schema::new("RET", vec![])),
(OpCode::TCallAcc, Schema::new("TCALL", vec![Operand::Acc])),
(OpCode::VarArg, Schema::new("VARARG", vec![]))
]);
}
&SCHEMA
}
#[derive(Debug)]
pub struct DecompiledInstruction {
op: &'static str,
operands: Vec<(VCell, Operand)>,
values: Vec<String>,
}
impl Display for DecompiledInstruction {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let op = self.op;
let operands = self
.operands
.iter()
.map(|it| match it.1 {
Operand::StoreReference | Operand::LoadReference => match it.0 {
VCell::Acc => it.0.to_string(),
_ => format!("[{}]", it.0),
},
_ => it.0.to_string(),
})
.collect::<Vec<_>>();
let values = &self.values;
if !operands.is_empty() && !values.is_empty() {
write!(
f,
"{0: <8} {1: <12} {2: <12} //{3: <10}",
op,
operands.get(0).unwrap_or(&"".to_string()),
operands.get(1).unwrap_or(&"".to_string()),
values.join(" ")
)
} else if !operands.is_empty() {
write!(
f,
"{0: <8} {1: <12} {2: <12}",
op,
operands.get(0).unwrap_or(&"".to_string()),
operands.get(1).unwrap_or(&"".to_string()),
)
} else {
write!(f, "{}", op)
}
}
}
impl Vm {
pub fn decompile_text(&self, lambda: &Lambda) -> String {
let mut text = String::new();
for instruction in self.decompile(lambda).unwrap() {
text.push_str(&format!("{}\n", instruction));
}
text
}
pub fn decompile(&self, lambda: &Lambda) -> Result<Vec<DecompiledInstruction>, Error> {
let mut cur = lambda.bc.iter().peekable();
let mut instructions = vec![];
while cur.peek().is_some() {
instructions.push(self.decompile_one(&mut cur)?);
}
Ok(instructions)
}
pub fn decompile_one<'a, T: Iterator<Item = &'a VCell>>(
&self,
iter: &mut Peekable<T>,
) -> Result<DecompiledInstruction, Error> {
if let Some(VCell::OpCode(opcode)) = iter.next() {
let schema = schema().get(opcode).expect("unknown opcode");
let mut operands = vec![];
let mut values = vec![];
for it in &schema.operands {
let operand = match it {
Operand::Acc => &VCell::Acc,
_ => iter.next().expect("expected operand"),
};
operands.push((operand.clone(), it.clone()));
if values.is_empty() {
match operand {
VCell::Ptr(_) => {
values.push(self.heap.get_as_cell(operand).to_string());
}
VCell::GlobalEnvSlot(slot) => {
let ptr = self.globenv.get_slot(*slot);
values.push(self.heap.get_as_cell(&ptr).to_string());
}
_ => {}
}
}
}
Ok(DecompiledInstruction {
op: schema.name,
operands,
values,
})
} else {
Err(InvalidBytecode)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mov() {
{
let mut vm = Vm::new();
let ptr = vm.heap.put(VCell::Bool(true));
vm.ip.0 = vm
.heap
.put(Lambda::from(vec![
OpCode::MovImmediate.into(),
ptr.clone(),
VCell::Acc,
OpCode::Halt.into(),
]))
.as_ptr()
.unwrap();
vm.ip.1 = 0;
assert!(vm.run().is_ok());
assert_eq!(vm.acc, ptr);
}
{
let mut vm = Vm::new();
let ptr = vm.heap.put(VCell::Bool(true));
vm.ip.0 = vm
.heap
.put(Lambda::from(vec![
OpCode::Mov.into(),
ptr.clone(),
VCell::Acc,
OpCode::Halt.into(),
]))
.as_ptr()
.unwrap();
vm.ip.1 = 0;
assert!(vm.run().is_ok());
assert_eq!(vm.acc, VCell::Bool(true));
}
}
}