interpreter: add conditional branches, etc.

This commit is contained in:
Guillem L. Jara
2026-05-24 01:04:06 +02:00
parent d4842f7aa9
commit 345e7c4411
4 changed files with 124 additions and 67 deletions
+25 -37
View File
@@ -26,7 +26,7 @@ pub struct Reg(pub u16);
#[derive(Clone, Copy, Debug)]
#[repr(transparent)]
pub struct Label(u16);
pub struct Label(pub u16);
#[derive(Clone, Copy, Debug)]
#[repr(transparent)]
@@ -73,7 +73,6 @@ pub enum OpCode {
Jump,
Return,
Branch,
BrIf,
}
const _: () = const { assert!(size_of::<Instruction>() <= 8) };
@@ -95,7 +94,6 @@ pub union Arguments {
jump: JumpArg,
ret: RetArg,
branch: BranchArg,
br_if: BrIfArg,
call: CallArgs,
ind_call: IndCallArgs,
}
@@ -106,7 +104,6 @@ pub type LoadStoreArg = (Reg, NonLocal);
pub type JumpArg = Label;
pub type RetArg = Reg;
pub type BranchArg = (Reg, Label, Label);
pub type BrIfArg = (Reg, Label);
pub type CallArgs = (Reg, NonLocal, ArgCount);
pub type IndCallArgs = (Reg, Reg, ArgCount);
@@ -159,21 +156,17 @@ impl Instruction {
}
}
fn jump(opcode: impl Into<OpCode>, to: Label) -> Self {
let opcode = opcode.into();
debug_assert!(opcode.is_jump());
fn jump(to: Label) -> Self {
Self {
opcode,
opcode: OpCode::Jump,
args: Arguments { jump: to },
hint: Hint::None,
}
}
fn branch(opcode: impl Into<OpCode>, cond: Reg, true_to: Label, false_to: Label) -> Self {
let opcode = opcode.into();
debug_assert!(opcode.is_branch());
fn branch(cond: Reg, true_to: Label, false_to: Label) -> Self {
Self {
opcode,
opcode: OpCode::Branch,
args: Arguments {
branch: (cond, true_to, false_to),
},
@@ -181,32 +174,32 @@ impl Instruction {
}
}
fn br_if(opcode: impl Into<OpCode>, cond: Reg, to: Label) -> Self {
let opcode = opcode.into();
debug_assert!(opcode.is_branch_if());
Self {
opcode,
args: Arguments { br_if: (cond, to) },
hint: Hint::None,
}
}
pub fn get_unary(&self) -> Option<&UnaryArg> {
self.opcode
.is_unary()
.then(|| unsafe { &self.args.unary_local })
.then_some(unsafe { &self.args.unary_local })
}
pub fn get_binary(&self) -> Option<&BinaryArg> {
self.opcode
.is_binary()
.then(|| unsafe { &self.args.binary_local })
.then_some(unsafe { &self.args.binary_local })
}
pub fn get_load_store(&self) -> Option<&LoadStoreArg> {
self.opcode
.is_load_store()
.then(|| unsafe { &self.args.load_store })
.then_some(unsafe { &self.args.load_store })
}
pub fn get_branch(&self) -> Option<&BranchArg> {
self.opcode
.is_branch()
.then_some(unsafe { &self.args.branch })
}
pub fn get_jump(&self) -> Option<&JumpArg> {
self.opcode.is_jump().then_some(unsafe { &self.args.jump })
}
}
@@ -258,10 +251,6 @@ impl OpCode {
fn is_branch(self) -> bool {
matches!(self, Self::Branch)
}
fn is_branch_if(self) -> bool {
matches!(self, Self::BrIf)
}
}
#[repr(u8, align(1))]
@@ -294,9 +283,9 @@ impl Debug for Instruction {
let (dest, src) = unsafe { &self.args.load_store };
write!(f, "({dest:?}, {src:?})")
}
OpCode::BrIf => {
let (cond, label) = unsafe { self.args.br_if };
write!(f, "({cond:?}, {label:?})")
OpCode::Branch => {
let (cond, label_then, label_else) = unsafe { self.args.branch };
write!(f, "({cond:?}, {label_then:?}, {label_else:?})")
}
OpCode::Jump => {
let label = unsafe { self.args.jump };
@@ -350,9 +339,9 @@ impl Display for Instruction {
let (dest, src) = unsafe { &self.args.load_store };
write!(f, "{dest} <- {op} intrinsic[{src}]")
}
op @ OpCode::BrIf => {
let (cond, label) = unsafe { self.args.br_if };
write!(f, "{op} {cond}, {label}")
op @ OpCode::Branch => {
let (cond, label_then, label_else) = unsafe { self.args.branch };
write!(f, "{op} {cond}, {label_then}, {label_else}")
}
op @ OpCode::Jump => {
let label = unsafe { self.args.jump };
@@ -405,8 +394,7 @@ impl Display for OpCode {
Self::IndirectCall => "vcall",
Self::Jump => "jmp",
Self::Return => "ret",
Self::Branch => "br",
Self::BrIf => "brif",
Self::Branch => "brif",
};
<_ as Display>::fmt(str, f)
}
+81 -23
View File
@@ -9,7 +9,10 @@ use std::mem::forget;
use bumpalo::{Bump, collections::Vec};
use indexmap::IndexSet;
use parser::{Atom, BinaryOperator, Expr, ExprNode, UnaryOperator, Variable};
use parser::{
Atom, BinaryOperator, BinaryPlaceOperator, Body, Expr, ExprNode, Place, SimpleStatement,
Statement, UnaryOperator, Variable,
};
use crate::{
ir::{Hint, HintedReg, Instruction, Label, NonLocal, OpCode, Reg},
@@ -31,11 +34,51 @@ pub struct Code<'arena> {
struct LinearReg(Reg, Hint);
impl Code<'_> {
fn lower_expr(&mut self, expr: &Expr) -> LinearReg {
let dest = self.alloc_reg();
let hint = self.lower_expr_into(expr, dest);
LinearReg(dest, hint)
fn lower_body(&mut self, body: &Body) {
for stmnt in &body.0 {
self.lower_statement(stmnt);
}
}
fn lower_statement(&mut self, stmnt: &Statement) {
match stmnt {
Statement::If {
condition,
then_body,
else_body,
} => {
let mut state = RegsState::new(self);
let condition = self.lower_expr(condition);
let label_then = self.following_instr(1);
let if_label =
self.bc
.emit(Instruction::branch(condition.reg(), label_then, Label(0)));
self.free_reg(condition);
self.lower_body(then_body);
self.bc.nth(if_label).args.branch.2 = self.following_instr(1);
if let Some(else_body) = else_body {
state.reg_pointer += 1;
let end_label = self.bc.emit(Instruction::jump(Label(0)));
state.scope_hwm(self, |c| c.lower_body(else_body));
self.bc.nth(end_label).args.jump = self.following_instr(0);
}
}
Statement::Simple(SimpleStatement::Expression(expr)) => {
let reg = self.lower_expr(expr);
self.free_reg(reg);
}
_ => todo!(),
}
}
fn lower_expr(&mut self, expr: &Expr) -> LinearReg {
let mut dest = self.alloc_reg();
let hint = self.lower_expr_into(expr, dest.reg());
dest.1 = hint;
dest
}
fn lower_expr_into(&mut self, expr: &Expr, dest: Reg) -> Hint {
match expr {
Expr::Leaf(atom) => match atom {
@@ -69,19 +112,28 @@ impl Code<'_> {
self.lower_expr_into(cond, dest);
let mut state = RegsState::new(self);
let br_if = self
.bc
.emit(Instruction::br_if(OpCode::BrIf, dest, Label(0)));
let branch =
self.bc
.emit(Instruction::branch(dest, self.following_instr(1), Label(0)));
state = state.scope(self, |c| c.lower_expr_into(false_then, dest));
state = state.scope(self, |c| c.lower_expr_into(true_then, dest));
let jump = self.bc.emit(Instruction::jump(OpCode::Jump, Label(0)));
let label = Label(self.bc.len());
let jump = self.bc.emit(Instruction::jump(Label(0)));
let label = self.following_instr(0);
state.scope_hwm(self, |c| c.lower_expr_into(true_then, dest));
state.scope_hwm(self, |c| c.lower_expr_into(false_then, dest));
self.bc.nth(br_if).args.br_if.1 = label;
self.bc.nth(jump).args.jump = Label(self.bc.len());
self.bc.nth(branch).args.branch.2 = label;
self.bc.nth(jump).args.jump = self.following_instr(0);
}
ExprNode::BinaryPlaceOperation(BinaryPlaceOperator::Assignment, place, expr) => {
self.lower_expr_into(expr, dest);
let Place::Variable(Variable::User(var)) = place else {
todo!()
};
let var = self.symbols.register_user_var(var, self.arena);
self.bc
.emit(Instruction::load_store(OpCode::StoreUser, dest, var));
}
_ => todo!(),
},
@@ -89,12 +141,15 @@ impl Code<'_> {
Hint::None
}
fn alloc_reg(&mut self) -> Reg {
self.free_regs.pop().unwrap_or_else(|| {
let current = self.reg_pointer;
self.reg_pointer += 1;
Reg(current)
})
fn alloc_reg(&mut self) -> LinearReg {
self.free_regs
.pop()
.map(|r| LinearReg(r, Hint::None))
.unwrap_or_else(|| {
let current = self.reg_pointer;
self.reg_pointer += 1;
LinearReg(Reg(current), Hint::None)
})
}
fn free_reg(&mut self, reg: LinearReg) {
@@ -104,6 +159,10 @@ impl Code<'_> {
fn register_const(&mut self, value: Value) -> NonLocal {
NonLocal(self.consts.insert_full(value).0 as u16)
}
fn following_instr(&self, nth: u16) -> Label {
Label(self.bc.len() + nth)
}
}
#[derive(Debug, Clone)]
@@ -163,7 +222,7 @@ impl RegsState {
}
}
pub fn test_interpreter(expr: &Expr<'_>) -> impl Display {
pub fn test_interpreter(stmnt: &Body<'_>) -> impl Display {
let bump = Bump::with_capacity(16384);
let mut c = Code {
arena: &bump,
@@ -173,8 +232,7 @@ pub fn test_interpreter(expr: &Expr<'_>) -> impl Display {
reg_pointer: 0,
free_regs: Vec::new_in(&bump),
};
let result = c.lower_expr(expr);
forget(result);
c.lower_body(stmnt);
let code = c.to_string();
let mut vm = Interpreter::new(ExecMode::Uu, c);
vm.run();
+15 -2
View File
@@ -87,7 +87,7 @@ impl Interpreter<'_> {
pub fn run(&mut self) {
while let Some(instr) = self.bc.code.get(self.program_counter) {
match instr {
ix if let Some(&(dest, src)) = ix.get_unary() => {}
// ix if let Some(&(dest, src)) = ix.get_unary() => {}
ix if let Some(&(dest, lhs, rhs)) = ix.get_binary() => {
let lhs = self.registers.read(lhs);
let rhs = self.registers.read(rhs);
@@ -113,7 +113,20 @@ impl Interpreter<'_> {
}
_ => todo!(),
},
_ => todo!(),
ix if let Some((cond, true_to, false_to)) = ix.get_branch() => {
let label = if self.registers.read(*cond).0 == 0. {
false_to.0
} else {
true_to.0
};
self.program_counter = label as _;
continue;
}
ix if let Some(label) = ix.get_jump() => {
self.program_counter = label.0 as _;
continue;
}
ix => todo!("{ix:?}"),
}
self.program_counter += 1;
}
+3 -5
View File
@@ -15,7 +15,7 @@ use bumpalo::Bump;
use clap::Parser as _;
use color_eyre::Result;
use interpreter::test_interpreter;
use parser::{Body, Parser, Rule};
use parser::{Parser, Rule};
use crate::{
cli::Args,
@@ -55,13 +55,11 @@ fn uu_main() -> Result<()> {
dbg!(arena.chunk_capacity());
if let Some(Rule {
actions: Some(Body(a)),
actions: Some(body),
pattern: _,
}) = ast.rules.first()
&& let Some(parser::Statement::Simple(parser::SimpleStatement::Expression(expr))) =
a.first()
{
let x = test_interpreter(expr);
let x = test_interpreter(body);
if let Err(e) = writeln!(io::stdout(), "---\n{x}")
&& e.kind() != io::ErrorKind::BrokenPipe
{