second commit - still messy
This commit is contained in:
parent
427e139161
commit
18a5f17b80
117
spec.md
Normal file
117
spec.md
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
### Disclaimer
|
||||||
|
|
||||||
|
This is a fantasy architecture on which I intend to write fantasy compilers. It was born out of the
|
||||||
|
"fuck around and find out" philosophy, and is a toy project. I will change a lot of stuff as I learn
|
||||||
|
how it's done in the real world. For now, I'm just gonna guess and have fun.
|
||||||
|
|
||||||
|
Since I'm studying riscV, this will be a lot riscv inspired.
|
||||||
|
|
||||||
|
# The GRAVEJIT virtual machine
|
||||||
|
|
||||||
|
The gravejit virtual machine sports 16 16-bit registers (plus the program counter!) and 16 operations.
|
||||||
|
|
||||||
|
Here is the list of registers togheter with memonics.
|
||||||
|
|
||||||
|
0 : zero // register 0 is always 0.
|
||||||
|
1 : ra // return address
|
||||||
|
2 : sp // stack pointer
|
||||||
|
3 : t0 // temporary
|
||||||
|
4 : t1
|
||||||
|
5 : t2
|
||||||
|
6 : t3
|
||||||
|
7 : a0 // function arguments
|
||||||
|
8 : a1
|
||||||
|
9 : a2
|
||||||
|
10: a3
|
||||||
|
11: s0 // saved registers
|
||||||
|
12: s1
|
||||||
|
13: s2
|
||||||
|
14: s3
|
||||||
|
15: t4 // don't know what to do with this
|
||||||
|
|
||||||
|
pc: program counter.
|
||||||
|
|
||||||
|
## ISA
|
||||||
|
|
||||||
|
opcode | memonic | format | description
|
||||||
|
|
||||||
|
0000 | NOP | just 0s'| Does nothing.
|
||||||
|
0001 | ADD s0 s1 s2 | R | s0 = s1 + s2
|
||||||
|
0010 | SUB s0 s1 s2 | R | s0 = s1 - s2
|
||||||
|
0011 | AND s0 s1 s2 | R | s0 = s1 && s2
|
||||||
|
0100 | XOR s0 s1 s2 | R | s0 = s1 xor s2
|
||||||
|
0101 | SLL s0 s1 s2 | R | s0 = s1 << s2
|
||||||
|
0110 | SLI s0 c | I | s0 = s0 << c
|
||||||
|
0111 | ADDI s0 c | I | s0 = s0 + c
|
||||||
|
1000 | BEQ s0 s1 s2 | R | if (s1 == s2) -> pc = s0
|
||||||
|
1001 | BGT s0 s1 s2 | R | if (s1 > s2) -> pc = s0
|
||||||
|
1010 | JAL s0 s1 c | J | s0 = pc+1; pc += s1 + c;
|
||||||
|
1011 |
|
||||||
|
1100 | LOAD s0 s1 s2 | R | loads s1 + shift by s2 in s0
|
||||||
|
1101 | STORE s0 s1 s2| R | stores s0 in address s1 + shift by s2
|
||||||
|
1110 | CALL s0 c | I | performs system call
|
||||||
|
1111 | HALT | just 1s'| halt, and possibly catch fire.
|
||||||
|
|
||||||
|
|
||||||
|
### Operation formats:
|
||||||
|
|
||||||
|
Each istruction is 16 bits long.
|
||||||
|
The first 4 most-significant bits are the opcode.
|
||||||
|
Constants (c in the above table) are always considered signed, and written in
|
||||||
|
two's compliment. Sign extension also takes place whenever needed.
|
||||||
|
i.e., to make an immediate subtraction, one just needs to add a negative number.
|
||||||
|
|
||||||
|
#### R-type:
|
||||||
|
opcode: 4 bits
|
||||||
|
dest register: 4 bits
|
||||||
|
source 1 register: 4 bits
|
||||||
|
source 2 register: 4 bits
|
||||||
|
|
||||||
|
example: ADD s0 s1 s2 = 0001 1011 1100 1101
|
||||||
|
|
||||||
|
#### I-type
|
||||||
|
opcode: 4 bits
|
||||||
|
dest register: 4 bits
|
||||||
|
constant: 8 bits
|
||||||
|
|
||||||
|
example:
|
||||||
|
ADDI s0 28 = 0111 1011 00011100
|
||||||
|
ADDI s0 -2 = 0111 1011 11111110
|
||||||
|
|
||||||
|
|
||||||
|
#### J-Type
|
||||||
|
opcode: 4 bits
|
||||||
|
dest register: 4 bits
|
||||||
|
jump address register: 4 bits
|
||||||
|
constant: 4 bits
|
||||||
|
|
||||||
|
|
||||||
|
The constant is added to the value of the second register argument.
|
||||||
|
|
||||||
|
### JIT's system calls:
|
||||||
|
|
||||||
|
the `CALL` instruction is a bit of a hack because I want to load more functionality into the thing.
|
||||||
|
The JIT can decide what to do with the register s0 and the number c.
|
||||||
|
It should be possible to open files, write files, read stdin, write to stdout, etc...
|
||||||
|
|
||||||
|
#### io\_vec: first systemcall environment
|
||||||
|
|
||||||
|
Working on this, quick and dirty.
|
||||||
|
|
||||||
|
### Binary executable format:
|
||||||
|
|
||||||
|
Binary files start with two 16 bit numbers, a constant and a length N, followed by a list of
|
||||||
|
length N of pairs 16 bit numbers. This is the header of the file.
|
||||||
|
|
||||||
|
The initial constant is currently unused and unimportant. In this draft-toy-spec, the initial constant
|
||||||
|
is always 39979.
|
||||||
|
|
||||||
|
The first number is an offset, and the second number is a size N in bytes.
|
||||||
|
|
||||||
|
The offset points at a null-terminated UTF-8 (yes.) string, located offset\*16 bits to the right after the end of the header in the binary file, followed by arbitrary binary content of size N\*16 bits.
|
||||||
|
|
||||||
|
The utf-8 string cannot contain the null character anywhere, as that will be used as terminator.
|
||||||
|
|
||||||
|
This represents a "symbols table" of the binary file, where functions and data can be stored.
|
||||||
|
|
||||||
|
There must exist a symbol named "main", and it must point to a function: this will be the entrypoint to our program.
|
35
src/assembler/AST.rs
Normal file
35
src/assembler/AST.rs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
use crate::cpu::Word;
|
||||||
|
|
||||||
|
type RegisterMem = String;
|
||||||
|
|
||||||
|
pub type ConstId = String;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Const {
|
||||||
|
CS(ConstId),
|
||||||
|
C(u8),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Operation {
|
||||||
|
NOP,
|
||||||
|
HALT,
|
||||||
|
// R type
|
||||||
|
ADD(RegisterMem, RegisterMem, RegisterMem),
|
||||||
|
SUB(RegisterMem, RegisterMem, RegisterMem),
|
||||||
|
AND(RegisterMem, RegisterMem, RegisterMem),
|
||||||
|
XOR(RegisterMem, RegisterMem, RegisterMem),
|
||||||
|
SLL(RegisterMem, RegisterMem, RegisterMem),
|
||||||
|
BEQ(RegisterMem, RegisterMem, RegisterMem),
|
||||||
|
BGT(RegisterMem, RegisterMem, RegisterMem),
|
||||||
|
LOAD(RegisterMem, RegisterMem, RegisterMem),
|
||||||
|
STORE(RegisterMem, RegisterMem, RegisterMem),
|
||||||
|
|
||||||
|
// I Type
|
||||||
|
SLI(RegisterMem, Const),
|
||||||
|
ADDI(RegisterMem, Const),
|
||||||
|
CALL(RegisterMem, Const),
|
||||||
|
|
||||||
|
// J Type
|
||||||
|
JAL(RegisterMem, RegisterMem, Word),
|
||||||
|
}
|
5
src/assembler/mod.rs
Normal file
5
src/assembler/mod.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
mod AST;
|
||||||
|
mod tests;
|
||||||
|
mod parser;
|
||||||
|
|
||||||
|
struct Assembler {}
|
246
src/assembler/parser.rs
Normal file
246
src/assembler/parser.rs
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
use crate::cpu::Registers;
|
||||||
|
|
||||||
|
use super::AST::{Operation, Const};
|
||||||
|
use Operation::*;
|
||||||
|
|
||||||
|
type Loc = u16;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ParseError {
|
||||||
|
BadSectionHeader,
|
||||||
|
UnknownSectionKind,
|
||||||
|
UnexpectedEOF,
|
||||||
|
BadSectionContent,
|
||||||
|
BadInstruction
|
||||||
|
}
|
||||||
|
|
||||||
|
/// represents the state of our parser.
|
||||||
|
pub struct Parser {
|
||||||
|
loc: u16, // current number of operations parsed.
|
||||||
|
symtable: Vec<(String, u16)>, // symbols encountered, position.
|
||||||
|
pub input: Vec<String>, // input file
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parser {
|
||||||
|
pub fn new(i: String) -> Self {
|
||||||
|
Parser {
|
||||||
|
loc: 0,
|
||||||
|
symtable: vec![],
|
||||||
|
input: sanitize(i),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// removes comments and whitespaces, and splits the input in lines.
|
||||||
|
fn sanitize(i: String) -> Vec<String> {
|
||||||
|
i.lines()
|
||||||
|
.map(|x| remove_comments(x))
|
||||||
|
.map(|x| x.trim())
|
||||||
|
.filter(|x| *x != "")
|
||||||
|
.map(|x| x.to_string())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_comments(i: &str) -> &str {
|
||||||
|
if let Some(end) = i.find(';') {
|
||||||
|
return &i[0..end];
|
||||||
|
} else {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Checks if the string i starts with pat.
|
||||||
|
/// Returns the rest of the input string on success
|
||||||
|
/// else, returns None
|
||||||
|
fn match_string(i: &str, pat: &str) -> Option<String> {
|
||||||
|
|
||||||
|
let mut in_chars = i.chars();
|
||||||
|
|
||||||
|
for pat_c in pat.chars() {
|
||||||
|
if let Some(c) = in_chars.next() {
|
||||||
|
if c != pat_c {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
let rest = in_chars.collect();
|
||||||
|
|
||||||
|
return Some(rest);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Matches till the "stop" string is found.
|
||||||
|
/// Returns a tuple containing the preceeding string and
|
||||||
|
/// the rest of the input string.
|
||||||
|
///
|
||||||
|
/// Ex: assert_eq!(Ok("Lorem ", " Ipsum"), match_alpha_till("Lorem X Ipsum", "X") );
|
||||||
|
///
|
||||||
|
fn take_alpha_till(i: &str, stop: &str) -> Option<(String, String)> {
|
||||||
|
|
||||||
|
//
|
||||||
|
if let Some((matched, rest)) = i.split_once(stop) {
|
||||||
|
return Some((matched.to_string(), rest.to_string()))
|
||||||
|
} else {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Matches inside the `start` and `stop` delimiters.
|
||||||
|
/// Return a tuple with the string in between the two
|
||||||
|
/// togheter with the rest of the string.
|
||||||
|
fn take_between(i: &str, start: &str, stop: &str) -> Option<(String, String)> {
|
||||||
|
|
||||||
|
let s1 = match_string(i, start)?;
|
||||||
|
|
||||||
|
return take_alpha_till(&s1, stop);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn take_between_test() {
|
||||||
|
assert_eq!(take_between("\"wow\" etc", "\"", "\""), Some(("wow".to_string(), " etc".to_string())));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//// SECTION PARSING
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum SectionContent {
|
||||||
|
Code(Vec<Operation>),
|
||||||
|
CString(String),
|
||||||
|
CVec()
|
||||||
|
}
|
||||||
|
|
||||||
|
use SectionContent::*;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Section {
|
||||||
|
name: String,
|
||||||
|
content: SectionContent,
|
||||||
|
}
|
||||||
|
|
||||||
|
// A .section has a name and variable content.
|
||||||
|
impl Parser {
|
||||||
|
|
||||||
|
pub fn parse_sections(&mut self) -> Result<Vec<Section>, ParseError> {
|
||||||
|
let mut res = vec![];
|
||||||
|
|
||||||
|
let mut lines = self.input.iter().map(|x| x.as_str()).into_iter();
|
||||||
|
|
||||||
|
while let Some(l) = lines.next() {
|
||||||
|
println!("Examing line: {}", l);
|
||||||
|
if l.starts_with(".") {
|
||||||
|
let Some((kind, name)) = take_alpha_till(&l[1..], " ") else {
|
||||||
|
return Err(ParseError::BadSectionHeader);
|
||||||
|
};
|
||||||
|
|
||||||
|
match kind.as_str() {
|
||||||
|
"text" => {
|
||||||
|
let s : Vec<&str> = lines.clone().take_while(|&x| !(x).starts_with(".")).map(|x| x).collect();
|
||||||
|
res.push(Section { name: name.trim().to_owned(), content: Code(parse_code(&s)?)})
|
||||||
|
}
|
||||||
|
"asciiz" => {
|
||||||
|
let Some(s) = lines.next() else {return Err(ParseError::UnexpectedEOF)};
|
||||||
|
let Some((s, _)) = take_between(s.trim(), "\"", "\"") else {return Err(ParseError::BadSectionContent)};
|
||||||
|
res.push(Section { name: name.trim().to_owned(), content: CString(s)})
|
||||||
|
|
||||||
|
}
|
||||||
|
"i16" => {
|
||||||
|
let s = lines.next();
|
||||||
|
|
||||||
|
}
|
||||||
|
"u16" => {
|
||||||
|
let s = lines.next();
|
||||||
|
|
||||||
|
}
|
||||||
|
"vi16" => {
|
||||||
|
let s = lines.next();
|
||||||
|
|
||||||
|
}
|
||||||
|
"vu16" => {
|
||||||
|
let s = lines.next();
|
||||||
|
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(ParseError::UnknownSectionKind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return Ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn parse_code(i: &[&str]) -> Result<Vec<Operation>, ParseError> {
|
||||||
|
|
||||||
|
let mut res = vec![];
|
||||||
|
|
||||||
|
for line in i {
|
||||||
|
res.push(parse_code_line(line)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn parse_code_line(i: &str) -> Result<Operation, ParseError> {
|
||||||
|
|
||||||
|
// every operation has at most 3 arguments
|
||||||
|
let mut bits = i.split_whitespace();
|
||||||
|
println!("current parse code line: {}", i);
|
||||||
|
let Some(op) = bits.next() else {return Err(ParseError::BadSectionContent)};
|
||||||
|
|
||||||
|
// no type
|
||||||
|
match op {
|
||||||
|
"nop" => {return Ok(NOP);},
|
||||||
|
"halt" => {return Ok(HALT);},
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// I-type
|
||||||
|
let Some(r1) = bits.next() else {return Err(ParseError::BadSectionHeader)};
|
||||||
|
let Some(r2) = bits.next() else {return Err(ParseError::BadSectionHeader)};
|
||||||
|
|
||||||
|
match op {
|
||||||
|
"addi" => {
|
||||||
|
return Ok(ADDI(r1.to_owned(), parse_const(r2)?));
|
||||||
|
}
|
||||||
|
"sli" => {
|
||||||
|
return Ok(SLI(r1.to_owned(), parse_const(r2)?));
|
||||||
|
|
||||||
|
}
|
||||||
|
"call" => {
|
||||||
|
|
||||||
|
return Ok(CALL(r1.to_owned(), parse_const(r2)?));
|
||||||
|
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return Err(ParseError::BadInstruction);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_const(i: &str) -> Result<Const, ParseError> {
|
||||||
|
|
||||||
|
// we try to parse the number, if we fail, we treat it as a string.
|
||||||
|
let Ok(num) = i.parse() else {
|
||||||
|
return Ok(Const::CS(i.to_owned()));
|
||||||
|
};
|
||||||
|
return Ok(Const::C(num));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
16
src/assembler/tests.rs
Normal file
16
src/assembler/tests.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// use super::*;
|
||||||
|
|
||||||
|
use crate::assembler::parser;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parser_test() {
|
||||||
|
|
||||||
|
println!("Parser test begins");
|
||||||
|
let code = std::fs::read_to_string("./tests/assembly/hello_world.grasm").unwrap();
|
||||||
|
|
||||||
|
let mut parser = parser::Parser::new(code);
|
||||||
|
|
||||||
|
let r = parser.parse_sections().unwrap();
|
||||||
|
|
||||||
|
println!("Parsed sections: {:?}", r);
|
||||||
|
}
|
60
src/cpu/decoder.rs
Normal file
60
src/cpu/decoder.rs
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
use super::registers::Register;
|
||||||
|
|
||||||
|
type Constant = i8; // 8 bits max, so it works.
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum OP {
|
||||||
|
NOP,
|
||||||
|
ADD(Register, Register, Register),
|
||||||
|
SUB(Register, Register, Register),
|
||||||
|
AND(Register, Register, Register),
|
||||||
|
XOR(Register, Register, Register),
|
||||||
|
SLL(Register, Register, Register),
|
||||||
|
|
||||||
|
SLI(Register, Constant),
|
||||||
|
ADDI(Register, Constant),
|
||||||
|
|
||||||
|
BEQ(Register, Register, Register),
|
||||||
|
BGT(Register, Register, Register),
|
||||||
|
JAL(Register, Register, Constant),
|
||||||
|
|
||||||
|
LOAD(Register, Register, Register),
|
||||||
|
STORE(Register, Register, Register),
|
||||||
|
CALL(Register, Constant),
|
||||||
|
HALT,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use OP::*;
|
||||||
|
|
||||||
|
pub fn decode(op: u16) -> OP {
|
||||||
|
let opcode = op >> 12;
|
||||||
|
let dest = ((op & 0x0F00) >> 8) as Register;
|
||||||
|
let r1 = ((op & 0x00F0) >> 4) as Register;
|
||||||
|
let r2 = (op & 0x000F) as Register;
|
||||||
|
|
||||||
|
let c = Constant::from_be_bytes([(op & 0x00FF) as u8]);
|
||||||
|
let c4 = Constant::from_be_bytes([(op & 0x000F) as u8]);
|
||||||
|
|
||||||
|
println!("opcode: {}", opcode);
|
||||||
|
|
||||||
|
return match opcode {
|
||||||
|
// todo: write a macro for every type (I-type, R-type)
|
||||||
|
0b0000 => NOP,
|
||||||
|
0b0001 => ADD(dest, r1, r2),
|
||||||
|
0b0010 => SUB(dest, r1, r2),
|
||||||
|
0b0011 => AND(dest, r1, r2),
|
||||||
|
0b0100 => XOR(dest, r1, r2),
|
||||||
|
0b0101 => SLL(dest, r1, r2),
|
||||||
|
0b0110 => SLI(dest, c),
|
||||||
|
0b0111 => ADDI(dest, c),
|
||||||
|
0b1000 => BEQ(dest, r1, r2),
|
||||||
|
0b1001 => BGT(dest, r1, r2),
|
||||||
|
0b1010 => JAL(dest, r1, c4),
|
||||||
|
0b1011 => todo!(),
|
||||||
|
0b1100 => LOAD(dest, r1, r2),
|
||||||
|
0b1101 => STORE(dest, r1, r2),
|
||||||
|
0b1110 => CALL(dest, c),
|
||||||
|
0b1111 => HALT,
|
||||||
|
_ => panic!("Not an operation."),
|
||||||
|
};
|
||||||
|
}
|
187
src/cpu/mod.rs
Normal file
187
src/cpu/mod.rs
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
mod decoder;
|
||||||
|
mod ram;
|
||||||
|
mod registers;
|
||||||
|
mod sysenv;
|
||||||
|
mod tests;
|
||||||
|
pub use sysenv::*;
|
||||||
|
|
||||||
|
pub use registers::*;
|
||||||
|
|
||||||
|
use decoder::OP;
|
||||||
|
use ram::Ram;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ExecErr {
|
||||||
|
InvalidRegister,
|
||||||
|
InvalidMemoryAddr,
|
||||||
|
InvalidSyscall,
|
||||||
|
InvalidPC,
|
||||||
|
SyscallError(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
use ExecErr::*;
|
||||||
|
|
||||||
|
use crate::{interpret_as_signed, interpret_as_unsigned};
|
||||||
|
|
||||||
|
use self::decoder::decode;
|
||||||
|
|
||||||
|
/// Simple synonim for Result<T, ExecErr>.
|
||||||
|
type CPUResult<T> = Result<T, ExecErr>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// The state of the interpreter.
|
||||||
|
pub struct CPU<'a, T> {
|
||||||
|
pub regs: Registers,
|
||||||
|
pub ram: Ram,
|
||||||
|
pub env: &'a mut T,
|
||||||
|
// should execution be halted? not sure if to include this or nah
|
||||||
|
halt: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> CPU<'a, T>
|
||||||
|
where
|
||||||
|
T: Sys,
|
||||||
|
{
|
||||||
|
pub fn execute_op(&mut self, op: OP) -> CPUResult<()> {
|
||||||
|
match op {
|
||||||
|
OP::NOP => {
|
||||||
|
self.regs.pc += 1;
|
||||||
|
}
|
||||||
|
OP::ADD(d, r1, r2) => {
|
||||||
|
let v1 = self.regs.get(r1).ok_or(InvalidRegister)?;
|
||||||
|
let v2 = self.regs.get(r2).ok_or(InvalidRegister)?;
|
||||||
|
self.regs.write(d, v1 + v2).ok_or(InvalidRegister)?;
|
||||||
|
|
||||||
|
self.regs.pc += 1;
|
||||||
|
}
|
||||||
|
OP::SUB(d, r1, r2) => {
|
||||||
|
let v1 = self.regs.get(r1).ok_or(InvalidRegister)?;
|
||||||
|
let v2 = self.regs.get(r2).ok_or(InvalidRegister)?;
|
||||||
|
self.regs.write(d, v1 - v2).ok_or(InvalidRegister)?;
|
||||||
|
|
||||||
|
self.regs.pc += 1;
|
||||||
|
}
|
||||||
|
OP::AND(d, r1, r2) => {
|
||||||
|
let v1 = self.regs.get(r1).ok_or(InvalidRegister)?;
|
||||||
|
let v2 = self.regs.get(r2).ok_or(InvalidRegister)?;
|
||||||
|
self.regs.write(d, v1 & v2).ok_or(InvalidRegister)?;
|
||||||
|
|
||||||
|
self.regs.pc += 1;
|
||||||
|
}
|
||||||
|
OP::XOR(d, r1, r2) => {
|
||||||
|
let v1 = self.regs.get(r1).ok_or(InvalidRegister)?;
|
||||||
|
let v2 = self.regs.get(r2).ok_or(InvalidRegister)?;
|
||||||
|
self.regs.write(d, v1 ^ v2).ok_or(InvalidRegister)?;
|
||||||
|
|
||||||
|
self.regs.pc += 1;
|
||||||
|
}
|
||||||
|
OP::SLL(d, r1, r2) => {
|
||||||
|
let v1 = self.regs.get(r1).ok_or(InvalidRegister)?;
|
||||||
|
let v2 = self.regs.get(r2).ok_or(InvalidRegister)?;
|
||||||
|
self.regs.write(d, v1 << v2).ok_or(InvalidRegister)?;
|
||||||
|
|
||||||
|
self.regs.pc += 1;
|
||||||
|
}
|
||||||
|
OP::SLI(d, c) => {
|
||||||
|
let v1 = self.regs.get(d).ok_or(InvalidRegister)?;
|
||||||
|
self.regs.write(d, v1 << c).ok_or(InvalidRegister)?;
|
||||||
|
|
||||||
|
self.regs.pc += 1;
|
||||||
|
}
|
||||||
|
OP::ADDI(d, c) => {
|
||||||
|
let v1 = self.regs.get(d).ok_or(InvalidRegister)?;
|
||||||
|
self.regs
|
||||||
|
.write(
|
||||||
|
d,
|
||||||
|
interpret_as_unsigned(interpret_as_signed(v1) + (c as i16)),
|
||||||
|
)
|
||||||
|
.ok_or(InvalidRegister)?;
|
||||||
|
|
||||||
|
self.regs.pc += 1;
|
||||||
|
}
|
||||||
|
OP::BEQ(d, x0, x1) => {
|
||||||
|
if x0 == x1 {
|
||||||
|
let v = self.regs.get(d).ok_or(InvalidRegister)?;
|
||||||
|
self.regs.pc = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OP::BGT(d, x0, x1) => {
|
||||||
|
if x0 > x1 {
|
||||||
|
let v = self.regs.get(d).ok_or(InvalidRegister)?;
|
||||||
|
self.regs.pc = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OP::JAL(s0, s1, c) => {
|
||||||
|
self.regs
|
||||||
|
.write(s0, self.regs.pc + 1)
|
||||||
|
.ok_or(InvalidRegister)?;
|
||||||
|
let v = self.regs.get(s1).ok_or(InvalidRegister)?;
|
||||||
|
self.regs.pc = (v as i16 + (c as i16)) as Word;
|
||||||
|
}
|
||||||
|
|
||||||
|
OP::LOAD(d, s1, s2) => {
|
||||||
|
let start = self.regs.get(s1).ok_or(InvalidRegister)?;
|
||||||
|
let offset = self.regs.get(s2).ok_or(InvalidRegister)?;
|
||||||
|
|
||||||
|
let v = self.ram.get(start + offset).ok_or(InvalidMemoryAddr)?;
|
||||||
|
|
||||||
|
self.regs.write(d, v).ok_or(InvalidRegister)?;
|
||||||
|
|
||||||
|
self.regs.pc += 1;
|
||||||
|
}
|
||||||
|
OP::STORE(d, s1, s2) => {
|
||||||
|
let start = self.regs.get(s1).ok_or(InvalidRegister)?;
|
||||||
|
let offset = self.regs.get(s2).ok_or(InvalidRegister)?;
|
||||||
|
|
||||||
|
let v = self.regs.get(d).ok_or(InvalidRegister)?;
|
||||||
|
self.ram.write(start + offset, v).ok_or(InvalidMemoryAddr)?;
|
||||||
|
|
||||||
|
self.regs.pc += 1;
|
||||||
|
}
|
||||||
|
OP::CALL(r, c) => {
|
||||||
|
T::call(self, r.into(), c as u16)?;
|
||||||
|
self.regs.pc += 1;
|
||||||
|
}
|
||||||
|
OP::HALT => {
|
||||||
|
self.halt = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch(&self) -> CPUResult<OP> {
|
||||||
|
let binop = self.ram.get(self.regs.pc).ok_or(ExecErr::InvalidPC)?;
|
||||||
|
|
||||||
|
println!("binop: {:#018b}", binop);
|
||||||
|
|
||||||
|
Ok(decode(binop))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn step(&mut self) -> CPUResult<()> {
|
||||||
|
let op = self.fetch()?;
|
||||||
|
println!("fetched op: {:?}, pc: {} ", op, self.regs.pc);
|
||||||
|
self.execute_op(op)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_code_raw(&mut self, bin_code: &[Word]) -> CPUResult<()> {
|
||||||
|
self.halt = false;
|
||||||
|
// put the code in memory:
|
||||||
|
self.ram.write_array(bin_code, 0);
|
||||||
|
|
||||||
|
while !self.halt {
|
||||||
|
self.step()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(env: &'a mut T) -> Self {
|
||||||
|
CPU {
|
||||||
|
regs: Registers::default(),
|
||||||
|
ram: Ram::default(),
|
||||||
|
env,
|
||||||
|
halt: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
66
src/cpu/ram.rs
Normal file
66
src/cpu/ram.rs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
use crate::cpu::registers::Word;
|
||||||
|
|
||||||
|
/// We'll define our RAM as a static array.
|
||||||
|
/// The maximum adressable memory is, right now, just 65kbit of memory.
|
||||||
|
|
||||||
|
// pub const MAX_MEM: usize = 65536;
|
||||||
|
pub const MAX_MEM: usize = 40;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Ram {
|
||||||
|
mem: [Word; MAX_MEM],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Ram {
|
||||||
|
fn default() -> Self {
|
||||||
|
return Ram { mem: [0; MAX_MEM] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ram {
|
||||||
|
/// Gets the word at memory address i. Returns none if i is
|
||||||
|
/// out of bounds.
|
||||||
|
pub fn get(&self, i: Word) -> Option<Word> {
|
||||||
|
if (i as usize) < MAX_MEM {
|
||||||
|
return Some(self.mem[i as usize]);
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes val into memory address i. Returns none if i is
|
||||||
|
/// out of bounds.
|
||||||
|
pub fn write(&mut self, i: Word, val: Word) -> Option<()> {
|
||||||
|
if (i as usize) < MAX_MEM {
|
||||||
|
self.mem[i as usize] = val;
|
||||||
|
return Some(());
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a slice of memory from start to end address, inclusive.
|
||||||
|
/// None is returned if the address is out of bounds.
|
||||||
|
pub fn slice(&self, start: Word, end: Word) -> Option<&[Word]> {
|
||||||
|
if (start as usize) < MAX_MEM && (end as usize) < MAX_MEM {
|
||||||
|
return Some(&self.mem[(start as usize)..(end as usize)]);
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes an array of data directly into memory, starting from
|
||||||
|
/// the "start" address.
|
||||||
|
/// Returns None if the data exceeds memory.
|
||||||
|
pub fn write_array(&mut self, data: &[Word], start: Word) -> Option<()> {
|
||||||
|
if start as usize + data.len() < MAX_MEM {
|
||||||
|
for i in 0..data.len() {
|
||||||
|
self.mem[start as usize + i] = data[i];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(());
|
||||||
|
}
|
||||||
|
}
|
85
src/cpu/registers.rs
Normal file
85
src/cpu/registers.rs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
pub type Word = u16;
|
||||||
|
pub type Register = u8;
|
||||||
|
|
||||||
|
/// We need to hold 15 registers (zero is constant) + the program counter.
|
||||||
|
/// We'll just use a vector of u16.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Registers {
|
||||||
|
regs: [Word; 15],
|
||||||
|
pub pc: Word,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Registers {
|
||||||
|
fn default() -> Self {
|
||||||
|
Registers {
|
||||||
|
regs: [0; 15],
|
||||||
|
pc: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Registers {
|
||||||
|
/// retrives the register's value. Returns None if trying to access a register
|
||||||
|
/// that doesn't exist.
|
||||||
|
pub fn get(&self, i: Register) -> Option<Word> {
|
||||||
|
match i {
|
||||||
|
0 => Some(0), // zero is always 0
|
||||||
|
1..=15 => Some(self.regs[(i - 1) as usize]),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// writes val to the register i. Returns none on trying to write to zero, or to a register
|
||||||
|
/// that doesn't exist.
|
||||||
|
pub fn write(&mut self, i: Register, val: Word) -> Option<()> {
|
||||||
|
match i {
|
||||||
|
0 => None, // cannot write to 0
|
||||||
|
1..=15 => {
|
||||||
|
self.regs[(i - 1) as usize] = val;
|
||||||
|
return Some(());
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ASSOCS: &'static [(Register, &'static str)] = &[
|
||||||
|
(0, "zero"),
|
||||||
|
(1, "ra"),
|
||||||
|
(2, "sp"),
|
||||||
|
(3, "t0"),
|
||||||
|
(4, "t1"),
|
||||||
|
(5, "t2"),
|
||||||
|
(6, "t3"),
|
||||||
|
(7, "a0"),
|
||||||
|
(8, "a1"),
|
||||||
|
(9, "a2"),
|
||||||
|
(10, "a3"),
|
||||||
|
(11, "s0"),
|
||||||
|
(12, "s1"),
|
||||||
|
(13, "s2"),
|
||||||
|
(14, "s3"),
|
||||||
|
(15, "t4"),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// gets the register memonic name. Useful for pretty printing. (11 -> s0)
|
||||||
|
pub fn get_memo(i: Register) -> Option<&'static str> {
|
||||||
|
for (a, b) in ASSOCS {
|
||||||
|
if i == *a {
|
||||||
|
return Some(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// gets the register index from its memonic name (s0 -> 11)
|
||||||
|
pub fn get_num(s: &str) -> Option<Register> {
|
||||||
|
for (a, b) in ASSOCS {
|
||||||
|
if s == *b {
|
||||||
|
return Some(*a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return None;
|
||||||
|
}
|
82
src/cpu/sysenv/io_vec.rs
Normal file
82
src/cpu/sysenv/io_vec.rs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
use std::io::stdin;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
cpu::{ram::MAX_MEM, registers::Register},
|
||||||
|
loader::{loader::find_and_read_string, unloader::make_string},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// first working environment, we get input from stdin and we write output
|
||||||
|
// to a string.
|
||||||
|
//
|
||||||
|
|
||||||
|
// using strings to singal errors kinda sucks.
|
||||||
|
// TODO: Fix this
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct IOBuffer {
|
||||||
|
pub output: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sys for IOBuffer {
|
||||||
|
fn call(cpu: &mut CPU<IOBuffer>, r: Register, c: Word) -> CPUResult<()> {
|
||||||
|
println!("called: {}", c);
|
||||||
|
match c {
|
||||||
|
// 0: write an integer to output
|
||||||
|
0 => {
|
||||||
|
let i = cpu.regs.get(r).ok_or(ExecErr::InvalidRegister)?;
|
||||||
|
cpu.env.output.push_str(&format!("{}", i));
|
||||||
|
}
|
||||||
|
// 1: read an integer to some register
|
||||||
|
1 => {
|
||||||
|
let mut buf = String::new();
|
||||||
|
stdin()
|
||||||
|
.read_line(&mut buf)
|
||||||
|
.map_err(|_| ExecErr::SyscallError("Cannot read stdin".to_owned()))?;
|
||||||
|
let n: Word = buf
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| ExecErr::SyscallError("Cannot read number".to_owned()))?;
|
||||||
|
cpu.regs.write(r, n).ok_or(ExecErr::InvalidRegister)?;
|
||||||
|
}
|
||||||
|
// 2: reads a string from input and writes it to some location.
|
||||||
|
2 => {
|
||||||
|
let mut buf = String::new();
|
||||||
|
stdin()
|
||||||
|
.read_line(&mut buf)
|
||||||
|
.map_err(|_| ExecErr::SyscallError("Cannot read stdin".to_owned()))?;
|
||||||
|
|
||||||
|
let s: Vec<u16> = make_string(&buf);
|
||||||
|
|
||||||
|
let start = cpu.regs.get(r).ok_or(ExecErr::InvalidRegister)?;
|
||||||
|
|
||||||
|
cpu.ram
|
||||||
|
.write_array(&s[..], start)
|
||||||
|
.ok_or(ExecErr::SyscallError("Cannot write slice".to_owned()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3: prints a string, reading it from memory.
|
||||||
|
// r must contain the address of the string.
|
||||||
|
// the string needs to be null-delimited.
|
||||||
|
3 => {
|
||||||
|
let pos = cpu.regs.get(r).ok_or(ExecErr::InvalidRegister)?;
|
||||||
|
|
||||||
|
// we slice from start to the end.
|
||||||
|
// why? good question. The find_and_read_string
|
||||||
|
// will short circuit as soon as it finds a null terminator,
|
||||||
|
// which might <potentially> never be found.
|
||||||
|
let data = cpu
|
||||||
|
.ram
|
||||||
|
.slice(pos, MAX_MEM as Word - 1)
|
||||||
|
.ok_or(ExecErr::InvalidMemoryAddr)?;
|
||||||
|
let (s, _) = find_and_read_string(&data)
|
||||||
|
.map_err(|p| ExecErr::SyscallError("parse error!".to_owned()))?;
|
||||||
|
cpu.env.output.push_str(&s);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => return Err(ExecErr::InvalidSyscall),
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
17
src/cpu/sysenv/mod.rs
Normal file
17
src/cpu/sysenv/mod.rs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
mod io_vec;
|
||||||
|
|
||||||
|
pub use io_vec::IOBuffer;
|
||||||
|
|
||||||
|
use super::{CPUResult, ExecErr, CPU};
|
||||||
|
|
||||||
|
use super::registers::{Register, Word};
|
||||||
|
|
||||||
|
/// This trait represents all environments where our CPU can operate.
|
||||||
|
/// What this means is roughly defining system calls.
|
||||||
|
|
||||||
|
pub trait Sys: Sized {
|
||||||
|
// r should actually be 4 bits, while c should be
|
||||||
|
// 8 bits. TODO: more efficient packing?
|
||||||
|
/// Performs system call.
|
||||||
|
fn call(cpu: &mut CPU<Self>, r: Register, c: Word) -> CPUResult<()>;
|
||||||
|
}
|
35
src/cpu/tests.rs
Normal file
35
src/cpu/tests.rs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use crate::loader::unloader::*;
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hello_world_binary_test() {
|
||||||
|
|
||||||
|
let hw = String::from("Hello world!");
|
||||||
|
|
||||||
|
let mut k = make_string(&hw);
|
||||||
|
|
||||||
|
let mut code: Vec<u16> = vec![
|
||||||
|
0b0111000100000011, // addi ra 3
|
||||||
|
0b1110000100000011, // ecall ra 3
|
||||||
|
0b1111000000000000, // HALT.
|
||||||
|
];
|
||||||
|
|
||||||
|
code.append(&mut k);
|
||||||
|
|
||||||
|
let mut env = IOBuffer::default();
|
||||||
|
|
||||||
|
let mut cpu = CPU::new(&mut env);
|
||||||
|
|
||||||
|
for c in &code[..] {
|
||||||
|
println!("{:#018b}", c);
|
||||||
|
}
|
||||||
|
|
||||||
|
cpu.run_code_raw(&code);
|
||||||
|
|
||||||
|
assert_eq!(hw, cpu.env.output);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
1
src/jit/mod.rs
Normal file
1
src/jit/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
23
src/lib.rs
Normal file
23
src/lib.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
use std::mem::transmute;
|
||||||
|
|
||||||
|
pub mod cpu;
|
||||||
|
pub mod jit;
|
||||||
|
pub mod loader;
|
||||||
|
// pub mod ;
|
||||||
|
pub mod assembler;
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
pub fn interpret_as_signed(x: u16) -> i16 {
|
||||||
|
// the two types have the same size.
|
||||||
|
unsafe {
|
||||||
|
return transmute::<u16, i16>(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interpret_as_unsigned(x: i16) -> u16 {
|
||||||
|
// the two types have the same size.
|
||||||
|
unsafe {
|
||||||
|
return transmute::<i16, u16>(x);
|
||||||
|
}
|
||||||
|
}
|
1
src/loader/constants.rs
Normal file
1
src/loader/constants.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub const MAGIC: u16 = 39979;
|
1
src/loader/display.rs
Normal file
1
src/loader/display.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
100
src/loader/loader.rs
Normal file
100
src/loader/loader.rs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
use crate::cpu::Word;
|
||||||
|
|
||||||
|
use super::{constants::MAGIC, Section};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ParseError {
|
||||||
|
EmptyHeader,
|
||||||
|
MagicNumberCheckFail,
|
||||||
|
UnexpectedHeaderEnd,
|
||||||
|
UnexpectedFileEnd,
|
||||||
|
Utf8ConvError,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a requence of u16, and returns a rust utf8 owned string
|
||||||
|
/// and the index of the next 16bit word that follows the string,
|
||||||
|
/// so the first 16 bit word after the null delimiter.
|
||||||
|
/// It splits every u16 into two chunks of 8 bits, reads until
|
||||||
|
/// it finds the first empty u8 and attempts to convert the u8 array to
|
||||||
|
/// a rust UTF8 String.
|
||||||
|
pub fn find_and_read_string(s: &[u16]) -> Result<(String, usize), ParseError> {
|
||||||
|
let mut bytes = vec![];
|
||||||
|
|
||||||
|
let mut index: usize = 0;
|
||||||
|
for b in s.iter() {
|
||||||
|
let x0 = (*b & 0xFF00) >> 8;
|
||||||
|
let x1 = *b & 0x00FF;
|
||||||
|
// exit when the first 0 bit is found.
|
||||||
|
if x0 == 0 {
|
||||||
|
index += 1;
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
bytes.push(x0 as u8);
|
||||||
|
if x1 == 0 {
|
||||||
|
index += 1;
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
bytes.push(x1 as u8);
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let s = String::from_utf8(bytes).map_err(|_| ParseError::Utf8ConvError)?;
|
||||||
|
|
||||||
|
return Ok((s, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes a binary file and returns a list of sections
|
||||||
|
pub fn read_binary(b: &[u16]) -> Result<Vec<Section>, ParseError> {
|
||||||
|
let mut res = vec![];
|
||||||
|
|
||||||
|
let headers = parse_header(b)?;
|
||||||
|
let hlen = headers.len() * 2 + 2; // two 16bits words for every entry,
|
||||||
|
// and 2 etxra 16 bits number at the start
|
||||||
|
|
||||||
|
for (offset, length) in headers {
|
||||||
|
// section start. The name begins here
|
||||||
|
let start = hlen + offset as usize;
|
||||||
|
let str_buffer = b.get(start..).ok_or(ParseError::UnexpectedHeaderEnd)?;
|
||||||
|
let (name, i) = find_and_read_string(str_buffer)?;
|
||||||
|
|
||||||
|
let c_start = start + i;
|
||||||
|
let c_end = start + (length as usize) + i;
|
||||||
|
|
||||||
|
println!("{:?}, start: {}, end: {}", b, c_start, c_end);
|
||||||
|
let Some(content) = b.get((c_start)..(c_end)) else {return Err(ParseError::UnexpectedFileEnd)};
|
||||||
|
|
||||||
|
res.push(Section::new(name, content))
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses binary headers
|
||||||
|
fn parse_header(b: &[Word]) -> Result<Vec<(u16, u16)>, ParseError> {
|
||||||
|
let Some([m, s]) = b.get(0..2) else {return Err(ParseError::EmptyHeader)};
|
||||||
|
|
||||||
|
// Magic number check. Can go unchecked, check spec.
|
||||||
|
// if (*m != MAGIC) {
|
||||||
|
// return Err(WrongMagicNum)
|
||||||
|
// };
|
||||||
|
|
||||||
|
// s is the number of pairs (offset, length) in our header.
|
||||||
|
// since we're counting pairs, we need s*2 numbers from input.
|
||||||
|
|
||||||
|
let Some(headerdata) = b.get(2..((*s as usize * 2) + 2)) else {return Err(ParseError::UnexpectedHeaderEnd)};
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
headerdata.len() % 2 == 0,
|
||||||
|
"Header does not have an even number of words"
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut hd = headerdata.iter();
|
||||||
|
|
||||||
|
let mut res = vec![];
|
||||||
|
|
||||||
|
while let (Some(offset), Some(len)) = (hd.next(), hd.next()) {
|
||||||
|
res.push((*offset, *len))
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
}
|
23
src/loader/mod.rs
Normal file
23
src/loader/mod.rs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
mod display;
|
||||||
|
pub mod loader;
|
||||||
|
pub mod unloader;
|
||||||
|
|
||||||
|
mod constants;
|
||||||
|
|
||||||
|
mod tests;
|
||||||
|
|
||||||
|
/// Represents a section, or symbol, that must end up in the
|
||||||
|
/// binary file.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Section<'a> {
|
||||||
|
/// Name of the symbol/section
|
||||||
|
name: String,
|
||||||
|
/// Content in bytes
|
||||||
|
content: &'a [u16],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Section<'_> {
|
||||||
|
pub fn new<'a>(name: String, content: &'a [u16]) -> Section<'a> {
|
||||||
|
Section { name, content }
|
||||||
|
}
|
||||||
|
}
|
61
src/loader/tests.rs
Normal file
61
src/loader/tests.rs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
use super::loader::*;
|
||||||
|
use super::unloader::*;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// fuzzable, TODO
|
||||||
|
fn write_read_str_identity(s: &str) {
|
||||||
|
let mut bytes = make_string(s);
|
||||||
|
|
||||||
|
// pop null-terminator;
|
||||||
|
// bytes.pop().expect("String doesn't even have a single byte?");
|
||||||
|
// println!("w-r s: {:?}, b: {:?}", s, bytes);
|
||||||
|
let (s0, _) = find_and_read_string(&bytes).unwrap();
|
||||||
|
assert_eq!(s, &s0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_write_str_identity(b: Vec<u16>) {
|
||||||
|
let (string, _) = find_and_read_string(&b).unwrap();
|
||||||
|
let mut n_term = b.clone();
|
||||||
|
// n_term.push(0);
|
||||||
|
// println!("r-w s: {:?}, b: {:?}", string, n_term);
|
||||||
|
assert_eq!(n_term, make_string(&string));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn label_parse_identity() {
|
||||||
|
let testwords = vec!["Hello,", "main", "è", "Hello,,", "v", "main", "pi"]; // should support utf-8
|
||||||
|
//
|
||||||
|
for word in testwords {
|
||||||
|
println!("\nTEST {}\n", word);
|
||||||
|
|
||||||
|
write_read_str_identity(word);
|
||||||
|
let bytes = make_string(word);
|
||||||
|
// bytes.pop();
|
||||||
|
println!(
|
||||||
|
"word: {:?}, bytes: {:?}, length: {:?}",
|
||||||
|
word,
|
||||||
|
bytes,
|
||||||
|
bytes.len()
|
||||||
|
);
|
||||||
|
read_write_str_identity(bytes);
|
||||||
|
println!("Done with {:?}", word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Symbol table test
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sy_test() {
|
||||||
|
let fake_symbol_table: Vec<Section> = vec![
|
||||||
|
Section::new("v".to_owned(), &[1, 2, 3]),
|
||||||
|
Section::new("pi".to_owned(), &[3]),
|
||||||
|
Section::new("main".to_owned(), &[231, 323, 433]), // Section { name: todo!(), content: todo!() },
|
||||||
|
// Section { name: todo!(), content: todo!() }
|
||||||
|
];
|
||||||
|
|
||||||
|
let bin = make_binary(&fake_symbol_table);
|
||||||
|
println!("{:?}", bin);
|
||||||
|
|
||||||
|
let parsed_symbol_table = read_binary(&bin).expect("Wtf! We got error!");
|
||||||
|
println!("{:?}", parsed_symbol_table);
|
||||||
|
}
|
115
src/loader/unloader.rs
Normal file
115
src/loader/unloader.rs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
use super::constants::MAGIC;
|
||||||
|
|
||||||
|
use super::Section;
|
||||||
|
|
||||||
|
impl Section<'_> {
|
||||||
|
/// Converts the entry's name to utf8 packed in bits of length 16.
|
||||||
|
fn serialize_name(&self) -> Vec<u16> {
|
||||||
|
return make_string(&self.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Joins the name of the section followed by its contents in a
|
||||||
|
/// vector of 16 bits.
|
||||||
|
fn serialize(&self) -> Vec<u16> {
|
||||||
|
let mut tmp = make_string(&self.name);
|
||||||
|
tmp.append(&mut self.content.to_owned());
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Entry in the symbols table.
|
||||||
|
/// Consists of an offset and a lenght.
|
||||||
|
struct STEntry {
|
||||||
|
offset: u16,
|
||||||
|
length: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes a string, and creates a u16, null-terminated utf8 string,
|
||||||
|
/// in a vector of u16 (padding possible)
|
||||||
|
pub fn make_string(s: &str) -> Vec<u16> {
|
||||||
|
let raw_bytes: &[u8] = s.as_bytes();
|
||||||
|
|
||||||
|
let mut rb = raw_bytes.iter();
|
||||||
|
// raw_bytes must be converted to u16.
|
||||||
|
//
|
||||||
|
let mut bytes: Vec<u16> = {
|
||||||
|
let mut res = vec![];
|
||||||
|
// highly cursed: depends on the order in which the arguments of a tuple are
|
||||||
|
// evaluated. Does its job!
|
||||||
|
while let (Some(word0), Some(word1)) = (rb.next(), rb.next()) {
|
||||||
|
// println!("Pair: {}, {}, word: {:?}", word0, word1, raw_bytes);
|
||||||
|
res.push(((*word0 as u16) << 8) + (*word1 as u16));
|
||||||
|
}
|
||||||
|
// if we branch into this else, either there's a single word left or zero.
|
||||||
|
// since, in case a single word was left, the first rb.next() call in the line
|
||||||
|
// above would've consumed it, we have to gather that last element again
|
||||||
|
match raw_bytes.len() {
|
||||||
|
0 => res,
|
||||||
|
n => {
|
||||||
|
if n % 2 != 0 {
|
||||||
|
// if there's an uneven number of chunks of 8 bits,
|
||||||
|
// we introduce padding!
|
||||||
|
// println!("Adding last one too");
|
||||||
|
res.push((*raw_bytes.last().unwrap() as u16) << 8);
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// adding null termination byte
|
||||||
|
bytes.push(0);
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes a list of Section struct to be inserted in the symbols table and returns
|
||||||
|
/// both the table and the inserted data, with offsets
|
||||||
|
fn conv(sy_table: &[Section]) -> (Vec<STEntry>, Vec<u16>) {
|
||||||
|
let mut current_offset: u16 = 0;
|
||||||
|
let mut entries: Vec<STEntry> = vec![];
|
||||||
|
let mut content: Vec<u16> = vec![];
|
||||||
|
|
||||||
|
for entry in sy_table {
|
||||||
|
let mut binary_entry = entry.serialize();
|
||||||
|
|
||||||
|
// Add name + content to the whole file
|
||||||
|
content.extend_from_slice(&mut binary_entry);
|
||||||
|
|
||||||
|
// take note of the current offset and content lenght in the entry table
|
||||||
|
// (without including the length of the string)
|
||||||
|
entries.push(STEntry {
|
||||||
|
offset: current_offset,
|
||||||
|
length: entry.content.len() as u16,
|
||||||
|
});
|
||||||
|
|
||||||
|
// add to the current offset the length of the data we've saved:
|
||||||
|
current_offset += binary_entry.len() as u16;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (entries, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes a list of STentry and serializes them
|
||||||
|
fn make_header(sy_table: &[STEntry]) -> Vec<u16> {
|
||||||
|
let mut res = vec![];
|
||||||
|
for entry in sy_table {
|
||||||
|
res.push(entry.offset);
|
||||||
|
res.push(entry.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes a list of Sections and returns a binary file.
|
||||||
|
pub fn make_binary(sections: &[Section]) -> Vec<u16> {
|
||||||
|
let (sy_table, mut data) = conv(sections);
|
||||||
|
|
||||||
|
let mut header = make_header(&sy_table);
|
||||||
|
|
||||||
|
let mut res = vec![MAGIC, sections.len() as u16];
|
||||||
|
res.append(&mut header);
|
||||||
|
res.append(&mut data);
|
||||||
|
res
|
||||||
|
}
|
31
src/main.rs
31
src/main.rs
@ -1,3 +1,32 @@
|
|||||||
|
use dekejit::cpu::IOBuffer;
|
||||||
|
use dekejit::cpu::CPU;
|
||||||
|
use dekejit::loader::unloader::*;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("Hello, world!");
|
let mut k = make_string("Hello world!");
|
||||||
|
|
||||||
|
let mut code: Vec<u16> = vec![
|
||||||
|
0b0111000100000011, // addi ra 3
|
||||||
|
0b1110000100000011, // ecall ra 3
|
||||||
|
0b1111000000000000, // HALT.
|
||||||
|
];
|
||||||
|
|
||||||
|
code.append(&mut k);
|
||||||
|
|
||||||
|
let mut env = IOBuffer::default();
|
||||||
|
|
||||||
|
let mut cpu = CPU::new(&mut env);
|
||||||
|
|
||||||
|
for c in &code[..] {
|
||||||
|
println!("{:#018b}", c);
|
||||||
|
}
|
||||||
|
|
||||||
|
match cpu.run_code_raw(&code) {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("Result: {}", env.output)
|
||||||
|
}
|
||||||
|
Err(e) => println!("Err: {:?}", e),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
11
tests/assembly/hello_world.grasm
Normal file
11
tests/assembly/hello_world.grasm
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
; Hello world program.
|
||||||
|
|
||||||
|
.asciiz World
|
||||||
|
"Hello world\n"
|
||||||
|
|
||||||
|
.text main
|
||||||
|
addi t0 World ; load World's address into t0
|
||||||
|
call t0 3 ; print string syscall
|
||||||
|
|
||||||
|
.asciiz hey
|
||||||
|
"Hey dude\n"
|
1
tests/mod.rs
Normal file
1
tests/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
Loading…
Reference in New Issue
Block a user