use std::collections::{BTreeSet, HashMap};
use super::functions::{estimate_max_stack_depth, opcode_kind};
#[derive(Clone, Debug, Default)]
#[allow(dead_code)]
pub struct ChunkStats {
pub total_instructions: usize,
pub branch_count: usize,
pub arith_count: usize,
pub mem_count: usize,
pub call_count: usize,
pub max_stack_depth: usize,
}
impl ChunkStats {
pub fn compute(chunk: &BytecodeChunk) -> Self {
let mut stats = ChunkStats::default();
stats.total_instructions = chunk.opcodes.len();
stats.max_stack_depth = estimate_max_stack_depth(chunk);
for op in &chunk.opcodes {
match op {
Opcode::Jump(_) | Opcode::JumpIf(_) | Opcode::JumpIfNot(_) => {
stats.branch_count += 1;
}
Opcode::Add
| Opcode::Sub
| Opcode::Mul
| Opcode::Div
| Opcode::Mod
| Opcode::Eq
| Opcode::Lt
| Opcode::Le
| Opcode::Not
| Opcode::And
| Opcode::Or => {
stats.arith_count += 1;
}
Opcode::Load(_) | Opcode::Store(_) | Opcode::LoadGlobal(_) => {
stats.mem_count += 1;
}
Opcode::Call(_) | Opcode::Return => {
stats.call_count += 1;
}
_ => {}
}
}
stats
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct CallFrame {
pub return_ip: usize,
pub locals_base: usize,
pub fn_name: String,
}
impl CallFrame {
pub fn new(return_ip: usize, locals_base: usize, fn_name: impl Into<String>) -> Self {
CallFrame {
return_ip,
locals_base,
fn_name: fn_name.into(),
}
}
}
#[derive(Clone, Debug)]
#[allow(dead_code)]
pub struct InlineCacheEntry {
pub call_ip: usize,
pub hit_count: u64,
pub last_target: Option<u32>,
pub is_monomorphic: bool,
}
impl InlineCacheEntry {
pub fn new(call_ip: usize) -> Self {
InlineCacheEntry {
call_ip,
hit_count: 0,
last_target: None,
is_monomorphic: true,
}
}
pub fn record_call(&mut self, target_fn: u32) {
self.hit_count += 1;
match self.last_target {
None => {
self.last_target = Some(target_fn);
}
Some(prev) if prev != target_fn => {
self.is_monomorphic = false;
}
_ => {}
}
}
}
pub struct ChunkBuilder {
chunk: BytecodeChunk,
}
impl ChunkBuilder {
pub fn new(name: &str) -> Self {
ChunkBuilder {
chunk: BytecodeChunk::new(name),
}
}
pub fn push_nat(mut self, n: u64) -> Self {
self.chunk.push_op(Opcode::Push(n));
self
}
pub fn push_bool(mut self, b: bool) -> Self {
self.chunk.push_op(Opcode::PushBool(b));
self
}
pub fn push_str(mut self, s: &str) -> Self {
self.chunk.push_op(Opcode::PushStr(s.to_string()));
self
}
pub fn push_nil(mut self) -> Self {
self.chunk.push_op(Opcode::PushNil);
self
}
pub fn add(mut self) -> Self {
self.chunk.push_op(Opcode::Add);
self
}
pub fn sub(mut self) -> Self {
self.chunk.push_op(Opcode::Sub);
self
}
pub fn mul(mut self) -> Self {
self.chunk.push_op(Opcode::Mul);
self
}
pub fn div(mut self) -> Self {
self.chunk.push_op(Opcode::Div);
self
}
pub fn modulo(mut self) -> Self {
self.chunk.push_op(Opcode::Mod);
self
}
pub fn eq(mut self) -> Self {
self.chunk.push_op(Opcode::Eq);
self
}
pub fn lt(mut self) -> Self {
self.chunk.push_op(Opcode::Lt);
self
}
pub fn le(mut self) -> Self {
self.chunk.push_op(Opcode::Le);
self
}
pub fn not(mut self) -> Self {
self.chunk.push_op(Opcode::Not);
self
}
pub fn and(mut self) -> Self {
self.chunk.push_op(Opcode::And);
self
}
pub fn or(mut self) -> Self {
self.chunk.push_op(Opcode::Or);
self
}
pub fn dup(mut self) -> Self {
self.chunk.push_op(Opcode::Dup);
self
}
pub fn pop(mut self) -> Self {
self.chunk.push_op(Opcode::Pop);
self
}
pub fn swap(mut self) -> Self {
self.chunk.push_op(Opcode::Swap);
self
}
pub fn load(mut self, idx: u32) -> Self {
self.chunk.push_op(Opcode::Load(idx));
self
}
pub fn store(mut self, idx: u32) -> Self {
self.chunk.push_op(Opcode::Store(idx));
self
}
pub fn load_global(mut self, name: &str) -> Self {
self.chunk.push_op(Opcode::LoadGlobal(name.to_string()));
self
}
pub fn jump(mut self, offset: i32) -> Self {
self.chunk.push_op(Opcode::Jump(offset));
self
}
pub fn jump_if(mut self, offset: i32) -> Self {
self.chunk.push_op(Opcode::JumpIf(offset));
self
}
pub fn jump_if_not(mut self, offset: i32) -> Self {
self.chunk.push_op(Opcode::JumpIfNot(offset));
self
}
pub fn call(mut self, pos: u32) -> Self {
self.chunk.push_op(Opcode::Call(pos));
self
}
pub fn ret(mut self) -> Self {
self.chunk.push_op(Opcode::Return);
self
}
pub fn make_closure(mut self, n: u32) -> Self {
self.chunk.push_op(Opcode::MakeClosure(n));
self
}
pub fn apply(mut self) -> Self {
self.chunk.push_op(Opcode::Apply);
self
}
pub fn halt(mut self) -> Self {
self.chunk.push_op(Opcode::Halt);
self
}
pub fn build(self) -> BytecodeChunk {
self.chunk
}
pub fn current_ip(&self) -> usize {
self.chunk.opcodes.len()
}
}
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
pub enum CallResult {
Exact { fn_pos: u32, args: Vec<StackValue> },
Partial {
captured: Vec<StackValue>,
remaining_arity: u32,
},
Over {
fn_pos: u32,
first_args: Vec<StackValue>,
rest_args: Vec<StackValue>,
},
}
pub struct DeadCodeEliminator;
impl DeadCodeEliminator {
pub fn eliminate(chunk: &BytecodeChunk) -> BytecodeChunk {
let n = chunk.opcodes.len();
let mut reachable = vec![false; n];
let mut worklist = vec![0usize];
while let Some(ip) = worklist.pop() {
if ip >= n || reachable[ip] {
continue;
}
reachable[ip] = true;
let op = &chunk.opcodes[ip];
match op {
Opcode::Halt | Opcode::Return => {}
Opcode::Jump(off) => {
let target = (ip as i64 + 1 + *off as i64) as usize;
if target < n {
worklist.push(target);
}
}
Opcode::JumpIf(off) | Opcode::JumpIfNot(off) => {
let target = (ip as i64 + 1 + *off as i64) as usize;
if target < n {
worklist.push(target);
}
worklist.push(ip + 1);
}
_ => {
worklist.push(ip + 1);
}
}
}
let mut out = BytecodeChunk::new(&chunk.name);
for (i, op) in chunk.opcodes.iter().enumerate() {
if reachable[i] {
out.push_op(op.clone());
}
}
out
}
}
#[derive(Default, Debug)]
#[allow(dead_code)]
pub struct InlineCache {
entries: std::collections::HashMap<usize, InlineCacheEntry>,
}
impl InlineCache {
pub fn new() -> Self {
InlineCache::default()
}
pub fn record(&mut self, call_ip: usize, target_fn: u32) {
self.entries
.entry(call_ip)
.or_insert_with(|| InlineCacheEntry::new(call_ip))
.record_call(target_fn);
}
pub fn entry(&self, call_ip: usize) -> Option<&InlineCacheEntry> {
self.entries.get(&call_ip)
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn monomorphic_sites(&self) -> Vec<usize> {
self.entries
.values()
.filter(|e| e.is_monomorphic && e.last_target.is_some())
.map(|e| e.call_ip)
.collect()
}
}
#[derive(Default, Clone, Debug)]
#[allow(dead_code)]
pub struct OpcodeProfile {
counts: std::collections::HashMap<String, u64>,
pub total_executed: u64,
}
impl OpcodeProfile {
pub fn new() -> Self {
OpcodeProfile::default()
}
pub fn record(&mut self, op: &Opcode) {
let key = opcode_kind(op);
*self.counts.entry(key).or_insert(0) += 1;
self.total_executed += 1;
}
pub fn count(&self, kind: &str) -> u64 {
self.counts.get(kind).copied().unwrap_or(0)
}
pub fn top_opcodes(&self) -> Vec<(String, u64)> {
let mut v: Vec<_> = self.counts.iter().map(|(k, v)| (k.clone(), *v)).collect();
v.sort_by_key(|b| std::cmp::Reverse(b.1));
v
}
pub fn reset(&mut self) {
self.counts.clear();
self.total_executed = 0;
}
}
pub struct FramedInterpreter {
pub stack: Vec<StackValue>,
pub locals: Vec<StackValue>,
pub frames: Vec<CallFrame>,
pub ip: usize,
pub max_depth: usize,
}
impl FramedInterpreter {
pub fn new() -> Self {
FramedInterpreter {
stack: Vec::new(),
locals: Vec::new(),
frames: Vec::new(),
ip: 0,
max_depth: 256,
}
}
pub fn with_max_depth(mut self, d: usize) -> Self {
self.max_depth = d;
self
}
pub fn push_frame(&mut self, return_ip: usize, fn_name: &str) -> Result<(), String> {
if self.max_depth > 0 && self.frames.len() >= self.max_depth {
return Err(format!(
"call stack overflow (max depth {})",
self.max_depth
));
}
let base = self.locals.len();
self.frames.push(CallFrame::new(return_ip, base, fn_name));
Ok(())
}
pub fn pop_frame(&mut self) -> Option<usize> {
let frame = self.frames.pop()?;
self.locals.truncate(frame.locals_base);
Some(frame.return_ip)
}
pub fn depth(&self) -> usize {
self.frames.len()
}
pub fn frame(&self, depth: usize) -> Option<&CallFrame> {
self.frames.get(self.frames.len().saturating_sub(1 + depth))
}
pub fn stack_trace(&self) -> String {
let mut out = String::new();
for (i, frame) in self.frames.iter().rev().enumerate() {
out.push_str(&format!(
" #{}: {} (return @{})\n",
i, frame.fn_name, frame.return_ip
));
}
out
}
pub fn reset(&mut self) {
self.stack.clear();
self.locals.clear();
self.frames.clear();
self.ip = 0;
}
}
pub struct BytecodeChunk {
pub opcodes: Vec<Opcode>,
pub name: String,
}
impl BytecodeChunk {
pub fn new(name: &str) -> Self {
BytecodeChunk {
opcodes: Vec::new(),
name: name.to_string(),
}
}
pub fn push_op(&mut self, op: Opcode) {
self.opcodes.push(op);
}
pub fn len(&self) -> usize {
self.opcodes.len()
}
pub fn is_empty(&self) -> bool {
self.opcodes.is_empty()
}
}
#[derive(Clone, Debug)]
#[allow(dead_code)]
pub struct OpcodeInfo {
pub tag: u8,
pub mnemonic: &'static str,
pub byte_size: usize,
pub is_branch: bool,
pub is_terminator: bool,
}
#[derive(Clone, Debug)]
#[allow(dead_code)]
pub struct BasicBlock {
pub start: usize,
pub end: usize,
pub successors: Vec<usize>,
}
impl BasicBlock {
pub fn len(&self) -> usize {
self.end - self.start
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
pub struct ConstantFolder;
impl ConstantFolder {
pub fn fold(chunk: &BytecodeChunk) -> BytecodeChunk {
let folded = Self::fold_ops(&chunk.opcodes);
let mut out = BytecodeChunk::new(&chunk.name);
for op in folded {
out.push_op(op);
}
out
}
fn fold_ops(ops: &[Opcode]) -> Vec<Opcode> {
let mut result: Vec<Opcode> = Vec::new();
let mut const_stack: Vec<Option<StackValue>> = Vec::new();
for op in ops {
match op {
Opcode::Push(n) => {
result.push(op.clone());
const_stack.push(Some(StackValue::Nat(*n)));
}
Opcode::PushBool(b) => {
result.push(op.clone());
const_stack.push(Some(StackValue::Bool(*b)));
}
Opcode::PushStr(s) => {
result.push(op.clone());
const_stack.push(Some(StackValue::Str(s.clone())));
}
Opcode::PushNil => {
result.push(op.clone());
const_stack.push(Some(StackValue::Nil));
}
Opcode::Add => {
let b = const_stack.pop().flatten();
let a = const_stack.pop().flatten();
if let (Some(StackValue::Nat(av)), Some(StackValue::Nat(bv))) = (a, b) {
let len = result.len();
if len >= 2 {
result.truncate(len - 2);
}
let folded = av.wrapping_add(bv);
result.push(Opcode::Push(folded));
const_stack.push(Some(StackValue::Nat(folded)));
} else {
result.push(op.clone());
const_stack.push(None);
}
}
Opcode::Mul => {
let b = const_stack.pop().flatten();
let a = const_stack.pop().flatten();
if let (Some(StackValue::Nat(av)), Some(StackValue::Nat(bv))) = (a, b) {
let len = result.len();
if len >= 2 {
result.truncate(len - 2);
}
let folded = av.wrapping_mul(bv);
result.push(Opcode::Push(folded));
const_stack.push(Some(StackValue::Nat(folded)));
} else {
result.push(op.clone());
const_stack.push(None);
}
}
Opcode::Sub => {
let b = const_stack.pop().flatten();
let a = const_stack.pop().flatten();
if let (Some(StackValue::Nat(av)), Some(StackValue::Nat(bv))) = (a, b) {
let len = result.len();
if len >= 2 {
result.truncate(len - 2);
}
let folded = av.wrapping_sub(bv);
result.push(Opcode::Push(folded));
const_stack.push(Some(StackValue::Nat(folded)));
} else {
result.push(op.clone());
const_stack.push(None);
}
}
Opcode::Not => {
let a = const_stack.pop().flatten();
if let Some(StackValue::Bool(bv)) = a {
let len = result.len();
if len >= 1 {
result.truncate(len - 1);
}
result.push(Opcode::PushBool(!bv));
const_stack.push(Some(StackValue::Bool(!bv)));
} else {
result.push(op.clone());
const_stack.push(None);
}
}
Opcode::Pop => {
const_stack.pop();
result.push(op.clone());
}
Opcode::Dup => {
let top = const_stack.last().cloned().flatten();
result.push(op.clone());
const_stack.push(top.map(Some).unwrap_or(None));
}
Opcode::Swap => {
let len = const_stack.len();
if len >= 2 {
const_stack.swap(len - 1, len - 2);
}
result.push(op.clone());
}
_ => {
result.push(op.clone());
const_stack.push(None);
}
}
}
result
}
}
#[derive(Clone, Debug, Default)]
#[allow(dead_code)]
pub struct LivenessInfo {
pub live_before: Vec<std::collections::BTreeSet<u32>>,
}
pub struct ProfilingInterpreter {
pub interp: Interpreter,
pub profile: OpcodeProfile,
}
impl ProfilingInterpreter {
pub fn new() -> Self {
ProfilingInterpreter {
interp: Interpreter::new(),
profile: OpcodeProfile::new(),
}
}
pub fn execute_chunk(&mut self, chunk: &BytecodeChunk) -> Result<StackValue, String> {
self.interp.ip = 0;
loop {
if self.interp.ip >= chunk.opcodes.len() {
break;
}
let op = chunk.opcodes[self.interp.ip].clone();
self.interp.ip += 1;
self.profile.record(&op);
let cont = self.interp.execute_op(&op, chunk)?;
if !cont {
break;
}
}
self.interp
.pop()
.ok_or_else(|| "stack underflow at end of chunk".to_string())
}
}
pub struct PeepholeOptimizer {
pub passes: usize,
}
impl PeepholeOptimizer {
pub fn new(passes: usize) -> Self {
PeepholeOptimizer {
passes: passes.max(1),
}
}
pub fn optimize(&self, chunk: &BytecodeChunk) -> BytecodeChunk {
let mut ops = chunk.opcodes.clone();
for _ in 0..self.passes {
ops = Self::run_pass(&ops);
}
let mut out = BytecodeChunk::new(&chunk.name);
for op in ops {
out.push_op(op);
}
out
}
fn run_pass(ops: &[Opcode]) -> Vec<Opcode> {
let mut result = Vec::with_capacity(ops.len());
let mut i = 0;
while i < ops.len() {
if i + 1 < ops.len() {
if let (Opcode::Push(_), Opcode::Pop) = (&ops[i], &ops[i + 1]) {
i += 2;
continue;
}
}
if i + 2 < ops.len() {
if let (Opcode::PushBool(a), Opcode::PushBool(b), Opcode::And) =
(&ops[i], &ops[i + 1], &ops[i + 2])
{
result.push(Opcode::PushBool(*a && *b));
i += 3;
continue;
}
}
if i + 2 < ops.len() {
if let (Opcode::PushBool(a), Opcode::PushBool(b), Opcode::Or) =
(&ops[i], &ops[i + 1], &ops[i + 2])
{
result.push(Opcode::PushBool(*a || *b));
i += 3;
continue;
}
}
if i + 1 < ops.len() {
if let (Opcode::PushBool(b), Opcode::Not) = (&ops[i], &ops[i + 1]) {
result.push(Opcode::PushBool(!*b));
i += 2;
continue;
}
}
if i + 2 < ops.len() {
if let (Opcode::Push(a), Opcode::Push(b), Opcode::Add) =
(&ops[i], &ops[i + 1], &ops[i + 2])
{
result.push(Opcode::Push(a.wrapping_add(*b)));
i += 3;
continue;
}
}
if i + 2 < ops.len() {
if let (Opcode::Push(a), Opcode::Push(b), Opcode::Mul) =
(&ops[i], &ops[i + 1], &ops[i + 2])
{
result.push(Opcode::Push(a.wrapping_mul(*b)));
i += 3;
continue;
}
}
if i + 1 < ops.len() {
if let (Opcode::Dup, Opcode::Pop) = (&ops[i], &ops[i + 1]) {
i += 2;
continue;
}
}
result.push(ops[i].clone());
i += 1;
}
result
}
}
pub struct BytecodeCompiler;
impl BytecodeCompiler {
pub fn new() -> Self {
BytecodeCompiler
}
pub fn compile_nat(n: u64) -> BytecodeChunk {
let mut chunk = BytecodeChunk::new("nat");
chunk.push_op(Opcode::Push(n));
chunk.push_op(Opcode::Halt);
chunk
}
pub fn compile_add(a: u64, b: u64) -> BytecodeChunk {
let mut chunk = BytecodeChunk::new("add");
chunk.push_op(Opcode::Push(a));
chunk.push_op(Opcode::Push(b));
chunk.push_op(Opcode::Add);
chunk.push_op(Opcode::Halt);
chunk
}
pub fn compile_if(cond: bool, then_val: u64, else_val: u64) -> BytecodeChunk {
let mut chunk = BytecodeChunk::new("if");
chunk.push_op(Opcode::PushBool(cond));
chunk.push_op(Opcode::JumpIfNot(2));
chunk.push_op(Opcode::Push(then_val));
chunk.push_op(Opcode::Jump(1));
chunk.push_op(Opcode::Push(else_val));
chunk.push_op(Opcode::Halt);
chunk
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum StackValue {
Nat(u64),
Int(i64),
Bool(bool),
Str(String),
Closure {
code: Vec<Opcode>,
env: Vec<StackValue>,
},
Nil,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Opcode {
Push(u64),
PushBool(bool),
PushStr(String),
PushNil,
Pop,
Dup,
Swap,
Add,
Sub,
Mul,
Div,
Mod,
Eq,
Lt,
Le,
Not,
And,
Or,
Jump(i32),
JumpIf(i32),
JumpIfNot(i32),
Call(u32),
Return,
Load(u32),
Store(u32),
LoadGlobal(String),
MakeClosure(u32),
Apply,
Halt,
}
pub struct Interpreter {
pub stack: Vec<StackValue>,
pub locals: Vec<StackValue>,
pub ip: usize,
pub call_stack: Vec<usize>,
}
impl Interpreter {
pub fn new() -> Self {
Interpreter {
stack: Vec::new(),
locals: Vec::with_capacity(64),
ip: 0,
call_stack: Vec::new(),
}
}
pub fn push(&mut self, v: StackValue) {
self.stack.push(v);
}
pub fn pop(&mut self) -> Option<StackValue> {
self.stack.pop()
}
pub fn peek(&self) -> Option<&StackValue> {
self.stack.last()
}
pub fn execute_chunk(&mut self, chunk: &BytecodeChunk) -> Result<StackValue, String> {
self.ip = 0;
loop {
if self.ip >= chunk.opcodes.len() {
break;
}
let op = chunk.opcodes[self.ip].clone();
self.ip += 1;
let cont = self.execute_op(&op, chunk)?;
if !cont {
break;
}
}
self.pop()
.ok_or_else(|| "stack underflow at end of chunk".to_string())
}
pub fn execute_op(&mut self, op: &Opcode, _chunk: &BytecodeChunk) -> Result<bool, String> {
match op {
Opcode::Push(n) => {
self.stack.push(StackValue::Nat(*n));
}
Opcode::PushBool(b) => {
self.stack.push(StackValue::Bool(*b));
}
Opcode::PushStr(s) => {
self.stack.push(StackValue::Str(s.clone()));
}
Opcode::PushNil => {
self.stack.push(StackValue::Nil);
}
Opcode::Pop => {
self.stack.pop();
}
Opcode::Dup => {
let top = self.stack.last().ok_or("Dup: stack underflow")?.clone();
self.stack.push(top);
}
Opcode::Swap => {
let len = self.stack.len();
if len < 2 {
return Err("Swap: stack underflow".to_string());
}
self.stack.swap(len - 1, len - 2);
}
Opcode::Add => {
let b = self.pop_nat("Add")?;
let a = self.pop_nat("Add")?;
self.stack.push(StackValue::Nat(a.wrapping_add(b)));
}
Opcode::Sub => {
let b = self.pop_nat("Sub")?;
let a = self.pop_nat("Sub")?;
self.stack.push(StackValue::Nat(a.wrapping_sub(b)));
}
Opcode::Mul => {
let b = self.pop_nat("Mul")?;
let a = self.pop_nat("Mul")?;
self.stack.push(StackValue::Nat(a.wrapping_mul(b)));
}
Opcode::Div => {
let b = self.pop_nat("Div")?;
let a = self.pop_nat("Div")?;
if b == 0 {
return Err("division by zero".to_string());
}
self.stack.push(StackValue::Nat(a / b));
}
Opcode::Mod => {
let b = self.pop_nat("Mod")?;
let a = self.pop_nat("Mod")?;
if b == 0 {
return Err("modulo by zero".to_string());
}
self.stack.push(StackValue::Nat(a % b));
}
Opcode::Eq => {
let b = self.pop().ok_or("Eq: stack underflow")?;
let a = self.pop().ok_or("Eq: stack underflow")?;
self.stack.push(StackValue::Bool(a == b));
}
Opcode::Lt => {
let b = self.pop_nat("Lt")?;
let a = self.pop_nat("Lt")?;
self.stack.push(StackValue::Bool(a < b));
}
Opcode::Le => {
let b = self.pop_nat("Le")?;
let a = self.pop_nat("Le")?;
self.stack.push(StackValue::Bool(a <= b));
}
Opcode::Not => {
let v = self.pop_bool("Not")?;
self.stack.push(StackValue::Bool(!v));
}
Opcode::And => {
let b = self.pop_bool("And")?;
let a = self.pop_bool("And")?;
self.stack.push(StackValue::Bool(a && b));
}
Opcode::Or => {
let b = self.pop_bool("Or")?;
let a = self.pop_bool("Or")?;
self.stack.push(StackValue::Bool(a || b));
}
Opcode::Jump(offset) => {
let new_ip = self.ip as i64 + *offset as i64;
if new_ip < 0 {
return Err(format!("Jump: negative IP {}", new_ip));
}
self.ip = new_ip as usize;
}
Opcode::JumpIf(offset) => {
let cond = self.pop_bool("JumpIf")?;
if cond {
let new_ip = self.ip as i64 + *offset as i64;
if new_ip < 0 {
return Err(format!("JumpIf: negative IP {}", new_ip));
}
self.ip = new_ip as usize;
}
}
Opcode::JumpIfNot(offset) => {
let cond = self.pop_bool("JumpIfNot")?;
if !cond {
let new_ip = self.ip as i64 + *offset as i64;
if new_ip < 0 {
return Err(format!("JumpIfNot: negative IP {}", new_ip));
}
self.ip = new_ip as usize;
}
}
Opcode::Call(pos) => {
self.call_stack.push(self.ip);
self.ip = *pos as usize;
}
Opcode::Return => {
if let Some(ret_addr) = self.call_stack.pop() {
self.ip = ret_addr;
} else {
return Ok(false);
}
}
Opcode::Load(idx) => {
let idx = *idx as usize;
let v = self
.locals
.get(idx)
.ok_or_else(|| format!("Load: local {} not found", idx))?
.clone();
self.stack.push(v);
}
Opcode::Store(idx) => {
let idx = *idx as usize;
let v = self.stack.last().ok_or("Store: stack underflow")?.clone();
while self.locals.len() <= idx {
self.locals.push(StackValue::Nil);
}
self.locals[idx] = v;
}
Opcode::LoadGlobal(name) => {
self.stack
.push(StackValue::Str(format!("<global:{}>", name)));
}
Opcode::MakeClosure(n_captured) => {
let n = *n_captured as usize;
if self.stack.len() < n {
return Err(format!(
"MakeClosure: need {} captures, have {}",
n,
self.stack.len()
));
}
let mut env = Vec::with_capacity(n);
for _ in 0..n {
env.push(self.pop().expect(
"stack has at least n elements as verified by the length check above",
));
}
env.reverse();
self.stack.push(StackValue::Closure {
code: Vec::new(),
env,
});
}
Opcode::Apply => {
let _arg = self.pop().ok_or("Apply: stack underflow (arg)")?;
let _closure = self.pop().ok_or("Apply: stack underflow (closure)")?;
self.stack.push(StackValue::Nil);
}
Opcode::Halt => {
return Ok(false);
}
}
Ok(true)
}
fn pop_nat(&mut self, ctx: &str) -> Result<u64, String> {
match self.pop() {
Some(StackValue::Nat(n)) => Ok(n),
Some(other) => Err(format!("{}: expected Nat, got {:?}", ctx, other)),
None => Err(format!("{}: stack underflow", ctx)),
}
}
fn pop_bool(&mut self, ctx: &str) -> Result<bool, String> {
match self.pop() {
Some(StackValue::Bool(b)) => Ok(b),
Some(other) => Err(format!("{}: expected Bool, got {:?}", ctx, other)),
None => Err(format!("{}: stack underflow", ctx)),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EncodedInstruction {
pub bytes: Vec<u8>,
}
impl EncodedInstruction {
pub fn encode(op: &Opcode) -> Self {
let mut bytes = Vec::new();
match op {
Opcode::Push(n) => {
bytes.push(0x01);
bytes.extend_from_slice(&n.to_le_bytes());
}
Opcode::PushBool(b) => {
bytes.push(0x02);
bytes.push(*b as u8);
}
Opcode::PushStr(s) => {
bytes.push(0x03);
let len = s.len() as u32;
bytes.extend_from_slice(&len.to_le_bytes());
bytes.extend_from_slice(s.as_bytes());
}
Opcode::PushNil => bytes.push(0x04),
Opcode::Pop => bytes.push(0x10),
Opcode::Dup => bytes.push(0x11),
Opcode::Swap => bytes.push(0x12),
Opcode::Add => bytes.push(0x20),
Opcode::Sub => bytes.push(0x21),
Opcode::Mul => bytes.push(0x22),
Opcode::Div => bytes.push(0x23),
Opcode::Mod => bytes.push(0x24),
Opcode::Eq => bytes.push(0x30),
Opcode::Lt => bytes.push(0x31),
Opcode::Le => bytes.push(0x32),
Opcode::Not => bytes.push(0x40),
Opcode::And => bytes.push(0x41),
Opcode::Or => bytes.push(0x42),
Opcode::Jump(off) => {
bytes.push(0x50);
bytes.extend_from_slice(&off.to_le_bytes());
}
Opcode::JumpIf(off) => {
bytes.push(0x51);
bytes.extend_from_slice(&off.to_le_bytes());
}
Opcode::JumpIfNot(off) => {
bytes.push(0x52);
bytes.extend_from_slice(&off.to_le_bytes());
}
Opcode::Call(pos) => {
bytes.push(0x60);
bytes.extend_from_slice(&pos.to_le_bytes());
}
Opcode::Return => bytes.push(0x61),
Opcode::Load(idx) => {
bytes.push(0x70);
bytes.extend_from_slice(&idx.to_le_bytes());
}
Opcode::Store(idx) => {
bytes.push(0x71);
bytes.extend_from_slice(&idx.to_le_bytes());
}
Opcode::LoadGlobal(name) => {
bytes.push(0x72);
let len = name.len() as u32;
bytes.extend_from_slice(&len.to_le_bytes());
bytes.extend_from_slice(name.as_bytes());
}
Opcode::MakeClosure(n) => {
bytes.push(0x80);
bytes.extend_from_slice(&n.to_le_bytes());
}
Opcode::Apply => bytes.push(0x81),
Opcode::Halt => bytes.push(0xFF),
}
EncodedInstruction { bytes }
}
pub fn decode(data: &[u8]) -> Option<(Opcode, usize)> {
if data.is_empty() {
return None;
}
let tag = data[0];
match tag {
0x01 => {
if data.len() < 9 {
return None;
}
let n = u64::from_le_bytes(data[1..9].try_into().ok()?);
Some((Opcode::Push(n), 9))
}
0x02 => {
if data.len() < 2 {
return None;
}
Some((Opcode::PushBool(data[1] != 0), 2))
}
0x03 => {
if data.len() < 5 {
return None;
}
let len = u32::from_le_bytes(data[1..5].try_into().ok()?) as usize;
if data.len() < 5 + len {
return None;
}
let s = std::str::from_utf8(&data[5..5 + len]).ok()?.to_string();
Some((Opcode::PushStr(s), 5 + len))
}
0x04 => Some((Opcode::PushNil, 1)),
0x10 => Some((Opcode::Pop, 1)),
0x11 => Some((Opcode::Dup, 1)),
0x12 => Some((Opcode::Swap, 1)),
0x20 => Some((Opcode::Add, 1)),
0x21 => Some((Opcode::Sub, 1)),
0x22 => Some((Opcode::Mul, 1)),
0x23 => Some((Opcode::Div, 1)),
0x24 => Some((Opcode::Mod, 1)),
0x30 => Some((Opcode::Eq, 1)),
0x31 => Some((Opcode::Lt, 1)),
0x32 => Some((Opcode::Le, 1)),
0x40 => Some((Opcode::Not, 1)),
0x41 => Some((Opcode::And, 1)),
0x42 => Some((Opcode::Or, 1)),
0x50 => {
if data.len() < 5 {
return None;
}
let off = i32::from_le_bytes(data[1..5].try_into().ok()?);
Some((Opcode::Jump(off), 5))
}
0x51 => {
if data.len() < 5 {
return None;
}
let off = i32::from_le_bytes(data[1..5].try_into().ok()?);
Some((Opcode::JumpIf(off), 5))
}
0x52 => {
if data.len() < 5 {
return None;
}
let off = i32::from_le_bytes(data[1..5].try_into().ok()?);
Some((Opcode::JumpIfNot(off), 5))
}
0x60 => {
if data.len() < 5 {
return None;
}
let pos = u32::from_le_bytes(data[1..5].try_into().ok()?);
Some((Opcode::Call(pos), 5))
}
0x61 => Some((Opcode::Return, 1)),
0x70 => {
if data.len() < 5 {
return None;
}
let idx = u32::from_le_bytes(data[1..5].try_into().ok()?);
Some((Opcode::Load(idx), 5))
}
0x71 => {
if data.len() < 5 {
return None;
}
let idx = u32::from_le_bytes(data[1..5].try_into().ok()?);
Some((Opcode::Store(idx), 5))
}
0x72 => {
if data.len() < 5 {
return None;
}
let len = u32::from_le_bytes(data[1..5].try_into().ok()?) as usize;
if data.len() < 5 + len {
return None;
}
let s = std::str::from_utf8(&data[5..5 + len]).ok()?.to_string();
Some((Opcode::LoadGlobal(s), 5 + len))
}
0x80 => {
if data.len() < 5 {
return None;
}
let n = u32::from_le_bytes(data[1..5].try_into().ok()?);
Some((Opcode::MakeClosure(n), 5))
}
0x81 => Some((Opcode::Apply, 1)),
0xFF => Some((Opcode::Halt, 1)),
_ => None,
}
}
}