~tmpod/brainfucc

64c3ad34de948557e59585b65bcaef5617f58132 — tmpod 2 years ago 025a886
Implement first PoC for LC3 backend
1 files changed, 109 insertions(+), 0 deletions(-)

A src/lc3.rs
A src/lc3.rs => src/lc3.rs +109 -0
@@ 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()
    }
}