mega refactor:
essentially completely reorganised the project. previous contents of the dsa folder have now been split into emulator/ tooling/ and libraries/ libraries is for common functionality across crates, tooling is for core binaries needed for building and debugging DSA projects, including dsx client/server/build emulator contains the dsa_emulator crate that implements the core DSA emulator, the dsa_gui crate that implements the graphical user interface for the emulator & all future tooling under a unified application, and finally the dsa_cli crate which will eventually provide lightweight wrapper for the core emulator, simply providing a tool for running DSA binaries without the overhead of a heavy GUI.
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "common"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
binrw = "0.15.1"
|
||||
strum = { version = "0.28.0", features = ["derive"] }
|
||||
@@ -0,0 +1,317 @@
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
use crate::prelude::Register;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Location {
|
||||
offset: u16,
|
||||
register: Option<Register>,
|
||||
label: Option<String>,
|
||||
}
|
||||
|
||||
impl Location {
|
||||
pub fn new(offset: u16, register: Option<Register>, label: Option<String>) -> Self {
|
||||
Location {
|
||||
offset,
|
||||
register,
|
||||
label,
|
||||
}
|
||||
}
|
||||
|
||||
// need implementation to intelligently figure out if we need to load immediates
|
||||
// so if the label is a const and > u16::max, we do Lui $label temp, then use temp as an offset
|
||||
// if a register is being used already we have to add the offset to that.
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ImmConst {
|
||||
Label(String),
|
||||
Imm16(u16),
|
||||
Imm32(u32),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, EnumString, Display)]
|
||||
#[strum(ascii_case_insensitive)]
|
||||
pub enum AsmOpcode {
|
||||
Nop,
|
||||
|
||||
Mov,
|
||||
CMov,
|
||||
|
||||
Ldb,
|
||||
Ldbs,
|
||||
Ldh,
|
||||
Ldhs,
|
||||
Ldw,
|
||||
|
||||
Stb,
|
||||
Sth,
|
||||
Stw,
|
||||
|
||||
Lli,
|
||||
Lui,
|
||||
Lwi,
|
||||
|
||||
Jmp,
|
||||
Jez,
|
||||
Jnz,
|
||||
Jic,
|
||||
Jnc,
|
||||
|
||||
Ieq,
|
||||
Ine,
|
||||
Igt,
|
||||
Ige,
|
||||
Ilt,
|
||||
Ile,
|
||||
|
||||
Shl,
|
||||
Shr,
|
||||
Add,
|
||||
Sub,
|
||||
AddI,
|
||||
SubI,
|
||||
|
||||
And,
|
||||
Or,
|
||||
Not,
|
||||
Xor,
|
||||
Nand,
|
||||
Nor,
|
||||
Xnor,
|
||||
|
||||
Int,
|
||||
IRet,
|
||||
Hlt,
|
||||
|
||||
// Function instructions
|
||||
Call,
|
||||
Ret,
|
||||
|
||||
// Stack ops
|
||||
Push,
|
||||
Pop,
|
||||
|
||||
// data directives
|
||||
Db,
|
||||
Dh,
|
||||
Dw,
|
||||
|
||||
// data reservations
|
||||
Resb,
|
||||
Resh,
|
||||
Resw,
|
||||
|
||||
// Function pseudo-instructions
|
||||
Func,
|
||||
Return,
|
||||
|
||||
// include directive
|
||||
Include,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AsmInstruction {
|
||||
Nop,
|
||||
|
||||
// move
|
||||
Mov {
|
||||
src: Register,
|
||||
dest: Register,
|
||||
},
|
||||
CMov {
|
||||
src: Register,
|
||||
dest: Register,
|
||||
condition: Register,
|
||||
},
|
||||
|
||||
// load
|
||||
Ldb {
|
||||
addr: Location,
|
||||
dest: Register,
|
||||
},
|
||||
Ldbs {
|
||||
addr: Location,
|
||||
dest: Register,
|
||||
},
|
||||
Ldh {
|
||||
addr: Location,
|
||||
dest: Register,
|
||||
},
|
||||
Ldhs {
|
||||
addr: Location,
|
||||
dest: Register,
|
||||
},
|
||||
Ldw {
|
||||
addr: Location,
|
||||
dest: Register,
|
||||
},
|
||||
|
||||
// store
|
||||
Stb {
|
||||
src: Register,
|
||||
addr: Location,
|
||||
},
|
||||
Sth {
|
||||
src: Register,
|
||||
addr: Location,
|
||||
},
|
||||
Stw {
|
||||
src: Register,
|
||||
addr: Location,
|
||||
},
|
||||
|
||||
// load immediate
|
||||
Lli {
|
||||
dest: Register,
|
||||
value: ImmConst,
|
||||
},
|
||||
Lui {
|
||||
dest: Register,
|
||||
value: ImmConst,
|
||||
},
|
||||
Lwi {
|
||||
dest: Register,
|
||||
value: ImmConst,
|
||||
},
|
||||
|
||||
// comparison
|
||||
Ieq {
|
||||
src1: Register,
|
||||
src2: Register,
|
||||
dest: Register,
|
||||
},
|
||||
Ine {
|
||||
src1: Register,
|
||||
src2: Register,
|
||||
dest: Register,
|
||||
},
|
||||
Ilt {
|
||||
src1: Register,
|
||||
src2: Register,
|
||||
dest: Register,
|
||||
},
|
||||
Ile {
|
||||
src1: Register,
|
||||
src2: Register,
|
||||
dest: Register,
|
||||
},
|
||||
Igt {
|
||||
src1: Register,
|
||||
src2: Register,
|
||||
dest: Register,
|
||||
},
|
||||
Ige {
|
||||
src1: Register,
|
||||
src2: Register,
|
||||
dest: Register,
|
||||
},
|
||||
|
||||
// jump
|
||||
Jmp {
|
||||
dest: Location,
|
||||
},
|
||||
Jez {
|
||||
dest: Location,
|
||||
cond: Register,
|
||||
},
|
||||
Jnz {
|
||||
dest: Location,
|
||||
cond: Register,
|
||||
},
|
||||
Jic {
|
||||
dest: Location,
|
||||
},
|
||||
Jnc {
|
||||
dest: Location,
|
||||
},
|
||||
|
||||
// bitwise
|
||||
And {
|
||||
src1: Register,
|
||||
src2: Register,
|
||||
dest: Option<Register>,
|
||||
},
|
||||
Nand {
|
||||
src1: Register,
|
||||
src2: Register,
|
||||
dest: Option<Register>,
|
||||
},
|
||||
Or {
|
||||
src1: Register,
|
||||
src2: Register,
|
||||
dest: Option<Register>,
|
||||
},
|
||||
Nor {
|
||||
src1: Register,
|
||||
src2: Register,
|
||||
dest: Register,
|
||||
},
|
||||
Xor {
|
||||
src1: Register,
|
||||
src2: Register,
|
||||
dest: Option<Register>,
|
||||
},
|
||||
Xnor {
|
||||
src1: Register,
|
||||
src2: Register,
|
||||
dest: Option<Register>,
|
||||
},
|
||||
Not {
|
||||
src: Register,
|
||||
dest: Option<Register>,
|
||||
},
|
||||
|
||||
// arithmetic
|
||||
Add {
|
||||
src1: Register,
|
||||
src2: Register,
|
||||
dest: Option<Register>,
|
||||
},
|
||||
Sub {
|
||||
src1: Register,
|
||||
src2: Register,
|
||||
dest: Option<Register>,
|
||||
},
|
||||
Shl {
|
||||
src: Register,
|
||||
shamt_r: Option<Register>,
|
||||
shamt_i: Option<ImmConst>,
|
||||
dest: Option<Register>,
|
||||
},
|
||||
Shr {
|
||||
src: Register,
|
||||
shamt_r: Option<Register>,
|
||||
shamt_i: Option<ImmConst>,
|
||||
dest: Option<Register>,
|
||||
},
|
||||
Addi {
|
||||
src: Register,
|
||||
value: ImmConst,
|
||||
dest: Option<Register>,
|
||||
},
|
||||
Subi {
|
||||
src: Register,
|
||||
value: ImmConst,
|
||||
dest: Option<Register>,
|
||||
},
|
||||
|
||||
// utility
|
||||
Push {
|
||||
src: Register,
|
||||
},
|
||||
Pop {
|
||||
dest: Register,
|
||||
},
|
||||
Call {
|
||||
dest: Location,
|
||||
},
|
||||
Ret,
|
||||
|
||||
// system
|
||||
Int {
|
||||
code: ImmConst,
|
||||
},
|
||||
IRet,
|
||||
Hlt,
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
use crate::isa::instructions::Instruction;
|
||||
use crate::isa::register::Register;
|
||||
|
||||
impl Instruction {
|
||||
#[inline]
|
||||
pub fn opcode(self) -> u8 {
|
||||
((self.0 >> 26) & 0x3F) as u8
|
||||
}
|
||||
|
||||
// Src shared between R type and I type
|
||||
#[inline]
|
||||
pub unsafe fn src1_uc(self) -> Register {
|
||||
unsafe { Register::from_u8_unchecked(((self.0 >> 21) & 0x1F) as u8) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn src1_checked(self) -> Register {
|
||||
Register::from_u8(((self.0 >> 21) & 0x1F) as u8).unwrap_or(Register::Null)
|
||||
}
|
||||
|
||||
// Dest shared between R type and I type
|
||||
#[inline]
|
||||
pub unsafe fn dest_uc(self) -> Register {
|
||||
unsafe { Register::from_u8_unchecked(((self.0 >> 16) & 0x1F) as u8) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn dest_checked(self) -> Register {
|
||||
Register::from_u8(((self.0 >> 16) & 0x1F) as u8).unwrap_or(Register::Null)
|
||||
}
|
||||
|
||||
// Secondary Src for R type only
|
||||
#[inline]
|
||||
pub unsafe fn src2_uc(self) -> Register {
|
||||
unsafe { Register::from_u8_unchecked(((self.0 >> 11) & 0x1F) as u8) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn src2_checked(self) -> Register {
|
||||
Register::from_u8(((self.0 >> 11) & 0x1F) as u8).unwrap_or(Register::Null)
|
||||
}
|
||||
|
||||
// Misc/Conditional reg for R type only
|
||||
#[inline]
|
||||
pub unsafe fn misc_uc(self) -> Register {
|
||||
unsafe { Register::from_u8_unchecked(((self.0 >> 6) & 0x1F) as u8) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn misc_checked(self) -> Register {
|
||||
Register::from_u8(((self.0 >> 6) & 0x1F) as u8).unwrap_or(Register::Null)
|
||||
}
|
||||
|
||||
// Shift amount / misc data for R type only
|
||||
#[inline]
|
||||
pub fn shamt(self) -> u8 {
|
||||
(self.0 & 0x3F) as u8
|
||||
}
|
||||
|
||||
// 16 Bit immediate for I type only
|
||||
#[inline]
|
||||
pub fn imm16(self) -> u16 {
|
||||
self.0 as u16
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use crate::isa::instructions::{Instruction, Opcode};
|
||||
use crate::isa::register::Register;
|
||||
|
||||
impl From<u32> for Instruction {
|
||||
#[inline]
|
||||
fn from(word: u32) -> Self {
|
||||
Self(word)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Instruction {
|
||||
type Target = u32;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Instruction {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Instruction {
|
||||
// ---- R Type Builders ----
|
||||
|
||||
#[inline]
|
||||
fn build_r(
|
||||
opcode: Opcode,
|
||||
src1: Register,
|
||||
dest: Register,
|
||||
src2: Register,
|
||||
misc: Register,
|
||||
shamt: u8,
|
||||
) -> Self {
|
||||
Self(
|
||||
((opcode as u32) << 26)
|
||||
| ((src1 as u32) << 21)
|
||||
| ((dest as u32) << 16)
|
||||
| ((src2 as u32) << 11)
|
||||
| ((misc as u32) << 6)
|
||||
| ((shamt as u32) & 0x3F),
|
||||
)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn build_i(opcode: Opcode, src: Register, dest: Register, imm: u16) -> Self {
|
||||
Self(((opcode as u32) << 26) | ((src as u32) << 21) | ((dest as u32) << 16) | (imm as u32))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn build_noarg(opcode: Opcode) -> Self {
|
||||
Self((opcode as u32) << 26)
|
||||
}
|
||||
|
||||
/// No operation.
|
||||
pub fn nop() -> Self {
|
||||
Self::build_noarg(Opcode::Nop)
|
||||
}
|
||||
|
||||
// ---- Move ----
|
||||
|
||||
/// Move a value from `src` to `dest`.
|
||||
pub fn mov(src: Register, dest: Register) -> Self {
|
||||
Self::build_r(Opcode::Mov, src, dest, Register::Zero, Register::Zero, 0)
|
||||
}
|
||||
|
||||
/// Conditional move: if `cmp` is true then move `src` to `dest`.
|
||||
pub fn cmov(src: Register, dest: Register, cmp: Register) -> Self {
|
||||
Self::build_r(Opcode::CMov, src, dest, cmp, Register::Zero, 0)
|
||||
}
|
||||
|
||||
// ---- Load ----
|
||||
|
||||
/// Load byte from memory at `src + offset` into `dest`.
|
||||
pub fn ldb(src: Register, dest: Register, offset: u16) -> Self {
|
||||
Self::build_i(Opcode::Ldb, src, dest, offset)
|
||||
}
|
||||
|
||||
/// Load signed byte (sign‑extended) from memory at `src + offset` into `dest`.
|
||||
pub fn ldbs(src: Register, dest: Register, offset: u16) -> Self {
|
||||
Self::build_i(Opcode::Ldbs, src, dest, offset)
|
||||
}
|
||||
|
||||
/// Load halfword from memory at `src + offset` into `dest`.
|
||||
pub fn ldh(src: Register, dest: Register, offset: u16) -> Self {
|
||||
Self::build_i(Opcode::Ldh, src, dest, offset)
|
||||
}
|
||||
|
||||
/// Load signed halfword (sign‑extended) from memory at `src + offset` into `dest`.
|
||||
pub fn ldhs(src: Register, dest: Register, offset: u16) -> Self {
|
||||
Self::build_i(Opcode::Ldhs, src, dest, offset)
|
||||
}
|
||||
|
||||
/// Load word from memory at `src + offset` into `dest`.
|
||||
pub fn ldw(src: Register, dest: Register, offset: u16) -> Self {
|
||||
Self::build_i(Opcode::Ldw, src, dest, offset)
|
||||
}
|
||||
|
||||
// ---- Store ----
|
||||
|
||||
/// Store byte from `src` to memory at `dest + offset`.
|
||||
pub fn stb(src: Register, dest: Register, offset: u16) -> Self {
|
||||
Self::build_i(Opcode::Stb, src, dest, offset)
|
||||
}
|
||||
|
||||
/// Store halfword from `src` to memory at `dest + offset`.
|
||||
pub fn sth(src: Register, dest: Register, offset: u16) -> Self {
|
||||
Self::build_i(Opcode::Sth, src, dest, offset)
|
||||
}
|
||||
|
||||
/// Store word from `src` to memory at `dest + offset`.
|
||||
pub fn stw(src: Register, dest: Register, offset: u16) -> Self {
|
||||
Self::build_i(Opcode::Stw, src, dest, offset)
|
||||
}
|
||||
|
||||
// ---- Load Immediate ----
|
||||
|
||||
/// Load lower 16 bits of an immediate into `dest`.
|
||||
pub fn lli(dest: Register, imm: u16) -> Self {
|
||||
Self::build_i(Opcode::Lli, Register::Zero, dest, imm)
|
||||
}
|
||||
|
||||
/// Load upper 16 bits of an immediate into `dest`.
|
||||
pub fn lui(dest: Register, imm: u16) -> Self {
|
||||
Self::build_i(Opcode::Lui, Register::Zero, dest, imm)
|
||||
}
|
||||
|
||||
/// Load a full 32‑bit immediate using an `lli` + `lui` pair.
|
||||
pub fn load_imm32(dest: Register, imm: u32) -> [Self; 2] {
|
||||
[
|
||||
Self::lli(dest, imm as u16),
|
||||
Self::lui(dest, (imm >> 16) as u16),
|
||||
]
|
||||
}
|
||||
|
||||
// ---- Comparison ----
|
||||
|
||||
/// Set `dest` to 1 if `sr1 == sr2`, else 0.
|
||||
pub fn ieq(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||
Self::build_r(Opcode::Ieq, sr1, dest, sr2, Register::Zero, 0)
|
||||
}
|
||||
|
||||
/// Set `dest` to 1 if `sr1 != sr2`, else 0.
|
||||
pub fn ine(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||
Self::build_r(Opcode::Ine, sr1, dest, sr2, Register::Zero, 0)
|
||||
}
|
||||
|
||||
/// Set `dest` to 1 if `sr1 < sr2`, else 0.
|
||||
pub fn ilt(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||
Self::build_r(Opcode::Ilt, sr1, dest, sr2, Register::Zero, 0)
|
||||
}
|
||||
|
||||
/// Set `dest` to 1 if `sr1 <= sr2`, else 0.
|
||||
pub fn ile(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||
Self::build_r(Opcode::Ile, sr1, dest, sr2, Register::Zero, 0)
|
||||
}
|
||||
|
||||
/// Set `dest` to 1 if `sr1 > sr2`, else 0.
|
||||
pub fn igt(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||
Self::build_r(Opcode::Igt, sr1, dest, sr2, Register::Zero, 0)
|
||||
}
|
||||
|
||||
/// Set `dest` to 1 if `sr1 >= sr2`, else 0.
|
||||
pub fn ige(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||
Self::build_r(Opcode::Ige, sr1, dest, sr2, Register::Zero, 0)
|
||||
}
|
||||
|
||||
// ---- Jump ----
|
||||
|
||||
/// Unconditional jump to address in `addr` with offset.
|
||||
pub fn jmp(addr: Register, offset: u16) -> Self {
|
||||
Self::build_i(Opcode::Jmp, Register::Zero, addr, offset)
|
||||
}
|
||||
|
||||
/// Jump if zero flag set (`jez`).
|
||||
pub fn jez(cmp: Register, addr: Register, offset: u16) -> Self {
|
||||
Self::build_i(Opcode::Jez, cmp, addr, offset)
|
||||
}
|
||||
|
||||
/// Jump if zero flag clear (`jnz`).
|
||||
pub fn jnz(cmp: Register, addr: Register, offset: u16) -> Self {
|
||||
Self::build_i(Opcode::Jnz, cmp, addr, offset)
|
||||
}
|
||||
|
||||
/// Jump if carry set (`jic`).
|
||||
pub fn jic(addr: Register, offset: u16) -> Self {
|
||||
Self::build_i(Opcode::Jic, Register::Zero, addr, offset)
|
||||
}
|
||||
|
||||
/// Jump if carry clear (`jnc`).
|
||||
pub fn jnc(addr: Register, offset: u16) -> Self {
|
||||
Self::build_i(Opcode::Jnc, Register::Zero, addr, offset)
|
||||
}
|
||||
|
||||
// ---- Bitwise ----
|
||||
|
||||
/// Bitwise AND of `sr1` and `sr2`, result in `dest`.
|
||||
pub fn and(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||
Self::build_r(Opcode::And, sr1, dest, sr2, Register::Zero, 0)
|
||||
}
|
||||
|
||||
/// Bitwise NAND of `sr1` and `sr2`, result in `dest`.
|
||||
pub fn nand(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||
Self::build_r(Opcode::Nand, sr1, dest, sr2, Register::Zero, 0)
|
||||
}
|
||||
|
||||
/// Bitwise OR of `sr1` and `sr2`, result in `dest`.
|
||||
pub fn or(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||
Self::build_r(Opcode::Or, sr1, dest, sr2, Register::Zero, 0)
|
||||
}
|
||||
|
||||
/// Bitwise NOR of `sr1` and `sr2`, result in `dest`.
|
||||
pub fn nor(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||
Self::build_r(Opcode::Nor, sr1, dest, sr2, Register::Zero, 0)
|
||||
}
|
||||
|
||||
/// Bitwise XOR of `sr1` and `sr2`, result in `dest`.
|
||||
pub fn xor(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||
Self::build_r(Opcode::Xor, sr1, dest, sr2, Register::Zero, 0)
|
||||
}
|
||||
|
||||
/// Bitwise XNOR of `sr1` and `sr2`, result in `dest`.
|
||||
pub fn xnor(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||
Self::build_r(Opcode::Xnor, sr1, dest, sr2, Register::Zero, 0)
|
||||
}
|
||||
|
||||
/// Bitwise NOT of `src`, result in `dest`.
|
||||
pub fn not(src: Register, dest: Register) -> Self {
|
||||
Self::build_r(Opcode::Not, src, dest, Register::Zero, Register::Zero, 0)
|
||||
}
|
||||
|
||||
// ---- Arithmetic ----
|
||||
|
||||
/// Add `sr1` and `sr2`, store result in `dest`.
|
||||
pub fn add(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||
Self::build_r(Opcode::Add, sr1, dest, sr2, Register::Zero, 0)
|
||||
}
|
||||
|
||||
/// Subtract `sr2` from `sr1`, store result in `dest`.
|
||||
pub fn sub(sr1: Register, sr2: Register, dest: Register) -> Self {
|
||||
Self::build_r(Opcode::Sub, sr1, dest, sr2, Register::Zero, 0)
|
||||
}
|
||||
|
||||
/// Shift left logical: shift `src` by (`rshamt` + `ishamt`) bits into `dest`.
|
||||
pub fn shl(src: Register, rshamt: Register, dest: Register, ishamt: u8) -> Self {
|
||||
debug_assert!(ishamt < 64);
|
||||
Self::build_r(Opcode::Shl, src, dest, rshamt, Register::Zero, ishamt)
|
||||
}
|
||||
|
||||
/// Shift right logical: shift `src` by (`rshamt` + `ishamt`) bits into `dest`.
|
||||
pub fn shr(src: Register, rshamt: Register, dest: Register, ishamt: u8) -> Self {
|
||||
debug_assert!(ishamt < 64);
|
||||
Self::build_r(Opcode::Shr, src, dest, rshamt, Register::Zero, ishamt)
|
||||
}
|
||||
|
||||
/// Add immediate `imm` to `sr1`, store result in `dest`.
|
||||
pub fn addi(sr1: Register, dest: Register, imm: u16) -> Self {
|
||||
Self::build_i(Opcode::Addi, sr1, dest, imm)
|
||||
}
|
||||
|
||||
/// Subtract immediate `imm` from `sr1`, store result in `dest`.
|
||||
pub fn subi(sr1: Register, dest: Register, imm: u16) -> Self {
|
||||
Self::build_i(Opcode::Subi, sr1, dest, imm)
|
||||
}
|
||||
|
||||
// ---- Util -----
|
||||
pub fn call(addr: Register, offset: u16) -> Self {
|
||||
Self::build_i(Opcode::Call, Register::Zero, addr, offset)
|
||||
}
|
||||
|
||||
pub fn ret() -> Self {
|
||||
Self::build_noarg(Opcode::Ret)
|
||||
}
|
||||
|
||||
pub fn push(reg: Register) -> Self {
|
||||
Self::build_r(
|
||||
Opcode::Push,
|
||||
reg,
|
||||
Register::Zero,
|
||||
Register::Zero,
|
||||
Register::Zero,
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn pop(reg: Register) -> Self {
|
||||
Self::build_r(
|
||||
Opcode::Pop,
|
||||
Register::Zero,
|
||||
reg,
|
||||
Register::Zero,
|
||||
Register::Zero,
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
// ---- System ----
|
||||
|
||||
/// Trigger an interrupt with the given 6‑bit code.
|
||||
pub fn int(code: u8) -> Self {
|
||||
debug_assert!(code < 64);
|
||||
Self::build_i(Opcode::Int, Register::Zero, Register::Zero, code as u16)
|
||||
}
|
||||
|
||||
/// Return from interrupt.
|
||||
pub fn irt() -> Self {
|
||||
Self::build_noarg(Opcode::IRet)
|
||||
}
|
||||
|
||||
/// Halt execution.
|
||||
pub fn hlt() -> Self {
|
||||
Self::build_r(
|
||||
Opcode::Hlt,
|
||||
Register::Zero,
|
||||
Register::Zero,
|
||||
Register::Zero,
|
||||
Register::Zero,
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
/// Raw data (not an instruction)
|
||||
pub fn data(value: u32) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
pub mod decode;
|
||||
pub mod encode;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(transparent)]
|
||||
pub struct Instruction(pub u32);
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Opcode {
|
||||
Nop = 0,
|
||||
|
||||
// move
|
||||
Mov = 1,
|
||||
CMov = 2,
|
||||
|
||||
// load
|
||||
Ldb = 3,
|
||||
Ldbs = 4,
|
||||
Ldh = 5,
|
||||
Ldhs = 6,
|
||||
Ldw = 7,
|
||||
|
||||
// store
|
||||
Stb = 8,
|
||||
Sth = 9,
|
||||
Stw = 10,
|
||||
|
||||
// load immediate
|
||||
Lli = 11,
|
||||
Lui = 12,
|
||||
|
||||
// comparison
|
||||
Ieq = 13,
|
||||
Ine = 14,
|
||||
Ilt = 15,
|
||||
Ile = 16,
|
||||
Igt = 17,
|
||||
Ige = 18,
|
||||
|
||||
// jump
|
||||
Jmp = 19,
|
||||
Jez = 20,
|
||||
Jnz = 21,
|
||||
Jic = 22,
|
||||
Jnc = 23,
|
||||
|
||||
// bitwise
|
||||
And = 24,
|
||||
Nand = 25,
|
||||
Or = 26,
|
||||
Nor = 27,
|
||||
Xor = 28,
|
||||
Xnor = 29,
|
||||
Not = 30,
|
||||
|
||||
// arithmetic
|
||||
Add = 31,
|
||||
Sub = 32,
|
||||
Shl = 33,
|
||||
Shr = 34,
|
||||
Addi = 35,
|
||||
Subi = 36,
|
||||
|
||||
// utility
|
||||
Push = 37,
|
||||
Pop = 38,
|
||||
Call = 39,
|
||||
Ret = 40,
|
||||
|
||||
// system
|
||||
Int = 41,
|
||||
IRet = 42,
|
||||
// Acs = 43,
|
||||
Hlt = 44,
|
||||
}
|
||||
|
||||
impl Opcode {
|
||||
#[inline]
|
||||
pub fn from_u8(val: u8) -> Option<Self> {
|
||||
if val <= Self::Hlt as u8 {
|
||||
Some(unsafe { std::mem::transmute(val) })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use std::fmt;
|
||||
|
||||
impl fmt::Debug for Instruction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let opcode = Opcode::from_u8(self.opcode());
|
||||
|
||||
match opcode {
|
||||
None => write!(f, "Instruction(INVALID OPCODE {:#04x})", self.opcode()),
|
||||
|
||||
Some(op @ (Opcode::Nop | Opcode::Hlt | Opcode::Ret | Opcode::IRet)) => {
|
||||
write!(f, "{op:?}")
|
||||
}
|
||||
|
||||
Some(op @ Opcode::Push) => {
|
||||
write!(f, "{op:?} {src1:?}", op = op, src1 = self.src1_checked())
|
||||
}
|
||||
|
||||
Some(op @ Opcode::Pop) => {
|
||||
write!(f, "{op:?} {dest:?}", op = op, dest = self.dest_checked())
|
||||
}
|
||||
|
||||
// I-type: single reg + immediate
|
||||
Some(op @ (Opcode::Lli | Opcode::Lui)) => {
|
||||
write!(
|
||||
f,
|
||||
"{op:?} {dest:?}, {imm:#06x}",
|
||||
op = op,
|
||||
dest = self.dest_checked(),
|
||||
imm = self.imm16()
|
||||
)
|
||||
}
|
||||
|
||||
// I-type: no reg, immediate only
|
||||
Some(op @ Opcode::Int) => {
|
||||
write!(f, "{op:?} {imm:#06x}", op = op, imm = self.imm16())
|
||||
}
|
||||
|
||||
// I-type: src + dest + immediate
|
||||
Some(
|
||||
op @ (Opcode::Ldb
|
||||
| Opcode::Ldbs
|
||||
| Opcode::Ldh
|
||||
| Opcode::Ldhs
|
||||
| Opcode::Ldw
|
||||
| Opcode::Stb
|
||||
| Opcode::Sth
|
||||
| Opcode::Stw
|
||||
| Opcode::Addi
|
||||
| Opcode::Subi),
|
||||
) => {
|
||||
write!(
|
||||
f,
|
||||
"{op:?} {src:?}, {dest:?}, {imm:#06x}",
|
||||
op = op,
|
||||
src = self.src1_checked(),
|
||||
dest = self.dest_checked(),
|
||||
imm = self.imm16()
|
||||
)
|
||||
}
|
||||
|
||||
// I-type: cmp + addr + immediate (conditional jumps)
|
||||
Some(op @ (Opcode::Jez | Opcode::Jnz)) => {
|
||||
write!(
|
||||
f,
|
||||
"{op:?} {cmp:?}, {addr:?}, {imm:#06x}",
|
||||
op = op,
|
||||
cmp = self.src1_checked(),
|
||||
addr = self.dest_checked(),
|
||||
imm = self.imm16()
|
||||
)
|
||||
}
|
||||
|
||||
// I-type: addr + immediate only (unconditional/carry jumps)
|
||||
Some(op @ (Opcode::Jmp | Opcode::Call | Opcode::Jic | Opcode::Jnc)) => {
|
||||
write!(
|
||||
f,
|
||||
"{op:?} {addr:?}, {imm:#06x}",
|
||||
op = op,
|
||||
addr = self.dest_checked(),
|
||||
imm = self.imm16()
|
||||
)
|
||||
}
|
||||
|
||||
// R-type: src + dest (two reg)
|
||||
Some(op @ (Opcode::Mov | Opcode::Not)) => {
|
||||
write!(
|
||||
f,
|
||||
"{op:?} {src:?}, {dest:?}",
|
||||
op = op,
|
||||
src = self.src1_checked(),
|
||||
dest = self.dest_checked()
|
||||
)
|
||||
}
|
||||
|
||||
// R-type: src + dest + cmp (cmov)
|
||||
Some(op @ Opcode::CMov) => {
|
||||
write!(
|
||||
f,
|
||||
"{op:?} {src:?}, {dest:?}, {cmp:?}",
|
||||
op = op,
|
||||
src = self.src1_checked(),
|
||||
dest = self.dest_checked(),
|
||||
cmp = self.src2_checked()
|
||||
)
|
||||
}
|
||||
|
||||
// R-type: sr1 + sr2 + dest (arithmetic/bitwise/comparison)
|
||||
Some(
|
||||
op @ (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),
|
||||
) => {
|
||||
write!(
|
||||
f,
|
||||
"{op:?} {sr1:?}, {sr2:?}, {dest:?}",
|
||||
op = op,
|
||||
sr1 = self.src1_checked(),
|
||||
sr2 = self.src2_checked(),
|
||||
dest = self.dest_checked()
|
||||
)
|
||||
}
|
||||
|
||||
// R-type: src + rshamt + dest + ishamt
|
||||
Some(op @ (Opcode::Shl | Opcode::Shr)) => {
|
||||
write!(
|
||||
f,
|
||||
"{op:?} {src:?}, {dest:?}, {rshamt:?} + {ishamt}",
|
||||
op = op,
|
||||
src = self.src1_checked(),
|
||||
dest = self.dest_checked(),
|
||||
rshamt = self.src2_checked(),
|
||||
ishamt = self.shamt()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod register;
|
||||
pub mod instructions;
|
||||
@@ -0,0 +1,82 @@
|
||||
use std::fmt;
|
||||
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, EnumString)]
|
||||
#[repr(u8)]
|
||||
#[non_exhaustive]
|
||||
#[strum(ascii_case_insensitive)]
|
||||
pub enum Register {
|
||||
// general purpose
|
||||
Rg0 = 0,
|
||||
Rg1 = 1,
|
||||
Rg2 = 2,
|
||||
Rg3 = 3,
|
||||
Rg4 = 4,
|
||||
Rg5 = 5,
|
||||
Rg6 = 6,
|
||||
Rg7 = 7,
|
||||
Rg8 = 8,
|
||||
Rg9 = 9,
|
||||
Rga = 10,
|
||||
Rgb = 11,
|
||||
Rgc = 12,
|
||||
Rgd = 13,
|
||||
Rge = 14,
|
||||
Rgf = 15,
|
||||
|
||||
Acc = 16, // temp/scratch register
|
||||
|
||||
// stack registers
|
||||
Spr = 17,
|
||||
Bpr = 18,
|
||||
|
||||
Idr = 20, // points to interrupt descriptor table
|
||||
Mmr = 21, // points to page tables
|
||||
|
||||
// system - read only
|
||||
Zero = 22, // always reads as zero.
|
||||
Sts = 23, // status register for flags
|
||||
Cir = 24, // current instruction register
|
||||
Pcx = 25, // program counter
|
||||
|
||||
#[strum(disabled, to_string = "Ret")]
|
||||
Ret = 19, // used under the hood by call/ret instructions but not used directly in asm.
|
||||
|
||||
#[default]
|
||||
#[strum(disabled, to_string = "Null")] // don't include this in FromStr impl
|
||||
Null = 26,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Register {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Register {
|
||||
/// Total number of registers, not counting Null as it's not a real register.
|
||||
/// reading or writing to Null is undefined behaviour!!
|
||||
pub const COUNT: u8 = Self::Null as u8;
|
||||
|
||||
/// # Safety
|
||||
/// the caller must ensure that the index is always within the range 0..COUNT, failing to do so will result in undefined behaviour.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub unsafe fn from_u8_unchecked(idx: u8) -> Self {
|
||||
debug_assert!(
|
||||
idx < Self::COUNT,
|
||||
"Tried to access a null register using the unchecked method. undefined behaviour alert!"
|
||||
);
|
||||
unsafe { std::mem::transmute(idx) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn from_u8(idx: u8) -> Result<Self, ()> {
|
||||
if idx >= Self::COUNT {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
Ok(unsafe { Self::from_u8_unchecked(idx) })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
pub mod asm;
|
||||
pub mod isa;
|
||||
pub mod process;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::isa::instructions::Instruction;
|
||||
pub use crate::isa::register::Register;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
use std::fmt::Debug;
|
||||
use std::sync::{mpsc, Arc};
|
||||
use crate::process::AgentSink::{Callback, Channel};
|
||||
|
||||
|
||||
pub struct ProcessAgent {
|
||||
sink: Arc<AgentSink>,
|
||||
}
|
||||
|
||||
impl ProcessAgent {
|
||||
pub fn new(tx: mpsc::Sender<String>) -> Self {
|
||||
Self { sink: Arc::new(Channel(tx)) }
|
||||
}
|
||||
|
||||
fn new_sync(f: impl Fn(&str) + Send + Sync + 'static) -> Self {
|
||||
Self { sink: Arc::new(Callback(Box::new(f))) }
|
||||
}
|
||||
|
||||
pub fn log(&self, msg: impl Into<String>) {
|
||||
let msg = msg.into();
|
||||
match &*self.sink {
|
||||
Channel(tx) => { let _ = tx.send(msg); }
|
||||
Callback(f) => f(&msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum AgentSink {
|
||||
Channel(mpsc::Sender<String>),
|
||||
Callback(Box<dyn Fn(&str) + Send + Sync>)
|
||||
}
|
||||
|
||||
pub trait TraceableProcess: Debug + Send + 'static {
|
||||
type Output: Send + 'static;
|
||||
type Error: std::error::Error + Send + 'static;
|
||||
|
||||
fn label(&self) -> String {
|
||||
format!("{self:?}")
|
||||
}
|
||||
|
||||
fn run(self: Box<Self>, ctx: ProcessAgent) -> Result<Self::Output, Self::Error>;
|
||||
|
||||
///
|
||||
fn run_sync(self) -> Result<Self::Output, Self::Error>
|
||||
where
|
||||
Self: Sized
|
||||
{
|
||||
Self::run(
|
||||
Box::new(self),
|
||||
ProcessAgent::new_sync(|line| println!("{line}") )
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "dsa_formats"
|
||||
edition.workspace = true
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
|
||||
[dependencies]
|
||||
binrw = "0.15.2"
|
||||
common = { path = "../common" }
|
||||
@@ -0,0 +1,252 @@
|
||||
use std::fs::File;
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
|
||||
use std::path::PathBuf;
|
||||
use binrw::{binrw, BinRead, BinReaderExt, BinWriterExt, BinWrite};
|
||||
use std::fmt;
|
||||
use common::isa::instructions::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 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 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 dsa_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);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
pub mod binary_dse;
|
||||
pub mod binary_dso;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
// #[test]
|
||||
// fn it_works() {
|
||||
// let result = add(2, 2);
|
||||
// assert_eq!(result, 4);
|
||||
// }
|
||||
}
|
||||
Reference in New Issue
Block a user