~jpl8/lorthc3

38373a7890ff62590fd029c3a7ee3590a812b918 — jpl 1 year, 1 month ago 1adb95e
Add label assembling
4 files changed, 306 insertions(+), 161 deletions(-)

A src/assembler.rs
M src/compiler.rs
D src/lorth_instruction.rs
M src/main.rs
A src/assembler.rs => src/assembler.rs +255 -0
@@ 0,0 1,255 @@
use std::collections::HashMap;

use toasty_lc3_primitives::condition_codes::ConditionCode;
use toasty_lc3_primitives::instructions::{
    AddMode, AndMode, Instruction, JsrMode, TrapVector, TrapVector::*,
};
use toasty_lc3_primitives::registers::{Register, Register::*, Registers};
use toasty_lc3_primitives::types::Word;

use crate::assembler::AsmInstruction::*;

use anyhow::{bail, Result};

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AsmAddr {
    // label_name: String, pc: Word
    Label(String, Word),
    Word(Word),
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AsmInstruction {
    // Conditional branch
    BR(u16, AsmAddr),
    /// Addition
    ADD(Register, Register, AddMode),
    // Load
    LD(Register, AsmAddr),
    /// Store
    ST(Register, AsmAddr),
    /// Jump to subroutine
    JSR(JsrMode),
    /// Bit-wise logical AND
    AND(Register, Register, AndMode),
    /// Load base+offset
    LDR(Register, Register, u16),
    /// Store base+offset
    STR(Register, Register, u16),
    /// Return from trap or interrupt
    RTI,
    /// Bit-wise complement
    NOT(Register, Register),
    /// Load indirect
    LDI(Register, AsmAddr),
    /// Store indirect
    STI(Register, AsmAddr),
    /// Jump
    JMP(Register),
    /// Reserved (unused instruction)
    RES,
    /// Load effective address
    LEA(Register, AsmAddr),
    /// Trap (system call)
    TRAP(TrapVector),
    FILL(Word),
}

#[derive(Debug, PartialEq, Eq)]
pub enum PseudoInstruction {
    LC3(Instruction),
    FILL(Word),
}

impl PseudoInstruction {
    pub fn emit(&self) -> Word {
        match self {
            Self::LC3(instr) => instr.emit(),
            Self::FILL(w) => *w,
        }
    }
}

pub struct LC3Assembler {
    pub instructions: Vec<AsmInstruction>,
    pub labels: HashMap<String, Word>,
    pub pc: Word,
}
pub type SignedWord = i16;

impl LC3Assembler {
    pub const ORIGIN: u16 = 0x3000;

    pub fn new() -> Self {
        Self {
            instructions: vec![],
            labels: HashMap::new(),
            pc: Self::ORIGIN,
        }
    }

    pub fn assemble(&self) -> Result<Vec<PseudoInstruction>> {
        let mut assembled_instructions = vec![];

        for instr in self.instructions.clone() {
            let assembled = match instr {
                BR(cond, addr) => PseudoInstruction::LC3(Instruction::BR(
                    cond,
                    self.asm_addr_to_offset(addr)? as Word,
                )),
                ADD(r1, r2, mode) => PseudoInstruction::LC3(Instruction::ADD(r1, r2, mode)),
                LD(r, addr) => PseudoInstruction::LC3(Instruction::LD(
                    r,
                    self.asm_addr_to_offset(addr)? as Word,
                )),
                ST(r, addr) => PseudoInstruction::LC3(Instruction::ST(
                    r,
                    self.asm_addr_to_offset(addr)? as Word,
                )),
                JSR(mode) => PseudoInstruction::LC3(Instruction::JSR(mode)),
                AND(r1, r2, mode) => PseudoInstruction::LC3(Instruction::AND(r1, r2, mode)),
                LDR(r1, r2, offset) => PseudoInstruction::LC3(Instruction::LDR(r1, r2, offset)),
                STR(r1, r2, offset) => PseudoInstruction::LC3(Instruction::STR(r1, r2, offset)),
                RTI => PseudoInstruction::LC3(Instruction::RTI),
                NOT(r1, r2) => PseudoInstruction::LC3(Instruction::NOT(r1, r2)),

                LDI(r, addr) => PseudoInstruction::LC3(Instruction::LDI(
                    r,
                    self.asm_addr_to_offset(addr)? as Word,
                )),
                STI(r, addr) => PseudoInstruction::LC3(Instruction::STI(
                    r,
                    self.asm_addr_to_offset(addr)? as Word,
                )),
                JMP(r) => PseudoInstruction::LC3(Instruction::JMP(r)),
                RES => PseudoInstruction::LC3(Instruction::RES),

                LEA(r, addr) => PseudoInstruction::LC3(Instruction::LEA(
                    r,
                    self.asm_addr_to_offset(addr)? as Word,
                )),
                TRAP(trap) => PseudoInstruction::LC3(Instruction::TRAP(trap)),
                FILL(word) => PseudoInstruction::FILL(word),
            };

            assembled_instructions.push(assembled);
        }

        Ok(assembled_instructions)
    }

    fn asm_addr_to_offset(&self, addr: AsmAddr) -> Result<SignedWord> {
        match addr {
            AsmAddr::Word(w) => Ok(w as SignedWord),
            AsmAddr::Label(label, pc) => {
                Ok((self.get_label_addr(label)? as SignedWord) - (pc as SignedWord) - 1)
            }
        }
    }

    pub fn get_label_addr(&self, label: String) -> Result<Word> {
        if let Some(addr) = self.labels.get(&label) {
            Ok(*addr)
        } else {
            bail!("Label {} not defined!", label);
        }
    }

    pub fn load(&mut self, r: Register, addr: AsmAddr) -> &mut Self {
        self.instr(LD(r, addr))
    }

    pub fn load_addr(&mut self, r: Register, addr: AsmAddr) -> &mut Self {
        self.instr(LEA(r, addr))
    }

    pub fn get_char(&mut self) -> &mut Self {
        self.instr(TRAP(GETC))
    }

    pub fn out_char(&mut self) -> &mut Self {
        self.instr(TRAP(OUT))
    }

    pub fn add(&mut self, dest: Register, src: Register, arg: AddMode) -> &mut Self {
        self.instr(ADD(dest, src, arg))
    }
    pub fn and(&mut self, dest: Register, src: Register, arg: AndMode) -> &mut Self {
        self.instr(AND(dest, src, arg))
    }

    pub fn not(&mut self, dest: Register, src: Register) -> &mut Self {
        self.instr(NOT(dest, src))
    }

    pub fn branch(&mut self, cond: Word, addr: AsmAddr) -> &mut Self {
        self.instr(BR(cond, addr))
    }

    pub fn branch_unconditionally(&mut self, addr: AsmAddr) -> &mut Self {
        self.instr(BR(
            ConditionCode::POS as Word | ConditionCode::NEG as Word | ConditionCode::ZER as Word,
            addr,
        ))
    }

    pub fn label(&mut self, s: &'static str) -> &mut Self {
        self.labels.insert(String::from(s), self.pc());
        self
    }

    pub fn reference_label(&self, s: &'static str) -> AsmAddr {
        AsmAddr::Label(String::from(s), self.pc())
    }

    pub fn load_indirect(&mut self, r: Register, addr: AsmAddr) -> &mut Self {
        self.instr(LDI(r, addr))
    }

    // Optimizations

    fn num_diff_bits(a: Word, b: Word) -> u32 {
        return (a ^ b).count_ones();
    }

    pub fn mov(&mut self, r: Register, v: Word) -> &mut Self {
        self.and(r, r, AndMode::Immediate(0));
        const MAX_POS_5_BIT: Word = 0x0F;
        let div = v / MAX_POS_5_BIT;
        let rem = v % MAX_POS_5_BIT;
        for _ in 0..div {
            self.add(r, r, AddMode::Immediate(MAX_POS_5_BIT));
        }
        self.add(r, r, AddMode::Immediate(rem))
    }

    pub fn store(&mut self, src: Register, base: Register, offset: Word) -> &mut Self {
        self.instr(STR(src, base, offset))
    }

    pub fn fill(&mut self, w: Word) -> &mut Self {
        self.instr(FILL(w))
    }

    pub fn halt(&mut self) -> &mut Self {
        self.instr(TRAP(HALT))
    }

    pub fn instr(&mut self, instruction: AsmInstruction) -> &mut Self {
        self.instructions.push(instruction);
        self.inc_pc()
    }
    pub fn inc_pc(&mut self) -> &mut Self {
        self.pc += 1;
        self
    }

    fn pc_offset(&self, label: Word) -> SignedWord {
        return (label as SignedWord) - (self.pc() as SignedWord) - 1;
    }

    pub fn pc(&self) -> Word {
        eprintln!("PC: {:#02X}", self.pc);
        self.pc
    }
}

M src/compiler.rs => src/compiler.rs +50 -143
@@ 1,6 1,5 @@
use toasty_lc3_primitives::condition_codes::ConditionCode;
use toasty_lc3_primitives::instructions::{AddMode, AndMode, JsrMode, TrapVector::*};
use toasty_lc3_primitives::instructions::{Instruction, Instruction::*};
use toasty_lc3_primitives::types::Word;

use toasty_lc3_primitives::registers::{Register, Register::*, Registers};


@@ 10,14 9,15 @@ use anyhow::{bail, Result};
use std::collections::HashMap;
use std::iter;

use crate::lorth_instruction::LorthInstruction::{self, *};
use crate::assembler::AsmInstruction::{self, *};
use crate::assembler::LC3Assembler;
use crate::assembler::{AsmAddr, PseudoInstruction};

pub type SignedWord = i16;

pub struct ForthCompiler {
    instructions: Vec<LorthInstruction>,
    registers: Registers,
    labels: HashMap<String, Word>,
    asm: LC3Assembler,
    assembled_instructions: Vec<PseudoInstruction>,
}

impl ForthCompiler {


@@ 27,116 27,16 @@ impl ForthCompiler {

    pub fn new() -> Self {
        Self {
            instructions: vec![],
            registers: Registers::new(),
            labels: HashMap::new(),
            asm: LC3Assembler::new(),
            assembled_instructions: vec![],
        }
    }
    pub fn load(&mut self, r: Register, addr: Word) -> &mut Self {
        let offset: SignedWord = (addr as SignedWord) - (self.pc() as SignedWord) - 1;
        eprintln!("Load Offset: {:#02X}", offset);
        self.instr(LC3(LD(r, offset as Word)))
    }

    pub fn load_addr(&mut self, r: Register, addr: Word) -> &mut Self {
        let offset: SignedWord = (addr as SignedWord) - (self.pc() as SignedWord) - 1;
        eprintln!("Load Offset: {:#02X}", offset);
        self.instr(LC3(LEA(r, offset as Word)))
    }

    pub fn get_char(&mut self) -> &mut Self {
        self.instr(LC3(TRAP(GETC)))
    }

    pub fn out_char(&mut self) -> &mut Self {
        self.instr(LC3(TRAP(OUT)))
    }

    pub fn add(&mut self, dest: Register, src: Register, arg: AddMode) -> &mut Self {
        self.instr(LC3(ADD(dest, src, arg)))
    }
    pub fn and(&mut self, dest: Register, src: Register, arg: AndMode) -> &mut Self {
        self.instr(LC3(AND(dest, src, arg)))
    }

    pub fn not(&mut self, dest: Register, src: Register) -> &mut Self {
        self.instr(LC3(NOT(dest, src)))
    }

    pub fn branch(&mut self, cond: Word, addr: Word) -> &mut Self {
        eprintln!("Addr: {:#02X}", addr);
        let offset: SignedWord = (addr as SignedWord) - (self.pc() as SignedWord) - 1;
        self.instr(LC3(BR(cond, offset as Word)))
    }

    // Optimizations

    fn num_diff_bits(a: Word, b: Word) -> u32 {
        return (a ^ b).count_ones();
    }

    pub fn mov(&mut self, r: Register, v: Word) -> &mut Self {
        self.and(r, r, AndMode::Immediate(0));
        const MAX_POS_5_BIT: Word = 0x0F;
        let div = v / MAX_POS_5_BIT;
        let rem = v % MAX_POS_5_BIT;
        for _ in 0..div {
            self.add(r, r, AddMode::Immediate(MAX_POS_5_BIT));
        }
        self.add(r, r, AddMode::Immediate(rem))
    }

    pub fn store(&mut self, src: Register, base: Register, offset: Word) -> &mut Self {
        self.instr(LC3(STR(src, base, offset)))
    }

    pub fn fill(&mut self, w: Word) -> &mut Self {
        self.instr(FILL(w))
    }

    pub fn halt(&mut self) -> &mut Self {
        self.instr(LC3(TRAP(HALT)))
    }

    pub fn instr(&mut self, instruction: LorthInstruction) -> &mut Self {
        self.instructions.push(instruction);
        self.inc_pc()
    }
    pub fn instrs(&mut self, instructions: Vec<LorthInstruction>) -> &mut Self {
        instructions.into_iter().for_each(|i| {
            self.instr(i);
        });
        self
    }

    pub fn inc_pc(&mut self) -> &mut Self {
        self.registers[PC] += 1;
        self
    }

    fn pc_offset(&self, label: Word) -> SignedWord {
        return (label as SignedWord) - (self.pc() as SignedWord) - 1;
    }

    pub fn pc(&self) -> Word {
        eprintln!("PC: {:#02X}", self.registers[PC]);
        self.registers[PC]
    }

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

    pub fn branch_unconditionally(&mut self, offset: Word) -> &mut Self {
        self.instr(LC3(BR(
            ConditionCode::POS as Word | ConditionCode::NEG as Word | ConditionCode::ZER as Word,
            offset,
        )))
    }

    fn load_intermediate_cells(&mut self) -> &mut Self {
        let cells = vec![
            ("tib", Self::TIB),


@@ 144,17 44,18 @@ impl ForthCompiler {
            ("space", ' ' as Word),
        ];

        self.branch_unconditionally(cells.len() as Word);
        self.asm
            .branch_unconditionally(AsmAddr::Word(cells.len() as Word));

        for (label, val) in cells {
            self.label(label);
            self.fill(val);
            self.asm.label(label);
            self.asm.fill(val);
        }
        self
    }

    fn get_tib(&self) -> Result<Word> {
        if let Some(tib) = self.labels.get("tib") {
        if let Some(tib) = self.asm.labels.get("tib") {
            eprintln!("TIB: {:#02X}", tib);
            Ok(*tib)
        } else {


@@ 163,16 64,22 @@ impl ForthCompiler {
    }

    pub fn read_input(&mut self) -> Result<&mut Self> {
        let tib = self.get_tib()?;
        self.load(R1, tib).load_addr(R0, ForthCompiler::TOIN);

        let loop_top = self.pc();
        self.get_char()
        let tib_addr = self.asm.reference_label("tib");
        self.asm.load(R1, tib_addr);
        let toin_addr = self.asm.reference_label("toin");
        self.asm.load(R0, toin_addr);

        let input_reading_loop_label = "input_reading_loop";
        self.asm
            .label(input_reading_loop_label)
            .get_char()
            .out_char()
            .store(R0, R1, 0)
            .add(R1, R1, AddMode::Immediate(1 as Word))
            .not(R0, R0)
            .and(R0, R0, AndMode::Immediate('\n' as Word))
            .and(R0, R0, AndMode::Immediate('\n' as Word));
        let loop_top = self.asm.reference_label(input_reading_loop_label);
        self.asm
            .branch(
                ConditionCode::NEG as Word | ConditionCode::POS as Word,
                loop_top,


@@ 183,46 90,46 @@ impl ForthCompiler {
        Ok(self)
    }

    fn label(&mut self, s: &'static str) -> &mut Self {
        self.labels.insert(String::from(s), self.pc());
        self
    }
    fn parse_words(&mut self) -> Result<&mut Self> {
        self.asm.label("token");

    fn label_addr(&self, s: &'static str) -> Result<Word> {
        if let Some(addr) = self.labels.get(s) {
            Ok(*addr)
        } else {
            bail!("Undefined label {}", s)
        }
    }
        let toin = self.asm.reference_label("toin");
        self.asm.load_indirect(R3, toin);

    fn load_indirect(&mut self, r: Register, addr: Word) -> &mut Self {
        self.instr(LC3(LDI(r, self.pc_offset(addr) as Word)))
    }
        let space = self.asm.reference_label("space");
        self.asm.load(R1, space);

    fn parse_words(&mut self) -> Result<&mut Self> {
        let space = self.label_addr("space")?;
        let toin = self.label_addr("toin")?;
        self.label("token").load_indirect(R3, toin).load(R1, space);
        unimplemented!();
        // unimplemented!();
        Ok(self)
    }

    pub fn print_tib(&mut self) -> Result<&mut Self> {
        let tib = self.get_tib()?;
        self.instr(LC3(LD(R0, self.pc_offset(tib) as Word)));
        self.instr(LC3(TRAP(PUTS)));
        let tib_addr = self.asm.reference_label("tib");
        self.asm.instr(LD(R0, tib_addr));
        self.asm.instr(TRAP(PUTS));
        Ok(self)
    }

    fn halt(&mut self) -> &mut Self {
        self.asm.halt();
        self
    }

    pub fn assemble(&mut self) -> Result<&mut Self> {
        self.assembled_instructions = self.asm.assemble()?;
        Ok(self)
    }

    pub fn compile(&mut self) -> Result<&mut Self> {
        self.load_intermediate_cells()
            .read_input()?
            .parse_words()?
            .print_tib()?
            .halt();
            .parse_words()?
            .halt()
            .assemble()?;

        eprintln!("{:?}", self.instructions);
        eprintln!("{:?}", self.asm.instructions);
        eprintln!("{:?}", self.assembled_instructions);

        Ok(self)
    }

D src/lorth_instruction.rs => src/lorth_instruction.rs +0 -17
@@ 1,17 0,0 @@
use toasty_lc3_primitives::instructions::{Instruction, Instruction::*};
use toasty_lc3_primitives::types::Word;

#[derive(Debug, PartialEq, Eq)]
pub enum LorthInstruction {
    LC3(Instruction),
    FILL(Word),
}

impl LorthInstruction {
    pub fn emit(&self) -> Word {
        match self {
            Self::LC3(instr) => instr.emit(),
            Self::FILL(w) => *w,
        }
    }
}

M src/main.rs => src/main.rs +1 -1
@@ 1,5 1,5 @@
mod assembler;
mod compiler;
mod lorth_instruction;

use compiler::ForthCompiler;