21 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
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
43 changed files with 3684 additions and 1178 deletions
+4
View File
@@ -5,3 +5,7 @@ rustc-wrapper = "sccache"
[future-incompat-report]
frequency = "always"
[profile.profiling]
inherits = "release"
debug = true
+4
View File
@@ -8,4 +8,8 @@
"files.trimTrailingWhitespace": true,
"gitea.owner": "LowLevelDevs",
"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]
codegen-backend = "cranelift"
panic = "abort" # Cranelift does not support stack unwinds.
panic = "abort" # Cranelift does not support stack unwinds.
lto = false
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
[[bin]]
name = "assembler_runner"
name = "assembler"
path = "src/main.rs"
[lib]
+2 -1
View File
@@ -4,7 +4,8 @@ use crate::assembler::model::{Node, Opcode, Symbol, Token};
/// Parse DSA assembly code with optional formatting
///
/// # Examples
/// ```
/// ```rs
/// use assembler::macros::dsa;
/// // With formatting:
/// let nodes = dsa!(hash, "mov r1, {}", 42)?;
///
+5 -5
View File
@@ -184,11 +184,11 @@ pub enum Token {
impl fmt::Display for Token {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Symbol(symbol) => write!(f, "{}", symbol),
Self::Register(register) => write!(f, "{}", register),
Self::Immediate(immediate) => write!(f, "{}", immediate),
Self::StringLit(string_lit) => write!(f, "{}", string_lit),
Self::Opcode(opcode) => write!(f, "{}", opcode),
Self::Symbol(symbol) => write!(f, "{symbol}"),
Self::Register(register) => write!(f, "{register}",),
Self::Immediate(immediate) => write!(f, "{immediate}",),
Self::StringLit(string_lit) => write!(f, "{string_lit}",),
Self::Opcode(opcode) => write!(f, "{opcode}",),
}
}
}
+14 -12
View File
@@ -101,6 +101,7 @@ impl Parser {
let opcode = expect_token!(self.next()?, Opcode)?;
let args: Vec<Token>;
#[allow(clippy::match_same_arms)]
match opcode {
// R-type instructions
Opcode::Mov | Opcode::Movs => {
@@ -113,24 +114,25 @@ impl Parser {
let base = expect_type!(self.next()?, Register, Symbol)?;
let dest = expect_type!(self.next()?, Register)?;
let mut offset = Token::Immediate(0);
if let Ok(next) = self.peek_next()
&& expect_type!(next, Immediate).is_ok()
{
offset = self.next()?;
}
let offset = match self.peek_next() {
Ok(next) if expect_type!(next.clone(), Immediate).is_ok() => {
self.next()?
}
_ => Token::Immediate(0),
};
args = vec![base, dest, offset];
}
Opcode::Stb | Opcode::Sth | Opcode::Stw => {
let base = expect_type!(self.next()?, Register)?;
let dest = expect_type!(self.next()?, Register, Symbol)?;
let mut offset = Token::Immediate(0);
if let Ok(next) = self.peek_next()
&& expect_type!(next, Immediate).is_ok()
{
offset = self.next()?;
}
let offset = match self.peek_next() {
Ok(next) if expect_type!(next.clone(), Immediate).is_ok() => {
self.next()?
}
_ => Token::Immediate(0),
};
args = vec![base, dest, offset];
}
+1 -1
View File
@@ -34,7 +34,7 @@ use crate::prelude::CompilerEngine;
pub fn assemble_file(input: &str, output: &str) -> Result<(), std::io::Error> {
let mut engine = CompilerEngine::new();
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
.iter()
+1
View File
@@ -0,0 +1 @@
disallowed-types = ["std::collections::HashMap", "std::collections::HashSet"]
+3 -7
View File
@@ -40,7 +40,7 @@ pub enum InstructionType {
Immediate,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum Register {
// general purpose registers
@@ -69,6 +69,8 @@ pub enum Register {
Idr,
Mmr,
Zero,
#[default]
Null, // Invalid - Triggers a fault if accessed
// system registers - can't be written to by instructions.
@@ -104,12 +106,6 @@ impl Register {
}
}
impl Default for Register {
fn default() -> Self {
Self::Null
}
}
impl TryFrom<u8> for Register {
type Error = RegisterParseError;
+1
View File
@@ -7,3 +7,4 @@ authors.workspace = true
[dependencies]
chrono = "0.4.43"
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};
mod codegen;
mod instruction;
mod registers;
mod scope;
mod variable;
pub fn generate_code(ast: &Program) -> Result<String, CompilerError> {
let mut codegen = codegen::CodeGenerator::new(ast.clone());
+130 -112
View File
@@ -1,12 +1,14 @@
use std::{collections::HashMap, fmt};
use crate::model::CompilerError;
use crate::{
backend::dsa::instruction::{InsBlock, Instruction},
model::CompilerError,
};
/// Register allocator for DSA assembly generation
/// Manages general-purpose registers (rg0-rgf) and handles stack spilling
pub struct RegisterAllocator {
/// Available general-purpose registers
/// Maps variable names to their current location (register or stack offset)
variable_locations: HashMap<String, Location>,
@@ -18,7 +20,7 @@ pub struct RegisterAllocator {
stack_offset: i32,
/// Track which registers are currently in use
in_use: HashMap<Register, bool>,
in_use: Vec<(Register, bool)>,
}
#[derive(Debug, Clone)]
@@ -79,14 +81,14 @@ impl RegisterAllocator {
/// Allocate a temporary register for expression evaluation
/// 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
// println!("finding! {:#?}", self.in_use);
if let Some(reg) = self.find_free_register() {
self.in_use.insert(reg, true);
return Ok((reg, Vec::new()));
self.in_use[reg as usize].1 = true;
return Ok((reg, InsBlock::new()));
}
// All registers in use - need to spill one
@@ -133,7 +135,7 @@ impl RegisterAllocator {
// This is a true temporary - safe to free
if !matches!(reg, Register::Zero | Register::Null) {
self.in_use.insert(reg, false);
self.in_use[reg as usize].1 = false;
}
}
@@ -144,7 +146,7 @@ impl RegisterAllocator {
&& !matches!(reg, Register::Zero | Register::Null)
{
self.register_contents.remove(&reg);
self.in_use.insert(reg, false);
self.in_use[reg as usize].1 = false;
}
self.variable_locations.remove(var);
@@ -156,11 +158,11 @@ impl RegisterAllocator {
pub fn alloc_var(
&mut self,
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 the var is in a register we can use it already.
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.
@@ -174,19 +176,17 @@ impl RegisterAllocator {
// Load from bpr + offset (offset is negative)
// code.push(format!("\tsubi bpr {} {}", -(offset + 4), reg));
code.push(format!(
"\tldw spr, {}, {} // spr+{}: {}",
code.push(Instruction::ldw_reg_offset(
Register::Spr,
reg,
offset - self.stack_offset,
offset - self.stack_offset,
var_name
));
// Update location to register
self.variable_locations
.insert(var_name.to_string(), location);
self.register_contents
.insert(reg.clone(), var_name.to_string());
self.register_contents.insert(reg, var_name.to_string());
return Ok((reg, code));
}
@@ -196,8 +196,7 @@ impl RegisterAllocator {
let (reg, code) = self.alloc_temp()?;
self.variable_locations
.insert(var_name.to_string(), Location::register(reg));
self.register_contents
.insert(reg.clone(), var_name.to_string());
self.register_contents.insert(reg, var_name.to_string());
Ok((reg, code))
}
@@ -212,34 +211,34 @@ impl RegisterAllocator {
pub fn load_var(
&mut self,
var_name: &str,
) -> Result<(Register, Vec<String>), CompilerError> {
) -> Result<(Register, InsBlock), CompilerError> {
self.alloc_var(var_name)
}
/// Store a value from a register into a variable
/// 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
if let Some(location) = self.variable_locations.get(var_name) {
// if the variable exists in a register we write to that.
if let Some(reg) = location.register {
if reg == *source_reg {
return vec![format!(
"\tmov {}, {} // save var:{} reg:{}",
source_reg, reg, var_name, reg
)];
match location.register {
Some(reg) if reg == *source_reg => {
block.push(Instruction::mov(*source_reg, reg));
return block;
}
_ => (),
}
// if the variable exists on the stack but not a register we write here.
if let Some(offset) = location.stack {
return vec![format!(
"\tstw {}, spr, {} // save var:{} offset:{}",
source_reg,
block.push(Instruction::stw_reg_offset(
*source_reg,
Register::Spr,
offset - self.stack_offset,
var_name,
offset
)];
));
return block;
}
}
@@ -252,9 +251,9 @@ impl RegisterAllocator {
.insert(var_name.to_string(), Location::register(*source_reg));
self.register_contents
.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
@@ -263,10 +262,11 @@ impl RegisterAllocator {
self.variable_locations
.insert(var_name.to_string(), Location::register(free_reg));
self.register_contents
.insert(free_reg.clone(), var_name.to_string());
self.in_use.insert(free_reg, true);
.insert(free_reg, var_name.to_string());
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
@@ -280,11 +280,8 @@ impl RegisterAllocator {
/// spill a register to the stack (WITHOUT FREEING)
/// DO NOT USE this if it's for a pointer!!!!
pub fn _spill_register(
&mut self,
reg: &Register,
) -> Result<Vec<String>, CompilerError> {
let mut code = Vec::new();
pub fn _spill_register(&mut self, reg: &Register) -> Result<InsBlock, CompilerError> {
let mut code = InsBlock::new();
// check if the variable is declared.
if let Some(var_name) = self.register_contents.get(reg).cloned()
@@ -292,13 +289,10 @@ impl RegisterAllocator {
{
// check if var is on the stack
if let Some(offset) = location.stack {
// ensure stack value is up to date with register value.
code.push(format!(
"\tstw {}, spr, {} // save var:{} offset:{}",
reg,
code.push(Instruction::stw_reg_offset(
*reg,
Register::Spr,
offset - self.stack_offset,
var_name,
offset
));
return Ok(code);
}
@@ -309,10 +303,7 @@ impl RegisterAllocator {
// if the variable is not on the stack:
// push register to stack (spr decrements automatically)
let offset = self.stack_offset;
code.push(format!(
"\tpush {} // free var:{} offset:{}",
reg, var_name, offset
));
code.push(Instruction::push(*reg));
// Update variable location - it's now at current spr
// Note: We track offset from bpr for consistency
@@ -332,9 +323,7 @@ impl RegisterAllocator {
pub fn free_register(
&mut self,
reg: &Register,
) -> Result<(i32, Vec<String>), CompilerError> {
let mut code = Vec::new();
) -> Result<(i32, Instruction), CompilerError> {
// check if the variable is declared.
if let Some(var_name) = self.register_contents.get(reg).cloned()
&& let Some(location) = self.variable_locations.get_mut(&var_name)
@@ -342,13 +331,11 @@ impl RegisterAllocator {
// check if var name is on the stack
if let Some(offset) = location.stack {
// store current register value in stack location
code.push(format!(
"\tstw {}, spr, {} // save var:{} offset:{}",
reg,
let code = Instruction::stw_reg_offset(
*reg,
Register::Spr,
offset - self.stack_offset,
var_name,
offset
));
);
// free the register.
location.register = None;
@@ -360,10 +347,7 @@ impl RegisterAllocator {
self.stack_offset -= 4;
let offset = self.stack_offset;
code.push(format!(
"\tpush {} // free var:{} offset:{}",
reg, var_name, offset
));
let code = Instruction::push(*reg);
// Update variable location
// Note: We track offset from bpr for consistency
@@ -390,15 +374,15 @@ impl RegisterAllocator {
}
/// Spill all registers to stack (useful before function calls)
pub fn _spill_all(&mut self) -> Vec<String> {
let mut code = Vec::new();
pub fn _spill_all(&mut self) -> InsBlock {
let mut code = InsBlock::new();
let regs_to_spill: Vec<Register> =
self.register_contents.keys().cloned().collect();
for reg in regs_to_spill {
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();
}
/// 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
/// These need to be saved before function calls
pub fn get_caller_saved_registers(&self) -> Vec<Register> {
self.register_contents
.iter()
.filter(|(reg, _)| *self.in_use.get(*reg).unwrap_or(&false))
.map(|(reg, _)| reg.clone())
.filter(|(reg, _)| {
self.in_use
.get(**reg as usize)
.unwrap_or(&(Register::Null, false))
.1
})
.map(|(reg, _)| *reg)
.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)]
pub enum Register {
// general purpose
Rg0 = 0,
Rg1 = 1,
Rg2 = 2,
@@ -512,8 +462,68 @@ pub enum Register {
Rgd = 13,
Rge = 14,
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 {
@@ -535,7 +545,15 @@ impl fmt::Display for Register {
Self::Rgd => write!(f, "rgd"),
Self::Rge => write!(f, "rge"),
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::Pcx => write!(f, "pcx"),
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!()
}
}
+176 -26
View File
@@ -18,12 +18,14 @@ pub enum Token {
Const,
As,
SizeOf,
Struct,
// Identifiers and literals
Identifier(Name),
String(String),
Integer(u64),
Char(char),
SignedInt(i32, Option<TypeId>),
UnsignedInt(u32, Option<TypeId>),
// Delimiters
LeftParen, // (
@@ -85,7 +87,7 @@ pub enum Token {
Eof,
}
use crate::model::Name;
use crate::model::{Name, TypeId};
use std::fmt;
impl fmt::Display for Name {
@@ -104,6 +106,7 @@ impl Token {
Token::Const => "Const",
Token::Static => "Static",
Token::Include => "Include",
Token::Struct => "Struct",
Token::Fn => "Fn",
Token::If => "If",
Token::Let => "Let",
@@ -116,7 +119,8 @@ impl Token {
Token::As => "As",
Token::Identifier(_) => "Identifier",
Token::String(_) => "String",
Token::Integer(_) => "UnsignedInt",
Token::UnsignedInt(_, _) => "UnsignedInt",
Token::SignedInt(_, _) => "SignedInt",
Token::Char(_) => "Char",
Token::LeftParen => "LeftParen",
Token::RightParen => "RightParen",
@@ -376,6 +380,7 @@ impl<'a> Lexer<'a> {
"static" => Some(Token::Static),
"as" => Some(Token::As),
"sizeof" => Some(Token::SizeOf),
"struct" => Some(Token::Struct),
_ => None,
}
}
@@ -385,8 +390,126 @@ impl<'a> Lexer<'a> {
// ========================================================================
fn scan_number(&mut self) -> Token {
// Check if number is negative
let is_negative = self.current == Some('-');
if is_negative {
self.advance(); // consume '-'
}
match self.read_number() {
Ok(num) => Token::Integer(num),
Ok((value, type_suffix)) => {
// Validate and construct appropriate token
if let Some(type_id) = type_suffix {
match type_id {
TypeId::I8 => {
let signed_val = if is_negative {
-(value as i32)
} else {
value as i32
};
if signed_val < i8::MIN as i32 || signed_val > i8::MAX as i32
{
self.error(&format!(
"Value {} out of range for i8",
signed_val
));
return Token::SignedInt(0, Some(TypeId::I8));
}
Token::SignedInt(signed_val, Some(TypeId::I8))
}
TypeId::I16 => {
let signed_val = if is_negative {
-(value as i32)
} else {
value as i32
};
if signed_val < i16::MIN as i32
|| signed_val > i16::MAX as i32
{
self.error(&format!(
"Value {} out of range for i16",
signed_val
));
return Token::SignedInt(0, Some(TypeId::I16));
}
Token::SignedInt(signed_val, Some(TypeId::I16))
}
TypeId::I32 => {
let signed_val = if is_negative {
if value > i32::MAX as u64 + 1 {
self.error(&format!(
"Value -{} out of range for i32",
value
));
return Token::SignedInt(0, Some(TypeId::I32));
}
-(value as i32)
} else {
if value > i32::MAX as u64 {
self.error(&format!(
"Value {} out of range for i32",
value
));
return Token::SignedInt(0, Some(TypeId::I32));
}
value as i32
};
Token::SignedInt(signed_val, Some(TypeId::I32))
}
TypeId::U8 => {
if is_negative {
self.error("Unsigned type u8 cannot be negative");
return Token::UnsignedInt(0, Some(TypeId::U8));
}
if value > u8::MAX as u64 {
self.error(&format!(
"Value {} out of range for u8",
value
));
return Token::UnsignedInt(0, Some(TypeId::U8));
}
Token::UnsignedInt(value as u32, Some(TypeId::U8))
}
TypeId::U16 => {
if is_negative {
self.error("Unsigned type u16 cannot be negative");
return Token::UnsignedInt(0, Some(TypeId::U16));
}
if value > u16::MAX as u64 {
self.error(&format!(
"Value {} out of range for u16",
value
));
return Token::UnsignedInt(0, Some(TypeId::U16));
}
Token::UnsignedInt(value as u32, Some(TypeId::U16))
}
TypeId::U32 => {
if is_negative {
self.error("Unsigned type u32 cannot be negative");
return Token::UnsignedInt(0, Some(TypeId::U32));
}
if value > u32::MAX as u64 {
self.error(&format!(
"Value {} out of range for u32",
value
));
return Token::UnsignedInt(0, Some(TypeId::U32));
}
Token::UnsignedInt(value as u32, Some(TypeId::U32))
}
_ => unreachable!(),
}
} else {
// No type suffix - decide based on sign
if is_negative {
let signed_val = -(value as i32);
Token::SignedInt(signed_val, None)
} else {
Token::UnsignedInt(value as u32, None)
}
}
}
Err(e) => {
self.error(&e);
// Skip the invalid number
@@ -396,31 +519,66 @@ impl<'a> Lexer<'a> {
}
self.advance();
}
Token::Integer(0)
Token::SignedInt(0, None)
}
}
}
fn read_number(&mut self) -> Result<u64, String> {
fn read_number(&mut self) -> Result<(u64, Option<TypeId>), String> {
// Check for hex (0x) or binary (0b) prefix
if self.current == Some('0') {
match self.peek() {
Some('x') | Some('X') => {
self.advance(); // consume '0'
self.advance(); // consume 'x'
return self.read_hex_number();
let value = self.read_hex_number()?;
let type_suffix = self.read_type_suffix()?;
return Ok((value, type_suffix));
}
Some('b') | Some('B') => {
self.advance(); // consume '0'
self.advance(); // consume 'b'
return self.read_binary_number();
let value = self.read_binary_number()?;
let type_suffix = self.read_type_suffix()?;
return Ok((value, type_suffix));
}
_ => {}
}
}
// Read decimal number
self.read_decimal_number()
let value = self.read_decimal_number()?;
let type_suffix = self.read_type_suffix()?;
Ok((value, type_suffix))
}
fn read_type_suffix(&mut self) -> Result<Option<TypeId>, String> {
// Check for type suffix like _i32, _u8, etc.
if self.peek() == Some('_') {
self.advance(); // consume '_'
let mut suffix = String::new();
while let Some(c) = self.peek() {
if c.is_ascii_alphanumeric() {
self.advance();
suffix.push(c);
} else {
break;
}
}
match suffix.as_str() {
"i8" => Ok(Some(TypeId::I8)),
"i16" => Ok(Some(TypeId::I16)),
"i32" => Ok(Some(TypeId::I32)),
"u8" => Ok(Some(TypeId::U8)),
"u16" => Ok(Some(TypeId::U16)),
"u32" => Ok(Some(TypeId::U32)),
_ => Err(format!("Invalid type suffix: {}", suffix)),
}
} else {
Ok(None)
}
}
fn read_decimal_number(&mut self) -> Result<u64, String> {
@@ -434,8 +592,9 @@ impl<'a> Lexer<'a> {
if c.is_ascii_digit() {
self.advance();
num_str.push(c);
} else if c == '_' {
// Allow underscores as separators (like Rust)
} else if c == '_' && self.peek_second().is_some_and(|ch| ch.is_ascii_digit())
{
// Allow underscores as separators only between digits
self.advance();
} else {
break;
@@ -451,10 +610,11 @@ impl<'a> Lexer<'a> {
let mut num_str = String::new();
// Read the first hex digit (current character)
if let Some(c) = self.current {
if c.is_ascii_hexdigit() {
match self.current {
Some(c) if c.is_ascii_hexdigit() => {
num_str.push(c);
}
_ => (),
}
while let Some(c) = self.peek() {
@@ -480,10 +640,11 @@ impl<'a> Lexer<'a> {
let mut num_str = String::new();
// Read the first binary digit (current character)
if let Some(c) = self.current {
if c == '0' || c == '1' {
match self.current {
Some(c) if c == '0' || c == '1' => {
num_str.push(c);
}
_ => (),
}
while let Some(c) = self.peek() {
@@ -880,17 +1041,6 @@ mod tests {
}
}
#[test]
fn test_numbers() {
let input = "42 0x2A 0b101010 123_456";
let mut lexer = Lexer::new(input);
assert_eq!(lexer.next_token(), Token::Integer(42));
assert_eq!(lexer.next_token(), Token::Integer(42));
assert_eq!(lexer.next_token(), Token::Integer(42));
assert_eq!(lexer.next_token(), Token::Integer(123456));
}
#[test]
fn test_namespaced_identifier() {
let input = "print::println std::io::read";
+1 -1
View File
@@ -13,7 +13,7 @@ pub fn generate_ast(input: &str) -> Result<Program, CompilerError> {
let lexer = lexer::Lexer::new(&input);
let tokens = lexer.collect::<Vec<_>>();
// println!("{tokens:?}");
println!("{tokens:#?}");
log(&format!("Parsing {} Tokens...", tokens.len()));
+181 -43
View File
@@ -1,8 +1,8 @@
use super::lexer::Token;
use crate::model::{
AssignmentOperator, BinaryOperator, Block, Call, CompilerError, ConstExpr,
Declaration, Dependency, Expression, Program, Statement, TypeId, UnaryOperator,
Variable,
Declaration, Dependency, Expression, Number, Program, Statement, TypeId,
UnaryOperator, Variable,
};
use crate::{expect_tt, expect_value};
use std::ops::{ControlFlow, FromResidual, Try};
@@ -39,6 +39,10 @@ impl Parser {
return self.parse_func();
}
if expect_tt!(self.peek_next()?, Struct).accepted() {
return self.parse_struct();
}
if expect_tt!(self.peek_next()?, Include).accepted() {
// expect include keyword
let _ = self.next();
@@ -79,7 +83,8 @@ impl Parser {
let value = self.next()?;
let init = match value {
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(
value.tt().to_string(),
@@ -99,6 +104,28 @@ impl Parser {
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> {
// expect function keyword
let _ = expect_tt!(self.next()?, Fn);
@@ -318,18 +345,28 @@ impl Parser {
});
}
// handle assignment without "let"
let name = expect_value!(self.peek_next()?, Identifier);
if name.accepted() {
let varname = name?;
if expect_tt!(self.peek(1)?, LeftParen).accepted() {
let expr = self.parse_expression()?; // a function call expr
let _ = expect_tt!(self.next()?, Semicolon)?;
return ParseResult::Accept(Statement::Expression { expr });
}
// handle an in-place function call
if let ParseResult::Accept(name) = expect_value!(self.peek_next()?, Identifier)
&& let ParseResult::Accept(operator) = expect_tt!(
self.peek(1)?,
Assign,
PlusEqual,
MinusEqual,
StarEqual,
SlashEqual,
PercentEqual,
AndEqual,
OrEqual,
XorEqual,
ShlEqual,
ShrEqual
)
{
// consume name token
self.next()?;
let operator = match self.peek_next()? {
// pattern match to find operator
let operator = match operator {
Token::Assign => AssignmentOperator::Assign,
Token::PlusEqual => AssignmentOperator::AddAssign,
Token::MinusEqual => AssignmentOperator::SubAssign,
@@ -348,6 +385,7 @@ impl Parser {
}
};
// consume operator token
self.next()?;
let value = self.parse_expression()?;
@@ -355,15 +393,17 @@ impl Parser {
let _ = expect_tt!(self.next()?, Semicolon);
return ParseResult::Accept(Statement::Assign {
varname: varname.name,
varname: name.name,
operator,
value,
});
}
ParseResult::Reject(CompilerError::UnexpectedToken(
self.peek_next()?.tt().to_string(),
))
// parse an expression and a semicolon
let expr = self.parse_expression()?;
let _ = expect_tt!(self.next()?, Semicolon)?;
ParseResult::Accept(Statement::Expression { expr })
}
fn parse_expression(&mut self) -> ParseResult<Expression, CompilerError> {
@@ -374,7 +414,7 @@ impl Parser {
let left = self.parse_logical_and()?;
let op = match self.peek_next()? {
Token::Ampersand => BinaryOperator::LogicalOr,
Token::LogicalOr => BinaryOperator::LogicalOr,
_ => return ParseResult::Accept(left),
};
@@ -391,7 +431,7 @@ impl Parser {
let left = self.parse_bitwise_or()?;
let op = match self.peek_next()? {
Token::Ampersand => BinaryOperator::LogicalAnd,
Token::LogicalAnd => BinaryOperator::LogicalAnd,
_ => return ParseResult::Accept(left),
};
@@ -408,7 +448,7 @@ impl Parser {
let left = self.parse_bitwise_xor()?;
let op = match self.peek_next()? {
Token::Ampersand => BinaryOperator::BitwiseOr,
Token::Pipe => BinaryOperator::BitwiseOr,
_ => return ParseResult::Accept(left),
};
@@ -425,7 +465,7 @@ impl Parser {
let left = self.parse_bitwise_and()?;
let op = match self.peek_next()? {
Token::Ampersand => BinaryOperator::BitwiseXor,
Token::Caret => BinaryOperator::BitwiseXor,
_ => return ParseResult::Accept(left),
};
@@ -660,12 +700,13 @@ impl Parser {
fn parse_primary(&mut self) -> ParseResult<Expression, CompilerError> {
match self.peek_next()? {
Token::Integer(value) => {
Token::UnsignedInt(value, type_id) => {
self.next()?;
ParseResult::Accept(Expression::Number {
value: value as isize,
type_id: None,
})
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()?;
@@ -678,9 +719,39 @@ impl Parser {
Token::Identifier(name) => {
self.next()?;
ParseResult::Accept(Expression::Variable {
// 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,
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,
expr_type: None,
fields,
type_id: None,
})
}
Token::LeftBracket => {
@@ -729,21 +800,88 @@ impl Parser {
}
fn parse_type(&mut self) -> ParseResult<TypeId, CompilerError> {
// get the type name incl namespace
let typename = expect_value!(self.next()?, Identifier)?;
println!("yes {:?}", self.peek_next()?);
match typename.name.as_str() {
"u32" => ParseResult::Accept(TypeId::U32),
"u16" => ParseResult::Accept(TypeId::U16),
"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 primitive or named type
if expect_tt!(self.peek_next()?, Identifier).accepted() {
return self.parse_type_identifier();
}
// 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> {
@@ -840,7 +978,7 @@ macro_rules! expect_value {
($expr:expr, $variant:ident) => {{
let tok = $expr;
match tok.clone() {
Token::$variant(value) => ParseResult::Accept(value),
Token::$variant(first, ..) => ParseResult::Accept(first),
_ => {
ParseResult::Reject(CompilerError::UnexpectedToken(tok.tt().to_string()))
}
-2
View File
@@ -1,7 +1,5 @@
use std::path::Path;
use compiler;
fn main() {
// read from input file: syntax "c_compiler <src.c> [output.dsa]"
let args: Vec<String> = std::env::args().collect();
+104 -21
View File
@@ -19,6 +19,14 @@ pub struct Name {
pub name: 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)]
pub struct Program {
@@ -40,6 +48,10 @@ pub enum Declaration {
is_const: bool,
},
Dependency(Dependency),
Struct {
name: Name,
fields: Vec<Variable>,
},
}
#[derive(Debug, Clone)]
@@ -62,8 +74,20 @@ pub enum TypeId {
Void,
Ptr(Box<TypeId>),
Ref(Box<TypeId>),
Array(Box<TypeId>, usize),
Struct { name: Name, fields: Vec<TypeId> },
Tuple(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 {
@@ -80,7 +104,10 @@ impl TypeId {
Self::Void => 0,
Self::Ptr(t) => t.size(),
Self::Ref(t) => t.size(),
Self::Array(t, size) => t.size() * 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(),
}
}
@@ -100,14 +127,47 @@ impl fmt::Display for TypeId {
Self::Void => write!(f, "void"),
Self::Ptr(t) => write!(f, "*{}", t),
Self::Ref(t) => write!(f, "&{}", t),
Self::Array(t, len) => write!(f, "[{}; {}]", t, len),
Self::Struct { name, fields } => {
write!(f, "struct {} {{", name)?;
for (i, field) in fields.iter().enumerate() {
write!(f, "{}: {}", i, field)?;
}
write!(f, "}}")
Self::Tuple(elems) => write!(
f,
"({})",
elems
.iter()
.map(|t| t.to_string())
.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(", ")
),
}
}
}
@@ -229,18 +289,24 @@ pub enum Expression {
// Post-Semantic Analysis
type_id: Option<TypeId>,
},
Number {
value: isize,
// Post-Semantic Analysis
type_id: Option<TypeId>,
},
Number(Number),
StringLiteral(String),
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)]
@@ -266,17 +332,20 @@ impl Expression {
expr.is_pure() && index.is_pure()
}
Expression::MemberAccess { expr, .. } => expr.is_pure(),
Expression::ArrayLiteral { elements, type_id } => {
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> {
match self {
Expression::Number { type_id, .. } => {
type_id.clone().ok_or(CompilerError::UnknownType)
}
Expression::Number(
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::CharLiteral(_) => Ok(TypeId::Char),
Expression::Call { type_id, .. } => {
@@ -304,7 +373,21 @@ impl Expression {
let element_type = elements
.first()
.map_or(TypeId::Void, |e| e.type_id().unwrap_or(TypeId::Void));
Ok(TypeId::Array(Box::new(element_type), elements.len()))
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(),
})
}
}
}
File diff suppressed because it is too large Load Diff
+21 -5
View File
@@ -1,10 +1,26 @@
# General TODO's
# Compiler optimisations!
# 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.
- [ ] [EASY] Add multiply and divide operations to code generation
- [ ] [MEDIUM] proper prefix/postfix inc/dec implementation. slightly more complex as we need to check for a variable and modify it in place
- [ ] [EASY] Investigate logical and operator not compiling - either a lexer or parser issue.
- [x] [MEDIUM] Get shift operations working correctly.
# 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" }
egui = "0.31.1"
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 }
serde = { version = "1.0.219", features = ["derive"], optional = true }
egui_file = "0.22.1"
rustc-hash = "2.1.1"
[features]
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 step = 0;
let mut addr;
let mut history = Vec::<(u32, Instruction)>::new();
let mut history = Vec::<(u32, u32)>::with_capacity(32768);
let size = 256;
let record_history = true;
state_tx
.send(StateUpdate::Running(Running::Paused))
.expect("Failed to send initial state!");
@@ -36,7 +38,9 @@ pub fn run_emulator(
let mut update = false;
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() {
Ok(cmd) => Some(cmd),
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 {
match cmd {
Command::Start => {
running = Running::Running;
step = 32768;
// Update RPC with current state. TODO: Make this only occur on state
// changes.
@@ -71,9 +80,11 @@ pub fn run_emulator(
}
Command::Stop => {
running = Running::Paused;
step = 0;
}
Command::Reset(x) => {
running = Running::Paused;
step = 0;
match x {
0 => {
@@ -95,20 +106,12 @@ pub fn run_emulator(
}
Command::Step(x) => {
step = x;
running = Running::Paused;
}
Command::Write(offset, data) => {
update = true;
processor
.memory
.write_range(offset, data)
.unwrap_or_else(|_| {
report_err(
state_tx,
"Failed to write memory range!",
&mut processor,
);
});
processor.memory.write_range(offset, data);
}
Command::Interrupt(_interrupt) => {
update = true;
@@ -118,14 +121,7 @@ pub fn run_emulator(
Command::MemRequest(new, size) if update => {
addr = new;
let _ = state_tx.send(StateUpdate::MemoryView(
processor.memory.read_range(addr, size).unwrap_or_else(|_| {
report_err(
state_tx,
"Failed to read memory range!",
&mut processor,
);
Vec::new()
}),
processor.memory.read_range(addr, size),
));
}
Command::DisplayRequest if update => {
@@ -163,50 +159,19 @@ pub fn run_emulator(
let _ = state_tx.send(StateUpdate::Instructions(instruction_count));
}
Command::WriteBlock(addr, block) => {
processor
.memory
.write_range(addr, block.to_vec())
.unwrap_or_else(|_| {
report_err(
state_tx,
"Failed to write memory block!",
&mut processor,
);
});
processor.memory.write_range(addr, block.to_vec());
}
_ => {}
}
}
if step > 0 {
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 {
step += 1;
}
if running == Running::Running {
if step > 0 {
step -= 1;
update = true;
// Execute one cycle.
@@ -227,9 +192,18 @@ pub fn run_emulator(
}
};
history.push(instruction);
if matches!(instruction.1, Instruction::Halt) {
if record_history {
history.push((
instruction.0,
processor
.get(Register::Cir)
.expect("CIR should never be invalid"),
));
}
if matches!(instruction, (_, Instruction::Halt)) {
running = Running::Halted;
step = 0;
}
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;
pub trait MemoryUnit: Send + Sync {
fn reset(&mut self);
fn read_byte(&mut self, addr: u32) -> Result<u8, ProcessorError>;
fn write_byte(&mut self, addr: u32, value: u8) -> Result<(), ProcessorError>;
fn read_byte(&mut self, addr: u32) -> u8;
fn write_byte(&mut self, addr: u32, value: u8);
fn read_word(&mut self, addr: u32) -> Result<u32, 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);
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() {
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> {
let mut data = [0; 256];
for (i, byte) in data.iter_mut().enumerate() {
*byte = self.read_byte(addr + i as u32)?;
}
Ok(data)
}
fn read_block(&mut self, addr: u32) -> &[u8; 256];
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() {
self.write_byte(addr + i as u32, *byte)?;
self.write_byte(addr + i as u32, *byte);
}
Ok(())
}
}
pub struct MainStore {
pub data: HashMap<u32, Block>,
pub data: FxHashMap<u32, Block>,
}
pub struct Block {
data: [u8; 256],
}
pub type Block = [u8; 256];
impl Default for MainStore {
fn default() -> Self {
@@ -58,113 +48,110 @@ impl MainStore {
#[must_use]
pub fn new() -> Self {
Self {
data: HashMap::new(),
data: FxHashMap::default(),
}
}
#[inline]
const fn segment_addr(addr: u32) -> (u32, u8) {
(addr / 256, (addr % 256) as u8)
}
#[inline]
fn mut_block(&mut self, addr: u32) -> &mut Block {
self.data
.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,
)
self.data.entry(addr).or_insert([0; 256])
}
#[inline]
fn block(&mut self, addr: u32) -> &Block {
self.data
.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,
)
self.data.entry(addr).or_insert([0; 256])
}
}
impl MemoryUnit for MainStore {
#[inline]
fn reset(&mut self) {
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 = 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> {
if addr % 4 != 0 {
if !addr.is_multiple_of(4) {
return Err(ProcessorError::BadMemoryAccess(addr));
}
let (block_addr, offset) = Self::segment_addr(addr);
let block = self.mut_block(block_addr);
let mut bytes = [0; 4];
bytes[0] = block.data[offset as usize];
bytes[1] = block.data[(offset + 1) as usize];
bytes[2] = block.data[(offset + 2) as usize];
bytes[3] = block.data[(offset + 3) as usize];
Ok(u32::from_be_bytes(bytes))
let offset = offset as usize;
let block = self.block(block_addr);
Ok(u32::from_be_bytes(
block[offset..=offset + 3]
.try_into()
.expect("Failed to read word!"),
))
}
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);
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 = self.mut_block(block_addr);
block.data[offset as usize] = value;
Ok(())
block[offset as usize] = value;
}
#[inline]
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));
}
let (block_addr, offset) = Self::segment_addr(addr);
let block = self.mut_block(block_addr);
block.data[offset as usize] = (value >> 24) as u8;
block.data[(offset + 1) as usize] = (value >> 16) as u8;
block.data[(offset + 2) as usize] = (value >> 8) as u8;
block.data[(offset + 3) as usize] = value as u8;
block[offset as usize..=(offset + 3) as usize]
.copy_from_slice(&value.to_be_bytes());
Ok(())
}
fn write_range(&mut self, addr: u32, value: Vec<u8>) -> Result<(), ProcessorError> {
for (i, byte) in value.into_iter().enumerate() {
let (block_addr, offset) = Self::segment_addr(addr + i as u32);
let block = self.mut_block(block_addr);
block.data[offset as usize] = byte;
#[inline]
fn write_range(&mut self, addr: u32, value: Vec<u8>) {
let mut current_block_addr = addr / 256;
let mut current_block = self.mut_block(current_block_addr);
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 = self.block(block_addr);
Ok(block.data)
self.block(block_addr)
}
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 = self.mut_block(block_addr);
block.data = data;
Ok(())
let _ = self.data.insert(block_addr, *data);
}
}
+1
View File
@@ -1,3 +1,4 @@
pub mod cache;
pub mod emulator;
pub mod memory;
pub mod model;
+2 -3
View File
@@ -78,7 +78,7 @@ pub struct State {
pub error_log: Vec<String>,
pub instruction_history: Vec<(u32, Instruction)>,
pub instruction_history: Vec<(u32, u32)>,
}
impl State {
@@ -154,7 +154,7 @@ pub enum StateUpdate {
MemoryView(Vec<u8>),
DisplayView(Vec<u8>),
Error(String),
InstructionHistory(Vec<(u32, Instruction)>),
InstructionHistory(Vec<(u32, u32)>),
}
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
@@ -290,7 +290,6 @@ impl RegFile {
})
}
#[must_use]
pub const fn get(&self, reg: Register) -> Result<u32, ProcessorError> {
Ok(match reg {
Register::Rg0 => self.rg0,
+33 -20
View File
@@ -4,6 +4,7 @@ use std::{
};
use crate::emulator::system::{
cache::Cache,
memory::MemoryUnit,
model::{IODevice, ProcessorError, RegFile},
};
@@ -17,10 +18,7 @@ pub struct Processor {
pub io_devices: Vec<Arc<dyn IODevice>>,
pub void: u32,
}
fn log(message: &str) {
println!("\x1b[32mINFO:\x1b[0m {message}");
pub cache: Cache,
}
impl Processor {
@@ -32,6 +30,7 @@ impl Processor {
halted: false,
io_devices,
void: 0,
cache: Cache::new(),
}
}
@@ -51,21 +50,35 @@ impl Processor {
// Get value from PCX.
let addr = self.fetch()?;
// Increment PCX.
self.advance();
self.advance()?;
// Set MAR to the previous value of PCX.
*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].
*self.reg(Register::Mar)? = val;
*self.reg(Register::Cir)? = encoded;
// Decode and execute the instruction.
let instruction = Instruction::decode(val)
.map_err(|_| ProcessorError::InvalidInstruction(val))?;
let decoded = if let Some(val) = self.cache.lookup_instruction(addr) {
val
} else {
let decoded = Instruction::decode(encoded)
.map_err(|_| ProcessorError::InvalidInstruction(encoded))?;
self.cache.insert(addr, decoded);
decoded
};
instruction.execute(self)?;
Ok((addr, instruction))
decoded.execute(self)?;
Ok((addr, decoded))
}
const fn fetch(&self) -> Result<u32, ProcessorError> {
@@ -84,7 +97,7 @@ impl Processor {
}
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) {
@@ -163,10 +176,10 @@ impl Processor {
let addr = self.get(Register::Spr)?;
let size = n * 4;
// returns the stack
self.memory.read_range(
Ok(self.memory.read_range(
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
)
))
}
}
@@ -209,7 +222,7 @@ impl Executable for Instruction {
Self::LoadByte(a) => {
*cpu.reg(a.r2)? = u32::from(
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) => {
*cpu.reg(a.r2)? = sign_extend(u32::from(
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.get(a.r2)? + u32::from(a.immediate),
cpu.get(a.r1)? as u8,
)?;
);
}
// 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
let bytes = (cpu.get(a.r1)? as u16).to_le_bytes();
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
.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
@@ -81,9 +81,7 @@ fn test_mov_signed_instruction() {
fn test_load_byte_instruction() {
let mut cpu = create_test_processor();
let addr = 0x100;
cpu.memory
.write_byte(addr, 0xAB)
.expect("Failed to write byte to memory");
cpu.memory.write_byte(addr, 0xAB);
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr - 4;
let load_byte_instr = Instruction::LoadByte(ITypeArgs::new(
@@ -105,9 +103,7 @@ fn test_load_byte_instruction() {
fn test_load_byte_signed_instruction() {
let mut cpu = create_test_processor();
let addr = 0x100;
cpu.memory
.write_byte(addr, 0xFF)
.expect("Failed to write byte to memory");
cpu.memory.write_byte(addr, 0xFF);
*cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr;
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(
"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]
@@ -468,7 +464,7 @@ fn test_shift_left_with_shamt() {
let shl_instr = Instruction::ShiftLeft(RTypeArgs::new(
Some(Register::Rg1),
Some(Register::Zero),
None,
Some(Register::Rg1),
Some(2),
));
@@ -489,7 +485,7 @@ fn test_shift_right_with_shamt() {
let shr_instr = Instruction::ShiftRight(RTypeArgs::new(
Some(Register::Rg1),
Some(Register::Zero),
None,
Some(Register::Rg1),
Some(2),
));
+6 -14
View File
@@ -117,10 +117,7 @@ impl Editor {
.file_name()
.unwrap_or_else(|| OsStr::new("Unnamed!"))
.to_str()
.map_or_else(
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
);
.unwrap_or_else(|| unreachable!("File name should be valid UTF-8."));
}
"Unnamed!"
}
@@ -129,12 +126,9 @@ impl Editor {
if let Some(path) = &self.path {
return path
.extension()
.map_or_else(|| OsStr::new("Unknown!"), |ext| ext)
.unwrap_or_else(|| OsStr::new("Unknown!"))
.to_str()
.map_or_else(
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
);
.unwrap_or_else(|| unreachable!("File name should be valid UTF-8."));
}
"Unknown!"
}
@@ -398,7 +392,7 @@ impl Editor {
_ => Syntax::default(),
};
let ed = CodeEditor::default()
let mut editor = CodeEditor::default()
.id_source("editor")
.with_fontsize(12.0)
.with_rows(0)
@@ -407,8 +401,6 @@ impl Editor {
.with_numlines(true)
.desired_width(available_width - 500.0);
let mut editor = ed.clone();
editor.show(ui, &mut self.text);
}
@@ -451,7 +443,7 @@ impl Editor {
Some("dsc") => {
let output_path = Path::new(path).with_extension("dsa");
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();
@@ -461,7 +453,7 @@ impl Editor {
let instructions = match compiler.wait_for_result() {
Ok(instructions) => instructions,
Err(e) => {
self.error = Some(format!("Assembler error: {}", e));
self.error = Some(format!("Assembler error: {e}"));
return;
}
};
+5 -1
View File
@@ -1,3 +1,4 @@
use common::prelude::Instruction;
use egui::{Context, Ui};
use crate::emulator::{
@@ -57,8 +58,11 @@ impl Component for History {
.color(egui::Color32::from_rgb(255, 200, 200)),
);
let decoded = Instruction::decode(instruction.1)
.unwrap_or(Instruction::Nop);
ui.label(
egui::RichText::new(instruction.1.to_string())
egui::RichText::new(decoded.to_string())
.font(egui::FontId::monospace(12.0))
.color(egui::Color32::from_rgb(200, 255, 200)),
);
+1 -4
View File
@@ -79,10 +79,7 @@ impl Loader {
.file_name()
.unwrap_or_else(|| OsStr::new("Unnamed!"))
.to_str()
.map_or_else(
|| unreachable!("File name should be valid UTF-8."),
|ext| ext,
);
.unwrap_or_else(|| unreachable!("File name should be valid UTF-8."));
}
"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
pop zero
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
// Generated at 2026-02-04 01:44:06
dw large_num: 0x333333 // 333,333 instructions
start:
ldw large_num, rg0
// Imports
include print: "./lib/io/print.dsa"
include fib: "./lib/maths/fib.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 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
// run approx 1m instructions
loop:
dec rg0
cmp rg0, zero
jgt loop
hlt
-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],
}
+2 -2
View File
@@ -1,4 +1,4 @@
include print "./lib/io/print.dsa"
include print: "./lib/io/print.dsa"
dw idt: 0xFFFF0000
dw stack: 0x10000
@@ -57,7 +57,7 @@ start:
// test reset cursor pos
call print::reset
// test print string at reset pos
lwi replace, rg0
push rg0