@@ 0,0 1,109 @@
+/// LC3 backend
+use toast_lc3_vm::{
+ instructions::{AddMode, Instruction as LC3Instruction},
+ registers::Register,
+};
+
+use crate::compiler::{BrainfuckIR, BrainfuckInstruction as BfInstruction};
+
+pub struct LC3CompiledBrainfuck {
+ pub instructions: Vec<LC3Instruction>,
+}
+
+impl LC3CompiledBrainfuck {
+ // Curent register usage:
+ // R0 -> temporary register for I/O and other operations
+ // R1 -> data pointer
+ // TODO: fix expected single-byte overflow not happening because LC3 words are 2-bytes wide.
+ // TODO: improve naive instruction emition to make use of registers for hot paths.
+ fn compile(ir: &BrainfuckIR) -> Self {
+ let ir_iter = ir.instructions.iter().peekable();
+ let lc3_insts = Vec::new();
+ let jump_stack = Vec::new();
+
+ let mut value_mod = 0;
+ let mut pos_mod = 0;
+
+ // One pass to do small optimizations and produce LC3 instructions
+ for i in ir_iter {
+ match i {
+ BfInstruction::INC | BfInstruction::DEC => {
+ value_mod += match i {
+ BfInstruction::INC => 1,
+ BfInstruction::DEC => -1,
+ _ => unreachable!(),
+ };
+ match ir_iter.peek() {
+ Some(BfInstruction::INC) | Some(BfInstruction::DEC) => continue,
+ _ => {
+ lc3_insts.push(LC3Instruction::LDR(Register::R0, Register::R1, 0));
+ lc3_insts.push(LC3Instruction::ADD(
+ Register::R0,
+ Register::R0,
+ AddMode::Immediate(value_mod),
+ ));
+ lc3_insts.push(LC3Instruction::STR(Register::R0, Register::R1, 0));
+ value_mod = 0;
+ }
+ }
+ }
+ BfInstruction::RGT | BfInstruction::LFT => {
+ pos_mod += match i {
+ BfInstruction::RGT => 1,
+ BfInstruction::LFT => -1,
+ _ => unreachable!(),
+ };
+ match ir_iter.peek() {
+ Some(BfInstruction::RGT) | Some(BfInstruction::LFT) => continue,
+ _ => {
+ lc3_insts.push(LC3Instruction::ADD(
+ Register::R1,
+ Register::R1,
+ AddMode::Immediate(pos_mod),
+ ));
+ pos_mod = 0;
+ }
+ }
+ }
+ BfInstruction::JMP => {
+ jump_stack.push(lc3_insts.len());
+ lc3_insts.push(LC3Instruction::LDR(Register::R0, Register::R1, 0));
+ // instruction is actually filled in on corresponding RET
+ lc3_insts.push(LC3Instruction::RES);
+ }
+ BfInstruction::RET => {
+ let opening_pc = jump_stack.pop().expect("Invalid IR");
+ let closing_pc = lc3_insts.len();
+ lc3_insts.push(LC3Instruction::LDR(Register::R0, Register::R1, 0));
+ lc3_insts.push(LC3Instruction::BR(
+ ConditionCode::NEG.into() | ConditionCode::POS.into(),
+ Register::R1,
+ opening_pc - closing_pc + 2, // +2 because we wanna land on the inst after the [
+ ));
+ // and fill in the jump instruction
+ let open_inst = lc3_insts[opening_pc];
+ lc3_insts[opening_pc] =
+ LC3Instruction::BR(ConditionCode::ZER.into(), Register::R1, closing_pc + 2);
+ }
+ BfInstruction::INP => {
+ lc3_insts.push(LC3Instruction::TRAP(TrapVector::GETC));
+ lc3_insts.push(LC3Instruction::STR(Register::R0, Register::R1, 0));
+ }
+ BfInstruction::OUT => {
+ lc3_insts.push(LC3Instruction::LDR(Register::R0, Register::R1, 0));
+ lc3_insts.push(LC3Instruction::TRAP(TrapVector::OUT));
+ }
+ }
+ }
+
+ lc3_insts.push(LC3Instruction::TRAP(TrapVector::HALT));
+
+ LC3CompiledBrainfuck {
+ instructions: lc3_insts,
+ }
+ }
+
+ fn emit(&self) -> Vec<u8> {
+ self.instructions.iter().map(|i| i.emit()).collect()
+ }
+}