rewrote assembler backend to support custom executable format (DSE) and also refactored (moving crates around)

This commit is contained in:
2026-07-16 00:34:13 +01:00
parent 7a84073bc4
commit a4d42bdad6
60 changed files with 1486 additions and 1669 deletions
+7 -3
View File
@@ -7,9 +7,13 @@
<sourceFolder url="file://$MODULE_DIR$/dsa/compiler/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/dsa/compiler/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/dsa/linker/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/dsa/linker/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/dsa/assembler/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/dsa/assembler/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/dsa/emulator/backend/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/dsa/disassembler/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/dsa/emulator/frontend/benches" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/dsa/emulator/emulator_core/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/dsa/emulator/frontend/src" isTestSource="false" /> <sourceFolder url="file://$MODULE_DIR$/dsa/emulator/emulator_ui/benches" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/dsa/emulator/emulator_ui/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/dsx/dsx_client/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/dsx/dsx_common/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/dsx/dsx_server/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" /> <excludeFolder url="file://$MODULE_DIR$/target" />
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
+1 -1
View File
@@ -1,5 +1,5 @@
[workspace] [workspace]
members = ["dsa/common", "dsa/assembler", "dsa/emulator/frontend", "dsa/linker", "dsa/compiler", "dsa/emulator/backend"] members = ["dsa/common", "dsa/assembler", "dsa/emulator/emulator_ui", "dsa/linker", "dsa/compiler", "dsa/emulator/emulator_core", "dsx/dsx_common", "dsx/dsx_server", "dsx/dsx_client", "dsa/disassembler"]
resolver = "3" resolver = "3"
[workspace.package] [workspace.package]
+1
View File
@@ -13,6 +13,7 @@ name = "assembler"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
binrw = "0.15.1"
clap = { version = "4.6.0", features = ["derive"] } clap = { version = "4.6.0", features = ["derive"] }
common = { path = "../common" } common = { path = "../common" }
+15 -15
View File
@@ -78,7 +78,7 @@ fn build_instruction(node: &Node) -> Result<Instruction, AssembleError> {
fn ins_mov( fn ins_mov(
opcode: Opcode, opcode: Opcode,
args: &[crate::assembler::model::Token], args: &[Token],
) -> Result<Instruction, AssembleError> { ) -> Result<Instruction, AssembleError> {
let Some(src_token) = args.first() else { let Some(src_token) = args.first() else {
return Err(AssembleError::MissingArgument(0)); return Err(AssembleError::MissingArgument(0));
@@ -98,7 +98,7 @@ fn ins_mov(
fn ins_cmov( fn ins_cmov(
opcode: Opcode, opcode: Opcode,
args: &[crate::assembler::model::Token], args: &[Token],
) -> Result<Instruction, AssembleError> { ) -> Result<Instruction, AssembleError> {
let Some(src_token) = args.first() else { let Some(src_token) = args.first() else {
return Err(AssembleError::MissingArgument(0)); return Err(AssembleError::MissingArgument(0));
@@ -122,7 +122,7 @@ fn ins_cmov(
fn ins_ldx_stx( fn ins_ldx_stx(
opcode: Opcode, opcode: Opcode,
args: &[crate::assembler::model::Token], args: &[Token],
) -> Result<Instruction, AssembleError> { ) -> Result<Instruction, AssembleError> {
let Some(src_token) = args.first() else { let Some(src_token) = args.first() else {
return Err(AssembleError::MissingArgument(0)); return Err(AssembleError::MissingArgument(0));
@@ -153,7 +153,7 @@ fn ins_ldx_stx(
fn ins_stack( fn ins_stack(
opcode: Opcode, opcode: Opcode,
args: &[crate::assembler::model::Token], args: &[Token],
) -> Result<Instruction, AssembleError> { ) -> Result<Instruction, AssembleError> {
let Some(reg_token) = args.first() else { let Some(reg_token) = args.first() else {
return Err(AssembleError::MissingArgument(0)); return Err(AssembleError::MissingArgument(0));
@@ -169,7 +169,7 @@ fn ins_stack(
fn ins_load_imm( fn ins_load_imm(
opcode: Opcode, opcode: Opcode,
args: &[crate::assembler::model::Token], args: &[Token],
) -> Result<Instruction, AssembleError> { ) -> Result<Instruction, AssembleError> {
let Some(value_token) = args.first() else { let Some(value_token) = args.first() else {
return Err(AssembleError::MissingArgument(0)); return Err(AssembleError::MissingArgument(0));
@@ -190,7 +190,7 @@ fn ins_load_imm(
fn ins_jump_unconditional( fn ins_jump_unconditional(
opcode: Opcode, opcode: Opcode,
args: &[crate::assembler::model::Token], args: &[Token],
) -> Result<Instruction, AssembleError> { ) -> Result<Instruction, AssembleError> {
let Some(addr_imm_token) = args.first() else { let Some(addr_imm_token) = args.first() else {
return Err(AssembleError::MissingArgument(0)); return Err(AssembleError::MissingArgument(0));
@@ -212,7 +212,7 @@ fn ins_jump_unconditional(
fn ins_jump_conditional( fn ins_jump_conditional(
opcode: Opcode, opcode: Opcode,
args: &[crate::assembler::model::Token], args: &[Token],
) -> Result<Instruction, AssembleError> { ) -> Result<Instruction, AssembleError> {
let Some(condition_token) = args.first() else { let Some(condition_token) = args.first() else {
return Err(AssembleError::MissingArgument(0)); return Err(AssembleError::MissingArgument(0));
@@ -239,7 +239,7 @@ fn ins_jump_conditional(
fn ins_comparison( fn ins_comparison(
opcode: Opcode, opcode: Opcode,
args: &[crate::assembler::model::Token], args: &[Token],
) -> Result<Instruction, AssembleError> { ) -> Result<Instruction, AssembleError> {
let Some(left_token) = args.first() else { let Some(left_token) = args.first() else {
return Err(AssembleError::MissingArgument(0)); return Err(AssembleError::MissingArgument(0));
@@ -267,7 +267,7 @@ fn ins_comparison(
fn ins_bitshift( fn ins_bitshift(
opcode: Opcode, opcode: Opcode,
args: &[crate::assembler::model::Token], args: &[Token],
) -> Result<Instruction, AssembleError> { ) -> Result<Instruction, AssembleError> {
let Some(src_reg) = args.first() else { let Some(src_reg) = args.first() else {
return Err(AssembleError::MissingArgument(0)); return Err(AssembleError::MissingArgument(0));
@@ -296,7 +296,7 @@ fn ins_bitshift(
fn ins_arithmetic( fn ins_arithmetic(
opcode: Opcode, opcode: Opcode,
args: &[crate::assembler::model::Token], args: &[Token],
) -> Result<Instruction, AssembleError> { ) -> Result<Instruction, AssembleError> {
let Some(left_token) = args.first() else { let Some(left_token) = args.first() else {
return Err(AssembleError::MissingArgument(0)); return Err(AssembleError::MissingArgument(0));
@@ -327,7 +327,7 @@ fn ins_arithmetic(
fn ins_imm_arithmetic( fn ins_imm_arithmetic(
opcode: Opcode, opcode: Opcode,
args: &[crate::assembler::model::Token], args: &[Token],
) -> Result<Instruction, AssembleError> { ) -> Result<Instruction, AssembleError> {
let Some(reg_token) = args.first() else { let Some(reg_token) = args.first() else {
return Err(AssembleError::MissingArgument(0)); return Err(AssembleError::MissingArgument(0));
@@ -350,7 +350,7 @@ fn ins_imm_arithmetic(
}) })
} }
fn ins_not(args: &[crate::assembler::model::Token]) -> Result<Instruction, AssembleError> { fn ins_not(args: &[Token]) -> Result<Instruction, AssembleError> {
let Some(reg_token) = args.first() else { let Some(reg_token) = args.first() else {
return Err(AssembleError::MissingArgument(0)); return Err(AssembleError::MissingArgument(0));
}; };
@@ -363,7 +363,7 @@ fn ins_not(args: &[crate::assembler::model::Token]) -> Result<Instruction, Assem
Ok(Instruction::not(src, dest)) Ok(Instruction::not(src, dest))
} }
fn ins_interrupt(args: &[crate::assembler::model::Token]) -> Result<Instruction, AssembleError> { fn ins_interrupt(args: &[Token]) -> Result<Instruction, AssembleError> {
let Some(code_token) = args.first() else { let Some(code_token) = args.first() else {
return Err(AssembleError::MissingArgument(0)); return Err(AssembleError::MissingArgument(0));
}; };
@@ -373,7 +373,7 @@ fn ins_interrupt(args: &[crate::assembler::model::Token]) -> Result<Instruction,
} }
fn build_data_instruction( fn build_data_instruction(
args: &[crate::assembler::model::Token], args: &[Token],
) -> Result<Instruction, AssembleError> { ) -> Result<Instruction, AssembleError> {
let Some(immediate_token) = args.first() else { let Some(immediate_token) = args.first() else {
return Err(AssembleError::MissingArgument(0)); return Err(AssembleError::MissingArgument(0));
@@ -384,7 +384,7 @@ fn build_data_instruction(
} }
fn build_segment_instruction( fn build_segment_instruction(
args: &[crate::assembler::model::Token], args: &[Token],
) -> Result<Instruction, AssembleError> { ) -> Result<Instruction, AssembleError> {
let Some(immediate_token) = args.first() else { let Some(immediate_token) = args.first() else {
return Err(AssembleError::MissingArgument(0)); return Err(AssembleError::MissingArgument(0));
+121 -28
View File
@@ -11,8 +11,11 @@ use std::{
}, },
thread, thread,
}; };
use std::collections::HashMap;
use std::io::Cursor;
use common::formats::binary_dse::{DseExecutable, SymbolEntry};
use common::prelude::Instruction; use common::prelude::Instruction;
use crate::assembler::resolver2::{resolve_address, resolve_dependencies, resolve_symbols, split_sections, SectionId};
// Module declarations // Module declarations
#[macro_use] #[macro_use]
@@ -21,12 +24,14 @@ pub mod macros;
#[allow(clippy::module_inception)] #[allow(clippy::module_inception)]
pub mod codegen; pub mod codegen;
pub mod expand; pub mod expand;
pub mod lexer;
pub mod model;
pub mod parser;
pub mod resolver;
use crate::assemblerv2::lexer::Lexer; mod lexer;
mod model;
pub mod parser;
// pub mod resolver;
pub mod resolver2;
// Re-exports // Re-exports
pub use self::{ pub use self::{
@@ -35,7 +40,7 @@ pub use self::{
lexer::lexer, lexer::lexer,
model::{Module, Node, Opcode, Symbol, Token, TokenType}, model::{Module, Node, Opcode, Symbol, Token, TokenType},
parser::{Parser, Program}, parser::{Parser, Program},
resolver::{create_sections, resolve_dependencies, resolve_symbols}, // resolver::{create_sections, resolve_dependencies, resolve_symbols},
}; };
pub struct Assembler { pub struct Assembler {
@@ -67,23 +72,33 @@ impl Assembler {
let tx = self.result_tx.clone(); let tx = self.result_tx.clone();
thread::spawn(move || match assemble(&src) { thread::spawn(move || match assemble(&src) {
Ok(res) => { Ok(exe) => {
let buffer: Vec<u8> = res let mut buffer = Cursor::new(Vec::new());
.iter() match exe.write(&mut buffer) {
.flat_map(|instruction| instruction.to_le_bytes()) Ok(()) => {
.collect(); tx.send(Ok(buffer.into_inner()))
tx.send(Ok(buffer))
.expect("Failed to send compilation result from worker thread"); .expect("Failed to send compilation result from worker thread");
} }
Err(err) => { Err(e) => {
tx.send(Err(err)) tx.send(Err(AssembleError::Generic))
.expect("Failed to send compilation error from worker thread"); .expect("...");
} }
}
}
Err(err) => { /* unchanged */ }
}); });
self.is_running = true; self.is_running = true;
} }
/// Polls the result channel to check if a compilation result is ready.
///
/// This method attempts a non-blocking read (`try_recv`) from the result receiver.
/// It returns `Some(Ok(Vec<u8>))` if the compilation successfully finished and
/// the binary output is available, or `None` if no result was received yet.
///
/// If it receives an error via `mpsc::TryRecvError::Disconnected`, it indicates that
/// the worker thread terminated unexpectedly, returning a specific error string.
pub fn poll(&mut self) -> Option<Result<Vec<u8>, String>> { pub fn poll(&mut self) -> Option<Result<Vec<u8>, String>> {
if !self.is_running { if !self.is_running {
return None; return None;
@@ -130,36 +145,114 @@ impl Assembler {
impl Assembler {} impl Assembler {}
fn assemble(src: &Path) -> Result<Vec<Instruction>, AssembleError> { // fn assemble(src: &Path) -> Result<Vec<Instruction>, AssembleError> {
// let mut modules = HashSet::new();
// let mut program = Program::new();
//
// let hash = quick_hash(src);
//
// if modules.contains(&hash) {
// return Ok(vec![]);
// }
//
// prepare_dependency(src, &mut modules, &mut program)?;
//
// let mut nodes = program.nodes.clone();
//
// println!("PRE LINK");
// for node in &nodes {
// println!("{:?}", node);
// }
//
// create_sections(&mut nodes)?;
// resolve_symbols(&mut nodes)?;
//
// println!("Generating assembly output...");
//
// let instructions = codegen(nodes)?;
//
// println!("Compilation Successful");
// Ok(instructions)
// }
fn assemble(src: &Path) -> Result<DseExecutable, AssembleError> {
let mut modules = HashSet::new(); let mut modules = HashSet::new();
let mut program = Program::new(); let mut program = Program::new();
let hash = quick_hash(src);
if modules.contains(&hash) {
return Ok(vec![]);
}
prepare_dependency(src, &mut modules, &mut program)?; prepare_dependency(src, &mut modules, &mut program)?;
let mut nodes = program.nodes.clone(); let nodes = program.nodes.clone();
println!("PRE LINK"); println!("PRE LINK");
for node in &nodes { for node in &nodes {
println!("{:?}", node); println!("{:?}", node);
} }
create_sections(&mut nodes)?; let (data_nodes, mut text_nodes) = split_sections(nodes);
resolve_symbols(&mut nodes)?; let symbol_table = resolve_symbols(&data_nodes, &mut text_nodes)?;
let text_len = text_nodes.len() as u32;
println!("Generating assembly output..."); println!("Generating assembly output...");
let instructions = codegen(text_nodes)?;
let text: Vec<u32> = instructions
.iter()
.map(|i| i.0).collect();
let instructions = codegen(nodes)?; let data = build_data_bytes(&data_nodes)?;
// Build string table + SymbolEntry list from the resolved symbol table
let mut string_table = Vec::new();
let mut symbols = Vec::new();
for (symbol, entry) in &symbol_table {
let name_offset = string_table.len() as u32;
string_table.extend_from_slice(symbol.name.as_bytes());
string_table.push(0); // null terminator
symbols.push(SymbolEntry {
name_offset,
value: resolve_address(*entry, text_len),
section: match entry.1 {
SectionId::Text => 2,
SectionId::Data => 1,
},
binding: 1, // global — refine later if adding local/weak
_pad: 0,
});
}
// Entry point: first symbol to jump to is '_init'
let entry_point = symbol_table
.iter()
.find(|(sym, _)| sym.name == "_init")
.map(|(_, entry)| resolve_address(*entry, text_len))
.ok_or(AssembleError::UndefinedSymbol(Symbol {
name: "_init".to_string(),
module: Module::Resolved(0),
}))?;
println!("Compilation Successful"); println!("Compilation Successful");
Ok(instructions)
Ok(DseExecutable::new(
symbols.as_slice(),
&string_table,
&data,
&text,
entry_point,
).unwrap())
} }
fn build_data_bytes(data_nodes: &[Node]) -> Result<Vec<u8>, AssembleError> {
let mut bytes = Vec::with_capacity(data_nodes.len() * 4);
for node in data_nodes {
let Token::Immediate(value) = node.arg(0)? else {
return Err(AssembleError::InvalidArg);
};
bytes.extend_from_slice(&value.to_le_bytes());
}
Ok(bytes)
}
fn prepare_dependency( fn prepare_dependency(
path: &Path, path: &Path,
modules: &mut HashSet<u64>, modules: &mut HashSet<u64>,
+168
View File
@@ -0,0 +1,168 @@
use std::collections::HashMap;
use std::path::Path;
use crate::assembler::model::{Module, Node, Opcode, Symbol, Token};
use crate::assembler::{quick_hash, AssembleError};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SectionId {
Data,
Text,
}
/// Splits a flat (possibly multi-module) node list into separate data and
/// text streams. Segment/Include markers are dropped here — module
/// disambiguation lives on `Symbol.module`, not node position, so we don't
/// need them past this point.
pub fn split_sections(nodes: Vec<Node>) -> (Vec<Node>, Vec<Node>) {
let mut data = Vec::new();
let mut text = Vec::new();
for node in nodes {
match node.opcode() {
Opcode::Data => data.push(node),
Opcode::Segment | Opcode::Include => {}
_ => text.push(node),
}
}
(data, text)
}
pub fn generate_symbol_table(
data_nodes: &[Node],
text_nodes: &[Node],
) -> HashMap<Symbol, (u32, SectionId)> {
let mut table = HashMap::new();
for (i, node) in data_nodes.iter().enumerate() {
if let Some(symbol) = node.label() {
table.insert(symbol, (4 * i as u32, SectionId::Data));
}
}
for (i, node) in text_nodes.iter().enumerate() {
if let Some(symbol) = node.label() {
table.insert(symbol, (4 * i as u32, SectionId::Text));
}
}
table
}
/// v1 loader convention: TEXT is placed at address 0, DATA immediately
/// follows it. This is a stopgap until real relocations exist — it's the
/// same assumption your current jmp-hack already makes, just explicit now.
pub fn resolve_address((offset, section): (u32, SectionId), text_len_words: u32) -> u32 {
match section {
SectionId::Text => offset,
SectionId::Data => (text_len_words * 4) + offset,
}
}
/// Patches every symbol reference in `text_nodes` with its resolved absolute
/// address, and returns the symbol table so the caller can build the
/// executable's symbol/string tables from it.
pub fn resolve_symbols(
data_nodes: &[Node],
text_nodes: &mut [Node],
) -> Result<HashMap<Symbol, (u32, SectionId)>, AssembleError> {
let symbol_table = generate_symbol_table(data_nodes, text_nodes);
let text_len = text_nodes.len() as u32;
for node in text_nodes.iter_mut() {
match node.opcode() {
Opcode::Jmp | Opcode::Call | Opcode::Jic | Opcode::Jnc | Opcode::Lli | Opcode::Lui => {
if let Token::Symbol(symbol) = node
.arg(0)
.expect("Expected argument 0 for jump-like opcode")
{
let Some(entry) = symbol_table.get(&symbol) else {
return Err(AssembleError::UndefinedSymbol(symbol));
};
node.tokens[0] = Token::Immediate(resolve_address(*entry, text_len));
}
}
Opcode::Jez | Opcode::Jnz => {
if let Token::Symbol(symbol) = node
.arg(1)
.expect("Expected argument 1 for jump-like opcode")
{
let Some(entry) = symbol_table.get(&symbol) else {
return Err(AssembleError::UndefinedSymbol(symbol));
};
node.tokens[1] = Token::Immediate(resolve_address(*entry, text_len));
}
}
_ => (),
}
}
Ok(symbol_table)
}
// resolve_dependencies is unchanged — keep exactly as you have it.
pub fn resolve_dependencies(
mut nodes: Vec<Node>,
base_dir: &Path,
) -> Result<Vec<Node>, AssembleError> {
// First we get a list of imports.
let mut dependencies = Vec::new();
for node in &nodes {
if node.opcode() == Opcode::Include {
// we want the path, and the name
let name = if let Token::Symbol(name) = node
.arg(0)
.expect("Expected argument #0 for Include directive.")
{
name.name.clone()
} else {
unreachable!()
}; //node.2.get(0).unwrap()
let Ok(Token::StringLit(path)) = node.arg(1) else {
unreachable!()
};
let full_path = base_dir.join(path);
let canonical_path = full_path
.canonicalize()
.map_err(|_| AssembleError::InvalidFile(full_path.clone()))?;
let hash = quick_hash(&canonical_path);
dependencies.push((name, hash));
}
}
let mut changes = Vec::<(u32, u32, Symbol)>::new();
// now we resolve the symbols on all the nodes
// we need to check all operands for unresolved signals
for (i, node) in nodes.clone().iter().enumerate() {
let Node {
tokens: operands, ..
} = node;
for (j, token) in operands.iter().enumerate() {
if let Token::Symbol(symbol) = token {
for d in &dependencies {
if let Module::Unresolved(name) = symbol.module.clone() {
if name != d.0 {
continue;
}
let symbol = Symbol {
name: symbol.name.clone(),
module: Module::Resolved(d.1),
};
changes.push((i as u32, j as u32, symbol));
}
}
}
}
}
for (i, j, symbol) in changes {
nodes[i as usize].tokens[j as usize] = Token::Symbol(symbol);
}
Ok(nodes)
}
+24
View File
@@ -0,0 +1,24 @@
use std::path::PathBuf;
pub struct BuildContext {
filename: PathBuf,
stage: BuildStage,
// Logging
}
impl BuildContext {
pub fn new(filename: PathBuf) -> Self {
Self {
filename,
stage: BuildStage::Lexing,
}
}
}
pub enum BuildStage {
Lexing,
Parsing,
PseudoExpansion,
Linking,
CodeGeneration,
}
+3 -2
View File
@@ -2,7 +2,8 @@ use std::{str::FromStr, thread, time::Duration};
use common::{asm::AsmOpcode, prelude::Register}; use common::{asm::AsmOpcode, prelude::Register};
use crate::assembler::{AssembleError, Module, Opcode, Symbol, Token, lexer::parse_opcode}; use crate::assembler::{AssembleError, Module, Symbol};
use crate::assemblerv2::Token;
pub struct Lexer { pub struct Lexer {
src: Vec<u8>, src: Vec<u8>,
@@ -75,7 +76,7 @@ impl Lexer {
buffer.push(self.src.pop().unwrap() as char); buffer.push(self.src.pop().unwrap() as char);
} }
if let Ok(opcode) = Opcode::from_str(&buffer) { if let Ok(opcode) = AsmOpcode::from_str(&buffer) {
tokens.push(Token::Opcode(opcode)); tokens.push(Token::Opcode(opcode));
continue; continue;
} }
+28 -31
View File
@@ -1,46 +1,43 @@
// mod error; mod error;
pub mod lexer; mod lexer;
// pub mod parser; mod parser;
use core::fmt; use core::fmt;
use crate::assembler::{AssembleError, Symbol}; use crate::assembler::{AssembleError, Symbol};
// use common::{asm::AsmOpcode, prelude::Register}; use common::{asm::AsmOpcode, prelude::Register};
use lexer::Lexer; use lexer::Lexer;
// use parser::Parser; use parser::Parser;
pub fn asm(input: &str) -> Result<Vec<u8>, AssembleError> { pub fn asm(input: &str) -> Result<Vec<u8>, AssembleError> {
let mut lexer = Lexer::new(input.to_string(), 0); let mut lexer = Lexer::new(input.to_string(), 0);
let tokens = lexer.run()?; let tokens = lexer.run()?;
// let ast = Parser::new(tokens).parse().unwrap(); let ast = Parser::new(tokens).parse().unwrap();
// println!("{:#?}", ast); println!("{:#?}", ast);
// let ast = parser::parse(tokens)?;
Ok(vec![]) Ok(vec![])
} }
// #[derive(Debug, Clone)] #[derive(Debug, Clone)]
// pub enum Token { pub enum Token {
// Symbol(Symbol), Symbol(Symbol),
// Register(Register), Register(Register),
// Immediate(u32), Immediate(u32),
// StringLit(String), StringLit(String),
// CharLit(char), CharLit(char),
// Opcode(AsmOpcode), Opcode(AsmOpcode),
// } }
// 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::CharLit(char_lit) => write!(f, "{char_lit}",), Self::CharLit(char_lit) => write!(f, "{char_lit}",),
// Self::Opcode(opcode) => write!(f, "{opcode}",), Self::Opcode(opcode) => write!(f, "{opcode}",),
// } }
// } }
// } }
+2 -2
View File
@@ -2,11 +2,11 @@ use std::path::PathBuf;
use common::asm::{AsmInstruction, AsmOpcode}; use common::asm::{AsmInstruction, AsmOpcode};
use crate::assembler::Symbol; use crate::assembler::{Symbol};
use crate::{expect_tt, expect_value}; use crate::{expect_tt, expect_value};
use super::{Token, error::AssembleError}; use super::{error::AssembleError, Token};
pub struct AsmNode { pub struct AsmNode {
pub label: Option<Symbol>, pub label: Option<Symbol>,
-158
View File
@@ -1,158 +0,0 @@
use std::fs;
use std::path::PathBuf;
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use std::fs::File;
use binrw::{binrw, BinRead, binwrite, NullString, BinReaderExt, BinWriterExt, BinWrite};
use common::prelude::Instruction;
use crate::assembler::Symbol;
pub fn disassemble(in_path: PathBuf, out_path: PathBuf) -> Result<(), String> {
let Ok(content) = fs::read(in_path) else {
return Err(String::new())
};
for (line_num, chunk) in content.chunks(4).enumerate() {
let address = line_num * 4;
let value = u32::from_le_bytes(*chunk.as_array().unwrap());
println!("{:#?}", Instruction(value));
}
Ok(())
}
#[binrw]
#[brw(magic = b"DSAX", little)]
#[derive(Debug, Clone)]
struct DseHeader {
version: u16,
flags: u16,
entry_point: u32,
symbol_table_offset: u32,
symbol_table_size: u32,
string_table_offset: u32,
string_table_size: u32,
data_offset: u32,
data_size: u32,
text_offset: u32,
text_size: u32
}
#[binrw]
#[brw(little)]
#[derive(Debug, Clone)]
struct SymbolEntry {
name_offset: u32,
value: u32,
section: u8,
binding: u8,
_pad: u16
}
#[derive(Debug, Clone)]
struct DseExecutable {
header: DseHeader,
symbols: Vec<SymbolEntry>,
string_table: Vec<u8>,
data: Vec<u8>,
text: Vec<u32>
}
impl DseExecutable {
fn load(path: PathBuf) -> binrw::BinResult<Self> {
let mut file = File::open(path).map_err(|e| binrw::Error::Io(e))?;
Self::read(&mut file)
}
fn read<R: Read + Seek>(reader: &mut R) -> binrw::BinResult<Self> {
let header = DseHeader::read(reader)?;
reader.seek(SeekFrom::Start(header.symbol_table_offset as u64))?;
let symbols: Vec<SymbolEntry> = (0..header.symbol_table_size)
.map(|_| SymbolEntry::read(reader))
.collect::<binrw::BinResult<_>>()?;
reader.seek(SeekFrom::Start(header.string_table_offset as u64))?;
let mut string_table = vec![0u8; header.string_table_size as usize];
reader.read_exact(&mut string_table).map_err(|e| binrw::Error::Io(e))?;
reader.seek(SeekFrom::Start(header.data_offset as u64))?;
let mut data = vec![0u8; header.data_size as usize];
reader.read_exact(&mut data).map_err(|e| binrw::Error::Io(e))?;
reader.seek(SeekFrom::Start(header.text_offset as u64))?;
let text: Vec<u32> = (0..header.text_size)
.map(|_| reader.read_le::<u32>())
.collect::<binrw::BinResult<_>>()?;
Ok(DseExecutable { header, symbols, string_table, data, text })
}
fn save(&self, path: PathBuf) -> binrw::BinResult<()> {
let mut file = File::create(path)
.map_err(|e| binrw::Error::Io(e))?;
self.write(&mut file)
}
fn write<W: Write + Seek>(&self, writer: &mut W) -> binrw::BinResult<()> {
self.header.write(writer)?;
for symbol in &self.symbols {
symbol.write(writer)?;
}
writer.write_all(&self.string_table)?;
writer.write_all(&self.data)?;
for word in &self.text {
writer.write_le(word)?;
}
Ok(())
}
fn new(
symbols: &[SymbolEntry],
string_table: &[u8],
data: &[u8],
text: &[u32],
entry_point: u32,
) -> DseExecutable {
const HEADER_SIZE: u32 = 4 + 2 + 2 + 4 + 4*2 + 4*2 + 4*2 + 4*2; // adjust to actual packed size
let symbol_table_offset = HEADER_SIZE;
let symbol_table_size = symbols.len() as u32 * 8; // 4+4+1+1+2 padded = 8 bytes each
let string_table_offset = symbol_table_offset + symbol_table_size;
let data_offset = string_table_offset + string_table.len() as u32;
let text_offset = data_offset + data.len() as u32;
let header = DseHeader {
version: 1,
flags: 0,
entry_point,
symbol_table_offset,
symbol_table_size: symbols.len() as u32,
string_table_offset,
string_table_size: string_table.len() as u32,
data_offset,
data_size: data.len() as u32,
text_offset,
text_size: text.len() as u32,
};
DseExecutable {
header,
symbols: symbols.to_vec(),
string_table: string_table.to_vec(),
data: data.to_vec(),
text: text.to_vec(),
}
}
}
+1 -8
View File
@@ -1,5 +1,5 @@
use assembler::prelude::*; use assembler::prelude::*;
use clap::{Parser, arg, command}; use clap::{Parser, arg};
use std::{fs, path::PathBuf}; use std::{fs, path::PathBuf};
#[derive(Parser, Debug, Clone)] #[derive(Parser, Debug, Clone)]
@@ -23,11 +23,4 @@ fn main() {
eprintln!("Failed to write to output file: {e}"); eprintln!("Failed to write to output file: {e}");
std::process::exit(1); std::process::exit(1);
} }
// let input = fs::read_to_string("../../dsa_resources/framebuffer.dsa").unwrap();
// let output = asm(&input).unwrap();
// for token in output {
// println!("{:?}", token);
// }
} }
+1
View File
@@ -7,4 +7,5 @@ edition = "2024"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
binrw = "0.15.1"
strum = { version = "0.28.0", features = ["derive"] } strum = { version = "0.28.0", features = ["derive"] }
+264
View File
@@ -0,0 +1,264 @@
use std::fs::File;
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use std::path::PathBuf;
use binrw::{binrw, BinRead, binwrite, NullString, BinReaderExt, BinWriterExt, BinWrite};
use std::fmt;
use crate::prelude::Instruction;
#[binrw]
#[brw(magic = b"DSAX", little)]
#[derive(Debug, Clone)]
pub struct DseHeader {
version: u16,
flags: u16,
pub entry_point: u32,
symbol_table_offset: u32,
symbol_table_size: u32,
string_table_offset: u32,
string_table_size: u32,
data_offset: u32,
data_size: u32,
pub text_offset: u32,
text_size: u32
}
#[binrw]
#[brw(little)]
#[derive(Debug, Clone)]
pub struct SymbolEntry {
pub name_offset: u32,
pub value: u32,
pub section: u8,
pub binding: u8,
pub _pad: u16
}
#[derive(Debug, Clone)]
pub struct DseExecutable {
pub header: DseHeader,
pub symbols: Vec<SymbolEntry>,
pub string_table: Vec<u8>,
pub data: Vec<u8>,
pub text: Vec<u32>
}
impl DseExecutable {
pub fn load(path: PathBuf) -> binrw::BinResult<Self> {
let mut file = File::open(path).map_err(|e| binrw::Error::Io(e))?;
Self::read(&mut file)
}
pub fn read<R: Read + Seek>(reader: &mut R) -> binrw::BinResult<Self> {
let header = DseHeader::read(reader)?;
reader.seek(SeekFrom::Start(header.symbol_table_offset as u64))?;
let symbols: Vec<SymbolEntry> = (0..header.symbol_table_size)
.map(|_| SymbolEntry::read(reader))
.collect::<binrw::BinResult<_>>()?;
reader.seek(SeekFrom::Start(header.string_table_offset as u64))?;
let mut string_table = vec![0u8; header.string_table_size as usize];
reader.read_exact(&mut string_table).map_err(|e| binrw::Error::Io(e))?;
reader.seek(SeekFrom::Start(header.data_offset as u64))?;
let mut data = vec![0u8; header.data_size as usize];
reader.read_exact(&mut data).map_err(|e| binrw::Error::Io(e))?;
reader.seek(SeekFrom::Start(header.text_offset as u64))?;
let text: Vec<u32> = (0..header.text_size)
.map(|_| reader.read_le::<u32>())
.collect::<binrw::BinResult<_>>()?;
Ok(DseExecutable { header, symbols, string_table, data, text })
}
pub fn save(&self, path: PathBuf) -> binrw::BinResult<()> {
let mut file = File::create(path)
.map_err(|e| binrw::Error::Io(e))?;
self.write(&mut file)
}
pub fn write<W: Write + Seek>(&self, writer: &mut W) -> binrw::BinResult<()> {
self.header.write(writer)?;
for symbol in &self.symbols {
symbol.write(writer)?;
}
writer.write_all(&self.string_table)?;
writer.write_all(&self.data)?;
for word in &self.text {
writer.write_le(word)?;
}
Ok(())
}
pub fn new(
symbols: &[SymbolEntry],
string_table: &[u8],
data: &[u8],
text: &[u32],
entry_point: u32,
) -> binrw::BinResult<DseExecutable> {
// Derive sizes from real serialization instead of hand-counted constants.
let header_size = {
let mut buf = Cursor::new(Vec::new());
// dummy header just to measure length; fields' values don't matter here
let dummy = DseHeader {
version: 0, flags: 0, entry_point: 0,
symbol_table_offset: 0, symbol_table_size: 0,
string_table_offset: 0, string_table_size: 0,
data_offset: 0, data_size: 0,
text_offset: 0, text_size: 0,
};
dummy.write(&mut buf)?;
buf.into_inner().len() as u32
};
let symbol_entry_size = if symbols.is_empty() {
// measure using a zeroed entry even if symbols is empty
let mut buf = Cursor::new(Vec::new());
SymbolEntry { name_offset: 0, value: 0, section: 0, binding: 0, _pad: 0 }
.write(&mut buf)?;
buf.into_inner().len() as u32
} else {
let mut buf = Cursor::new(Vec::new());
symbols[0].write(&mut buf)?;
buf.into_inner().len() as u32
};
let symbol_table_offset = header_size;
let symbol_table_bytes = symbols.len() as u32 * symbol_entry_size;
let string_table_offset = symbol_table_offset + symbol_table_bytes;
let data_offset = string_table_offset + string_table.len() as u32;
let text_offset = data_offset + data.len() as u32;
let header = DseHeader {
version: 1,
flags: 0,
entry_point,
symbol_table_offset,
symbol_table_size: symbols.len() as u32,
string_table_offset,
string_table_size: string_table.len() as u32,
data_offset,
data_size: data.len() as u32,
text_offset,
text_size: text.len() as u32,
};
Ok(DseExecutable {
header,
symbols: symbols.to_vec(),
string_table: string_table.to_vec(),
data: data.to_vec(),
text: text.to_vec(),
})
}
/// Produces the runtime memory image: TEXT followed immediately by DATA,
/// matching the exact layout `resolve_address` assumed when the assembler
/// resolved symbol addresses. This is NOT the same as the on-disk file
/// layout (header/symtab/strtab/data/text) — this is what actually needs
/// to end up in emulator memory.
pub fn to_memory_image(&self) -> Vec<u8> {
let mut image = Vec::with_capacity(self.text.len() * 4 + self.data.len());
for word in &self.text {
image.extend_from_slice(&word.to_le_bytes());
}
image.extend_from_slice(&self.data);
image
}
/// Runtime address where DATA begins, given TEXT is loaded at `text_base`.
/// Matches the assembler's `resolve_address` convention exactly.
pub fn data_base(&self, text_base: u32) -> u32 {
text_base + (self.text.len() as u32 * 4)
}
/// Absolute runtime entry point address, given TEXT is loaded at `text_base`.
/// `self.header.entry_point` is already an absolute address under the
/// assumption text_base == 0, so this just re-bases it if you're loading
/// somewhere other than address 0.
pub fn entry_point(&self, text_base: u32) -> u32 {
text_base + self.header.entry_point
}
}
// Display implementations for dumping binary contents
impl fmt::Display for DseHeader {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "DseHeader {{ version: {}, flags: {}, entry_point: 0x{:08X}, symbol_table_offset: 0x{:08X}, symbol_table_size: {}, string_table_offset: 0x{:08X}, string_table_size: {}, data_offset: 0x{:08X}, data_size: {}, text_offset: 0x{:08X}, text_size: {} }}",
self.version,
self.flags,
self.entry_point,
self.symbol_table_offset,
self.symbol_table_size,
self.string_table_offset,
self.string_table_size,
self.data_offset,
self.data_size,
self.text_offset,
self.text_size)
}
}
impl fmt::Display for SymbolEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SymbolEntry {{ name_offset: 0x{:08X}, value: 0x{:08X}, section: {}, binding: {} }}",
self.name_offset,
self.value,
self.section,
self.binding)
}
}
impl fmt::Display for DseExecutable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Header
writeln!(f, "{}", self.header)?;
// Symbols
writeln!(f, "Symbols:")?;
for s in &self.symbols {
writeln!(f, " {}", s)?;
}
// String table as hex dump
writeln!(f, "String Table ({} bytes):", self.string_table.len())?;
for chunk in self.string_table.chunks(16) {
write!(f, "{}", String::from_utf8_lossy(chunk))?;
// for byte in chunk { write!(f, "{:02X} ", byte)?; }
writeln!(f)?;
}
// Data section
writeln!(f, "Data ({} bytes):", self.data.len())?;
for chunk in self.data.chunks(16) {
write!(f, " ")?;
for byte in chunk { write!(f, "{:02X} ", byte)?; }
for byte in chunk { write!(f, "{} ", *byte as char)?; }
writeln!(f)?;
}
// Text section as Instructions
writeln!(f, "Text ({} words):", self.text.len())?;
for word in &self.text {
writeln!(f, " {:#010X} {:?}", word, Instruction(*word))?;
}
Ok(())
}
}
#[test]
fn symbol_entry_size_is_12_bytes() {
let mut buf = Cursor::new(Vec::new());
SymbolEntry { name_offset: 0, value: 0, section: 0, binding: 0, _pad: 0 }
.write(&mut buf).unwrap();
assert_eq!(buf.into_inner().len(), 12);
}
+2
View File
@@ -0,0 +1,2 @@
pub mod binary_dse;
pub mod binary_dso;
+1
View File
@@ -1,5 +1,6 @@
pub mod asm; pub mod asm;
pub mod isa; pub mod isa;
pub mod formats;
pub mod prelude { pub mod prelude {
pub use crate::isa::instructions::Instruction; pub use crate::isa::instructions::Instruction;
+17
View File
@@ -0,0 +1,17 @@
[package]
name = "disassembler"
edition.workspace = true
version.workspace = true
authors.workspace = true
[[bin]]
name = "dsa-a"
path = "src/main.rs"
[lib]
name = "disassembler"
path = "src/lib.rs"
[dependencies]
clap = { version = "4.6.0", features = ["derive"] }
common = { path = "../common" }
+350
View File
@@ -0,0 +1,350 @@
use std::collections::HashMap;
use std::fmt;
use common::formats::binary_dse::DseExecutable;
use common::isa::instructions::Opcode;
use common::prelude::{Instruction, Register};
#[derive(Debug)]
pub struct Disassembly {
pub symbols: Vec<SymbolLine>,
pub text_lines: Vec<String>,
pub data_lines: Vec<String>,
}
#[derive(Debug)]
pub struct SymbolLine {
pub name: String,
pub address: u32,
pub section: SectionKind,
pub binding: BindingKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SectionKind { Data, Text, Abs, Undef }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BindingKind { Local, Global, Weak }
impl SectionKind {
fn from_raw(v: u8) -> Self {
match v { 1 => Self::Data, 2 => Self::Text, 0 => Self::Abs, _ => Self::Undef }
}
}
impl BindingKind {
fn from_raw(v: u8) -> Self {
match v { 1 => Self::Global, 2 => Self::Weak, _ => Self::Local }
}
}
pub fn disassemble(exe: &DseExecutable) -> Disassembly {
let symbols = build_symbol_lines(exe);
let mut text_labels: HashMap<u32, String> = HashMap::new();
let mut data_labels: HashMap<u32, String> = HashMap::new();
for sym in &symbols {
match sym.section {
SectionKind::Text => { text_labels.insert(sym.address, sym.name.clone()); }
SectionKind::Data => { data_labels.insert(sym.address, sym.name.clone()); }
_ => {}
}
}
// symbols can refer to either section by full resolved address (per your
// v1 loader convention: data lives right after text), so a single combined
// map is actually what address-resolution needs — keep both, but merge
// for lookups against full 32-bit addresses.
let mut all_labels = text_labels.clone();
all_labels.extend(data_labels.clone());
let text_lines = build_text_lines(&exe.text, &all_labels);
let data_lines = build_data_lines(&exe.data, &data_labels);
Disassembly { symbols, text_lines, data_lines }
}
fn build_symbol_lines(exe: &DseExecutable) -> Vec<SymbolLine> {
exe.symbols
.iter()
.map(|s| SymbolLine {
name: read_string(&exe.string_table, s.name_offset),
address: s.value,
section: SectionKind::from_raw(s.section),
binding: BindingKind::from_raw(s.binding),
})
.collect()
}
fn read_string(strtab: &[u8], offset: u32) -> String {
let start = offset as usize;
let end = strtab[start..].iter().position(|&b| b == 0).map(|p| start + p).unwrap_or(strtab.len());
String::from_utf8_lossy(&strtab[start..end]).into_owned()
}
/// Address a jump-like instruction actually targets, if statically known.
/// Returns None when the jump is register-relative (addr reg != Zero) since
/// that depends on runtime state and can't be resolved here.
fn static_jump_target(addr_reg: Register, imm: u16) -> Option<u32> {
(addr_reg == Register::Zero).then_some(imm as u32)
}
fn build_text_lines(words: &[u32], labels: &HashMap<u32, String>) -> Vec<String> {
let mut out = Vec::new();
let mut i = 0;
while i < words.len() {
let addr = 4 * i as u32;
if let Some(label) = labels.get(&addr) {
out.push(format!("{label}:"));
}
let instr = Instruction(words[i]);
let opcode = Opcode::from_u8(instr.opcode());
// Collapse Lli+Lui address-load pairs back into `lwi` (or a full
// ldx/stx pseudo-op if a matching mem instruction follows), mirroring
// what expand_ldx/expand_stx/expand_lwi originally expanded from.
if let Some(Opcode::Lli) = opcode {
if let Some(collapsed) = try_collapse_addr_load(words, i, labels) {
out.push(format!(" {}", collapsed.text));
i += collapsed.consumed;
continue;
}
}
out.push(format!(" {}", render_instruction(instr, opcode, labels)));
i += 1;
}
out
}
struct Collapsed {
text: String,
consumed: usize,
}
fn try_collapse_addr_load(
words: &[u32],
i: usize,
labels: &HashMap<u32, String>,
) -> Option<Collapsed> {
let lli = Instruction(words[i]);
let lui = Instruction(*words.get(i + 1)?);
if Opcode::from_u8(lui.opcode())? as u8 != Opcode::Lui as u8 {
return None;
}
if lui.dest_checked() != lli.dest_checked() {
return None;
}
let dest = lli.dest_checked();
let full_addr = (lli.imm16() as u32) | ((lui.imm16() as u32) << 16);
let addr_text = labels.get(&full_addr).cloned().unwrap_or_else(|| format!("{full_addr:#010x}"));
// Check whether a mem op immediately follows, referencing `dest` —
// that's the ldb/ldh/ldw/stb/sth/stw expansion pattern, not a bare lwi.
if let Some(&mem_word) = words.get(i + 2) {
let mem = Instruction(mem_word);
if let Some(mem_op) = Opcode::from_u8(mem.opcode()) {
let is_mem_op = matches!(
mem_op,
Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw
| Opcode::Stb | Opcode::Sth | Opcode::Stw
);
if is_mem_op && (mem.src1_checked() == dest || mem.dest_checked() == dest) {
let mnemonic = format!("{mem_op:?}").to_lowercase();
let other = if mem.src1_checked() == dest { mem.dest_checked() } else { mem.src1_checked() };
return Some(Collapsed {
text: format!("{mnemonic} {addr_text}, {other:?}, {:#06x}", mem.imm16()),
consumed: 3,
});
}
}
}
Some(Collapsed {
text: format!("lwi {addr_text}, {dest:?}"),
consumed: 2,
})
}
fn render_instruction(instr: Instruction, opcode: Option<Opcode>, labels: &HashMap<u32, String>) -> String {
let Some(op) = opcode else {
return format!("; INVALID OPCODE {:#04x} (word {:#010x})", instr.opcode(), instr.0);
};
let mnemonic = format!("{op:?}").to_lowercase();
match op {
Opcode::Nop | Opcode::Hlt | Opcode::Ret | Opcode::IRet => mnemonic,
Opcode::Push => format!("{mnemonic} {:?}", instr.src1_checked()),
Opcode::Pop => format!("{mnemonic} {:?}", instr.dest_checked()),
Opcode::Lli | Opcode::Lui => {
format!("{mnemonic} {:?}, {:#06x}", instr.dest_checked(), instr.imm16())
}
Opcode::Int => format!("{mnemonic} {:#06x}", instr.imm16()),
Opcode::Ldb | Opcode::Ldbs | Opcode::Ldh | Opcode::Ldhs | Opcode::Ldw
| Opcode::Stb | Opcode::Sth | Opcode::Stw | Opcode::Addi | Opcode::Subi => format!(
"{mnemonic} {:?}, {:?}, {:#06x}",
instr.src1_checked(), instr.dest_checked(), instr.imm16()
),
Opcode::Jez | Opcode::Jnz => {
let addr_reg = instr.dest_checked();
let target = static_jump_target(addr_reg, instr.imm16())
.and_then(|a| labels.get(&a).cloned());
match target {
Some(label) => format!("{mnemonic} {:?}, {label}", instr.src1_checked()),
None if addr_reg == Register::Zero => {
format!("{mnemonic} {:?}, {:#06x}", instr.src1_checked(), instr.imm16())
}
None => format!(
"{mnemonic} {:?}, {:#06x}, {addr_reg:?}",
instr.src1_checked(), instr.imm16()
),
}
}
Opcode::Jmp | Opcode::Call | Opcode::Jic | Opcode::Jnc => {
let addr_reg = instr.dest_checked();
let target = static_jump_target(addr_reg, instr.imm16())
.and_then(|a| labels.get(&a).cloned());
match target {
Some(label) => format!("{mnemonic} {label}"),
None if addr_reg == Register::Zero => format!("{mnemonic} {:#06x}", instr.imm16()),
None => format!("{mnemonic} {:#06x}, {addr_reg:?}", instr.imm16()),
}
}
Opcode::Mov | Opcode::Not => {
format!("{mnemonic} {:?}, {:?}", instr.src1_checked(), instr.dest_checked())
}
Opcode::CMov => format!(
"{mnemonic} {:?}, {:?}, {:?}",
instr.src1_checked(), instr.dest_checked(), instr.src2_checked()
),
Opcode::Add | Opcode::Sub | Opcode::And | Opcode::Nand | Opcode::Or | Opcode::Nor
| Opcode::Xor | Opcode::Xnor | Opcode::Ieq | Opcode::Ine | Opcode::Ilt | Opcode::Ile
| Opcode::Igt | Opcode::Ige => format!(
"{mnemonic} {:?}, {:?}, {:?}",
instr.src1_checked(), instr.src2_checked(), instr.dest_checked()
),
Opcode::Shl | Opcode::Shr => format!(
"{mnemonic} {:?}, {:?}, {:?}, {}",
instr.src1_checked(), instr.dest_checked(), instr.src2_checked(), instr.shamt()
),
}
}
fn build_data_lines(data: &[u8], data_labels: &HashMap<u32, String>) -> Vec<String> {
let mut out = Vec::new();
let mut i = 0;
while i < data.len() {
let addr = i as u32;
if let Some(label) = data_labels.get(&addr) {
out.push(format!("{label}:"));
}
// Try string detection only when this byte could actually start one.
if data[i] != 0 {
if let Some(end) = data[i..].iter().position(|&b| b == 0) {
let slice = &data[i..i + end];
if slice.iter().all(|&b| b.is_ascii_graphic() || b == b' ') {
out.push(format!(" db \"{}\"", String::from_utf8_lossy(slice).escape_default()));
i += end + 1; // skip the null terminator too
continue;
}
}
}
// Not a string start: consume only the run of zero bytes here,
// grouped into 4-byte words, stopping the instant a non-zero byte
// appears so we never swallow the start of the next string/value.
let run_end = data[i..]
.iter()
.position(|&b| b != 0)
.map(|p| i + p)
.unwrap_or(data.len());
if run_end == i {
// data[i] is non-zero but didn't form a printable/terminated
// string (e.g. binary constant) — dump one raw word.
let mut buf = [0u8; 4];
let take = (data.len() - i).min(4);
buf[..take].copy_from_slice(&data[i..i + take]);
out.push(format!(" dw {:#010x}", u32::from_le_bytes(buf)));
i += take;
} else {
let mut j = i;
while j < run_end {
let take = (run_end - j).min(4);
let mut buf = [0u8; 4];
buf[..take].copy_from_slice(&data[j..j + take]);
out.push(format!(" dw {:#010x}", u32::from_le_bytes(buf)));
j += take;
}
i = j;
}
}
out
}
impl fmt::Display for SectionKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Data => write!(f, "DATA"),
Self::Text => write!(f, "TEXT"),
Self::Abs => write!(f, "ABS"),
Self::Undef => write!(f, "UNDEF"),
}
}
}
impl fmt::Display for BindingKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Local => write!(f, "local"),
Self::Global => write!(f, "global"),
Self::Weak => write!(f, "weak"),
}
}
}
impl fmt::Display for SymbolLine {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"; {:<24} {:#010x} {:<5} {}",
self.name, self.address, self.section, self.binding
)
}
}
impl fmt::Display for Disassembly {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "; ==== symbols ====")?;
for sym in &self.symbols {
writeln!(f, "{sym}")?;
}
writeln!(f, "\n; ==== data ====")?;
for line in &self.data_lines {
writeln!(f, "{line}")?;
}
writeln!(f, "\n; ==== text ====")?;
for line in &self.text_lines {
writeln!(f, "{line}")?;
}
Ok(())
}
}
+15
View File
@@ -0,0 +1,15 @@
mod internal;
use std::fs;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::collections::HashMap;
use common::formats::binary_dse::DseExecutable;
use common::isa::instructions::{Instruction, Opcode};
use common::isa::register::Register;
pub use internal::{
disassemble, Disassembly, BindingKind, SectionKind, SymbolLine
};
+27
View File
@@ -0,0 +1,27 @@
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use clap::Parser;
use disassembler::disassemble;
use common::formats::binary_dse::DseExecutable;
#[derive(Parser, Debug, Clone)]
#[command(version, about, long_about = None)]
struct Args {
#[arg(short = 'i')]
pub input_path: PathBuf,
#[arg(short = 'o')]
pub output_path: PathBuf,
}
fn main() {
let args = Args::parse();
let Ok(exec) = DseExecutable::load(args.input_path) else {
panic!("Failed to load the input file")
};
let result = disassemble(&exec);
File::create(args.output_path).unwrap()
.write(result.to_string().as_bytes()).unwrap();
}
@@ -1,11 +1,11 @@
[package] [package]
name = "emu_core" name = "emulator_core"
edition.workspace = true edition.workspace = true
version.workspace = true version.workspace = true
authors.workspace = true authors.workspace = true
[lib] [lib]
name = "emu_core" name = "emulator_core"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
@@ -164,6 +164,11 @@ impl Emulator {
self.update(); self.update();
} }
if let x = self.shared_state.goto.load(Ordering::Relaxed) && x != -1 {
*self.mut_reg(Register::Pcx) = x as u32;
self.shared_state.goto.store(-1, Ordering::Relaxed);
}
if self.shared_state.reseting.load(Ordering::Relaxed) { if self.shared_state.reseting.load(Ordering::Relaxed) {
self.internal_state.registers = [0; Register::COUNT as usize]; self.internal_state.registers = [0; Register::COUNT as usize];
self.internal_state.clock = 0; self.internal_state.clock = 0;
@@ -1,5 +1,5 @@
use std::sync::{Arc, atomic::AtomicBool, mpsc}; use std::sync::{Arc, atomic::AtomicBool, mpsc};
use std::sync::atomic::AtomicIsize;
use arc_swap::ArcSwap; use arc_swap::ArcSwap;
use crate::processor::processor::ProcessorSnapshot; use crate::processor::processor::ProcessorSnapshot;
@@ -9,6 +9,7 @@ use super::interrupts::Interrupt;
pub struct SharedState { pub struct SharedState {
pub proc: ArcSwap<ProcessorSnapshot>, pub proc: ArcSwap<ProcessorSnapshot>,
pub goto: AtomicIsize,
pub reseting: AtomicBool, pub reseting: AtomicBool,
pub paused: AtomicBool, pub paused: AtomicBool,
pub update_req: AtomicBool, pub update_req: AtomicBool,
@@ -21,6 +22,7 @@ impl SharedState {
reseting: AtomicBool::new(false), reseting: AtomicBool::new(false),
proc: ArcSwap::new(Arc::new(ProcessorSnapshot::default())), proc: ArcSwap::new(Arc::new(ProcessorSnapshot::default())),
goto: AtomicIsize::new(-1),
paused: AtomicBool::new(true), paused: AtomicBool::new(true),
interrupt_queue: sender, interrupt_queue: sender,
update_req: AtomicBool::new(false), update_req: AtomicBool::new(false),
@@ -1,5 +1,5 @@
[package] [package]
name = "emulator" name = "emulator_ui"
version = "0.3.0" version = "0.3.0"
edition.workspace = true edition.workspace = true
authors.workspace = true authors.workspace = true
@@ -13,7 +13,7 @@ name = "dsa"
path = "src/lib.rs" path = "src/lib.rs"
[dev-dependencies] [dev-dependencies]
criterion = { version = "0.5.0", features = ["html_reports"] } criterion = { version = "0.8.2", features = ["html_reports"] }
[dependencies] [dependencies]
arc-swap = "1.8.2" arc-swap = "1.8.2"
@@ -23,15 +23,15 @@ ron = "0.12.0"
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
common = { path = "../../common" } common = { path = "../../common" }
egui = "0.33.3" egui = "0.35.0"
eframe = "0.33.3" eframe = "0.35.0"
egui_tiles = "0.14.1" egui_tiles = "0.16.0"
egui_code_editor = "0.2.21" egui_code_editor = "0.3.7"
egui_file = "0.25.0" egui_file = "0.27.0"
dirs = "6.0.0" dirs = "6.0.0"
assembler = { path = "../../assembler" } assembler = { path = "../../assembler" }
egui_extras = "0.33.3" egui_extras = "0.35.0"
emu_core = { version = "0.3.0", path = "../backend" } emulator_core = { version = "0.3.0", path = "../emulator_core" }
[features] [features]
default = ["mainstore-prealloc"] default = ["mainstore-prealloc"]
@@ -2,7 +2,7 @@ use std::{fs, path::PathBuf};
use clap::Parser; use clap::Parser;
use emu_core::config::{IoMap, IoMapping, MemoryMap}; use emulator_core::config::{IoMap, IoMapping, MemoryMap};
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(version, about, long_about = None)] #[command(version, about, long_about = None)]
@@ -4,8 +4,10 @@ use common::prelude::Instruction;
use std::thread; use std::thread;
use dsa::args::DsaArgs; use dsa::args::DsaArgs;
use dsa::ui::run_app; use dsa::ui::run_app;
use emu_core::memory::ram::Page; use emulator_core::{
use emu_core::processor::processor::Emulator; memory::ram::Page,
processor::processor::Emulator
};
const STACK_SIZE: usize = 1024 * 1024 * 16; const STACK_SIZE: usize = 1024 * 1024 * 16;
@@ -2,8 +2,10 @@ use std::sync::{Arc, atomic::Ordering};
use super::Component; use super::Component;
use common::prelude::Register; use common::prelude::Register;
use emu_core::memory::ram::MemoryBank; use emulator_core::{
use emu_core::processor::state::SharedState; memory::ram::MemoryBank,
processor::state::SharedState
};
pub struct Controller { pub struct Controller {
state: Arc<SharedState>, state: Arc<SharedState>,
@@ -30,7 +32,7 @@ impl Component for Controller {
&mut self.visible &mut self.visible
} }
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { fn ui(&mut self, ui: &mut egui::Ui) {
let state = self.state.proc.load(); let state = self.state.proc.load();
let paused = self.state.paused.load(Ordering::Relaxed); let paused = self.state.paused.load(Ordering::Relaxed);
@@ -1,6 +1,8 @@
use egui::{Color32, FontId, Vec2}; use egui::{Color32, FontId, Vec2};
use emu_core::memory::PhysAddr; use emulator_core::{
use emu_core::memory::ram::{MemoryBank, Page}; memory::PhysAddr,
memory::ram::{MemoryBank, Page}
};
use super::Component; use super::Component;
pub struct Display { pub struct Display {
@@ -72,7 +74,7 @@ impl Component for Display {
self.visible() self.visible()
} }
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { fn ui(&mut self, ui: &mut egui::Ui) {
let display_w = self.width(); let display_w = self.width();
let display_h = self.height(); let display_h = self.height();
let data = self.read(); let data = self.read();
@@ -1,7 +1,8 @@
use std::fmt::Write; use std::fmt::Write;
use std::fs::{self, File}; use std::fs::{self};
use std::sync::Arc; use std::io::Cursor;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::{ use std::{
ffi::OsStr, ffi::OsStr,
path::{Path, PathBuf}, path::{Path, PathBuf},
@@ -9,15 +10,18 @@ use std::{
pub mod syntax; pub mod syntax;
use super::Component;
use assembler::prelude::Assembler; use assembler::prelude::Assembler;
use common::formats::binary_dse::DseExecutable;
use common::prelude::Instruction; use common::prelude::Instruction;
use egui::{Align, Context, Key, Layout, Ui}; use egui::{Align, Context, Key, Layout, Ui};
use egui_code_editor::{CodeEditor, ColorTheme, Completer, Syntax}; use egui_code_editor::{CodeEditor, ColorTheme, Completer, Syntax};
use egui_extras::{Column, TableBuilder}; use egui_extras::{Column, TableBuilder};
use egui_file::FileDialog; use egui_file::FileDialog;
use emu_core::memory::ram::{MemoryBank, Page}; use emulator_core::{
use emu_core::processor::state::SharedState; memory::ram::{MemoryBank, Page},
use super::Component; processor::state::SharedState
};
pub struct Editor { pub struct Editor {
state: Arc<SharedState>, state: Arc<SharedState>,
@@ -32,16 +36,13 @@ pub struct Editor {
// editor widget // editor widget
editor: CodeEditor, editor: CodeEditor,
completer: Completer, completer: Completer,
syntax: Syntax,
// output / loading // output / loading
output: Output, output: Output,
load_offset: u32, load_offset: u32,
offset_str: String, offset_str: String,
// cursor - currently unused
cursor_col: usize,
cursor_line: usize,
// file dialogs // file dialogs
open_file_dialog: Option<FileDialog>, open_file_dialog: Option<FileDialog>,
save_file_dialog: Option<FileDialog>, save_file_dialog: Option<FileDialog>,
@@ -61,7 +62,7 @@ impl Component for Editor {
&mut self.visible &mut self.visible
} }
fn ui(&mut self, ui: &mut Ui, ctx: &Context) { fn ui(&mut self, ui: &mut Ui) {
if self.buffer != self.text { if self.buffer != self.text {
self.unsaved = true; self.unsaved = true;
} }
@@ -73,7 +74,7 @@ impl Component for Editor {
self.save(); self.save();
} }
self.render_toolbar(ui, ctx); self.render_toolbar(ui);
ui.add_space(4.0); // Add some spacing instead of just a separator ui.add_space(4.0); // Add some spacing instead of just a separator
ui.separator(); ui.separator();
@@ -85,15 +86,15 @@ impl Component for Editor {
Layout::left_to_right(Align::Min), Layout::left_to_right(Align::Min),
|ui| { |ui| {
if self.show_output { if self.show_output {
egui::SidePanel::right("Editor Output").resizable(false).show_inside(ui, |ui| { egui::Panel::right("Editor Output").resizable(false).show_inside(ui, |ui| {
self.output.ui(ui, ctx); self.output.ui(ui);
}); });
} }
self.render_editor(ui, ctx); self.render_editor(ui);
}, },
); );
self.render_bottom_bar(ui, ctx); self.render_bottom_bar(ui);
}); });
} }
} }
@@ -108,17 +109,15 @@ impl Editor {
text: String::new(), text: String::new(),
buffer: String::new(), buffer: String::new(),
completer: Completer::new_with_syntax(&Syntax::default()).with_user_words(), completer: Completer::new_with_syntax(&Syntax::default()).with_user_words(),
syntax: Syntax::default(),
editor: CodeEditor::default() editor: CodeEditor::default()
.id_source("editor") .id_source("editor")
.with_fontsize(12.0) .with_fontsize(12.0)
.with_rows(0) .with_rows(0)
.with_theme(THEME) .with_theme(THEME)
.with_syntax(Syntax::default())
.with_numlines(true), .with_numlines(true),
output: Output::new(), output: Output::new(),
unsaved: true, unsaved: true,
cursor_col: 1,
cursor_line: 1,
visible: false, visible: false,
load_offset: 0, load_offset: 0,
offset_str: String::new(), offset_str: String::new(),
@@ -226,21 +225,20 @@ impl Editor {
self.open_file_dialog = Some(dialog); self.open_file_dialog = Some(dialog);
} }
let syntax = match self.extension() { self.syntax = match self.extension() {
"dsa" => syntax::dsa(), "dsa" => syntax::dsa(),
"dsc" => syntax::dsc(), "dsc" => syntax::dsc(),
"rs" => Syntax::rust(), "rs" => Syntax::rust(),
_ => Syntax::default(), _ => Syntax::default(),
}; };
self.completer = Completer::new_with_syntax(&syntax).with_user_words(); self.completer = Completer::new_with_syntax(&self.syntax).with_user_words();
self.editor = self.editor.clone().with_syntax(syntax);
} }
fn handle_file_dialogs(&mut self, ctx: &egui::Context) { fn handle_file_dialogs(&mut self, ui: &Ui) {
// Handle open dialog // Handle open dialog
if let Some(dialog) = &mut self.open_file_dialog if let Some(dialog) = &mut self.open_file_dialog
&& dialog.show(ctx).selected() && dialog.show(ui).selected()
{ {
if let Some(file) = dialog.path() { if let Some(file) = dialog.path() {
// check if the file is a binary file // check if the file is a binary file
@@ -284,7 +282,7 @@ impl Editor {
// Handle save dialog // Handle save dialog
if let Some(dialog) = &mut self.save_file_dialog if let Some(dialog) = &mut self.save_file_dialog
&& dialog.show(ctx).selected() && dialog.show(ui).selected()
{ {
if let Some(file) = dialog.path() { if let Some(file) = dialog.path() {
self.buffer = self.text.clone(); self.buffer = self.text.clone();
@@ -324,24 +322,19 @@ impl Editor {
} }
} }
fn render_editor(&mut self, ui: &mut Ui, _ctx: &Context) { fn render_editor(&mut self, ui: &mut Ui) {
let available_width = ui.available_width(); let available_width = ui.available_width();
let mut editor = self.editor.clone().desired_width(available_width); let mut editor = self.editor.clone().desired_width(available_width);
editor.show_with_completer(ui, &mut self.text, &mut self.completer); editor.show_with_completer(ui, &mut self.text, &self.syntax, &mut self.completer);
} }
fn render_bottom_bar(&self, ui: &mut Ui, _ctx: &Context) { fn render_bottom_bar(&self, ui: &mut Ui) {
ui.horizontal(|ui| { ui.horizontal(|ui| {
// error display // error display
ui.label( ui.label(
egui::RichText::new(self.error.clone().unwrap_or_default()) egui::RichText::new(self.error.clone().unwrap_or_default())
.color(egui::Color32::RED), .color(egui::Color32::RED),
); );
// line and col
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
ui.label(format!("Ln {}, Col {}", self.cursor_line, self.cursor_col));
});
}); });
} }
@@ -399,8 +392,8 @@ impl Editor {
} }
} }
fn render_toolbar(&mut self, ui: &mut Ui, ctx: &Context) { fn render_toolbar(&mut self, ui: &mut Ui) {
self.handle_file_dialogs(ctx); self.handle_file_dialogs(ui);
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.label(format!("File type: {}", self.extension())); ui.label(format!("File type: {}", self.extension()));
@@ -443,13 +436,21 @@ impl Editor {
self.error = Some("Can't load program at invalid offset!".to_string()); self.error = Some("Can't load program at invalid offset!".to_string());
} }
let program: Vec<Page> = Page::paginate(self.output.data()).collect(); let mut reader = Cursor::new(self.output.data().clone());
let Ok(executable) = DseExecutable::read(&mut reader) else {
self.error = Some("Failed to read program!".to_string());
return
};
let image: Vec<Page> = Page::paginate(&executable.to_memory_image()).collect();
self.state.reseting.store(true, Ordering::Relaxed); self.state.reseting.store(true, Ordering::Relaxed);
self.memory.clear(); self.memory.clear();
for (i, page) in program.iter().enumerate() { for (i, page) in image.iter().enumerate() {
self.memory.write_page(i as u32 * 4096, &page); self.memory.write_page(i as u32 * 4096, &page);
} }
self.state.goto.store(executable.entry_point(0) as isize, Ordering::Relaxed);
self.state.reseting.store(false, Ordering::Relaxed); self.state.reseting.store(false, Ordering::Relaxed);
} }
@@ -463,7 +464,7 @@ impl Editor {
} }
} }
ui.checkbox(&mut self.show_output, "Show Output") ui.checkbox(&mut self.show_output, "Show Output");
}); });
} }
} }
@@ -488,18 +489,18 @@ fn parse_address(address: &str) -> Option<u32> {
pub const THEME: ColorTheme = ColorTheme { pub const THEME: ColorTheme = ColorTheme {
name: "Theme", name: "Theme",
dark: true, dark: true,
bg: "#1b1b1b", bg: "1b1b1b",
cursor: "#de5252", // fg4 cursor: "de5252", // fg4
selection: "#28323B", // bg2 selection: "28323B", // bg2
comments: "#444444", // gray1 comments: "444444", // gray1
functions: "#7CCCC7", // green1 functions: "7CCCC7", // green1
keywords: "#6C81E0", // red1 keywords: "6C81E0", // red1
literals: "#A3ABFF", // fg1 literals: "A3ABFF", // fg1
numerics: "#8A46CF", // purple1 numerics: "8A46CF", // purple1
punctuation: "#99C9C9", // orange1 punctuation: "99C9C9", // orange1
strs: "#618c84", // aqua1 strs: "618c84", // aqua1
types: "#B8B9D4", // yellow1 types: "B8B9D4", // yellow1
special: "#de5252", // blue1 special: "de5252", // blue1
}; };
struct Output { struct Output {
@@ -532,7 +533,7 @@ impl Output {
&self.data &self.data
} }
fn ui(&mut self, ui: &mut Ui, _ctx: &Context) { fn ui(&mut self, ui: &mut Ui) {
// Output area with synchronized scrolling // Output area with synchronized scrolling
egui::ScrollArea::vertical() egui::ScrollArea::vertical()
.id_salt("output_scroll") .id_salt("output_scroll")
@@ -613,8 +614,9 @@ impl Output {
for (line_num, chunk) in self.data.chunks(4).enumerate() { for (line_num, chunk) in self.data.chunks(4).enumerate() {
body.row(18.0, |mut row| { body.row(18.0, |mut row| {
let address = line_num * 4; let address = line_num * 4;
let mut buf = [0u8; 4];
let value = u32::from_le_bytes(*chunk.as_array().unwrap()); buf[..chunk.len()].copy_from_slice(chunk);
let value = u32::from_le_bytes(buf);
// Address column // Address column
row.col(|ui| { row.col(|ui| {
@@ -688,111 +690,6 @@ impl Output {
}) })
} }
}); });
// egui::Grid::new("output_grid")
// .spacing([5.0, 2.0]) // Horizontal and vertical spacing
// .num_columns(4)
// .striped(false)
// .show(ui, |ui| {
// ui.label("Address");
// if self.show_bytes {
// ui.label("Bytes");
// }
// if self.show_words {
// ui.label("Word");
// }
// if self.show_decimal {
// ui.label("Decimal");
// }
// if self.show_ascii {
// ui.label("Ascii");
// }
// if self.show_instruction {
// ui.label("Hex");
// }
// ui.end_row();
// // Process bytes in chunks of 4
// for (line_num, chunk) in self.data.chunks(4).enumerate() {
// let address = line_num * 4;
// // Convert chunk to u32 (little-endian)
// let mut bytes = [0u8; 4];
// for (i, &byte) in chunk.iter().enumerate() {
// if i < 4 {
// bytes[i] = byte;
// }
// }
// let value = u32::from_le_bytes(bytes);
// // Address column
// ui.with_layout(
// egui::Layout::left_to_right(egui::Align::Center),
// |ui| {
// ui.set_min_width(80.0);
// let style = ui.style_mut();
// style.visuals.widgets.inactive.bg_fill =
// egui::Color32::from_gray(30);
// ui.label(
// egui::RichText::new(format!("0x{address:04X}"))
// .font(egui::FontId::monospace(12.0)),
// );
// },
// );
// // Individual bytes column
// let byte_str = chunk
// .iter()
// .map(|b| format!("{b:02X}"))
// .collect::<Vec<_>>()
// .join(" ");
// if self.show_bytes {
// ui.label(
// egui::RichText::new(format!("{byte_str:<11}"))
// .font(egui::FontId::monospace(12.0))
// .color(egui::Color32::from_rgb(200, 200, 255)),
// );
// }
// if self.show_words {
// // Hex column
// ui.label(
// egui::RichText::new(format!("0x{value:08X}"))
// .font(egui::FontId::monospace(12.0))
// .color(egui::Color32::from_rgb(255, 100, 100)),
// );
// }
// if self.show_decimal {
// ui.label(
// egui::RichText::new(format!("{value}"))
// .font(egui::FontId::monospace(12.0))
// .color(egui::Color32::from_rgb(100, 255, 100)),
// );
// }
// if self.show_ascii {
// let ascii = value.to_le_bytes();
// ui.label(
// egui::RichText::new(String::from_utf8_lossy(&ascii))
// .font(egui::FontId::monospace(12.0))
// .color(egui::Color32::from_rgb(100, 255, 255)),
// );
// }
// if self.show_instruction {
// ui.label(
// egui::RichText::new(format!("{:?}", Instruction(value)))
// .font(egui::FontId::monospace(12.0))
// .color(egui::Color32::from_rgb(255, 100, 255)),
// );
// }
// ui.end_row();
// }
// });
}); });
} }
} }
@@ -1,9 +1,10 @@
use std::collections::BTreeSet; use std::collections::BTreeSet;
use egui_code_editor::Syntax; use egui_code_editor::{Patch, Syntax};
pub fn dsa() -> Syntax { pub fn dsa() -> Syntax {
Syntax { Syntax {
word_start: BTreeSet::new(),
quotes: BTreeSet::from(['"', '\'']), quotes: BTreeSet::from(['"', '\'']),
language: "Assembly", language: "Assembly",
case_sensitive: false, case_sensitive: false,
@@ -22,11 +23,13 @@ pub fn dsa() -> Syntax {
"RGC", "RGD", "RGE", "RGF", "ACC", "SPR", "BPR", "IDR", "MMR", "ZERO", "PCX", "STS", "RGC", "RGD", "RGE", "RGF", "ACC", "SPR", "BPR", "IDR", "MMR", "ZERO", "PCX", "STS",
"CIR", "CIR",
]), ]),
patch: Patch::default(),
} }
} }
pub fn dsc() -> Syntax { pub fn dsc() -> Syntax {
Syntax { Syntax {
word_start: BTreeSet::from(['_']),
quotes: BTreeSet::from(['"', '\'', '`']), quotes: BTreeSet::from(['"', '\'', '`']),
language: "Damn Simple Code", language: "Damn Simple Code",
case_sensitive: false, case_sensitive: false,
@@ -44,5 +47,6 @@ pub fn dsc() -> Syntax {
",", ";", ".", ":", "=", "+", "-", "*", "/", "%", "&", "|", "^", "~", "!", "?", "<", ",", ";", ".", ":", "=", "+", "-", "*", "/", "%", "&", "|", "^", "~", "!", "?", "<",
">", "<<", ">>", "==", "!=", "<=", ">=", "&&", "||", ">", "<<", ">>", "==", "!=", "<=", ">=", "&&", "||",
]), ]),
patch: Patch::default(),
} }
} }
@@ -1,6 +1,8 @@
use egui::Vec2; use egui::Vec2;
use emu_core::memory::PhysAddr; use emulator_core::{
use emu_core::memory::ram::{MemoryBank, Page}; memory::PhysAddr,
memory::ram::{MemoryBank, Page}
};
use super::Component; use super::Component;
pub struct FrameBuffer { pub struct FrameBuffer {
@@ -86,12 +88,12 @@ impl Component for FrameBuffer {
&mut self.visible &mut self.visible
} }
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { fn ui(&mut self, ui: &mut egui::Ui) {
let changed = self.changed(); let changed = self.changed();
// get or create the texture // get or create the texture
let texture = self.texture.get_or_insert_with(|| { let texture = self.texture.get_or_insert_with(|| {
ctx.load_texture( ui.load_texture(
"framebuffer", "framebuffer",
egui::ColorImage { egui::ColorImage {
size: [self.width, self.height], size: [self.width, self.height],
@@ -0,0 +1,264 @@
use std::num::ParseIntError;
use egui_extras::{Column, TableBuilder};
use common::prelude::Instruction;
use emulator_core::memory::ram::MemoryBank;
use super::Component;
pub struct MemoryInspector {
memory: MemoryBank,
view_size: u32,
view_addr: u32,
visible: bool,
addr_input: String,
// settings
show_bytes: bool,
show_words: bool,
show_ascii: bool,
show_instruction: bool,
show_decimal: bool,
}
impl MemoryInspector {
#[must_use]
pub fn new(memory: MemoryBank) -> Self {
Self {
memory,
view_size: 1024,
view_addr: 0,
visible: false,
addr_input: String::from("0x00"),
show_bytes: true,
show_words: true,
show_ascii: true,
show_instruction: true,
show_decimal: true,
}
}
}
impl Component for MemoryInspector {
fn title(&self) -> &'static str {
"Memory Inspector"
}
fn visible(&mut self) -> &mut bool {
&mut self.visible
}
fn ui(&mut self, ui: &mut egui::Ui) {
// Right column - Memory
// ui.vertical(|ui| {
// ui.add_space(10.0);
// });
// Memory table
egui::ScrollArea::vertical()
.auto_shrink([false; 2])
.id_salt("memory_inspector_scroll")
.show(ui, |ui| {
// ui.heading("Memory Inspector");
// ui.add_space(10.0);
//
// // Address input section
ui.horizontal(|ui| {
ui.label("Address:");
let address_response = ui.add(
egui::TextEdit::singleline(&mut self.addr_input)
.hint_text("0x1000 or 4096")
.desired_width(150.0),
);
ui.add_space(10.0);
// Search button
let search_clicked = ui.button("🔍 Search").clicked();
// Handle Enter key in text field
let enter_pressed =
address_response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter));
if search_clicked || enter_pressed {
if let Ok(new) = parse_address(&self.addr_input) {
if new % 4 == 0 {
self.view_addr = new;
} else {
println!("Address must be 4-byte aligned");
}
} else {
println!("Invalid address");
// state.error_log.push("Invalid address".to_string());
}
}
ui.label("(hex or decimal)");
});
ui.horizontal(|ui| {
ui.toggle_value(&mut self.show_bytes, "Bytes");
ui.toggle_value(&mut self.show_words, "Words");
ui.toggle_value(&mut self.show_decimal, "Decimal");
ui.toggle_value(&mut self.show_ascii, "Ascii");
ui.toggle_value(&mut self.show_instruction, "Instructions");
});
ui.separator();
let mut table = TableBuilder::new(ui)
.resizable(false) // makes all columns resizable at once
.column(Column::initial(75.0)); // addr
if self.show_bytes {
table = table.column(Column::initial(90.0));
}
if self.show_words {
table = table.column(Column::initial(75.0));
}
if self.show_decimal {
table = table.column(Column::initial(80.0));
}
if self.show_ascii {
table = table.column(Column::initial(50.0));
}
if self.show_instruction {
table = table.column(Column::initial(200.0).clip(true));
}
table
.header(20.0, |mut header| {
header.col(|ui| {
ui.label("Addr");
});
if self.show_bytes {
header.col(|ui| {
ui.label("Bytes");
});
}
if self.show_words {
header.col(|ui| {
ui.label("Word");
});
}
if self.show_decimal {
header.col(|ui| {
ui.label("Decimal");
});
}
if self.show_ascii {
header.col(|ui| {
ui.label("Ascii");
});
}
if self.show_instruction {
header.col(|ui| {
ui.label("Instruction");
});
}
})
.body(|mut body| {
// Process bytes in chunks of 4
for (line_num, value) in (0..self.view_size / 4)
.map(|i| (i, self.memory.read_word(self.view_addr + i * 4))) {
body.row(18.0, |mut row| {
let address = line_num * 4 * self.view_addr;
let chunk = value.to_le_bytes();
// Address column
row.col(|ui| {
ui.label(
egui::RichText::new(format!("0x{address:08X}"))
.font(egui::FontId::monospace(12.0)),
);
});
if self.show_bytes {
row.col(|ui| {
// Individual bytes column
ui.label(
egui::RichText::new(
chunk
.iter()
.map(|b| format!("{b:02X}"))
.collect::<Vec<_>>()
.join(" "),
)
.font(egui::FontId::monospace(12.0))
.color(egui::Color32::from_rgb(200, 200, 255)),
);
});
}
if self.show_words {
row.col(|ui| {
// Hex column
ui.label(
egui::RichText::new(format!("0x{value:08X}"))
.font(egui::FontId::monospace(12.0))
.color(egui::Color32::from_rgb(255, 100, 100)),
);
});
}
if self.show_decimal {
row.col(|ui| {
ui.label(
egui::RichText::new(format!("{value}"))
.font(egui::FontId::monospace(12.0))
.color(egui::Color32::from_rgb(100, 255, 100)),
);
});
}
if self.show_ascii {
let ascii = value.to_le_bytes();
row.col(|ui| {
ui.label(
egui::RichText::new(String::from_utf8_lossy(&ascii))
.font(egui::FontId::monospace(12.0))
.color(egui::Color32::from_rgb(100, 255, 255)),
);
});
}
if self.show_instruction {
row.col(|ui| {
ui.label(
egui::RichText::new(format!(
"{:?}",
Instruction(value)
))
.font(egui::FontId::monospace(12.0))
.color(egui::Color32::from_rgb(255, 100, 255)),
);
});
}
})
}
});
});
}
}
fn parse_address(address: &str) -> Result<u32, ParseIntError> {
if let Some(hex_part) = address.strip_prefix("0x") {
return u32::from_str_radix(hex_part, 16);
}
if let Some(bin_part) = address.strip_prefix("0b") {
return u32::from_str_radix(bin_part, 2);
}
if let Some(oct_part) = address.strip_prefix("0o") {
return u32::from_str_radix(oct_part, 8);
}
address.parse::<u32>()
}
@@ -13,5 +13,5 @@ pub trait Component {
fn visible(&mut self) -> &mut bool; fn visible(&mut self) -> &mut bool;
/// Draw the component's UI inside the provided `Ui`. /// Draw the component's UI inside the provided `Ui`.
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context); fn ui(&mut self, ui: &mut egui::Ui);
} }
@@ -1,7 +1,7 @@
use std::{f32, sync::Arc}; use std::{f32, sync::Arc};
use common::prelude::Register; use common::prelude::Register;
use emu_core::processor::state::SharedState; use emulator_core::processor::state::SharedState;
use super::Component; use super::Component;
@@ -14,7 +14,6 @@ impl Registers {
fn section( fn section(
&mut self, &mut self,
ui: &mut egui::Ui, ui: &mut egui::Ui,
ctx: &egui::Context,
registers: Vec<(Register, u32)>, registers: Vec<(Register, u32)>,
col_pairs: usize, col_pairs: usize,
name: &str, name: &str,
@@ -67,7 +66,7 @@ impl Component for Registers {
&mut self.visible &mut self.visible
} }
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) { fn ui(&mut self, ui: &mut egui::Ui) {
ui.set_min_width(200.0); ui.set_min_width(200.0);
let state = self.state.proc.load(); let state = self.state.proc.load();
@@ -87,7 +86,6 @@ impl Component for Registers {
.collect(); .collect();
self.section( self.section(
ui, ui,
ctx,
gp_registers, gp_registers,
col_pairs, col_pairs,
"General Purpose Registers", "General Purpose Registers",
@@ -99,7 +97,7 @@ impl Component for Registers {
(Register::Spr, state.registers[Register::Spr as usize]), (Register::Spr, state.registers[Register::Spr as usize]),
(Register::Bpr, state.registers[Register::Bpr as usize]), (Register::Bpr, state.registers[Register::Bpr as usize]),
]; ];
self.section(ui, ctx, stack_registers, col_pairs, "Stack Registers"); self.section(ui, stack_registers, col_pairs, "Stack Registers");
ui.separator(); ui.separator();
// ── Special Purpose ─────────────────────────────────────── // ── Special Purpose ───────────────────────────────────────
@@ -111,7 +109,6 @@ impl Component for Registers {
]; ];
self.section( self.section(
ui, ui,
ctx,
special_registers, special_registers,
col_pairs, col_pairs,
"Special Purpose Registers", "Special Purpose Registers",
@@ -124,7 +121,7 @@ impl Component for Registers {
(Register::Sts, state.registers[Register::Sts as usize]), (Register::Sts, state.registers[Register::Sts as usize]),
(Register::Cir, state.registers[Register::Cir as usize]), (Register::Cir, state.registers[Register::Cir as usize]),
]; ];
self.section(ui, ctx, system_registers, col_pairs, "System Registers"); self.section(ui, system_registers, col_pairs, "System Registers");
}); });
} }
} }
@@ -6,10 +6,16 @@ use std::{
mpsc::{Receiver, Sender}, mpsc::{Receiver, Sender},
}, },
}; };
use emu_core::memory::PhysAddr; use emulator_core::{
use emu_core::memory::ram::MemoryBank; memory::{
use emu_core::processor::interrupts::Interrupt; PhysAddr,
use emu_core::processor::state::SharedState; ram::MemoryBank
},
processor::{
interrupts::Interrupt,
state::SharedState
}
};
use super::Component; use super::Component;
/// ## SERIAL LAYOUT: (from the perspective of code inside the ui) /// ## SERIAL LAYOUT: (from the perspective of code inside the ui)
@@ -100,7 +106,7 @@ impl Component for Serial {
&mut self.visible &mut self.visible
} }
fn ui(&mut self, ui: &mut egui::Ui, _ctx: &egui::Context) { fn ui(&mut self, ui: &mut egui::Ui) {
self.update(); self.update();
// Text field for input and a send button // Text field for input and a send button
@@ -8,9 +8,11 @@ use components::{
controller::Controller, display::Display, editor::Editor, framebuffer::FrameBuffer, memory::MemoryInspector, controller::Controller, display::Display, editor::Editor, framebuffer::FrameBuffer, memory::MemoryInspector,
registers::Registers, serial::Serial, Component, registers::Registers, serial::Serial, Component,
}; };
use emu_core::config::VERSION; use emulator_core::{
use emu_core::memory::ram::MemoryBank; config::VERSION,
use emu_core::processor::state::SharedState; memory::ram::MemoryBank,
processor::state::SharedState
};
pub fn run_app(state: Arc<SharedState>, mem_handle: MemoryBank) -> eframe::Result<()> { pub fn run_app(state: Arc<SharedState>, mem_handle: MemoryBank) -> eframe::Result<()> {
eframe::run_native( eframe::run_native(
@@ -21,12 +23,13 @@ pub fn run_app(state: Arc<SharedState>, mem_handle: MemoryBank) -> eframe::Resul
} }
impl eframe::App for DsaUi { impl eframe::App for DsaUi {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
fn ui(&mut self, ui: &mut egui::Ui, frame: &mut eframe::Frame) {
// request an update from ui every cycle // request an update from ui every cycle
self.state.update_req.store(true, Ordering::Relaxed); self.state.update_req.store(true, Ordering::Relaxed);
// title panel with dsa title // title panel with dsa title
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { egui::containers::Panel::top("top_panel").show(ui, |ui| {
ui.with_layout( ui.with_layout(
egui::Layout::top_down_justified(egui::Align::Center) egui::Layout::top_down_justified(egui::Align::Center)
.with_main_align(egui::Align::Min), .with_main_align(egui::Align::Min),
@@ -39,7 +42,7 @@ impl eframe::App for DsaUi {
}); });
// menu bar. // menu bar.
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| { egui::Panel::top("menu_bar").show(ui, |ui| {
egui::MenuBar::new().ui(ui, |ui| { egui::MenuBar::new().ui(ui, |ui| {
ui.menu_button("Panels", |ui| { ui.menu_button("Panels", |ui| {
ui.set_max_width(300.0); ui.set_max_width(300.0);
@@ -54,21 +57,21 @@ impl eframe::App for DsaUi {
}); });
}); });
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ui, |ui| {
for c in &mut self.components { for c in &mut self.components {
let mut visible = *c.visible(); let mut visible = *c.visible();
if visible { if visible {
egui::Window::new(c.title()) egui::Window::new(c.title())
.open(&mut visible) .open(&mut visible)
.show(ctx, |ui| { .show(ui, |ui| {
c.ui(ui, ctx); c.ui(ui);
}); });
} }
*c.visible() = visible; *c.visible() = visible;
} }
}); });
ctx.request_repaint_after(std::time::Duration::from_millis(16)); // ~60fps ui.request_repaint_after(std::time::Duration::from_millis(16)); // ~60fps
} }
} }
@@ -1,138 +0,0 @@
use std::num::ParseIntError;
use common::prelude::Instruction;
use emu_core::memory::ram::MemoryBank;
use super::Component;
pub struct MemoryInspector {
memory: MemoryBank,
view_size: u32,
view_addr: u32,
visible: bool,
addr_input: String,
}
impl MemoryInspector {
#[must_use]
pub fn new(memory: MemoryBank) -> Self {
Self {
memory,
view_size: 1024,
view_addr: 0,
visible: false,
addr_input: String::from("0x00"),
}
}
}
impl Component for MemoryInspector {
fn title(&self) -> &'static str {
"Memory Inspector"
}
fn visible(&mut self) -> &mut bool {
&mut self.visible
}
fn ui(&mut self, ui: &mut egui::Ui, ctx: &egui::Context) {
// Right column - Memory
ui.vertical(|ui| {
ui.heading("Memory Inspector");
ui.add_space(10.0);
// Address input section
ui.horizontal(|ui| {
ui.label("Address:");
let address_response = ui.add(
egui::TextEdit::singleline(&mut self.addr_input)
.hint_text("0x1000 or 4096")
.desired_width(150.0),
);
ui.add_space(10.0);
// Search button
let search_clicked = ui.button("🔍 Search").clicked();
// Handle Enter key in text field
let enter_pressed =
address_response.lost_focus() && ctx.input(|i| i.key_pressed(egui::Key::Enter));
if search_clicked || enter_pressed {
if let Ok(new) = parse_address(&self.addr_input) {
if new % 4 == 0 {
self.view_addr = new;
} else {
println!("Address must be 4-byte aligned");
}
} else {
println!("Invalid address");
// state.error_log.push("Invalid address".to_string());
}
}
ui.label("(hex or decimal)");
});
ui.add_space(10.0);
// Memory table
egui::ScrollArea::vertical()
.auto_shrink(true)
.id_salt("memory_inspector_scroll")
.show(ui, |ui| {
egui::Grid::new("memory_grid")
.spacing([12.0, 2.0])
.min_col_width(5.0)
.striped(true)
.show(ui, |ui| {
// Header
ui.strong("Address");
for i in 0..4 {
ui.strong(format!("{i:X}"));
}
ui.strong("Decimal");
ui.strong("Instruction");
ui.end_row();
// Memory data (8 bytes per row)
for (offset, word) in (0..self.view_size / 4)
.map(|i| i * 4)
.map(|i| (i, self.memory.read_word(self.view_addr + i)))
{
let row_address = self.view_addr + offset;
ui.monospace(format!("0x{row_address:08X} ({row_address})"));
for byte in word.to_le_bytes() {
ui.monospace(format!("{byte:02X}"));
}
ui.monospace(format!("{word}"));
ui.monospace(format!("{:?}", Instruction(word)));
ui.end_row();
}
});
});
});
}
}
fn parse_address(address: &str) -> Result<u32, ParseIntError> {
if let Some(hex_part) = address.strip_prefix("0x") {
return u32::from_str_radix(hex_part, 16);
}
if let Some(bin_part) = address.strip_prefix("0b") {
return u32::from_str_radix(bin_part, 2);
}
if let Some(oct_part) = address.strip_prefix("0o") {
return u32::from_str_radix(oct_part, 8);
}
address.parse::<u32>()
}
+3 -3
View File
@@ -37,11 +37,11 @@ db program: "++++++++++++++++++++++++++++++++++++++++++++
db error: "Invalid Instruction!" db error: "Invalid Instruction!"
dh counter: 0xFF dh counter: 0xFF
dw stack: 0x10000 dw stack: 0x10000
dw input: 0x30000 dw input_addr: 0x30000
resb data: 1024 resb data: 1024
// set up a stack so we can call functions // set up a stack so we can call functions
_init_stack: _init:
ldw stack, bpr ldw stack, bpr
mov bpr, spr mov bpr, spr
@@ -162,7 +162,7 @@ output:
// ------------------------------------------ // ------------------------------------------
// read a byte into the current cell // read a byte into the current cell
input: input:
ldw input, rg2 ldw input_addr, rg2
jmp loop_end jmp loop_end
// ------------------------------------------ // ------------------------------------------
+7
View File
@@ -0,0 +1,7 @@
[package]
name = "dsx_client"
edition.workspace = true
version.workspace = true
authors.workspace = true
[dependencies]
+3
View File
@@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}
+7
View File
@@ -0,0 +1,7 @@
[package]
name = "dsx_common"
edition.workspace = true
version.workspace = true
authors.workspace = true
[dependencies]
+14
View File
@@ -0,0 +1,14 @@
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
+7
View File
@@ -0,0 +1,7 @@
[package]
name = "dsx_server"
edition.workspace = true
version.workspace = true
authors.workspace = true
[dependencies]
+3
View File
@@ -0,0 +1,3 @@
fn main() {
println!("Hello, world!");
}
-1072
View File
File diff suppressed because it is too large Load Diff