~tmpod/brainfucc

7ac15793b9f7807df326e576a295f50b02d19e74 — tmpod 1 year, 5 months ago 8675849
Fix and improve LC3 compilation

Also improve Cargo.toml and add toasty LC3 VM primitives as dependency
main.rs is a test ground now, should be ignored.
6 files changed, 206 insertions(+), 45 deletions(-)

M Cargo.lock
M Cargo.toml
M src/interpreter.rs
R src/{compiler.rs => ir.rs}
M src/lc3.rs
M src/main.rs
M Cargo.lock => Cargo.lock +113 -0
@@ 1,5 1,118 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3

[[package]]
name = "brainfucc"
version = "0.1.0"
dependencies = [
 "toasty_lc3_primitives",
]

[[package]]
name = "num_enum"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9"
dependencies = [
 "num_enum_derive",
]

[[package]]
name = "num_enum_derive"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce"
dependencies = [
 "proc-macro-crate",
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "proc-macro-crate"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a"
dependencies = [
 "thiserror",
 "toml",
]

[[package]]
name = "proc-macro2"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
dependencies = [
 "unicode-ident",
]

[[package]]
name = "quote"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
dependencies = [
 "proc-macro2",
]

[[package]]
name = "serde"
version = "1.0.137"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"

[[package]]
name = "syn"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
dependencies = [
 "proc-macro2",
 "quote",
 "unicode-ident",
]

[[package]]
name = "thiserror"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
dependencies = [
 "thiserror-impl",
]

[[package]]
name = "thiserror-impl"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "toasty_lc3_primitives"
version = "0.2.0"
dependencies = [
 "num_enum",
]

[[package]]
name = "toml"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
dependencies = [
 "serde",
]

[[package]]
name = "unicode-ident"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"

M Cargo.toml => Cargo.toml +10 -2
@@ 2,11 2,19 @@
name = "brainfucc"
description = "Brainfuck compiler and interpreter"
authors = ["tmpod <tom@tmpod.dev>"]
readme = "README.md"
repository = "https://git.sr.ht/~tmpod/brainfucc"

keywords = ["brainfuck", "compiler", "interpreter", "lc3"]
categories = ["compilers"]

version = "0.1.0"
edition = "2021"
readme = "README.md"
repository = "https://git.sr.ht/~tmpod/brainfucc"

[features]
default = ["lc3"]
lc3 = ["dep:toasty_lc3_primitives"]

[dependencies]
# toasty_lc3_primitives = { git = "https://git.sr.ht/~tmpod/toasty-lc3-vm", optional = true }
toasty_lc3_primitives = { path = "../toast-lc3/vm/primitives/", optional = true }

M src/interpreter.rs => src/interpreter.rs +3 -4
@@ 1,7 1,6 @@
use std::io::Read;

use crate::compiler::Word;
use crate::compiler::{BrainfuckIR, BrainfuckInstruction};
use crate::ir::{BrainfuckIR, BrainfuckInstruction, Word};

const DEFAULT_TAPE_SIZE: usize = 30_000;



@@ 9,7 8,7 @@ type InputHook = fn() -> Word;
type OutputHook = fn(Word) -> ();

pub fn execute(
    compiled: BrainfuckIR,
    compiled: &BrainfuckIR,
    tape_size: usize,
    input_hook: InputHook,
    output_hook: OutputHook,


@@ 78,7 77,7 @@ pub fn default_output_hook(out: u8) {
    print!("{}", out as char);
}

pub fn execute_default(compiled: BrainfuckIR) {
pub fn execute_default(compiled: &BrainfuckIR) {
    execute(
        compiled,
        DEFAULT_TAPE_SIZE,

R src/compiler.rs => src/ir.rs +2 -2
@@ 35,8 35,8 @@ impl BrainfuckIR {

        for c in source.chars() {
            let inst = match c {
                '<' => RGT,
                '>' => LFT,
                '<' => LFT,
                '>' => RGT,
                '+' => INC,
                '-' => DEC,
                '.' => OUT,

M src/lc3.rs => src/lc3.rs +54 -29
@@ 1,31 1,56 @@
/// LC3 backend
use toast_lc3_vm::{
    instructions::{AddMode, Instruction as LC3Instruction},
use std::iter;

use toasty_lc3_primitives::{
    condition_codes::ConditionCode,
    instructions::{AddMode, Instruction as LC3Instruction, TrapVector},
    registers::Register,
    types::Word,
};

use crate::compiler::{BrainfuckIR, BrainfuckInstruction as BfInstruction};
use crate::ir::{BrainfuckIR, BrainfuckInstruction as BfInstruction};

#[derive(Debug)]
pub struct LC3CompiledBrainfuck {
    pub instructions: Vec<LC3Instruction>,
}

impl LC3CompiledBrainfuck {
    const ORIGIN: Word = 0x3000;

    // TODO: improve this to make use of negative bound being -16 and not -15
    fn compile_add(r: Register, n: i16) -> impl Iterator<Item = LC3Instruction> {
        let base = if n >= 0 { 0xF } else { 0x1F };
        let repeats = n / 0xF;
        let rem = n % 0xF;
        iter::repeat(LC3Instruction::ADD(r, r, AddMode::Immediate(base)))
            .take(repeats as usize)
            .chain(iter::once(LC3Instruction::ADD(
                r,
                r,
                AddMode::Immediate(rem as u16 & 0x1F),
            )))
    }

    // 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();
    // TODO: fix ADDs due to immediate mode only bearing 5 bits
    pub fn compile(ir: &BrainfuckIR) -> Self {
        let mut ir_iter = ir.instructions.iter().peekable();
        let mut lc3_insts = Vec::new();
        let mut jump_stack = Vec::new();

        let mut value_mod = 0i16;
        let mut pos_mod = 0i16;

        let mut value_mod = 0;
        let mut pos_mod = 0;
        // Placeholder instruction to then be replaced by R1 init to tape beginning
        lc3_insts.push(LC3Instruction::RES);

        // One pass to do small optimizations and produce LC3 instructions
        for i in ir_iter {
        while let Some(i) = ir_iter.next() {
            match i {
                BfInstruction::INC | BfInstruction::DEC => {
                    value_mod += match i {


@@ 37,11 62,7 @@ impl LC3CompiledBrainfuck {
                        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.extend(Self::compile_add(Register::R0, value_mod));
                            lc3_insts.push(LC3Instruction::STR(Register::R0, Register::R1, 0));
                            value_mod = 0;
                        }


@@ 56,11 77,7 @@ impl LC3CompiledBrainfuck {
                    match ir_iter.peek() {
                        Some(BfInstruction::RGT) | Some(BfInstruction::LFT) => continue,
                        _ => {
                            lc3_insts.push(LC3Instruction::ADD(
                                Register::R1,
                                Register::R1,
                                AddMode::Immediate(pos_mod),
                            ));
                            lc3_insts.extend(Self::compile_add(Register::R1, pos_mod));
                            pos_mod = 0;
                        }
                    }


@@ 73,17 90,15 @@ impl LC3CompiledBrainfuck {
                }
                BfInstruction::RET => {
                    let opening_pc = jump_stack.pop().expect("Invalid IR");
                    let closing_pc = lc3_insts.len();
                    let closing_pc = lc3_insts.len() as u16;
                    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 [
                        ConditionCode::NEG as u16 | ConditionCode::POS as u16,
                        closing_pc - opening_pc as u16 + 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);
                    lc3_insts[opening_pc + 1] =
                        LC3Instruction::BR(ConditionCode::ZER.into(), closing_pc + 2);
                }
                BfInstruction::INP => {
                    lc3_insts.push(LC3Instruction::TRAP(TrapVector::GETC));


@@ 98,12 113,22 @@ impl LC3CompiledBrainfuck {

        lc3_insts.push(LC3Instruction::TRAP(TrapVector::HALT));

        // Complete R1 init
        lc3_insts[0] = LC3Instruction::ADD(
            Register::R1,
            Register::R1,
            AddMode::Immediate(lc3_insts.len() as Word + 1),
        );

        LC3CompiledBrainfuck {
            instructions: lc3_insts,
        }
    }

    fn emit(&self) -> Vec<u8> {
        self.instructions.iter().map(|i| i.emit()).collect()
    pub fn emit(&self) -> impl Iterator<Item = u8> + '_ {
        let inst_words = self.instructions.iter().map(|i| i.emit());
        iter::once(Self::ORIGIN)
            .chain(inst_words)
            .flat_map(|w| w.to_be_bytes())
    }
}

M src/main.rs => src/main.rs +24 -8
@@ 1,21 1,37 @@
use std::env;
use std::error::Error;
use std::fs::File;
use std::io::Read;
use std::io::{Read, Result, Write};

mod cli;
mod compiler;
mod interpreter;
mod ir;
#[cfg(feature = "lc3")]
mod lc3;

fn main() -> Result<(), Box<dyn Error>> {
    let mut src_file = File::open(env::args().skip(1).next().expect("Missing source filename"))?;
fn main() -> Result<()> {
    let src_filename = env::args().skip(1).next().expect("Missing source filename");
    let mut out_filename = src_filename.clone();
    let mut src_file = File::open(src_filename)?;
    let mut src = String::new();
    src_file.read_to_string(&mut src).unwrap();

    let compiled = crate::compiler::BrainfuckIR::parse(src.as_str());
    eprintln!("Compiled: {:?}", compiled);
    crate::interpreter::execute_default(compiled);
    let ir = crate::ir::BrainfuckIR::parse(src.as_str());
    // eprintln!("IR: {:?}", ir);
    // crate::interpreter::execute_default(&ir);
    let lc3_compiled = crate::lc3::LC3CompiledBrainfuck::compile(&ir);
    eprintln!("LC3 compiled:");
    for i in lc3_compiled.instructions.iter() {
        eprintln!("{:?}", i);
    }

    // Write LC3 image
    out_filename.push_str(".lc3.obj");
    println!("Emitting to {}", out_filename);
    let mut out_file = File::create(out_filename)?;
    // TODO: implement output buffering
    for c in lc3_compiled.emit() {
        out_file.write(&[c])?;
    }

    Ok(())
}