26 Commits

Author SHA1 Message Date
zxq5 7b18922cc7 Merge pull request 'Merge compiler and emulator progress from last few months into main.' (#11) from compiler into main
Reviewed-on: #11
2026-02-14 11:54:15 +00:00
zxq5 a0b02cb955 Create tasks.json 2026-02-14 11:50:59 +00:00
zxq5 240f0e553f fixed failing tests
TODO: add comprehensive testing to everything
2026-02-14 11:50:54 +00:00
zxq5 25a59a6b19 fixed clippy lints 2026-02-14 11:50:36 +00:00
zxq5 c67217a6b8 Merge branch 'main' into compiler 2026-02-14 11:09:33 +00:00
zxq5 7ccbd9258f - fixed some clippy lints
- updated comments in compiler codegen
- deleted old dsa compiler outputs
- settings for zed
2026-02-14 11:05:15 +00:00
zxq5 201b18069b continued on register allocator rewrite, slow progress as scoping is
proving to be a challenge
2026-02-14 02:46:29 +00:00
zxq5 d66baf6f99 moved loc 2026-02-13 21:42:59 +00:00
zxq5 75ad04cf95 forgot to commit these 2026-02-10 16:37:33 +00:00
zxq5 8361833b1c broken commit, started working on scopes 2026-02-10 16:33:32 +00:00
zxq5 5e575e2cd8 used claude to write a language spec (syntax and simple examples) for
DSC that we can follow as a reference for implementation.
2026-02-10 10:05:13 +00:00
zxq5 931af90789 - renamed assembler_runner to just assembler
- implemented type parsing including custom types and generics (useless
  for now as we do no semantic analysis)
- implemented struct literal parsing
- implemented struct definition parsing (no generics yet)
- implemented tuple parsing
- registers are now allocated starting from zero
- updated to-dos
2026-02-10 10:03:48 +00:00
zxq5 509b3465f1 docs update 2026-02-09 00:10:49 +00:00
zxq5 22241a5633 - implementation of <var> <op> = <expr> type statements such as `x +=
5`
- implementation of logical and shift operations in parser and codegen.
- implementation of sizeof keyword as unary operator

in progress (non functional)
- implementation of prefix and postfix inc/dec operators

- array access by index (implemented, untested as arrays aren't
  implemented yet). essentially just a pointer write with offset.
- struct/member access (parsing implemented, untested.)
2026-02-09 00:10:37 +00:00
zxq5 e2be83414b - updated assembler to support new shift implementation
- updated emulator to support new shift implementation
- updated emulator to rename NoReg to Null as in the common lib
2026-02-09 00:05:45 +00:00
zxq5 f7ed764e96 renamed NoReg to Null in common 2026-02-09 00:04:19 +00:00
zxq5 328741eb51 updated compiler with support for more operators.
(only the unary operators from this are implemented for now)
2026-02-08 20:03:31 +00:00
nullndvoid 458661b02a misc: add 'profiling' profile. 2025-06-29 04:11:41 +01:00
nullndvoid c41e5328e6 docs: fix failing doctest 2025-06-29 02:21:31 +01:00
zxq5 67ebf48d6f removed old log file 2025-06-29 02:08:06 +01:00
zxq5 98668c681e more optimisations test program ~54MIPS -> ~110MIPS 2025-06-29 02:04:14 +01:00
zxq5 05a25447b2 minor optimisation to reduce unnecessary allocations 2025-06-28 03:43:20 +01:00
zxq5 56d2abe17f - optimised main emulator loop, allowing updates only once every roughly 32,000 instructions.
- optimised memory access patterns, removing unecessary mutability and accesses.
- replaced the standard HashMap with an implementation that uses a faster hashing algorithm.

results:

before:
    - our benchmark program with ~4m instructions would take around for their data to make it to the UI, and a bit over 200ms to actually run

after:
    - our benchmark program with ~4m instructions can run in around 75ms, and the UI receives the update almost instantly.

conclusion:
- emulator performance should be around 2-3x faster than before.
2025-06-28 03:21:46 +01:00
zxq5 eaaefd1b07 added rule to .gitignore 2025-06-27 18:31:52 +01:00
zxq5 5302ad3876 removed junk files 2025-06-27 18:30:53 +01:00
zxq5 2280f1e5d9 updated vscode settings 2025-06-27 18:30:26 +01:00
47 changed files with 4929 additions and 1506 deletions
+4
View File
@@ -5,3 +5,7 @@ rustc-wrapper = "sccache"
[future-incompat-report] [future-incompat-report]
frequency = "always" frequency = "always"
[profile.profiling]
inherits = "release"
debug = true
+4
View File
@@ -8,4 +8,8 @@
"files.trimTrailingWhitespace": true, "files.trimTrailingWhitespace": true,
"gitea.owner": "LowLevelDevs", "gitea.owner": "LowLevelDevs",
"gitea.repo": "damn_simple_architecture", "gitea.repo": "damn_simple_architecture",
"[markdown]": {
"editor.formatOnSave": true,
"editor.formatOnPaste": true
}
} }
+15
View File
@@ -0,0 +1,15 @@
// Folder-specific settings
//
// For a full list of overridable settings, and general information on folder-specific settings,
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
{
"lsp": {
"rust-analyzer": {
"initialization_options": {
"check": {
"command": "clippy", // rust-analyzer.check.command (default: "check")
},
},
},
},
}
+37
View File
@@ -0,0 +1,37 @@
[
{
"label": "Run Emulator",
"command": "cargo run --bin emulator",
"use_new_terminal": true,
},
{
"label": "Run Compiler",
"command": "cargo run --bin compiler",
"use_new_terminal": true,
},
{
"label": "Run Assembler",
"command": "cargo run --bin assembler",
"use_new_terminal": true,
},
{
"label": "Run Build System (dsx-build)",
"command": "cargo run --bin dsx-build",
"use_new_terminal": true,
},
{
"label": "Build All (Release)",
"command": "cargo build --release",
"use_new_terminal": false,
},
{
"label": "Run Tests",
"command": "cargo test",
"use_new_terminal": true,
},
{
"label": "Profile Emulator with perf",
"command": "cargo build --profile profiling; perf record -g -F 999 target/profiling/emulator; perf script -F +pid | save test.perf",
"use_new_terminal": true,
},
]
+6 -2
View File
@@ -11,7 +11,11 @@ authors = ["zxq5", "nullndvoid"]
[profile.dev] [profile.dev]
codegen-backend = "cranelift" codegen-backend = "cranelift"
panic = "abort" # Cranelift does not support stack unwinds. panic = "abort" # Cranelift does not support stack unwinds.
lto = false lto = false
debug = true debug = true
incremental = false # sccache does not support caching incremental crates. incremental = false # sccache does not support caching incremental crates.
[profile.release]
debug = true
lto = "fat"
+1 -1
View File
@@ -5,7 +5,7 @@ edition.workspace = true
authors.workspace = true authors.workspace = true
[[bin]] [[bin]]
name = "assembler_runner" name = "assembler"
path = "src/main.rs" path = "src/main.rs"
[lib] [lib]
+18 -6
View File
@@ -223,19 +223,31 @@ fn build_shift_instruction(
opcode: Opcode, opcode: Opcode,
args: &[crate::assembler::model::Token], args: &[crate::assembler::model::Token],
) -> Result<Instruction, AssembleError> { ) -> Result<Instruction, AssembleError> {
let Some(reg_token) = args.first() else { let Some(src_reg) = args.first() else {
return Err(AssembleError::MissingArgument(0)); return Err(AssembleError::MissingArgument(0));
}; };
let Some(amount_token) = args.get(1) else { let Some(r_shamt) = args.get(1) else {
return Err(AssembleError::MissingArgument(0));
};
let Some(i_shamt) = args.get(2) else {
return Err(AssembleError::MissingArgument(1));
};
let Some(dest_reg) = args.get(3) else {
return Err(AssembleError::MissingArgument(1)); return Err(AssembleError::MissingArgument(1));
}; };
let reg = expect_token!(reg_token, Register)?; let src = expect_token!(src_reg, Register)?;
let amount = expect_token!(amount_token, Immediate)? as u8; let r_shamt = expect_token!(r_shamt, Register)?;
let i_shamt = expect_token!(i_shamt, Immediate)? as u8;
let dest = expect_token!(dest_reg, Register)?;
match opcode { match opcode {
Opcode::Shl => Ok(Instruction::ShiftLeft(args!(R, sr1: reg, shamt: amount))), Opcode::Shl => Ok(Instruction::ShiftLeft(
Opcode::Shr => Ok(Instruction::ShiftRight(args!(R, sr1: reg, shamt: amount))), args!(R, sr1: src, sr2: r_shamt, shamt: i_shamt, dr: dest),
)),
Opcode::Shr => Ok(Instruction::ShiftRight(
args!(R, sr1: src, sr2: r_shamt, shamt: i_shamt, dr: dest),
)),
_ => unreachable!(), _ => unreachable!(),
} }
} }
+2 -1
View File
@@ -4,7 +4,8 @@ use crate::assembler::model::{Node, Opcode, Symbol, Token};
/// Parse DSA assembly code with optional formatting /// Parse DSA assembly code with optional formatting
/// ///
/// # Examples /// # Examples
/// ``` /// ```rs
/// use assembler::macros::dsa;
/// // With formatting: /// // With formatting:
/// let nodes = dsa!(hash, "mov r1, {}", 42)?; /// let nodes = dsa!(hash, "mov r1, {}", 42)?;
/// ///
+5 -5
View File
@@ -184,11 +184,11 @@ pub enum Token {
impl fmt::Display for Token { impl fmt::Display for Token {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Self::Symbol(symbol) => write!(f, "{}", symbol), Self::Symbol(symbol) => write!(f, "{symbol}"),
Self::Register(register) => write!(f, "{}", register), Self::Register(register) => write!(f, "{register}",),
Self::Immediate(immediate) => write!(f, "{}", immediate), Self::Immediate(immediate) => write!(f, "{immediate}",),
Self::StringLit(string_lit) => write!(f, "{}", string_lit), Self::StringLit(string_lit) => write!(f, "{string_lit}",),
Self::Opcode(opcode) => write!(f, "{}", opcode), Self::Opcode(opcode) => write!(f, "{opcode}",),
} }
} }
} }
+54 -15
View File
@@ -1,5 +1,6 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use crate::assembler::TokenType;
use crate::{assembler::AssembleError, expect_token, expect_type, node}; use crate::{assembler::AssembleError, expect_token, expect_type, node};
use crate::assembler::model::{Node, Opcode, Token}; use crate::assembler::model::{Node, Opcode, Token};
@@ -100,6 +101,7 @@ impl Parser {
let opcode = expect_token!(self.next()?, Opcode)?; let opcode = expect_token!(self.next()?, Opcode)?;
let args: Vec<Token>; let args: Vec<Token>;
#[allow(clippy::match_same_arms)]
match opcode { match opcode {
// R-type instructions // R-type instructions
Opcode::Mov | Opcode::Movs => { Opcode::Mov | Opcode::Movs => {
@@ -112,22 +114,25 @@ impl Parser {
let base = expect_type!(self.next()?, Register, Symbol)?; let base = expect_type!(self.next()?, Register, Symbol)?;
let dest = expect_type!(self.next()?, Register)?; let dest = expect_type!(self.next()?, Register)?;
let mut offset = Token::Immediate(0); let offset = match self.peek_next() {
if let Ok(next) = self.peek_next() Ok(next) if expect_type!(next.clone(), Immediate).is_ok() => {
&& expect_type!(next, Immediate).is_ok() { self.next()?
offset = self.next()?;
} }
_ => Token::Immediate(0),
};
args = vec![base, dest, offset]; args = vec![base, dest, offset];
} }
Opcode::Stb | Opcode::Sth | Opcode::Stw => { Opcode::Stb | Opcode::Sth | Opcode::Stw => {
let base = expect_type!(self.next()?, Register)?; let base = expect_type!(self.next()?, Register)?;
let dest = expect_type!(self.next()?, Register, Symbol)?; let dest = expect_type!(self.next()?, Register, Symbol)?;
let mut offset = Token::Immediate(0);
if let Ok(next) = self.peek_next() let offset = match self.peek_next() {
&& expect_type!(next, Immediate).is_ok() { Ok(next) if expect_type!(next.clone(), Immediate).is_ok() => {
offset = self.next()?; self.next()?
} }
_ => Token::Immediate(0),
};
args = vec![base, dest, offset]; args = vec![base, dest, offset];
} }
@@ -146,15 +151,49 @@ impl Parser {
} }
Opcode::Not | Opcode::Cmp => { Opcode::Not | Opcode::Cmp => {
let reg1 = expect_type!(self.next()?, Register, Symbol)?; let src = expect_type!(self.next()?, Register, Symbol)?;
let reg2 = expect_type!(self.next()?, Register, Symbol)?; let dest = expect_type!(self.next()?, Register, Symbol)?;
args = vec![reg1, reg2]; args = vec![src, dest];
} }
Opcode::Shl | Opcode::Shr => { Opcode::Shl | Opcode::Shr => {
let reg = expect_type!(self.next()?, Register, Symbol)?; let src = expect_type!(self.next()?, Register, Symbol)?;
let num = expect_type!(self.next()?, Immediate)?;
args = vec![reg, num]; // First operand after src: could be immediate or register
let first = self.next()?;
let (r_shamt, i_shamt) = match first {
Token::Register(_) => (
first,
if let Ok(tok) = self.peek_next() {
if expect_type!(tok, Immediate).is_ok() {
self.next()?
} else {
Token::Immediate(0)
}
} else {
Token::Immediate(0)
},
),
Token::Immediate(_) => (Token::Register(Register::Zero), first),
_ => {
return Err(AssembleError::UnexpectedToken(
first,
TokenType::Immediate,
));
}
};
let dest = if let Ok(tok) = self.peek_next() {
if expect_type!(tok, Register).is_ok() {
self.next()?
} else {
src.clone() // Default to src if no dest specified
}
} else {
src.clone() // Default to src if no dest specified
};
args = vec![src, r_shamt, i_shamt, dest];
} }
Opcode::Inc | Opcode::Dec => { Opcode::Inc | Opcode::Dec => {
+1 -1
View File
@@ -34,7 +34,7 @@ use crate::prelude::CompilerEngine;
pub fn assemble_file(input: &str, output: &str) -> Result<(), std::io::Error> { pub fn assemble_file(input: &str, output: &str) -> Result<(), std::io::Error> {
let mut engine = CompilerEngine::new(); let mut engine = CompilerEngine::new();
engine.start_compilation(Path::new(input)); engine.start_compilation(Path::new(input));
let result = engine.wait_for_result().unwrap(); let result = engine.wait_for_result().expect("assembler failed.");
let buffer: Vec<u8> = result let buffer: Vec<u8> = result
.iter() .iter()
+1
View File
@@ -0,0 +1 @@
disallowed-types = ["std::collections::HashMap", "std::collections::HashSet"]
+7 -11
View File
@@ -40,7 +40,7 @@ pub enum InstructionType {
Immediate, Immediate,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
#[non_exhaustive] #[non_exhaustive]
pub enum Register { pub enum Register {
// general purpose registers // general purpose registers
@@ -69,7 +69,9 @@ pub enum Register {
Idr, Idr,
Mmr, Mmr,
Zero, Zero,
NoReg,
#[default]
Null, // Invalid - Triggers a fault if accessed
// system registers - can't be written to by instructions. // system registers - can't be written to by instructions.
Mar, Mar,
@@ -104,12 +106,6 @@ impl Register {
} }
} }
impl Default for Register {
fn default() -> Self {
Self::NoReg
}
}
impl TryFrom<u8> for Register { impl TryFrom<u8> for Register {
type Error = RegisterParseError; type Error = RegisterParseError;
@@ -144,7 +140,7 @@ impl TryFrom<u8> for Register {
0x14 => Self::Idr, 0x14 => Self::Idr,
0x15 => Self::Mmr, 0x15 => Self::Mmr,
0x16 => Self::Zero, 0x16 => Self::Zero,
0x17 => Self::NoReg, 0x17 => Self::Null,
0x18 => Self::Mar, 0x18 => Self::Mar,
0x19 => Self::Mdr, 0x19 => Self::Mdr,
0x1A => Self::Sts, 0x1A => Self::Sts,
@@ -183,7 +179,7 @@ impl TryFrom<&str> for Register {
"idr" => Ok(Self::Idr), "idr" => Ok(Self::Idr),
"mmr" => Ok(Self::Mmr), "mmr" => Ok(Self::Mmr),
"zero" => Ok(Self::Zero), "zero" => Ok(Self::Zero),
"null" => Ok(Self::NoReg), "null" => Ok(Self::Null),
"pcx" => Ok(Self::Pcx), "pcx" => Ok(Self::Pcx),
_ => Err(RegisterParseError::InvalidName(value.to_string())), _ => Err(RegisterParseError::InvalidName(value.to_string())),
} }
@@ -216,7 +212,7 @@ impl std::fmt::Display for Register {
Self::Idr => write!(f, "idr"), Self::Idr => write!(f, "idr"),
Self::Mmr => write!(f, "mmr"), Self::Mmr => write!(f, "mmr"),
Self::Zero => write!(f, "zero"), Self::Zero => write!(f, "zero"),
Self::NoReg => write!(f, "noreg"), Self::Null => write!(f, "null"),
Self::Mar => write!(f, "mar"), Self::Mar => write!(f, "mar"),
Self::Mdr => write!(f, "mdr"), Self::Mdr => write!(f, "mdr"),
Self::Sts => write!(f, "sts"), Self::Sts => write!(f, "sts"),
+3 -3
View File
@@ -8,9 +8,9 @@ pub trait Encode {
/// Encodes a zero argument instruction. /// Encodes a zero argument instruction.
fn encode_no_args(opcode: u8) -> u32 { fn encode_no_args(opcode: u8) -> u32 {
let opcode = u32::from(opcode); let opcode = u32::from(opcode);
let sr1 = Register::NoReg as u32; let sr1 = Register::Null as u32;
let sr2 = Register::NoReg as u32; let sr2 = Register::Null as u32;
let dr = Register::NoReg as u32; let dr = Register::Null as u32;
let shamt = 0; let shamt = 0;
(opcode << 26) | (sr1 << 21) | (sr2 << 16) | (dr << 11) | (shamt << 6) (opcode << 26) | (sr1 << 21) | (sr2 << 16) | (dr << 11) | (shamt << 6)
+4 -4
View File
@@ -2,7 +2,7 @@ use crate::prelude::*;
#[test] #[test]
fn test_encode_nop() { fn test_encode_nop() {
let no_reg = Register::NoReg as u32; let no_reg = Register::Null as u32;
let no_op = u32::from(Instruction::Nop.opcode()); let no_op = u32::from(Instruction::Nop.opcode());
let expected = (no_op << 26) | (no_reg << 21) | (no_reg << 16) | (no_reg << 11); let expected = (no_op << 26) | (no_reg << 21) | (no_reg << 16) | (no_reg << 11);
@@ -15,7 +15,7 @@ fn test_encode_nop() {
fn test_encode_mov() { fn test_encode_mov() {
let rg0 = Register::Rg0 as u32; let rg0 = Register::Rg0 as u32;
let rg1 = Register::Rg1 as u32; let rg1 = Register::Rg1 as u32;
let no_reg = Register::NoReg as u32; let no_reg = Register::Null as u32;
let instruction = Instruction::Mov(RTypeArgs::new( let instruction = Instruction::Mov(RTypeArgs::new(
Some(Register::Rg0), Some(Register::Rg0),
@@ -53,7 +53,7 @@ fn test_encode_load_byte() {
#[test] #[test]
fn test_encode_shift_left_shamt() { fn test_encode_shift_left_shamt() {
let rg0 = Register::Rg0 as u32; let rg0 = Register::Rg0 as u32;
let no_reg = Register::NoReg as u32; let no_reg = Register::Null as u32;
let shift_amount = 5; let shift_amount = 5;
@@ -80,7 +80,7 @@ fn test_encode_shift_left_shamt() {
fn test_encode_shift_left_reg() { fn test_encode_shift_left_reg() {
let rg0 = Register::Rg0 as u32; let rg0 = Register::Rg0 as u32;
let rg1 = Register::Rg1 as u32; let rg1 = Register::Rg1 as u32;
let no_reg = Register::NoReg as u32; let no_reg = Register::Null as u32;
let instruction = Instruction::ShiftLeft(RTypeArgs::new( let instruction = Instruction::ShiftLeft(RTypeArgs::new(
Some(Register::Rg0), Some(Register::Rg0),
+1
View File
@@ -7,3 +7,4 @@ authors.workspace = true
[dependencies] [dependencies]
chrono = "0.4.43" chrono = "0.4.43"
common = { path = "../common" } common = { path = "../common" }
uuid = { version = "1.20.0", features = ["v4"] }
File diff suppressed because it is too large Load Diff
+797
View File
@@ -0,0 +1,797 @@
use std::fmt;
use crate::backend::dsa::registers::Register;
pub struct InsBlock {
instructions: Vec<Instruction>,
}
impl InsBlock {
pub fn new() -> Self {
Self {
instructions: vec![],
}
}
pub fn insert(&mut self, index: usize, instr: Instruction) {
self.instructions.insert(index, instr);
}
pub fn push(&mut self, instr: Instruction) {
self.instructions.push(instr);
}
pub fn append(&mut self, mut other: Self) {
self.instructions.append(&mut other.instructions);
}
pub fn extend(&mut self, instrs: impl IntoIterator<Item = Instruction>) {
self.instructions.extend(instrs);
}
pub fn is_empty(&self) -> bool {
self.instructions.is_empty()
}
pub fn len(&self) -> usize {
self.instructions.len()
}
pub fn iter(&self) -> impl Iterator<Item = &Instruction> {
self.instructions.iter()
}
}
impl From<Vec<Instruction>> for InsBlock {
fn from(instructions: Vec<Instruction>) -> Self {
Self { instructions }
}
}
impl From<Instruction> for InsBlock {
fn from(instr: Instruction) -> Self {
Self {
instructions: vec![instr],
}
}
}
#[derive(Debug, Clone)]
pub enum Instruction {
// Labels and comments
Label(Label),
Comment {
text: String,
top_level: bool,
},
Newline,
// Data Directives
Db {
label: String,
data: Vec<u8>,
},
Dh {
label: String,
data: Vec<u16>,
},
Dw {
label: String,
data: Vec<u32>,
},
DString {
// alias for db.
label: String,
data: String,
},
Resx {
label: String,
size: u32,
},
// Include
Include {
name: String,
path: String,
},
// Data movement
Mov {
src: Register,
dest: Register,
},
Movs {
src: Register,
dest: Register,
},
// Memory operations
Ldb {
src: MemOperand,
dest: Register,
},
Ldh {
src: MemOperand,
dest: Register,
},
Ldw {
src: MemOperand,
dest: Register,
},
Stb {
src: Register,
dest: MemOperand,
},
Sth {
src: Register,
dest: MemOperand,
},
Stw {
src: Register,
dest: MemOperand,
},
// Immediate loads
Lli {
imm: Imm,
dest: Register,
},
Lui {
imm: Imm,
dest: Register,
},
Lwi {
imm: Imm,
dest: Register,
},
LwiLabel {
label: String,
dest: Register,
},
// Arithmetic
Add {
src1: Register,
src2: Register,
dest: Register,
},
Sub {
src1: Register,
src2: Register,
dest: Register,
},
IAdd {
src: Register,
imm: Imm,
dest: Option<Register>,
},
ISub {
src: Register,
imm: Imm,
dest: Option<Register>,
},
Inc {
reg: Register,
},
Dec {
reg: Register,
},
// Bitwise
And {
src1: Register,
src2: Register,
dest: Register,
},
Or {
src1: Register,
src2: Register,
dest: Register,
},
Xor {
src1: Register,
src2: Register,
dest: Register,
},
Not {
src: Register,
dest: Register,
},
Nand {
src1: Register,
src2: Register,
dest: Register,
},
Nor {
src1: Register,
src2: Register,
dest: Register,
},
Xnor {
src1: Register,
src2: Register,
dest: Register,
},
// Shifts
Shl {
src1: Register,
r_shamt: Register,
i_shamt: u16,
dest: Register,
},
Shr {
src1: Register,
r_shamt: Register,
i_shamt: u16,
dest: Register,
},
// Comparison
Cmp {
reg1: Register,
reg2: Register,
},
// Jumps
Jmp {
target: Label,
},
Jeq {
target: Label,
},
Jne {
target: Label,
},
Jgt {
target: Label,
},
Jge {
target: Label,
},
Jlt {
target: Label,
},
Jle {
target: Label,
},
// Stack
Push {
reg: Register,
},
Pop {
reg: Register,
},
// Function calls
Call {
target: String,
}, // namespace::function
Return,
// System
Hlt,
Nop,
Int {
code: u8,
},
}
pub enum DataDirective {
U8(Vec<u8>),
U16(Vec<u16>),
U32(Vec<u32>),
String(String),
Char(char),
}
impl fmt::Display for Instruction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Label(l) => write!(f, "{}:", l),
Self::Newline => write!(f, ""), /* empty string as newlines are inserted */
// automatically.
Self::Comment { text, top_level } => write!(
f,
"{}",
text.lines()
.map(|line| format!(
"{}// {}",
if *top_level { "" } else { " " },
line.trim(),
))
.collect::<Vec<String>>()
.join("\n")
),
Self::Include { name, path } => write!(f, "include {name}: \"{}\"", path),
Self::Db { label, data } => write!(
f,
"db {}: {}",
label,
data.iter()
.map(|&b| format!("{:#04X}", b))
.collect::<Vec<String>>()
.join(", ")
),
Self::Dh { label, data } => write!(
f,
"dh {}: {}",
label,
data.iter()
.map(|&b| format!("{:#06X}", b))
.collect::<Vec<String>>()
.join(", ")
),
Self::Dw { label, data } => write!(
f,
"dw {}: {}",
label,
data.iter()
.map(|&b| format!("{:#08X}", b))
.collect::<Vec<String>>()
.join(", ")
),
Self::DString { label, data } => write!(f, "db {}: \"{}\"", label, data),
Self::Resx { label, size } => write!(f, "resx {}: {}", label, size),
Self::Mov { src, dest } => write!(f, " mov {}, {}", src, dest),
Self::Movs { src, dest } => write!(f, " movs {}, {}", src, dest),
Self::Ldb { src: addr, dest } => {
let (reg, offset) = reg_and_offset(addr);
write!(f, " ldb {}, {}, {}", reg, dest, offset)
}
Self::Ldh { src: addr, dest } => {
let (reg, offset) = reg_and_offset(addr);
write!(f, " ldh {}, {}, {}", reg, dest, offset)
}
Self::Ldw { src, dest } => {
let (reg, offset) = reg_and_offset(src);
write!(f, " ldw {}, {}, {}", reg, dest, offset)
}
// Self::Ldbs { addr, dest } => {
// write!(f, " ldbs {}, {}", format_mem_operand(addr), dest)
// }
// Self::Ldhs { addr, dest } => {
// write!(f, " ldhs {}, {}", format_mem_operand(addr), dest)
// }
// Self::Ldws { addr, dest } => {
// write!(f, " ldws {}, {}", format_mem_operand(addr), dest)
// }
Self::Stb { src, dest: addr } => {
let (reg, offset) = reg_and_offset(addr);
write!(f, " stb {}, {}, {}", src, reg, offset)
}
Self::Sth { src, dest: addr } => {
let (reg, offset) = reg_and_offset(addr);
write!(f, " sth {}, {}, {}", src, reg, offset)
}
Self::Stw { src, dest: addr } => {
let (reg, offset) = reg_and_offset(addr);
write!(f, " stw {}, {}, {}", src, reg, offset)
}
Self::Lli { imm, dest } => write!(f, " lli {}, {}", imm, dest),
Self::Lui { imm, dest } => write!(f, " lui {}, {}", imm, dest),
Self::Lwi { imm, dest } => write!(f, " lwi {}, {}", imm, dest),
Self::LwiLabel { label, dest } => write!(f, " lwi {}, {}", label, dest),
// arithmetic
Self::Add { src1, src2, dest } => {
write!(f, " add {}, {}, {}", src1, src2, dest)
}
Self::Sub { src1, src2, dest } => {
write!(f, " sub {}, {}, {}", src1, src2, dest)
}
Self::And { src1, src2, dest } => {
write!(f, " and {}, {}, {}", src1, src2, dest)
}
Self::Or { src1, src2, dest } => {
write!(f, " or {}, {}, {}", src1, src2, dest)
}
Self::Nand { src1, src2, dest } => {
write!(f, " nand {}, {}, {}", src1, src2, dest)
}
Self::Xor { src1, src2, dest } => {
write!(f, " xor {}, {}, {}", src1, src2, dest)
}
Self::Nor { src1, src2, dest } => {
write!(f, " nor {}, {}, {}", src1, src2, dest)
}
Self::Not { src, dest } => {
write!(f, " not {} {}", src, dest)
}
Self::Xnor { src1, src2, dest } => {
write!(f, " xnor {}, {}, {}", src1, src2, dest)
}
Self::IAdd { src, imm, dest } => {
if let Some(d) = dest {
write!(f, " addi {}, {}, {}", src, imm, d)
} else {
write!(f, " addi {}, {}", src, imm)
}
}
Self::ISub { src, imm, dest } => {
if let Some(d) = dest {
write!(f, " subi {}, {}, {}", src, imm, d)
} else {
write!(f, " subi {}, {}", src, imm)
}
}
// shift instructions
Self::Shl {
src1,
r_shamt,
i_shamt,
dest,
} => {
write!(f, " shl {}, {}, {}, {}", src1, r_shamt, i_shamt, dest)
}
Self::Shr {
src1,
r_shamt,
i_shamt,
dest,
} => {
write!(f, " shl {}, {}, {}, {}", src1, r_shamt, i_shamt, dest)
}
// increment instructions
Self::Inc { reg } => write!(f, " inc {}", reg),
Self::Dec { reg } => write!(f, " dec {}", reg),
Self::Cmp { reg1, reg2 } => write!(f, " cmp {}, {}", reg1, reg2),
// jump instructions
Self::Jmp { target } => write!(f, " jmp {}", target),
Self::Jeq { target } => write!(f, " jeq {}", target),
Self::Jne { target } => write!(f, " jne {}", target),
Self::Jgt { target } => write!(f, " jgt {}", target),
Self::Jge { target } => write!(f, " jge {}", target),
Self::Jlt { target } => write!(f, " jlt {}", target),
Self::Jle { target } => write!(f, " jle {}", target),
// stack pseudoinstructions
Self::Push { reg } => write!(f, " push {}", reg),
Self::Pop { reg } => write!(f, " pop {}", reg),
// call & return pseudoinstructions
Self::Call { target } => write!(f, " call {}", target),
Self::Return => write!(f, " return"),
// misc instructions
Self::Int { code } => write!(f, " int {}", code),
Self::Hlt => write!(f, " hlt"),
Self::Nop => write!(f, " nop"),
}
}
}
impl Instruction {
// data directives
pub fn db_string(label: impl Into<String>, data: impl Into<String>) -> Self {
Self::DString {
label: label.into(),
data: data.into(),
}
}
pub fn db_word(label: impl Into<String>, data: u32) -> Self {
Self::Dw {
label: label.into(),
data: vec![data],
}
}
pub fn db_bytes(label: impl Into<String>, data: &[u8]) -> Self {
Self::Db {
label: label.into(),
data: data.to_vec(),
}
}
// Movement
pub fn mov<R1, R2>(src: R1, dest: R2) -> Self
where
R1: Into<Register>,
R2: Into<Register>,
{
Self::Mov {
src: src.into(),
dest: dest.into(),
}
}
// Memory loads
pub fn ldw_reg<R>(base: R, dest: Register) -> Self
where
R: Into<Register>,
{
Self::Ldw {
src: MemOperand::RegIndirect(base.into()),
dest,
}
}
pub fn ldw_reg_offset<R>(base: R, dest: Register, offset: i32) -> Self
where
R: Into<Register>,
{
Self::Ldw {
src: MemOperand::RegOffset(base.into(), offset),
dest,
}
}
pub fn ldw_label(label: impl Into<Label>, dest: Register) -> Self {
Self::Ldw {
src: MemOperand::Label(label.into()),
dest,
}
}
// Memory stores
pub fn stw_reg<R>(src: Register, base: R) -> Self
where
R: Into<Register>,
{
Self::Stw {
src,
dest: MemOperand::RegIndirect(base.into()),
}
}
pub fn stw_reg_offset<R>(src: Register, base: R, offset: i32) -> Self
where
R: Into<Register>,
{
Self::Stw {
src,
dest: MemOperand::RegOffset(base.into(), offset),
}
}
pub fn stw_label(src: Register, label: impl Into<Label>) -> Self {
Self::Stw {
src,
dest: MemOperand::Label(label.into()),
}
}
// Arithmetic
pub fn add(src1: Register, src2: Register, dest: Register) -> Self {
Self::Add { src1, src2, dest }
}
pub fn sub(src1: Register, src2: Register, dest: Register) -> Self {
Self::Sub { src1, src2, dest }
}
pub fn and(src1: Register, src2: Register, dest: Register) -> Self {
Self::And { src1, src2, dest }
}
pub fn or(src1: Register, src2: Register, dest: Register) -> Self {
Self::Or { src1, src2, dest }
}
pub fn xor(src1: Register, src2: Register, dest: Register) -> Self {
Self::Xor { src1, src2, dest }
}
pub fn not(src: Register, dest: Register) -> Self {
Self::Not { src, dest }
}
pub fn shl(src1: Register, r_shamt: Register, i_shamt: u16, dest: Register) -> Self {
Self::Shl {
src1,
r_shamt,
i_shamt,
dest,
}
}
pub fn shr(src1: Register, r_shamt: Register, i_shamt: u16, dest: Register) -> Self {
Self::Shr {
src1,
r_shamt,
i_shamt,
dest,
}
}
pub fn iadd(src: Register, value: i64) -> Self {
let imm = Imm(value.unsigned_abs() as u32);
if value < 0 {
Self::ISub {
src,
imm,
dest: None,
}
} else {
Self::IAdd {
src,
imm,
dest: None,
}
}
}
pub fn iadd_dest(src: Register, value: i32, dest: Register) -> Self {
let imm = Imm(value.unsigned_abs());
if value < 0 {
Self::ISub {
src,
imm,
dest: Some(dest),
}
} else {
Self::IAdd {
src,
imm,
dest: Some(dest),
}
}
}
pub fn inc(reg: Register) -> Self {
Self::Inc { reg }
}
pub fn dec(reg: Register) -> Self {
Self::Dec { reg }
}
// Immediate loads
pub fn lwi(value: u32, dest: Register) -> Self {
if value > 0xFFFF {
Self::Lwi {
imm: Imm(value),
dest,
}
} else {
Self::Lli {
imm: Imm(value),
dest,
}
}
}
pub fn lwi_label(label: impl Into<String>, dest: Register) -> Self {
Self::LwiLabel {
label: label.into(),
dest,
}
}
// Control flow
pub fn label(name: impl Into<String>) -> Self {
Self::Label(Label(name.into()))
}
pub fn jmp(target: impl Into<Label>) -> Self {
Self::Jmp {
target: target.into(),
}
}
pub fn jeq(target: impl Into<Label>) -> Self {
Self::Jeq {
target: target.into(),
}
}
pub fn cmp(reg1: Register, reg2: Register) -> Self {
Self::Cmp { reg1, reg2 }
}
// Stack
pub fn push(reg: Register) -> Self {
Self::Push { reg }
}
pub fn pop(reg: Register) -> Self {
Self::Pop { reg }
}
// Functions
pub fn call(target: impl Into<String>) -> Self {
Self::Call {
target: target.into(),
}
}
pub fn int(code: u8) -> Self {
Self::Int { code }
}
pub fn ret() -> Self {
Self::Return
}
// Utilities
pub fn comment(text: impl Into<String>) -> Self {
Self::Comment {
text: text.into(),
top_level: false,
}
}
pub fn global_comment(text: impl Into<String>) -> Self {
Self::Comment {
text: text.into(),
top_level: true,
}
}
pub fn include(name: impl Into<String>, path: impl Into<String>) -> Self {
Self::Include {
name: name.into(),
path: path.into(),
}
}
}
// Convenience trait for Label conversion
impl From<String> for Label {
fn from(s: String) -> Self {
Label(s)
}
}
impl From<&str> for Label {
fn from(s: &str) -> Self {
Label(s.to_string())
}
}
fn reg_and_offset(op: &MemOperand) -> (String, i32) {
match op {
MemOperand::RegIndirect(reg) => (reg.to_string(), 0),
MemOperand::RegOffset(reg, offset) => (reg.to_string(), *offset),
MemOperand::Label(label) => (label.to_string(), 0),
MemOperand::LabelOffset(label, offset) => (label.to_string(), *offset),
}
}
/// Memory operand for loads/stores
#[derive(Debug, Clone)]
pub enum MemOperand {
/// Register indirect: [reg]
RegIndirect(Register),
/// Register with offset: [reg + offset]
RegOffset(Register, i32),
/// Label: [label]
Label(Label),
/// Label with offset: [label + offset]
LabelOffset(Label, i32),
}
/// Immediate value (16-bit or 32-bit)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Imm(pub u32);
impl fmt::Display for Imm {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
/// Label reference
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Label(pub String);
impl fmt::Display for Label {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
+3
View File
@@ -1,7 +1,10 @@
use crate::model::{CompilerError, Program}; use crate::model::{CompilerError, Program};
mod codegen; mod codegen;
mod instruction;
mod registers; mod registers;
mod scope;
mod variable;
pub fn generate_code(ast: &Program) -> Result<String, CompilerError> { pub fn generate_code(ast: &Program) -> Result<String, CompilerError> {
let mut codegen = codegen::CodeGenerator::new(ast.clone()); let mut codegen = codegen::CodeGenerator::new(ast.clone());
+132 -114
View File
@@ -1,12 +1,14 @@
use std::{collections::HashMap, fmt}; use std::{collections::HashMap, fmt};
use crate::model::CompilerError; use crate::{
backend::dsa::instruction::{InsBlock, Instruction},
model::CompilerError,
};
/// Register allocator for DSA assembly generation /// Register allocator for DSA assembly generation
/// Manages general-purpose registers (rg0-rgf) and handles stack spilling /// Manages general-purpose registers (rg0-rgf) and handles stack spilling
pub struct RegisterAllocator { pub struct RegisterAllocator {
/// Available general-purpose registers /// Available general-purpose registers
/// Maps variable names to their current location (register or stack offset) /// Maps variable names to their current location (register or stack offset)
variable_locations: HashMap<String, Location>, variable_locations: HashMap<String, Location>,
@@ -18,7 +20,7 @@ pub struct RegisterAllocator {
stack_offset: i32, stack_offset: i32,
/// Track which registers are currently in use /// Track which registers are currently in use
in_use: HashMap<Register, bool>, in_use: Vec<(Register, bool)>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -79,14 +81,14 @@ impl RegisterAllocator {
/// Allocate a temporary register for expression evaluation /// Allocate a temporary register for expression evaluation
/// Returns the register name and optionally assembly code to save it /// Returns the register name and optionally assembly code to save it
pub fn alloc_temp(&mut self) -> Result<(Register, Vec<String>), CompilerError> { pub fn alloc_temp(&mut self) -> Result<(Register, InsBlock), CompilerError> {
// Try to find an unused register // Try to find an unused register
// println!("finding! {:#?}", self.in_use); // println!("finding! {:#?}", self.in_use);
if let Some(reg) = self.find_free_register() { if let Some(reg) = self.find_free_register() {
self.in_use.insert(reg, true); self.in_use[reg as usize].1 = true;
return Ok((reg, Vec::new())); return Ok((reg, InsBlock::new()));
} }
// All registers in use - need to spill one // All registers in use - need to spill one
@@ -132,8 +134,8 @@ impl RegisterAllocator {
} }
// This is a true temporary - safe to free // This is a true temporary - safe to free
if reg != Register::Zero { if !matches!(reg, Register::Zero | Register::Null) {
self.in_use.insert(reg, false); self.in_use[reg as usize].1 = false;
} }
} }
@@ -141,10 +143,10 @@ impl RegisterAllocator {
// Check if this variable is in a register // Check if this variable is in a register
if let Some(location) = self.variable_locations.get(var).cloned() { if let Some(location) = self.variable_locations.get(var).cloned() {
if let Some(reg) = location.register if let Some(reg) = location.register
&& reg != Register::Zero && !matches!(reg, Register::Zero | Register::Null)
{ {
self.register_contents.remove(&reg); self.register_contents.remove(&reg);
self.in_use.insert(reg, false); self.in_use[reg as usize].1 = false;
} }
self.variable_locations.remove(var); self.variable_locations.remove(var);
@@ -156,11 +158,11 @@ impl RegisterAllocator {
pub fn alloc_var( pub fn alloc_var(
&mut self, &mut self,
var_name: &str, var_name: &str,
) -> Result<(Register, Vec<String>), CompilerError> { ) -> Result<(Register, InsBlock), CompilerError> {
if let Some(mut location) = self.variable_locations.get(var_name).cloned() { if let Some(mut location) = self.variable_locations.get(var_name).cloned() {
// if the var is in a register we can use it already. // if the var is in a register we can use it already.
if let Some(reg) = location.register { if let Some(reg) = location.register {
return Ok((reg, Vec::new())); return Ok((reg, InsBlock::new()));
} }
// if the variable is on the stack only, we need to get it in a register. // if the variable is on the stack only, we need to get it in a register.
@@ -174,19 +176,17 @@ impl RegisterAllocator {
// Load from bpr + offset (offset is negative) // Load from bpr + offset (offset is negative)
// code.push(format!("\tsubi bpr {} {}", -(offset + 4), reg)); // code.push(format!("\tsubi bpr {} {}", -(offset + 4), reg));
code.push(format!(
"\tldw spr, {}, {} // spr+{}: {}", code.push(Instruction::ldw_reg_offset(
Register::Spr,
reg, reg,
offset - self.stack_offset, offset - self.stack_offset,
offset - self.stack_offset,
var_name
)); ));
// Update location to register // Update location to register
self.variable_locations self.variable_locations
.insert(var_name.to_string(), location); .insert(var_name.to_string(), location);
self.register_contents self.register_contents.insert(reg, var_name.to_string());
.insert(reg.clone(), var_name.to_string());
return Ok((reg, code)); return Ok((reg, code));
} }
@@ -196,8 +196,7 @@ impl RegisterAllocator {
let (reg, code) = self.alloc_temp()?; let (reg, code) = self.alloc_temp()?;
self.variable_locations self.variable_locations
.insert(var_name.to_string(), Location::register(reg)); .insert(var_name.to_string(), Location::register(reg));
self.register_contents self.register_contents.insert(reg, var_name.to_string());
.insert(reg.clone(), var_name.to_string());
Ok((reg, code)) Ok((reg, code))
} }
@@ -212,34 +211,34 @@ impl RegisterAllocator {
pub fn load_var( pub fn load_var(
&mut self, &mut self,
var_name: &str, var_name: &str,
) -> Result<(Register, Vec<String>), CompilerError> { ) -> Result<(Register, InsBlock), CompilerError> {
self.alloc_var(var_name) self.alloc_var(var_name)
} }
/// Store a value from a register into a variable /// Store a value from a register into a variable
/// Updates tracking and returns any necessary assembly code /// Updates tracking and returns any necessary assembly code
pub fn store_var(&mut self, var_name: &str, source_reg: &Register) -> Vec<String> { pub fn store_var(&mut self, var_name: &str, source_reg: &Register) -> InsBlock {
let mut block = InsBlock::new();
// Check if variable already has a location // Check if variable already has a location
if let Some(location) = self.variable_locations.get(var_name) { if let Some(location) = self.variable_locations.get(var_name) {
// if the variable exists in a register we write to that. // if the variable exists in a register we write to that.
if let Some(reg) = location.register { match location.register {
if reg == *source_reg { Some(reg) if reg == *source_reg => {
return vec![format!( block.push(Instruction::mov(*source_reg, reg));
"\tmov {}, {} // save var:{} reg:{}", return block;
source_reg, reg, var_name, reg
)];
} }
_ => (),
} }
// if the variable exists on the stack but not a register we write here. // if the variable exists on the stack but not a register we write here.
if let Some(offset) = location.stack { if let Some(offset) = location.stack {
return vec![format!( block.push(Instruction::stw_reg_offset(
"\tstw {}, spr, {} // save var:{} offset:{}", *source_reg,
source_reg, Register::Spr,
offset - self.stack_offset, offset - self.stack_offset,
var_name, ));
offset return block;
)];
} }
} }
@@ -252,9 +251,9 @@ impl RegisterAllocator {
.insert(var_name.to_string(), Location::register(*source_reg)); .insert(var_name.to_string(), Location::register(*source_reg));
self.register_contents self.register_contents
.insert(*source_reg, var_name.to_string()); .insert(*source_reg, var_name.to_string());
self.in_use.insert(*source_reg, true); self.in_use[*source_reg as usize].1 = true;
return Vec::new(); return block;
} }
// if current register isn't free, (eg is another variable) we assign somewhere // if current register isn't free, (eg is another variable) we assign somewhere
@@ -263,10 +262,11 @@ impl RegisterAllocator {
self.variable_locations self.variable_locations
.insert(var_name.to_string(), Location::register(free_reg)); .insert(var_name.to_string(), Location::register(free_reg));
self.register_contents self.register_contents
.insert(free_reg.clone(), var_name.to_string()); .insert(free_reg, var_name.to_string());
self.in_use.insert(free_reg, true); self.in_use[free_reg as usize].1 = true;
return vec![format!("\tmov {}, {}", source_reg, free_reg)]; block.push(Instruction::mov(*source_reg, free_reg));
return block;
} }
// No free registers - allocate on stack // No free registers - allocate on stack
@@ -280,11 +280,8 @@ impl RegisterAllocator {
/// spill a register to the stack (WITHOUT FREEING) /// spill a register to the stack (WITHOUT FREEING)
/// DO NOT USE this if it's for a pointer!!!! /// DO NOT USE this if it's for a pointer!!!!
pub fn _spill_register( pub fn _spill_register(&mut self, reg: &Register) -> Result<InsBlock, CompilerError> {
&mut self, let mut code = InsBlock::new();
reg: &Register,
) -> Result<Vec<String>, CompilerError> {
let mut code = Vec::new();
// check if the variable is declared. // check if the variable is declared.
if let Some(var_name) = self.register_contents.get(reg).cloned() if let Some(var_name) = self.register_contents.get(reg).cloned()
@@ -292,13 +289,10 @@ impl RegisterAllocator {
{ {
// check if var is on the stack // check if var is on the stack
if let Some(offset) = location.stack { if let Some(offset) = location.stack {
// ensure stack value is up to date with register value. code.push(Instruction::stw_reg_offset(
code.push(format!( *reg,
"\tstw {}, spr, {} // save var:{} offset:{}", Register::Spr,
reg,
offset - self.stack_offset, offset - self.stack_offset,
var_name,
offset
)); ));
return Ok(code); return Ok(code);
} }
@@ -309,10 +303,7 @@ impl RegisterAllocator {
// if the variable is not on the stack: // if the variable is not on the stack:
// push register to stack (spr decrements automatically) // push register to stack (spr decrements automatically)
let offset = self.stack_offset; let offset = self.stack_offset;
code.push(format!( code.push(Instruction::push(*reg));
"\tpush {} // free var:{} offset:{}",
reg, var_name, offset
));
// Update variable location - it's now at current spr // Update variable location - it's now at current spr
// Note: We track offset from bpr for consistency // Note: We track offset from bpr for consistency
@@ -332,9 +323,7 @@ impl RegisterAllocator {
pub fn free_register( pub fn free_register(
&mut self, &mut self,
reg: &Register, reg: &Register,
) -> Result<(i32, Vec<String>), CompilerError> { ) -> Result<(i32, Instruction), CompilerError> {
let mut code = Vec::new();
// check if the variable is declared. // check if the variable is declared.
if let Some(var_name) = self.register_contents.get(reg).cloned() if let Some(var_name) = self.register_contents.get(reg).cloned()
&& let Some(location) = self.variable_locations.get_mut(&var_name) && let Some(location) = self.variable_locations.get_mut(&var_name)
@@ -342,13 +331,11 @@ impl RegisterAllocator {
// check if var name is on the stack // check if var name is on the stack
if let Some(offset) = location.stack { if let Some(offset) = location.stack {
// store current register value in stack location // store current register value in stack location
code.push(format!( let code = Instruction::stw_reg_offset(
"\tstw {}, spr, {} // save var:{} offset:{}", *reg,
reg, Register::Spr,
offset - self.stack_offset, offset - self.stack_offset,
var_name, );
offset
));
// free the register. // free the register.
location.register = None; location.register = None;
@@ -360,10 +347,7 @@ impl RegisterAllocator {
self.stack_offset -= 4; self.stack_offset -= 4;
let offset = self.stack_offset; let offset = self.stack_offset;
code.push(format!( let code = Instruction::push(*reg);
"\tpush {} // free var:{} offset:{}",
reg, var_name, offset
));
// Update variable location // Update variable location
// Note: We track offset from bpr for consistency // Note: We track offset from bpr for consistency
@@ -390,15 +374,15 @@ impl RegisterAllocator {
} }
/// Spill all registers to stack (useful before function calls) /// Spill all registers to stack (useful before function calls)
pub fn _spill_all(&mut self) -> Vec<String> { pub fn _spill_all(&mut self) -> InsBlock {
let mut code = Vec::new(); let mut code = InsBlock::new();
let regs_to_spill: Vec<Register> = let regs_to_spill: Vec<Register> =
self.register_contents.keys().cloned().collect(); self.register_contents.keys().cloned().collect();
for reg in regs_to_spill { for reg in regs_to_spill {
if let Ok(spill_code) = self.free_register(&reg) { if let Ok(spill_code) = self.free_register(&reg) {
code.extend(spill_code.1); code.push(spill_code.1);
} }
} }
@@ -443,59 +427,25 @@ impl RegisterAllocator {
.collect(); .collect();
} }
/// Mark a variable as dead (no longer needed)
/// Frees its register if it's in one
// pub fn _free_var(&mut self, var_name: &str) {
// if let Some(Location::Register(reg)) = self.variable_locations.get(var_name) {
// let reg = reg.clone();
// self.register_contents.remove(&reg);
// self.in_use.insert(reg, false);
// }
// self.variable_locations.remove(var_name);
// }
/// Get list of registers that contain variables and are in use /// Get list of registers that contain variables and are in use
/// These need to be saved before function calls /// These need to be saved before function calls
pub fn get_caller_saved_registers(&self) -> Vec<Register> { pub fn get_caller_saved_registers(&self) -> Vec<Register> {
self.register_contents self.register_contents
.iter() .iter()
.filter(|(reg, _)| *self.in_use.get(*reg).unwrap_or(&false)) .filter(|(reg, _)| {
.map(|(reg, _)| reg.clone()) self.in_use
.get(**reg as usize)
.unwrap_or(&(Register::Null, false))
.1
})
.map(|(reg, _)| *reg)
.collect() .collect()
} }
/// Save caller-saved registers before a function call
/// Returns assembly code to save them
pub fn _save_caller_saved(&mut self) -> Vec<String> {
let mut code = Vec::new();
// For simplicity, save all currently used registers
// In a more sophisticated compiler, you'd only save registers that are live
for (reg, _) in self.register_contents.clone() {
if *self.in_use.get(&reg).unwrap_or(&false) {
code.push(format!("\tpush {}", reg));
}
}
code
}
/// Restore caller-saved registers after a function call
/// Returns assembly code to restore them
pub fn _restore_caller_saved(&mut self, saved_regs: &[String]) -> Vec<String> {
let mut code = Vec::new();
// Restore in reverse order (LIFO)
for reg in saved_regs.iter().rev() {
code.push(format!("\tpop {}", reg));
}
code
}
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum Register { pub enum Register {
// general purpose
Rg0 = 0, Rg0 = 0,
Rg1 = 1, Rg1 = 1,
Rg2 = 2, Rg2 = 2,
@@ -512,8 +462,68 @@ pub enum Register {
Rgd = 13, Rgd = 13,
Rge = 14, Rge = 14,
Rgf = 15, Rgf = 15,
Zero = 16,
Null = 17, // special
Bpr,
Spr,
Ret,
Acc,
// read only
Pcx,
Zero,
// null
Null,
}
impl Register {
pub fn get_gp() -> [Register; 16] {
[
Register::Rg0,
Register::Rg1,
Register::Rg2,
Register::Rg3,
Register::Rg4,
Register::Rg5,
Register::Rg6,
Register::Rg7,
Register::Rg8,
Register::Rg9,
Register::Rga,
Register::Rgb,
Register::Rgc,
Register::Rgd,
Register::Rge,
Register::Rgf,
]
}
pub fn is_gp(&self) -> bool {
(*self as u8) < 16
}
pub fn from_index(idx: usize) -> Register {
match idx {
0 => Register::Rg0,
1 => Register::Rg1,
2 => Register::Rg2,
3 => Register::Rg3,
4 => Register::Rg4,
5 => Register::Rg5,
6 => Register::Rg6,
7 => Register::Rg7,
8 => Register::Rg8,
9 => Register::Rg9,
10 => Register::Rga,
11 => Register::Rgb,
12 => Register::Rgc,
13 => Register::Rgd,
14 => Register::Rge,
15 => Register::Rgf,
_ => unreachable!("this function shouldn't ever be called with idx>15"),
}
}
} }
impl fmt::Display for Register { impl fmt::Display for Register {
@@ -535,7 +545,15 @@ impl fmt::Display for Register {
Self::Rgd => write!(f, "rgd"), Self::Rgd => write!(f, "rgd"),
Self::Rge => write!(f, "rge"), Self::Rge => write!(f, "rge"),
Self::Rgf => write!(f, "rgf"), Self::Rgf => write!(f, "rgf"),
Self::Acc => write!(f, "acc"),
Self::Ret => write!(f, "ret"),
Self::Bpr => write!(f, "bpr"),
Self::Spr => write!(f, "spr"),
Self::Zero => write!(f, "zero"), Self::Zero => write!(f, "zero"),
Self::Pcx => write!(f, "pcx"),
Self::Null => write!(f, "null"), Self::Null => write!(f, "null"),
} }
} }
+287
View File
@@ -0,0 +1,287 @@
use std::{cell::RefCell, collections::HashMap, ops::Deref, rc::Rc};
use uuid::Uuid;
use crate::{
backend::dsa::{
instruction::{InsBlock, Instruction},
registers::{Register, RegisterAllocator},
variable::Variable,
},
model::CompilerError,
};
pub struct Allocator {
stack_offset: i32,
in_use: [(Register, bool); 16],
}
pub struct TempReg(Register);
pub struct AssignedReg(Register);
pub struct StackSlot(i32);
impl Deref for TempReg {
type Target = Register;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Deref for AssignedReg {
type Target = Register;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Deref for StackSlot {
type Target = i32;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Allocator {
pub fn new() -> Self {
let mut in_use = [(Register::Null, false); 16];
in_use.copy_from_slice(&Register::get_gp().map(|r| (r, false))[0..16]);
Self {
stack_offset: 0,
in_use,
}
}
pub fn get_stack_offset(&self) -> i32 {
self.stack_offset
}
pub fn destroy_scope(&mut self, scope: &mut Scope) {
self.stack_offset = scope.entry_stack_offset;
for var in scope.variables.drain() {
if let Some(assigned) = var.1.register {
self.free_assigned(&assigned);
}
}
}
// what we need:
// - create var in register from temporary register. free temp and use it.
//
// - create var on stack from struct/array literal. return stack offset to write to.
//
// - spill var from register to stack. return stack offset to write to.
//
// - read/write var from stack+offset into register to use while preserving the stack
// slot.
//
// - read / write bytes from the stack+offset in a larger variable into a register.
pub fn read_var(&mut self, var: &mut Variable) -> Result<InsBlock, CompilerError> {
if let Some(slot) = &mut var.stack_slot {
if var.register.is_none() {
var.register = Some(self.allocate_var()?);
}
if let Some(reg) = &var.register {
return Ok(InsBlock::from(Instruction::ldw_reg_offset(
**reg,
Register::Spr,
**slot - self.stack_offset,
)));
}
unreachable!()
}
Err(CompilerError::Generic(format!(
"Tried to write var {} to stack but var was not assigned a reg and/or stack slot",
var.name
)))
}
pub fn write_var(&mut self, var: &mut Variable) -> Result<InsBlock, CompilerError> {
if let Some(slot) = &var.stack_slot {
if let Some(reg) = &var.register {
return Ok(InsBlock::from(Instruction::stw_reg_offset(
**reg,
Register::Spr,
**slot - self.stack_offset,
)));
}
}
Err(CompilerError::Generic(format!(
"Tried to write var {} to stack but var was not assigned a reg and/or stack slot",
var.name
)))
}
pub fn spill_var(&mut self, var: &mut Variable) -> Result<InsBlock, CompilerError> {
if let Some(slot) = &var.stack_slot {
let block = self.write_var(var)?;
if let Some(reg) = &var.register {
self.free_assigned(reg);
var.register = None;
}
return Ok(block);
}
// var doesn't have a stack slot so we need to create one
if let Some(reg) = &var.register {
let slot = self.allocate_stack_slot(var.size);
let block = InsBlock::from(Instruction::push(**reg));
self.free_assigned(reg);
var.register = None;
var.stack_slot = Some(slot);
return Ok(block);
}
return Err(CompilerError::Generic(
"spill_var called on a variable without a register".to_string(),
));
}
pub fn allocate_stack_slot(&mut self, size: usize) -> StackSlot {
self.stack_offset -= size as i32;
let offset = self.stack_offset;
StackSlot(offset)
}
pub fn allocate_var(&mut self) -> Result<AssignedReg, CompilerError> {
if let Some(reg) = self.find_free_register() {
Ok(AssignedReg(reg))
} else {
Err(CompilerError::Generic(
"No free registers available".to_string(),
))
}
}
pub fn allocate_temp(&mut self) -> Result<TempReg, CompilerError> {
// allocates a temporary register
if let Some(reg) = self.find_free_register() {
Ok(TempReg(reg))
} else {
todo!("an efficient stack spilling algorithm. needs scope awareness.");
}
}
pub fn free_temp(&mut self, temp: &TempReg) {
// frees a temporary register.
self.in_use[**temp as usize].1 = false;
}
fn free_assigned(&mut self, reg: &AssignedReg) {
// frees a register.
self.in_use[**reg as usize].1 = false;
}
// if we have register(s) free, return the first one.
fn find_free_register(&mut self) -> Option<Register> {
self.in_use.iter_mut().find_map(|(reg, used)| {
if !*used {
*used = true;
Some(*reg)
} else {
None
}
})
}
}
pub struct FunctionContext {
name: String,
allocator: RefCell<Allocator>,
}
impl FunctionContext {
pub fn new(name: String) -> Self {
Self {
name,
allocator: RefCell::new(Allocator::new()),
}
}
pub fn get_stack_offset(&self) -> i32 {
self.allocator.borrow().get_stack_offset()
}
}
/// scope object
pub struct Scope<'a> {
/// outer scope, for a function this will be the global scope.
parent: Option<&'a mut Scope<'a>>,
context: Rc<FunctionContext>,
/// is the scope a function body or just a loop?
/// depending on the type, ending a scope will have different behaviour
r#type: ScopeType,
/// variables
variables: HashMap<Uuid, Variable>,
entry_stack_offset: i32,
}
impl<'a> Scope<'a> {
pub fn new(parent: &'a mut Scope<'a>, r#type: ScopeType) -> Scope<'a> {
Self {
entry_stack_offset: parent.context.get_stack_offset(),
context: Rc::clone(&parent.context),
parent: Some(parent),
r#type,
variables: HashMap::new(),
}
}
pub fn close(&mut self) -> Result<(), CompilerError> {
// closing a scope means we need to drop all variables in scope and free
// registers.
for (name, var) in self.variables.iter() {
todo!()
// if let Some(reg) = var.allocated_register {}
// if let Some(offset) = var.bpr_offset {
// self.stack_offset -= offset;
// }
}
Ok(())
}
pub fn alloc_temp_reg(&mut self) -> Result<(Register, InsBlock), CompilerError> {
todo!()
}
pub fn alloc_var_reg(&mut self) -> Result<(Register, InsBlock), CompilerError> {
todo!()
}
pub fn alloc_var_stack(&mut self) -> Result<(Register, InsBlock), CompilerError> {
todo!()
}
pub fn free_var_stack(&mut self) -> Result<(Register, InsBlock), CompilerError> {
todo!()
}
pub fn free_temp_reg(&mut self) -> Result<(Register, InsBlock), CompilerError> {
todo!()
}
}
#[derive(PartialEq, Copy, Clone, Debug)]
pub enum ScopeType {
Function,
IfBlock,
LoopBlock,
}
+93
View File
@@ -0,0 +1,93 @@
use std::{collections::HashMap, hash::Hash, rc::Rc};
use uuid::Uuid;
use crate::{
backend::dsa::{
instruction::InsBlock,
registers::Register,
scope::{AssignedReg, FunctionContext, Scope, StackSlot},
},
model::{CompilerError, TypeId},
};
pub struct Variable {
pub name: String,
pub uuid: Uuid,
/// the type of the variable.
r#type: TypeId,
/// size taken up in bytes.
/// if size > 4, value must be stored on the stack.
pub size: usize,
pub stack_slot: Option<StackSlot>,
pub register: Option<AssignedReg>,
}
impl Variable {
pub fn new_uninit(name: String, r#type: TypeId) -> Self {
Self {
name,
uuid: Uuid::new_v4(),
size: r#type.size(),
r#type,
stack_slot: None,
register: None,
}
}
pub fn new(
name: String,
r#type: TypeId,
scope: &'_ mut Scope,
) -> Result<Self, CompilerError> {
let mut var = Self::new_uninit(name, r#type);
var.alloc_default(scope);
Ok(var)
}
fn alloc_default(&mut self, scope: &'_ mut Scope) {
if self.size > 4 {
self.alloc_stack(scope).unwrap();
} else {
self.alloc_register(scope).unwrap();
}
}
fn alloc_register(
&mut self,
scope: &'_ mut Scope,
) -> Result<Register, CompilerError> {
if self.size > 4 {
return Err(CompilerError::Generic(format!(
"Type {} cannot be allocated a register as it has a size of {} bytes",
self.r#type, self.size
)));
}
todo!("integrate with register alloc logic")
// self.allocated_register = Some(...)
}
fn alloc_stack(&mut self, scope: &'_ mut Scope) -> Result<usize, CompilerError> {
todo!("integrate with stack alloc logic")
// self.bpr_offset = Some(...)
}
pub fn load(&mut self, scope: &'_ mut Scope) -> Result<Register, CompilerError> {
todo!("load var from stack to reg (if possible)")
}
pub fn drop(&mut self, scope: &'_ mut Scope) -> Result<(), CompilerError> {
Ok(())
}
pub fn spill(&mut self, scope: &'_ mut Scope) -> Result<(), CompilerError> {
todo!()
}
}
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -13,7 +13,7 @@ pub fn generate_ast(input: &str) -> Result<Program, CompilerError> {
let lexer = lexer::Lexer::new(&input); let lexer = lexer::Lexer::new(&input);
let tokens = lexer.collect::<Vec<_>>(); let tokens = lexer.collect::<Vec<_>>();
// println!("{tokens:?}"); println!("{tokens:#?}");
log(&format!("Parsing {} Tokens...", tokens.len())); log(&format!("Parsing {} Tokens...", tokens.len()));
+451 -85
View File
@@ -1,7 +1,8 @@
use super::lexer::Token; use super::lexer::Token;
use crate::model::{ use crate::model::{
BinaryOperator, Block, Call, CompilerError, ConstExpr, Declaration, Dependency, AssignmentOperator, BinaryOperator, Block, Call, CompilerError, ConstExpr,
Expression, Program, Statement, TypeId, UnaryOperator, Variable, Declaration, Dependency, Expression, Number, Program, Statement, TypeId,
UnaryOperator, Variable,
}; };
use crate::{expect_tt, expect_value}; use crate::{expect_tt, expect_value};
use std::ops::{ControlFlow, FromResidual, Try}; use std::ops::{ControlFlow, FromResidual, Try};
@@ -38,6 +39,10 @@ impl Parser {
return self.parse_func(); return self.parse_func();
} }
if expect_tt!(self.peek_next()?, Struct).accepted() {
return self.parse_struct();
}
if expect_tt!(self.peek_next()?, Include).accepted() { if expect_tt!(self.peek_next()?, Include).accepted() {
// expect include keyword // expect include keyword
let _ = self.next(); let _ = self.next();
@@ -78,7 +83,8 @@ impl Parser {
let value = self.next()?; let value = self.next()?;
let init = match value { let init = match value {
Token::String(x) => Some(ConstExpr::String(x)), Token::String(x) => Some(ConstExpr::String(x)),
Token::Integer(x) => Some(ConstExpr::Number(x as i32)), Token::SignedInt(x, _) => Some(ConstExpr::Number(x)),
Token::UnsignedInt(x, _) => Some(ConstExpr::Number(x as i32)),
_ => { _ => {
return ParseResult::Reject(CompilerError::UnexpectedToken( return ParseResult::Reject(CompilerError::UnexpectedToken(
value.tt().to_string(), value.tt().to_string(),
@@ -98,6 +104,28 @@ impl Parser {
ParseResult::Reject(CompilerError::UnexpectedEndOfInput) ParseResult::Reject(CompilerError::UnexpectedEndOfInput)
} }
fn parse_struct(&mut self) -> ParseResult<Declaration, CompilerError> {
let _ = expect_tt!(self.next()?, Struct)?;
let name = expect_value!(self.next()?, Identifier)?;
let _ = expect_tt!(self.next()?, LeftBrace)?;
let mut fields = Vec::new();
while expect_tt!(self.peek_next()?, Identifier).accepted() {
let arg = self.parse_var_decl()?;
fields.push(arg);
if expect_tt!(self.peek_next()?, Comma).accepted() {
self.next()?;
} else {
break;
}
}
let _ = expect_tt!(self.next()?, RightBrace)?;
ParseResult::Accept(Declaration::Struct { name, fields })
}
fn parse_func(&mut self) -> ParseResult<Declaration, CompilerError> { fn parse_func(&mut self) -> ParseResult<Declaration, CompilerError> {
// expect function keyword // expect function keyword
let _ = expect_tt!(self.next()?, Fn); let _ = expect_tt!(self.next()?, Fn);
@@ -317,61 +345,194 @@ impl Parser {
}); });
} }
// handle assignment without "let" // handle an in-place function call
let name = expect_value!(self.peek_next()?, Identifier); if let ParseResult::Accept(name) = expect_value!(self.peek_next()?, Identifier)
if name.accepted() { && let ParseResult::Accept(operator) = expect_tt!(
let varname = name?; self.peek(1)?,
if expect_tt!(self.peek(1)?, LeftParen).accepted() { Assign,
let expr = self.parse_expression()?; // a function call expr PlusEqual,
let _ = expect_tt!(self.next()?, Semicolon)?; MinusEqual,
return ParseResult::Accept(Statement::Expression { expr }); StarEqual,
} SlashEqual,
PercentEqual,
AndEqual,
OrEqual,
XorEqual,
ShlEqual,
ShrEqual
)
{
// consume name token
self.next()?;
// pattern match to find operator
let operator = match operator {
Token::Assign => AssignmentOperator::Assign,
Token::PlusEqual => AssignmentOperator::AddAssign,
Token::MinusEqual => AssignmentOperator::SubAssign,
Token::StarEqual => AssignmentOperator::MulAssign,
Token::SlashEqual => AssignmentOperator::DivAssign,
Token::PercentEqual => AssignmentOperator::ModAssign,
Token::AndEqual => AssignmentOperator::AndAssign,
Token::OrEqual => AssignmentOperator::OrAssign,
Token::XorEqual => AssignmentOperator::XorAssign,
Token::ShlEqual => AssignmentOperator::LeftShiftAssign,
Token::ShrEqual => AssignmentOperator::RightShiftAssign,
_ => {
return ParseResult::Reject(CompilerError::UnexpectedToken(
self.peek_next()?.tt().to_string(),
));
}
};
// consume operator token
self.next()?; self.next()?;
let _ = expect_tt!(self.next()?, Assign)?;
let value = self.parse_expression()?; let value = self.parse_expression()?;
let _ = expect_tt!(self.next()?, Semicolon); let _ = expect_tt!(self.next()?, Semicolon);
return ParseResult::Accept(Statement::Assign { return ParseResult::Accept(Statement::Assign {
varname: varname.name, varname: name.name,
operator,
value, value,
}); });
} }
ParseResult::Reject(CompilerError::UnexpectedToken( // parse an expression and a semicolon
self.peek_next()?.tt().to_string(), let expr = self.parse_expression()?;
)) let _ = expect_tt!(self.next()?, Semicolon)?;
ParseResult::Accept(Statement::Expression { expr })
} }
fn parse_expression(&mut self) -> ParseResult<Expression, CompilerError> { fn parse_expression(&mut self) -> ParseResult<Expression, CompilerError> {
self.parse_comparison() self.parse_logical_or()
}
fn parse_logical_or(&mut self) -> ParseResult<Expression, CompilerError> {
let left = self.parse_logical_and()?;
let op = match self.peek_next()? {
Token::LogicalOr => BinaryOperator::LogicalOr,
_ => return ParseResult::Accept(left),
};
self.next()?;
ParseResult::Accept(Expression::Binary {
op,
left: Box::new(left),
right: Box::new(self.parse_logical_or()?),
type_id: Some(TypeId::U32),
})
}
fn parse_logical_and(&mut self) -> ParseResult<Expression, CompilerError> {
let left = self.parse_bitwise_or()?;
let op = match self.peek_next()? {
Token::LogicalAnd => BinaryOperator::LogicalAnd,
_ => return ParseResult::Accept(left),
};
self.next()?;
ParseResult::Accept(Expression::Binary {
op,
left: Box::new(left),
right: Box::new(self.parse_logical_and()?),
type_id: Some(TypeId::U32),
})
}
fn parse_bitwise_or(&mut self) -> ParseResult<Expression, CompilerError> {
let left = self.parse_bitwise_xor()?;
let op = match self.peek_next()? {
Token::Pipe => BinaryOperator::BitwiseOr,
_ => return ParseResult::Accept(left),
};
self.next()?;
ParseResult::Accept(Expression::Binary {
op,
left: Box::new(left),
right: Box::new(self.parse_bitwise_or()?),
type_id: Some(TypeId::U32),
})
}
fn parse_bitwise_xor(&mut self) -> ParseResult<Expression, CompilerError> {
let left = self.parse_bitwise_and()?;
let op = match self.peek_next()? {
Token::Caret => BinaryOperator::BitwiseXor,
_ => return ParseResult::Accept(left),
};
self.next()?;
ParseResult::Accept(Expression::Binary {
op,
left: Box::new(left),
right: Box::new(self.parse_bitwise_xor()?),
type_id: Some(TypeId::U32),
})
}
fn parse_bitwise_and(&mut self) -> ParseResult<Expression, CompilerError> {
let left = self.parse_comparison()?;
let op = match self.peek_next()? {
Token::Ampersand => BinaryOperator::BitwiseAnd,
_ => return ParseResult::Accept(left),
};
self.next()?;
ParseResult::Accept(Expression::Binary {
op,
left: Box::new(left),
right: Box::new(self.parse_bitwise_and()?),
type_id: Some(TypeId::U32),
})
} }
fn parse_comparison(&mut self) -> ParseResult<Expression, CompilerError> { fn parse_comparison(&mut self) -> ParseResult<Expression, CompilerError> {
let mut expr = self.parse_additive()?; let left = self.parse_shift()?;
while let Some(op) = match self.peek_next()? { let op = match self.peek_next()? {
Token::EqualEqual => Some(BinaryOperator::Eq), Token::EqualEqual => BinaryOperator::Equal,
Token::BangEqual => Some(BinaryOperator::Ne), Token::BangEqual => BinaryOperator::NotEqual,
Token::Less => Some(BinaryOperator::Lt), Token::Less => BinaryOperator::LessThan,
Token::Greater => Some(BinaryOperator::Gt), Token::Greater => BinaryOperator::GreaterThan,
Token::LessEqual => Some(BinaryOperator::Le), Token::LessEqual => BinaryOperator::LessOrEqual,
Token::GreaterEqual => Some(BinaryOperator::Ge), Token::GreaterEqual => BinaryOperator::GreaterOrEqual,
_ => None, _ => return ParseResult::Accept(left),
} { };
self.next()?;
let right = Box::new(self.parse_additive()?);
expr = Expression::Binary {
op,
left: Box::new(expr),
right,
type_id: Some(TypeId::Bool),
};
}
ParseResult::Accept(expr) self.next()?;
ParseResult::Accept(Expression::Binary {
op,
left: Box::new(left),
right: Box::new(self.parse_comparison()?),
type_id: Some(TypeId::Bool),
})
}
fn parse_shift(&mut self) -> ParseResult<Expression, CompilerError> {
let left = self.parse_additive()?;
let op = match self.peek_next()? {
Token::LeftShift => BinaryOperator::LeftShift,
Token::RightShift => BinaryOperator::RightShift,
_ => return ParseResult::Accept(left),
};
self.next()?;
ParseResult::Accept(Expression::Binary {
op,
left: Box::new(left),
right: Box::new(self.parse_shift()?),
type_id: Some(TypeId::U32),
})
} }
fn parse_additive(&mut self) -> ParseResult<Expression, CompilerError> { fn parse_additive(&mut self) -> ParseResult<Expression, CompilerError> {
@@ -412,11 +573,27 @@ impl Parser {
fn parse_unary(&mut self) -> ParseResult<Expression, CompilerError> { fn parse_unary(&mut self) -> ParseResult<Expression, CompilerError> {
let op = match self.peek_next()? { let op = match self.peek_next()? {
// prefix inc/dec
Token::PlusPlus => UnaryOperator::Increment,
Token::MinusMinus => UnaryOperator::Decrement,
// arithmetic
Token::Plus => UnaryOperator::Plus, Token::Plus => UnaryOperator::Plus,
Token::Minus => UnaryOperator::Minus, Token::Minus => UnaryOperator::Minus,
// pointer
Token::Star => UnaryOperator::Dereference, Token::Star => UnaryOperator::Dereference,
Token::Amphersand => UnaryOperator::Reference, Token::Ampersand => UnaryOperator::AddressOf,
_ => return ParseResult::Accept(self.parse_primary()?),
// boolean
Token::Bang => UnaryOperator::LogicalNot,
Token::Tilde => UnaryOperator::BitwiseNot,
Token::SizeOf => UnaryOperator::SizeOf,
_ => {
let expr = self.parse_primary()?;
return self.parse_postfix(expr);
}
}; };
self.next()?; self.next()?;
@@ -428,52 +605,174 @@ impl Parser {
}) })
} }
fn parse_primary(&mut self) -> ParseResult<Expression, CompilerError> { fn parse_postfix(
match self.peek_next()? { &mut self,
Token::Integer(value) => { mut expr: Expression,
self.next()?; ) -> ParseResult<Expression, CompilerError> {
ParseResult::Accept(Expression::Number { loop {
value: value as isize, match self.peek_next()? {
type_id: None, // Type cast: expr as Type
}) Token::As => {
} self.next()?; // consume 'as'
Token::String(value) => { let target_type = self.parse_type()?;
self.next()?; expr = Expression::TypeCast {
ParseResult::Accept(Expression::StringLiteral(value)) expr: Box::new(expr),
} target_type,
Token::Identifier(_) => { type_id: None,
let name = expect_value!(self.next()?, Identifier)?; };
}
if matches!(self.peek_next()?, Token::LeftParen) { // Postfix increment/decrement
// Function call Token::PlusPlus => {
self.next()?; self.next()?;
expr = Expression::UnaryPostfix {
op: UnaryOperator::Increment,
operand: Box::new(expr),
type_id: None,
};
}
Token::MinusMinus => {
self.next()?;
expr = Expression::UnaryPostfix {
op: UnaryOperator::Decrement,
operand: Box::new(expr),
type_id: None,
};
}
// Array indexing: expr[index]
Token::LeftBracket => {
self.next()?; // consume '['
let index = Box::new(self.parse_expression()?);
let _ = expect_tt!(self.next()?, RightBracket)?;
expr = Expression::IndexAccess {
expr: Box::new(expr),
index,
type_id: None,
};
}
// Function call: expr(args...)
Token::LeftParen => {
self.next()?; // consume '('
let mut args = Vec::new(); let mut args = Vec::new();
if !matches!(self.peek_next()?, Token::RightParen) { if !matches!(self.peek_next()?, Token::RightParen) {
args.push(self.parse_expression()?); loop {
while matches!(self.peek_next()?, Token::Comma) {
self.next()?;
args.push(self.parse_expression()?); args.push(self.parse_expression()?);
if !matches!(self.peek_next()?, Token::Comma) {
break;
}
self.next()?; // consume comma
} }
} }
let _ = expect_tt!(self.next()?, RightParen)?; let _ = expect_tt!(self.next()?, RightParen)?;
ParseResult::Accept(Expression::Call { if let Expression::Variable { name, .. } = expr {
func: Call { expr = Expression::Call {
name: name.clone(), func: Call { name, args },
args, type_id: None,
}, };
}
}
// Member access: expr.member (if you support structs)
Token::Dot => {
self.next()?;
let field_name = expect_value!(self.next()?, Identifier)?;
expr = Expression::MemberAccess {
expr: Box::new(expr),
field_name,
type_id: None, type_id: None,
}) };
} else { }
ParseResult::Accept(Expression::Variable {
// No more postfix operations
_ => break,
}
}
ParseResult::Accept(expr)
}
fn parse_primary(&mut self) -> ParseResult<Expression, CompilerError> {
match self.peek_next()? {
Token::UnsignedInt(value, type_id) => {
self.next()?;
ParseResult::Accept(Expression::Number(Number::Unsigned(value, type_id)))
}
Token::SignedInt(value, type_id) => {
self.next()?;
ParseResult::Accept(Expression::Number(Number::Signed(value, type_id)))
}
Token::String(value) => {
self.next()?;
ParseResult::Accept(Expression::StringLiteral(value))
}
Token::Char(value) => {
self.next()?;
ParseResult::Accept(Expression::CharLiteral(value))
}
Token::Identifier(name) => {
self.next()?;
// if the next token isn't the beginning of a struct literal this is just
// an identifier.
if !expect_tt!(self.peek_next()?, LeftBrace).accepted() {
return ParseResult::Accept(Expression::Variable {
name, name,
expr_type: None, expr_type: None,
}) });
} }
let _ = self.next()?;
let mut fields = Vec::new();
while !expect_tt!(self.peek_next()?, RightBrace).accepted() {
let name = expect_value!(self.next()?, Identifier)?;
let _ = expect_tt!(self.next()?, Colon)?;
let expr = self.parse_expression()?;
fields.push((name, expr));
if expect_tt!(self.peek_next()?, Comma).accepted() {
self.next()?;
} else {
break;
}
}
let _ = expect_tt!(self.next()?, RightBrace)?;
ParseResult::Accept(Expression::StructLiteral {
name,
fields,
type_id: None,
})
}
Token::LeftBracket => {
self.next()?; // consume '['
let mut elements = Vec::new();
if !matches!(self.peek_next()?, Token::RightBracket) {
loop {
elements.push(self.parse_expression()?);
if !matches!(self.peek_next()?, Token::Comma) {
break;
}
self.next()?; // consume comma
}
}
expect_tt!(self.next()?, RightBracket)?;
ParseResult::Accept(Expression::ArrayLiteral {
elements,
type_id: None,
})
} }
Token::LeftParen => { Token::LeftParen => {
self.next()?; self.next()?;
@@ -501,21 +800,88 @@ impl Parser {
} }
fn parse_type(&mut self) -> ParseResult<TypeId, CompilerError> { fn parse_type(&mut self) -> ParseResult<TypeId, CompilerError> {
// get the type name incl namespace println!("yes {:?}", self.peek_next()?);
let typename = expect_value!(self.next()?, Identifier)?;
match typename.name.as_str() { // parse primitive or named type
"u32" => ParseResult::Accept(TypeId::U32), if expect_tt!(self.peek_next()?, Identifier).accepted() {
"u16" => ParseResult::Accept(TypeId::U16), return self.parse_type_identifier();
"u8" => ParseResult::Accept(TypeId::U8),
"i32" => ParseResult::Accept(TypeId::I32),
"i16" => ParseResult::Accept(TypeId::I16),
"i8" => ParseResult::Accept(TypeId::I8),
"void" => ParseResult::Accept(TypeId::Void),
"char" => ParseResult::Accept(TypeId::Char),
"str" => ParseResult::Accept(TypeId::Ptr(Box::new(TypeId::Char))),
_ => todo!("Implement parsing for other types!!"),
} }
// parse array type
if expect_tt!(self.peek_next()?, LeftBracket).accepted() {
let _ = self.next()?;
let internal_type = self.parse_type()?;
let _ = expect_tt!(self.next()?, Semicolon)?;
let size = expect_value!(self.next()?, UnsignedInt)?;
let _ = expect_tt!(self.next()?, RightBracket)?;
return ParseResult::Accept(TypeId::Array {
r#type: Box::new(internal_type),
size: size as usize,
});
}
// parse tuple type
if expect_tt!(self.peek_next()?, LeftParen).accepted() {
let _ = self.next()?;
let mut types = Vec::new();
while !expect_tt!(self.peek_next()?, RightParen).accepted() {
types.push(self.parse_type()?);
if !expect_tt!(self.peek_next()?, Comma).accepted() {
break;
}
let _ = self.next()?;
}
let _ = expect_tt!(self.next()?, RightParen)?;
return ParseResult::Accept(TypeId::Tuple(types));
}
ParseResult::Reject(CompilerError::Generic(format!(
"Parsing type but no valid type was detected: {:?}",
self.peek_next()?
)))
}
fn parse_type_identifier(&mut self) -> ParseResult<TypeId, CompilerError> {
// get the type name incl namespace
let name = expect_value!(self.next()?, Identifier)?;
let type_id = match name.name.as_str() {
"u32" => TypeId::U32,
"u16" => TypeId::U16,
"u8" => TypeId::U8,
"i32" => TypeId::I32,
"i16" => TypeId::I16,
"i8" => TypeId::I8,
"void" => TypeId::Void,
"char" => TypeId::Char,
"str" => TypeId::Ptr(Box::new(TypeId::Char)),
_ => {
let mut generics = Vec::new();
if expect_tt!(self.peek_next()?, Less).accepted() {
let _ = self.next()?;
// loop until we find the closing '>'
while !expect_tt!(self.peek_next()?, Greater).accepted() {
generics.push(self.parse_type()?);
if !expect_tt!(self.peek_next()?, Comma).accepted() {
break;
}
let _ = self.next()?;
}
let _ = expect_tt!(self.next()?, Greater)?;
}
TypeId::UnknownCustom { name, generics }
}
};
ParseResult::Accept(type_id)
} }
fn next(&mut self) -> ParseResult<Token, CompilerError> { fn next(&mut self) -> ParseResult<Token, CompilerError> {
@@ -612,7 +978,7 @@ macro_rules! expect_value {
($expr:expr, $variant:ident) => {{ ($expr:expr, $variant:ident) => {{
let tok = $expr; let tok = $expr;
match tok.clone() { match tok.clone() {
Token::$variant(value) => ParseResult::Accept(value), Token::$variant(first, ..) => ParseResult::Accept(first),
_ => { _ => {
ParseResult::Reject(CompilerError::UnexpectedToken(tok.tt().to_string())) ParseResult::Reject(CompilerError::UnexpectedToken(tok.tt().to_string()))
} }
-2
View File
@@ -1,7 +1,5 @@
use std::path::Path; use std::path::Path;
use compiler;
fn main() { fn main() {
// read from input file: syntax "c_compiler <src.c> [output.dsa]" // read from input file: syntax "c_compiler <src.c> [output.dsa]"
let args: Vec<String> = std::env::args().collect(); let args: Vec<String> = std::env::args().collect();
+249 -39
View File
@@ -11,6 +11,7 @@ pub enum CompilerError {
Generic(String), Generic(String),
UnknownType, UnknownType,
TypeMismatch(TypeId, TypeId), TypeMismatch(TypeId, TypeId),
Unimplemented(String),
} }
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
@@ -18,6 +19,14 @@ pub struct Name {
pub name: String, pub name: String,
pub namespace: Option<String>, pub namespace: Option<String>,
} }
impl Name {
pub fn new(name: impl Into<String>, namespace: Option<String>) -> Self {
Self {
name: name.into(),
namespace,
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Program { pub struct Program {
@@ -39,6 +48,10 @@ pub enum Declaration {
is_const: bool, is_const: bool,
}, },
Dependency(Dependency), Dependency(Dependency),
Struct {
name: Name,
fields: Vec<Variable>,
},
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -61,8 +74,43 @@ pub enum TypeId {
Void, Void,
Ptr(Box<TypeId>), Ptr(Box<TypeId>),
Ref(Box<TypeId>), Ref(Box<TypeId>),
Array(Box<TypeId>, usize), Tuple(Vec<TypeId>),
Struct { name: Name, fields: Vec<TypeId> }, Array {
r#type: Box<TypeId>,
size: usize,
},
UnknownCustom {
name: Name,
generics: Vec<TypeId>,
},
Struct {
name: Name,
fields: Vec<TypeId>,
generics: Vec<TypeId>,
},
}
impl TypeId {
pub fn size(&self) -> usize {
match self {
Self::U8 => 1,
Self::U16 => 2,
Self::U32 => 4,
Self::I8 => 1,
Self::I16 => 2,
Self::I32 => 4,
Self::Bool => 1,
Self::Char => 1,
Self::Void => 0,
Self::Ptr(t) => t.size(),
Self::Ref(t) => t.size(),
Self::Tuple(types) => types.iter().map(|t| t.size()).sum(),
Self::Array { r#type, size } => r#type.size() * size,
Self::UnknownCustom { .. } => 1, /* TODO: calculate type size during */
// semantic analysis
Self::Struct { fields, .. } => fields.iter().map(|t| t.size()).sum(),
}
}
} }
impl fmt::Display for TypeId { impl fmt::Display for TypeId {
@@ -79,14 +127,47 @@ impl fmt::Display for TypeId {
Self::Void => write!(f, "void"), Self::Void => write!(f, "void"),
Self::Ptr(t) => write!(f, "*{}", t), Self::Ptr(t) => write!(f, "*{}", t),
Self::Ref(t) => write!(f, "&{}", t), Self::Ref(t) => write!(f, "&{}", t),
Self::Array(t, len) => write!(f, "[{}; {}]", t, len), Self::Tuple(elems) => write!(
Self::Struct { name, fields } => { f,
write!(f, "struct {} {{", name)?; "({})",
for (i, field) in fields.iter().enumerate() { elems
write!(f, "{}: {}", i, field)?; .iter()
} .map(|t| t.to_string())
write!(f, "}}") .collect::<Vec<String>>()
.join(", ")
),
Self::Array { r#type, size } => write!(f, "[{}; {}]", r#type, size),
Self::UnknownCustom { name, generics } => {
write!(
f,
"{}<{}>",
name,
generics
.iter()
.map(|t| t.to_string())
.collect::<Vec<String>>()
.join(", ")
)
} }
Self::Struct {
name,
fields,
generics,
} => write!(
f,
"struct<{}> {} {{{}}}",
generics
.iter()
.map(|t| t.to_string())
.collect::<Vec<String>>()
.join(", "),
name,
fields
.iter()
.map(|t| t.to_string())
.collect::<Vec<String>>()
.join(", ")
),
} }
} }
} }
@@ -110,6 +191,7 @@ pub enum Statement {
}, },
Assign { Assign {
varname: String, varname: String,
operator: AssignmentOperator,
value: Expression, value: Expression,
}, },
PtrWrite { PtrWrite {
@@ -169,24 +251,62 @@ pub enum Expression {
// Post-Semantic Analysis // Post-Semantic Analysis
type_id: Option<TypeId>, type_id: Option<TypeId>,
}, },
UnaryPostfix {
op: UnaryOperator,
operand: Box<Expression>,
// Post-Semantic Analysis
type_id: Option<TypeId>,
},
Variable { Variable {
name: Name, name: Name,
expr_type: Option<TypeId>, expr_type: Option<TypeId>,
}, },
TypeCast {
expr: Box<Expression>,
target_type: TypeId,
// Post-Semantic Analysis
type_id: Option<TypeId>,
},
IndexAccess {
expr: Box<Expression>,
index: Box<Expression>,
// Post-Semantic Analysis
type_id: Option<TypeId>,
},
MemberAccess {
expr: Box<Expression>,
field_name: Name,
// Post-Semantic Analysis
type_id: Option<TypeId>,
},
Call { Call {
func: Call, func: Call,
// Post-Semantic Analysis // Post-Semantic Analysis
type_id: Option<TypeId>, type_id: Option<TypeId>,
}, },
Number { Number(Number),
value: isize,
// Post-Semantic Analysis
type_id: Option<TypeId>,
},
StringLiteral(String), StringLiteral(String),
CharLiteral(char), CharLiteral(char),
ArrayLiteral {
elements: Vec<Expression>,
type_id: Option<TypeId>,
},
StructLiteral {
name: Name,
fields: Vec<(Name, Expression)>,
type_id: Option<TypeId>,
},
}
#[derive(Debug, Clone)]
pub enum Number {
Signed(i32, Option<TypeId>),
Unsigned(u32, Option<TypeId>),
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -204,16 +324,28 @@ impl Expression {
Expression::Call { .. } => false, Expression::Call { .. } => false,
Expression::Binary { left, right, .. } => left.is_pure() && right.is_pure(), Expression::Binary { left, right, .. } => left.is_pure() && right.is_pure(),
Expression::Unary { operand, .. } => operand.is_pure(), Expression::Unary { operand, .. } => operand.is_pure(),
Expression::UnaryPostfix { operand, .. } => operand.is_pure(),
Expression::Empty => true, Expression::Empty => true,
Expression::Variable { .. } => true, Expression::Variable { .. } => true,
Expression::TypeCast { expr, .. } => expr.is_pure(),
Expression::IndexAccess { expr, index, .. } => {
expr.is_pure() && index.is_pure()
}
Expression::MemberAccess { expr, .. } => expr.is_pure(),
Expression::ArrayLiteral { elements, .. } => {
elements.iter().all(|element| element.is_pure())
}
Expression::StructLiteral { fields, .. } => {
fields.iter().all(|(_, expr)| expr.is_pure())
}
} }
} }
pub fn type_id(&self) -> Result<TypeId, CompilerError> { pub fn type_id(&self) -> Result<TypeId, CompilerError> {
match self { match self {
Expression::Number { type_id, .. } => { Expression::Number(
type_id.clone().ok_or(CompilerError::UnknownType) Number::Signed(_, type_id) | Number::Unsigned(_, type_id),
} ) => type_id.clone().ok_or(CompilerError::UnknownType),
Expression::StringLiteral(_) => Ok(TypeId::Ptr(Box::new(TypeId::Char))), Expression::StringLiteral(_) => Ok(TypeId::Ptr(Box::new(TypeId::Char))),
Expression::CharLiteral(_) => Ok(TypeId::Char), Expression::CharLiteral(_) => Ok(TypeId::Char),
Expression::Call { type_id, .. } => { Expression::Call { type_id, .. } => {
@@ -225,42 +357,110 @@ impl Expression {
Expression::Unary { type_id, .. } => { Expression::Unary { type_id, .. } => {
type_id.clone().ok_or(CompilerError::UnknownType) type_id.clone().ok_or(CompilerError::UnknownType)
} }
Expression::UnaryPostfix { type_id, .. } => {
type_id.clone().ok_or(CompilerError::UnknownType)
}
Expression::Empty => Ok(TypeId::Void), Expression::Empty => Ok(TypeId::Void),
Expression::Variable { expr_type, .. } => { Expression::Variable { expr_type, .. } => {
expr_type.clone().ok_or(CompilerError::UnknownType) expr_type.clone().ok_or(CompilerError::UnknownType)
} }
Expression::TypeCast { type_id, .. } => {
type_id.clone().ok_or(CompilerError::UnknownType)
}
Expression::IndexAccess { expr, .. } => expr.type_id(),
Expression::MemberAccess { expr, .. } => expr.type_id(),
Expression::ArrayLiteral { elements, .. } => {
let element_type = elements
.first()
.map_or(TypeId::Void, |e| e.type_id().unwrap_or(TypeId::Void));
Ok(TypeId::Array {
r#type: Box::new(element_type),
size: elements.len(),
})
}
Expression::StructLiteral { name, fields, .. } => {
let fields = fields
.iter()
.map(|(_, expr)| expr.type_id())
.collect::<Result<Vec<_>, _>>()?;
Ok(TypeId::Struct {
name: name.clone(),
fields,
generics: Vec::new(),
})
}
} }
} }
} }
#[derive(Debug, Clone, PartialEq)]
pub enum AssignmentOperator {
Assign,
AddAssign,
SubAssign,
MulAssign,
DivAssign,
ModAssign,
AndAssign,
OrAssign,
XorAssign,
LeftShiftAssign,
RightShiftAssign,
}
#[allow(unused)] #[allow(unused)]
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum BinaryOperator { pub enum BinaryOperator {
// arithmetic
Add, Add,
Sub, Sub,
Mul, Mul,
Div, Div,
Eq, Mod,
Ne,
Lt, // comparison
Gt, Equal,
Le, NotEqual,
Ge, LessThan,
GreaterThan,
LessOrEqual,
GreaterOrEqual,
// bitwise
BitwiseAnd,
BitwiseOr,
BitwiseXor,
// logical
LogicalAnd,
LogicalOr,
// shift
LeftShift,
RightShift,
} }
impl fmt::Display for BinaryOperator { impl fmt::Display for BinaryOperator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
BinaryOperator::Add => write!(f, "+"), Self::Add => write!(f, "+"),
BinaryOperator::Sub => write!(f, "-"), Self::Sub => write!(f, "-"),
BinaryOperator::Mul => write!(f, "*"), Self::Mul => write!(f, "*"),
BinaryOperator::Div => write!(f, "/"), Self::Div => write!(f, "/"),
BinaryOperator::Eq => write!(f, "=="), Self::Mod => write!(f, "%"),
BinaryOperator::Ne => write!(f, "!="), Self::Equal => write!(f, "=="),
BinaryOperator::Lt => write!(f, "<"), Self::NotEqual => write!(f, "!="),
BinaryOperator::Gt => write!(f, ">"), Self::LessThan => write!(f, "<"),
BinaryOperator::Le => write!(f, "<="), Self::GreaterThan => write!(f, ">"),
BinaryOperator::Ge => write!(f, ">="), Self::LessOrEqual => write!(f, "<="),
Self::GreaterOrEqual => write!(f, ">="),
Self::BitwiseAnd => write!(f, "&"),
Self::BitwiseOr => write!(f, "|"),
Self::BitwiseXor => write!(f, "^"),
Self::LogicalAnd => write!(f, "&&"),
Self::LogicalOr => write!(f, "||"),
Self::LeftShift => write!(f, "<<"),
Self::RightShift => write!(f, ">>"),
} }
} }
} }
@@ -269,17 +469,27 @@ impl fmt::Display for BinaryOperator {
pub enum UnaryOperator { pub enum UnaryOperator {
Plus, Plus,
Minus, Minus,
Reference, AddressOf,
Dereference, Dereference,
BitwiseNot,
LogicalNot,
Increment,
Decrement,
SizeOf,
} }
impl fmt::Display for UnaryOperator { impl fmt::Display for UnaryOperator {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
UnaryOperator::Plus => write!(f, "+"), Self::Increment => write!(f, "++"),
UnaryOperator::Minus => write!(f, "-"), Self::Decrement => write!(f, "--"),
UnaryOperator::Dereference => write!(f, "*"), Self::Plus => write!(f, "+"),
UnaryOperator::Reference => write!(f, "&"), Self::Minus => write!(f, "-"),
Self::Dereference => write!(f, "*"),
Self::AddressOf => write!(f, "&"),
Self::BitwiseNot => write!(f, "~"),
Self::LogicalNot => write!(f, "!"),
Self::SizeOf => write!(f, "sizeof"),
} }
} }
} }
+4
View File
@@ -0,0 +1,4 @@
- we definitely need to be able to use registers for shift operations.
- we need logical boolean operations in addition to the bitwise ones.
- better conditionals.
File diff suppressed because it is too large Load Diff
+26
View File
@@ -0,0 +1,26 @@
# General TODO's
# Bugfixes
- [x] [EASY] Investigate logical and operator not compiling - either a lexer or parser issue.
- **note**: this was a parser issue.
# Missing features
- [x] [MEDIUM] Get shift operations working correctly.
- [ ] [MEDIUM] proper prefix/postfix inc/dec implementation. slightly more complex as we need to check for a variable and modify it in place
- [ ] [EASY] Add multiply and divide operations to code generation
- **note**: very easy to do but our division algorithm is hopelessly slow so not worth doing for now.
# Performance Improvements
- [ ] [MEDIUM] implement a proper div/mod library that's not slow af.
- [ ] [HARD] Immediate operations for values that support it (up to +/- u16::max for addi and subi respectively)
- this requires significant complexity in code generation as we need to traverse down the tree when we come across these operations to prevent additional register allocations.
# Compiler optimisations
# Codegen improvements
- [ ] [MEDIUM / time consuming] Add scoping to code generation
- [ ] [MEDIUM / time consuming] Rewrite entire codegen to imrpove code quality and make the code more readable.
- [ ] type-safe instruction builder
- [ ] Instruction & Register enums
- [ ] Instruction builder helper fns eg `fn add(left: &Register, right: &Register, dest: &Register) -> Instruction`
- [ ] Instruction Block types.
+2 -1
View File
@@ -20,10 +20,11 @@ compiler = { path = "../compiler" }
dsa_editor = { path = "../dsa_editor" } dsa_editor = { path = "../dsa_editor" }
egui = "0.31.1" egui = "0.31.1"
dirs = "6.0.0" dirs = "6.0.0"
discord-presence = { version = "1.6.0", optional = true } discord-presence = { version = "2.0.0", optional = true }
toml = { version = "0.8.23", optional = true } toml = { version = "0.8.23", optional = true }
serde = { version = "1.0.219", features = ["derive"], optional = true } serde = { version = "1.0.219", features = ["derive"], optional = true }
egui_file = "0.22.1" egui_file = "0.22.1"
rustc-hash = "2.1.1"
[features] [features]
default = ["config"] default = ["config"]
+53
View File
@@ -0,0 +1,53 @@
use common::prelude::Instruction;
use rustc_hash::FxHashMap;
#[derive(Debug)]
pub struct Cache {
addr: u32,
instruction_block: Option<[u8; 256]>,
instruction_lookup: FxHashMap<u32, Instruction>,
}
impl Cache {
#[must_use]
pub fn new() -> Self {
Self {
addr: 0,
instruction_block: None,
instruction_lookup: FxHashMap::default(),
}
}
pub fn lookup_value(&mut self, addr: u32) -> Option<u32> {
if addr < self.addr || addr >= self.addr + 256 || self.instruction_block.is_none()
{
return None;
}
Some(u32::from_be_bytes(
self.instruction_block.expect("this should not be none!")
[(addr - self.addr) as usize..(addr - self.addr + 4) as usize]
.try_into()
.expect("Failed to convert bytes to u32"),
))
}
pub const fn set(&mut self, addr: u32, block: &[u8; 256]) {
self.addr = addr - addr % 256;
self.instruction_block = Some(*block);
}
pub fn lookup_instruction(&mut self, instruction: u32) -> Option<Instruction> {
self.instruction_lookup.get(&instruction).copied()
}
pub fn insert(&mut self, value: u32, instruction: Instruction) {
self.instruction_lookup.insert(value, instruction);
}
}
impl Default for Cache {
fn default() -> Self {
Self::new()
}
}
+32 -58
View File
@@ -25,9 +25,11 @@ pub fn run_emulator(
let mut running = Running::Paused; let mut running = Running::Paused;
let mut step = 0; let mut step = 0;
let mut addr; let mut addr;
let mut history = Vec::<(u32, Instruction)>::new(); let mut history = Vec::<(u32, u32)>::with_capacity(32768);
let size = 256; let size = 256;
let record_history = true;
state_tx state_tx
.send(StateUpdate::Running(Running::Paused)) .send(StateUpdate::Running(Running::Paused))
.expect("Failed to send initial state!"); .expect("Failed to send initial state!");
@@ -36,7 +38,9 @@ pub fn run_emulator(
let mut update = false; let mut update = false;
loop { loop {
let cmd = if running == Running::Running || step > 0 { let cmd = if step > 0 {
None
} else if running == Running::Running && step == 0 {
match cmd_rx.try_recv() { match cmd_rx.try_recv() {
Ok(cmd) => Some(cmd), Ok(cmd) => Some(cmd),
Err(mpsc::TryRecvError::Empty) => { Err(mpsc::TryRecvError::Empty) => {
@@ -52,10 +56,15 @@ pub fn run_emulator(
} }
}; };
if running == Running::Running && step == 0 {
step = 32768;
}
if let Some(cmd) = cmd { if let Some(cmd) = cmd {
match cmd { match cmd {
Command::Start => { Command::Start => {
running = Running::Running; running = Running::Running;
step = 32768;
// Update RPC with current state. TODO: Make this only occur on state // Update RPC with current state. TODO: Make this only occur on state
// changes. // changes.
@@ -71,9 +80,11 @@ pub fn run_emulator(
} }
Command::Stop => { Command::Stop => {
running = Running::Paused; running = Running::Paused;
step = 0;
} }
Command::Reset(x) => { Command::Reset(x) => {
running = Running::Paused; running = Running::Paused;
step = 0;
match x { match x {
0 => { 0 => {
@@ -95,20 +106,12 @@ pub fn run_emulator(
} }
Command::Step(x) => { Command::Step(x) => {
step = x; step = x;
running = Running::Paused;
} }
Command::Write(offset, data) => { Command::Write(offset, data) => {
update = true; update = true;
processor processor.memory.write_range(offset, data);
.memory
.write_range(offset, data)
.unwrap_or_else(|_| {
report_err(
state_tx,
"Failed to write memory range!",
&mut processor,
);
});
} }
Command::Interrupt(_interrupt) => { Command::Interrupt(_interrupt) => {
update = true; update = true;
@@ -118,14 +121,7 @@ pub fn run_emulator(
Command::MemRequest(new, size) if update => { Command::MemRequest(new, size) if update => {
addr = new; addr = new;
let _ = state_tx.send(StateUpdate::MemoryView( let _ = state_tx.send(StateUpdate::MemoryView(
processor.memory.read_range(addr, size).unwrap_or_else(|_| { processor.memory.read_range(addr, size),
report_err(
state_tx,
"Failed to read memory range!",
&mut processor,
);
Vec::new()
}),
)); ));
} }
Command::DisplayRequest if update => { Command::DisplayRequest if update => {
@@ -163,50 +159,19 @@ pub fn run_emulator(
let _ = state_tx.send(StateUpdate::Instructions(instruction_count)); let _ = state_tx.send(StateUpdate::Instructions(instruction_count));
} }
Command::WriteBlock(addr, block) => { Command::WriteBlock(addr, block) => {
processor processor.memory.write_range(addr, block.to_vec());
.memory
.write_range(addr, block.to_vec())
.unwrap_or_else(|_| {
report_err(
state_tx,
"Failed to write memory block!",
&mut processor,
);
});
} }
_ => {} _ => {}
} }
} }
if step > 0 { if running == Running::Running {
step -= 1; step += 1;
update = true;
running = Running::Paused;
// Execute one cycle.
match processor.cycle() {
Ok((addr, instruction)) => {
history.push((addr, instruction));
}
Err(why) => {
let pcx = processor
.get(Register::Pcx)
.expect("SPR should never be invalid");
report_err(
state_tx,
&format!(
"Could not decode instruction at {pcx:x}. Reason: {why}"
),
&mut processor,
);
}
}
instruction_count += 1;
continue;
} }
if running == Running::Running { if step > 0 {
step -= 1;
update = true; update = true;
// Execute one cycle. // Execute one cycle.
@@ -227,9 +192,18 @@ pub fn run_emulator(
} }
}; };
history.push(instruction); if record_history {
if matches!(instruction.1, Instruction::Halt) { history.push((
instruction.0,
processor
.get(Register::Cir)
.expect("CIR should never be invalid"),
));
}
if matches!(instruction, (_, Instruction::Halt)) {
running = Running::Halted; running = Running::Halted;
step = 0;
} }
instruction_count += 1; instruction_count += 1;
+62 -75
View File
@@ -1,52 +1,42 @@
use std::collections::HashMap; use rustc_hash::FxHashMap;
use crate::emulator::system::model::ProcessorError; use crate::emulator::system::model::ProcessorError;
pub trait MemoryUnit: Send + Sync { pub trait MemoryUnit: Send + Sync {
fn reset(&mut self); fn reset(&mut self);
fn read_byte(&mut self, addr: u32) -> Result<u8, ProcessorError>; fn read_byte(&mut self, addr: u32) -> u8;
fn write_byte(&mut self, addr: u32, value: u8) -> Result<(), ProcessorError>; fn write_byte(&mut self, addr: u32, value: u8);
fn read_word(&mut self, addr: u32) -> Result<u32, ProcessorError>; fn read_word(&mut self, addr: u32) -> Result<u32, ProcessorError>;
fn write_word(&mut self, addr: u32, value: u32) -> Result<(), ProcessorError>; fn write_word(&mut self, addr: u32, value: u32) -> Result<(), ProcessorError>;
fn read_range(&mut self, addr: u32, size: u32) -> Result<Vec<u8>, ProcessorError> { fn read_range(&mut self, addr: u32, size: u32) -> Vec<u8> {
let mut data = Vec::with_capacity(size as usize); let mut data = Vec::with_capacity(size as usize);
for i in 0..size { for i in 0..size {
data.push(self.read_byte(addr + i)?); data.push(self.read_byte(addr + i));
} }
Ok(data) data
} }
fn write_range(&mut self, addr: u32, value: Vec<u8>) -> Result<(), ProcessorError> { fn write_range(&mut self, addr: u32, value: Vec<u8>) {
for (i, byte) in value.into_iter().enumerate() { for (i, byte) in value.into_iter().enumerate() {
self.write_byte(addr + i as u32, byte)?; self.write_byte(addr + i as u32, byte);
} }
Ok(())
} }
fn read_block(&mut self, addr: u32) -> Result<[u8; 256], ProcessorError> { fn read_block(&mut self, addr: u32) -> &[u8; 256];
let mut data = [0; 256];
for (i, byte) in data.iter_mut().enumerate() {
*byte = self.read_byte(addr + i as u32)?;
}
Ok(data)
}
fn write_block(&mut self, addr: u32, data: [u8; 256]) -> Result<(), ProcessorError> { fn write_block(&mut self, addr: u32, data: &[u8; 256]) {
for (i, byte) in data.iter().enumerate() { for (i, byte) in data.iter().enumerate() {
self.write_byte(addr + i as u32, *byte)?; self.write_byte(addr + i as u32, *byte);
} }
Ok(())
} }
} }
pub struct MainStore { pub struct MainStore {
pub data: HashMap<u32, Block>, pub data: FxHashMap<u32, Block>,
} }
pub struct Block { pub type Block = [u8; 256];
data: [u8; 256],
}
impl Default for MainStore { impl Default for MainStore {
fn default() -> Self { fn default() -> Self {
@@ -58,113 +48,110 @@ impl MainStore {
#[must_use] #[must_use]
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
data: HashMap::new(), data: FxHashMap::default(),
} }
} }
#[inline]
const fn segment_addr(addr: u32) -> (u32, u8) { const fn segment_addr(addr: u32) -> (u32, u8) {
(addr / 256, (addr % 256) as u8) (addr / 256, (addr % 256) as u8)
} }
#[inline]
fn mut_block(&mut self, addr: u32) -> &mut Block { fn mut_block(&mut self, addr: u32) -> &mut Block {
self.data self.data.entry(addr).or_insert([0; 256])
.entry(addr)
.or_insert_with(|| Block { data: [0; 256] });
self.data.get_mut(&addr).map_or_else(
|| panic!("Could not fetch block with address {addr:x?}"),
|block| block,
)
} }
#[inline]
fn block(&mut self, addr: u32) -> &Block { fn block(&mut self, addr: u32) -> &Block {
self.data self.data.entry(addr).or_insert([0; 256])
.entry(addr)
.or_insert_with(|| Block { data: [0; 256] });
self.data.get(&addr).map_or_else(
|| panic!("Could not fetch block with address {addr:x?}"),
|block| block,
)
} }
} }
impl MemoryUnit for MainStore { impl MemoryUnit for MainStore {
#[inline]
fn reset(&mut self) { fn reset(&mut self) {
self.data.clear(); self.data.clear();
} }
fn read_byte(&mut self, addr: u32) -> Result<u8, ProcessorError> { #[inline]
fn read_byte(&mut self, addr: u32) -> u8 {
let (block_addr, offset) = Self::segment_addr(addr); let (block_addr, offset) = Self::segment_addr(addr);
let block = self.block(block_addr); let block = self.block(block_addr);
Ok(block.data[offset as usize]) block[offset as usize]
} }
#[inline]
fn read_word(&mut self, addr: u32) -> Result<u32, ProcessorError> { fn read_word(&mut self, addr: u32) -> Result<u32, ProcessorError> {
if addr % 4 != 0 { if !addr.is_multiple_of(4) {
return Err(ProcessorError::BadMemoryAccess(addr)); return Err(ProcessorError::BadMemoryAccess(addr));
} }
let (block_addr, offset) = Self::segment_addr(addr); let (block_addr, offset) = Self::segment_addr(addr);
let block = self.mut_block(block_addr); let offset = offset as usize;
let mut bytes = [0; 4]; let block = self.block(block_addr);
bytes[0] = block.data[offset as usize]; Ok(u32::from_be_bytes(
bytes[1] = block.data[(offset + 1) as usize]; block[offset..=offset + 3]
bytes[2] = block.data[(offset + 2) as usize]; .try_into()
bytes[3] = block.data[(offset + 3) as usize]; .expect("Failed to read word!"),
Ok(u32::from_be_bytes(bytes)) ))
} }
fn read_range(&mut self, addr: u32, size: u32) -> Result<Vec<u8>, ProcessorError> { #[inline]
fn read_range(&mut self, addr: u32, size: u32) -> Vec<u8> {
let mut data = Vec::with_capacity(size as usize); let mut data = Vec::with_capacity(size as usize);
for i in 0..size { for i in 0..size {
data.push(self.read_byte(addr + i)?); data.push(self.read_byte(addr + i));
} }
Ok(data) data
} }
fn write_byte(&mut self, addr: u32, value: u8) -> Result<(), ProcessorError> { #[inline]
fn write_byte(&mut self, addr: u32, value: u8) {
let (block_addr, offset) = Self::segment_addr(addr); let (block_addr, offset) = Self::segment_addr(addr);
let block = self.mut_block(block_addr); let block = self.mut_block(block_addr);
block.data[offset as usize] = value; block[offset as usize] = value;
Ok(())
} }
#[inline]
fn write_word(&mut self, addr: u32, value: u32) -> Result<(), ProcessorError> { fn write_word(&mut self, addr: u32, value: u32) -> Result<(), ProcessorError> {
if addr % 4 != 0 { if !addr.is_multiple_of(4) {
return Err(ProcessorError::BadMemoryAccess(addr)); return Err(ProcessorError::BadMemoryAccess(addr));
} }
let (block_addr, offset) = Self::segment_addr(addr); let (block_addr, offset) = Self::segment_addr(addr);
let block = self.mut_block(block_addr); let block = self.mut_block(block_addr);
block.data[offset as usize] = (value >> 24) as u8; block[offset as usize..=(offset + 3) as usize]
block.data[(offset + 1) as usize] = (value >> 16) as u8; .copy_from_slice(&value.to_be_bytes());
block.data[(offset + 2) as usize] = (value >> 8) as u8;
block.data[(offset + 3) as usize] = value as u8;
Ok(()) Ok(())
} }
fn write_range(&mut self, addr: u32, value: Vec<u8>) -> Result<(), ProcessorError> { #[inline]
for (i, byte) in value.into_iter().enumerate() { fn write_range(&mut self, addr: u32, value: Vec<u8>) {
let (block_addr, offset) = Self::segment_addr(addr + i as u32); let mut current_block_addr = addr / 256;
let block = self.mut_block(block_addr); let mut current_block = self.mut_block(current_block_addr);
block.data[offset as usize] = byte; let mut offset = addr % 256;
for byte in value {
current_block[offset as usize] = byte;
offset += 1;
if offset >= 256 {
offset = 0;
current_block_addr += 1;
current_block = self.mut_block(current_block_addr);
}
} }
Ok(())
} }
fn read_block(&mut self, addr: u32) -> Result<[u8; 256], ProcessorError> { #[inline]
fn read_block(&mut self, addr: u32) -> &[u8; 256] {
let (block_addr, _) = Self::segment_addr(addr); let (block_addr, _) = Self::segment_addr(addr);
let block = self.block(block_addr); self.block(block_addr)
Ok(block.data)
} }
fn write_block(&mut self, addr: u32, data: [u8; 256]) -> Result<(), ProcessorError> { #[inline]
fn write_block(&mut self, addr: u32, data: &[u8; 256]) {
let (block_addr, _) = Self::segment_addr(addr); let (block_addr, _) = Self::segment_addr(addr);
let block = self.mut_block(block_addr); let _ = self.data.insert(block_addr, *data);
block.data = data;
Ok(())
} }
} }
+1
View File
@@ -1,3 +1,4 @@
pub mod cache;
pub mod emulator; pub mod emulator;
pub mod memory; pub mod memory;
pub mod model; pub mod model;
+4 -5
View File
@@ -78,7 +78,7 @@ pub struct State {
pub error_log: Vec<String>, pub error_log: Vec<String>,
pub instruction_history: Vec<(u32, Instruction)>, pub instruction_history: Vec<(u32, u32)>,
} }
impl State { impl State {
@@ -154,7 +154,7 @@ pub enum StateUpdate {
MemoryView(Vec<u8>), MemoryView(Vec<u8>),
DisplayView(Vec<u8>), DisplayView(Vec<u8>),
Error(String), Error(String),
InstructionHistory(Vec<(u32, Instruction)>), InstructionHistory(Vec<(u32, u32)>),
} }
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] #[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
@@ -286,11 +286,10 @@ impl RegFile {
Register::Sts => &mut self.sts, Register::Sts => &mut self.sts,
Register::Cir => &mut self.cir, Register::Cir => &mut self.cir,
Register::Pcx => &mut self.pcx, Register::Pcx => &mut self.pcx,
_ => return Err(ProcessorError::InvalidRegister(Register::NoReg as u8)), _ => return Err(ProcessorError::InvalidRegister(Register::Null as u8)),
}) })
} }
#[must_use]
pub const fn get(&self, reg: Register) -> Result<u32, ProcessorError> { pub const fn get(&self, reg: Register) -> Result<u32, ProcessorError> {
Ok(match reg { Ok(match reg {
Register::Rg0 => self.rg0, Register::Rg0 => self.rg0,
@@ -321,7 +320,7 @@ impl RegFile {
Register::Cir => self.cir, Register::Cir => self.cir,
Register::Pcx => self.pcx, Register::Pcx => self.pcx,
Register::Zero => 0, Register::Zero => 0,
_ => return Err(ProcessorError::InvalidRegister(Register::NoReg as u8)), _ => return Err(ProcessorError::InvalidRegister(Register::Null as u8)),
}) })
} }
} }
+35 -26
View File
@@ -4,6 +4,7 @@ use std::{
}; };
use crate::emulator::system::{ use crate::emulator::system::{
cache::Cache,
memory::MemoryUnit, memory::MemoryUnit,
model::{IODevice, ProcessorError, RegFile}, model::{IODevice, ProcessorError, RegFile},
}; };
@@ -17,10 +18,7 @@ pub struct Processor {
pub io_devices: Vec<Arc<dyn IODevice>>, pub io_devices: Vec<Arc<dyn IODevice>>,
pub void: u32, pub void: u32,
} pub cache: Cache,
fn log(message: &str) {
println!("\x1b[32mINFO:\x1b[0m {message}");
} }
impl Processor { impl Processor {
@@ -32,6 +30,7 @@ impl Processor {
halted: false, halted: false,
io_devices, io_devices,
void: 0, void: 0,
cache: Cache::new(),
} }
} }
@@ -51,21 +50,35 @@ impl Processor {
// Get value from PCX. // Get value from PCX.
let addr = self.fetch()?; let addr = self.fetch()?;
// Increment PCX. // Increment PCX.
self.advance(); self.advance()?;
// Set MAR to the previous value of PCX. // Set MAR to the previous value of PCX.
*self.reg(Register::Mar)? = addr; *self.reg(Register::Mar)? = addr;
let val = self.memory.read_word(addr)?;
let encoded = if let Some(val) = self.cache.lookup_value(addr) {
val
} else {
let block = self.memory.read_block(addr);
self.cache.set(addr, block);
self.cache
.lookup_value(addr)
.expect("Failed to lookup value!")
};
// Set CIR to the value of RAM[MAR]. // Set CIR to the value of RAM[MAR].
*self.reg(Register::Mar)? = val; *self.reg(Register::Cir)? = encoded;
// Decode and execute the instruction. let decoded = if let Some(val) = self.cache.lookup_instruction(addr) {
let instruction = Instruction::decode(val) val
.map_err(|_| ProcessorError::InvalidInstruction(val))?; } else {
let decoded = Instruction::decode(encoded)
.map_err(|_| ProcessorError::InvalidInstruction(encoded))?;
self.cache.insert(addr, decoded);
decoded
};
instruction.execute(self)?; decoded.execute(self)?;
Ok((addr, instruction)) Ok((addr, decoded))
} }
const fn fetch(&self) -> Result<u32, ProcessorError> { const fn fetch(&self) -> Result<u32, ProcessorError> {
@@ -84,7 +97,7 @@ impl Processor {
} }
pub fn display(&mut self) -> Result<Vec<u8>, ProcessorError> { pub fn display(&mut self) -> Result<Vec<u8>, ProcessorError> {
self.memory.read_range(0x20000, 2000) Ok(self.memory.read_range(0x20000, 2000))
} }
pub fn cmp(&mut self, a: u32, b: u32) { pub fn cmp(&mut self, a: u32, b: u32) {
@@ -163,10 +176,10 @@ impl Processor {
let addr = self.get(Register::Spr)?; let addr = self.get(Register::Spr)?;
let size = n * 4; let size = n * 4;
// returns the stack // returns the stack
self.memory.read_range( Ok(self.memory.read_range(
max(addr, 0), // ensures that we cannot read from a negative address max(addr, 0), // ensures that we cannot read from a negative address
min(size, addr), // ensures we don't read above the top of the stack min(size, addr), // ensures we don't read above the top of the stack
) ))
} }
} }
@@ -209,7 +222,7 @@ impl Executable for Instruction {
Self::LoadByte(a) => { Self::LoadByte(a) => {
*cpu.reg(a.r2)? = u32::from( *cpu.reg(a.r2)? = u32::from(
cpu.memory cpu.memory
.read_byte(cpu.get(a.r1)? + u32::from(a.immediate))?, .read_byte(cpu.get(a.r1)? + u32::from(a.immediate)),
); );
} }
@@ -218,7 +231,7 @@ impl Executable for Instruction {
Self::LoadByteSigned(a) => { Self::LoadByteSigned(a) => {
*cpu.reg(a.r2)? = sign_extend(u32::from( *cpu.reg(a.r2)? = sign_extend(u32::from(
cpu.memory cpu.memory
.read_byte(cpu.get(a.r1)? + u32::from(a.immediate))?, .read_byte(cpu.get(a.r1)? + u32::from(a.immediate)),
)); ));
} }
@@ -257,7 +270,7 @@ impl Executable for Instruction {
cpu.memory.write_byte( cpu.memory.write_byte(
cpu.get(a.r2)? + u32::from(a.immediate), cpu.get(a.r2)? + u32::from(a.immediate),
cpu.get(a.r1)? as u8, cpu.get(a.r1)? as u8,
)?; );
} }
// Stores a half-word from SrcReg in memory address (base + offset) The // Stores a half-word from SrcReg in memory address (base + offset) The
@@ -266,9 +279,9 @@ impl Executable for Instruction {
// split the value into bytes and then write two bytes // split the value into bytes and then write two bytes
let bytes = (cpu.get(a.r1)? as u16).to_le_bytes(); let bytes = (cpu.get(a.r1)? as u16).to_le_bytes();
cpu.memory cpu.memory
.write_byte(cpu.get(a.r2)? + u32::from(a.immediate), bytes[0])?; .write_byte(cpu.get(a.r2)? + u32::from(a.immediate), bytes[0]);
cpu.memory cpu.memory
.write_byte(cpu.get(a.r2)? + u32::from(a.immediate) + 1, bytes[1])?; .write_byte(cpu.get(a.r2)? + u32::from(a.immediate) + 1, bytes[1]);
} }
// Stores a word from SrcReg in memory address (base + offset) The effective // Stores a word from SrcReg in memory address (base + offset) The effective
@@ -349,17 +362,13 @@ impl Executable for Instruction {
// Left shifts the value in Reg by the given amount (either a register, or a // Left shifts the value in Reg by the given amount (either a register, or a
// literal value) // literal value)
Self::ShiftLeft(a) => { Self::ShiftLeft(a) => {
let reg = cpu.get(a.sr1)?; *cpu.reg(a.dr)? = shl(cpu.get(a.sr1)?, a.shamt + cpu.get(a.sr2)? as u8);
let val = a.shamt;
*cpu.reg(a.sr1)? = shl(reg, val);
} }
// Right shifts the value in Reg by the given amount (either a register, or a // Right shifts the value in Reg by the given amount (either a register, or a
// literal value). // literal value).
Self::ShiftRight(a) => { Self::ShiftRight(a) => {
let regval = cpu.get(a.sr1)?; *cpu.reg(a.dr)? = shr(cpu.get(a.sr1)?, a.shamt + cpu.get(a.sr2)? as u8);
let val = a.shamt;
*cpu.reg(a.sr1)? = shr(regval, val);
} }
// Adds the value of Src2 to Src1 and writes the result to a.dr // Adds the value of Src2 to Src1 and writes the result to a.dr
@@ -81,9 +81,7 @@ fn test_mov_signed_instruction() {
fn test_load_byte_instruction() { fn test_load_byte_instruction() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
let addr = 0x100; let addr = 0x100;
cpu.memory cpu.memory.write_byte(addr, 0xAB);
.write_byte(addr, 0xAB)
.expect("Failed to write byte to memory");
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr - 4; *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr - 4;
let load_byte_instr = Instruction::LoadByte(ITypeArgs::new( let load_byte_instr = Instruction::LoadByte(ITypeArgs::new(
@@ -105,9 +103,7 @@ fn test_load_byte_instruction() {
fn test_load_byte_signed_instruction() { fn test_load_byte_signed_instruction() {
let mut cpu = create_test_processor(); let mut cpu = create_test_processor();
let addr = 0x100; let addr = 0x100;
cpu.memory cpu.memory.write_byte(addr, 0xFF);
.write_byte(addr, 0xFF)
.expect("Failed to write byte to memory");
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr; *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr;
let load_byte_signed_instr = Instruction::LoadByteSigned(ITypeArgs::new( let load_byte_signed_instr = Instruction::LoadByteSigned(ITypeArgs::new(
@@ -189,7 +185,7 @@ fn test_store_byte_instruction() {
store_byte_instr.execute(&mut cpu).expect( store_byte_instr.execute(&mut cpu).expect(
"Emulator was slain by losing the game while attempting to execute instruction", "Emulator was slain by losing the game while attempting to execute instruction",
); );
assert_eq!(cpu.memory.read_byte(addr).expect("Emulator was slain by losing the game while attempting to execute instruction"), 0xAB); assert_eq!(cpu.memory.read_byte(addr), 0xAB);
} }
#[test] #[test]
@@ -468,7 +464,7 @@ fn test_shift_left_with_shamt() {
let shl_instr = Instruction::ShiftLeft(RTypeArgs::new( let shl_instr = Instruction::ShiftLeft(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
Some(Register::Zero), Some(Register::Zero),
None, Some(Register::Rg1),
Some(2), Some(2),
)); ));
@@ -489,7 +485,7 @@ fn test_shift_right_with_shamt() {
let shr_instr = Instruction::ShiftRight(RTypeArgs::new( let shr_instr = Instruction::ShiftRight(RTypeArgs::new(
Some(Register::Rg1), Some(Register::Rg1),
Some(Register::Zero), Some(Register::Zero),
None, Some(Register::Rg1),
Some(2), Some(2),
)); ));
+6 -14
View File
@@ -117,10 +117,7 @@ impl Editor {
.file_name() .file_name()
.unwrap_or_else(|| OsStr::new("Unnamed!")) .unwrap_or_else(|| OsStr::new("Unnamed!"))
.to_str() .to_str()
.map_or_else( .unwrap_or_else(|| unreachable!("File name should be valid UTF-8."));
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
);
} }
"Unnamed!" "Unnamed!"
} }
@@ -129,12 +126,9 @@ impl Editor {
if let Some(path) = &self.path { if let Some(path) = &self.path {
return path return path
.extension() .extension()
.map_or_else(|| OsStr::new("Unknown!"), |ext| ext) .unwrap_or_else(|| OsStr::new("Unknown!"))
.to_str() .to_str()
.map_or_else( .unwrap_or_else(|| unreachable!("File name should be valid UTF-8."));
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
);
} }
"Unknown!" "Unknown!"
} }
@@ -398,7 +392,7 @@ impl Editor {
_ => Syntax::default(), _ => Syntax::default(),
}; };
let ed = CodeEditor::default() let mut editor = CodeEditor::default()
.id_source("editor") .id_source("editor")
.with_fontsize(12.0) .with_fontsize(12.0)
.with_rows(0) .with_rows(0)
@@ -407,8 +401,6 @@ impl Editor {
.with_numlines(true) .with_numlines(true)
.desired_width(available_width - 500.0); .desired_width(available_width - 500.0);
let mut editor = ed.clone();
editor.show(ui, &mut self.text); editor.show(ui, &mut self.text);
} }
@@ -451,7 +443,7 @@ impl Editor {
Some("dsc") => { Some("dsc") => {
let output_path = Path::new(path).with_extension("dsa"); let output_path = Path::new(path).with_extension("dsa");
if let Err(e) = compiler::compile_file(path, &output_path) { if let Err(e) = compiler::compile_file(path, &output_path) {
self.error = Some(format!("Compiler error: {}", e)); self.error = Some(format!("Compiler error: {e}"));
} }
let mut compiler = CompilerEngine::new(); let mut compiler = CompilerEngine::new();
@@ -461,7 +453,7 @@ impl Editor {
let instructions = match compiler.wait_for_result() { let instructions = match compiler.wait_for_result() {
Ok(instructions) => instructions, Ok(instructions) => instructions,
Err(e) => { Err(e) => {
self.error = Some(format!("Assembler error: {}", e)); self.error = Some(format!("Assembler error: {e}"));
return; return;
} }
}; };
+5 -1
View File
@@ -1,3 +1,4 @@
use common::prelude::Instruction;
use egui::{Context, Ui}; use egui::{Context, Ui};
use crate::emulator::{ use crate::emulator::{
@@ -57,8 +58,11 @@ impl Component for History {
.color(egui::Color32::from_rgb(255, 200, 200)), .color(egui::Color32::from_rgb(255, 200, 200)),
); );
let decoded = Instruction::decode(instruction.1)
.unwrap_or(Instruction::Nop);
ui.label( ui.label(
egui::RichText::new(instruction.1.to_string()) egui::RichText::new(decoded.to_string())
.font(egui::FontId::monospace(12.0)) .font(egui::FontId::monospace(12.0))
.color(egui::Color32::from_rgb(200, 255, 200)), .color(egui::Color32::from_rgb(200, 255, 200)),
); );
+1 -4
View File
@@ -79,10 +79,7 @@ impl Loader {
.file_name() .file_name()
.unwrap_or_else(|| OsStr::new("Unnamed!")) .unwrap_or_else(|| OsStr::new("Unnamed!"))
.to_str() .to_str()
.map_or_else( .unwrap_or_else(|| unreachable!("File name should be valid UTF-8."));
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
);
} }
"Unnamed!" "Unnamed!"
} }
-121
View File
@@ -1,121 +0,0 @@
// GENERATED BY DSC COMPILER
// Generated at 2026-02-05 00:42:40
// Imports
include print: "./lib/io/print.dsa"
include arena: "./lib/memory/arena_alloc.dsa"
// Globals & Reserved Memory
// Entry Point
dw stack: 0x10000
db message: "Process Exited with code:"
_init:
ldw stack, bpr
mov bpr, spr
push zero
call main
call print::print_newline
lwi message, rg0
push rg0
call print::print
pop zero
call print::print_hex_word
pop zero
hlt
// Return
_ret:
mov bpr, spr
pop bpr
return
// Compiled Code Starts...
main:
push bpr
mov spr, bpr
lli 0, rg0
push rg0 // bpr-4: x
subi bpr 4 rg1
lli 512, rg0
push rg1 // bpr-8: y
push rg0 // push arg 0
call arena::new
pop rg2
lli 32, rg0
push rg2 // bpr-12: alloc
push rg0 // push arg 1
push rg2 // push arg 0
call arena::alloc
pop rg3
pop zero
lli 32, rg0
subi bpr 12 rg2
ldw rg2, rg2 // bpr-20: alloc
push rg2 // bpr-16: alloc
push rg3 // bpr-20: ptr1
push rg0 // push arg 1
push rg2 // push arg 0
call arena::alloc
pop rg4
pop zero
subi bpr 16 rg0
ldw rg0, rg0 // bpr-24: alloc
push rg4 // bpr-24: ptr2
push rg0 // bpr-28: alloc
push rg0 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 20 rg0
ldw rg0, rg0 // bpr-28: ptr1
push rg0 // bpr-32: ptr1
push rg0 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 24 rg0
ldw rg0, rg0 // bpr-32: ptr2
push rg0 // bpr-36: ptr2
push rg0 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 36 rg0
ldw rg0, rg0 // bpr-44: ptr2
ldw rg0, rg2
push rg0 // bpr-40: ptr2
push rg2 // push arg 0
call print::print_num
pop zero
call print::print_newline
lli 42, rg2
subi bpr 40 rg5
ldw rg5, rg5 // bpr-48: ptr2
stw rg2, rg5
push rg5 // bpr-44: ptr2
push rg5 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 44 rg2
ldw rg2, rg2 // bpr-52: ptr2
ldw rg2, rg5
push rg2 // bpr-48: ptr2
push rg5 // push arg 0
call print::print_num
pop zero
call print::print_newline
db str_1: "end"
lwi str_1, rg5
push rg5 // push arg 0
call print::println
pop zero
lli 0, rg5
stw rg5, bpr, 8
jmp _ret
+10
View File
@@ -32,3 +32,13 @@ handle_hard_fault:
call print::print call print::print
pop zero pop zero
hlt hlt
trigger:
push bpr
mov spr, bpr
int 0x01
mov bpr, spr
pop bpr
return
+10 -48
View File
@@ -1,50 +1,12 @@
// program to just test compute power
// GENERATED BY DSC COMPILER dw large_num: 0x333333 // 333,333 instructions
// Generated at 2026-02-04 01:44:06 start:
ldw large_num, rg0
// Imports // run approx 1m instructions
include print: "./lib/io/print.dsa" loop:
include fib: "./lib/maths/fib.dsa" dec rg0
cmp rg0, zero
// Globals & Reserved Memory jgt loop
hlt
// Entry Point
dw stack: 0x10000
db message: "Process Exited with code:"
_init:
ldw stack, bpr
mov bpr, spr
push zero
call main
call print::print_newline
lwi message, rg0
push rg0
call print::print
pop zero
call print::print_hex_word
pop zero
hlt
// Return
_ret:
mov bpr, spr
pop bpr
return
// Compiled Code Starts...
main:
push bpr
mov spr, bpr
lli 6, rg0
push rg0 // bpr-4: x
push rg0 // push arg 0
call fib::fib_n
pop rg1
push rg1 // bpr-8: y
push rg1 // push arg 0
call print::print_num
pop zero
jmp _ret
-214
View File
@@ -1,214 +0,0 @@
// GENERATED BY DSC COMPILER
// Generated at 2026-02-03 23:37:16
// Imports
include print: "./lib/io/print.dsa"
// Globals & Reserved Memory
dw heap_start: 196608
dw heap_end: 262144
dw heap_current: 196608
// Entry Point
dw stack: 0x10000
db message: "Process Exited with code:"
_init:
ldw stack, bpr
mov bpr, spr
push zero
call main
call print::print_newline
lwi message, rg0
push rg0
call print::print
pop zero
call print::print_hex_word
pop zero
hlt
// Return
_ret:
mov bpr, spr
pop bpr
return
// Compiled Code Starts...
main:
push bpr
mov spr, bpr
lli 0, rg0
push rg0 // bpr-4: x
subi bpr 4 rg1
lli 512, rg0
push rg1 // bpr-8: y
push rg0 // push arg 0
call arena_create
pop rg2
lli 32, rg0
push rg2 // bpr-12: alloc
push rg0 // push arg 1
push rg2 // push arg 0
call arena_alloc
pop rg3
pop zero
lli 32, rg0
subi bpr 12 rg2
ldw rg2, rg2 // bpr-20: alloc
push rg3 // bpr-16: ptr1
push rg2 // bpr-20: alloc
push rg0 // push arg 1
push rg2 // push arg 0
call arena_alloc
pop rg4
pop zero
subi bpr 20 rg0
ldw rg0, rg0 // bpr-28: alloc
push rg4 // bpr-24: ptr2
push rg0 // bpr-28: alloc
push rg0 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 16 rg0
ldw rg0, rg0 // bpr-24: ptr1
push rg0 // bpr-32: ptr1
push rg0 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 24 rg0
ldw rg0, rg0 // bpr-32: ptr2
push rg0 // bpr-36: ptr2
push rg0 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 36 rg0
ldw rg0, rg0 // bpr-44: ptr2
ldw rg0, rg2
push rg0 // bpr-40: ptr2
push rg2 // push arg 0
call print::print_num
pop zero
call print::print_newline
lli 42, rg2
subi bpr 40 rg5
ldw rg5, rg5 // bpr-48: ptr2
stw rg2, rg5
push rg5 // bpr-44: ptr2
push rg5 // push arg 0
call print::print_hex_word
pop zero
call print::print_newline
subi bpr 44 rg2
ldw rg2, rg2 // bpr-52: ptr2
ldw rg2, rg5
push rg2 // bpr-48: ptr2
push rg5 // push arg 0
call print::print_num
pop zero
call print::print_newline
db str_1: "end"
lwi str_1, rg5
push rg5 // push arg 0
call print::println
pop zero
lli 0, rg5
stw rg5, bpr, 8
jmp _ret
arena_create:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
lli 12, rg1
add rg0, rg1, rg2
ldw heap_current, rg1
add rg1, rg2, rg3
ldw heap_end, rg4
cmp rg3, rg4
lli 0, rg5
jle _cmp_end_2
lli 1, rg5
_cmp_end_2:
cmp rg5, zero
jeq _else_4
_then_3:
lli 0, rg4
stw rg4, bpr, 8
jmp _ret
jmp _end_5
_else_4:
nop
_end_5:
lli 12, rg4
add rg1, rg4, rg5
add rg1, rg2, rg4
stw rg5, rg1
lli 4, rg6
add rg1, rg6, rg7
stw rg5, rg7
lli 8, rg6
add rg1, rg6, rg7
stw rg4, rg7
stw rg3, heap_current
stw rg1, bpr, 8
jmp _ret
arena_alloc:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
ldw bpr, rg1, 12
lli 4, rg2
add rg0, rg2, rg3
ldw rg3, rg2
lli 8, rg3
add rg0, rg3, rg4
ldw rg4, rg3
add rg2, rg1, rg4
cmp rg4, rg3
lli 0, rg5
jle _cmp_end_6
lli 1, rg5
_cmp_end_6:
cmp rg5, zero
jeq _else_8
_then_7:
lli 0, rg5
stw rg5, bpr, 8
jmp _ret
jmp _end_9
_else_8:
nop
_end_9:
lli 4, rg5
add rg0, rg5, rg6
stw rg4, rg6
stw rg2, bpr, 8
jmp _ret
arena_destroy:
push bpr
mov spr, bpr
ldw bpr, rg0, 8
lli 0, rg1
stw rg1, bpr, 8
jmp _ret
reset_all:
push bpr
mov spr, bpr
ldw heap_start, rg0
stw rg0, heap_current
lli 0, rg0
stw rg0, bpr, 8
jmp _ret
+20
View File
@@ -0,0 +1,20 @@
include print: "./lib/io/print.dsa";
fn main() -> u32 {
let x: u32 = 30;
print::print_num(x);
200 + 5;
let p: Point = Point {
x: 10,
y: 20,
test: [2, 3, 4]
};
}
struct Point {
x: u32,
y: u32,
test: [u32; 3],
}
+1 -1
View File
@@ -1,4 +1,4 @@
include print "./lib/io/print.dsa" include print: "./lib/io/print.dsa"
dw idt: 0xFFFF0000 dw idt: 0xFFFF0000
dw stack: 0x10000 dw stack: 0x10000